Compare commits

...

21 Commits

Author SHA1 Message Date
Vadym Melnychuk f66bd14ed1 remove old stuff 2024-08-22 18:32:04 +03:00
Vadym 9a3036b161 Merge branch 'main' into main 2024-08-22 18:23:28 +03:00
Andre Basche 70eb6c0111 Update manifest.json 2024-08-14 22:51:18 +02:00
Andre Basche 9bab35f8c4 Update manifest.json 2024-08-14 22:46:43 +02:00
zawadzkipiter 39fc30c95e Update pyhon to 0.17.5
fix for HA "Can't login" based on:
https://github.com/Andre0512/pyhOn/pull/29
2024-08-14 22:46:10 +02:00
Andre Basche 6906e751b1 Bump version 2024-04-09 22:49:49 +02:00
Andre Basche 6d2a6ce2e9 Fix unit of current elecricity #158 2024-03-30 23:16:54 +01:00
Andre Basche 0e166f3c66 Bump version 2024-03-30 20:26:08 +01:00
Andre Basche 54dd406ec2 Fix checks 2024-03-30 20:25:08 +01:00
Andre Basche a746584833 Fix unkown for 0 in number entity 2024-03-30 20:23:39 +01:00
Andre Basche 36aed2e6ea Fix update entity when changing config 2024-03-30 19:47:46 +01:00
Andre Basche 510c10bd9f Improve device info 2024-03-30 19:46:24 +01:00
Andre Basche 09189ff0f8 Change to new climate enity style, fix #165 2024-03-30 17:29:25 +01:00
Andre Basche 0e26b4a0f7 Fix applance connection handling 2024-03-29 14:48:35 +01:00
Andre Basche a6c2c3e992 Rebuild to single data coordinator 2024-03-29 01:22:44 +01:00
Andre Basche 8f1fc627e6 Bump version 2024-03-26 00:21:42 +01:00
Vadym 085de726dd Merge branch 'Andre0512:main' into main 2024-02-26 10:53:36 +02:00
Vadym Melnychuk 5647cc24e6 fix black checks 2023-12-03 20:57:27 +02:00
Vadym Melnychuk 6ba50f8456 removed mode 2023-12-03 20:50:37 +02:00
Vadym Melnychuk 4772374db7 fix pipeline 2023-12-03 20:31:46 +02:00
Vadym Melnychuk 191bcedaa2 Water Heater 2023-12-03 18:13:45 +02:00
37 changed files with 732 additions and 223 deletions
+20 -14
View File
@@ -1,11 +1,13 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Any
import voluptuous as vol # type: ignore[import-untyped] import voluptuous as vol # type: ignore[import-untyped]
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.helpers import config_validation as cv, aiohttp_client from homeassistant.helpers import config_validation as cv, aiohttp_client
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from pyhon import Hon from pyhon import Hon
from .const import DOMAIN, PLATFORMS, MOBILE_ID, CONF_REFRESH_TOKEN from .const import DOMAIN, PLATFORMS, MOBILE_ID, CONF_REFRESH_TOKEN
@@ -29,23 +31,27 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
session = aiohttp_client.async_get_clientsession(hass) session = aiohttp_client.async_get_clientsession(hass)
if (config_dir := hass.config.config_dir) is None: if (config_dir := hass.config.config_dir) is None:
raise ValueError("Missing Config Dir") raise ValueError("Missing Config Dir")
kwargs = { hon = await Hon(
"email": entry.data[CONF_EMAIL], email=entry.data[CONF_EMAIL],
"password": entry.data[CONF_PASSWORD], password=entry.data[CONF_PASSWORD],
"mobile_id": MOBILE_ID, mobile_id=MOBILE_ID,
"session": session, session=session,
"test_data_path": Path(config_dir), test_data_path=Path(config_dir),
} refresh_token=entry.data.get(CONF_REFRESH_TOKEN, ""),
if refresh_token := entry.data.get(CONF_REFRESH_TOKEN): ).create()
kwargs["refresh_token"] = refresh_token
hon = await Hon(**kwargs).create()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = hon
# Save the new refresh token
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_REFRESH_TOKEN: hon.api.auth.refresh_token} entry, data={**entry.data, CONF_REFRESH_TOKEN: hon.api.auth.refresh_token}
) )
hass.data[DOMAIN]["coordinators"] = {}
coordinator: DataUpdateCoordinator[dict[str, Any]] = DataUpdateCoordinator(
hass, _LOGGER, name=DOMAIN
)
hon.subscribe_updates(coordinator.async_set_updated_data)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.unique_id] = {"hon": hon, "coordinator": coordinator}
for platform in PLATFORMS: for platform in PLATFORMS:
hass.async_create_task( hass.async_create_task(
@@ -55,7 +61,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool
async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool:
refresh_token = hass.data[DOMAIN][entry.unique_id].api.auth.refresh_token refresh_token = hass.data[DOMAIN][entry.unique_id]["hon"].api.auth.refresh_token
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
entry, data={**entry.data, CONF_REFRESH_TOKEN: refresh_token} entry, data={**entry.data, CONF_REFRESH_TOKEN: refresh_token}
+13 -2
View File
@@ -12,7 +12,8 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import HomeAssistantType from homeassistant.helpers.typing import HomeAssistantType
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, unique_entities from .entity import HonEntity
from .util import unique_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -284,6 +285,16 @@ BINARY_SENSORS: dict[str, tuple[HonBinarySensorEntityDescription, ...]] = {
translation_key="on", translation_key="on",
), ),
), ),
"WH": (
HonBinarySensorEntityDescription(
key="onOffStatus",
name="Power State",
icon="mdi:power-standby",
device_class=BinarySensorDeviceClass.POWER,
on_value=1,
translation_key="power-state",
),
),
"FRE": ( "FRE": (
HonBinarySensorEntityDescription( HonBinarySensorEntityDescription(
key="quickModeZ1", key="quickModeZ1",
@@ -319,7 +330,7 @@ async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
entities = [] entities = []
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in BINARY_SENSORS.get(device.appliance_type, []): for description in BINARY_SENSORS.get(device.appliance_type, []):
if device.get(description.key) is None: if device.get(description.key) is None:
continue continue
+7 -13
View File
@@ -10,7 +10,7 @@ from homeassistant.helpers.typing import HomeAssistantType
from pyhon.appliance import HonAppliance from pyhon.appliance import HonAppliance
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity from .entity import HonEntity
from .typedefs import HonButtonType from .typedefs import HonButtonType
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -59,7 +59,7 @@ async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
entities: list[HonButtonType] = [] entities: list[HonButtonType] = []
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in BUTTONS.get(device.appliance_type, []): for description in BUTTONS.get(device.appliance_type, []):
if not device.commands.get(description.key): if not device.commands.get(description.key):
continue continue
@@ -82,7 +82,7 @@ class HonButtonEntity(HonEntity, ButtonEntity):
return ( return (
super().available super().available
and int(self._device.get("remoteCtrValid", "1")) == 1 and int(self._device.get("remoteCtrValid", "1")) == 1
and self._device.get("attributes.lastConnEvent.category") != "DISCONNECTED" and self._device.connection
) )
@@ -96,19 +96,14 @@ class HonDeviceInfo(HonEntity, ButtonEntity):
self._attr_icon = "mdi:information" self._attr_icon = "mdi:information"
self._attr_name = "Show Device Info" self._attr_name = "Show Device Info"
self._attr_entity_category = EntityCategory.DIAGNOSTIC self._attr_entity_category = EntityCategory.DIAGNOSTIC
if "beta" not in self.coordinator.info.hon_version: self._attr_entity_registry_enabled_default = False
self._attr_entity_registry_enabled_default = False
async def async_press(self) -> None: async def async_press(self) -> None:
versions = "versions:\n"
versions += f" hon: {self.coordinator.info.hon_version}\n"
versions += f" pyhOn: {self.coordinator.info.pyhon_version}\n"
info = f"{self._device.diagnose}{versions}"
title = f"{self._device.nick_name} Device Info" title = f"{self._device.nick_name} Device Info"
persistent_notification.create( persistent_notification.create(
self._hass, f"````\n```\n{info}\n```\n````", title self._hass, f"````\n```\n{self._device.diagnose}\n```\n````", title
) )
_LOGGER.info(info.replace(" ", "\u200B ")) _LOGGER.info(self._device.diagnose.replace(" ", "\u200B "))
class HonDataArchive(HonEntity, ButtonEntity): class HonDataArchive(HonEntity, ButtonEntity):
@@ -121,8 +116,7 @@ class HonDataArchive(HonEntity, ButtonEntity):
self._attr_icon = "mdi:archive-arrow-down" self._attr_icon = "mdi:archive-arrow-down"
self._attr_name = "Create Data Archive" self._attr_name = "Create Data Archive"
self._attr_entity_category = EntityCategory.DIAGNOSTIC self._attr_entity_category = EntityCategory.DIAGNOSTIC
if "beta" not in self.coordinator.info.hon_version: self._attr_entity_registry_enabled_default = False
self._attr_entity_registry_enabled_default = False
async def async_press(self) -> None: async def async_press(self) -> None:
if (config_dir := self._hass.config.config_dir) is None: if (config_dir := self._hass.config.config_dir) is None:
+22 -4
View File
@@ -26,7 +26,7 @@ from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM from .const import HON_HVAC_MODE, HON_FAN, DOMAIN, HON_HVAC_PROGRAM
from .hon import HonEntity from .entity import HonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -108,7 +108,7 @@ async def async_setup_entry(
) -> None: ) -> None:
entities = [] entities = []
entity: HonClimateEntity | HonACClimateEntity entity: HonClimateEntity | HonACClimateEntity
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in CLIMATES.get(device.appliance_type, []): for description in CLIMATES.get(device.appliance_type, []):
if isinstance(description, HonACClimateEntityDescription): if isinstance(description, HonACClimateEntityDescription):
if description.key not in list(device.commands): if description.key not in list(device.commands):
@@ -126,6 +126,7 @@ async def async_setup_entry(
class HonACClimateEntity(HonEntity, ClimateEntity): class HonACClimateEntity(HonEntity, ClimateEntity):
entity_description: HonACClimateEntityDescription entity_description: HonACClimateEntityDescription
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,
@@ -211,6 +212,14 @@ class HonACClimateEntity(HonEntity, ClimateEntity):
await self._device.commands["settings"].send() await self._device.commands["settings"].send()
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
await self._device.commands["startProgram"].send()
self._device.sync_command("startProgram", "settings")
async def async_turn_off(self, **kwargs: Any) -> None:
await self._device.commands["stopProgram"].send()
self._device.sync_command("stopProgram", "settings")
@property @property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return the current Preset for this channel.""" """Return the current Preset for this channel."""
@@ -223,7 +232,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity):
self._device.sync_command("startProgram", "settings") self._device.sync_command("startProgram", "settings")
self._set_temperature_bound() self._set_temperature_bound()
self._handle_coordinator_update(update=False) self._handle_coordinator_update(update=False)
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
self._attr_preset_mode = preset_mode self._attr_preset_mode = preset_mode
await self._device.commands["startProgram"].send() await self._device.commands["startProgram"].send()
self.async_write_ha_state() self.async_write_ha_state()
@@ -286,6 +295,7 @@ class HonACClimateEntity(HonEntity, ClimateEntity):
class HonClimateEntity(HonEntity, ClimateEntity): class HonClimateEntity(HonEntity, ClimateEntity):
entity_description: HonClimateEntityDescription entity_description: HonClimateEntityDescription
_enable_turn_on_off_backwards_compatibility = False
def __init__( def __init__(
self, self,
@@ -363,6 +373,14 @@ class HonClimateEntity(HonEntity, ClimateEntity):
self._attr_hvac_mode = hvac_mode self._attr_hvac_mode = hvac_mode
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_on(self) -> None:
"""Set the HVAC State to on."""
await self._device.commands["startProgram"].send()
async def async_turn_off(self) -> None:
"""Set the HVAC State to off."""
await self._device.commands["stopProgram"].send()
@property @property
def preset_mode(self) -> str | None: def preset_mode(self) -> str | None:
"""Return the current Preset for this channel.""" """Return the current Preset for this channel."""
@@ -390,7 +408,7 @@ class HonClimateEntity(HonEntity, ClimateEntity):
self._device.sync_command(command, "settings") self._device.sync_command(command, "settings")
self._set_temperature_bound() self._set_temperature_bound()
self._attr_preset_mode = preset_mode self._attr_preset_mode = preset_mode
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
await self._device.commands[command].send() await self._device.commands[command].send()
self.async_write_ha_state() self.async_write_ha_state()
+6
View File
@@ -294,3 +294,9 @@ AC_POSITION_VERTICAL = {
7: "position_5", 7: "position_5",
8: "swing", 8: "swing",
} }
WH_MACH_MODE: dict[int, str] = {
1: "eco",
2: "max",
3: "bps",
}
+56
View File
@@ -0,0 +1,56 @@
from typing import Optional, Any
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
)
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from pyhon.appliance import HonAppliance
from .const import DOMAIN
from .typedefs import HonEntityDescription
class HonEntity(CoordinatorEntity[DataUpdateCoordinator[dict[str, Any]]]):
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(
self,
hass: HomeAssistantType,
entry: ConfigEntry,
device: HonAppliance,
description: Optional[HonEntityDescription] = None,
) -> None:
self.coordinator = hass.data[DOMAIN][entry.unique_id]["coordinator"]
super().__init__(self.coordinator)
self._hon = hass.data[DOMAIN][entry.unique_id]["hon"]
self._hass = hass
self._device: HonAppliance = device
if description is not None:
self.entity_description = description
self._attr_unique_id = f"{self._device.unique_id}{description.key}"
else:
self._attr_unique_id = self._device.unique_id
self._handle_coordinator_update(update=False)
@property
def device_info(self) -> DeviceInfo:
return DeviceInfo(
identifiers={(DOMAIN, self._device.unique_id)},
manufacturer=self._device.get("brand", "").capitalize(),
name=self._device.nick_name,
model=self._device.model_name,
sw_version=self._device.get("fwVersion", ""),
hw_version=f"{self._device.appliance_type}{self._device.model_id}",
serial_number=self._device.get("serialNumber", ""),
)
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
if update:
self.async_write_ha_state()
+2 -2
View File
@@ -19,7 +19,7 @@ from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity from .entity import HonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -39,7 +39,7 @@ async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
entities = [] entities = []
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in FANS.get(device.appliance_type, []): for description in FANS.get(device.appliance_type, []):
if ( if (
description.key not in device.available_settings description.key not in device.available_settings
-142
View File
@@ -1,142 +0,0 @@
import json
import logging
from contextlib import suppress
from datetime import timedelta
from pathlib import Path
from typing import Optional, Any
import pkg_resources # type: ignore[import, unused-ignore]
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.typing import HomeAssistantType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from pyhon.appliance import HonAppliance
from .const import DOMAIN
from .typedefs import HonEntityDescription, HonOptionEntityDescription, T
_LOGGER = logging.getLogger(__name__)
class HonInfo:
def __init__(self) -> None:
self._manifest: dict[str, Any] = self._get_manifest()
self._hon_version: str = self._manifest.get("version", "")
self._pyhon_version: str = pkg_resources.get_distribution("pyhon").version
@staticmethod
def _get_manifest() -> dict[str, Any]:
manifest = Path(__file__).parent / "manifest.json"
with open(manifest, "r", encoding="utf-8") as file:
result: dict[str, Any] = json.loads(file.read())
return result
@property
def manifest(self) -> dict[str, Any]:
return self._manifest
@property
def hon_version(self) -> str:
return self._hon_version
@property
def pyhon_version(self) -> str:
return self._pyhon_version
class HonCoordinator(DataUpdateCoordinator[None]):
def __init__(self, hass: HomeAssistantType, device: HonAppliance):
"""Initialize my coordinator."""
super().__init__(
hass,
_LOGGER,
name=device.unique_id,
)
self._device = device
self._info = HonInfo()
self._device.subscribe(self.async_set_updated_data)
async def _async_update_data(self) -> None:
return
@property
def info(self) -> HonInfo:
return self._info
class HonEntity(CoordinatorEntity[HonCoordinator]):
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(
self,
hass: HomeAssistantType,
entry: ConfigEntry,
device: HonAppliance,
description: Optional[HonEntityDescription] = None,
) -> None:
coordinator = get_coordinator(hass, device)
super().__init__(coordinator)
self._hon = hass.data[DOMAIN][entry.unique_id]
self._hass = hass
self._coordinator = coordinator
self._device: HonAppliance = device
if description is not None:
self.entity_description = description
self._attr_unique_id = f"{self._device.unique_id}{description.key}"
else:
self._attr_unique_id = self._device.unique_id
self._handle_coordinator_update(update=False)
@property
def device_info(self) -> DeviceInfo:
return DeviceInfo(
identifiers={(DOMAIN, self._device.unique_id)},
manufacturer=self._device.get("brand", ""),
name=self._device.nick_name,
model=self._device.model_name,
sw_version=self._device.get("fwVersion", ""),
)
@callback
def _handle_coordinator_update(self, update: bool = True) -> None:
if update:
self.async_write_ha_state()
def unique_entities(
base_entities: tuple[T, ...],
new_entities: tuple[T, ...],
) -> tuple[T, ...]:
result = list(base_entities)
existing_entities = [entity.key for entity in base_entities]
entity: HonEntityDescription
for entity in new_entities:
if entity.key not in existing_entities:
result.append(entity)
return tuple(result)
def get_coordinator(hass: HomeAssistantType, appliance: HonAppliance) -> HonCoordinator:
coordinators = hass.data[DOMAIN]["coordinators"]
if appliance.unique_id in coordinators:
coordinator: HonCoordinator = hass.data[DOMAIN]["coordinators"][
appliance.unique_id
]
else:
coordinator = HonCoordinator(hass, appliance)
hass.data[DOMAIN]["coordinators"][appliance.unique_id] = coordinator
return coordinator
def get_readable(
description: HonOptionEntityDescription, value: float | str
) -> float | str:
if description.option_list is not None:
with suppress(ValueError):
return description.option_list.get(int(value), value)
return value
+2 -2
View File
@@ -15,7 +15,7 @@ from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity from .entity import HonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -56,7 +56,7 @@ async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
entities = [] entities = []
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in LIGHTS.get(device.appliance_type, []): for description in LIGHTS.get(device.appliance_type, []):
if ( if (
description.key not in device.available_settings description.key not in device.available_settings
+5 -5
View File
@@ -10,7 +10,7 @@ from pyhon.parameter.base import HonParameter
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity from .entity import HonEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -29,7 +29,7 @@ async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistantType, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
entities = [] entities = []
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in LOCKS.get(device.appliance_type, []): for description in LOCKS.get(device.appliance_type, []):
if ( if (
f"settings.{description.key}" not in device.available_settings f"settings.{description.key}" not in device.available_settings
@@ -58,7 +58,7 @@ class HonLockEntity(HonEntity, LockEntity):
setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 setting.value = setting.max if isinstance(setting, HonParameterRange) else 1
self.async_write_ha_state() self.async_write_ha_state()
await self._device.commands["settings"].send() await self._device.commands["settings"].send()
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
async def async_unlock(self, **kwargs: Any) -> None: async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock method.""" """Unlock method."""
@@ -68,7 +68,7 @@ class HonLockEntity(HonEntity, LockEntity):
setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 setting.value = setting.min if isinstance(setting, HonParameterRange) else 0
self.async_write_ha_state() self.async_write_ha_state()
await self._device.commands["settings"].send() await self._device.commands["settings"].send()
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -76,7 +76,7 @@ class HonLockEntity(HonEntity, LockEntity):
return ( return (
super().available super().available
and int(self._device.get("remoteCtrValid", 1)) == 1 and int(self._device.get("remoteCtrValid", 1)) == 1
and self._device.get("attributes.lastConnEvent.category") != "DISCONNECTED" and self._device.connection
) )
@callback @callback
+2 -2
View File
@@ -9,7 +9,7 @@
"iot_class": "cloud_push", "iot_class": "cloud_push",
"issue_tracker": "https://github.com/Andre0512/hon/issues", "issue_tracker": "https://github.com/Andre0512/hon/issues",
"requirements": [ "requirements": [
"pyhOn==0.17.0" "pyhOn==0.17.5"
], ],
"version": "0.14.0-beta.2" "version": "0.14.0"
} }
+26 -9
View File
@@ -5,6 +5,7 @@ from dataclasses import dataclass
from homeassistant.components.number import ( from homeassistant.components.number import (
NumberEntity, NumberEntity,
NumberEntityDescription, NumberEntityDescription,
NumberDeviceClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTime, UnitOfTemperature from homeassistant.const import UnitOfTime, UnitOfTemperature
@@ -16,7 +17,8 @@ from pyhon.appliance import HonAppliance
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, unique_entities from .entity import HonEntity
from .util import unique_entities
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -26,7 +28,7 @@ class HonConfigNumberEntityDescription(NumberEntityDescription):
@dataclass(frozen=True) @dataclass(frozen=True)
class HonNumberEntityDescription(NumberEntityDescription): class HonNumberEntityDescription(NumberEntityDescription):
pass send_key_only: bool = False
NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = { NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
@@ -200,6 +202,17 @@ NUMBERS: dict[str, tuple[NumberEntityDescription, ...]] = {
translation_key="pollen_level", translation_key="pollen_level",
), ),
), ),
"WH": (
HonNumberEntityDescription(
key="settings.tempSel",
name="Target Temperature",
icon="mdi:thermometer",
device_class=NumberDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
translation_key="target_temperature",
send_key_only=True,
),
),
} }
NUMBERS["WD"] = unique_entities(NUMBERS["WM"], NUMBERS["TD"]) NUMBERS["WD"] = unique_entities(NUMBERS["WM"], NUMBERS["TD"])
@@ -210,7 +223,7 @@ async def async_setup_entry(
) -> None: ) -> None:
entities = [] entities = []
entity: HonNumberEntity | HonConfigNumberEntity entity: HonNumberEntity | HonConfigNumberEntity
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in NUMBERS.get(device.appliance_type, []): for description in NUMBERS.get(device.appliance_type, []):
if description.key not in device.available_settings: if description.key not in device.available_settings:
continue continue
@@ -252,11 +265,15 @@ class HonNumberEntity(HonEntity, NumberEntity):
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
if isinstance(setting, HonParameterRange): if isinstance(setting, HonParameterRange):
setting.value = value setting.value = value
command = self.entity_description.key.split(".")[0] key_parts = self.entity_description.key.split(".")
await self._device.commands[command].send() command = key_parts[0]
if self.entity_description.send_key_only:
await self._device.commands[command].send_specific([key_parts[1]])
else:
await self._device.commands[command].send()
if command != "settings": if command != "settings":
self._device.sync_command(command, "settings") self._device.sync_command(command, "settings")
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@callback @callback
def _handle_coordinator_update(self, update: bool = True) -> None: def _handle_coordinator_update(self, update: bool = True) -> None:
@@ -275,7 +292,7 @@ class HonNumberEntity(HonEntity, NumberEntity):
return ( return (
super().available super().available
and int(self._device.get("remoteCtrValid", 1)) == 1 and int(self._device.get("remoteCtrValid", 1)) == 1
and self._device.get("attributes.lastConnEvent.category") != "DISCONNECTED" and self._device.connection
) )
@@ -299,7 +316,7 @@ class HonConfigNumberEntity(HonEntity, NumberEntity):
@property @property
def native_value(self) -> float | None: def native_value(self) -> float | None:
if value := self._device.settings[self.entity_description.key].value: if (value := self._device.settings[self.entity_description.key].value) != "":
return float(value) return float(value)
return None return None
@@ -307,7 +324,7 @@ class HonConfigNumberEntity(HonEntity, NumberEntity):
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
if isinstance(setting, HonParameterRange): if isinstance(setting, HonParameterRange):
setting.value = value setting.value = value
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@property @property
def available(self) -> bool: def available(self) -> bool:
+31 -7
View File
@@ -13,7 +13,8 @@ from homeassistant.helpers.typing import HomeAssistantType
from . import const from . import const
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, unique_entities, get_readable from .entity import HonEntity
from .util import unique_entities, get_readable
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -21,6 +22,7 @@ _LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True) @dataclass(frozen=True)
class HonSelectEntityDescription(SelectEntityDescription): class HonSelectEntityDescription(SelectEntityDescription):
option_list: dict[int, str] | None = None option_list: dict[int, str] | None = None
send_key_only: bool = False
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -184,6 +186,24 @@ SELECTS: dict[str, tuple[SelectEntityDescription, ...]] = {
translation_key="mode", translation_key="mode",
), ),
), ),
"WH": (
HonSelectEntityDescription(
key="settings.tempSel",
name="Target Temperature",
icon="mdi:thermometer",
unit_of_measurement=UnitOfTemperature.CELSIUS,
translation_key="target_temperature",
send_key_only=True,
),
HonSelectEntityDescription(
key="settings.machMode",
name="Mode",
send_key_only=True,
icon="mdi:information",
option_list=const.WH_MACH_MODE,
translation_key="mach_modes_wh",
),
),
"FRE": ( "FRE": (
HonConfigSelectEntityDescription( HonConfigSelectEntityDescription(
key="startProgram.program", key="startProgram.program",
@@ -214,7 +234,7 @@ async def async_setup_entry(
) -> None: ) -> None:
entities = [] entities = []
entity: HonSelectEntity | HonConfigSelectEntity entity: HonSelectEntity | HonConfigSelectEntity
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in SELECTS.get(device.appliance_type, []): for description in SELECTS.get(device.appliance_type, []):
if description.key not in device.available_settings: if description.key not in device.available_settings:
continue continue
@@ -262,7 +282,7 @@ class HonConfigSelectEntity(HonEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
setting.value = self._option_to_number(option, setting.values) setting.value = self._option_to_number(option, setting.values)
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@callback @callback
def _handle_coordinator_update(self, update: bool = True) -> None: def _handle_coordinator_update(self, update: bool = True) -> None:
@@ -312,11 +332,15 @@ class HonSelectEntity(HonEntity, SelectEntity):
async def async_select_option(self, option: str) -> None: async def async_select_option(self, option: str) -> None:
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
setting.value = self._option_to_number(option, setting.values) setting.value = self._option_to_number(option, setting.values)
command = self.entity_description.key.split(".")[0] key_parts = self.entity_description.key.split(".")
await self._device.commands[command].send() command = key_parts[0]
if self.entity_description.send_key_only:
await self._device.commands[command].send_specific([key_parts[1]])
else:
await self._device.commands[command].send()
if command != "settings": if command != "settings":
self._device.sync_command(command, "settings") self._device.sync_command(command, "settings")
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -324,7 +348,7 @@ class HonSelectEntity(HonEntity, SelectEntity):
return ( return (
super().available super().available
and int(self._device.get("remoteCtrValid", 1)) == 1 and int(self._device.get("remoteCtrValid", 1)) == 1
and self._device.get("attributes.lastConnEvent.category") != "DISCONNECTED" and self._device.connection
) )
@callback @callback
+61 -4
View File
@@ -18,7 +18,6 @@ from homeassistant.const import (
UnitOfEnergy, UnitOfEnergy,
UnitOfVolume, UnitOfVolume,
UnitOfMass, UnitOfMass,
UnitOfPower,
UnitOfTime, UnitOfTime,
UnitOfTemperature, UnitOfTemperature,
) )
@@ -29,7 +28,8 @@ from homeassistant.helpers.typing import HomeAssistantType
from . import const from . import const
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, unique_entities, get_readable from .entity import HonEntity
from .util import unique_entities, get_readable
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -83,7 +83,7 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
name="Current Electricity Used", name="Current Electricity Used",
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.POWER, device_class=SensorDeviceClass.POWER,
native_unit_of_measurement=UnitOfPower.KILO_WATT, native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
icon="mdi:lightning-bolt", icon="mdi:lightning-bolt",
translation_key="energy_current", translation_key="energy_current",
), ),
@@ -780,6 +780,63 @@ SENSORS: dict[str, tuple[SensorEntityDescription, ...]] = {
translation_key="air_quality", translation_key="air_quality",
), ),
), ),
"WH": (
HonSensorEntityDescription(
key="temp",
name="Temperature",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
translation_key="temperature",
),
HonSensorEntityDescription(
key="tempZ1",
name="Temp Z1",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
HonSensorEntityDescription(
key="tempZ2",
name="Temp Z2",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
),
HonSensorEntityDescription(
key="tempSel",
name="Target Temperature",
icon="mdi:thermometer",
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
translation_key="target_temperature",
),
HonSensorEntityDescription(
key="machMode",
name="Mode",
icon="mdi:information",
device_class=SensorDeviceClass.ENUM,
option_list=const.WH_MACH_MODE,
translation_key="mach_modes_wh",
),
HonSensorEntityDescription(
key="smartTestStatus",
name="Smart Test Status",
),
HonSensorEntityDescription(
key="anodeMaintenanceStatus",
name="Anode Maintenance Status",
),
HonSensorEntityDescription(
key="tankMaintenanceStatus",
name="Tank Maintenance Status",
),
HonSensorEntityDescription(
key="heatingStatus",
name="Heating Status",
),
),
"FRE": ( "FRE": (
HonSensorEntityDescription( HonSensorEntityDescription(
key="tempEnv", key="tempEnv",
@@ -812,7 +869,7 @@ async def async_setup_entry(
) -> None: ) -> None:
entities = [] entities = []
entity: HonSensorEntity | HonConfigSensorEntity entity: HonSensorEntity | HonConfigSensorEntity
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in SENSORS.get(device.appliance_type, []): for description in SENSORS.get(device.appliance_type, []):
if isinstance(description, HonSensorEntityDescription): if isinstance(description, HonSensorEntityDescription):
if device.get(description.key) is None: if device.get(description.key) is None:
+41 -16
View File
@@ -13,7 +13,8 @@ from pyhon.parameter.base import HonParameter
from pyhon.parameter.range import HonParameterRange from pyhon.parameter.range import HonParameterRange
from .const import DOMAIN from .const import DOMAIN
from .hon import HonEntity, unique_entities from .entity import HonEntity
from .util import unique_entities
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -22,6 +23,10 @@ _LOGGER = logging.getLogger(__name__)
class HonControlSwitchEntityDescription(SwitchEntityDescription): class HonControlSwitchEntityDescription(SwitchEntityDescription):
turn_on_key: str = "" turn_on_key: str = ""
turn_off_key: str = "" turn_off_key: str = ""
only_mandatory_parameters: bool = False
on_value: bool | float = True
off_value: bool | float = False
to_sync: bool = False
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -381,6 +386,20 @@ SWITCHES: dict[str, tuple[SwitchEntityDescription, ...]] = {
translation_key="touch_tone", translation_key="touch_tone",
), ),
), ),
"WH": (
HonControlSwitchEntityDescription(
key="onOffStatus",
name="Power",
icon="mdi:power-standby",
turn_on_key="startProgram",
turn_off_key="stopProgram",
translation_key="power",
only_mandatory_parameters=True,
on_value=1,
off_value=0,
to_sync=True,
),
),
"FRE": ( "FRE": (
HonSwitchEntityDescription( HonSwitchEntityDescription(
key="quickModeZ2", key="quickModeZ2",
@@ -406,7 +425,7 @@ async def async_setup_entry(
) -> None: ) -> None:
entities = [] entities = []
entity: HonConfigSwitchEntity | HonControlSwitchEntity | HonSwitchEntity entity: HonConfigSwitchEntity | HonControlSwitchEntity | HonSwitchEntity
for device in hass.data[DOMAIN][entry.unique_id].appliances: for device in hass.data[DOMAIN][entry.unique_id]["hon"].appliances:
for description in SWITCHES.get(device.appliance_type, []): for description in SWITCHES.get(device.appliance_type, []):
if isinstance(description, HonConfigSwitchEntityDescription): if isinstance(description, HonConfigSwitchEntityDescription):
if description.key not in device.available_settings: if description.key not in device.available_settings:
@@ -446,7 +465,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity):
setting.value = setting.max if isinstance(setting, HonParameterRange) else 1 setting.value = setting.max if isinstance(setting, HonParameterRange) else 1
self.async_write_ha_state() self.async_write_ha_state()
await self._device.commands["settings"].send() await self._device.commands["settings"].send()
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
setting = self._device.settings[f"settings.{self.entity_description.key}"] setting = self._device.settings[f"settings.{self.entity_description.key}"]
@@ -455,7 +474,7 @@ class HonSwitchEntity(HonEntity, SwitchEntity):
setting.value = setting.min if isinstance(setting, HonParameterRange) else 0 setting.value = setting.min if isinstance(setting, HonParameterRange) else 0
self.async_write_ha_state() self.async_write_ha_state()
await self._device.commands["settings"].send() await self._device.commands["settings"].send()
await self.coordinator.async_refresh() self.coordinator.async_set_updated_data({})
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -484,20 +503,26 @@ class HonControlSwitchEntity(HonEntity, SwitchEntity):
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
"""Return True if entity is on.""" """Return True if entity is on."""
return self._device.get(self.entity_description.key, False) on_value = self.entity_description.on_value
off_value = self.entity_description.off_value
return self._device.get(self.entity_description.key, off_value) == on_value
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
self._device.sync_command(self.entity_description.turn_on_key, "settings") desc = self.entity_description
await self.coordinator.async_refresh() self._device.sync_command(desc.turn_on_key, "settings", desc.to_sync)
await self._device.commands[self.entity_description.turn_on_key].send() self.coordinator.async_set_updated_data({})
self._device.attributes[self.entity_description.key] = True command = self._device.commands[desc.turn_on_key]
await command.send(desc.only_mandatory_parameters)
self._device.attributes[desc.key] = desc.on_value
self.async_write_ha_state() self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
self._device.sync_command(self.entity_description.turn_off_key, "settings") desc = self.entity_description
await self.coordinator.async_refresh() self._device.sync_command(desc.turn_off_key, "settings", desc.to_sync)
await self._device.commands[self.entity_description.turn_off_key].send() self.coordinator.async_set_updated_data({})
self._device.attributes[self.entity_description.key] = False command = self._device.commands[desc.turn_off_key]
await command.send(desc.only_mandatory_parameters)
self._device.attributes[desc.key] = desc.off_value
self.async_write_ha_state() self.async_write_ha_state()
@property @property
@@ -506,7 +531,7 @@ class HonControlSwitchEntity(HonEntity, SwitchEntity):
return ( return (
super().available super().available
and int(self._device.get("remoteCtrValid", 1)) == 1 and int(self._device.get("remoteCtrValid", 1)) == 1
and self._device.get("attributes.lastConnEvent.category") != "DISCONNECTED" and self._device.connection
) )
@property @property
@@ -540,16 +565,16 @@ class HonConfigSwitchEntity(HonEntity, SwitchEntity):
if type(setting) == HonParameter: if type(setting) == HonParameter:
return return
setting.value = setting.max if isinstance(setting, HonParameterRange) else "1" setting.value = setting.max if isinstance(setting, HonParameterRange) else "1"
self.coordinator.async_set_updated_data({})
self.async_write_ha_state() self.async_write_ha_state()
await self.coordinator.async_refresh()
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
setting = self._device.settings[self.entity_description.key] setting = self._device.settings[self.entity_description.key]
if type(setting) == HonParameter: if type(setting) == HonParameter:
return return
setting.value = setting.min if isinstance(setting, HonParameterRange) else "0" setting.value = setting.min if isinstance(setting, HonParameterRange) else "0"
self.coordinator.async_set_updated_data({})
self.async_write_ha_state() self.async_write_ha_state()
await self.coordinator.async_refresh()
@callback @callback
def _handle_coordinator_update(self, update: bool = True) -> None: def _handle_coordinator_update(self, update: bool = True) -> None:
@@ -9,6 +9,79 @@
} }
} }
} }
}
}
},
"entity": {
"sensor": {
"mode": {
"state": {
"0": "Изключен",
"1": "Готов",
"2": "Работи",
"3": "На пауза",
"5": "Scheduled",
"6": "Грешка",
"7": "Завършен"
}
},
"errors": {
"state": {
"00": "Няма грешки",
"100000000000": "E2: Провери дали вратата е затворена",
"8000000000000": "E4: Провери подаването на вода"
}
},
"programs": {
"state": {
"0": "Стандартна",
"62": "Памук",
"63": "Синтетика",
"64": "Смесен тип",
"66": "Чаршафи",
"71": "Пердета",
"72": "Спорт",
"74": "i-time",
"75": "Олекотени завивки",
"76": "Вълна",
"78": "i-Refresh",
"83": "Хавлиена кърпа",
"85": "Бързо Сушене",
"92": "Деликатно пране",
"103": "Отдалечен"
}
},
"program_phases_td": {
"state": {
"0": "Изчаване",
"2": "Сушене",
"3": "Охлажане",
"11": "11"
}
},
"tumbledryertemplevel": {
"state": {
"1": "Хладен въздух",
"2": "Ниска температура L-1",
"3": "Средна температура L-2",
"4": "Висока температура L-3"
}
},
"dry_levels": {
"state": {
"3": "Готови за съхранение",
"12": "Готови за гладене H-1",
"13": "Готови за съхранение H-2",
"14": "Екстра сухо H-3"
}
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
}
}, },
"entity": { "entity": {
"sensor": { "sensor": {
@@ -2326,5 +2399,18 @@
"name": "Light" "name": "Light"
} }
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
}
},
"binary_sensor": {
"power-state": {
"name": "Power State"
}
} }
} }
@@ -931,6 +931,13 @@
"high": "Vysoká" "high": "Vysoká"
}, },
"name": "Úroveň vlhkosti" "name": "Úroveň vlhkosti"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Pevný - Poloha 5", "position_5": "Pevný - Poloha 5",
"swing": "Pohyb lamel" "swing": "Pohyb lamel"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Výměna filtru" "name": "Výměna filtru"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Hoch" "high": "Hoch"
}, },
"name": "Grad der Luftfeuchtigkeit" "name": "Grad der Luftfeuchtigkeit"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fest - Position 5", "position_5": "Fest - Position 5",
"swing": "Schwenkbewegung" "swing": "Schwenkbewegung"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Filteraustausch" "name": "Filteraustausch"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Υψηλός" "high": "Υψηλός"
}, },
"name": "Επίπεδο υγρασίας" "name": "Επίπεδο υγρασίας"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Σταθερός - Θέση 5", "position_5": "Σταθερός - Θέση 5",
"swing": "Ταλάντευση" "swing": "Ταλάντευση"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Αντικατάσταση φίλτρου" "name": "Αντικατάσταση φίλτρου"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -964,6 +964,13 @@
"high": "High" "high": "High"
}, },
"name": "Humidity level" "name": "Humidity level"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1869,6 +1876,13 @@
"position_5": "Fixed - Position 5", "position_5": "Fixed - Position 5",
"swing": "Swing" "swing": "Swing"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2068,6 +2082,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Filter replacement" "name": "Filter replacement"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Alto" "high": "Alto"
}, },
"name": "Nivel de humedad" "name": "Nivel de humedad"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fijo - Posición 5", "position_5": "Fijo - Posición 5",
"swing": "Oscilar" "swing": "Oscilar"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Sustitución del filtro" "name": "Sustitución del filtro"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Élevé" "high": "Élevé"
}, },
"name": "Niveau dhumidité" "name": "Niveau dhumidité"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fixe - Position 5", "position_5": "Fixe - Position 5",
"swing": "Oscillation" "swing": "Oscillation"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Remplacement du filtre" "name": "Remplacement du filtre"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -448,6 +448,13 @@
"high": "גָבוֹהַ" "high": "גָבוֹהַ"
}, },
"name": "Humidity level" "name": "Humidity level"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -859,6 +866,13 @@
"position_5": "Fixed - Position 5", "position_5": "Fixed - Position 5",
"swing": "Swing" "swing": "Swing"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -1058,6 +1072,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Filter replacement" "name": "Filter replacement"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Visoko" "high": "Visoko"
}, },
"name": "Razina vlažnosti" "name": "Razina vlažnosti"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fiksno - Položaj 5", "position_5": "Fiksno - Položaj 5",
"swing": "Njihanje" "swing": "Njihanje"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Zamjena filtra" "name": "Zamjena filtra"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -940,6 +940,13 @@
"high": "Alto" "high": "Alto"
}, },
"name": "Livello di umidità" "name": "Livello di umidità"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1829,6 +1836,13 @@
"position_5": "Fissa - Posizione 5", "position_5": "Fissa - Posizione 5",
"swing": "Swing" "swing": "Swing"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2028,6 +2042,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Sostituzione filtro" "name": "Sostituzione filtro"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Hoog" "high": "Hoog"
}, },
"name": "Vochtigheidsniveau" "name": "Vochtigheidsniveau"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Vast - Positie 5", "position_5": "Vast - Positie 5",
"swing": "Draaiend" "swing": "Draaiend"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Filter vervangen" "name": "Filter vervangen"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Wysokie" "high": "Wysokie"
}, },
"name": "Poziom wilgotności" "name": "Poziom wilgotności"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Stały - Pozycja 5", "position_5": "Stały - Pozycja 5",
"swing": "Swing" "swing": "Swing"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Wymiana filtra" "name": "Wymiana filtra"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Alta" "high": "Alta"
}, },
"name": "Nível de humidade" "name": "Nível de humidade"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fixa - Posição 5", "position_5": "Fixa - Posição 5",
"swing": "Oscilação" "swing": "Oscilação"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Substituição do filtro" "name": "Substituição do filtro"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Crescută" "high": "Crescută"
}, },
"name": "Nivelul de umiditate" "name": "Nivelul de umiditate"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fix - Poziție 5", "position_5": "Fix - Poziție 5",
"swing": "Baleiere" "swing": "Baleiere"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Înlocuirea filtrului" "name": "Înlocuirea filtrului"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Высок." "high": "Высок."
}, },
"name": "Уровень влажности" "name": "Уровень влажности"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Фиксированное - Позиция 5", "position_5": "Фиксированное - Позиция 5",
"swing": "Качание" "swing": "Качание"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Замена фильтра" "name": "Замена фильтра"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Vysoké" "high": "Vysoké"
}, },
"name": "Úroveň vlhkosti" "name": "Úroveň vlhkosti"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Pevný - Poloha 5", "position_5": "Pevný - Poloha 5",
"swing": "Otáčanie" "swing": "Otáčanie"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Výmena filtra" "name": "Výmena filtra"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "High" "high": "High"
}, },
"name": "Nivo vlažnosti" "name": "Nivo vlažnosti"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fiksno - Položaj 5", "position_5": "Fiksno - Položaj 5",
"swing": "Nihanje" "swing": "Nihanje"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Menjava filtra" "name": "Menjava filtra"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Visoka" "high": "Visoka"
}, },
"name": "Nivo vlage" "name": "Nivo vlage"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Fiksiran - Položaj 5", "position_5": "Fiksiran - Položaj 5",
"swing": "Njihanje" "swing": "Njihanje"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Zamena filtera" "name": "Zamena filtera"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -931,6 +931,13 @@
"high": "Yüksek" "high": "Yüksek"
}, },
"name": "Nem seviyesi" "name": "Nem seviyesi"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1816,6 +1823,13 @@
"position_5": "Sabit - Pozisyon 5", "position_5": "Sabit - Pozisyon 5",
"swing": "Salınım" "swing": "Salınım"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2015,6 +2029,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "Filtre değişimi" "name": "Filtre değişimi"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
@@ -924,6 +924,13 @@
"high": "高" "high": "高"
}, },
"name": "湿度水平" "name": "湿度水平"
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"select": { "select": {
@@ -1802,6 +1809,13 @@
"position_5": "固定 - 位置 5", "position_5": "固定 - 位置 5",
"swing": "摆动" "swing": "摆动"
} }
},
"mach_modes_wh": {
"state": {
"eco": "Eco",
"max": "Max",
"bps": "BPS"
}
} }
}, },
"switch": { "switch": {
@@ -2001,6 +2015,9 @@
}, },
"filter_replacement": { "filter_replacement": {
"name": "更换过滤器" "name": "更换过滤器"
},
"power-state": {
"name": "Power State"
} }
}, },
"button": { "button": {
+28
View File
@@ -0,0 +1,28 @@
import logging
from contextlib import suppress
from .typedefs import HonEntityDescription, HonOptionEntityDescription, T
_LOGGER = logging.getLogger(__name__)
def unique_entities(
base_entities: tuple[T, ...],
new_entities: tuple[T, ...],
) -> tuple[T, ...]:
result = list(base_entities)
existing_entities = [entity.key for entity in base_entities]
entity: HonEntityDescription
for entity in new_entities:
if entity.key not in existing_entities:
result.append(entity)
return tuple(result)
def get_readable(
description: HonOptionEntityDescription, value: float | str
) -> float | str:
if description.option_list is not None:
with suppress(ValueError):
return description.option_list.get(int(value), value)
return value
+1 -1
View File
@@ -1 +1 @@
pyhOn==0.17.0 pyhOn==0.17.5