added code to handle disconnect, as in when the device is not powered for any reason
This commit is contained in:
parent
7bfb98cabf
commit
65caf803ed
1 changed files with 156 additions and 79 deletions
235
plugin.py
235
plugin.py
|
@ -28,25 +28,35 @@ __email__ = "mail@petermolnar.net"
|
||||||
import Domoticz
|
import Domoticz
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
class BasePlugin:
|
class BasePlugin:
|
||||||
httpConn = None
|
httpConn = None
|
||||||
runAgain = 6
|
oustandingPings = 0
|
||||||
disconnectCount = 0
|
connectRetry = 3
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
return
|
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):
|
def onStart(self):
|
||||||
if Parameters["Mode6"] != "0":
|
if Parameters["Mode6"] != "0":
|
||||||
Domoticz.Debugging(int(Parameters["Mode6"]))
|
Domoticz.Debugging(int(Parameters["Mode6"]))
|
||||||
DumpConfigToLog()
|
|
||||||
|
|
||||||
self.httpConn = Domoticz.Connection(
|
self.httpConn = Domoticz.Connection(
|
||||||
Name= Parameters["Address"],
|
Name=Parameters["Address"],
|
||||||
Transport="TCP/IP",
|
Transport="TCP/IP",
|
||||||
Protocol="HTTP",
|
Protocol="HTTP",
|
||||||
Address=Parameters["Address"],
|
Address=Parameters["Address"],
|
||||||
Port=Parameters["Mode1"]
|
Port=Parameters["Mode1"],
|
||||||
)
|
)
|
||||||
self.httpConn.Connect()
|
self.httpConn.Connect()
|
||||||
|
|
||||||
|
@ -54,144 +64,205 @@ class BasePlugin:
|
||||||
Domoticz.Log("onStop - Plugin is stopping.")
|
Domoticz.Log("onStop - Plugin is stopping.")
|
||||||
|
|
||||||
def onConnect(self, Connection, Status, Description):
|
def onConnect(self, Connection, Status, Description):
|
||||||
if (Status == 0):
|
if Status == 0:
|
||||||
Domoticz.Debug("Connected to Sonoff DIY interface")
|
Domoticz.Debug("Connected to Sonoff DIY interface")
|
||||||
Connection.Send({
|
self.query_status(Connection)
|
||||||
'Verb' : 'POST',
|
|
||||||
'URL' : '/zeroconf/info',
|
|
||||||
'Headers' : {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
'Data': json.dumps({
|
|
||||||
'data': ''
|
|
||||||
})
|
|
||||||
})
|
|
||||||
else:
|
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):
|
def onMessage(self, Connection, Data):
|
||||||
strData = Data["Data"].decode("utf-8", "ignore")
|
|
||||||
Status = int(Data["Status"])
|
|
||||||
try:
|
try:
|
||||||
|
strData = Data["Data"].decode("utf-8", "ignore")
|
||||||
strData = json.loads(strData)
|
strData = json.loads(strData)
|
||||||
Domoticz.Log("Parsed JSON:" + json.dumps(strData))
|
# Status = int(Data["Status"])
|
||||||
if "data" in strData:
|
Domoticz.Debug("Parsed JSON:" + json.dumps(strData))
|
||||||
if (len(Devices) == 0) and "deviceid" in strData["data"]:
|
self.oustandingPings = self.oustandingPings - 1
|
||||||
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
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
Domoticz.Log("Failed to parse response as JSON" + strData)
|
Domoticz.Error("Failed to parse response as JSON" + strData)
|
||||||
return
|
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):
|
def onCommand(self, Unit, Command, Level, Hue):
|
||||||
Domoticz.Debug("onCommand called for Unit " + str(Unit) + ": Parameter '" + str(Command) + "', Level: " + str(Level))
|
Domoticz.Debug(
|
||||||
if "Off" == Command or "On" == Command:
|
"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"
|
url = "/zeroconf/switch"
|
||||||
data = {
|
data = {"switch": Command.lower()}
|
||||||
"switch": Command.lower()
|
|
||||||
}
|
|
||||||
else:
|
else:
|
||||||
url = "/zeroconf/dimmable"
|
url = "/zeroconf/dimmable"
|
||||||
if Level > 0:
|
if Level > 0:
|
||||||
switch = "on"
|
switch = "on"
|
||||||
else:
|
else:
|
||||||
switch = "off"
|
switch = "off"
|
||||||
data = {
|
data = {"switch": switch, "brightness": Level}
|
||||||
"switch": switch,
|
self.httpConn.Send(
|
||||||
"brightness": Level
|
{
|
||||||
|
"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):
|
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):
|
def onHeartbeat(self):
|
||||||
if (self.httpConn != None and (self.httpConn.Connecting() or self.httpConn.Connected())):
|
try:
|
||||||
Domoticz.Debug("onHeartbeat called, Connection is alive.")
|
if self.httpConn and self.httpConn.Connected():
|
||||||
self.httpConn.Send({
|
self.oustandingPings = self.oustandingPings + 1
|
||||||
'Verb' : 'POST',
|
if self.oustandingPings > 6:
|
||||||
'URL' : '/zeroconf/info',
|
Domoticz.Log(
|
||||||
'Headers' : {
|
"Too many outstanding connection issues forcing disconnect."
|
||||||
'Content-Type': 'application/json'
|
)
|
||||||
},
|
self.httpConn.Disconnect()
|
||||||
'Data': json.dumps({
|
self.nextConnect = 0
|
||||||
'data': ''
|
else:
|
||||||
})
|
self.query_status(self.httpConn)
|
||||||
})
|
|
||||||
else:
|
|
||||||
self.runAgain = self.runAgain - 1
|
|
||||||
if self.runAgain <= 0:
|
|
||||||
if (self.httpConn == None):
|
|
||||||
self.onStart()
|
|
||||||
self.runAgain = 6
|
|
||||||
else:
|
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
|
global _plugin
|
||||||
_plugin = BasePlugin()
|
_plugin = BasePlugin()
|
||||||
|
|
||||||
|
|
||||||
def onStart():
|
def onStart():
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onStart()
|
_plugin.onStart()
|
||||||
|
|
||||||
|
|
||||||
def onStop():
|
def onStop():
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onStop()
|
_plugin.onStop()
|
||||||
|
|
||||||
|
|
||||||
def onConnect(Connection, Status, Description):
|
def onConnect(Connection, Status, Description):
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onConnect(Connection, Status, Description)
|
_plugin.onConnect(Connection, Status, Description)
|
||||||
|
|
||||||
|
|
||||||
def onMessage(Connection, Data):
|
def onMessage(Connection, Data):
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onMessage(Connection, Data)
|
_plugin.onMessage(Connection, Data)
|
||||||
|
|
||||||
|
|
||||||
def onCommand(Unit, Command, Level, Hue):
|
def onCommand(Unit, Command, Level, Hue):
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onCommand(Unit, Command, Level, Hue)
|
_plugin.onCommand(Unit, Command, Level, Hue)
|
||||||
|
|
||||||
|
|
||||||
def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
|
def onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile):
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onNotification(Name, Subject, Text, Status, Priority, Sound, ImageFile)
|
_plugin.onNotification(
|
||||||
|
Name, Subject, Text, Status, Priority, Sound, ImageFile
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def onDisconnect(Connection):
|
def onDisconnect(Connection):
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onDisconnect(Connection)
|
_plugin.onDisconnect(Connection)
|
||||||
|
|
||||||
|
|
||||||
def onHeartbeat():
|
def onHeartbeat():
|
||||||
global _plugin
|
global _plugin
|
||||||
_plugin.onHeartbeat()
|
_plugin.onHeartbeat()
|
||||||
|
|
||||||
|
|
||||||
# Generic helper functions
|
# Generic helper functions
|
||||||
def LogMessage(Message):
|
def LogMessage(Message):
|
||||||
if Parameters["Mode6"] == "File":
|
if Parameters["Mode6"] == "File":
|
||||||
f = open(Parameters["HomeFolder"]+"http.html","w")
|
f = open(Parameters["HomeFolder"] + "http.html", "w")
|
||||||
f.write(Message)
|
f.write(Message)
|
||||||
f.close()
|
f.close()
|
||||||
Domoticz.Log("File written")
|
Domoticz.Log("File written")
|
||||||
|
|
||||||
|
|
||||||
def DumpConfigToLog():
|
def DumpConfigToLog():
|
||||||
for x in Parameters:
|
for x in Parameters:
|
||||||
if Parameters[x] != "":
|
if Parameters[x] != "":
|
||||||
Domoticz.Debug( "'" + x + "':'" + str(Parameters[x]) + "'")
|
Domoticz.Debug("'" + x + "':'" + str(Parameters[x]) + "'")
|
||||||
Domoticz.Debug("Device count: " + str(len(Devices)))
|
Domoticz.Debug("Device count: " + str(len(Devices)))
|
||||||
for x in Devices:
|
for x in Devices:
|
||||||
Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x]))
|
Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x]))
|
||||||
|
@ -202,18 +273,24 @@ def DumpConfigToLog():
|
||||||
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
|
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
def DumpHTTPResponseToLog(httpResp, level=0):
|
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 = ""
|
indentStr = ""
|
||||||
for x in range(level):
|
for x in range(level):
|
||||||
indentStr += "----"
|
indentStr += "----"
|
||||||
if isinstance(httpResp, dict):
|
if isinstance(httpResp, dict):
|
||||||
for x in httpResp:
|
for x in httpResp:
|
||||||
if not isinstance(httpResp[x], dict) and not isinstance(httpResp[x], list):
|
if not isinstance(httpResp[x], dict) and not isinstance(
|
||||||
Domoticz.Debug(indentStr + ">'" + x + "':'" + str(httpResp[x]) + "'")
|
httpResp[x], list
|
||||||
|
):
|
||||||
|
Domoticz.Debug(
|
||||||
|
indentStr + ">'" + x + "':'" + str(httpResp[x]) + "'"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
Domoticz.Debug(indentStr + ">'" + x + "':")
|
Domoticz.Debug(indentStr + ">'" + x + "':")
|
||||||
DumpHTTPResponseToLog(httpResp[x], level+1)
|
DumpHTTPResponseToLog(httpResp[x], level + 1)
|
||||||
elif isinstance(httpResp, list):
|
elif isinstance(httpResp, list):
|
||||||
for x in httpResp:
|
for x in httpResp:
|
||||||
Domoticz.Debug(indentStr + "['" + x + "']")
|
Domoticz.Debug(indentStr + "['" + x + "']")
|
||||||
|
|
Loading…
Reference in a new issue