Support multiple devices

This commit is contained in:
2024-11-17 17:23:37 +01:00
parent e82a23c459
commit 6eae6866e8
2 changed files with 94 additions and 83 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
Domoticz.py
parameters.properties
run.py
__pycache__/

149
plugin.py
View File

@@ -2,6 +2,7 @@
# #
# Author: fjumelle # Author: fjumelle
# #
#pylint: disable=line-too-long,broad-exception-caught,possibly-used-before-assignment
""" """
<plugin key="Heatzy_FJU" name="Heatzy Pilote" author="fjumelle" version="1.0.2" wikilink="" externallink=""> <plugin key="Heatzy_FJU" name="Heatzy Pilote" author="fjumelle" version="1.0.2" wikilink="" externallink="">
<description> <description>
@@ -11,6 +12,7 @@
<params> <params>
<param field="Username" label="Heatzy Username" width="200px" required="true" default=""/> <param field="Username" label="Heatzy Username" width="200px" required="true" default=""/>
<param field="Password" label="Heatzy Password" width="200px" required="true" default="" password="true"/> <param field="Password" label="Heatzy Password" width="200px" required="true" default="" password="true"/>
<param field="Mode3" label="Device name" width="200px" required="true" default=""/>
<param field="Mode4" label="'Off=Normal' bug?" width="200px"> <param field="Mode4" label="'Off=Normal' bug?" width="200px">
<options> <options>
<option label="No" value="0" default="true"/> <option label="No" value="0" default="true"/>
@@ -27,13 +29,17 @@
</params> </params>
</plugin> </plugin>
""" """
import Domoticz import math
import requests
import json
import time import time
import urllib.parse as parse from datetime import datetime
import urllib.request as request
from datetime import datetime, timedelta import requests
import Domoticz # type: ignore
if None is not None: #Fake statement to remove warning on global Domoticz variables #NOSONAR
Parameters = Parameters # type: ignore #NOSONAR #pylint: disable=undefined-variable,self-assigning-variable
Images = Images # type: ignore #NOSONAR #pylint: disable=undefined-variable,self-assigning-variable
Devices = Devices # type: ignore #NOSONAR #pylint: disable=undefined-variable,self-assigning-variable
HEATZY_MODE = { HEATZY_MODE = {
'停止': 'OFF', '停止': 'OFF',
@@ -60,14 +66,8 @@ HEATZY_MODE_VALUE_INV = {v: k for k, v in HEATZY_MODE_VALUE.items()}
DEFAULT_POOLING = 60 DEFAULT_POOLING = 60
class deviceparam:
def __init__(self, unit, nvalue, svalue):
self.unit = unit
self.nvalue = nvalue
self.svalue = svalue
class BasePlugin: class BasePlugin:
"""Class for plugin"""
debug = False debug = False
token = "" token = ""
token_expire_at = 0 token_expire_at = 0
@@ -84,15 +84,14 @@ class BasePlugin:
def __init__(self): def __init__(self):
return return
def onStart(self): def on_start(self):
import math """At statup"""
# setup the appropriate logging level # setup the appropriate logging level
debuglevel = int(Parameters["Mode6"]) debuglevel = int(Parameters["Mode6"])
if debuglevel != 0: if debuglevel != 0:
self.debug = True self.debug = True
Domoticz.Debugging(debuglevel) Domoticz.Debugging(debuglevel)
DumpConfigToLog() dump_config_to_log()
else: else:
self.debug = False self.debug = False
Domoticz.Debugging(0) Domoticz.Debugging(0)
@@ -100,26 +99,23 @@ class BasePlugin:
# Polling interval = X sec # Polling interval = X sec
try: try:
pooling = int(Parameters["Mode5"]) pooling = int(Parameters["Mode5"])
except: except Exception:
pooling = DEFAULT_POOLING pooling = DEFAULT_POOLING
self.pooling_steps = math.ceil(pooling/30) self.pooling_steps = math.ceil(pooling/30)
self.pooling = pooling // self.pooling_steps self.pooling = pooling // self.pooling_steps
Domoticz.Heartbeat(self.pooling) Domoticz.Heartbeat(self.pooling)
# create the child devices if these do not exist yet # create the child devices if these do not exist yet
devicecreated = []
if 1 not in Devices: if 1 not in Devices:
Domoticz.Device(Name="Control", Unit=1, TypeName="Switch", Image=9, Used=1).Create() Domoticz.Device(Name="Control", Unit=1, TypeName="Switch", Image=9, Used=1).Create()
devicecreated.append(deviceparam(1, 0, "Off")) # default is Off
if 2 not in Devices: if 2 not in Devices:
Options = {"LevelActions": "||", options = {"LevelActions": "||",
"LevelNames": HEATZY_MODE_NAME['OFF'] + "|" + HEATZY_MODE_NAME['FROSTFREE'] + "|" + HEATZY_MODE_NAME['ECONOMY'] + "|" + HEATZY_MODE_NAME['NORMAL'], "LevelNames": HEATZY_MODE_NAME['OFF'] + "|" + HEATZY_MODE_NAME['FROSTFREE'] + "|" + HEATZY_MODE_NAME['ECONOMY'] + "|" + HEATZY_MODE_NAME['NORMAL'],
"LevelOffHidden": "false", #Bug with off mode... "LevelOffHidden": "false", #Bug with off mode...
#"LevelOffHidden": "true",t #"LevelOffHidden": "true",t
"SelectorStyle": "0"} "SelectorStyle": "0"}
Domoticz.Device(Name="Mode", Unit=2, TypeName="Selector Switch", Switchtype=18, Image=15, Domoticz.Device(Name="Mode", Unit=2, TypeName="Selector Switch", Switchtype=18, Image=15,
Options=Options, Used=1).Create() Options=options, Used=1).Create()
devicecreated.append(deviceparam(2, 0, "30")) # default is confort mode
# Bug Off = Normal? # Bug Off = Normal?
if str(Parameters["Mode4"]) != "0": if str(Parameters["Mode4"]) != "0":
@@ -127,35 +123,37 @@ class BasePlugin:
self.bug = True self.bug = True
# Get Token # Get Token
self.token, self.token_expire_at = self.getToken(Parameters["Username"], Parameters["Password"]) self.token, self.token_expire_at = self.get_token(Parameters["Username"], Parameters["Password"])
# Get Devide Id # Get Devide Id
self.did = self.getDevideId(self.token) self.did = self.get_device_id(self.token, Parameters["Mode3"])
# Get mode # Get mode
self.mode = self.getMode() self.mode = self.get_mode()
def onCommand(self, Unit, Command, Level, Hue): def on_command(self, unit, command, level, hue): #pylint: disable=unused-argument
if Unit == 1: """Send a command"""
self.mode = self.onOff(Command) if unit == 1:
elif Unit == 2: self.mode = self.on_off(command)
self.mode = self.setMode(Level) elif unit == 2:
self.mode = self.set_mode(level)
def onHeartbeat(self): def on_heartbeat(self):
"""Time to heartbeat :)"""
if self.pooling_current_step >= self.pooling_steps: if self.pooling_current_step >= self.pooling_steps:
Domoticz.Debug("Retry counter:{}".format(self.retry)) Domoticz.Debug(f"Retry counter:{self.retry}")
if self.retry < 0: if self.retry < 0:
Domoticz.Status("No connection to Heatzy API ==> Device disabled for 15 minutes") Domoticz.Status("No connection to Heatzy API ==> Device disabled for 15 minutes")
self.pooling_current_step = - 15 * 60 // self.pooling + self.pooling_steps self.pooling_current_step = - 15 * 60 // self.pooling + self.pooling_steps
self.retry = self.max_retry self.retry = self.max_retry
return return
self.mode = self.getMode() self.mode = self.get_mode()
# If mode = OFF and bug, then mode = FROSTFREE # If mode = OFF and bug, then mode = FROSTFREE
if self.bug and self.mode == 'OFF': if self.bug and self.mode == 'OFF':
Domoticz.Log("Switch to FROSTFREE because of the OFF bug...") Domoticz.Log("Switch to FROSTFREE because of the OFF bug...")
self.mode = self.setMode(HEATZY_MODE_VALUE['FROSTFREE']) self.mode = self.set_mode(HEATZY_MODE_VALUE['FROSTFREE'])
# check if need to refresh device so that they do not turn red in GUI # check if need to refresh device so that they do not turn red in GUI
#now = datetime.now() #now = datetime.now()
@@ -167,9 +165,8 @@ class BasePlugin:
else: else:
self.pooling_current_step = self.pooling_current_step + 1 self.pooling_current_step = self.pooling_current_step + 1
def getToken(self, user, password): def get_token(self, user, password):
import time """Get token using the Heatzy API"""
need_to_get_token = False need_to_get_token = False
if self.token == "" or self.token_expire_at == "": if self.token == "" or self.token_expire_at == "":
@@ -211,7 +208,7 @@ class BasePlugin:
else: else:
error_code = "Unknown" if 'error_code' not in response else response['error_code'] error_code = "Unknown" if 'error_code' not in response else response['error_code']
error_message = "Unknown" if 'error_message' not in response else response['error_message'] error_message = "Unknown" if 'error_message' not in response else response['error_message']
Domoticz.Error("Cannot get Heatzy Token: {} ({})\n{}".format(error_message, error_code, response)) Domoticz.Error(f"Cannot get Heatzy Token: {error_message} ({error_code})\n{response}")
self.token = "" self.token = ""
self.token_expire_at = 0 self.token_expire_at = 0
self.did = "" self.did = ""
@@ -220,7 +217,8 @@ class BasePlugin:
return self.token, self.token_expire_at return self.token, self.token_expire_at
def getDevideId(self, token): def get_device_id(self, token, alias):
"""Get the device id from the token and the device name, using the Heatzy API"""
if token == "" or self.retry<0: if token == "" or self.retry<0:
return "" return ""
@@ -245,23 +243,30 @@ class BasePlugin:
Domoticz.Debug("Get Device Id Response:" + str(response)) Domoticz.Debug("Get Device Id Response:" + str(response))
if 'devices' in response and 'did' in response['devices'][0]: found = False
self.did = response['devices'][0]['did'] if 'devices' in response:
devices = response['devices']
for device in devices:
if "dev_alias" in device and "did" in device and device["dev_alias"].lower() == alias.lower():
found = True
self.did = device['did']
Domoticz.Status("Devide Id from Heatzy API: " + self.did) Domoticz.Status("Devide Id from Heatzy API: " + self.did)
else:
if not found:
self.did = "" self.did = ""
error_code = "Unknown" if 'error_code' not in response else response['error_code'] error_code = "Unknown" if 'error_code' not in response else response['error_code']
error_message = "Unknown" if 'error_message' not in response else response['error_message'] error_message = "Unknown" if 'error_message' not in response else response['error_message']
Domoticz.Error("Cannot get Heatzy Devide Id: {} ({})\n{}".format(error_message, error_code, response)) Domoticz.Error(f"Cannot get Heatzy Devide Id: {error_message} ({error_code})\n{response}")
return self.did return self.did
def getMode(self): def get_mode(self):
"Get the device mode using the Heatzy API"
mode = "" mode = ""
response = "" response = ""
self.token, self.token_expire_at = self.getToken(Parameters["Username"], Parameters["Password"]) self.token, self.token_expire_at = self.get_token(Parameters["Username"], Parameters["Password"])
self.did = self.getDevideId(self.token) self.did = self.get_device_id(self.token, Parameters["Mode3"])
if self.retry<0: if self.retry<0:
return "" return ""
@@ -270,7 +275,7 @@ class BasePlugin:
'Accept': 'application/json', 'Accept': 'application/json',
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3', 'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
} }
url = 'https://euapi.gizwits.com/app/devdata/{}/latest'.format(self.did) url = f"https://euapi.gizwits.com/app/devdata/{self.did}/latest"
try: try:
response = requests.get(url, headers=headers, timeout=3).json() response = requests.get(url, headers=headers, timeout=3).json()
except Exception as exc: except Exception as exc:
@@ -289,13 +294,13 @@ class BasePlugin:
if 'attr' in response and 'mode' in response['attr']: if 'attr' in response and 'mode' in response['attr']:
mode = HEATZY_MODE[response['attr']['mode']] mode = HEATZY_MODE[response['attr']['mode']]
Domoticz.Debug("Current Heatzy Mode: {}".format(HEATZY_MODE_NAME[mode])) Domoticz.Debug(f"Current Heatzy Mode: {HEATZY_MODE_NAME[mode]}")
#Reset retry counter #Reset retry counter
self.retry = self.max_retry self.retry = self.max_retry
if Devices[2].nValue != HEATZY_MODE_VALUE[mode]: if Devices[2].nValue != HEATZY_MODE_VALUE[mode]:
Domoticz.Status("New Heatzy Mode: {}".format(HEATZY_MODE_NAME[mode])) Domoticz.Status(f"New Heatzy Mode: {HEATZY_MODE_NAME[mode]}")
Devices[2].Update(nValue=HEATZY_MODE_VALUE[mode], sValue=str(HEATZY_MODE_VALUE[mode]), TimedOut = 0) Devices[2].Update(nValue=HEATZY_MODE_VALUE[mode], sValue=str(HEATZY_MODE_VALUE[mode]), TimedOut = 0)
if not self.bug: if not self.bug:
@@ -314,7 +319,7 @@ class BasePlugin:
error_code = "Unknown" if 'error_code' not in response else response['error_code'] error_code = "Unknown" if 'error_code' not in response else response['error_code']
error_message = "Unknown" if 'error_message' not in response else response['error_message'] error_message = "Unknown" if 'error_message' not in response else response['error_message']
Domoticz.Error("Cannot get Heatzy Mode: {} ({})\n{}\nToken: {}\nDeviceId: {}".format(error_message, error_code, response, self.token, self.did)) Domoticz.Error(f"Cannot get Heatzy Mode: {error_message} ({error_code})\n{response}\nToken: {self.token}\nDeviceId: {self.did}")
if error_code == 9004: if error_code == 9004:
#Invalid token #Invalid token
self.token = "" self.token = ""
@@ -323,7 +328,8 @@ class BasePlugin:
return mode return mode
def setMode(self, mode): def set_mode(self, mode):
"Set the device mode using the Heatzy API"
if Devices[2].nValue != mode: if Devices[2].nValue != mode:
mode_str = { mode_str = {
HEATZY_MODE_VALUE['NORMAL']: '[1,1,0]', HEATZY_MODE_VALUE['NORMAL']: '[1,1,0]',
@@ -338,7 +344,7 @@ class BasePlugin:
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3', 'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
} }
data = '{"raw": '+mode_str[mode]+'}' data = '{"raw": '+mode_str[mode]+'}'
url = 'https://euapi.gizwits.com/app/control/{}'.format(self.did) url = f"https://euapi.gizwits.com/app/control/{self.did}"
try: try:
response = requests.post(url, headers=headers, data=data, timeout=3).json() response = requests.post(url, headers=headers, data=data, timeout=3).json()
except Exception as exc: except Exception as exc:
@@ -350,10 +356,10 @@ class BasePlugin:
Domoticz.Debug("Set Mode Response:" + str(response)) Domoticz.Debug("Set Mode Response:" + str(response))
if response != None: if response is not None:
self.mode = HEATZY_MODE_VALUE_INV[mode] self.mode = HEATZY_MODE_VALUE_INV[mode]
Devices[2].Update(nValue=int(mode), sValue=str(mode)) Devices[2].Update(nValue=int(mode), sValue=str(mode))
Domoticz.Status("New Heatzy Mode: {}".format(HEATZY_MODE_NAME[self.mode])) Domoticz.Status(f"New Heatzy Mode: {HEATZY_MODE_NAME[self.mode]}")
if not self.bug: if not self.bug:
if self.mode == 'OFF' and Devices[1].nValue != 0: if self.mode == 'OFF' and Devices[1].nValue != 0:
Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0) Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0)
@@ -367,7 +373,7 @@ class BasePlugin:
else: else:
error_code = "Unknown" if 'error_code' not in response else response['error_code'] error_code = "Unknown" if 'error_code' not in response else response['error_code']
error_message = "Unknown" if 'error_message' not in response else response['error_message'] error_message = "Unknown" if 'error_message' not in response else response['error_message']
Domoticz.Error("Cannot set Heatzy Mode: {} ({})\n{}\nToken: {}\nDeviceId: {}".format(error_message, error_code, response, self.token, self.did)) Domoticz.Error(f"Cannot set Heatzy Mode: {error_message} ({error_code})\n{response}\nToken: {self.token}\nDeviceId: {self.did}")
if error_code == 9004: if error_code == 9004:
#Invalid token #Invalid token
self.token = "" self.token = ""
@@ -379,36 +385,37 @@ class BasePlugin:
return self.mode return self.mode
def onOff(self, command): def on_off(self, command):
"""Toggle device on/off"""
if Devices[1].sValue != command: if Devices[1].sValue != command:
if command == "On": if command == "On":
self.mode = self.setMode(HEATZY_MODE_VALUE['NORMAL']) self.mode = self.set_mode(HEATZY_MODE_VALUE['NORMAL'])
else: else:
if not self.bug: if not self.bug:
self.mode = self.setMode(HEATZY_MODE_VALUE['OFF']) self.mode = self.set_mode(HEATZY_MODE_VALUE['OFF'])
else: else:
#Because of issue with the equipment (Off do not work...) #Because of issue with the equipment (Off do not work...)
self.mode = self.setMode(HEATZY_MODE_VALUE['FROSTFREE']) self.mode = self.set_mode(HEATZY_MODE_VALUE['FROSTFREE'])
return self.mode return self.mode
global _plugin
_plugin = BasePlugin() _plugin = BasePlugin()
def onStart(): def onStart(): #NOSONAR #pylint: disable=invalid-name
global _plugin """OnStart"""
_plugin.onStart() _plugin.on_start()
def onCommand(Unit, Command, Level, Hue): def onCommand(Unit, Command, Level, Hue): #NOSONAR #pylint: disable=invalid-name
global _plugin """OnCommand"""
_plugin.onCommand(Unit, Command, Level, Hue) _plugin.on_command(Unit, Command, Level, Hue)
def onHeartbeat(): def onHeartbeat(): #NOSONAR #pylint: disable=invalid-name
global _plugin """onHeartbeat"""
_plugin.onHeartbeat() _plugin.on_heartbeat()
# Generic helper functions # Generic helper functions
def DumpConfigToLog(): def dump_config_to_log():
"""Dump the config to the Domoticz Log"""
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]) + "'")