Compare commits
	
		
			14 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| bc7e8994c9 | |||
| 8ca40d7ad0 | |||
| 9a6a07fd46 | |||
| f1818bbc5d | |||
| 3d5c8405ea | |||
| e234ef3bbb | |||
| e00e147ecd | |||
| 26bc35c8a6 | |||
| 17d73cdeb8 | |||
| a10ab4423e | |||
| 0553e6c17d | |||
| 44f40c531e | |||
| 4e88bc7a9f | |||
| b5d8a90d79 | 
							
								
								
									
										5
									
								
								mypy.ini
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								mypy.ini
									
									
									
									
									
								
							| @ -2,3 +2,8 @@ | |||||||
| check_untyped_defs = True | check_untyped_defs = True | ||||||
| disallow_any_generics = True | disallow_any_generics = True | ||||||
| disallow_untyped_defs = True | disallow_untyped_defs = True | ||||||
|  | disallow_any_unimported = True | ||||||
|  | no_implicit_optional = True | ||||||
|  | warn_return_any = True | ||||||
|  | show_error_codes = True | ||||||
|  | warn_unused_ignores = True | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ async def main() -> None: | |||||||
|                 print(printer.key_print(data)) |                 print(printer.key_print(data)) | ||||||
|                 print( |                 print( | ||||||
|                     printer.pretty_print( |                     printer.pretty_print( | ||||||
|                         printer.create_command(device.commands, concat=True) |                         printer.create_commands(device.commands, concat=True) | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             else: |             else: | ||||||
|  | |||||||
| @ -6,10 +6,12 @@ from pathlib import Path | |||||||
| from typing import Optional, Dict, Any, TYPE_CHECKING, List | from typing import Optional, Dict, Any, TYPE_CHECKING, List | ||||||
|  |  | ||||||
| from pyhon import diagnose, exceptions | from pyhon import diagnose, exceptions | ||||||
|  | from pyhon.appliances.base import ApplianceBase | ||||||
| from pyhon.attributes import HonAttribute | from pyhon.attributes import HonAttribute | ||||||
| from pyhon.command_loader import HonCommandLoader | from pyhon.command_loader import HonCommandLoader | ||||||
| from pyhon.commands import HonCommand | from pyhon.commands import HonCommand | ||||||
| from pyhon.parameter.base import HonParameter | from pyhon.parameter.base import HonParameter | ||||||
|  | from pyhon.parameter.enum import HonParameterEnum | ||||||
| from pyhon.parameter.range import HonParameterRange | from pyhon.parameter.range import HonParameterRange | ||||||
| from pyhon.typedefs import Parameter | from pyhon.typedefs import Parameter | ||||||
|  |  | ||||||
| @ -40,7 +42,7 @@ class HonAppliance: | |||||||
|         self._default_setting = HonParameter("", {}, "") |         self._default_setting = HonParameter("", {}, "") | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             self._extra = importlib.import_module( |             self._extra: Optional[ApplianceBase] = importlib.import_module( | ||||||
|                 f"pyhon.appliances.{self.appliance_type.lower()}" |                 f"pyhon.appliances.{self.appliance_type.lower()}" | ||||||
|             ).Appliance(self) |             ).Appliance(self) | ||||||
|         except ModuleNotFoundError: |         except ModuleNotFoundError: | ||||||
| @ -71,7 +73,8 @@ class HonAppliance: | |||||||
|  |  | ||||||
|     def _check_name_zone(self, name: str, frontend: bool = True) -> str: |     def _check_name_zone(self, name: str, frontend: bool = True) -> str: | ||||||
|         zone = " Z" if frontend else "_z" |         zone = " Z" if frontend else "_z" | ||||||
|         if (attribute := self._info.get(name, "")) and self._zone: |         attribute: str = self._info.get(name, "") | ||||||
|  |         if attribute and self._zone: | ||||||
|             return f"{attribute}{zone}{self._zone}" |             return f"{attribute}{zone}{self._zone}" | ||||||
|         return attribute |         return attribute | ||||||
|  |  | ||||||
| @ -101,20 +104,22 @@ class HonAppliance: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def brand(self) -> str: |     def brand(self) -> str: | ||||||
|         return self._check_name_zone("brand") |         brand = self._check_name_zone("brand") | ||||||
|  |         return brand[0].upper() + brand[1:] | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def nick_name(self) -> str: |     def nick_name(self) -> str: | ||||||
|         result = self._check_name_zone("nickName") |         result = self._check_name_zone("nickName") | ||||||
|         if not result or re.findall("^[xX\s]+$", result): |         if not result or re.findall("^[xX1\\s-]+$", result): | ||||||
|             return self.model_name |             return self.model_name | ||||||
|         return result |         return result | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def code(self) -> str: |     def code(self) -> str: | ||||||
|         if code := self.info.get("code"): |         code: str = self.info.get("code", "") | ||||||
|  |         if code: | ||||||
|             return code |             return code | ||||||
|         serial_number = self.info.get("serialNumber", "") |         serial_number: str = self.info.get("serialNumber", "") | ||||||
|         return serial_number[:8] if len(serial_number) < 18 else serial_number[:11] |         return serial_number[:8] if len(serial_number) < 18 else serial_number[:11] | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
| @ -162,16 +167,18 @@ class HonAppliance: | |||||||
|         self._commands = command_loader.commands |         self._commands = command_loader.commands | ||||||
|         self._additional_data = command_loader.additional_data |         self._additional_data = command_loader.additional_data | ||||||
|         self._appliance_model = command_loader.appliance_data |         self._appliance_model = command_loader.appliance_data | ||||||
|  |         self.sync_params_to_command("settings") | ||||||
|  |  | ||||||
|     async def load_attributes(self) -> None: |     async def load_attributes(self) -> None: | ||||||
|         self._attributes = await self.api.load_attributes(self) |         attributes = await self.api.load_attributes(self) | ||||||
|         for name, values in self._attributes.pop("shadow").get("parameters").items(): |         for name, values in attributes.pop("shadow", {}).get("parameters", {}).items(): | ||||||
|             if name in self._attributes.get("parameters", {}): |             if name in self._attributes.get("parameters", {}): | ||||||
|                 self._attributes["parameters"][name].update(values) |                 self._attributes["parameters"][name].update(values) | ||||||
|             else: |             else: | ||||||
|                 self._attributes.setdefault("parameters", {})[name] = HonAttribute( |                 self._attributes.setdefault("parameters", {})[name] = HonAttribute( | ||||||
|                     values |                     values | ||||||
|                 ) |                 ) | ||||||
|  |         self._attributes |= attributes | ||||||
|         if self._extra: |         if self._extra: | ||||||
|             self._attributes = self._extra.attributes(self._attributes) |             self._attributes = self._extra.attributes(self._attributes) | ||||||
|  |  | ||||||
| @ -189,6 +196,7 @@ class HonAppliance: | |||||||
|         ): |         ): | ||||||
|             self._last_update = now |             self._last_update = now | ||||||
|             await self.load_attributes() |             await self.load_attributes() | ||||||
|  |             self.sync_params_to_command("settings") | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def command_parameters(self) -> Dict[str, Dict[str, str | float]]: |     def command_parameters(self) -> Dict[str, Dict[str, str | float]]: | ||||||
| @ -196,7 +204,7 @@ class HonAppliance: | |||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def settings(self) -> Dict[str, Parameter]: |     def settings(self) -> Dict[str, Parameter]: | ||||||
|         result = {} |         result: Dict[str, Parameter] = {} | ||||||
|         for name, command in self._commands.items(): |         for name, command in self._commands.items(): | ||||||
|             for key in command.setting_keys: |             for key in command.setting_keys: | ||||||
|                 setting = command.settings.get(key, self._default_setting) |                 setting = command.settings.get(key, self._default_setting) | ||||||
| @ -232,16 +240,32 @@ class HonAppliance: | |||||||
|     async def data_archive(self, path: Path) -> str: |     async def data_archive(self, path: Path) -> str: | ||||||
|         return await diagnose.zip_archive(self, path, anonymous=True) |         return await diagnose.zip_archive(self, path, anonymous=True) | ||||||
|  |  | ||||||
|     def sync_to_params(self, command_name: str) -> None: |     def sync_command_to_params(self, command_name: str) -> None: | ||||||
|         if not (command := self.commands.get(command_name)): |         if not (command := self.commands.get(command_name)): | ||||||
|             return |             return | ||||||
|         for key, value in self.attributes.get("parameters", {}).items(): |         for key in self.attributes.get("parameters", {}): | ||||||
|             if isinstance(value, str) and (new := command.parameters.get(key)): |             if new := command.parameters.get(key): | ||||||
|                 self.attributes["parameters"][key].update( |                 self.attributes["parameters"][key].update( | ||||||
|                     str(new.intern_value), shield=True |                     str(new.intern_value), shield=True | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|     def sync_command(self, main: str, target: Optional[List[str]] = None) -> None: |     def sync_params_to_command(self, command_name: str) -> None: | ||||||
|  |         if not (command := self.commands.get(command_name)): | ||||||
|  |             return | ||||||
|  |         for key in command.setting_keys: | ||||||
|  |             if (new := self.attributes.get("parameters", {}).get(key)) is None: | ||||||
|  |                 continue | ||||||
|  |             setting = command.settings[key] | ||||||
|  |             try: | ||||||
|  |                 if not isinstance(setting, HonParameterRange): | ||||||
|  |                     command.settings[key].value = str(new.value) | ||||||
|  |                 else: | ||||||
|  |                     command.settings[key].value = float(new.value) | ||||||
|  |             except ValueError as error: | ||||||
|  |                 _LOGGER.info("Can't set %s - %s", key, error) | ||||||
|  |                 continue | ||||||
|  |  | ||||||
|  |     def sync_command(self, main: str, target: Optional[List[str] | str] = None) -> None: | ||||||
|         base: Optional[HonCommand] = self.commands.get(main) |         base: Optional[HonCommand] = self.commands.get(main) | ||||||
|         if not base: |         if not base: | ||||||
|             return |             return | ||||||
| @ -260,4 +284,6 @@ class HonAppliance: | |||||||
|                         parameter.max = int(base_value.value) |                         parameter.max = int(base_value.value) | ||||||
|                         parameter.min = int(base_value.value) |                         parameter.min = int(base_value.value) | ||||||
|                         parameter.step = 1 |                         parameter.step = 1 | ||||||
|  |                     elif isinstance(parameter, HonParameterEnum): | ||||||
|  |                         parameter.values = base_value.values | ||||||
|                     parameter.value = base_value.value |                     parameter.value = base_value.value | ||||||
|  | |||||||
| @ -14,11 +14,4 @@ class Appliance(ApplianceBase): | |||||||
|             data["parameters"]["remainingTimeMM"].value = "0" |             data["parameters"]["remainingTimeMM"].value = "0" | ||||||
|  |  | ||||||
|         data["active"] = data["parameters"]["onOffStatus"] == "1" |         data["active"] = data["parameters"]["onOffStatus"] == "1" | ||||||
|  |  | ||||||
|         if program := int(data["parameters"]["prCode"]): |  | ||||||
|             if (setting := self.parent.settings["startProgram.program"]) and isinstance( |  | ||||||
|                 setting, HonParameterProgram |  | ||||||
|             ): |  | ||||||
|                 data["programName"] = setting.ids.get(program, "") |  | ||||||
|  |  | ||||||
|         return data |         return data | ||||||
|  | |||||||
| @ -55,7 +55,7 @@ class HonCommandLoader: | |||||||
|     async def load_commands(self) -> None: |     async def load_commands(self) -> None: | ||||||
|         """Trigger loading of command data""" |         """Trigger loading of command data""" | ||||||
|         await self._load_data() |         await self._load_data() | ||||||
|         self._appliance_data = self._api_commands.pop("applianceModel") |         self._appliance_data = self._api_commands.pop("applianceModel", {}) | ||||||
|         self._get_commands() |         self._get_commands() | ||||||
|         self._add_favourites() |         self._add_favourites() | ||||||
|         self._recover_last_command_states() |         self._recover_last_command_states() | ||||||
|  | |||||||
| @ -115,7 +115,7 @@ class HonCommand: | |||||||
|         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) |         ancillary_params.pop("programRules", None) | ||||||
|         self.appliance.sync_to_params(self.name) |         self.appliance.sync_command_to_params(self.name) | ||||||
|         try: |         try: | ||||||
|             result = await self.api.send_command( |             result = await self.api.send_command( | ||||||
|                 self._appliance, self._name, params, ancillary_params |                 self._appliance, self._name, params, ancillary_params | ||||||
|  | |||||||
| @ -75,8 +75,12 @@ class HonAPI: | |||||||
|  |  | ||||||
|     async def load_appliances(self) -> List[Dict[str, Any]]: |     async def load_appliances(self) -> List[Dict[str, Any]]: | ||||||
|         async with self._hon.get(f"{const.API_URL}/commands/v1/appliance") as resp: |         async with self._hon.get(f"{const.API_URL}/commands/v1/appliance") as resp: | ||||||
|             if result := await resp.json(): |             result = await resp.json() | ||||||
|                 return result.get("payload", {}).get("appliances", {}) |         if result: | ||||||
|  |             appliances: List[Dict[str, Any]] = result.get("payload", {}).get( | ||||||
|  |                 "appliances", {} | ||||||
|  |             ) | ||||||
|  |             return appliances | ||||||
|         return [] |         return [] | ||||||
|  |  | ||||||
|     async def load_commands(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_commands(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
| @ -110,9 +114,10 @@ class HonAPI: | |||||||
|         ) |         ) | ||||||
|         async with self._hon.get(url) as response: |         async with self._hon.get(url) as response: | ||||||
|             result: Dict[str, Any] = await response.json() |             result: Dict[str, Any] = await response.json() | ||||||
|             if not result or not result.get("payload"): |         if not result or not result.get("payload"): | ||||||
|                 return [] |             return [] | ||||||
|             return result["payload"]["history"] |         command_history: List[Dict[str, Any]] = result["payload"]["history"] | ||||||
|  |         return command_history | ||||||
|  |  | ||||||
|     async def load_favourites(self, appliance: HonAppliance) -> List[Dict[str, Any]]: |     async def load_favourites(self, appliance: HonAppliance) -> List[Dict[str, Any]]: | ||||||
|         url: str = ( |         url: str = ( | ||||||
| @ -120,17 +125,20 @@ class HonAPI: | |||||||
|         ) |         ) | ||||||
|         async with self._hon.get(url) as response: |         async with self._hon.get(url) as response: | ||||||
|             result: Dict[str, Any] = await response.json() |             result: Dict[str, Any] = await response.json() | ||||||
|             if not result or not result.get("payload"): |         if not result or not result.get("payload"): | ||||||
|                 return [] |             return [] | ||||||
|             return result["payload"]["favourites"] |         favourites: List[Dict[str, Any]] = result["payload"]["favourites"] | ||||||
|  |         return favourites | ||||||
|  |  | ||||||
|     async def load_last_activity(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_last_activity(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
|         url: str = f"{const.API_URL}/commands/v1/retrieve-last-activity" |         url: str = f"{const.API_URL}/commands/v1/retrieve-last-activity" | ||||||
|         params: Dict[str, str] = {"macAddress": appliance.mac_address} |         params: Dict[str, str] = {"macAddress": appliance.mac_address} | ||||||
|         async with self._hon.get(url, params=params) as response: |         async with self._hon.get(url, params=params) as response: | ||||||
|             result: Dict[str, Any] = await response.json() |             result: Dict[str, Any] = await response.json() | ||||||
|             if result and (activity := result.get("attributes")): |             if result: | ||||||
|                 return activity |                 activity: Dict[str, Any] = result.get("attributes", "") | ||||||
|  |                 if activity: | ||||||
|  |                     return activity | ||||||
|         return {} |         return {} | ||||||
|  |  | ||||||
|     async def load_appliance_data(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_appliance_data(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
| @ -142,7 +150,10 @@ class HonAPI: | |||||||
|         async with self._hon.get(url, params=params) as response: |         async with self._hon.get(url, params=params) as response: | ||||||
|             result: Dict[str, Any] = await response.json() |             result: Dict[str, Any] = await response.json() | ||||||
|             if result: |             if result: | ||||||
|                 return result.get("payload", {}).get("applianceModel", {}) |                 appliance_data: Dict[str, Any] = result.get("payload", {}).get( | ||||||
|  |                     "applianceModel", {} | ||||||
|  |                 ) | ||||||
|  |                 return appliance_data | ||||||
|         return {} |         return {} | ||||||
|  |  | ||||||
|     async def load_attributes(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_attributes(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
| @ -153,7 +164,8 @@ class HonAPI: | |||||||
|         } |         } | ||||||
|         url: str = f"{const.API_URL}/commands/v1/context" |         url: str = f"{const.API_URL}/commands/v1/context" | ||||||
|         async with self._hon.get(url, params=params) as response: |         async with self._hon.get(url, params=params) as response: | ||||||
|             return (await response.json()).get("payload", {}) |             attributes: Dict[str, Any] = (await response.json()).get("payload", {}) | ||||||
|  |         return attributes | ||||||
|  |  | ||||||
|     async def load_statistics(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_statistics(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
|         params: Dict[str, str] = { |         params: Dict[str, str] = { | ||||||
| @ -162,13 +174,15 @@ class HonAPI: | |||||||
|         } |         } | ||||||
|         url: str = f"{const.API_URL}/commands/v1/statistics" |         url: str = f"{const.API_URL}/commands/v1/statistics" | ||||||
|         async with self._hon.get(url, params=params) as response: |         async with self._hon.get(url, params=params) as response: | ||||||
|             return (await response.json()).get("payload", {}) |             statistics: Dict[str, Any] = (await response.json()).get("payload", {}) | ||||||
|  |         return statistics | ||||||
|  |  | ||||||
|     async def load_maintenance(self, appliance: HonAppliance) -> Dict[str, Any]: |     async def load_maintenance(self, appliance: HonAppliance) -> Dict[str, Any]: | ||||||
|         url = f"{const.API_URL}/commands/v1/maintenance-cycle" |         url = f"{const.API_URL}/commands/v1/maintenance-cycle" | ||||||
|         params = {"macAddress": appliance.mac_address} |         params = {"macAddress": appliance.mac_address} | ||||||
|         async with self._hon.get(url, params=params) as response: |         async with self._hon.get(url, params=params) as response: | ||||||
|             return (await response.json()).get("payload", {}) |             maintenance: Dict[str, Any] = (await response.json()).get("payload", {}) | ||||||
|  |         return maintenance | ||||||
|  |  | ||||||
|     async def send_command( |     async def send_command( | ||||||
|         self, |         self, | ||||||
| @ -207,9 +221,8 @@ class HonAPI: | |||||||
|         url: str = f"{const.API_URL}/config/v1/program-list-rules" |         url: str = f"{const.API_URL}/config/v1/program-list-rules" | ||||||
|         async with self._hon_anonymous.get(url) as response: |         async with self._hon_anonymous.get(url) as response: | ||||||
|             result: Dict[str, Any] = await response.json() |             result: Dict[str, Any] = await response.json() | ||||||
|             if result and (data := result.get("payload")): |         data: Dict[str, Any] = result.get("payload", {}) | ||||||
|                 return data |         return data | ||||||
|         return {} |  | ||||||
|  |  | ||||||
|     async def app_config( |     async def app_config( | ||||||
|         self, language: str = "en", beta: bool = True |         self, language: str = "en", beta: bool = True | ||||||
| @ -223,17 +236,17 @@ class HonAPI: | |||||||
|         } |         } | ||||||
|         payload: str = json.dumps(payload_data, separators=(",", ":")) |         payload: str = json.dumps(payload_data, separators=(",", ":")) | ||||||
|         async with self._hon_anonymous.post(url, data=payload) as response: |         async with self._hon_anonymous.post(url, data=payload) as response: | ||||||
|             if (result := await response.json()) and (data := result.get("payload")): |             result = await response.json() | ||||||
|                 return data |         data: Dict[str, Any] = result.get("payload", {}) | ||||||
|         return {} |         return data | ||||||
|  |  | ||||||
|     async def translation_keys(self, language: str = "en") -> Dict[str, Any]: |     async def translation_keys(self, language: str = "en") -> Dict[str, Any]: | ||||||
|         config = await self.app_config(language=language) |         config = await self.app_config(language=language) | ||||||
|         if url := config.get("language", {}).get("jsonPath"): |         if not (url := config.get("language", {}).get("jsonPath")): | ||||||
|             async with self._hon_anonymous.get(url) as response: |             return {} | ||||||
|                 if result := await response.json(): |         async with self._hon_anonymous.get(url) as response: | ||||||
|                     return result |             result: Dict[str, Any] = await response.json() | ||||||
|         return {} |         return result | ||||||
|  |  | ||||||
|     async def close(self) -> None: |     async def close(self) -> None: | ||||||
|         if self._hon_handler is not None: |         if self._hon_handler is not None: | ||||||
| @ -250,9 +263,17 @@ class TestAPI(HonAPI): | |||||||
|  |  | ||||||
|     def _load_json(self, appliance: HonAppliance, file: str) -> Dict[str, Any]: |     def _load_json(self, appliance: HonAppliance, file: str) -> Dict[str, Any]: | ||||||
|         directory = f"{appliance.appliance_type}_{appliance.appliance_model_id}".lower() |         directory = f"{appliance.appliance_type}_{appliance.appliance_model_id}".lower() | ||||||
|         path = f"{self._path}/{directory}/{file}.json" |         if not (path := self._path / directory / f"{file}.json").exists(): | ||||||
|  |             _LOGGER.warning("Can't open %s", str(path)) | ||||||
|  |             return {} | ||||||
|         with open(path, "r", encoding="utf-8") as json_file: |         with open(path, "r", encoding="utf-8") as json_file: | ||||||
|             return json.loads(json_file.read()) |             text = json_file.read() | ||||||
|  |         try: | ||||||
|  |             data: Dict[str, Any] = json.loads(text) | ||||||
|  |             return data | ||||||
|  |         except json.decoder.JSONDecodeError as error: | ||||||
|  |             _LOGGER.error("%s - %s", str(path), error) | ||||||
|  |             return {} | ||||||
|  |  | ||||||
|     async def load_appliances(self) -> List[Dict[str, Any]]: |     async def load_appliances(self) -> List[Dict[str, Any]]: | ||||||
|         result = [] |         result = [] | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ import urllib | |||||||
| from contextlib import suppress | from contextlib import suppress | ||||||
| from dataclasses import dataclass | from dataclasses import dataclass | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| from typing import Dict, Optional, Any | from typing import Dict, Optional, Any, List | ||||||
| from urllib import parse | from urllib import parse | ||||||
| from urllib.parse import quote | from urllib.parse import quote | ||||||
|  |  | ||||||
| @ -115,7 +115,8 @@ class HonAuth: | |||||||
|         async with self._request.get(url) as response: |         async with self._request.get(url) as response: | ||||||
|             text = await response.text() |             text = await response.text() | ||||||
|             self._expires = datetime.utcnow() |             self._expires = datetime.utcnow() | ||||||
|             if not (login_url := re.findall("url = '(.+?)'", text)): |             login_url: List[str] = re.findall("url = '(.+?)'", text) | ||||||
|  |             if not login_url: | ||||||
|                 if "oauth/done#access_token=" in text: |                 if "oauth/done#access_token=" in text: | ||||||
|                     self._parse_token_data(text) |                     self._parse_token_data(text) | ||||||
|                     raise exceptions.HonNoAuthenticationNeeded() |                     raise exceptions.HonNoAuthenticationNeeded() | ||||||
| @ -184,7 +185,8 @@ class HonAuth: | |||||||
|             if response.status == 200: |             if response.status == 200: | ||||||
|                 with suppress(json.JSONDecodeError, KeyError): |                 with suppress(json.JSONDecodeError, KeyError): | ||||||
|                     result = await response.json() |                     result = await response.json() | ||||||
|                     return result["events"][0]["attributes"]["values"]["url"] |                     url: str = result["events"][0]["attributes"]["values"]["url"] | ||||||
|  |                     return url | ||||||
|             await self._error_logger(response) |             await self._error_logger(response) | ||||||
|             return "" |             return "" | ||||||
|  |  | ||||||
|  | |||||||
| @ -4,6 +4,7 @@ from contextlib import asynccontextmanager | |||||||
| from typing import Dict, Any | from typing import Dict, Any | ||||||
|  |  | ||||||
| import aiohttp | import aiohttp | ||||||
|  | from yarl import URL | ||||||
|  |  | ||||||
| from pyhon import const | from pyhon import const | ||||||
| from pyhon.connection.handler.base import ConnectionHandler | from pyhon.connection.handler.base import ConnectionHandler | ||||||
| @ -17,10 +18,10 @@ class HonAnonymousConnectionHandler(ConnectionHandler): | |||||||
|  |  | ||||||
|     @asynccontextmanager |     @asynccontextmanager | ||||||
|     async def _intercept( |     async def _intercept( | ||||||
|         self, method: Callback, *args: Any, **kwargs: Any |         self, method: Callback, url: str | URL, *args: Any, **kwargs: Dict[str, Any] | ||||||
|     ) -> AsyncIterator[aiohttp.ClientResponse]: |     ) -> AsyncIterator[aiohttp.ClientResponse]: | ||||||
|         kwargs["headers"] = kwargs.pop("headers", {}) | self._HEADERS |         kwargs["headers"] = kwargs.pop("headers", {}) | self._HEADERS | ||||||
|         async with method(*args, **kwargs) as response: |         async with method(url, *args, **kwargs) as response: | ||||||
|             if response.status == 403: |             if response.status == 403: | ||||||
|                 _LOGGER.error("Can't authenticate anymore") |                 _LOGGER.error("Can't authenticate anymore") | ||||||
|             yield response |             yield response | ||||||
|  | |||||||
| @ -1,9 +1,10 @@ | |||||||
| import logging | import logging | ||||||
| from collections.abc import AsyncIterator | from collections.abc import AsyncIterator | ||||||
| from contextlib import asynccontextmanager | from contextlib import asynccontextmanager | ||||||
| from typing import Optional, List, Tuple, Any | from typing import Optional, List, Tuple, Any, Dict | ||||||
|  |  | ||||||
| import aiohttp | import aiohttp | ||||||
|  | from yarl import URL | ||||||
|  |  | ||||||
| from pyhon import const | from pyhon import const | ||||||
| from pyhon.connection.handler.base import ConnectionHandler | from pyhon.connection.handler.base import ConnectionHandler | ||||||
| @ -29,9 +30,9 @@ class HonAuthConnectionHandler(ConnectionHandler): | |||||||
|  |  | ||||||
|     @asynccontextmanager |     @asynccontextmanager | ||||||
|     async def _intercept( |     async def _intercept( | ||||||
|         self, method: Callback, *args: Any, **kwargs: Any |         self, method: Callback, url: str | URL, *args: Any, **kwargs: Dict[str, Any] | ||||||
|     ) -> AsyncIterator[aiohttp.ClientResponse]: |     ) -> AsyncIterator[aiohttp.ClientResponse]: | ||||||
|         kwargs["headers"] = kwargs.pop("headers", {}) | self._HEADERS |         kwargs["headers"] = kwargs.pop("headers", {}) | self._HEADERS | ||||||
|         async with method(*args, **kwargs) as response: |         async with method(url, *args, **kwargs) as response: | ||||||
|             self._called_urls.append((response.status, str(response.request_info.url))) |             self._called_urls.append((response.status, str(response.request_info.url))) | ||||||
|             yield response |             yield response | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ from typing import Optional, Dict, Type, Any, Protocol | |||||||
|  |  | ||||||
| import aiohttp | import aiohttp | ||||||
| from typing_extensions import Self | from typing_extensions import Self | ||||||
|  | from yarl import URL | ||||||
|  |  | ||||||
| from pyhon import const, exceptions | from pyhon import const, exceptions | ||||||
| from pyhon.typedefs import Callback | from pyhon.typedefs import Callback | ||||||
| @ -47,7 +48,7 @@ class ConnectionHandler: | |||||||
|  |  | ||||||
|     @asynccontextmanager |     @asynccontextmanager | ||||||
|     def _intercept( |     def _intercept( | ||||||
|         self, method: Callback, *args: Any, loop: int = 0, **kwargs: Any |         self, method: Callback, url: str | URL, *args: Any, **kwargs: Dict[str, Any] | ||||||
|     ) -> AsyncIterator[aiohttp.ClientResponse]: |     ) -> AsyncIterator[aiohttp.ClientResponse]: | ||||||
|         raise NotImplementedError |         raise NotImplementedError | ||||||
|  |  | ||||||
|  | |||||||
| @ -6,6 +6,7 @@ from typing import Optional, Dict, Any | |||||||
|  |  | ||||||
| import aiohttp | import aiohttp | ||||||
| from typing_extensions import Self | from typing_extensions import Self | ||||||
|  | from yarl import URL | ||||||
|  |  | ||||||
| from pyhon.connection.auth import HonAuth | from pyhon.connection.auth import HonAuth | ||||||
| from pyhon.connection.device import HonDevice | from pyhon.connection.device import HonDevice | ||||||
| @ -54,16 +55,19 @@ class HonConnectionHandler(ConnectionHandler): | |||||||
|  |  | ||||||
|     @asynccontextmanager |     @asynccontextmanager | ||||||
|     async def _intercept( |     async def _intercept( | ||||||
|         self, method: Callback, *args: Any, loop: int = 0, **kwargs: Dict[str, str] |         self, method: Callback, url: str | URL, *args: Any, **kwargs: Any | ||||||
|     ) -> AsyncIterator[aiohttp.ClientResponse]: |     ) -> AsyncIterator[aiohttp.ClientResponse]: | ||||||
|  |         loop: int = kwargs.pop("loop", 0) | ||||||
|         kwargs["headers"] = await self._check_headers(kwargs.get("headers", {})) |         kwargs["headers"] = await self._check_headers(kwargs.get("headers", {})) | ||||||
|         async with method(args[0], *args[1:], **kwargs) as response: |         async with method(url, *args, **kwargs) as response: | ||||||
|             if ( |             if ( | ||||||
|                 self.auth.token_expires_soon or response.status in [401, 403] |                 self.auth.token_expires_soon or response.status in [401, 403] | ||||||
|             ) and loop == 0: |             ) and loop == 0: | ||||||
|                 _LOGGER.info("Try refreshing token...") |                 _LOGGER.info("Try refreshing token...") | ||||||
|                 await self.auth.refresh() |                 await self.auth.refresh() | ||||||
|                 async with self._intercept(method, loop=loop + 1, **kwargs) as result: |                 async with self._intercept( | ||||||
|  |                     method, url, *args, loop=loop + 1, **kwargs | ||||||
|  |                 ) as result: | ||||||
|                     yield result |                     yield result | ||||||
|             elif ( |             elif ( | ||||||
|                 self.auth.token_is_expired or response.status in [401, 403] |                 self.auth.token_is_expired or response.status in [401, 403] | ||||||
| @ -75,7 +79,9 @@ class HonConnectionHandler(ConnectionHandler): | |||||||
|                     await response.text(), |                     await response.text(), | ||||||
|                 ) |                 ) | ||||||
|                 await self.create() |                 await self.create() | ||||||
|                 async with self._intercept(method, loop=loop + 1, **kwargs) as result: |                 async with self._intercept( | ||||||
|  |                     method, url, *args, loop=loop + 1, **kwargs | ||||||
|  |                 ) as result: | ||||||
|                     yield result |                     yield result | ||||||
|             elif loop >= 2: |             elif loop >= 2: | ||||||
|                 _LOGGER.error( |                 _LOGGER.error( | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ API_KEY = "GRCqFhC6Gk@ikWXm1RmnSmX1cm,MxY-configuration" | |||||||
| APP = "hon" | APP = "hon" | ||||||
| # All seen id's (different accounts, different devices) are the same, so I guess this hash is static | # All seen id's (different accounts, different devices) are the same, so I guess this hash is static | ||||||
| CLIENT_ID = "3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9.HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6" | CLIENT_ID = "3MVG9QDx8IX8nP5T2Ha8ofvlmjLZl5L_gvfbT9.HJvpHGKoAS_dcMN8LYpTSYeVFCraUnV.2Ag1Ki7m4znVO6" | ||||||
| APP_VERSION = "2.0.10" | APP_VERSION = "2.1.2" | ||||||
| OS_VERSION = 31 | OS_VERSION = 31 | ||||||
| OS = "android" | OS = "android" | ||||||
| DEVICE_MODEL = "exynos9820" | DEVICE_MODEL = "exynos9820" | ||||||
|  | |||||||
| @ -70,7 +70,7 @@ async def zip_archive( | |||||||
| ) -> str: | ) -> str: | ||||||
|     data = await appliance_data(appliance, path, anonymous) |     data = await appliance_data(appliance, path, anonymous) | ||||||
|     archive = data[0].parent |     archive = data[0].parent | ||||||
|     shutil.make_archive(str(archive.parent), "zip", archive) |     shutil.make_archive(str(archive), "zip", archive) | ||||||
|     shutil.rmtree(archive) |     shutil.rmtree(archive) | ||||||
|     return f"{archive.stem}.zip" |     return f"{archive.stem}.zip" | ||||||
|  |  | ||||||
| @ -89,12 +89,11 @@ def yaml_export(appliance: "HonAppliance", anonymous: bool = False) -> str: | |||||||
|     if anonymous: |     if anonymous: | ||||||
|         for sensible in ["serialNumber", "coords"]: |         for sensible in ["serialNumber", "coords"]: | ||||||
|             data.get("appliance", {}).pop(sensible, None) |             data.get("appliance", {}).pop(sensible, None) | ||||||
|     data = { |     result = printer.pretty_print({"data": data}) | ||||||
|         "data": data, |     if commands := printer.create_commands(appliance.commands): | ||||||
|         "commands": printer.create_command(appliance.commands), |         result += printer.pretty_print({"commands": commands}) | ||||||
|         "rules": printer.create_rules(appliance.commands), |     if rules := printer.create_rules(appliance.commands): | ||||||
|     } |         result += printer.pretty_print({"rules": rules}) | ||||||
|     result = printer.pretty_print(data) |  | ||||||
|     if anonymous: |     if anonymous: | ||||||
|         result = anonymize_data(result) |         result = anonymize_data(result) | ||||||
|     return result |     return result | ||||||
|  | |||||||
| @ -41,4 +41,4 @@ class HonParameterEnum(HonParameter): | |||||||
|             self._value = value |             self._value = value | ||||||
|             self.check_trigger(value) |             self.check_trigger(value) | ||||||
|         else: |         else: | ||||||
|             raise ValueError(f"Allowed values {self._values}") |             raise ValueError(f"Allowed values: {self._values} But was: {value}") | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ class HonParameterProgram(HonParameterEnum): | |||||||
|         if value in self.values: |         if value in self.values: | ||||||
|             self._command.category = value |             self._command.category = value | ||||||
|         else: |         else: | ||||||
|             raise ValueError(f"Allowed values {self.values}") |             raise ValueError(f"Allowed values: {self.values} But was: {value}") | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def values(self) -> List[str]: |     def values(self) -> List[str]: | ||||||
|  | |||||||
| @ -49,11 +49,15 @@ class HonParameterRange(HonParameter): | |||||||
|     @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) * 100) % ( | ||||||
|  |             self.step * 100 | ||||||
|  |         ): | ||||||
|             self._value = value |             self._value = value | ||||||
|             self.check_trigger(value) |             self.check_trigger(value) | ||||||
|         else: |         else: | ||||||
|             raise ValueError(f"Allowed: min {self.min} max {self.max} step {self.step}") |             raise ValueError( | ||||||
|  |                 f"Allowed: min {self.min} max {self.max} step {self.step} But was: {value}" | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def values(self) -> List[str]: |     def values(self) -> List[str]: | ||||||
|  | |||||||
| @ -59,7 +59,7 @@ def pretty_print( | |||||||
|     return result |     return result | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_command( | def create_commands( | ||||||
|     commands: Dict[str, "HonCommand"], concat: bool = False |     commands: Dict[str, "HonCommand"], concat: bool = False | ||||||
| ) -> Dict[str, Any]: | ) -> Dict[str, Any]: | ||||||
|     result: Dict[str, Any] = {} |     result: Dict[str, Any] = {} | ||||||
|  | |||||||
| @ -1,2 +1,3 @@ | |||||||
| aiohttp==3.8.4 | aiohttp==3.8.4 | ||||||
| yarl==1.8.2 | yarl==1.8.2 | ||||||
|  | typing-extensions==4.7.1 | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								setup.py
									
									
									
									
									
								
							| @ -7,7 +7,7 @@ with open("README.md", "r") as f: | |||||||
|  |  | ||||||
| setup( | setup( | ||||||
|     name="pyhOn", |     name="pyhOn", | ||||||
|     version="0.14.3", |     version="0.14.9", | ||||||
|     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, | ||||||
| @ -21,7 +21,7 @@ setup( | |||||||
|     packages=find_packages(), |     packages=find_packages(), | ||||||
|     include_package_data=True, |     include_package_data=True, | ||||||
|     python_requires=">=3.10", |     python_requires=">=3.10", | ||||||
|     install_requires=["aiohttp"], |     install_requires=["aiohttp==3.8.4", "typing-extensions==4.7.1"], | ||||||
|     classifiers=[ |     classifiers=[ | ||||||
|         "Development Status :: 4 - Beta", |         "Development Status :: 4 - Beta", | ||||||
|         "Environment :: Console", |         "Environment :: Console", | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	