diff options
-rw-r--r-- | custom_components/hon/__init__.py | 34 | ||||
-rw-r--r-- | custom_components/hon/binary_sensor.py | 5 | ||||
-rw-r--r-- | custom_components/hon/button.py | 18 | ||||
-rw-r--r-- | custom_components/hon/climate.py | 8 | ||||
-rw-r--r-- | custom_components/hon/entity.py | 54 | ||||
-rw-r--r-- | custom_components/hon/fan.py | 4 | ||||
-rw-r--r-- | custom_components/hon/hon.py | 142 | ||||
-rw-r--r-- | custom_components/hon/light.py | 4 | ||||
-rw-r--r-- | custom_components/hon/lock.py | 8 | ||||
-rw-r--r-- | custom_components/hon/manifest.json | 4 | ||||
-rw-r--r-- | custom_components/hon/number.py | 9 | ||||
-rw-r--r-- | custom_components/hon/select.py | 9 | ||||
-rw-r--r-- | custom_components/hon/sensor.py | 5 | ||||
-rw-r--r-- | custom_components/hon/switch.py | 17 | ||||
-rw-r--r-- | custom_components/hon/util.py | 28 | ||||
-rw-r--r-- | requirements.txt | 2 |
16 files changed, 148 insertions, 203 deletions
diff --git a/custom_components/hon/__init__.py b/custom_components/hon/__init__.py index c4f9aea..1d6e1cd 100644 --- a/custom_components/hon/__init__.py +++ b/custom_components/hon/__init__.py | |||
@@ -1,11 +1,13 @@ | |||
1 | import logging | 1 | import logging |
2 | from pathlib import Path | 2 | from pathlib import Path |
3 | from typing import Any | ||
3 | 4 | ||
4 | import voluptuous as vol # type: ignore[import-untyped] | 5 | import voluptuous as vol # type: ignore[import-untyped] |
5 | from homeassistant.config_entries import ConfigEntry | 6 | from homeassistant.config_entries import ConfigEntry |
6 | from homeassistant.const import CONF_EMAIL, CONF_PASSWORD | 7 | from homeassistant.const import CONF_EMAIL, CONF_PASSWORD |
7 | from homeassistant.helpers import config_validation as cv, aiohttp_client | 8 | from homeassistant.helpers import config_validation as cv, aiohttp_client |
8 | from homeassistant.helpers.typing import HomeAssistantType | 9 | from homeassistant.helpers.typing import HomeAssistantType |
10 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
9 | from pyhon import Hon | 11 | from pyhon import Hon |
10 | 12 | ||
11 | from .const import DOMAIN, PLATFORMS, MOBILE_ID, CONF_REFRESH_TOKEN | 13 | from .const import DOMAIN, PLATFORMS, MOBILE_ID, CONF_REFRESH_TOKEN |
@@ -29,23 +31,27 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool | |||
29 | session = aiohttp_client.async_get_clientsession(hass) | 31 | session = aiohttp_client.async_get_clientsession(hass) |
30 | if (config_dir := hass.config.config_dir) is None: | 32 | if (config_dir := hass.config.config_dir) is None: |
31 | raise ValueError("Missing Config Dir") | 33 | raise ValueError("Missing Config Dir") |
32 | kwargs = { | 34 | hon = await Hon( |
33 | "email": entry.data[CONF_EMAIL], | 35 | email=entry.data[CONF_EMAIL], |
34 | "password": entry.data[CONF_PASSWORD], | 36 | password=entry.data[CONF_PASSWORD], |
35 | "mobile_id": MOBILE_ID, | 37 | mobile_id=MOBILE_ID, |
36 | "session": session, | 38 | session=session, |
37 | "test_data_path": Path(config_dir), | 39 | # test_data_path=Path(config_dir), |
38 | } | 40 | refresh_token=entry.data.get(CONF_REFRESH_TOKEN, ""), |
39 | if refresh_token := entry.data.get(CONF_REFRESH_TOKEN): | 41 | ).create() |
40 | kwargs["refresh_token"] = refresh_token | ||
41 | hon = await Hon(**kwargs).create() | ||
42 | hass.data.setdefault(DOMAIN, {}) | ||
43 | hass.data[DOMAIN][entry.unique_id] = hon | ||
44 | 42 | ||
43 | # Save the new refresh token | ||
45 | hass.config_entries.async_update_entry( | 44 | hass.config_entries.async_update_entry( |
46 | entry, data={**entry.data, CONF_REFRESH_TOKEN: hon.api.auth.refresh_token} | 45 | entry, data={**entry.data, CONF_REFRESH_TOKEN: hon.api.auth.refresh_token} |
47 | ) | 46 | ) |
48 | hass.data[DOMAIN]["coordinators"] = {} | 47 | |
48 | coordinator: DataUpdateCoordinator[dict[str, Any]] = DataUpdateCoordinator( | ||
49 | hass, _LOGGER, name=DOMAIN | ||
50 | ) | ||
51 | hon.subscribe_updates(coordinator.async_set_updated_data) | ||
52 | |||
53 | hass.data.setdefault(DOMAIN, {}) | ||
54 | hass.data[DOMAIN][entry.unique_id] = {"hon": hon, "coordinator": coordinator} | ||
49 | 55 | ||
50 | for platform in PLATFORMS: | 56 | for platform in PLATFORMS: |
51 | hass.async_create_task( | 57 | hass.async_create_task( |
@@ -55,7 +61,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool | |||
55 | 61 | ||
56 | 62 | ||
57 | async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: | 63 | async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: |
58 | refresh_token = hass.data[DOMAIN][entry.unique_id].api.auth.refresh_token | 64 | refresh_token = hass.data[DOMAIN][entry.unique_id]["hon"].api.auth.refresh_token |
59 | 65 | ||
60 | hass.config_entries.async_update_entry( | 66 | hass.config_entries.async_update_entry( |
61 | entry, data={**entry.data, CONF_REFRESH_TOKEN: refresh_token} | 67 | entry, data={**entry.data, CONF_REFRESH_TOKEN: refresh_token} |
diff --git a/custom_components/hon/binary_sensor.py b/custom_components/hon/binary_sensor.py index 7e861eb..3907325 100644 --- a/custom_components/hon/binary_sensor.py +++ b/custom_components/hon/binary_sensor.py | |||
@@ -12,7 +12,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback | |||
12 | from homeassistant.helpers.typing import HomeAssistantType | 12 | from homeassistant.helpers.typing import HomeAssistantType |
13 | 13 | ||
14 | from .const import DOMAIN | 14 | from .const import DOMAIN |
15 | from .hon import HonEntity, unique_entities | 15 | from .entity import HonEntity |
16 | from .util import unique_entities | ||
16 | 17 | ||
17 | _LOGGER = logging.getLogger(__name__) | 18 | _LOGGER = logging.getLogger(__name__) |
18 | 19 | ||
@@ -319,7 +320,7 @@ async def async_setup_entry( | |||
319 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | 320 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback |
320 | ) -> None: | 321 | ) -> None: |
321 | entities = [] | 322 | entities = [] |
322 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 323 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
323 | for description in BINARY_SENSORS.get(device.appliance_type, []): | 324 | for description in BINARY_SENSORS.get(device.appliance_type, []): |
324 | if device.get(description.key) is None: | 325 | if device.get(description.key) is None: |
325 | continue | 326 | continue |
diff --git a/custom_components/hon/button.py b/custom_components/hon/button.py index 0d4b4b8..f5f3d28 100644 --- a/custom_components/hon/button.py +++ b/custom_components/hon/button.py | |||
@@ -10,7 +10,7 @@ from homeassistant.helpers.typing import HomeAssistantType | |||
10 | from pyhon.appliance import HonAppliance | 10 | from pyhon.appliance import HonAppliance |
11 | 11 | ||
12 | from .const import DOMAIN | 12 | from .const import DOMAIN |
13 | from .hon import HonEntity | 13 | from .entity import HonEntity |
14 | from .typedefs import HonButtonType | 14 | from .typedefs import HonButtonType |
15 | 15 | ||
16 | _LOGGER = logging.getLogger(__name__) | 16 | _LOGGER = logging.getLogger(__name__) |
@@ -59,7 +59,7 @@ async def async_setup_entry( | |||
59 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | 59 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback |
60 | ) -> None: | 60 | ) -> None: |
61 | entities: list[HonButtonType] = [] | 61 | entities: list[HonButtonType] = [] |
62 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 62 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
63 | for description in BUTTONS.get(device.appliance_type, []): | 63 | for description in BUTTONS.get(device.appliance_type, []): |
64 | if not device.commands.get(description.key): | 64 | if not device.commands.get(description.key): |
65 | continue | 65 | continue |
@@ -96,19 +96,14 @@ class HonDeviceInfo(HonEntity, ButtonEntity): | |||
96 | self._attr_icon = "mdi:information" | 96 | self._attr_icon = "mdi:information" |
97 | self._attr_name = "Show Device Info" | 97 | self._attr_name = "Show Device Info" |
98 | self._attr_entity_category = EntityCategory.DIAGNOSTIC | 98 | self._attr_entity_category = EntityCategory.DIAGNOSTIC |
99 | if "beta" not in self.coordinator.info.hon_version: | 99 | self._attr_entity_registry_enabled_default = False |
100 | self._attr_entity_registry_enabled_default = False | ||
101 | 100 | ||
102 | async def async_press(self) -> None: | 101 | async def async_press(self) -> None: |
103 | versions = "versions:\n" | ||
104 | versions += f" hon: {self.coordinator.info.hon_version}\n" | ||
105 | versions += f" pyhOn: {self.coordinator.info.pyhon_version}\n" | ||
106 | info = f"{self._device.diagnose}{versions}" | ||
107 | title = f"{self._device.nick_name} Device Info" | 102 | title = f"{self._device.nick_name} Device Info" |
108 | persistent_notification.create( | 103 | persistent_notification.create( |
109 | self._hass, f"````\n```\n{info}\n```\n````", title | 104 | self._hass, f"````\n```\n{self._device.diagnose}\n```\n````", title |
110 | ) | 105 | ) |
111 | _LOGGER.info(info.replace(" ", "\u200B ")) | 106 | _LOGGER.info(self._device.diagnose.replace(" ", "\u200B ")) |
112 | 107 | ||
113 | 108 | ||
114 | class HonDataArchive(HonEntity, ButtonEntity): | 109 | class HonDataArchive(HonEntity, ButtonEntity): |
@@ -121,8 +116,7 @@ class HonDataArchive(HonEntity, ButtonEntity): | |||
121 | self._attr_icon = "mdi:archive-arrow-down" | 116 | self._attr_icon = "mdi:archive-arrow-down" |
122 | self._attr_name = "Create Data Archive" | 117 | self._attr_name = "Create Data Archive" |
123 | self._attr_entity_category = EntityCategory.DIAGNOSTIC | 118 | self._attr_entity_category = EntityCategory.DIAGNOSTIC |
124 | if "beta" not in self.coordinator.info.hon_version: | 119 | self._attr_entity_registry_enabled_default = False |
125 | self._attr_entity_registry_enabled_default = False | ||
126 | 120 | ||
127 | async def async_press(self) -> None: | 121 | async def async_press(self) -> None: |
128 | if (config_dir := self._hass.config.config_dir) is None: | 122 | if (config_dir := self._hass.config.config_dir) is None: |
diff --git a/custom_components/hon/climate.py b/custom_components/hon/climate.py index 9f26171..958465c 100644 --- a/custom_components/hon/climate.py +++ b/custom_components/hon/climate.py | |||
@@ -26,7 +26,7 @@ from pyhon.appliance import HonAppliance | |||
26 | from pyhon.parameter.range import HonParameterRange | 26 | from pyhon.parameter.range import HonParameterRange |
27 | 27 | ||
28 | from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM | 28 | from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM |
29 | from .hon import HonEntity | 29 | from .entity import HonEntity |
30 | 30 | ||
31 | _LOGGER = logging.getLogger(__name__) | 31 | _LOGGER = logging.getLogger(__name__) |
32 | 32 | ||
@@ -108,7 +108,7 @@ async def async_setup_entry( | |||
108 | ) -> None: | 108 | ) -> None: |
109 | entities = [] | 109 | entities = [] |
110 | entity: HonClimateEntity | HonACClimateEntity | 110 | entity: HonClimateEntity | HonACClimateEntity |
111 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 111 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
112 | for description in CLIMATES.get(device.appliance_type, []): | 112 | for description in CLIMATES.get(device.appliance_type, []): |
113 | if isinstance(description, HonACClimateEntityDescription): | 113 | if isinstance(description, HonACClimateEntityDescription): |
114 | if description.key not in list(device.commands): | 114 | if description.key not in list(device.commands): |
@@ -223,7 +223,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity): | |||
223 | self._device.sync_command("startProgram", "settings") | 223 | self._device.sync_command("startProgram", "settings") |
224 | self._set_temperature_bound() | 224 | self._set_temperature_bound() |
225 | self._handle_coordinator_update(update=False) | 225 | self._handle_coordinator_update(update=False) |
226 | await self.coordinator.async_refresh() | 226 | self.async_write_ha_state() |
227 | self._attr_preset_mode = preset_mode | 227 | self._attr_preset_mode = preset_mode |
228 | await self._device.commands["startProgram"].send() | 228 | await self._device.commands["startProgram"].send() |
229 | self.async_write_ha_state() | 229 | self.async_write_ha_state() |
@@ -390,7 +390,7 @@ class HonClimateEntity(HonEntity, ClimateEntity): | |||
390 | self._device.sync_command(command, "settings") | 390 | self._device.sync_command(command, "settings") |
391 | self._set_temperature_bound() | 391 | self._set_temperature_bound() |
392 | self._attr_preset_mode = preset_mode | 392 | self._attr_preset_mode = preset_mode |
393 | await self.coordinator.async_refresh() | 393 | self.async_write_ha_state() |
394 | await self._device.commands[command].send() | 394 | await self._device.commands[command].send() |
395 | self.async_write_ha_state() | 395 | self.async_write_ha_state() |
396 | 396 | ||
diff --git a/custom_components/hon/entity.py b/custom_components/hon/entity.py new file mode 100644 index 0000000..7e459ff --- /dev/null +++ b/custom_components/hon/entity.py | |||
@@ -0,0 +1,54 @@ | |||
1 | from typing import Optional, Any | ||
2 | |||
3 | from homeassistant.config_entries import ConfigEntry | ||
4 | from homeassistant.core import callback | ||
5 | from homeassistant.helpers.entity import DeviceInfo | ||
6 | from homeassistant.helpers.typing import HomeAssistantType | ||
7 | from homeassistant.helpers.update_coordinator import ( | ||
8 | CoordinatorEntity, | ||
9 | ) | ||
10 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
11 | from pyhon.appliance import HonAppliance | ||
12 | |||
13 | from .const import DOMAIN | ||
14 | from .typedefs import HonEntityDescription | ||
15 | |||
16 | |||
17 | class HonEntity(CoordinatorEntity[DataUpdateCoordinator[dict[str, Any]]]): | ||
18 | _attr_has_entity_name = True | ||
19 | _attr_should_poll = False | ||
20 | |||
21 | def __init__( | ||
22 | self, | ||
23 | hass: HomeAssistantType, | ||
24 | entry: ConfigEntry, | ||
25 | device: HonAppliance, | ||
26 | description: Optional[HonEntityDescription] = None, | ||
27 | ) -> None: | ||
28 | self.coordinator = hass.data[DOMAIN][entry.unique_id]["coordinator"] | ||
29 | super().__init__(self.coordinator) | ||
30 | self._hon = hass.data[DOMAIN][entry.unique_id]["hon"] | ||
31 | self._hass = hass | ||
32 | self._device: HonAppliance = device | ||
33 | |||
34 | if description is not None: | ||
35 | self.entity_description = description | ||
36 | self._attr_unique_id = f"{self._device.unique_id}{description.key}" | ||
37 | else: | ||
38 | self._attr_unique_id = self._device.unique_id | ||
39 | self._handle_coordinator_update(update=False) | ||
40 | |||
41 | @property | ||
42 | def device_info(self) -> DeviceInfo: | ||
43 | return DeviceInfo( | ||
44 | identifiers={(DOMAIN, self._device.unique_id)}, | ||
45 | manufacturer=self._device.get("brand", ""), | ||
46 | name=self._device.nick_name, | ||
47 | model=self._device.model_name, | ||
48 | sw_version=self._device.get("fwVersion", ""), | ||
49 | ) | ||
50 | |||
51 | @callback | ||
52 | def _handle_coordinator_update(self, update: bool = True) -> None: | ||
53 | if update: | ||
54 | self.async_write_ha_state() | ||
diff --git a/custom_components/hon/fan.py b/custom_components/hon/fan.py index 02e6673..a07a08f 100644 --- a/custom_components/hon/fan.py +++ b/custom_components/hon/fan.py | |||
@@ -19,7 +19,7 @@ from pyhon.appliance import HonAppliance | |||
19 | from pyhon.parameter.range import HonParameterRange | 19 | from pyhon.parameter.range import HonParameterRange |
20 | 20 | ||
21 | from .const import DOMAIN | 21 | from .const import DOMAIN |
22 | from .hon import HonEntity | 22 | from .entity import HonEntity |
23 | 23 | ||
24 | _LOGGER = logging.getLogger(__name__) | 24 | _LOGGER = logging.getLogger(__name__) |
25 | 25 | ||
@@ -39,7 +39,7 @@ async def async_setup_entry( | |||
39 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | 39 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback |
40 | ) -> None: | 40 | ) -> None: |
41 | entities = [] | 41 | entities = [] |
42 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 42 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
43 | for description in FANS.get(device.appliance_type, []): | 43 | for description in FANS.get(device.appliance_type, []): |
44 | if ( | 44 | if ( |
45 | description.key not in device.available_settings | 45 | description.key not in device.available_settings |
diff --git a/custom_components/hon/hon.py b/custom_components/hon/hon.py deleted file mode 100644 index e873f32..0000000 --- a/custom_components/hon/hon.py +++ /dev/null | |||
@@ -1,142 +0,0 @@ | |||
1 | import json | ||
2 | import logging | ||
3 | from contextlib import suppress | ||
4 | from datetime import timedelta | ||
5 | from pathlib import Path | ||
6 | from typing import Optional, Any | ||
7 | |||
8 | import pkg_resources # type: ignore[import, unused-ignore] | ||
9 | from homeassistant.config_entries import ConfigEntry | ||
10 | from homeassistant.core import callback | ||
11 | from homeassistant.helpers.entity import DeviceInfo | ||
12 | from homeassistant.helpers.typing import HomeAssistantType | ||
13 | from homeassistant.helpers.update_coordinator import CoordinatorEntity | ||
14 | from homeassistant.helpers.update_coordinator import DataUpdateCoordinator | ||
15 | from pyhon.appliance import HonAppliance | ||
16 | |||
17 | from .const import DOMAIN | ||
18 | from .typedefs import HonEntityDescription, HonOptionEntityDescription, T | ||
19 | |||
20 | _LOGGER = logging.getLogger(__name__) | ||
21 | |||
22 | |||
23 | class HonInfo: | ||
24 | def __init__(self) -> None: | ||
25 | self._manifest: dict[str, Any] = self._get_manifest() | ||
26 | self._hon_version: str = self._manifest.get("version", "") | ||
27 | self._pyhon_version: str = pkg_resources.get_distribution("pyhon").version | ||
28 | |||
29 | @staticmethod | ||
30 | def _get_manifest() -> dict[str, Any]: | ||
31 | manifest = Path(__file__).parent / "manifest.json" | ||
32 | with open(manifest, "r", encoding="utf-8") as file: | ||
33 | result: dict[str, Any] = json.loads(file.read()) | ||
34 | return result | ||
35 | |||
36 | @property | ||
37 | def manifest(self) -> dict[str, Any]: | ||
38 | return self._manifest | ||
39 | |||
40 | @property | ||
41 | def hon_version(self) -> str: | ||
42 | return self._hon_version | ||
43 | |||
44 | @property | ||
45 | def pyhon_version(self) -> str: | ||
46 | return self._pyhon_version | ||
47 | |||
48 | |||
49 | class HonCoordinator(DataUpdateCoordinator[None]): | ||
50 | def __init__(self, hass: HomeAssistantType, device: HonAppliance): | ||
51 | """Initialize my coordinator.""" | ||
52 | super().__init__( | ||
53 | hass, | ||
54 | _LOGGER, | ||
55 | name=device.unique_id, | ||
56 | ) | ||
57 | self._device = device | ||
58 | self._info = HonInfo() | ||
59 | self._device.subscribe(self.async_set_updated_data) | ||
60 | |||
61 | async def _async_update_data(self) -> None: | ||
62 | return | ||
63 | |||
64 | @property | ||
65 | def info(self) -> HonInfo: | ||
66 | return self._info | ||
67 | |||
68 | |||
69 | class HonEntity(CoordinatorEntity[HonCoordinator]): | ||
70 | _attr_has_entity_name = True | ||
71 | _attr_should_poll = False | ||
72 | |||
73 | def __init__( | ||
74 | self, | ||
75 | hass: HomeAssistantType, | ||
76 | entry: ConfigEntry, | ||
77 | device: HonAppliance, | ||
78 | description: Optional[HonEntityDescription] = None, | ||
79 | ) -> None: | ||
80 | coordinator = get_coordinator(hass, device) | ||
81 | super().__init__(coordinator) | ||
82 | |||
83 | self._hon = hass.data[DOMAIN][entry.unique_id] | ||
84 | self._hass = hass | ||
85 | self._coordinator = coordinator | ||
86 | self._device: HonAppliance = device | ||
87 | |||
88 | if description is not None: | ||
89 | self.entity_description = description | ||
90 | self._attr_unique_id = f"{self._device.unique_id}{description.key}" | ||
91 | else: | ||
92 | self._attr_unique_id = self._device.unique_id | ||
93 | self._handle_coordinator_update(update=False) | ||
94 | |||
95 | @property | ||
96 | def device_info(self) -> DeviceInfo: | ||
97 | return DeviceInfo( | ||
98 | identifiers={(DOMAIN, self._device.unique_id)}, | ||
99 | manufacturer=self._device.get("brand", ""), | ||
100 | name=self._device.nick_name, | ||
101 | model=self._device.model_name, | ||
102 | sw_version=self._device.get("fwVersion", ""), | ||
103 | ) | ||
104 | |||
105 | @callback | ||
106 | def _handle_coordinator_update(self, update: bool = True) -> None: | ||
107 | if update: | ||
108 | self.async_write_ha_state() | ||
109 | |||
110 | |||
111 | def unique_entities( | ||
112 | base_entities: tuple[T, ...], | ||
113 | new_entities: tuple[T, ...], | ||
114 | ) -> tuple[T, ...]: | ||
115 | result = list(base_entities) | ||
116 | existing_entities = [entity.key for entity in base_entities] | ||
117 | entity: HonEntityDescription | ||
118 | for entity in new_entities: | ||
119 | if entity.key not in existing_entities: | ||
120 | result.append(entity) | ||
121 | return tuple(result) | ||
122 | |||
123 | |||
124 | def get_coordinator(hass: HomeAssistantType, appliance: HonAppliance) -> HonCoordinator: | ||
125 | coordinators = hass.data[DOMAIN]["coordinators"] | ||
126 | if appliance.unique_id in coordinators: | ||
127 | coordinator: HonCoordinator = hass.data[DOMAIN]["coordinators"][ | ||
128 | appliance.unique_id | ||
129 | ] | ||
130 | else: | ||
131 | coordinator = HonCoordinator(hass, appliance) | ||
132 | hass.data[DOMAIN]["coordinators"][appliance.unique_id] = coordinator | ||
133 | return coordinator | ||
134 | |||
135 | |||
136 | def get_readable( | ||
137 | description: HonOptionEntityDescription, value: float | str | ||
138 | ) -> float | str: | ||
139 | if description.option_list is not None: | ||
140 | with suppress(ValueError): | ||
141 | return description.option_list.get(int(value), value) | ||
142 | return value | ||
diff --git a/custom_components/hon/light.py b/custom_components/hon/light.py index e54956d..d38a994 100644 --- a/custom_components/hon/light.py +++ b/custom_components/hon/light.py | |||
@@ -15,7 +15,7 @@ from pyhon.appliance import HonAppliance | |||
15 | from pyhon.parameter.range import HonParameterRange | 15 | from pyhon.parameter.range import HonParameterRange |
16 | 16 | ||
17 | from .const import DOMAIN | 17 | from .const import DOMAIN |
18 | from .hon import HonEntity | 18 | from .entity import HonEntity |
19 | 19 | ||
20 | _LOGGER = logging.getLogger(__name__) | 20 | _LOGGER = logging.getLogger(__name__) |
21 | 21 | ||
@@ -56,7 +56,7 @@ async def async_setup_entry( | |||
56 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | 56 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback |
57 | ) -> None: | 57 | ) -> None: |
58 | entities = [] | 58 | entities = [] |
59 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 59 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
60 | for description in LIGHTS.get(device.appliance_type, []): | 60 | for description in LIGHTS.get(device.appliance_type, []): |
61 | if ( | 61 | if ( |
62 | description.key not in device.available_settings | 62 | description.key not in device.available_settings |
diff --git a/custom_components/hon/lock.py b/custom_components/hon/lock.py index 97c3b58..510641c 100644 --- a/custom_components/hon/lock.py +++ b/custom_components/hon/lock.py | |||
@@ -10,7 +10,7 @@ from pyhon.parameter.base import HonParameter | |||
10 | from pyhon.parameter.range import HonParameterRange | 10 | from pyhon.parameter.range import HonParameterRange |
11 | 11 | ||
12 | from .const import DOMAIN | 12 | from .const import DOMAIN |
13 | from .hon import HonEntity | 13 | from .entity import HonEntity |
14 | 14 | ||
15 | _LOGGER = logging.getLogger(__name__) | 15 | _LOGGER = logging.getLogger(__name__) |
16 | 16 | ||
@@ -29,7 +29,7 @@ async def async_setup_entry( | |||
29 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback | 29 | hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback |
30 | ) -> None: | 30 | ) -> None: |
31 | entities = [] | 31 | entities = [] |
32 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 32 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
33 | for description in LOCKS.get(device.appliance_type, []): | 33 | for description in LOCKS.get(device.appliance_type, []): |
34 | if ( | 34 | if ( |
35 | f"settings.{description.key}" not in device.available_settings | 35 | f"settings.{description.key}" not in device.available_settings |
@@ -58,7 +58,7 @@ class HonLockEntity(HonEntity, LockEntity): | |||
58 | setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 | 58 | setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 |
59 | self.async_write_ha_state() | 59 | self.async_write_ha_state() |
60 | await self._device.commands["settings"].send() | 60 | await self._device.commands["settings"].send() |
61 | await self.coordinator.async_refresh() | 61 | self.async_write_ha_state() |
62 | 62 | ||
63 | async def async_unlock(self, **kwargs: Any) -> None: | 63 | async def async_unlock(self, **kwargs: Any) -> None: |
64 | """Unlock method.""" | 64 | """Unlock method.""" |
@@ -68,7 +68,7 @@ class HonLockEntity(HonEntity, LockEntity): | |||
68 | setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 | 68 | setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 |
69 | self.async_write_ha_state() | 69 | self.async_write_ha_state() |
70 | await self._device.commands["settings"].send() | 70 | await self._device.commands["settings"].send() |
71 | await self.coordinator.async_refresh() | 71 | self.async_write_ha_state() |
72 | 72 | ||
73 | @property | 73 | @property |
74 | def available(self) -> bool: | 74 | def available(self) -> bool: |
diff --git a/custom_components/hon/manifest.json b/custom_components/hon/manifest.json index b6cfe15..49abb37 100644 --- a/custom_components/hon/manifest.json +++ b/custom_components/hon/manifest.json | |||
@@ -9,7 +9,7 @@ | |||
9 | "iot_class": "cloud_push", | 9 | "iot_class": "cloud_push", |
10 | "issue_tracker": "https://github.com/Andre0512/hon/issues", | 10 | "issue_tracker": "https://github.com/Andre0512/hon/issues", |
11 | "requirements": [ | 11 | "requirements": [ |
12 | "pyhOn==0.17.1" | 12 | "pyhOn==0.17.2" |
13 | ], | 13 | ], |
14 | "version": "0.14.0-beta.3" | 14 | "version": "0.14.0-beta.4" |
15 | } | 15 | } |
diff --git a/custom_components/hon/number.py b/custom_components/hon/number.py index e5e84dd..8cf8a1e 100644 --- a/custom_components/hon/number.py +++ b/custom_components/hon/number.py | |||
@@ -16,7 +16,8 @@ from pyhon.appliance import HonAppliance | |||
16 | from pyhon.parameter.range import HonParameterRange | 16 | from pyhon.parameter.range import HonParameterRange |
17 | 17 | ||
18 | from .const import DOMAIN | 18 | from .const import DOMAIN |
19 | from .hon import HonEntity, unique_entities | 19 | from .entity import HonEntity |
20 | from .util import unique_entities | ||
20 | 21 | ||
21 | 22 | ||
22 | @dataclass(frozen=True) | 23 | @dataclass(frozen=True) |
@@ -210,7 +211,7 @@ async def async_setup_entry( | |||
210 | ) -> None: | 211 | ) -> None: |
211 | entities = [] | 212 | entities = [] |
212 | entity: HonNumberEntity | HonConfigNumberEntity | 213 | entity: HonNumberEntity | HonConfigNumberEntity |
213 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 214 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
214 | for description in NUMBERS.get(device.appliance_type, []): | 215 | for description in NUMBERS.get(device.appliance_type, []): |
215 | if description.key not in device.available_settings: | 216 | if description.key not in device.available_settings: |
216 | continue | 217 | continue |
@@ -256,7 +257,7 @@ class HonNumberEntity(HonEntity, NumberEntity): | |||
256 | await self._device.commands[command].send() | 257 | await self._device.commands[command].send() |
257 | if command != "settings": | 258 | if command != "settings": |
258 | self._device.sync_command(command, "settings") | 259 | self._device.sync_command(command, "settings") |
259 | await self.coordinator.async_refresh() | 260 | self.async_write_ha_state() |
260 | 261 | ||
261 | @callback | 262 | @callback |
262 | def _handle_coordinator_update(self, update: bool = True) -> None: | 263 | def _handle_coordinator_update(self, update: bool = True) -> None: |
@@ -307,7 +308,7 @@ class HonConfigNumberEntity(HonEntity, NumberEntity): | |||
307 | setting = self._device.settings[self.entity_description.key] | 308 | setting = self._device.settings[self.entity_description.key] |
308 | if isinstance(setting, HonParameterRange): | 309 | if isinstance(setting, HonParameterRange): |
309 | setting.value = value | 310 | setting.value = value |
310 | await self.coordinator.async_refresh() | 311 | self.async_write_ha_state() |
311 | 312 | ||
312 | @property | 313 | @property |
313 | def available(self) -> bool: | 314 | def available(self) -> bool: |
diff --git a/custom_components/hon/select.py b/custom_components/hon/select.py index 650698f..1eb620e 100644 --- a/custom_components/hon/select.py +++ b/custom_components/hon/select.py | |||
@@ -13,7 +13,8 @@ from homeassistant.helpers.typing import HomeAssistantType | |||
13 | 13 | ||
14 | from . import const | 14 | from . import const |
15 | from .const import DOMAIN | 15 | from .const import DOMAIN |
16 | from .hon import HonEntity, unique_entities, get_readable | 16 | from .entity import HonEntity |
17 | from .util import unique_entities, get_readable | ||
17 | 18 | ||
18 | _LOGGER = logging.getLogger(__name__) | 19 | _LOGGER = logging.getLogger(__name__) |
19 | 20 | ||
@@ -214,7 +215,7 @@ async def async_setup_entry( | |||
214 | ) -> None: | 215 | ) -> None: |
215 | entities = [] | 216 | entities = [] |
216 | entity: HonSelectEntity | HonConfigSelectEntity | 217 | entity: HonSelectEntity | HonConfigSelectEntity |
217 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 218 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
218 | for description in SELECTS.get(device.appliance_type, []): | 219 | for description in SELECTS.get(device.appliance_type, []): |
219 | if description.key not in device.available_settings: | 220 | if description.key not in device.available_settings: |
220 | continue | 221 | continue |
@@ -262,7 +263,7 @@ class HonConfigSelectEntity(HonEntity, SelectEntity): | |||
262 | async def async_select_option(self, option: str) -> None: | 263 | async def async_select_option(self, option: str) -> None: |
263 | setting = self._device.settings[self.entity_description.key] | 264 | setting = self._device.settings[self.entity_description.key] |
264 | setting.value = self._option_to_number(option, setting.values) | 265 | setting.value = self._option_to_number(option, setting.values) |
265 | await self.coordinator.async_refresh() | 266 | self.async_write_ha_state() |
266 | 267 | ||
267 | @callback | 268 | @callback |
268 | def _handle_coordinator_update(self, update: bool = True) -> None: | 269 | def _handle_coordinator_update(self, update: bool = True) -> None: |
@@ -316,7 +317,7 @@ class HonSelectEntity(HonEntity, SelectEntity): | |||
316 | await self._device.commands[command].send() | 317 | await self._device.commands[command].send() |
317 | if command != "settings": | 318 | if command != "settings": |
318 | self._device.sync_command(command, "settings") | 319 | self._device.sync_command(command, "settings") |
319 | await self.coordinator.async_refresh() | 320 | self.async_write_ha_state() |
320 | 321 | ||
321 | @property | 322 | @property |
322 | def available(self) -> bool: | 323 | def available(self) -> bool: |
diff --git a/custom_components/hon/sensor.py b/custom_components/hon/sensor.py index 6b5355c..9f8bb44 100644 --- a/custom_components/hon/sensor.py +++ b/custom_components/hon/sensor.py | |||
@@ -29,7 +29,8 @@ from homeassistant.helpers.typing import HomeAssistantType | |||
29 | 29 | ||
30 | from . import const | 30 | from . import const |
31 | from .const import DOMAIN | 31 | from .const import DOMAIN |
32 | from .hon import HonEntity, unique_entities, get_readable | 32 | from .entity import HonEntity |
33 | from .util import unique_entities, get_readable | ||
33 | 34 | ||
34 | _LOGGER = logging.getLogger(__name__) | 35 | _LOGGER = logging.getLogger(__name__) |
35 | 36 | ||
@@ -812,7 +813,7 @@ async def async_setup_entry( | |||
812 | ) -> None: | 813 | ) -> None: |
813 | entities = [] | 814 | entities = [] |
814 | entity: HonSensorEntity | HonConfigSensorEntity | 815 | entity: HonSensorEntity | HonConfigSensorEntity |
815 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 816 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
816 | for description in SENSORS.get(device.appliance_type, []): | 817 | for description in SENSORS.get(device.appliance_type, []): |
817 | if isinstance(description, HonSensorEntityDescription): | 818 | if isinstance(description, HonSensorEntityDescription): |
818 | if device.get(description.key) is None: | 819 | if device.get(description.key) is None: |
diff --git a/custom_components/hon/switch.py b/custom_components/hon/switch.py index 8b68d21..36d283e 100644 --- a/custom_components/hon/switch.py +++ b/custom_components/hon/switch.py | |||
@@ -13,7 +13,8 @@ from pyhon.parameter.base import HonParameter | |||
13 | from pyhon.parameter.range import HonParameterRange | 13 | from pyhon.parameter.range import HonParameterRange |
14 | 14 | ||
15 | from .const import DOMAIN | 15 | from .const import DOMAIN |
16 | from .hon import HonEntity, unique_entities | 16 | from .entity import HonEntity |
17 | from .util import unique_entities | ||
17 | 18 | ||
18 | _LOGGER = logging.getLogger(__name__) | 19 | _LOGGER = logging.getLogger(__name__) |
19 | 20 | ||
@@ -406,7 +407,7 @@ async def async_setup_entry( | |||
406 | ) -> None: | 407 | ) -> None: |
407 | entities = [] | 408 | entities = [] |
408 | entity: HonConfigSwitchEntity | HonControlSwitchEntity | HonSwitchEntity | 409 | entity: HonConfigSwitchEntity | HonControlSwitchEntity | HonSwitchEntity |
409 | for device in hass.data[DOMAIN][entry.unique_id].appliances: | 410 | for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances: |
410 | for description in SWITCHES.get(device.appliance_type, []): | 411 | for description in SWITCHES.get(device.appliance_type, []): |
411 | if isinstance(description, HonConfigSwitchEntityDescription): | 412 | if isinstance(description, HonConfigSwitchEntityDescription): |
412 | if description.key not in device.available_settings: | 413 | if description.key not in device.available_settings: |
@@ -446,7 +447,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity): | |||
446 | setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 | 447 | setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 |
447 | self.async_write_ha_state() | 448 | self.async_write_ha_state() |
448 | await self._device.commands["settings"].send() | 449 | await self._device.commands["settings"].send() |
449 | await self.coordinator.async_refresh() | 450 | self.async_write_ha_state() |
450 | 451 | ||
451 | async def async_turn_off(self, **kwargs: Any) -> None: | 452 | async def async_turn_off(self, **kwargs: Any) -> None: |
452 | setting = self._device.settings[f"settings.{self.entity_description.key}"] | 453 | setting = self._device.settings[f"settings.{self.entity_description.key}"] |
@@ -455,7 +456,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity): | |||
455 | setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 | 456 | setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 |
456 | self.async_write_ha_state() | 457 | self.async_write_ha_state() |
457 | await self._device.commands["settings"].send() | 458 | await self._device.commands["settings"].send() |
458 | await self.coordinator.async_refresh() | 459 | self.async_write_ha_state() |
459 | 460 | ||
460 | @property | 461 | @property |
461 | def available(self) -> bool: | 462 | def available(self) -> bool: |
@@ -488,14 +489,14 @@ class HonControlSwitchEntity(HonEntity, SwitchEntity): | |||
488 | 489 | ||
489 | async def async_turn_on(self, **kwargs: Any) -> None: | 490 | async def async_turn_on(self, **kwargs: Any) -> None: |
490 | self._device.sync_command(self.entity_description.turn_on_key, "settings") | 491 | self._device.sync_command(self.entity_description.turn_on_key, "settings") |
491 | await self.coordinator.async_refresh() | 492 | self.async_write_ha_state() |
492 | await self._device.commands[self.entity_description.turn_on_key].send() | 493 | await self._device.commands[self.entity_description.turn_on_key].send() |
493 | self._device.attributes[self.entity_description.key] = True | 494 | self._device.attributes[self.entity_description.key] = True |
494 | self.async_write_ha_state() | 495 | self.async_write_ha_state() |
495 | 496 | ||
496 | async def async_turn_off(self, **kwargs: Any) -> None: | 497 | async def async_turn_off(self, **kwargs: Any) -> None: |
497 | self._device.sync_command(self.entity_description.turn_off_key, "settings") | 498 | self._device.sync_command(self.entity_description.turn_off_key, "settings") |
498 | await self.coordinator.async_refresh() | 499 | self.async_write_ha_state() |
499 | await self._device.commands[self.entity_description.turn_off_key].send() | 500 | await self._device.commands[self.entity_description.turn_off_key].send() |
500 | self._device.attributes[self.entity_description.key] = False | 501 | self._device.attributes[self.entity_description.key] = False |
501 | self.async_write_ha_state() | 502 | self.async_write_ha_state() |
@@ -541,7 +542,7 @@ class HonConfigSwitchEntity(HonEntity, SwitchEntity): | |||
541 | return | 542 | return |
542 | setting.value = setting.max if isinstance(setting, HonParameterRange) else "1" | 543 | setting.value = setting.max if isinstance(setting, HonParameterRange) else "1" |
543 | self.async_write_ha_state() | 544 | self.async_write_ha_state() |
544 | await self.coordinator.async_refresh() | 545 | self.async_write_ha_state() |
545 | 546 | ||
546 | async def async_turn_off(self, **kwargs: Any) -> None: | 547 | async def async_turn_off(self, **kwargs: Any) -> None: |
547 | setting = self._device.settings[self.entity_description.key] | 548 | setting = self._device.settings[self.entity_description.key] |
@@ -549,7 +550,7 @@ class HonConfigSwitchEntity(HonEntity, SwitchEntity): | |||
549 | return | 550 | return |
550 | setting.value = setting.min if isinstance(setting, HonParameterRange) else "0" | 551 | setting.value = setting.min if isinstance(setting, HonParameterRange) else "0" |
551 | self.async_write_ha_state() | 552 | self.async_write_ha_state() |
552 | await self.coordinator.async_refresh() | 553 | self.async_write_ha_state() |
553 | 554 | ||
554 | @callback | 555 | @callback |
555 | def _handle_coordinator_update(self, update: bool = True) -> None: | 556 | def _handle_coordinator_update(self, update: bool = True) -> None: |
diff --git a/custom_components/hon/util.py b/custom_components/hon/util.py new file mode 100644 index 0000000..dab270b --- /dev/null +++ b/custom_components/hon/util.py | |||
@@ -0,0 +1,28 @@ | |||
1 | import logging | ||
2 | from contextlib import suppress | ||
3 | |||
4 | from .typedefs import HonEntityDescription, HonOptionEntityDescription, T | ||
5 | |||
6 | _LOGGER = logging.getLogger(__name__) | ||
7 | |||
8 | |||
9 | def unique_entities( | ||
10 | base_entities: tuple[T, ...], | ||
11 | new_entities: tuple[T, ...], | ||
12 | ) -> tuple[T, ...]: | ||
13 | result = list(base_entities) | ||
14 | existing_entities = [entity.key for entity in base_entities] | ||
15 | entity: HonEntityDescription | ||
16 | for entity in new_entities: | ||
17 | if entity.key not in existing_entities: | ||
18 | result.append(entity) | ||
19 | return tuple(result) | ||
20 | |||
21 | |||
22 | def get_readable( | ||
23 | description: HonOptionEntityDescription, value: float | str | ||
24 | ) -> float | str: | ||
25 | if description.option_list is not None: | ||
26 | with suppress(ValueError): | ||
27 | return description.option_list.get(int(value), value) | ||
28 | return value | ||
diff --git a/requirements.txt b/requirements.txt index 22a82d9..11d498a 100644 --- a/requirements.txt +++ b/requirements.txt | |||
@@ -1 +1 @@ | |||
pyhOn==0.17.0 | pyhOn==0.17.2 | ||