Since i have moved to a new router (Asus ac86u) the connection to the Harmony Hub has become more stable. The plugin however is now locking up every few days. It becomes unresponsive and when i try to stop (disable) it,
It will stop, but after re-enabling it nothing happens (log says it started, but still unresponsive). Restarting Domoticz does restore functionality.
This is the latest version of plugin.py (some changes in the error handling, to improve the way connection problems are handled. However, that didn't fix this issue)
Code: Select all
# Domoticz Python Plugin alternative to the Logitech Harmony hardware
# Requires https://github.com/bwssytems/restful-harmony
#
# Author: ESCape
#
"""
<plugin key="restharmony" name="Logitech H-rmony restful plugin" author="ESCape" version="0.1.3">
<params>
<param field="Address" label="Restful-harmony Address" width="200px" required="true" default="localhost"/>
<param field="Port" label="Restful-harmony port" width="200px" required="true" default="8081"/>
<param field="Mode1" label="Check current activity every" width="200px" required="true">
<options>
<option label="5 seconds" value="5"/>
<option label="10 seconds" value="10" default="true"/>
<option label="30 seconds" value="30"/>
</options>
</param>
</params>
</plugin>
"""
import Domoticz
import json
class BasePlugin:
def __init__(self):
#some variables to reduce the number of reconnects and log entries on (common) Harmony connection issues
self.reconnectdelay = 1 #wait X poll cyclus (Heartbeats) before reconnecting
self.reconnectcountdown = self.reconnectdelay
self.reqwaiting = 0
self.maxreqwaiting = 5 #reset the connection if more than X requests have gone without response from restful Harmony
self.huberrorsallowed = 4 #only log an error every X times the plugin receives a 500 error from restful Harmony
self.huberrorcount = 0
self.errorstate = False
return
def setactivitystatus(self, activity):
Domoticz.Log("Activity has been changed to: " + Devices[self.harmonyidlist[activity]].Name)
Devices[self.harmonyidlist[activity]].Update(nValue=1, sValue="On")
for other in Devices:
if other != self.harmonyidlist[activity]:
if Devices[other].nValue == 1:
Devices[other].Update(nValue=0, sValue="Off")
def managedevices(self, actlist):
#Select or create icons for devices
icon="Restharmony"
if icon not in Images: Domoticz.Image('restharmony_icons.zip').Create()
iconid=Images[icon].ID
#Create device for every Activity
for actid in actlist:
if actid not in self.harmonyidlist:
Domoticz.Device(Name=actlist[actid], Unit=firstfree(), DeviceID=actid, TypeName="Switch", Used=1, Image=iconid).Create()
Domoticz.Log("Harmony activity " + actlist[actid] + " added.")
self.harmonyidlist=act2id()
#find and remove obsolete activities
obsolete=[]
for check in Devices:
if Devices[check].DeviceID not in actlist:
obsolete.append(check)
for trash in obsolete:
Domoticz.Log("Removing Harmony activity " + str(Devices[trash].Name) + " (obsolete).")
Devices[trash].Delete()
self.harmonyidlist=act2id()
def onStart(self):
self.restaddress=Parameters["Address"]
self.restport=Parameters["Port"]
Domoticz.Heartbeat(int(Parameters["Mode1"]))
self.harmonyidlist=act2id()
self.HubConn = Domoticz.Connection(Name="HarmonyHUB", Transport="TCP/IP", Protocol="HTTP", Address=Parameters["Address"], Port=Parameters["Port"])
self.HubConn.Connect()
def onStop(self):
Domoticz.Log("onStop called")
def onConnect(self, Connection, Status, Description):
Domoticz.Debug("Connection ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with status: "+Description)
if (Status == 0):
Domoticz.Debug("Harmony connected successfully.")
sendData = { 'Verb' : 'GET',
'URL' : '/harmony/list/activities',
'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
'Connection': 'keep-alive', \
'Cache-Control': 'max-age=0', \
'Upgrade-Insecure-Requests': '1', \
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', \
'Accept-Encoding': 'gzip, deflate', \
'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7', \
'User-Agent':'Domoticz/1.0' }
}
self.HubConn.Send(sendData)
self.reqwaiting += 1
else:
Domoticz.Error("Failed to connect ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Port"]+" with error: "+Description)
def onMessage(self, Connection, Data):
Domoticz.Debug("onMessage called for " + Connection.Name)
self.reqwaiting -= 1
if self.reqwaiting < 0:
Domoticz.Error("Unexepected response from Harmony. Resetting connection.")
self.HubConn.Disconnect()
Status = int(Data["Status"])
if (Status == 200):
self.huberrorcount = 0
if self.errorstate:
Domoticz.Log("Recovered from error. Received a usable response from restful Harmony.")
self.errorstate = False
else:
Domoticz.Debug("Good Response received from Harmony.")
if "Data" in Data:
strData = Data["Data"].decode("utf-8", "ignore")
Domoticz.Debug("rawdata:" + strData)
jsonData=json.loads(strData)
if "status" in jsonData:
Domoticz.Debug("Response from hub looks like activity switch confirmation")
if jsonData['status']=="OK":
Domoticz.Debug("Activity request from domoticz confirmed by restful harmony")
self.setactivitystatus(encodehubid(self.updateactivity))
else:
Domoticz.Error("Failed to set activity from domoticz")
elif "id" in jsonData:
Domoticz.Debug("Response from hub looks like current activity")
curact=int(jsonData["id"])
Domoticz.Debug("Current: " + str(curact))
Domoticz.Debug("Current domid: " + str(encodehubid(curact)))
if encodehubid(curact) in self.harmonyidlist:
if Devices[self.harmonyidlist[encodehubid(curact)]].nValue != 1:
self.setactivitystatus(encodehubid(curact))
else:
Domoticz.Error("Current Harmony activity unknown to Domoticz... Resetting plugin to update data.")
self.HubConn.Disconnect()
else:
Domoticz.Debug("Response from hub looks like list of activities")
myacts = {}
for act in jsonData:
myacts[encodehubid(act['id'])]= act["label"]
self.managedevices(myacts)
Domoticz.Log("Harmony Hub offering the following activities: " + str(myacts))
else:
Domoticz.Log("Resonse contains no data")
elif (Status == 500):
if self.huberrorcount >= self.huberrorsallowed:
Domoticz.Error("Harmony returned a status: "+str(Status)+" (restful harmony might not be able to connect to Harmony hub)")
self.huberrorcount = 0
self.errorstate = True
else:
self.huberrorcount += 1
else:
Domoticz.Error("Harmony returned a status: "+str(Status))
self.errorstate = True
def onCommand(self, Unit, Command, Level, Hue):
if str(Command)=='On':
harmonyact=decodehubid(int(Devices[Unit].DeviceID, 16))
command = {"activityid":harmonyact}
params = json.dumps(command)
Domoticz.Debug("Sending params:" + str(params))
sendData = { 'Verb' : 'PUT',
'URL' : '/harmony/start',
'Data' : params,
'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
'Connection': 'keep-alive', \
'Upgrade-Insecure-Requests': '1', \
'User-Agent':'Domoticz/1.0' }
}
self.HubConn.Send(sendData)
self.reqwaiting += 1
#store the command we just sent to update the corresponding switch immediately after confirmation is received
self.updateactivity = harmonyact
return
def onNotification(self, Name, Subject, Text, Status, Priority, Sound, ImageFile):
Domoticz.Log("Notification: " + Name + "," + Subject + "," + Text + "," + Status + "," + str(Priority) + "," + Sound + "," + ImageFile)
def onDisconnect(self, Connection):
Domoticz.Debug("onDisconnect called for " + Connection.Name)
self.reqwaiting = 0
def onHeartbeat(self):
Domoticz.Debug("Number of requests waiting for a response: " + str(self.reqwaiting))
if (self.HubConn.Connecting() or self.HubConn.Connected()):
if self.reqwaiting > self.maxreqwaiting:
Domoticz.Error("To many requests waiting. Resetting connection to restful Harmony.")
self.HubConn.Disconnect()
else:
sendData = { 'Verb' : 'GET',
'URL' : '/harmony/show/activity',
'Headers' : { 'Host': Parameters["Address"]+":"+Parameters["Port"], \
'Connection': 'keep-alive', \
'Cache-Control': 'max-age=0', \
'Upgrade-Insecure-Requests': '1', \
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', \
'Accept-Encoding': 'gzip, deflate', \
'Accept-Language': 'nl-NL,nl;q=0.9,en-US;q=0.8,en;q=0.7', \
'User-Agent':'Domoticz/1.0' }
}
self.HubConn.Send(sendData)
self.reqwaiting += 1
Domoticz.Debug("onHeartbeat called, Connection is alive.")
else:
self.reconnectcountdown -= 1
if self.reconnectcountdown <= 0:
Domoticz.Error("Connection lost.... reconnecting")
self.HubConn.Connect()
self.reconnectcountdown = self.reconnectdelay
else:
Domoticz.Debug("Harmony disconnected. Reconnecting in "+str(self.reconnectcountdown)+" heartbeats.")
global _plugin
_plugin = BasePlugin()
def onStart():
global _plugin
_plugin.onStart()
def onStop():
global _plugin
_plugin.onStop()
def onConnect(Connection, Status, Description):
global _plugin
_plugin.onConnect(Connection, Status, Description)
def onMessage(Connection, Data):
global _plugin
_plugin.onMessage(Connection, Data)
def onCommand(Unit, Command, Level, Hue):
global _plugin
_plugin.onCommand(Unit, Command, Level, Hue)
def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
global _plugin
_plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)
def onDisconnect(Connection):
global _plugin
_plugin.onDisconnect(Connection)
def onHeartbeat():
global _plugin
_plugin.onHeartbeat()
# Generic helper functions
#map Harmony (external) activity ID's to internal Domoticz ID's
def act2id():
actlist={}
for thing in Devices:
actlist[Devices[thing].DeviceID]=thing
return actlist
#find the first available (internal) unit id
def firstfree():
for num in range(1,250):
if num not in Devices:
return num
return
def encodehubid(realid):
if int(realid)==-1:
#results in FFFFFFFF hex
return format(4294967295, 'X')
else:
return format(int(realid), 'X')
def decodehubid(fakeid):
if fakeid==4294967295:
return -1
else:
return fakeid