From 20327fab9fc11fc116475b893020a0ff76c47c9b Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 13 Oct 2024 23:41:19 +0200 Subject: [PATCH 1/5] Add hibernation switch entity --- custom_components/zcsmower/const.py | 1 + custom_components/zcsmower/coordinator.py | 22 +++++ custom_components/zcsmower/entity.py | 51 +++++++++- custom_components/zcsmower/switch.py | 113 ++++++++++++++++++++++ 4 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 custom_components/zcsmower/switch.py diff --git a/custom_components/zcsmower/const.py b/custom_components/zcsmower/const.py index fb90253..9b27839 100644 --- a/custom_components/zcsmower/const.py +++ b/custom_components/zcsmower/const.py @@ -23,6 +23,7 @@ Platform.LAWN_MOWER, Platform.NUMBER, Platform.SENSOR, + Platform.SWITCH, Platform.VACUUM, ] diff --git a/custom_components/zcsmower/coordinator.py b/custom_components/zcsmower/coordinator.py index 92d111a..09b415f 100644 --- a/custom_components/zcsmower/coordinator.py +++ b/custom_components/zcsmower/coordinator.py @@ -169,6 +169,7 @@ def __init__( self._loop = asyncio.get_event_loop() self._scheduled_update_listeners: asyncio.TimerHandle | None = None + self._scheduled_update_entry: asyncio.TimerHandle | None = None def _convert_datetime_from_api( self, @@ -231,6 +232,27 @@ async def _async_update_listeners(self) -> None: lambda: self.async_update_listeners(), ) + async def async_set_entry_option( + self, + key: str, + value: any, + ) -> None: + """Set config entry option and update config entry after 3 second.""" + _options = dict(self.config_entry.options) + _options.update( + { + key: value + } + ) + if self._scheduled_update_entry: + self._scheduled_update_entry.cancel() + self._scheduled_update_entry = self.hass.loop.call_later( + 3, + lambda: self.hass.config_entries.async_update_entry( + self.config_entry, options=_options + ), + ) + def get_mower_attributes( self, imei: str, diff --git a/custom_components/zcsmower/entity.py b/custom_components/zcsmower/entity.py index 3c32b89..943d7a9 100644 --- a/custom_components/zcsmower/entity.py +++ b/custom_components/zcsmower/entity.py @@ -15,7 +15,10 @@ ATTR_SW_VERSION, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.device_registry import ( + DeviceEntryType, + DeviceInfo, +) from homeassistant.helpers.entity import ( EntityCategory, EntityDescription, @@ -30,12 +33,13 @@ ATTR_ERROR, ATTR_AVAILABLE, ATTRIBUTION, + MANUFACTURER_DEFAULT, ) from .coordinator import ZcsMowerDataUpdateCoordinator class ZcsMowerEntity(CoordinatorEntity): - """ZCS Lawn Mower Robot class.""" + """ZCS Lawn Mower Robot entity class.""" _attr_attribution = ATTRIBUTION _attr_has_entity_name = True @@ -133,7 +137,7 @@ def _update_handler(self) -> None: @property def unique_id(self) -> str: - """Return the unique ID of the sensor.""" + """Return the unique ID of the entity.""" return self._unique_id @property @@ -165,3 +169,44 @@ def _handle_coordinator_update(self) -> None: """Handle updated data from the coordinator.""" self._update_handler() self.async_write_ha_state() + + +class ZcsConfigEntity(CoordinatorEntity): + """ZCS Configuration entity class.""" + + _attr_has_entity_name = True + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + coordinator: ZcsMowerDataUpdateCoordinator, + entity_type: str, + entity_description: EntityDescription, + ) -> None: + """Initialize.""" + super().__init__(coordinator) + + self.hass = hass + self.config_entry = config_entry + self.entity_description = entity_description + + self._name = self.config_entry.title + self._entity_key = entity_description.key + self._unique_id = slugify(f"{self._entity_key}_{config_entry.entry_id}") + + self.entity_id = f"{entity_type}.{self._unique_id}" + self._attr_device_info = DeviceInfo( + entry_type=DeviceEntryType.SERVICE, + identifiers={ + (DOMAIN, self._unique_id) + }, + name=config_entry.title, + manufacturer=MANUFACTURER_DEFAULT, + ) + self._config_key = entity_description.config_key + + @property + def unique_id(self) -> str: + """Return the unique ID of the entity.""" + return self._unique_id diff --git a/custom_components/zcsmower/switch.py b/custom_components/zcsmower/switch.py new file mode 100644 index 0000000..381ce70 --- /dev/null +++ b/custom_components/zcsmower/switch.py @@ -0,0 +1,113 @@ +"""ZCS Lawn Mower Robot switch platform.""" + +from __future__ import annotations + +from dataclasses import dataclass + +from homeassistant.core import HomeAssistant +from homeassistant.const import ( + ATTR_STATE, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.components.switch import ( + SwitchDeviceClass, + SwitchEntity, + SwitchEntityDescription, +) +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.entity import ( + Entity, + EntityCategory, +) +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from homeassistant.util import slugify + +from .const import ( + CONF_HIBERNATION_ENABLE, +) +from .coordinator import ZcsMowerDataUpdateCoordinator +from .entity import ZcsConfigEntity + + +@dataclass(frozen=True, kw_only=True) +class ZcsConfigSwitchEntityDescription(SwitchEntityDescription): + """Describes ZCS Lawn Mower Robot switch entity.""" + + config_key: str + + +ENTITY_DESCRIPTIONS = ( + ZcsConfigSwitchEntityDescription( + key="mower_hibernation", + translation_key="hibernation", + entity_category=EntityCategory.CONFIG, + config_key=CONF_HIBERNATION_ENABLE, + ), +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Entity, +) -> None: + """Do setup sensors from a config entry created in the integrations UI.""" + coordinator = config_entry.runtime_data + async_add_entities( + [ + ZcsConfigSwitchEntity( + hass=hass, + config_entry=config_entry, + coordinator=coordinator, + entity_description=entity_description, + ) + for entity_description in ENTITY_DESCRIPTIONS + ], + update_before_add=True, + ) + + +class ZcsConfigSwitchEntity(ZcsConfigEntity, SwitchEntity): + """Representation of a ZCS configuration switch.""" + + _attr_has_entity_name = True + + def __init__( + self, + hass: HomeAssistant, + config_entry: ConfigEntry, + coordinator: ZcsMowerDataUpdateCoordinator, + entity_description: SwitchEntityDescription, + ) -> None: + """Initialize the switch class.""" + super().__init__( + hass=hass, + config_entry=config_entry, + coordinator=coordinator, + entity_type="switch", + entity_description=entity_description, + ) + + @property + def unique_id(self) -> str: + """Return the unique ID of the sensor.""" + return self._unique_id + + @property + def is_on(self) -> bool: + """Return True if entity is on.""" + return bool(self.coordinator.get_entry_option(self._config_key, False)) + + async def async_turn_on(self, **kwargs: any) -> None: + """Turn the entity on.""" + await self.coordinator.async_set_entry_option( + self._config_key, + True, + ) + + async def async_turn_off(self, **kwargs: any) -> None: + """Turn the entity off.""" + await self.coordinator.async_set_entry_option( + self._config_key, + False, + ) From 9c53f276e66ece0fdd4bd8684a12ff0327591b2b Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 13 Oct 2024 23:41:49 +0200 Subject: [PATCH 2/5] Fix is_on method in hibernation switch entity --- custom_components/zcsmower/switch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/zcsmower/switch.py b/custom_components/zcsmower/switch.py index 381ce70..d785780 100644 --- a/custom_components/zcsmower/switch.py +++ b/custom_components/zcsmower/switch.py @@ -96,7 +96,7 @@ def unique_id(self) -> str: @property def is_on(self) -> bool: """Return True if entity is on.""" - return bool(self.coordinator.get_entry_option(self._config_key, False)) + return bool(self.config_entry.options.get(self._config_key, False)) async def async_turn_on(self, **kwargs: any) -> None: """Turn the entity on.""" From 8736c7fd47d73bdef4f5e363c106f8c6bee6cc02 Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 13 Oct 2024 23:42:24 +0200 Subject: [PATCH 3/5] Add strings and translations for hibernation switch entity --- custom_components/zcsmower/strings.json | 5 +++++ custom_components/zcsmower/translations/de.json | 5 +++++ custom_components/zcsmower/translations/en.json | 5 +++++ custom_components/zcsmower/translations/it.json | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/custom_components/zcsmower/strings.json b/custom_components/zcsmower/strings.json index c946cc9..12fd741 100644 --- a/custom_components/zcsmower/strings.json +++ b/custom_components/zcsmower/strings.json @@ -807,6 +807,11 @@ } } }, + "switch": { + "hibernation": { + "name": "Winter break" + } + }, "vacuum": { "mower": { "state": { diff --git a/custom_components/zcsmower/translations/de.json b/custom_components/zcsmower/translations/de.json index 80bb88b..d0c20bc 100644 --- a/custom_components/zcsmower/translations/de.json +++ b/custom_components/zcsmower/translations/de.json @@ -804,6 +804,11 @@ } } }, + "switch": { + "hibernation": { + "name": "Winterpause" + } + }, "vacuum": { "mower": { "state": { diff --git a/custom_components/zcsmower/translations/en.json b/custom_components/zcsmower/translations/en.json index a63787f..cf84a21 100644 --- a/custom_components/zcsmower/translations/en.json +++ b/custom_components/zcsmower/translations/en.json @@ -807,6 +807,11 @@ } } }, + "switch": { + "hibernation": { + "name": "Winter break" + } + }, "vacuum": { "mower": { "state": { diff --git a/custom_components/zcsmower/translations/it.json b/custom_components/zcsmower/translations/it.json index 98baa71..6ad142d 100644 --- a/custom_components/zcsmower/translations/it.json +++ b/custom_components/zcsmower/translations/it.json @@ -804,6 +804,11 @@ } } }, + "switch": { + "hibernation": { + "name": "Pausa invernale" + } + }, "vacuum": { "mower": { "state": { From 3515bb2df6adf8e7606014d6b134d1b07db6404d Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 13 Oct 2024 23:47:47 +0200 Subject: [PATCH 4/5] Fix RUFF warnings --- custom_components/zcsmower/switch.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/custom_components/zcsmower/switch.py b/custom_components/zcsmower/switch.py index d785780..9e72a03 100644 --- a/custom_components/zcsmower/switch.py +++ b/custom_components/zcsmower/switch.py @@ -5,22 +5,15 @@ from dataclasses import dataclass from homeassistant.core import HomeAssistant -from homeassistant.const import ( - ATTR_STATE, -) from homeassistant.config_entries import ConfigEntry from homeassistant.components.switch import ( - SwitchDeviceClass, SwitchEntity, SwitchEntityDescription, ) -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import ( Entity, EntityCategory, ) -from homeassistant.helpers.update_coordinator import CoordinatorEntity -from homeassistant.util import slugify from .const import ( CONF_HIBERNATION_ENABLE, From 0657b3cf87a4143cf41cff30e87c1cece5d5d41d Mon Sep 17 00:00:00 2001 From: ufozone Date: Sun, 13 Oct 2024 23:47:56 +0200 Subject: [PATCH 5/5] Update README --- README.md | 5 +++++ info.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 73de7e9..904c020 100644 --- a/README.md +++ b/README.md @@ -308,6 +308,11 @@ _These entities are disabled by default. You have to activate it if you want to connect_expiration, infinity_state, infinity_expiration ``` +### Switch + +* hibernation + _This entity only exists once per configuration entry. The configuration affects all lawn mowers set up for this configuration entry._ + ### Vacuum _This entity is disabled by default. You have to activate it if you want to use it._ diff --git a/info.md b/info.md index 73de7e9..904c020 100644 --- a/info.md +++ b/info.md @@ -308,6 +308,11 @@ _These entities are disabled by default. You have to activate it if you want to connect_expiration, infinity_state, infinity_expiration ``` +### Switch + +* hibernation + _This entity only exists once per configuration entry. The configuration affects all lawn mowers set up for this configuration entry._ + ### Vacuum _This entity is disabled by default. You have to activate it if you want to use it._