From 35b5c5a8560e9c641be993025774826bda14f608 Mon Sep 17 00:00:00 2001 From: AndreasS Date: Fri, 12 Jan 2024 16:09:43 +0100 Subject: [PATCH 1/5] Support toggling features (#67) * Support toggling Ionizer, UILight and SafetyLock * Add Swedish translation * Small fix for translations * Handle incorrect fan speed percentage ( hopefully fixing https://github.com/JohNan/homeassistant-wellbeing/issues/66) Tested on A9/AX9 --- custom_components/wellbeing/api.py | 7 +++ custom_components/wellbeing/const.py | 3 +- custom_components/wellbeing/fan.py | 14 +++++- custom_components/wellbeing/switch.py | 45 +++++++++++++++++++ .../wellbeing/translations/en.json | 7 ++- .../wellbeing/translations/fr.json | 2 +- .../wellbeing/translations/nb.json | 2 +- .../wellbeing/translations/pl.json | 2 +- .../wellbeing/translations/se.json | 38 ++++++++++++++++ 9 files changed, 112 insertions(+), 8 deletions(-) create mode 100644 custom_components/wellbeing/switch.py create mode 100644 custom_components/wellbeing/translations/se.json diff --git a/custom_components/wellbeing/api.py b/custom_components/wellbeing/api.py index cf00289..7947641 100644 --- a/custom_components/wellbeing/api.py +++ b/custom_components/wellbeing/api.py @@ -434,6 +434,13 @@ async def set_work_mode(self, pnc_id: str, mode: Mode): result = await self._send_command(self._current_access_token, pnc_id, data) _LOGGER.debug(f"Set Fan Speed: {result}") + async def set_feature_state(self, pnc_id: str, feature: str, state: bool): + """Set the state of a feature (Ionizer, UILight, SafetyLock).""" + # Construct the command directly using the feature name + data = {feature: state} + await self._send_command(self._current_access_token, pnc_id, data) + _LOGGER.debug(f"Set {feature} State to {state}") + async def _send_command(self, access_token: str, pnc_id: str, command: dict) -> None: """Get data from the API.""" headers = { diff --git a/custom_components/wellbeing/const.py b/custom_components/wellbeing/const.py index fcc2d44..584c080 100644 --- a/custom_components/wellbeing/const.py +++ b/custom_components/wellbeing/const.py @@ -15,7 +15,8 @@ SENSOR = "sensor" SWITCH = "switch" FAN = "fan" -PLATFORMS = [SENSOR, FAN, BINARY_SENSOR] +SWITCH = "switch" +PLATFORMS = [SENSOR, FAN, BINARY_SENSOR, SWITCH] # Configuration and options CONF_ENABLED = "enabled" diff --git a/custom_components/wellbeing/fan.py b/custom_components/wellbeing/fan.py index 515340f..9e5ae17 100644 --- a/custom_components/wellbeing/fan.py +++ b/custom_components/wellbeing/fan.py @@ -114,9 +114,18 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: def is_on(self): return self.preset_mode is not Mode.OFF - async def async_turn_on(self, speed: str = None, percentage: int = None, - preset_mode: str = None, **kwargs) -> None: + async def async_turn_on(self, speed: str = None, percentage: int = None, preset_mode: str = None, **kwargs) -> None: self._preset_mode = Mode(preset_mode or Mode.AUTO.value) + + # Handle incorrect percentage + if percentage is not None and isinstance(percentage, str): + try: + percentage = int(percentage) + except ValueError: + _LOGGER.error(f"Invalid percentage value: {percentage}") + return + + # Proceed with the provided or default percentage self._speed = math.floor(percentage_to_ranged_value(self._speed_range, percentage or 10)) self.get_appliance.clear_mode() self.get_entity.clear_state() @@ -127,6 +136,7 @@ async def async_turn_on(self, speed: str = None, percentage: int = None, await asyncio.sleep(10) await self.coordinator.async_request_refresh() + async def async_turn_off(self, **kwargs) -> None: """Turn off the entity.""" self._preset_mode = Mode.OFF diff --git a/custom_components/wellbeing/switch.py b/custom_components/wellbeing/switch.py new file mode 100644 index 0000000..0b52c58 --- /dev/null +++ b/custom_components/wellbeing/switch.py @@ -0,0 +1,45 @@ +"""Switch platform for Wellbeing.""" +from homeassistant.components.switch import SwitchEntity +from .const import DOMAIN +from .entity import WellbeingEntity + +async def async_setup_entry(hass, entry, async_add_devices): + """Setup switch platform.""" + coordinator = hass.data[DOMAIN][entry.entry_id] + appliances = coordinator.data.get('appliances', None) + + if appliances is not None: + for pnc_id, appliance in appliances.appliances.items(): + # Assuming that the appliance supports these features + async_add_devices([ + WellbeingSwitch(coordinator, entry, pnc_id, "Ionizer"), + WellbeingSwitch(coordinator, entry, pnc_id, "UILight"), + WellbeingSwitch(coordinator, entry, pnc_id, "SafetyLock"), + ]) + +class WellbeingSwitch(WellbeingEntity, SwitchEntity): + """Wellbeing Switch class.""" + + def __init__(self, coordinator, config_entry, pnc_id, function): + super().__init__(coordinator, config_entry, pnc_id, "binary_sensor", function) + self._function = function + self._is_on = self.get_entity.state + + @property + def is_on(self): + """Return true if switch is on.""" + return self._is_on + + async def async_turn_on(self, **kwargs): + """Turn the switch on.""" + await self.coordinator.api.set_feature_state(self.pnc_id, self._function, True) + self._is_on = True + self.async_write_ha_state() + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs): + """Turn the switch off.""" + await self.coordinator.api.set_feature_state(self.pnc_id, self._function, False) + self._is_on = False + self.async_write_ha_state() + await self.coordinator.async_request_refresh() diff --git a/custom_components/wellbeing/translations/en.json b/custom_components/wellbeing/translations/en.json index 041a79e..475262c 100644 --- a/custom_components/wellbeing/translations/en.json +++ b/custom_components/wellbeing/translations/en.json @@ -13,7 +13,7 @@ "password": "Password" }, "description": "Enter the password for {username}.", - "title": "Reauthenticate an August account" + "title": "Reauthenticate an Electrolux account" } }, "error": { @@ -27,7 +27,10 @@ "step": { "user": { "data": { - "scan_interval": "API update interval (seconds)" + "scan_interval": "API update interval (seconds)", + "binary_sensor": "Binary sensor activated", + "sensor": "Sensor activated", + "switch": "Switch activated" } } } diff --git a/custom_components/wellbeing/translations/fr.json b/custom_components/wellbeing/translations/fr.json index 5844ad2..bc966b8 100644 --- a/custom_components/wellbeing/translations/fr.json +++ b/custom_components/wellbeing/translations/fr.json @@ -13,7 +13,7 @@ "password": "Password" }, "description": "Enter the password for {username}.", - "title": "Reauthenticate an August account" + "title": "Reauthenticate an Electrolux account" } }, "error": { diff --git a/custom_components/wellbeing/translations/nb.json b/custom_components/wellbeing/translations/nb.json index 55f707a..f87a4aa 100644 --- a/custom_components/wellbeing/translations/nb.json +++ b/custom_components/wellbeing/translations/nb.json @@ -13,7 +13,7 @@ "password": "Password" }, "description": "Enter the password for {username}.", - "title": "Reauthenticate an August account" + "title": "Reauthenticate an Electrolux account" } }, "error": { diff --git a/custom_components/wellbeing/translations/pl.json b/custom_components/wellbeing/translations/pl.json index 52a3249..aa5155d 100644 --- a/custom_components/wellbeing/translations/pl.json +++ b/custom_components/wellbeing/translations/pl.json @@ -13,7 +13,7 @@ "password": "Password" }, "description": "Enter the password for {username}.", - "title": "Reauthenticate an August account" + "title": "Reauthenticate an Electrolux account" } }, "error": { diff --git a/custom_components/wellbeing/translations/se.json b/custom_components/wellbeing/translations/se.json new file mode 100644 index 0000000..d8c2722 --- /dev/null +++ b/custom_components/wellbeing/translations/se.json @@ -0,0 +1,38 @@ +{ + "config": { + "step": { + "user": { + "description": "Om du behöver hjälp med konfigurationen, se här: https://github.com/JohNan/homeassistant-wellbeing", + "data": { + "username": "Användarnamn", + "password": "Lösenord" + } + }, + "reauth_validate": { + "data": { + "password": "Lösenord" + }, + "description": "Ange lösenord för {username}.", + "title": "Återautentisera Electrolux-kontot" + } + }, + "error": { + "auth": "Användarnamn eller lösenord är felaktigt." + }, + "abort": { + "single_instance_allowed": "Endast en instans är tillåten." + } + }, + "options": { + "step": { + "user": { + "data": { + "scan_interval": "API-uppdateringsintervall (sekunder)", + "binary_sensor": "Binär sensor aktiverad", + "sensor": "Sensor aktiverad", + "switch": "Brytare aktiverad" + } + } + } + } +} From 2cff817eaeaed095f31d8dcf58df1f810df6e0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Nenz=C3=A9n?= Date: Sun, 14 Jan 2024 20:40:05 +0100 Subject: [PATCH 2/5] Add support for CLEAN Ultrafine particle filter (#70) --- custom_components/wellbeing/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/wellbeing/api.py b/custom_components/wellbeing/api.py index 7947641..3915278 100644 --- a/custom_components/wellbeing/api.py +++ b/custom_components/wellbeing/api.py @@ -30,6 +30,7 @@ FILTER_TYPE = { 48: "BREEZE Complete air filter", + 49: "CLEAN Ultrafine particle filter", 51: "CARE Ultimate protect filter", 64: "Breeze 360 filter", 96: "Breeze 360 filter", From 48c704b2353f258a9e1a6976ca681601ac77a76d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Nenz=C3=A9n?= Date: Sun, 14 Jan 2024 22:56:11 +0100 Subject: [PATCH 3/5] Fallback to unknown filter type if not defined for A7 (#71) --- custom_components/wellbeing/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/wellbeing/api.py b/custom_components/wellbeing/api.py index 3915278..c2e71c5 100644 --- a/custom_components/wellbeing/api.py +++ b/custom_components/wellbeing/api.py @@ -124,12 +124,12 @@ def _create_entities(data): device_class=SensorDeviceClass.CO2 ), ApplianceSensor( - name=f"{FILTER_TYPE[data.get('FilterType_1', 0)]} Life", + name=f"{FILTER_TYPE.get(data.get('FilterType_1', 0), 'Unknown filter')} Life", attr='FilterLife_1', unit=PERCENTAGE ), ApplianceSensor( - name=f"{FILTER_TYPE[data.get('FilterType_2', 0)]} Life", + name=f"{FILTER_TYPE.get(data.get('FilterType_2', 0), 'Unknown filter')} Life", attr='FilterLife_2', unit=PERCENTAGE ), @@ -145,7 +145,7 @@ def _create_entities(data): a9_entities = [ ApplianceSensor( - name=f"{FILTER_TYPE.get(data.get('FilterType', 0), 'Filter')} Life", + name=f"{FILTER_TYPE.get(data.get('FilterType', 0), 'Unknown filter')} Life", attr='FilterLife', unit=PERCENTAGE ), From b2f773b1822adec399d2902ab455252b0895e8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johan=20Nenz=C3=A9n?= Date: Sat, 27 Jan 2024 19:18:37 +0100 Subject: [PATCH 4/5] Adds another ID for the Breeze 360 filter (#74) --- custom_components/wellbeing/api.py | 1 + 1 file changed, 1 insertion(+) diff --git a/custom_components/wellbeing/api.py b/custom_components/wellbeing/api.py index c2e71c5..e4a4f77 100644 --- a/custom_components/wellbeing/api.py +++ b/custom_components/wellbeing/api.py @@ -33,6 +33,7 @@ 49: "CLEAN Ultrafine particle filter", 51: "CARE Ultimate protect filter", 64: "Breeze 360 filter", + 67: "Breeze 360 filter", 96: "Breeze 360 filter", 99: "Breeze 360 filter", 192: "FRESH Odour protect filter", From 09c995fff44bb3cf54aea52a2a12e4fcaf521ac6 Mon Sep 17 00:00:00 2001 From: hermanops <55062051+hermanops@users.noreply.github.com> Date: Sun, 28 Jan 2024 08:47:29 +0100 Subject: [PATCH 5/5] Fix purea9 (#72) * Add FRESH360 filter debug logs show I have: "FilterType": 100 which according to the APP is FRESH360 * fix name for eCO2 I think this should be adjusted, as the A9 doesn't return CO2. It does show ECO2 in the json. * moved sensor to common array after review from JohNan * eCO2 is a common entity now --- custom_components/wellbeing/api.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/custom_components/wellbeing/api.py b/custom_components/wellbeing/api.py index e4a4f77..2bf997c 100644 --- a/custom_components/wellbeing/api.py +++ b/custom_components/wellbeing/api.py @@ -36,6 +36,7 @@ 67: "Breeze 360 filter", 96: "Breeze 360 filter", 99: "Breeze 360 filter", + 100: "Fresh 360 filter", 192: "FRESH Odour protect filter", 0: "Filter" } @@ -118,12 +119,6 @@ def __init__(self, name, pnc_id, model) -> None: @staticmethod def _create_entities(data): a7_entities = [ - ApplianceSensor( - name="eCO2", - attr='ECO2', - unit=CONCENTRATION_PARTS_PER_MILLION, - device_class=SensorDeviceClass.CO2 - ), ApplianceSensor( name=f"{FILTER_TYPE.get(data.get('FilterType_1', 0), 'Unknown filter')} Life", attr='FilterLife_1', @@ -149,13 +144,7 @@ def _create_entities(data): name=f"{FILTER_TYPE.get(data.get('FilterType', 0), 'Unknown filter')} Life", attr='FilterLife', unit=PERCENTAGE - ), - ApplianceSensor( - name="CO2", - attr='CO2', - unit=CONCENTRATION_PARTS_PER_MILLION, - device_class=SensorDeviceClass.CO2 - ), + ) ] common_entities = [ @@ -174,6 +163,12 @@ def _create_entities(data): attr='TVOC', unit=CONCENTRATION_PARTS_PER_BILLION ), + ApplianceSensor( + name="eCO2", + attr='ECO2', + unit=CONCENTRATION_PARTS_PER_MILLION, + device_class=SensorDeviceClass.CO2 + ), ApplianceSensor( name="PM1", attr='PM1',