Compare commits

...

6 Commits

Author SHA1 Message Date
c25e898b42 Bump version 2023-06-21 19:56:09 +02:00
55966dd52f Fix typeerror hon#77 2023-06-21 18:02:07 +02:00
310d1bafd7 Improve rule parsing 2023-06-10 06:47:37 +02:00
9e35dcf9cf Don't send optional program rules 2023-06-10 06:40:55 +02:00
f9d0fa4ae8 Add wine cellar 2023-06-09 22:52:33 +02:00
11988c73a6 Fix command parameter issue hon#63 2023-06-09 02:09:20 +02:00
14 changed files with 130 additions and 49 deletions

View File

@ -32,7 +32,7 @@ class HonAppliance:
self._api: Optional[HonAPI] = api self._api: Optional[HonAPI] = api
self._appliance_model: Dict = {} self._appliance_model: Dict = {}
self._commands: Dict = {} self._commands: Dict[str, HonCommand] = {}
self._statistics: Dict = {} self._statistics: Dict = {}
self._attributes: Dict = {} self._attributes: Dict = {}
self._zone: int = zone self._zone: int = zone
@ -112,7 +112,7 @@ class HonAppliance:
return self._appliance_model.get("options", {}) return self._appliance_model.get("options", {})
@property @property
def commands(self): def commands(self) -> Dict[str, HonCommand]:
return self._commands return self._commands
@property @property
@ -302,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")
@ -331,7 +329,9 @@ class HonAppliance:
self.attributes["parameters"][key] = str(new.intern_value) 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: Optional[HonCommand] = self.commands.get(main)
if not base:
return
for command, data in self.commands.items(): for command, data in self.commands.items():
if command == main or target and command not in target: if command == main or target and command not in target:
continue continue

View File

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

View File

@ -4,7 +4,7 @@ from pyhon.appliances.base import ApplianceBase
class Appliance(ApplianceBase): class Appliance(ApplianceBase):
def attributes(self, data): def attributes(self, data):
data = super().attributes(data) data = super().attributes(data)
if data["lastConnEvent"]["category"] == "DISCONNECTED": if data.get("lastConnEvent", {}).get("category", "") == "DISCONNECTED":
data["parameters"]["temp"] = "0" data["parameters"]["temp"] = "0"
data["parameters"]["onOffStatus"] = "0" data["parameters"]["onOffStatus"] = "0"
data["parameters"]["remoteCtrValid"] = "0" data["parameters"]["remoteCtrValid"] = "0"

View File

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

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

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

View File

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

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, NoAuthenticationException
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,10 +111,19 @@ 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", {})
ancillary_params.pop("programRules", None)
self.appliance.sync_to_params(self.name) self.appliance.sync_to_params(self.name)
return await self.api.send_command( try:
self._appliance, self._name, params, ancillary_params result = await self.api.send_command(
) self._appliance, self._name, params, ancillary_params
)
if not result:
_LOGGER.error(result)
raise ApiError("Can't send command")
except NoAuthenticationException:
_LOGGER.error("No Authentication")
return False
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]:
@ -66,5 +66,18 @@ class HonParameter:
def triggers(self): def triggers(self):
result = {} result = {}
for value, rules in self._triggers.items(): for value, rules in self._triggers.items():
result[value] = {rule.param_key: rule.param_value for _, rule in rules} for _, rule in rules:
if rule.extras:
param = result.setdefault(value, {})
for extra_key, extra_value in rule.extras.items():
param = param.setdefault(extra_key, {}).setdefault(
extra_value, {}
)
else:
param = result.setdefault(value, {})
if fixed_value := rule.param_data.get("fixedValue"):
param[rule.param_key] = fixed_value
else:
param[rule.param_key] = rule.param_data.get("defaultValue", "")
return result return result

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

@ -1,5 +1,5 @@
from dataclasses import dataclass from dataclasses import dataclass
from typing import List, Dict, TYPE_CHECKING from typing import List, Dict, TYPE_CHECKING, Any, Optional
from pyhon.parameter.enum import HonParameterEnum from pyhon.parameter.enum import HonParameterEnum
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
@ -13,7 +13,8 @@ class HonRule:
trigger_key: str trigger_key: str
trigger_value: str trigger_value: str
param_key: str param_key: str
param_value: str param_data: Dict[str, Any]
extras: Optional[Dict[str, str]] = None
class HonRuleSet: class HonRuleSet:
@ -23,40 +24,82 @@ class HonRuleSet:
self._parse_rule(rule) self._parse_rule(rule)
def _parse_rule(self, rule): def _parse_rule(self, rule):
for entity_key, params in rule.items(): for param_key, params in rule.items():
entity_key = self._command.appliance.options.get(entity_key, entity_key) param_key = self._command.appliance.options.get(param_key, param_key)
for trigger_key, values in params.items(): for trigger_key, trigger_data in params.items():
trigger_key = trigger_key.replace("@", "") self._parse_conditions(param_key, trigger_key, trigger_data)
trigger_key = self._command.appliance.options.get(
trigger_key, trigger_key def _parse_conditions(self, param_key, trigger_key, trigger_data, extra=None):
) trigger_key = trigger_key.replace("@", "")
for trigger_value, entity_value in values.items(): trigger_key = self._command.appliance.options.get(trigger_key, trigger_key)
if entity_value.get("fixedValue") == f"@{entity_key}": for multi_trigger_value, param_data in trigger_data.items():
continue for trigger_value in multi_trigger_value.split("|"):
self._rules.setdefault(trigger_key, []).append( if isinstance(param_data, dict) and "typology" in param_data:
HonRule( self._create_rule(
trigger_key, param_key, trigger_key, trigger_value, param_data, extra
trigger_value,
entity_key,
entity_value.get("fixedValue"),
)
) )
elif isinstance(param_data, dict):
if extra is None:
extra = {}
extra[trigger_key] = trigger_value
for extra_key, extra_data in param_data.items():
self._parse_conditions(param_key, extra_key, extra_data, extra)
def _create_rule(
self, param_key, trigger_key, trigger_value, param_data, extras=None
):
if param_data.get("fixedValue") == f"@{param_key}":
return
self._rules.setdefault(trigger_key, []).append(
HonRule(trigger_key, trigger_value, param_key, param_data, extras)
)
def _duplicate_for_extra_conditions(self):
new = {}
for rules in self._rules.values():
for rule in rules:
if rule.extras is None:
continue
for key, value in rule.extras.items():
extras = rule.extras.copy()
extras.pop(key)
extras[rule.trigger_key] = rule.trigger_value
new.setdefault(key, []).append(
HonRule(key, value, rule.param_key, rule.param_data, extras)
)
for key, rules in new.items():
for rule in rules:
self._rules.setdefault(key, []).append(rule)
def _add_trigger(self, parameter, data):
def apply(rule: HonRule):
if rule.extras is not None:
for key, value in rule.extras.items():
if str(self._command.parameters.get(key)) != str(value):
return
if param := self._command.parameters.get(rule.param_key):
if value := rule.param_data.get("fixedValue", ""):
if isinstance(param, HonParameterEnum) and set(param.values) != {
str(value)
}:
param.values = [str(value)]
elif isinstance(param, HonParameterRange):
param.value = float(value)
return
param.value = str(value)
elif rule.param_data.get("typology") == "enum":
if isinstance(param, HonParameterEnum):
if enum_values := rule.param_data.get("enumValues"):
param.values = enum_values.split("|")
if default_value := rule.param_data.get("defaultValue"):
param.value = default_value
parameter.add_trigger(data.trigger_value, apply, data)
def patch(self): def patch(self):
self._duplicate_for_extra_conditions()
for name, parameter in self._command.parameters.items(): for name, parameter in self._command.parameters.items():
if name not in self._rules: if name not in self._rules:
continue continue
for data in self._rules.get(name): for data in self._rules.get(name):
self._add_trigger(parameter, data)
def apply(rule):
if param := self._command.parameters.get(rule.param_key):
if isinstance(param, HonParameterEnum) and set(
param.values
) != {str(rule.param_value)}:
param.values = [str(rule.param_value)]
elif isinstance(param, HonParameterRange):
param.value = float(rule.param_value)
return
param.value = str(rule.param_value)
parameter.add_trigger(data.trigger_value, apply, data)

View File

@ -7,7 +7,7 @@ with open("README.md", "r") as f:
setup( setup(
name="pyhOn", name="pyhOn",
version="0.12.2", version="0.13.1",
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,