Last active
June 21, 2020 11:13
-
-
Save berland/ec762e4c555a167953829aece46ec4a3 to your computer and use it in GitHub Desktop.
Calls a Hunter Douglas PowerView API to mode shades based on MQTT commands
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This code is released to the public domain. | |
import sys | |
import paho.mqtt.client as mqtt | |
import requests | |
import datetime | |
import time | |
import sys | |
mqtt_broker = "server" | |
mqtt_port = 1883 | |
hubname = "hdpowerview.foo.bar.com" | |
topics = ["powerview/sleepingroom/"] | |
shadeid = 63778 | |
commands = ["upper/set", "lower/set"] | |
states = ["upper/state", "lower/state"] | |
def on_connect(client, userdata, flags, rc): | |
print("mqtt2powerview: Connected to MQTT and hub: " + hubname) | |
sys.stdout.flush() | |
for topic in topics: | |
for command in commands: | |
client.subscribe(topic + command) | |
def on_message(client, userdata, msg): | |
print("mqtt2powerview: Got message " + str(msg.topic) + " " + str(msg.payload)) | |
sys.stdout.flush() | |
payload = int(float(msg.payload)) | |
if payload == 100: # 100 is interpreted as zero.. confusing | |
payload = 99 | |
try: | |
if "upper" in msg.topic and "set" in msg.topic: | |
position = int(payload / 100.0 * 65536) | |
move_curtain("upper", position) | |
if "lower" in msg.topic and "set" in msg.topic: | |
position = int(payload / 100.0 * 65536) | |
move_curtain("lower", position) | |
except ValueError: | |
print("mqtt2powerview: ERROR: Unable to parse payload") | |
sys.stdout.flush() | |
pass | |
lowerposition = 0 # Global state variable... | |
upperposition = 0 # global state | |
def move_curtain(curtain, position): | |
print( | |
"mqtt2powerview: " | |
+ str(datetime.datetime.now()) | |
+ "moving {} to pos {}".format(curtain, position) | |
) | |
sys.stdout.flush() | |
if curtain == "upper": | |
# this only works if we also supply the lower position.. | |
body = { | |
"shade": { | |
"id": shadeid, | |
"positions": { | |
"position1": lowerposition, | |
"posKind1": 1, | |
"position2": int(position), | |
"posKind2": 2, | |
}, | |
} | |
} | |
elif curtain == "lower": | |
body = { | |
"shade": { | |
"id": shadeid, | |
"positions": { | |
"position1": int(position), | |
"posKind1": 1, | |
"position2": upperposition, | |
"posKind2": 2, | |
}, | |
} | |
} | |
r = requests.put("http://" + hubname + "/api/shades/" + str(shadeid), json=body) | |
print("mqtt2powerview: " + str(r.json())) | |
sys.stdout.flush() | |
def on_disconnect(client, userdata, rc): | |
if rc != 0: | |
print("mqtt2powerview: Unexpected MQTT disconnection. Will auto-reconnect") | |
sys.stdout.flush() | |
client = mqtt.Client() | |
client.on_connect = on_connect | |
client.on_disconnect = on_disconnect | |
client.connect(mqtt_broker, mqtt_port) | |
client.on_message = on_message | |
counter = 0 | |
prematureloops = 0 | |
lastprematureloop = -1 | |
while True: | |
# Poll: | |
r = None | |
try: | |
# Allow only 3 seconds response time | |
r = requests.get("http://" + hubname + "/api/shades/" + str(shadeid), timeout=3) | |
except requests.exceptions.RequestException as e: | |
print( | |
"mqtt2powerview: Connection error to PowerView HUB, sleeping and retrying" | |
) | |
print(e) | |
sys.stdout.flush() | |
client.loop(20) # loop() is better than sleep, avoiding MQTT connection loss | |
if not r: | |
print("mqtt2powerview: Retrying http in 5 secs..") | |
sys.stdout.flush() | |
client.loop(5) | |
continue | |
if "shade" not in r.json(): | |
# Hub maybe not ready, wait and try again | |
print("mqtt2powerview: mqtt2powerview not able to get shade, wait and retry") | |
sys.stdout.flush() | |
client.loop(20) | |
continue | |
positions = r.json()["shade"]["positions"] | |
if "position1" in positions: ## lower | |
pos1 = r.json()["shade"]["positions"]["position1"] | |
if int(pos1) != lowerposition or counter % 100 == 0: | |
client.publish(topics[0] + states[1], str(int(pos1 / 65536.0 * 100))) | |
lowerposition = int(pos1) | |
if "position2" in positions: | |
pos2 = r.json()["shade"]["positions"]["position2"] | |
if int(pos2) != upperposition or counter % 100 == 0: | |
client.publish(topics[0] + states[0], str(int(pos2 / 65536.0 * 100))) | |
upperposition = int(pos2) | |
# print("mqtt2powerview: heartbeat.. " + str(counter)) | |
sys.stdout.flush() | |
start_looptime = time.time() | |
errorvalue = client.loop( | |
2 | |
) ## Denne kan finne på å returnere før 2 sek har gått, da bør vi restarte | |
looptime = time.time() - start_looptime | |
if counter > 5 and looptime < 1.5: # in seconds | |
prematureloops += 1 | |
print( | |
"MQTT loop() returned in only " | |
+ str(looptime) | |
+ " seconds, cumulative " | |
+ str(prematureloops) | |
+ ", error " | |
+ str(errorvalue) | |
) | |
lastprematureloop = counter | |
if prematureloops > 100: | |
print("More than 100 premature MQTT loops, restarting") | |
sys.exit(1) | |
# Reset prematureloops if they count occur too often | |
if ( | |
int(counter) - int(lastprematureloop) > 10 | |
): # a minute since it happened the last time | |
# print("Heartbeat, reset prematureloop, counter={}, lastprematureloop={}".format(counter, lastprematureloop)) | |
prematureloops = 0 | |
if not int(counter) % 10: | |
print( | |
"Heartbeat, counter={}, lastprematureloop={}".format( | |
counter, lastprematureloop | |
) | |
) | |
counter = counter + 1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment