Refactor api access
This commit is contained in:
		@@ -1 +1,2 @@
 | 
			
		||||
from .connection.api import HonAPI
 | 
			
		||||
from hon import Hon
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ from pprint import pprint
 | 
			
		||||
if __name__ == "__main__":
 | 
			
		||||
    sys.path.insert(0, str(Path(__file__).parent.parent))
 | 
			
		||||
 | 
			
		||||
from pyhon import HonAPI
 | 
			
		||||
from pyhon import Hon, HonAPI
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger(__name__)
 | 
			
		||||
 | 
			
		||||
@@ -104,8 +104,8 @@ async def main():
 | 
			
		||||
        user = input("User for hOn account: ")
 | 
			
		||||
    if not (password := args["password"]):
 | 
			
		||||
        password = getpass("Password for hOn account: ")
 | 
			
		||||
    async with HonAPI(user, password) as hon:
 | 
			
		||||
        for device in hon.devices:
 | 
			
		||||
    async with Hon(user, password) as hon:
 | 
			
		||||
        for device in hon.appliances:
 | 
			
		||||
            print("=" * 10, device.appliance_type, "-", device.nick_name, "=" * 10)
 | 
			
		||||
            if args.get("keys"):
 | 
			
		||||
                data = device.data.copy()
 | 
			
		||||
 
 | 
			
		||||
@@ -6,11 +6,11 @@ from pyhon.parameter import HonParameterFixed
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HonAppliance:
 | 
			
		||||
    def __init__(self, connector, appliance):
 | 
			
		||||
        if attributes := appliance.get("attributes"):
 | 
			
		||||
            appliance["attributes"] = {v["parName"]: v["parValue"] for v in attributes}
 | 
			
		||||
        self._appliance = appliance
 | 
			
		||||
        self._connector = connector
 | 
			
		||||
    def __init__(self, api, info):
 | 
			
		||||
        if attributes := info.get("attributes"):
 | 
			
		||||
            info["attributes"] = {v["parName"]: v["parValue"] for v in attributes}
 | 
			
		||||
        self._info = info
 | 
			
		||||
        self._api = api
 | 
			
		||||
        self._appliance_model = {}
 | 
			
		||||
 | 
			
		||||
        self._commands = {}
 | 
			
		||||
@@ -36,7 +36,7 @@ class HonAppliance:
 | 
			
		||||
                return self.data[item]
 | 
			
		||||
            if item in self.attributes["parameters"]:
 | 
			
		||||
                return self.attributes["parameters"].get(item)
 | 
			
		||||
            return self.appliance[item]
 | 
			
		||||
            return self.info[item]
 | 
			
		||||
 | 
			
		||||
    def get(self, item, default=None):
 | 
			
		||||
        try:
 | 
			
		||||
@@ -46,23 +46,23 @@ class HonAppliance:
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def appliance_model_id(self):
 | 
			
		||||
        return self._appliance.get("applianceModelId")
 | 
			
		||||
        return self._info.get("applianceModelId")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def appliance_type(self):
 | 
			
		||||
        return self._appliance.get("applianceTypeName")
 | 
			
		||||
        return self._info.get("applianceTypeName")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def mac_address(self):
 | 
			
		||||
        return self._appliance.get("macAddress")
 | 
			
		||||
        return self._info.get("macAddress")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def model_name(self):
 | 
			
		||||
        return self._appliance.get("modelName")
 | 
			
		||||
        return self._info.get("modelName")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def nick_name(self):
 | 
			
		||||
        return self._appliance.get("nickName")
 | 
			
		||||
        return self._info.get("nickName")
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def commands_options(self):
 | 
			
		||||
@@ -81,11 +81,11 @@ class HonAppliance:
 | 
			
		||||
        return self._statistics
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def appliance(self):
 | 
			
		||||
        return self._appliance
 | 
			
		||||
    def info(self):
 | 
			
		||||
        return self._info
 | 
			
		||||
 | 
			
		||||
    async def _recover_last_command_states(self, commands):
 | 
			
		||||
        command_history = await self._connector.command_history(self)
 | 
			
		||||
        command_history = await self._api.command_history(self)
 | 
			
		||||
        for name, command in commands.items():
 | 
			
		||||
            last = next((index for (index, d) in enumerate(command_history) if d.get("command", {}).get("commandName") == name), None)
 | 
			
		||||
            if last is None:
 | 
			
		||||
@@ -100,19 +100,19 @@ class HonAppliance:
 | 
			
		||||
                        data.value = parameters.get(key)
 | 
			
		||||
 | 
			
		||||
    async def load_commands(self):
 | 
			
		||||
        raw = await self._connector.load_commands(self)
 | 
			
		||||
        raw = await self._api.load_commands(self)
 | 
			
		||||
        self._appliance_model = raw.pop("applianceModel")
 | 
			
		||||
        for item in ["settings", "options", "dictionaryId"]:
 | 
			
		||||
            raw.pop(item)
 | 
			
		||||
        commands = {}
 | 
			
		||||
        for command, attr in raw.items():
 | 
			
		||||
            if "parameters" in attr:
 | 
			
		||||
                commands[command] = HonCommand(command, attr, self._connector, self)
 | 
			
		||||
                commands[command] = HonCommand(command, attr, self._api, self)
 | 
			
		||||
            elif "parameters" in attr[list(attr)[0]]:
 | 
			
		||||
                multi = {}
 | 
			
		||||
                for program, attr2 in attr.items():
 | 
			
		||||
                    program = program.split(".")[-1].lower()
 | 
			
		||||
                    cmd = HonCommand(command, attr2, self._connector, self, multi=multi, program=program)
 | 
			
		||||
                    cmd = HonCommand(command, attr2, self._api, self, multi=multi, program=program)
 | 
			
		||||
                    multi[program] = cmd
 | 
			
		||||
                    commands[command] = cmd
 | 
			
		||||
        self._commands = commands
 | 
			
		||||
@@ -137,19 +137,19 @@ class HonAppliance:
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
    async def load_attributes(self):
 | 
			
		||||
        self._attributes = await self._connector.load_attributes(self)
 | 
			
		||||
        self._attributes = await self._api.load_attributes(self)
 | 
			
		||||
        for name, values in self._attributes.pop("shadow").get("parameters").items():
 | 
			
		||||
            self._attributes.setdefault("parameters", {})[name] = values["parNewVal"]
 | 
			
		||||
 | 
			
		||||
    async def load_statistics(self):
 | 
			
		||||
        self._statistics = await self._connector.load_statistics(self)
 | 
			
		||||
        self._statistics = await self._api.load_statistics(self)
 | 
			
		||||
 | 
			
		||||
    async def update(self):
 | 
			
		||||
        await self.load_attributes()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def data(self):
 | 
			
		||||
        result = {"attributes": self.attributes, "appliance": self.appliance, "statistics": self.statistics,
 | 
			
		||||
        result = {"attributes": self.attributes, "appliance": self.info, "statistics": self.statistics,
 | 
			
		||||
                  **self.parameters}
 | 
			
		||||
        if self._extra:
 | 
			
		||||
            return self._extra.data(result)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,10 @@
 | 
			
		||||
import asyncio
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from pyhon import const
 | 
			
		||||
from pyhon.appliance import HonAppliance
 | 
			
		||||
from pyhon.connection.connection import HonConnectionHandler, HonAnonymousConnectionHandler
 | 
			
		||||
from pyhon.connection.handler import HonConnectionHandler, HonAnonymousConnectionHandler
 | 
			
		||||
 | 
			
		||||
_LOGGER = logging.getLogger()
 | 
			
		||||
 | 
			
		||||
@@ -16,53 +14,34 @@ class HonAPI:
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        self._email = email
 | 
			
		||||
        self._password = password
 | 
			
		||||
        self._devices = []
 | 
			
		||||
        self._hon = None
 | 
			
		||||
        self._hon_anonymous = HonAnonymousConnectionHandler()
 | 
			
		||||
 | 
			
		||||
    async def __aenter__(self):
 | 
			
		||||
        self._hon = HonConnectionHandler(self._email, self._password)
 | 
			
		||||
        await self._hon.create()
 | 
			
		||||
        await self.setup()
 | 
			
		||||
 | 
			
		||||
        return self
 | 
			
		||||
        return await self.create()
 | 
			
		||||
 | 
			
		||||
    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        await self._hon.close()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def devices(self) -> List[HonAppliance]:
 | 
			
		||||
        return self._devices
 | 
			
		||||
    async def create(self):
 | 
			
		||||
        self._hon = await HonConnectionHandler(self._email, self._password).create()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    async def setup(self):
 | 
			
		||||
    async def load_appliances(self):
 | 
			
		||||
        async with self._hon.get(f"{const.API_URL}/commands/v1/appliance") as resp:
 | 
			
		||||
            try:
 | 
			
		||||
                appliances = (await resp.json())["payload"]["appliances"]
 | 
			
		||||
                for appliance in appliances:
 | 
			
		||||
                    device = HonAppliance(self, appliance)
 | 
			
		||||
                    if device.mac_address is None:
 | 
			
		||||
                        continue
 | 
			
		||||
                    await asyncio.gather(*[
 | 
			
		||||
                        device.load_attributes(),
 | 
			
		||||
                        device.load_commands(),
 | 
			
		||||
                        device.load_statistics()])
 | 
			
		||||
                    self._devices.append(device)
 | 
			
		||||
            except json.JSONDecodeError:
 | 
			
		||||
                _LOGGER.error("No JSON Data after GET: %s", await resp.text())
 | 
			
		||||
                return False
 | 
			
		||||
        return True
 | 
			
		||||
            return await resp.json()
 | 
			
		||||
 | 
			
		||||
    async def load_commands(self, device: HonAppliance):
 | 
			
		||||
    async def load_commands(self, appliance: HonAppliance):
 | 
			
		||||
        params = {
 | 
			
		||||
            "applianceType": device.appliance_type,
 | 
			
		||||
            "code": device.appliance["code"],
 | 
			
		||||
            "applianceModelId": device.appliance_model_id,
 | 
			
		||||
            "firmwareId": device.appliance["eepromId"],
 | 
			
		||||
            "macAddress": device.mac_address,
 | 
			
		||||
            "fwVersion": device.appliance["fwVersion"],
 | 
			
		||||
            "applianceType": appliance.appliance_type,
 | 
			
		||||
            "code": appliance.info["code"],
 | 
			
		||||
            "applianceModelId": appliance.appliance_model_id,
 | 
			
		||||
            "firmwareId": appliance.info["eepromId"],
 | 
			
		||||
            "macAddress": appliance.mac_address,
 | 
			
		||||
            "fwVersion": appliance.info["fwVersion"],
 | 
			
		||||
            "os": const.OS,
 | 
			
		||||
            "appVersion": const.APP_VERSION,
 | 
			
		||||
            "series": device.appliance["series"],
 | 
			
		||||
            "series": appliance.info["series"],
 | 
			
		||||
        }
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/retrieve"
 | 
			
		||||
        async with self._hon.get(url, params=params) as response:
 | 
			
		||||
@@ -71,51 +50,51 @@ class HonAPI:
 | 
			
		||||
                return {}
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
    async def command_history(self, device: HonAppliance):
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/appliance/{device.mac_address}/history"
 | 
			
		||||
    async def command_history(self, appliance: HonAppliance):
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/appliance/{appliance.mac_address}/history"
 | 
			
		||||
        async with self._hon.get(url) as response:
 | 
			
		||||
            result = await response.json()
 | 
			
		||||
            if not result or not result.get("payload"):
 | 
			
		||||
                return {}
 | 
			
		||||
            return result["payload"]["history"]
 | 
			
		||||
 | 
			
		||||
    async def last_activity(self, device: HonAppliance):
 | 
			
		||||
    async def last_activity(self, appliance: HonAppliance):
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/retrieve-last-activity"
 | 
			
		||||
        params = {"macAddress": device.mac_address}
 | 
			
		||||
        params = {"macAddress": appliance.mac_address}
 | 
			
		||||
        async with self._hon.get(url, params=params) as response:
 | 
			
		||||
            result = await response.json()
 | 
			
		||||
            if result and (activity := result.get("attributes")):
 | 
			
		||||
                return activity
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    async def load_attributes(self, device: HonAppliance):
 | 
			
		||||
    async def load_attributes(self, appliance: HonAppliance):
 | 
			
		||||
        params = {
 | 
			
		||||
            "macAddress": device.mac_address,
 | 
			
		||||
            "applianceType": device.appliance_type,
 | 
			
		||||
            "macAddress": appliance.mac_address,
 | 
			
		||||
            "applianceType": appliance.appliance_type,
 | 
			
		||||
            "category": "CYCLE"
 | 
			
		||||
        }
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/context"
 | 
			
		||||
        async with self._hon.get(url, params=params) as response:
 | 
			
		||||
            return (await response.json()).get("payload", {})
 | 
			
		||||
 | 
			
		||||
    async def load_statistics(self, device: HonAppliance):
 | 
			
		||||
    async def load_statistics(self, appliance: HonAppliance):
 | 
			
		||||
        params = {
 | 
			
		||||
            "macAddress": device.mac_address,
 | 
			
		||||
            "applianceType": device.appliance_type
 | 
			
		||||
            "macAddress": appliance.mac_address,
 | 
			
		||||
            "applianceType": appliance.appliance_type
 | 
			
		||||
        }
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/statistics"
 | 
			
		||||
        async with self._hon.get(url, params=params) as response:
 | 
			
		||||
            return (await response.json()).get("payload", {})
 | 
			
		||||
 | 
			
		||||
    async def send_command(self, device, command, parameters, ancillary_parameters):
 | 
			
		||||
    async def send_command(self, appliance, command, parameters, ancillary_parameters):
 | 
			
		||||
        now = datetime.utcnow().isoformat()
 | 
			
		||||
        data = {
 | 
			
		||||
            "macAddress": device.mac_address,
 | 
			
		||||
            "macAddress": appliance.mac_address,
 | 
			
		||||
            "timestamp": f"{now[:-3]}Z",
 | 
			
		||||
            "commandName": command,
 | 
			
		||||
            "transactionId": f"{device.mac_address}_{now[:-3]}Z",
 | 
			
		||||
            "applianceOptions": device.commands_options,
 | 
			
		||||
            "device": self._hon.device.get(),
 | 
			
		||||
            "transactionId": f"{appliance.mac_address}_{now[:-3]}Z",
 | 
			
		||||
            "applianceOptions": appliance.commands_options,
 | 
			
		||||
            "appliance": self._hon.device.get(),
 | 
			
		||||
            "attributes": {
 | 
			
		||||
                "channel": "mobileApp",
 | 
			
		||||
                "origin": "standardProgram",
 | 
			
		||||
@@ -123,15 +102,12 @@ class HonAPI:
 | 
			
		||||
            },
 | 
			
		||||
            "ancillaryParameters": ancillary_parameters,
 | 
			
		||||
            "parameters": parameters,
 | 
			
		||||
            "applianceType": device.appliance_type
 | 
			
		||||
            "applianceType": appliance.appliance_type
 | 
			
		||||
        }
 | 
			
		||||
        url = f"{const.API_URL}/commands/v1/send"
 | 
			
		||||
        async with self._hon.post(url, json=data) as resp:
 | 
			
		||||
            try:
 | 
			
		||||
                json_data = await resp.json()
 | 
			
		||||
            except json.JSONDecodeError:
 | 
			
		||||
                return False
 | 
			
		||||
            if json_data["payload"]["resultCode"] == "0":
 | 
			
		||||
            json_data = await resp.json()
 | 
			
		||||
            if json_data.get("payload", {}).get("resultCode") == "0":
 | 
			
		||||
                return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
@@ -164,3 +140,6 @@ class HonAPI:
 | 
			
		||||
                if result := await response.json():
 | 
			
		||||
                    return result
 | 
			
		||||
        return {}
 | 
			
		||||
 | 
			
		||||
    async def close(self):
 | 
			
		||||
        await self._hon.close()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,8 @@
 | 
			
		||||
import datetime
 | 
			
		||||
import json
 | 
			
		||||
import logging
 | 
			
		||||
import re
 | 
			
		||||
import secrets
 | 
			
		||||
import urllib
 | 
			
		||||
from pprint import pprint
 | 
			
		||||
from urllib import parse
 | 
			
		||||
 | 
			
		||||
from yarl import URL
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
import json
 | 
			
		||||
from contextlib import asynccontextmanager
 | 
			
		||||
 | 
			
		||||
import aiohttp
 | 
			
		||||
@@ -15,14 +16,14 @@ class HonBaseConnectionHandler:
 | 
			
		||||
        self._auth = None
 | 
			
		||||
 | 
			
		||||
    async def __aenter__(self):
 | 
			
		||||
        await self.create()
 | 
			
		||||
        return self
 | 
			
		||||
        return await self.create()
 | 
			
		||||
 | 
			
		||||
    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        await self.close()
 | 
			
		||||
 | 
			
		||||
    async def create(self):
 | 
			
		||||
        self._session = aiohttp.ClientSession(headers=self._HEADERS)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    @asynccontextmanager
 | 
			
		||||
    async def get(self, *args, **kwargs):
 | 
			
		||||
@@ -55,6 +56,7 @@ class HonConnectionHandler(HonBaseConnectionHandler):
 | 
			
		||||
    async def create(self):
 | 
			
		||||
        await super().create()
 | 
			
		||||
        self._auth = HonAuth(self._session, self._email, self._password, self._device)
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    async def _check_headers(self, headers):
 | 
			
		||||
        if "cognito-token" not in self._request_headers or "id-token" not in self._request_headers:
 | 
			
		||||
@@ -81,7 +83,13 @@ class HonConnectionHandler(HonBaseConnectionHandler):
 | 
			
		||||
                _LOGGER.error("%s - Error %s - %s", response.request_info.url, response.status, await response.text())
 | 
			
		||||
                raise PermissionError("Login failure")
 | 
			
		||||
            else:
 | 
			
		||||
                yield response
 | 
			
		||||
                try:
 | 
			
		||||
                    await response.json()
 | 
			
		||||
                    yield response
 | 
			
		||||
                except json.JSONDecodeError:
 | 
			
		||||
                    _LOGGER.warning("%s - JsonDecodeError %s - %s", response.request_info.url, response.status,
 | 
			
		||||
                                    await response.text())
 | 
			
		||||
                    yield {}
 | 
			
		||||
 | 
			
		||||
    @asynccontextmanager
 | 
			
		||||
    async def get(self, *args, **kwargs):
 | 
			
		||||
							
								
								
									
										36
									
								
								pyhon/hon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								pyhon/hon.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import asyncio
 | 
			
		||||
from typing import List
 | 
			
		||||
 | 
			
		||||
from pyhon import HonAPI
 | 
			
		||||
from pyhon.appliance import HonAppliance
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Hon:
 | 
			
		||||
    def __init__(self, email, password):
 | 
			
		||||
        self._email = email
 | 
			
		||||
        self._password = password
 | 
			
		||||
        self._appliances = []
 | 
			
		||||
        self._api = None
 | 
			
		||||
 | 
			
		||||
    async def __aenter__(self):
 | 
			
		||||
        self._api = await HonAPI(self._email, self._password).create()
 | 
			
		||||
        await self.setup()
 | 
			
		||||
        return self
 | 
			
		||||
 | 
			
		||||
    async def __aexit__(self, exc_type, exc_val, exc_tb):
 | 
			
		||||
        await self._api.close()
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def appliances(self) -> List[HonAppliance]:
 | 
			
		||||
        return self._appliances
 | 
			
		||||
 | 
			
		||||
    async def setup(self):
 | 
			
		||||
        for appliance in (await self._api.load_appliances())["payload"]["appliances"]:
 | 
			
		||||
            appliance = HonAppliance(self._api, appliance)
 | 
			
		||||
            if appliance.mac_address is None:
 | 
			
		||||
                continue
 | 
			
		||||
            await asyncio.gather(*[
 | 
			
		||||
                appliance.load_attributes(),
 | 
			
		||||
                appliance.load_commands(),
 | 
			
		||||
                appliance.load_statistics()])
 | 
			
		||||
            self._appliances.append(appliance)
 | 
			
		||||
		Reference in New Issue
	
	Block a user