From 65caf803ed79076946eec0c88c045a72b3bd7a28 Mon Sep 17 00:00:00 2001 From: Peter Molnar Date: Mon, 26 Oct 2020 10:46:02 +0000 Subject: [PATCH] added code to handle disconnect, as in when the device is not powered for any reason --- plugin.py | 235 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 156 insertions(+), 79 deletions(-) diff --git a/plugin.py b/plugin.py index 3616557..d4c0a8a 100644 --- a/plugin.py +++ b/plugin.py @@ -28,25 +28,35 @@ __email__ = "mail@petermolnar.net" import Domoticz import json + class BasePlugin: httpConn = None - runAgain = 6 - disconnectCount = 0 + oustandingPings = 0 + connectRetry = 3 def __init__(self): return + def query_status(self, Connection): + Connection.Send( + { + "Verb": "POST", + "URL": "/zeroconf/info", + "Headers": {"Content-Type": "application/json"}, + "Data": json.dumps({"data": ""}), + } + ) + def onStart(self): if Parameters["Mode6"] != "0": Domoticz.Debugging(int(Parameters["Mode6"])) - DumpConfigToLog() self.httpConn = Domoticz.Connection( - Name= Parameters["Address"], + Name=Parameters["Address"], Transport="TCP/IP", Protocol="HTTP", Address=Parameters["Address"], - Port=Parameters["Mode1"] + Port=Parameters["Mode1"], ) self.httpConn.Connect() @@ -54,144 +64,205 @@ class BasePlugin: Domoticz.Log("onStop - Plugin is stopping.") def onConnect(self, Connection, Status, Description): - if (Status == 0): + if Status == 0: Domoticz.Debug("Connected to Sonoff DIY interface") - Connection.Send({ - 'Verb' : 'POST', - 'URL' : '/zeroconf/info', - 'Headers' : { - 'Content-Type': 'application/json' - }, - 'Data': json.dumps({ - 'data': '' - }) - }) + self.query_status(Connection) else: - Domoticz.Log("Failed to connect ("+str(Status)+") to: "+Parameters["Address"]+":"+Parameters["Mode1"]+" with error: "+Description) + Domoticz.Log( + "Failed to connect (" + + str(Status) + + ") to: " + + Parameters["Address"] + + ":" + + Parameters["Mode1"] + + " with error: " + + Description + ) def onMessage(self, Connection, Data): - strData = Data["Data"].decode("utf-8", "ignore") - Status = int(Data["Status"]) try: + strData = Data["Data"].decode("utf-8", "ignore") strData = json.loads(strData) - Domoticz.Log("Parsed JSON:" + json.dumps(strData)) - if "data" in strData: - if (len(Devices) == 0) and "deviceid" in strData["data"]: - Domoticz.Device(Name=strData["data"]["deviceid"], Unit=1, Type=244, Subtype=73, Switchtype=7).Create() - if "switch" in strData["data"] and "brightness" in strData["data"]: - n_value = 1 if strData["data"]["switch"] == 'on' else 0 - s_value = str(strData["data"]["brightness"]) - Devices[1].Update( - nValue=n_value, - sValue=s_value - #SignalLevel=strData["data"]["signalStrength"], - #BatteryLevel=100 - ) + # Status = int(Data["Status"]) + Domoticz.Debug("Parsed JSON:" + json.dumps(strData)) + self.oustandingPings = self.oustandingPings - 1 except: - Domoticz.Log("Failed to parse response as JSON" + strData) + Domoticz.Error("Failed to parse response as JSON" + strData) return + if "data" not in strData: + Domoticz.Debug("`data` missing from Parsed JSON") + return + + data = strData["data"] + + # skip these messages, they are not status updates + if "deviceid" not in data: + Domoticz.Debug("`deviceid` missing from Parsed JSON") + return + # create new devices if the don't exist just yet + existing_devices = [d.DeviceID for d in Devices.values()] + if data["deviceid"] not in existing_devices: + # I guess brightness is only present in a dimmer + # I could be wrong + if "brightness" in data: + Domoticz.Device( + Name=data["deviceid"], + Unit=1, + Type=244, + Subtype=73, + Switchtype=7, + DeviceID=data["deviceid"], + ).Create() + + # now the device certainly exists, so find it + device = None + for index, d in Devices.items(): + if data["deviceid"] == d.DeviceID: + device = Devices[index] + + if not device: + Domoticz.Error("something is wrong: the device was not found?!") + return + + if "switch" in data and "brightness" in data: + if data["switch"] == "on": + n_value = 1 + else: + n_value = 0 + s_value = str(data["brightness"]) + + device.Update( + nValue=n_value, + sValue=s_value, + SignalLevel=int((1 / (-1 * data["signalStrength"])) * 100), + BatteryLevel=100, + ) + def onCommand(self, Unit, Command, Level, Hue): - Domoticz.Debug("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level)) - if "Off" == Command or "On" == Command: + Domoticz.Debug( + "onCommand called for Unit " + + str(Unit) + + ": Parameter '" + + str(Command) + + "', Level: " + + str(Level) + ) + + # in case of 'on' and 'off', the dimmable endpoint doesn't seem to respect the switch status + if Command.lower() in ["off", "on"]: url = "/zeroconf/switch" - data = { - "switch": Command.lower() - } + data = {"switch": Command.lower()} else: url = "/zeroconf/dimmable" if Level > 0: switch = "on" else: switch = "off" - data = { - "switch": switch, - "brightness": Level + data = {"switch": switch, "brightness": Level} + self.httpConn.Send( + { + "Verb": "POST", + "URL": url, + "Headers": {"Content-Type": "application/json"}, + "Data": json.dumps({"deviceid": "", "data": data}), } - self.httpConn.Send({ - 'Verb' : 'POST', - 'URL' : url, - 'Headers' : { - 'Content-Type': 'application/json' - }, - 'Data': json.dumps({ - "deviceid": "", - "data": data - }) - }) + ) def onDisconnect(self, Connection): - Domoticz.Log("onDisconnect called for connection to: "+Connection.Address+":"+Connection.Port) + Domoticz.Log( + "onDisconnect called for connection to: " + + Connection.Address + + ":" + + Connection.Port + ) def onHeartbeat(self): - if (self.httpConn != None and (self.httpConn.Connecting() or self.httpConn.Connected())): - Domoticz.Debug("onHeartbeat called, Connection is alive.") - self.httpConn.Send({ - 'Verb' : 'POST', - 'URL' : '/zeroconf/info', - 'Headers' : { - 'Content-Type': 'application/json' - }, - 'Data': json.dumps({ - 'data': '' - }) - }) - else: - self.runAgain = self.runAgain - 1 - if self.runAgain <= 0: - if (self.httpConn == None): - self.onStart() - self.runAgain = 6 + try: + if self.httpConn and self.httpConn.Connected(): + self.oustandingPings = self.oustandingPings + 1 + if self.oustandingPings > 6: + Domoticz.Log( + "Too many outstanding connection issues forcing disconnect." + ) + self.httpConn.Disconnect() + self.nextConnect = 0 + else: + self.query_status(self.httpConn) else: - Domoticz.Debug("onHeartbeat called, connection is dead") + # if not connected try and reconnected every 3 heartbeats + self.oustandingPings = 0 + self.nextConnect = self.nextConnect - 1 + if self.nextConnect <= 0: + self.nextConnect = 3 + self.httpConn.Connect() + return True + except: + Domoticz.Log( + "Unhandled exception in onHeartbeat, forcing disconnect." + ) + self.onDisconnect(self.httpConn) + self.httpConn = None 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) + _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 def LogMessage(Message): if Parameters["Mode6"] == "File": - f = open(Parameters["HomeFolder"]+"http.html","w") + f = open(Parameters["HomeFolder"] + "http.html", "w") f.write(Message) f.close() Domoticz.Log("File written") + def DumpConfigToLog(): for x in Parameters: if Parameters[x] != "": - Domoticz.Debug( "'" + x + "':'" + str(Parameters[x]) + "'") + Domoticz.Debug("'" + x + "':'" + str(Parameters[x]) + "'") Domoticz.Debug("Device count: " + str(len(Devices))) for x in Devices: Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x])) @@ -202,18 +273,24 @@ def DumpConfigToLog(): Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel)) return + def DumpHTTPResponseToLog(httpResp, level=0): - if (level==0): Domoticz.Debug("HTTP Details ("+str(len(httpResp))+"):") + if level == 0: + Domoticz.Debug("HTTP Details (" + str(len(httpResp)) + "):") indentStr = "" for x in range(level): indentStr += "----" if isinstance(httpResp, dict): for x in httpResp: - if not isinstance(httpResp[x], dict) and not isinstance(httpResp[x], list): - Domoticz.Debug(indentStr + ">'" + x + "':'" + str(httpResp[x]) + "'") + if not isinstance(httpResp[x], dict) and not isinstance( + httpResp[x], list + ): + Domoticz.Debug( + indentStr + ">'" + x + "':'" + str(httpResp[x]) + "'" + ) else: Domoticz.Debug(indentStr + ">'" + x + "':") - DumpHTTPResponseToLog(httpResp[x], level+1) + DumpHTTPResponseToLog(httpResp[x], level + 1) elif isinstance(httpResp, list): for x in httpResp: Domoticz.Debug(indentStr + "['" + x + "']")