Compare commits

...

5 Commits

Author SHA1 Message Date
84000fb76c Bump version 2023-06-10 06:50:11 +02:00
26528a7310 Don't send optional program rules 2023-06-10 06:49:45 +02:00
11988c73a6 Fix command parameter issue hon#63 2023-06-09 02:09:20 +02:00
7b51caecca Improve update performance 2023-06-08 19:52:08 +02:00
38d09e2ef5 Fix step is 0 hon#60 2023-06-08 14:20:08 +02:00
16 changed files with 95 additions and 65 deletions

View File

@ -242,15 +242,20 @@ class HonAppliance:
self._attributes = await self.api.load_attributes(self) self._attributes = await self.api.load_attributes(self)
for name, values in self._attributes.pop("shadow").get("parameters").items(): for name, values in self._attributes.pop("shadow").get("parameters").items():
self._attributes.setdefault("parameters", {})[name] = values["parNewVal"] self._attributes.setdefault("parameters", {})[name] = values["parNewVal"]
if self._extra:
self._attributes = self._extra.attributes(self._attributes)
async def load_statistics(self): async def load_statistics(self):
self._statistics = await self.api.load_statistics(self) self._statistics = await self.api.load_statistics(self)
self._statistics |= await self.api.load_maintenance(self) self._statistics |= await self.api.load_maintenance(self)
async def update(self): async def update(self, force=False):
now = datetime.now() now = datetime.now()
if not self._last_update or self._last_update < now - timedelta( if (
seconds=self._MINIMAL_UPDATE_INTERVAL force
or not self._last_update
or self._last_update
< now - timedelta(seconds=self._MINIMAL_UPDATE_INTERVAL)
): ):
self._last_update = now self._last_update = now
await self.load_attributes() await self.load_attributes()
@ -286,9 +291,8 @@ class HonAppliance:
"statistics": self.statistics, "statistics": self.statistics,
"additional_data": self._additional_data, "additional_data": self._additional_data,
**self.command_parameters, **self.command_parameters,
**self.attributes,
} }
if self._extra:
return self._extra.data(result)
return result return result
def diagnose(self, whitespace=" ", command_only=False): def diagnose(self, whitespace=" ", command_only=False):
@ -298,8 +302,6 @@ class HonAppliance:
"statistics": self.statistics, "statistics": self.statistics,
"additional_data": self._additional_data, "additional_data": self._additional_data,
} }
if self._extra and data.get("attributes"):
data = self._extra.data(data)
if command_only: if command_only:
data.pop("attributes") data.pop("attributes")
data.pop("appliance") data.pop("appliance")
@ -320,6 +322,12 @@ class HonAppliance:
) )
return result.replace(self.mac_address, "xx-xx-xx-xx-xx-xx") return result.replace(self.mac_address, "xx-xx-xx-xx-xx-xx")
def sync_to_params(self, command_name):
command: HonCommand = self.commands.get(command_name)
for key, value in self.attributes.get("parameters", {}).items():
if isinstance(value, str) and (new := command.parameters.get(key)):
self.attributes["parameters"][key] = str(new.intern_value)
def sync_command(self, main, target=None) -> None: def sync_command(self, main, target=None) -> None:
base: HonCommand = self.commands.get(main) base: HonCommand = self.commands.get(main)
for command, data in self.commands.items(): for command, data in self.commands.items():
@ -345,6 +353,7 @@ class HonApplianceTest(HonAppliance):
super().__init__(None, {}) super().__init__(None, {})
self._name = name self._name = name
self.load_commands() self.load_commands()
self.load_attributes()
self._info = self._appliance_model self._info = self._appliance_model
def load_commands(self): def load_commands(self):

View File

@ -2,12 +2,14 @@ class ApplianceBase:
def __init__(self, appliance): def __init__(self, appliance):
self.parent = appliance self.parent = appliance
def data(self, data): def attributes(self, data):
program_name = "No Program" program_name = "No Program"
if program := int(data["attributes"]["parameters"].get("prCode", "0")): if program := int(data["parameters"].get("prCode", "0")):
if ids := self.parent.settings["startProgram.program"].ids: if start_cmd := self.parent.settings.get("startProgram.program"):
if ids := start_cmd.ids:
program_name = ids.get(program, program_name) program_name = ids.get(program, program_name)
data["programName"] = program_name data["programName"] = program_name
return data
def settings(self, settings): def settings(self, settings):
return settings return settings

View File

@ -2,9 +2,9 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["lastConnEvent"]["category"] == "DISCONNECTED": if data["lastConnEvent"]["category"] == "DISCONNECTED":
data["attributes"]["parameters"]["machMode"] = "0" data["parameters"]["machMode"] = "0"
data["active"] = bool(data.get("attributes", {}).get("activity")) data["active"] = bool(data.get("activity"))
return data return data

View File

@ -2,17 +2,17 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["lastConnEvent"]["category"] == "DISCONNECTED": if data["lastConnEvent"]["category"] == "DISCONNECTED":
data["attributes"]["parameters"]["temp"] = "0" data["parameters"]["temp"] = "0"
data["attributes"]["parameters"]["onOffStatus"] = "0" data["parameters"]["onOffStatus"] = "0"
data["attributes"]["parameters"]["remoteCtrValid"] = "0" data["parameters"]["remoteCtrValid"] = "0"
data["attributes"]["parameters"]["remainingTimeMM"] = "0" data["parameters"]["remainingTimeMM"] = "0"
data["active"] = data["attributes"]["parameters"]["onOffStatus"] == "1" data["active"] = data["parameters"]["onOffStatus"] == "1"
if program := int(data["attributes"]["parameters"]["prCode"]): if program := int(data["parameters"]["prCode"]):
ids = self.parent.settings["startProgram.program"].ids ids = self.parent.settings["startProgram.program"].ids
data["programName"] = ids.get(program, "") data["programName"] = ids.get(program, "")

View File

@ -2,20 +2,20 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["parameters"]["holidayMode"] == "1": if data["parameters"]["holidayMode"] == "1":
data["modeZ1"] = "holiday" data["modeZ1"] = "holiday"
elif data["attributes"]["parameters"]["intelligenceMode"] == "1": elif data["parameters"]["intelligenceMode"] == "1":
data["modeZ1"] = "auto_set" data["modeZ1"] = "auto_set"
elif data["attributes"]["parameters"]["quickModeZ1"] == "1": elif data["parameters"]["quickModeZ1"] == "1":
data["modeZ1"] = "super_cool" data["modeZ1"] = "super_cool"
else: else:
data["modeZ1"] = "no_mode" data["modeZ1"] = "no_mode"
if data["attributes"]["parameters"]["quickModeZ2"] == "1": if data["parameters"]["quickModeZ2"] == "1":
data["modeZ2"] = "super_freeze" data["modeZ2"] = "super_freeze"
elif data["attributes"]["parameters"]["intelligenceMode"] == "1": elif data["parameters"]["intelligenceMode"] == "1":
data["modeZ2"] = "auto_set" data["modeZ2"] = "auto_set"
else: else:
data["modeZ2"] = "no_mode" data["modeZ2"] = "no_mode"

View File

@ -3,12 +3,12 @@ from pyhon.parameter.fixed import HonParameterFixed
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["lastConnEvent"]["category"] == "DISCONNECTED": if data["lastConnEvent"]["category"] == "DISCONNECTED":
data["attributes"]["parameters"]["machMode"] = "0" data["parameters"]["machMode"] = "0"
data["active"] = bool(data.get("attributes", {}).get("activity")) data["active"] = bool(data.get("activity"))
data["pause"] = data["attributes"]["parameters"]["machMode"] == "3" data["pause"] = data["parameters"]["machMode"] == "3"
return data return data
def settings(self, settings): def settings(self, settings):

5
pyhon/appliances/wc.py Normal file
View File

@ -0,0 +1,5 @@
from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase):
pass

View File

@ -2,12 +2,12 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["lastConnEvent"]["category"] == "DISCONNECTED": if data["lastConnEvent"]["category"] == "DISCONNECTED":
data["attributes"]["parameters"]["machMode"] = "0" data["parameters"]["machMode"] = "0"
data["active"] = bool(data.get("attributes", {}).get("activity")) data["active"] = bool(data.get("activity"))
data["pause"] = data["attributes"]["parameters"]["machMode"] == "3" data["pause"] = data["parameters"]["machMode"] == "3"
return data return data
def settings(self, settings): def settings(self, settings):

View File

@ -2,12 +2,12 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def data(self, data): def attributes(self, data):
super().data(data) data = super().attributes(data)
if data["attributes"]["lastConnEvent"]["category"] == "DISCONNECTED": if data["lastConnEvent"]["category"] == "DISCONNECTED":
data["attributes"]["parameters"]["machMode"] = "0" data["parameters"]["machMode"] = "0"
data["active"] = bool(data.get("attributes", {}).get("activity")) data["active"] = bool(data.get("activity"))
data["pause"] = data["attributes"]["parameters"]["machMode"] == "3" data["pause"] = data["parameters"]["machMode"] == "3"
return data return data
def settings(self, settings): def settings(self, settings):

View File

@ -2,6 +2,7 @@ import logging
from typing import Optional, Dict, Any, List, TYPE_CHECKING, Union from typing import Optional, Dict, Any, List, TYPE_CHECKING, Union
from pyhon import exceptions from pyhon import exceptions
from pyhon.exceptions import ApiError
from pyhon.parameter.base import HonParameter from pyhon.parameter.base import HonParameter
from pyhon.parameter.enum import HonParameterEnum from pyhon.parameter.enum import HonParameterEnum
from pyhon.parameter.fixed import HonParameterFixed from pyhon.parameter.fixed import HonParameterFixed
@ -110,9 +111,14 @@ class HonCommand:
async def send(self) -> bool: async def send(self) -> bool:
params = self.parameter_groups.get("parameters", {}) params = self.parameter_groups.get("parameters", {})
ancillary_params = self.parameter_groups.get("ancillaryParameters", {}) ancillary_params = self.parameter_groups.get("ancillaryParameters", {})
return await self.api.send_command( ancillary_params.pop("programRules", None)
self.appliance.sync_to_params(self.name)
result = await self.api.send_command(
self._appliance, self._name, params, ancillary_params self._appliance, self._name, params, ancillary_params
) )
if not result:
raise ApiError("Can't send command")
return result
@property @property
def categories(self) -> Dict[str, "HonCommand"]: def categories(self) -> Dict[str, "HonCommand"]:

View File

@ -1,6 +1,7 @@
import json import json
import logging import logging
from datetime import datetime from datetime import datetime
from pprint import pformat
from typing import Dict, Optional from typing import Dict, Optional
from aiohttp import ClientSession from aiohttp import ClientSession
@ -188,6 +189,7 @@ class HonAPI:
if json_data.get("payload", {}).get("resultCode") == "0": if json_data.get("payload", {}).get("resultCode") == "0":
return True return True
_LOGGER.error(await response.text()) _LOGGER.error(await response.text())
_LOGGER.error("%s - Payload:\n%s", url, pformat(data))
return False return False
async def appliance_configuration(self) -> Dict: async def appliance_configuration(self) -> Dict:

View File

@ -12,3 +12,7 @@ class NoSessionException(Exception):
class NoAuthenticationException(Exception): class NoAuthenticationException(Exception):
pass pass
class ApiError(Exception):
pass

View File

@ -28,8 +28,8 @@ class HonParameter:
self.check_trigger(value) self.check_trigger(value)
@property @property
def intern_value(self) -> str | float: def intern_value(self) -> str:
return str(self._value) if self._value is not None else "" return str(self.value)
@property @property
def values(self) -> List[str]: def values(self) -> List[str]:

View File

@ -27,6 +27,10 @@ class HonParameterEnum(HonParameter):
def values(self, values) -> None: def values(self, values) -> None:
self._values = values self._values = values
@property
def intern_value(self) -> str:
return str(self._value) if self._value is not None else str(self.values[0])
@property @property
def value(self) -> str | float: def value(self) -> str | float:
return clean_value(self._value) if self._value is not None else self.values[0] return clean_value(self._value) if self._value is not None else self.values[0]

View File

@ -16,27 +16,27 @@ class HonParameterRange(HonParameter):
self._min: float = str_to_float(attributes["minimumValue"]) self._min: float = str_to_float(attributes["minimumValue"])
self._max: float = str_to_float(attributes["maximumValue"]) self._max: float = str_to_float(attributes["maximumValue"])
self._step: float = str_to_float(attributes["incrementValue"]) self._step: float = str_to_float(attributes["incrementValue"])
self._default: float = str_to_float(attributes.get("defaultValue", self._min)) self._default: float = str_to_float(attributes.get("defaultValue", self.min))
self._value: float = self._default self._value: float = self._default
def __repr__(self): def __repr__(self):
return f"{self.__class__} (<{self.key}> [{self._min} - {self._max}])" return f"{self.__class__} (<{self.key}> [{self.min} - {self.max}])"
@property @property
def min(self) -> float: def min(self) -> float:
return self._min return self._min
@min.setter @min.setter
def min(self, min: float) -> None: def min(self, mini: float) -> None:
self._min = min self._min = mini
@property @property
def max(self) -> float: def max(self) -> float:
return self._max return self._max
@max.setter @max.setter
def max(self, max: float) -> None: def max(self, maxi: float) -> None:
self._max = max self._max = maxi
@property @property
def step(self) -> float: def step(self) -> float:
@ -50,18 +50,16 @@ class HonParameterRange(HonParameter):
@property @property
def value(self) -> str | float: def value(self) -> str | float:
return self._value if self._value is not None else self._min return self._value if self._value is not None else self.min
@value.setter @value.setter
def value(self, value: str | float) -> None: def value(self, value: str | float) -> None:
value = str_to_float(value) value = str_to_float(value)
if self._min <= value <= self._max and not (value - self._min) % self._step: if self.min <= value <= self.max and not (value - self.min) % self.step:
self._value = value self._value = value
self.check_trigger(value) self.check_trigger(value)
else: else:
raise ValueError( raise ValueError(f"Allowed: min {self.min} max {self.max} step {self.step}")
f"Allowed: min {self._min} max {self._max} step {self._step}"
)
@property @property
def values(self) -> List[str]: def values(self) -> List[str]:

View File

@ -7,7 +7,7 @@ with open("README.md", "r") as f:
setup( setup(
name="pyhOn", name="pyhOn",
version="0.12.1", version="0.12.4",
author="Andre Basche", author="Andre Basche",
description="Control hOn devices with python", description="Control hOn devices with python",
long_description=long_description, long_description=long_description,