Compare commits
31 Commits
21a207e7ed
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7285595b32 | |||
| f752b45ee1 | |||
| ad3cfbe74d | |||
| 122615dffe | |||
| be985698a0 | |||
| a9bcf978c9 | |||
| 81030f7ddc | |||
| 4eea028f0a | |||
| c77c76e2fe | |||
| b01c09559e | |||
| 164f0c77ff | |||
| 70f4fafbfd | |||
| 1672ba6dc5 | |||
| 7828542c6c | |||
| c53fbc8a6d | |||
| 0600e28f30 | |||
| 1b322c55e5 | |||
| 2d495ba0b3 | |||
| 95debb7ea3 | |||
| 07240a1693 | |||
| 76a4169308 | |||
| 8942775fbc | |||
| 80a3c9a96b | |||
| ffc676d16b | |||
| d61b89ca45 | |||
| dfdd62415e | |||
| 92023c0290 | |||
| 816e2d3c7c | |||
| 0d9d03ce4d | |||
| ea9f35f3b6 | |||
| 6eae6866e8 |
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Domoticz.py
|
||||||
|
DomoticzEx.py
|
||||||
|
parameters.properties
|
||||||
|
run.py
|
||||||
|
__pycache__/
|
||||||
490
plugin.py
490
plugin.py
@@ -2,8 +2,9 @@
|
|||||||
#
|
#
|
||||||
# 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.4" wikilink="" externallink="">
|
<plugin key="HeatzyEx" name="Heatzy Pilote Ex" author="fjumelle" version="2.3.1" wikilink="" externallink="">
|
||||||
<description>
|
<description>
|
||||||
<h2>Heatzy Pilote</h2><br/>
|
<h2>Heatzy Pilote</h2><br/>
|
||||||
Implementation of Heatzy Pilote as a Domoticz Plugin.<br/>
|
Implementation of Heatzy Pilote as a Domoticz Plugin.<br/>
|
||||||
@@ -17,7 +18,7 @@
|
|||||||
<option label="Yes" value="1"/>
|
<option label="Yes" value="1"/>
|
||||||
</options>
|
</options>
|
||||||
</param>
|
</param>
|
||||||
<param field="Mode5" label="Polling interval (s)" width="40px" required="true" default="60"/>
|
<param field="Mode5" label="Polling interval (s)" width="40px" required="true" default="120"/>
|
||||||
<param field="Mode6" label="Logging Level" width="200px">
|
<param field="Mode6" label="Logging Level" width="200px">
|
||||||
<options>
|
<options>
|
||||||
<option label="Normal" value="0" default="true"/>
|
<option label="Normal" value="0" default="true"/>
|
||||||
@@ -27,22 +28,28 @@
|
|||||||
</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 enum import IntEnum
|
||||||
from datetime import datetime, timedelta
|
|
||||||
|
|
||||||
global Parameters
|
import requests
|
||||||
global Devices
|
import DomoticzEx as Domoticz #pylint: disable=import-error #pyright: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',
|
||||||
'解冻': 'FROSTFREE',
|
'解冻': 'FROSTFREE',
|
||||||
'经济': 'ECONOMY',
|
'经济': 'ECONOMY',
|
||||||
'舒适': 'NORMAL'
|
'舒适': 'NORMAL',
|
||||||
|
'stop': 'OFF',
|
||||||
|
'fro': 'FROSTFREE',
|
||||||
|
'eco': 'ECONOMY',
|
||||||
|
'cft': 'NORMAL',
|
||||||
}
|
}
|
||||||
|
|
||||||
HEATZY_MODE_NAME = {
|
HEATZY_MODE_NAME = {
|
||||||
@@ -63,39 +70,38 @@ HEATZY_MODE_VALUE_INV = {v: k for k, v in HEATZY_MODE_VALUE.items()}
|
|||||||
|
|
||||||
DEFAULT_POOLING = 60
|
DEFAULT_POOLING = 60
|
||||||
|
|
||||||
class deviceparam:
|
class HeatzyUnit(IntEnum):
|
||||||
def __init__(self, unit, nvalue, svalue):
|
"""2 units: Control and Selector"""
|
||||||
self.unit = unit
|
CONTROL = 1
|
||||||
self.nvalue = nvalue
|
SELECTOR = 2
|
||||||
self.svalue = svalue
|
|
||||||
|
|
||||||
|
|
||||||
class BasePlugin:
|
class BasePlugin:
|
||||||
|
"""Class for plugin"""
|
||||||
|
_HTTP_TIMEOUT = 5
|
||||||
|
_MAX_RETRY_PER_DEVICE = 3
|
||||||
|
|
||||||
debug = False
|
debug = False
|
||||||
token = ""
|
token = ""
|
||||||
token_expire_at = 0
|
token_expire_at = 0
|
||||||
did = ""
|
did = {}
|
||||||
mode = 0
|
|
||||||
nextupdate = datetime.now()
|
nextupdate = datetime.now()
|
||||||
bug = False
|
bug = False
|
||||||
pooling = 30
|
pooling = 30
|
||||||
pooling_steps = 1
|
pooling_steps = 1
|
||||||
pooling_current_step = 1
|
pooling_current_step = 1
|
||||||
max_retry = 6
|
retry = 0
|
||||||
retry = max_retry
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -103,80 +109,75 @@ 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
|
# Get Token
|
||||||
devicecreated = []
|
self.token, self.token_expire_at = self.get_token(Parameters["Username"], Parameters["Password"])
|
||||||
if 1 not in Devices:
|
|
||||||
Domoticz.Device(Name="Control", Unit=1, TypeName="Switch", Image=9, Used=1).Create()
|
# Get Devide Id
|
||||||
devicecreated.append(deviceparam(1, 0, "Off")) # default is Off
|
self.did = self.get_heatzy_devices()
|
||||||
if 2 not in Devices:
|
|
||||||
Options = {"LevelActions": "||",
|
# max retry per device
|
||||||
"LevelNames": HEATZY_MODE_NAME['OFF'] + "|" + HEATZY_MODE_NAME['FROSTFREE'] + "|" + HEATZY_MODE_NAME['ECONOMY'] + "|" + HEATZY_MODE_NAME['NORMAL'],
|
self.retry = self._MAX_RETRY_PER_DEVICE * len(self.did)
|
||||||
"LevelOffHidden": "false", #Bug with off mode...
|
|
||||||
#"LevelOffHidden": "true",t
|
# Create the child devices if these do not exist yet
|
||||||
"SelectorStyle": "0"}
|
for deviceid in self.did:
|
||||||
Domoticz.Device(Name="Mode", Unit=2, TypeName="Selector Switch", Switchtype=18, Image=15,
|
if deviceid not in Devices:
|
||||||
Options=Options, Used=1).Create()
|
alias = self.did[deviceid]["alias"]
|
||||||
devicecreated.append(deviceparam(2, 0, "30")) # default is confort mode
|
#Control
|
||||||
|
Domoticz.Unit(Name=f"Heatzy {alias} - Control", DeviceID=deviceid, Unit=HeatzyUnit.CONTROL, TypeName="Switch", Image=9, Used=1).Create()
|
||||||
|
|
||||||
|
#Selector switch
|
||||||
|
options = {"LevelActions": "||",
|
||||||
|
"LevelNames": HEATZY_MODE_NAME['OFF'] + "|" + HEATZY_MODE_NAME['FROSTFREE'] + "|" + HEATZY_MODE_NAME['ECONOMY'] + "|" + HEATZY_MODE_NAME['NORMAL'],
|
||||||
|
"LevelOffHidden": "false", #Bug with off mode...
|
||||||
|
#"LevelOffHidden": "true",t
|
||||||
|
"SelectorStyle": "0"}
|
||||||
|
Domoticz.Unit(Name=f"Heatzy {alias} - Mode", DeviceID=deviceid, Unit=HeatzyUnit.SELECTOR,
|
||||||
|
TypeName="Selector Switch", Switchtype=18, Image=15,
|
||||||
|
Options=options, Used=1).Create()
|
||||||
|
|
||||||
# Bug Off = Normal?
|
# Bug Off = Normal?
|
||||||
if str(Parameters["Mode4"]) != "0":
|
if str(Parameters["Mode4"]) != "0":
|
||||||
Domoticz.Log("Heatzy plugin configured to support the bug Off=Confort. Then when switching to Off, Heatzy will switch to Frost Free.")
|
Domoticz.Log("Heatzy plugin configured to support the bug Off=Confort. Then when switching to Off, Heatzy will switch to Frost Free.")
|
||||||
self.bug = True
|
self.bug = True
|
||||||
|
|
||||||
# Get Token
|
|
||||||
self.token, self.token_expire_at = self.getToken(Parameters["Username"], Parameters["Password"])
|
|
||||||
|
|
||||||
# Get Devide Id
|
|
||||||
self.did = self.getDevideId(self.token)
|
|
||||||
|
|
||||||
# Get mode
|
# Get mode
|
||||||
self.mode = self.getMode()
|
self.get_mode()
|
||||||
|
|
||||||
def onCommand(self, Unit, Command, Level, Hue):
|
def on_command(self, DeviceID, Unit, Command, Level, Color): #pylint: disable=unused-argument,invalid-name
|
||||||
if Unit == 1:
|
"""Send a command"""
|
||||||
self.mode = self.onOff(Command)
|
if Unit == HeatzyUnit.CONTROL:
|
||||||
elif Unit == 2:
|
self.send_command(DeviceID, Command)
|
||||||
self.mode = self.setMode(Level)
|
elif Unit == HeatzyUnit.SELECTOR:
|
||||||
|
self.set_mode(DeviceID, 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.reset_retry()
|
||||||
#Force refresh token/did
|
#Force refresh token/did
|
||||||
Domoticz.Status("Force refresh token and device id.")
|
Domoticz.Status("Force refresh token and device id.")
|
||||||
self.token = ""
|
self.token = ""
|
||||||
self.did = ""
|
self.did = {}
|
||||||
return
|
return
|
||||||
|
|
||||||
self.mode = self.getMode()
|
self.get_mode()
|
||||||
|
|
||||||
# If mode = OFF and bug, then mode = FROSTFREE
|
|
||||||
if self.bug and self.mode == 'OFF':
|
|
||||||
Domoticz.Log("Switch to FROSTFREE because of the OFF bug...")
|
|
||||||
self.mode = self.setMode(HEATZY_MODE_VALUE['FROSTFREE'])
|
|
||||||
|
|
||||||
# check if need to refresh device so that they do not turn red in GUI
|
|
||||||
#now = datetime.now()
|
|
||||||
#if self.nextupdate <= now:
|
|
||||||
# self.nextupdate = now + timedelta(minutes=int(Settings["SensorTimeout"]))
|
|
||||||
# Devices[2].Update(nValue=Devices[2].nValue, sValue=Devices[2].sValue)
|
|
||||||
|
|
||||||
self.pooling_current_step = 1
|
self.pooling_current_step = 1
|
||||||
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 == "":
|
||||||
@@ -194,14 +195,12 @@ class BasePlugin:
|
|||||||
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
||||||
}
|
}
|
||||||
data = '{ "username": "'+user+'", "password": "'+password+'", "lang": "en" }'
|
data = '{ "username": "'+user+'", "password": "'+password+'", "lang": "en" }'
|
||||||
|
time.sleep(0.5)
|
||||||
|
url = 'https://euapi.gizwits.com/app/login'
|
||||||
try:
|
try:
|
||||||
time.sleep(0.5)
|
response = http("post", url, headers, data)
|
||||||
url = 'https://euapi.gizwits.com/app/login'
|
|
||||||
response = requests.post(url, headers=headers, data=data, timeout=3).json()
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
Domoticz.Error("Cannot open connection to Heatzy API to get the token: " + str(exc))
|
Domoticz.Error("Cannot open connection to Heatzy API to get the token: " + str(exc))
|
||||||
#Domoticz.Error("URL: " + str(url))
|
|
||||||
#Domoticz.Error("Headers: " + str(headers))
|
|
||||||
Domoticz.Error("Data: " + str(data))
|
Domoticz.Error("Data: " + str(data))
|
||||||
#Decrease retry
|
#Decrease retry
|
||||||
self.retry = self.retry - 1
|
self.retry = self.retry - 1
|
||||||
@@ -214,61 +213,66 @@ class BasePlugin:
|
|||||||
self.token_expire_at = response['expire_at']
|
self.token_expire_at = response['expire_at']
|
||||||
Domoticz.Status("Token from Heatzy API: " + self.token)
|
Domoticz.Status("Token from Heatzy API: " + self.token)
|
||||||
#Reset retry counter
|
#Reset retry counter
|
||||||
self.retry = self.max_retry
|
self.reset_retry()
|
||||||
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 = {}
|
||||||
#Decrease retry
|
#Decrease retry
|
||||||
self.retry = self.retry - 1
|
self.retry = self.retry - 1
|
||||||
|
|
||||||
return self.token, self.token_expire_at
|
return self.token, self.token_expire_at
|
||||||
|
|
||||||
def getDevideId(self, token):
|
def get_heatzy_devices(self):
|
||||||
if token == "" or self.retry<0:
|
"""Get the device id from the token, using the Heatzy API"""
|
||||||
return ""
|
if self.token == "" or self.retry<0:
|
||||||
|
return self.did
|
||||||
|
|
||||||
if self.did == "":
|
if len(self.did) == 0:
|
||||||
Domoticz.Status("Heatzy Devide Id unknown, need to call Heatzy API.")
|
Domoticz.Status("Heatzy Devide Id unknown, need to call Heatzy API.")
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
'X-Gizwits-User-token': token,
|
'X-Gizwits-User-token': self.token,
|
||||||
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
||||||
}
|
}
|
||||||
params = (('limit', '20'), ('skip', '0'),)
|
params = (('limit', '20'), ('skip', '0'),)
|
||||||
url = 'https://euapi.gizwits.com/app/bindings'
|
url = 'https://euapi.gizwits.com/app/bindings'
|
||||||
try:
|
try:
|
||||||
response = requests.get(url, headers=headers, params=params, timeout=3).json()
|
response = http("get", url, headers, params=params)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
Domoticz.Error("Cannot open connection to Heatzy API to get the device id: " + str(exc))
|
Domoticz.Error("Cannot open connection to Heatzy API to get the device id: " + str(exc))
|
||||||
#Domoticz.Error("URL: " + str(url))
|
#Domoticz.Error("URL: " + str(url))
|
||||||
#Domoticz.Error("Headers: " + str(headers))
|
#Domoticz.Error("Headers: " + str(headers))
|
||||||
#Domoticz.Error("Params: " + str(params))
|
#Domoticz.Error("Params: " + str(params))
|
||||||
return ""
|
return self.did
|
||||||
|
|
||||||
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]:
|
if 'devices' in response:
|
||||||
self.did = response['devices'][0]['did']
|
devices = response['devices']
|
||||||
Domoticz.Status("Devide Id from Heatzy API: " + self.did)
|
unit = 1
|
||||||
else:
|
for device in devices:
|
||||||
self.did = ""
|
if "dev_alias" in device and "did" in device and "product_key" in device:
|
||||||
error_code = "Unknown" if 'error_code' not in response else response['error_code']
|
product_key = device['product_key']
|
||||||
error_message = "Unknown" if 'error_message' not in response else response['error_message']
|
alias = device['dev_alias']
|
||||||
Domoticz.Error("Cannot get Heatzy Devide Id: {} ({})\n{}".format(error_message, error_code, response))
|
did = device['did']
|
||||||
|
self.did[product_key] = {"did":did, "alias":alias, "updated_at":0, "command_at":0}
|
||||||
|
Domoticz.Status(f"Devide Id from Heatzy API: {alias} - {did}")
|
||||||
|
unit = unit + 1
|
||||||
|
|
||||||
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_heatzy_devices() #Not really needed
|
||||||
|
|
||||||
if self.retry<0:
|
if self.retry<0:
|
||||||
return ""
|
return ""
|
||||||
@@ -278,70 +282,108 @@ class BasePlugin:
|
|||||||
'X-Gizwits-User-token': self.token,
|
'X-Gizwits-User-token': self.token,
|
||||||
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
'X-Gizwits-Application-Id': 'c70a66ff039d41b4a220e198b0fcc8b3',
|
||||||
}
|
}
|
||||||
url = 'https://euapi.gizwits.com/app/devdata/{}/latest'.format(self.did)
|
|
||||||
try:
|
|
||||||
response = requests.get(url, headers=headers, timeout=3).json()
|
|
||||||
except Exception as exc:
|
|
||||||
#Decrease retry
|
|
||||||
self.retry = self.retry - 1
|
|
||||||
|
|
||||||
if self.retry < self.max_retry//2:
|
Domoticz.Debug(f"Get Mode for {[self.did[d]['alias'] for d in self.did]}...")
|
||||||
Domoticz.Error("Cannot open connection to Heatzy API to get the mode: " + str(exc))
|
for deviceid in self.did:
|
||||||
#Domoticz.Error("URL: " + str(url))
|
device = self.did[deviceid]
|
||||||
#Domoticz.Error("Headers: " + str(headers))
|
did = device["did"]
|
||||||
if 'response' in locals() and response != "":
|
alias = device["alias"]
|
||||||
Domoticz.Error("Response: " + str(response))
|
|
||||||
return ""
|
|
||||||
|
|
||||||
Domoticz.Debug("Get Mode Response:" + str(response))
|
url = f"https://euapi.gizwits.com/app/devdata/{did}/latest"
|
||||||
|
try:
|
||||||
|
response = http("get", url, headers)
|
||||||
|
except Exception as exc:
|
||||||
|
message = f"Cannot open connection to Heatzy API to get the mode for {alias} (retry={self.retry}): {exc}"
|
||||||
|
if self.retry <= 0:
|
||||||
|
Domoticz.Error(message)
|
||||||
|
else:
|
||||||
|
Domoticz.Debug(message + f" (retry left: {self.retry})")
|
||||||
|
|
||||||
if 'attr' in response and 'mode' in response['attr']:
|
#Decrease retry
|
||||||
mode = HEATZY_MODE[response['attr']['mode']]
|
self.retry = self.retry - 1
|
||||||
Domoticz.Debug("Current Heatzy Mode: {}".format(HEATZY_MODE_NAME[mode]))
|
|
||||||
|
|
||||||
#Reset retry counter
|
continue
|
||||||
self.retry = self.max_retry
|
|
||||||
|
|
||||||
if Devices[2].nValue != HEATZY_MODE_VALUE[mode]:
|
Domoticz.Debug(f"Get Mode Response for {alias}: {response}")
|
||||||
Domoticz.Status("New Heatzy Mode: {}".format(HEATZY_MODE_NAME[mode]))
|
|
||||||
Devices[2].Update(nValue=HEATZY_MODE_VALUE[mode], sValue=str(HEATZY_MODE_VALUE[mode]), TimedOut = 0)
|
|
||||||
|
|
||||||
if not self.bug:
|
#Last Update
|
||||||
if mode == 'OFF' and Devices[1].nValue != 0:
|
if 'updated_at' in response:
|
||||||
Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0)
|
if response["updated_at"] == device["updated_at"]:
|
||||||
elif mode != 'OFF' and Devices[1].nValue == 0:
|
#No update since last heartbeat
|
||||||
Devices[1].Update(nValue=1, sValue="On", TimedOut = 0)
|
if device["command_at"] - 60 > device["updated_at"] and Devices[deviceid].TimedOut == 0:
|
||||||
|
#No update while a command has been sent
|
||||||
|
obsolete_min = int((time.time() - response["updated_at"])//60)
|
||||||
|
Domoticz.Error(f"Last update from '{alias}' was {obsolete_min} min earlier.")
|
||||||
|
Devices[deviceid].TimedOut = 1
|
||||||
|
|
||||||
|
#Toggle device to be sure it is not stucked
|
||||||
|
Domoticz.Error(f"Then I toggle the device '{alias}'.")
|
||||||
|
self.toggle(deviceid)
|
||||||
|
continue
|
||||||
|
device["updated_at"] = response["updated_at"]
|
||||||
|
|
||||||
|
if Devices[deviceid].TimedOut == 1:
|
||||||
|
Domoticz.Status(f"'{alias}' is now back.")
|
||||||
|
Devices[deviceid].TimedOut = 0
|
||||||
|
|
||||||
|
if 'attr' in response and 'mode' in response['attr']:
|
||||||
|
mode = HEATZY_MODE[response['attr']['mode']]
|
||||||
|
Domoticz.Debug(f"Current Heatzy Mode: {HEATZY_MODE_NAME[mode]} ({alias})")
|
||||||
|
|
||||||
|
#Reset retry counter
|
||||||
|
self.reset_retry()
|
||||||
|
|
||||||
|
if Devices[deviceid].Units[HeatzyUnit.SELECTOR].nValue != HEATZY_MODE_VALUE[mode]:
|
||||||
|
Domoticz.Status(f"New Heatzy Mode: {HEATZY_MODE_NAME[mode]} ({alias})")
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].nValue = HEATZY_MODE_VALUE[mode]
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].sValue = str(HEATZY_MODE_VALUE[mode])
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].Update()
|
||||||
|
|
||||||
|
if not self.bug:
|
||||||
|
if mode == 'OFF' and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue != 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 0
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "Off"
|
||||||
|
elif mode != 'OFF' and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue == 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 1
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "On"
|
||||||
|
else:
|
||||||
|
if mode in ('OFF', 'FROSTFREE') and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue != 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 0
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "Off"
|
||||||
|
elif mode not in ('OFF', 'FROSTFREE') and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue == 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 1
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "On"
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].Update()
|
||||||
else:
|
else:
|
||||||
if mode in ('OFF', 'FROSTFREE') and Devices[1].nValue != 0:
|
#Decrease retry
|
||||||
Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0)
|
self.retry = self.retry - 1
|
||||||
elif mode not in ('OFF', 'FROSTFREE') and Devices[1].nValue == 0:
|
|
||||||
Devices[1].Update(nValue=1, sValue="On", TimedOut = 0)
|
|
||||||
else:
|
|
||||||
#Decrease retry
|
|
||||||
self.retry = self.retry - 1
|
|
||||||
|
|
||||||
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: {did}")
|
||||||
if error_code == 9004:
|
if error_code == 9004:
|
||||||
#Invalid token
|
#Invalid token
|
||||||
self.token = ""
|
self.token = ""
|
||||||
self.did = ""
|
self.did = {}
|
||||||
elif 'attr' in response and len(response["attr"]) == 0:
|
elif 'attr' in response and len(response["attr"]) == 0:
|
||||||
#attr is empty...
|
#attr is empty...
|
||||||
Domoticz.Status("We force a setMode to try to get the correct mode at the next try...")
|
Domoticz.Status("We force a setMode to try to get the correct mode at the next try...")
|
||||||
self.setMode(HEATZY_MODE_VALUE['FROSTFREE'])
|
self.set_mode(deviceid, HEATZY_MODE_VALUE['FROSTFREE'])
|
||||||
return ""
|
continue
|
||||||
|
|
||||||
return mode
|
# If mode = OFF and bug, then mode = FROSTFREE
|
||||||
|
if self.bug and mode == 'OFF':
|
||||||
|
Domoticz.Log(f"Switch to FROSTFREE because of the OFF bug...({device})")
|
||||||
|
self.set_mode(deviceid, HEATZY_MODE_VALUE['FROSTFREE'])
|
||||||
|
|
||||||
def setMode(self, mode):
|
def set_mode(self, deviceid, mode):
|
||||||
if Devices[2].nValue != mode:
|
"Set the device mode using the Heatzy API"
|
||||||
|
if Devices[deviceid].Units[HeatzyUnit.SELECTOR].nValue != mode:
|
||||||
mode_str = {
|
mode_str = {
|
||||||
HEATZY_MODE_VALUE['NORMAL']: '[1,1,0]',
|
HEATZY_MODE_VALUE['NORMAL']: 0, #'[1,1,0]',
|
||||||
HEATZY_MODE_VALUE['ECONOMY']: '[1,1,1]',
|
HEATZY_MODE_VALUE['ECONOMY']: 1, #'[1,1,1]',
|
||||||
HEATZY_MODE_VALUE['FROSTFREE']: '[1,1,2]',
|
HEATZY_MODE_VALUE['FROSTFREE']: 2, #'[1,1,2]',
|
||||||
HEATZY_MODE_VALUE['OFF']: '[1,1,3]'
|
HEATZY_MODE_VALUE['OFF']: 3, #'[1,1,3]',
|
||||||
}
|
}
|
||||||
headers = {
|
headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -349,87 +391,143 @@ class BasePlugin:
|
|||||||
'X-Gizwits-User-token': self.token,
|
'X-Gizwits-User-token': self.token,
|
||||||
'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)
|
data = '{"attrs": {"mode":' + str(mode_str[mode]) + '}}'
|
||||||
|
if deviceid not in self.did:
|
||||||
|
#Should not occur... but it occurs sometimes
|
||||||
|
self.did = self.get_heatzy_devices()
|
||||||
|
did = self.did[deviceid]["did"]
|
||||||
|
self.did[deviceid]["command_at"] = time.time()
|
||||||
|
url = f"https://euapi.gizwits.com/app/control/{did}"
|
||||||
try:
|
try:
|
||||||
response = requests.post(url, headers=headers, data=data, timeout=3).json()
|
response = http("post", url, headers, data)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
Domoticz.Error("Cannot open connection to Heatzy API to set the mode: " + str(exc))
|
Domoticz.Error("Cannot open connection to Heatzy API to set the mode: " + str(exc))
|
||||||
#Domoticz.Error("URL: " + str(url))
|
#Domoticz.Error("URL: " + str(url))
|
||||||
#Domoticz.Error("Headers: " + str(headers))
|
#Domoticz.Error("Headers: " + str(headers))
|
||||||
Domoticz.Error("Data: " + str(data))
|
Domoticz.Error("Data: " + str(data))
|
||||||
return self.mode
|
return
|
||||||
|
|
||||||
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]
|
mode_str = HEATZY_MODE_VALUE_INV[mode]
|
||||||
Devices[2].Update(nValue=int(mode), sValue=str(mode))
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].nValue = int(mode)
|
||||||
Domoticz.Status("New Heatzy Mode: {}".format(HEATZY_MODE_NAME[self.mode]))
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].sValue = str(mode)
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.SELECTOR].Update()
|
||||||
|
alias = self.did[deviceid]["alias"]
|
||||||
|
Domoticz.Status(f"New Heatzy Mode: {HEATZY_MODE_NAME[mode_str]} ({alias})")
|
||||||
if not self.bug:
|
if not self.bug:
|
||||||
if self.mode == 'OFF' and Devices[1].nValue != 0:
|
if mode_str == 'OFF' and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue != 0:
|
||||||
Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0)
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 0
|
||||||
elif self.mode != 'OFF' and Devices[1].nValue == 0:
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "Off"
|
||||||
Devices[1].Update(nValue=1, sValue="On", TimedOut = 0)
|
elif mode_str != 'OFF' and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue == 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 1
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "On"
|
||||||
else:
|
else:
|
||||||
if self.mode in ('OFF', 'FROSTFREE') and Devices[1].nValue != 0:
|
if mode_str in ('OFF', 'FROSTFREE') and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue != 0:
|
||||||
Devices[1].Update(nValue=0, sValue="Off", TimedOut = 0)
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 0
|
||||||
elif self.mode not in ('OFF', 'FROSTFREE') and Devices[1].nValue == 0:
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "Off"
|
||||||
Devices[1].Update(nValue=1, sValue="On", TimedOut = 0)
|
elif mode_str not in ('OFF', 'FROSTFREE') and Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue == 0:
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].nValue = 1
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue = "On"
|
||||||
|
Devices[deviceid].Units[HeatzyUnit.CONTROL].Update()
|
||||||
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: {did}")
|
||||||
if error_code == 9004:
|
if error_code == 9004:
|
||||||
#Invalid token
|
#Invalid token
|
||||||
self.token = ""
|
self.token = ""
|
||||||
self.did = ""
|
self.did = {}
|
||||||
return self.mode
|
return
|
||||||
|
|
||||||
#Device is correctly running ==> we reset the retry counter
|
#Device is correctly running ==> we reset the retry counter
|
||||||
self.retry = self.max_retry
|
self.reset_retry()
|
||||||
|
|
||||||
return self.mode
|
def send_command(self, deviceid, command):
|
||||||
|
"""Send command to device, only when it is not already in the requested state"""
|
||||||
def onOff(self, command):
|
if Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue != command:
|
||||||
if Devices[1].sValue != command:
|
|
||||||
if command == "On":
|
if command == "On":
|
||||||
self.mode = self.setMode(HEATZY_MODE_VALUE['NORMAL'])
|
self.set_mode(deviceid, HEATZY_MODE_VALUE['NORMAL'])
|
||||||
else:
|
else:
|
||||||
if not self.bug:
|
if not self.bug:
|
||||||
self.mode = self.setMode(HEATZY_MODE_VALUE['OFF'])
|
self.set_mode(deviceid, 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.set_mode(deviceid, HEATZY_MODE_VALUE['FROSTFREE'])
|
||||||
|
|
||||||
return self.mode
|
def toggle(self, deviceid):
|
||||||
|
"""Toggle device"""
|
||||||
|
new_command1 = "Off" if Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue == "On" else "On"
|
||||||
|
new_command2 = "On" if Devices[deviceid].Units[HeatzyUnit.CONTROL].sValue == "On" else "Off"
|
||||||
|
self.send_command(deviceid, new_command1)
|
||||||
|
time.sleep(5) #slepp 5 seconds
|
||||||
|
self.send_command(deviceid, new_command2)
|
||||||
|
|
||||||
|
def reset_retry(self):
|
||||||
|
"""Reset the retry counter"""
|
||||||
|
if self.retry != self._MAX_RETRY_PER_DEVICE * len(self.did):
|
||||||
|
Domoticz.Status("Reset retry counter")
|
||||||
|
self.retry = self._MAX_RETRY_PER_DEVICE * len(self.did)
|
||||||
|
|
||||||
|
def http(mode:str, url:str, headers:dict, data:str="", params:tuple|None=None)->dict:
|
||||||
|
"""HTTP GET/POST helper function"""
|
||||||
|
retries = 3
|
||||||
|
timeout = 10
|
||||||
|
|
||||||
|
retry = 0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if mode.upper() == "GET":
|
||||||
|
if data != "":
|
||||||
|
raise ValueError("'data' shall be empty.")
|
||||||
|
response = requests.get(url, headers=headers, params=params, timeout=timeout).json()
|
||||||
|
Domoticz.Debug("HTTP GET Response:" + str(response))
|
||||||
|
else:
|
||||||
|
if params is not None:
|
||||||
|
raise ValueError("'params' shall be None.")
|
||||||
|
response = requests.post(url, headers=headers, data=data, timeout=timeout).json()
|
||||||
|
Domoticz.Debug("HTTP POST Response:" + str(response))
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
retry = retry + 1
|
||||||
|
if retry >= retries:
|
||||||
|
raise
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
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(DeviceID, Unit, Command, Level, Color): #NOSONAR #pylint: disable=invalid-name
|
||||||
global _plugin
|
"""OnCommand"""
|
||||||
_plugin.onCommand(Unit, Command, Level, Hue)
|
_plugin.on_command(DeviceID, Unit, Command, Level, Color)
|
||||||
|
|
||||||
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]) + "'")
|
||||||
Domoticz.Debug("Device count: " + str(len(Devices)))
|
Domoticz.Debug("Device count: " + str(len(Devices)))
|
||||||
for x in Devices:
|
for device_name in Devices:
|
||||||
Domoticz.Debug("Device: " + str(x) + " - " + str(Devices[x]))
|
device = Devices[device_name]
|
||||||
Domoticz.Debug("Device ID: '" + str(Devices[x].ID) + "'")
|
Domoticz.Debug("Device ID: '" + str(device.DeviceID) + "'")
|
||||||
Domoticz.Debug("Device Name: '" + Devices[x].Name + "'")
|
Domoticz.Debug("--->Unit Count: '" + str(len(device.Units)) + "'")
|
||||||
Domoticz.Debug("Device nValue: " + str(Devices[x].nValue))
|
for unit_no in device.Units:
|
||||||
Domoticz.Debug("Device sValue: '" + Devices[x].sValue + "'")
|
unit = device.Units[unit_no]
|
||||||
Domoticz.Debug("Device LastLevel: " + str(Devices[x].LastLevel))
|
Domoticz.Debug("--->Unit: " + str(unit_no))
|
||||||
|
Domoticz.Debug("--->Unit Name: '" + unit.Name + "'")
|
||||||
|
Domoticz.Debug("--->Unit nValue: " + str(unit.nValue))
|
||||||
|
Domoticz.Debug("--->Unit sValue: '" + unit.sValue + "'")
|
||||||
|
Domoticz.Debug("--->Unit LastLevel: " + str(unit.LastLevel))
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user