aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndre Basche <andre.basche@gmail.com>2024-03-29 01:22:44 +0100
committerAndre Basche <andre.basche@gmail.com>2024-03-29 01:22:44 +0100
commita6c2c3e9927b9ec58f68f7e84ebff22c0f0d0eb0 (patch)
treedf71f3b46c074e05e27c3e83f35bb31a55f558e7
parent8f1fc627e63dc3c6704a1177e4d44111c28baa30 (diff)
Rebuild to single data coordinatorv0.14.0-beta.4
-rw-r--r--custom_components/hon/__init__.py34
-rw-r--r--custom_components/hon/binary_sensor.py5
-rw-r--r--custom_components/hon/button.py18
-rw-r--r--custom_components/hon/climate.py8
-rw-r--r--custom_components/hon/entity.py54
-rw-r--r--custom_components/hon/fan.py4
-rw-r--r--custom_components/hon/hon.py142
-rw-r--r--custom_components/hon/light.py4
-rw-r--r--custom_components/hon/lock.py8
-rw-r--r--custom_components/hon/manifest.json4
-rw-r--r--custom_components/hon/number.py9
-rw-r--r--custom_components/hon/select.py9
-rw-r--r--custom_components/hon/sensor.py5
-rw-r--r--custom_components/hon/switch.py17
-rw-r--r--custom_components/hon/util.py28
-rw-r--r--requirements.txt2
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 @@
1import logging 1import logging
2from pathlib import Path 2from pathlib import Path
3from typing import Any
3 4
4import voluptuous as vol # type: ignore[import-untyped] 5import voluptuous as vol # type: ignore[import-untyped]
5from homeassistant.config_entries import ConfigEntry 6from homeassistant.config_entries import ConfigEntry
6from homeassistant.const import CONF_EMAIL, CONF_PASSWORD 7from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
7from homeassistant.helpers import config_validation as cv, aiohttp_client 8from homeassistant.helpers import config_validation as cv, aiohttp_client
8from homeassistant.helpers.typing import HomeAssistantType 9from homeassistant.helpers.typing import HomeAssistantType
10from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
9from pyhon import Hon 11from pyhon import Hon
10 12
11from .const import DOMAIN, PLATFORMS, MOBILE_ID, CONF_REFRESH_TOKEN 13from .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
57async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: 63async 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
12from homeassistant.helpers.typing import HomeAssistantType 12from homeassistant.helpers.typing import HomeAssistantType
13 13
14from .const import DOMAIN 14from .const import DOMAIN
15from .hon import HonEntity, unique_entities 15from .entity import HonEntity
16from .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
10from pyhon.appliance import HonAppliance 10from pyhon.appliance import HonAppliance
11 11
12from .const import DOMAIN 12from .const import DOMAIN
13from .hon import HonEntity 13from .entity import HonEntity
14from .typedefs import HonButtonType 14from .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
114class HonDataArchive(HonEntity, ButtonEntity): 109class 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
26from pyhon.parameter.range import HonParameterRange 26from pyhon.parameter.range import HonParameterRange
27 27
28from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM 28from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM
29from .hon import HonEntity 29from .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 @@
1from typing import Optional, Any
2
3from homeassistant.config_entries import ConfigEntry
4from homeassistant.core import callback
5from homeassistant.helpers.entity import DeviceInfo
6from homeassistant.helpers.typing import HomeAssistantType
7from homeassistant.helpers.update_coordinator import (
8 CoordinatorEntity,
9)
10from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
11from pyhon.appliance import HonAppliance
12
13from .const import DOMAIN
14from .typedefs import HonEntityDescription
15
16
17class 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
19from pyhon.parameter.range import HonParameterRange 19from pyhon.parameter.range import HonParameterRange
20 20
21from .const import DOMAIN 21from .const import DOMAIN
22from .hon import HonEntity 22from .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 @@
1import json
2import logging
3from contextlib import suppress
4from datetime import timedelta
5from pathlib import Path
6from typing import Optional, Any
7
8import pkg_resources # type: ignore[import, unused-ignore]
9from homeassistant.config_entries import ConfigEntry
10from homeassistant.core import callback
11from homeassistant.helpers.entity import DeviceInfo
12from homeassistant.helpers.typing import HomeAssistantType
13from homeassistant.helpers.update_coordinator import CoordinatorEntity
14from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
15from pyhon.appliance import HonAppliance
16
17from .const import DOMAIN
18from .typedefs import HonEntityDescription, HonOptionEntityDescription, T
19
20_LOGGER = logging.getLogger(__name__)
21
22
23class 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
49class 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
69class 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
111def 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
124def 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
136def 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
15from pyhon.parameter.range import HonParameterRange 15from pyhon.parameter.range import HonParameterRange
16 16
17from .const import DOMAIN 17from .const import DOMAIN
18from .hon import HonEntity 18from .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
10from pyhon.parameter.range import HonParameterRange 10from pyhon.parameter.range import HonParameterRange
11 11
12from .const import DOMAIN 12from .const import DOMAIN
13from .hon import HonEntity 13from .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
16from pyhon.parameter.range import HonParameterRange 16from pyhon.parameter.range import HonParameterRange
17 17
18from .const import DOMAIN 18from .const import DOMAIN
19from .hon import HonEntity, unique_entities 19from .entity import HonEntity
20from .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
14from . import const 14from . import const
15from .const import DOMAIN 15from .const import DOMAIN
16from .hon import HonEntity, unique_entities, get_readable 16from .entity import HonEntity
17from .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
30from . import const 30from . import const
31from .const import DOMAIN 31from .const import DOMAIN
32from .hon import HonEntity, unique_entities, get_readable 32from .entity import HonEntity
33from .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
13from pyhon.parameter.range import HonParameterRange 13from pyhon.parameter.range import HonParameterRange
14 14
15from .const import DOMAIN 15from .const import DOMAIN
16from .hon import HonEntity, unique_entities 16from .entity import HonEntity
17from .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 @@
1import logging
2from contextlib import suppress
3
4from .typedefs import HonEntityDescription, HonOptionEntityDescription, T
5
6_LOGGER = logging.getLogger(__name__)
7
8
9def 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
22def 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