From dd5db95bf20c33ffaa028e1591b8f8bd7e834dfc Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:33:28 -0400 Subject: [PATCH 1/9] add purifier constants and fan platform --- custom_components/petkit/const.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/custom_components/petkit/const.py b/custom_components/petkit/const.py index d49bd07..6716642 100644 --- a/custom_components/petkit/const.py +++ b/custom_components/petkit/const.py @@ -5,7 +5,7 @@ from aiohttp.client_exceptions import ClientConnectionError from petkitaio.exceptions import AuthError, PetKitError -from petkitaio.constants import W5Command +from petkitaio.constants import W5Command, PurifierCommand from homeassistant.const import Platform @@ -17,6 +17,7 @@ PLATFORMS = [ Platform.BINARY_SENSOR, Platform.BUTTON, + Platform.FAN, Platform.NUMBER, Platform.SELECT, Platform.SENSOR, @@ -68,6 +69,26 @@ 5: 'Eversweet Solo 2' } +PURIFIERS = { + 'k2': 'Air Magicube' +} + +PURIFIER_MODE_TO_COMMAND = { + 'auto': PurifierCommand.AUTO_MODE, + 'silent': PurifierCommand.SILENT_MODE, + 'standard': PurifierCommand.STANDARD_MODE, + 'strong': PurifierCommand.STRONG_MODE +} + +PURIFIER_MODES = ['auto', 'silent', 'standard', 'strong'] + +PURIFIER_MODE_NAMED = { + 0: 'auto', + 1: 'silent', + 2: 'standard', + 3: 'strong' +} + FEEDERS = { 'd3': 'Fresh Element Infinity', From 42243089f045a76b010db792e779adbf2ee90e9a Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:34:06 -0400 Subject: [PATCH 2/9] create purifier fan entity --- custom_components/petkit/fan.py | 154 ++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 custom_components/petkit/fan.py diff --git a/custom_components/petkit/fan.py b/custom_components/petkit/fan.py new file mode 100644 index 0000000..98eeaa3 --- /dev/null +++ b/custom_components/petkit/fan.py @@ -0,0 +1,154 @@ +"""Fan platform for PetKit integration.""" +from __future__ import annotations + +from typing import Any +import asyncio + +from petkitaio.constants import PurifierCommand +from petkitaio.model import Purifier + +from homeassistant.components.fan import FanEntity, FanEntityFeature +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from .const import ( + DOMAIN, + PURIFIERS, + PURIFIER_MODES, + PURIFIER_MODE_NAMED, + PURIFIER_MODE_TO_COMMAND +) + +from .coordinator import PetKitDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set Up PetKit Fan Entities.""" + + coordinator: PetKitDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id] + + fans = [] + + for purifier_id, purifier_data in coordinator.data.purifiers.items(): + fans.append( + Purifier(coordinator, purifier_id) + ) + + async_add_entities(fans) + + +class Purifier(CoordinatorEntity, FanEntity): + """Representation of a PetKit air purifier.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_purifier' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "purifier" + + @property + def available(self) -> bool: + """Only make available if device is online.""" + + if self.purifier_data.device_detail['state']['pim'] != 0: + return True + else: + return False + + @property + def is_on(self) -> bool: + """Determine if the purifier is On.""" + + if self.purifier_data.device_detail['state']['power'] in [1,2]: + return True + else: + return False + + @property + def preset_modes(self) -> list: + """Return the available preset modes.""" + + return PURIFIER_MODES + + @property + def preset_mode(self) -> str: + """Return the current preset mode.""" + + mode = self.purifier_data.device_detail['state']['mode'] + return PURIFIER_MODE_NAMED[mode] + + @property + def supported_features(self) -> int: + """Return supported features.""" + + return FanEntityFeature.PRESET_MODE + + async def async_turn_on(self, **kwargs) -> None: + """Turn the air purifier on.""" + + await self.coordinator.client.control_purifier(self.purifier_data, PurifierCommand.POWER) + self.purifier_data.device_detail['state']['power'] = 1 + self.async_write_ha_state() + # Have to wait before refreshing or PetKit will return wrong power state + await asyncio.sleep(1) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs) -> None: + """Turn the air purifier off.""" + + await self.coordinator.client.control_purifier(self.purifier_data, PurifierCommand.POWER) + self.purifier_data.device_detail['state']['power'] = 0 + self.async_write_ha_state() + # Have to wait before refreshing or PetKit will return wrong power state + await asyncio.sleep(1) + await self.coordinator.async_request_refresh() + + async def async_set_preset_mode(self, preset_mode: str) -> None: + """Set a preset mode for the purifier.""" + + command = PURIFIER_MODE_TO_COMMAND[preset_mode] + await self.coordinator.client.control_purifier(self.purifier_data, command) + MODE_TO_VALUE = {v: k for (k, v) in PURIFIER_MODE_NAMED.items()} + value = MODE_TO_VALUE.get(preset_mode) + self.purifier_data.device_detail['state']['mode'] = value + # Have to wait before refreshing or PetKit will return wrong mode state + await asyncio.sleep(1) + await self.coordinator.async_request_refresh() From 92a23bc23ba89002599fa507f7f241d64a5b1d77 Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:34:52 -0400 Subject: [PATCH 3/9] bump petkitaio and integration to 0.1.4 --- custom_components/petkit/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/petkit/manifest.json b/custom_components/petkit/manifest.json index 2717f5a..a4e1dff 100644 --- a/custom_components/petkit/manifest.json +++ b/custom_components/petkit/manifest.json @@ -8,6 +8,6 @@ "integration_type": "hub", "iot_class": "cloud_polling", "issue_tracker": "https://github.com/RobertD502/home-assistant-petkit/issues", - "requirements": ["petkitaio==0.1.3", "tzlocal>=4.2"], - "version": "0.1.3" + "requirements": ["petkitaio==0.1.4", "tzlocal>=4.2"], + "version": "0.1.4" } From 551af895090ec45c7c8fbaa20a79fb6fbcf25d28 Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:35:35 -0400 Subject: [PATCH 4/9] add air magicube sensors --- custom_components/petkit/sensor.py | 438 ++++++++++++++++++++++++++++- 1 file changed, 437 insertions(+), 1 deletion(-) diff --git a/custom_components/petkit/sensor.py b/custom_components/petkit/sensor.py index a2ebd90..a698c04 100644 --- a/custom_components/petkit/sensor.py +++ b/custom_components/petkit/sensor.py @@ -5,7 +5,7 @@ from math import floor as floor from typing import Any -from petkitaio.model import Feeder, LitterBox, Pet, W5Fountain +from petkitaio.model import Feeder, LitterBox, Pet, Purifier, W5Fountain from homeassistant.components.sensor import ( SensorDeviceClass, @@ -18,7 +18,9 @@ SIGNAL_STRENGTH_DECIBELS_MILLIWATT, UnitOfEnergy, UnitOfMass, + UnitOfTemperature, UnitOfTime, + UnitOfVolume ) from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import EntityCategory @@ -30,6 +32,7 @@ DOMAIN, FEEDERS, LITTER_BOXES, + PURIFIERS, WATER_FOUNTAINS ) from .coordinator import PetKitDataUpdateCoordinator @@ -152,6 +155,16 @@ async def async_setup_entry( PetLastUseDuration(coordinator, pet_id), )) + #Purifiers + for purifier_id, purifier_data in coordinator.data.purifiers.items(): + sensors.extend(( + PurifierError(coordinator, purifier_id), + PurifierHumidity(coordinator, purifier_id), + PurifierTemperature(coordinator, purifier_id), + AirPurified(coordinator, purifier_id), + PurifierRSSI(coordinator, purifier_id), + PurifierLiquid(coordinator, purifier_id) + )) async_add_entities(sensors) @@ -3593,3 +3606,426 @@ def icon(self) -> str: """Set icon.""" return 'mdi:food-drumstick' + + +class PurifierError(CoordinatorEntity, SensorEntity): + """Representation of Purifier error.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_purifier_error' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "error" + + @property + def native_value(self) -> str: + """Return current error if there is one.""" + + if 'errorMsg' in self.purifier_data.device_detail['state']: + return self.purifier_data.device_detail['state']['errorMsg'] + else: + return 'no_error' + + @property + def entity_category(self) -> EntityCategory: + """Set category to diagnostic.""" + + return EntityCategory.DIAGNOSTIC + + @property + def icon(self) -> str: + """Set icon.""" + + return 'mdi:alert-circle' + + +class PurifierHumidity(CoordinatorEntity, SensorEntity): + """ Representation of Purifier Humidity """ + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_purifier_humidity' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "humidity" + + @property + def native_value(self) -> int: + """ Return current humidity """ + + return round((self.purifier_data.device_detail['state']['humidity'] / 10)) + + @property + def native_unit_of_measurement(self) -> str: + """ Return percent as the native unit """ + + return PERCENTAGE + + @property + def device_class(self) -> SensorDeviceClass: + """ Return entity device class """ + + return SensorDeviceClass.HUMIDITY + + @property + def state_class(self) -> SensorStateClass: + """ Return the type of state class """ + + return SensorStateClass.MEASUREMENT + + +class PurifierTemperature(CoordinatorEntity, SensorEntity): + """ Representation of Purifier Temperature """ + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_purifier_temperature' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "temperature" + + @property + def native_value(self) -> int: + """ Return current temperature in Celsius """ + + return round((self.purifier_data.device_detail['state']['temp'] / 10)) + + @property + def native_unit_of_measurement(self) -> UnitOfTemperature: + """ Return Celsius as the native unit """ + + return UnitOfTemperature.CELSIUS + + @property + def device_class(self) -> SensorDeviceClass: + """ Return entity device class """ + + return SensorDeviceClass.TEMPERATURE + + @property + def state_class(self) -> SensorStateClass: + """ Return the type of state class """ + + return SensorStateClass.MEASUREMENT + + +class AirPurified(CoordinatorEntity, SensorEntity): + """ Representation of amount of air purified.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_air_purified' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "air_purified" + + @property + def native_value(self) -> int: + """Return amount of air purified in cubic meters.""" + + return round(self.purifier_data.device_detail['state']['refresh']) + + @property + def native_unit_of_measurement(self) -> UnitOfVolume: + """ Return cubic meters as the native unit """ + + return UnitOfVolume.CUBIC_METERS + + @property + def device_class(self) -> SensorDeviceClass: + """ Return entity device class """ + + return SensorDeviceClass.VOLUME + + @property + def state_class(self) -> SensorStateClass: + """ Return the type of state class """ + + return SensorStateClass.MEASUREMENT + + +class PurifierRSSI(CoordinatorEntity, SensorEntity): + """Representation of purifier WiFi connection strength.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_rssi' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "rssi" + + @property + def native_value(self) -> int: + """Return RSSI measurement.""" + + return self.purifier_data.device_detail['state']['wifi']['rsq'] + + @property + def state_class(self) -> SensorStateClass: + """Return the type of state class.""" + + return SensorStateClass.MEASUREMENT + + @property + def entity_category(self) -> EntityCategory: + """Set category to diagnostic.""" + + return EntityCategory.DIAGNOSTIC + + @property + def native_unit_of_measurement(self) -> str: + """Return dBm as the native unit.""" + + return SIGNAL_STRENGTH_DECIBELS_MILLIWATT + + @property + def device_class(self) -> SensorDeviceClass: + """Return entity device class.""" + + return SensorDeviceClass.SIGNAL_STRENGTH + + @property + def icon(self) -> str: + """Set icon.""" + + return 'mdi:wifi' + + +class PurifierLiquid(CoordinatorEntity, SensorEntity): + """Representation of purifier liquid left.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_liquid' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "liquid" + + @property + def icon(self) -> str: + """Set icon.""" + + return 'mdi:cup-water' + + @property + def native_value(self) -> int: + """Return current percentage left""" + + return self.purifier_data.device_detail['state']['liquid'] + + @property + def entity_category(self) -> EntityCategory: + """Set category to diagnostic.""" + + return EntityCategory.DIAGNOSTIC + + @property + def state_class(self) -> SensorStateClass: + """Return the type of state class.""" + + return SensorStateClass.MEASUREMENT + + @property + def native_unit_of_measurement(self) -> str: + """Return percent as the native unit.""" + + return PERCENTAGE From d420da99bc5eb5e1803ef29754ae332f0112b754 Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:36:11 -0400 Subject: [PATCH 5/9] add magicube keys --- custom_components/petkit/strings.json | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/custom_components/petkit/strings.json b/custom_components/petkit/strings.json index e657490..354999f 100644 --- a/custom_components/petkit/strings.json +++ b/custom_components/petkit/strings.json @@ -122,6 +122,21 @@ "name": "Food replenished" } }, + "fan": { + "purifier": { + "name": "Purifier", + "state_attributes": { + "preset_mode": { + "state": { + "auto": "Auto", + "silent": "Silent", + "standard": "Standard", + "strong": "Strong" + } + } + } + } + }, "number": { "set_weight": { "name": "Set weight" @@ -611,6 +626,18 @@ }, "dispensed_hopp_two": { "name": "Dispensed hopper 2" + }, + "humidity": { + "name": "Humidity" + }, + "temperature": { + "name": "Temperature" + }, + "air_purified": { + "name": "Air purified" + }, + "liquid": { + "name": "Liquid" } }, "switch": { @@ -676,6 +703,9 @@ }, "deep_deodor": { "name": "Deep deodorization" + }, + "prompt_tone": { + "name": "Prompt tone" } }, "text": { From a0db2d4d249c644b12864362938c6fe301149c37 Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:36:45 -0400 Subject: [PATCH 6/9] add magicube switch entities --- custom_components/petkit/switch.py | 185 ++++++++++++++++++++++++++++- 1 file changed, 182 insertions(+), 3 deletions(-) diff --git a/custom_components/petkit/switch.py b/custom_components/petkit/switch.py index 12bb8d7..7b6d8f7 100644 --- a/custom_components/petkit/switch.py +++ b/custom_components/petkit/switch.py @@ -4,9 +4,9 @@ from typing import Any import asyncio -from petkitaio.constants import FeederSetting, LitterBoxCommand, LitterBoxSetting, W5Command +from petkitaio.constants import FeederSetting, LitterBoxCommand, LitterBoxSetting, PurifierSetting, W5Command from petkitaio.exceptions import BluetoothError -from petkitaio.model import Feeder, LitterBox, W5Fountain +from petkitaio.model import Feeder, LitterBox, Purifier, W5Fountain from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry @@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import DOMAIN, FEEDERS, LITTER_BOXES, WATER_FOUNTAINS +from .const import DOMAIN, FEEDERS, LITTER_BOXES, PURIFIERS, WATER_FOUNTAINS from .coordinator import PetKitDataUpdateCoordinator from .exceptions import PetKitBluetoothError @@ -93,6 +93,13 @@ async def async_setup_entry( LBDeepDeodor(coordinator, lb_id) ) + # Purifiers + for purifier_id, purifier_data in coordinator.data.purifiers.items(): + switches.extend(( + PurifierLight(coordinator, purifier_id), + PurifierTone(coordinator, purifier_id) + )) + async_add_entities(switches) @@ -2452,3 +2459,175 @@ async def async_turn_off(self, **kwargs) -> None: self.lb_data.device_detail['settings']['deepRefresh'] = 0 self.async_write_ha_state() await self.coordinator.async_request_refresh() + + +class PurifierLight(CoordinatorEntity, SwitchEntity): + """Representation of Purifier indicator light switch.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_indicator_light' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "indicator_light" + + @property + def icon(self) -> str: + """Set icon.""" + + if self.is_on: + return 'mdi:lightbulb' + else: + return 'mdi:lightbulb-off' + + @property + def is_on(self) -> bool: + """Determine if indicator light is on.""" + + return self.purifier_data.device_detail['settings']['lightMode'] == 1 + + @property + def available(self) -> bool: + """Only make available if device is online.""" + + if self.purifier_data.device_detail['state']['pim'] != 0: + return True + else: + return False + + async def async_turn_on(self, **kwargs) -> None: + """Turn indicator light on.""" + + await self.coordinator.client.update_purifier_settings(self.purifier_data, PurifierSetting.LIGHT, 1) + + self.purifier_data.device_detail['settings']['lightMode'] = 1 + self.async_write_ha_state() + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs) -> None: + """Turn indicator light off.""" + + await self.coordinator.client.update_purifier_settings(self.purifier_data, PurifierSetting.LIGHT, 0) + + self.purifier_data.device_detail['settings']['lightMode'] = 1 + self.async_write_ha_state() + await self.coordinator.async_request_refresh() + + +class PurifierTone(CoordinatorEntity, SwitchEntity): + """Representation of purifier tone switch.""" + + def __init__(self, coordinator, purifier_id): + super().__init__(coordinator) + self.purifier_id = purifier_id + + @property + def purifier_data(self) -> Purifier: + """Handle coordinator Purifier data.""" + + return self.coordinator.data.purifiers[self.purifier_id] + + @property + def device_info(self) -> dict[str, Any]: + """Return device registry information for this entity.""" + + return { + "identifiers": {(DOMAIN, self.purifier_data.id)}, + "name": self.purifier_data.device_detail['name'], + "manufacturer": "PetKit", + "model": PURIFIERS[self.purifier_data.type], + "sw_version": f'{self.purifier_data.device_detail["firmware"]}' + } + + @property + def unique_id(self) -> str: + """Sets unique ID for this entity.""" + + return str(self.purifier_data.id) + '_tone' + + @property + def has_entity_name(self) -> bool: + """Indicate that entity has name defined.""" + + return True + + @property + def translation_key(self) -> str: + """Translation key for this entity.""" + + return "prompt_tone" + + @property + def icon(self) -> str: + """Set icon.""" + + if self.is_on: + return 'mdi:ear-hearing' + else: + return 'mdi:ear-hearing-off' + + @property + def is_on(self) -> bool: + """Determine if prompt tone is on.""" + + return self.purifier_data.device_detail['settings']['sound'] == 1 + + @property + def available(self) -> bool: + """Only make available if device is online.""" + + if self.purifier_data.device_detail['state']['pim'] != 0: + return True + else: + return False + + async def async_turn_on(self, **kwargs) -> None: + """Turn prompt tone on.""" + + await self.coordinator.client.update_purifier_settings(self.purifier_data, PurifierSetting.SOUND, 1) + + self.purifier_data.device_detail['settings']['sound'] = 1 + self.async_write_ha_state() + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs) -> None: + """Turn prompt tone off.""" + + await self.coordinator.client.update_purifier_settings(self.purifier_data, PurifierSetting.SOUND, 0) + + self.purifier_data.device_detail['settings']['sound'] = 1 + self.async_write_ha_state() + await self.coordinator.async_request_refresh() From 3ee5d267b8068412990688176f78b73c55dc212a Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:37:16 -0400 Subject: [PATCH 7/9] add magicube keys --- custom_components/petkit/translations/en.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/custom_components/petkit/translations/en.json b/custom_components/petkit/translations/en.json index de52fe1..8ea849c 100644 --- a/custom_components/petkit/translations/en.json +++ b/custom_components/petkit/translations/en.json @@ -122,6 +122,21 @@ "name": "Food replenished" } }, + "fan": { + "purifier": { + "name": "Purifier", + "state_attributes": { + "preset_mode": { + "state": { + "auto": "Auto", + "silent": "Silent", + "standard": "Standard", + "strong": "Strong" + } + } + } + } + }, "number": { "set_weight": { "name": "Set weight" @@ -611,6 +626,18 @@ }, "dispensed_hopp_two": { "name": "Dispensed hopper 2" + }, + "humidity": { + "name": "Humidity" + }, + "temperature": { + "name": "Temperature" + }, + "air_purified": { + "name": "Air purified" + }, + "liquid": { + "name": "Liquid" } }, "switch": { @@ -676,6 +703,9 @@ }, "deep_deodor": { "name": "Deep deodorization" + }, + "prompt_tone": { + "name": "Prompt tone" } }, "text": { From e38c01698026ca8ea6b5add4561d9d15cc7f282a Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:37:46 -0400 Subject: [PATCH 8/9] add magicube keys --- custom_components/petkit/translations/hr.json | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/custom_components/petkit/translations/hr.json b/custom_components/petkit/translations/hr.json index 9229163..46801e8 100644 --- a/custom_components/petkit/translations/hr.json +++ b/custom_components/petkit/translations/hr.json @@ -122,6 +122,21 @@ "name": "Hrana dopunjena" } }, + "fan": { + "purifier": { + "name": "Pročistač", + "state_attributes": { + "preset_mode": { + "state": { + "auto": "Auto", + "silent": "Tiho", + "standard": "Standard", + "strong": "Jak" + } + } + } + } + }, "number": { "set_weight": { "name": "Postavite težinu" @@ -611,6 +626,18 @@ }, "dispensed_hopp_two": { "name": "Spremnik za doziranje 2" + }, + "humidity": { + "name": "Vlažnost" + }, + "temperature": { + "name": "Temperatura" + }, + "air_purified": { + "name": "Pročišćeni zrak" + }, + "liquid": { + "name": "Tekućina" } }, "switch": { @@ -676,6 +703,9 @@ }, "deep_deodor": { "name": "Temeljita dezodoracija" + }, + "prompt_tone": { + "name": "Ton" } }, "text": { From 36a986d6badebc6f6f327b033f722fcb4972b2da Mon Sep 17 00:00:00 2001 From: "Dr. Drinovac" <52541649+RobertD502@users.noreply.github.com> Date: Wed, 21 Jun 2023 18:08:08 -0400 Subject: [PATCH 9/9] add air magicube documentation --- README.md | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2a44c2f..8706901 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,24 @@ Custom Home Assistant component for controlling and monitoring PetKit devices an ### Currently Supported Devices -`Feeders`: +`Feeders` - [Fresh Element Infinity](https://www.amazon.com/PETKIT-Automatic-Stainless-Programmable-Dispenser/dp/B09JFK8BCQ) - [Fresh Element Solo](https://www.amazon.com/PETKIT-Automatic-Dispenser-Compatible-Freeze-Dried/dp/B09158J9PF/) - [Fresh Element Mini Pro](https://www.amazon.com/PETKIT-Automatic-Stainless-Indicator-Dispenser-2-8L/dp/B08GS1CPHH/) - [Fresh Element Gemini](https://www.amazon.com/PETKIT-Automatic-Combination-Dispenser-Stainless/dp/B0BF56RTQH) -`Water Fountains`: +`Litter Boxes` +- [Pura X Litter Box](https://www.amazon.com/PETKIT-Self-Cleaning-Scooping-Automatic-Multiple/dp/B08T9CCP1M) +- [Pura MAX Litter Box with/without Pura Air deodorizer](https://www.amazon.com/PETKIT-Self-Cleaning-Capacity-Multiple-Automatic/dp/B09KC7Q4YF) + +`Purifiers` +- [Air Magicube](https://www.instachew.com/product-page/petkit-air-magicube-smart-odor-eliminator) + +`Water Fountains` - [Eversweet Solo 2 Water Fountain](https://www.amazon.com/PETKIT-EVERSWEET-Wireless-Visualization-Dispenser-2L/dp/B0B3RWF653) - [Eversweet 3 Pro Water Fountain](https://www.amazon.com/PETKIT-Wireless-Fountain-Stainless-Dispenser/dp/B09QRH6L3M/) - [Eversweet 5 Mini Water Fountain](https://www.petkit.nl/products/eversweet-5-mini-binnen-2-weken-geleverd) -`Litter Boxes`: -- [Pura X Litter Box](https://www.amazon.com/PETKIT-Self-Cleaning-Scooping-Automatic-Multiple/dp/B08T9CCP1M) -- [Pura MAX Litter Box with/without Pura Air deodorizer](https://www.amazon.com/PETKIT-Self-Cleaning-Capacity-Multiple-Automatic/dp/B09KC7Q4YF) #### Bluetooth only devices that don't use PetKit's BLE relay such as trackers (i.e., PetKit Fit) will not be supported: syncing new data from a bluetooth tracker requires the PetKit mobile app to communicate with the tracker which is not possible when your PetKit account is already in use with this integration. @@ -563,6 +567,30 @@ Each litter box has the following entities: +## Purifiers +___ + +
+ Air Magicube (click to expand) + +
+Each purifier has the following entities: +
+ +| Entity | Entity Type | Additional Comments | +|-------------------|-------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `Indicator light` | `Switch` | Only available if your purifier is online (connected to PetKit's servers) | +| `Prompt tone` | `Switch` | Only available if your purifier is online (connected to PetKit's servers). | +| `Purifier` | `Fan` | - Only available if purifier is online (Connected to PetKit's servers).
- Can turn the purifier `On` and `Off` and view/change the current mode to one of the following: `auto`, `silent`, `standard`, `strong`. | +| `Air purified` | `Sensor` | | +| `Humidity` | `Sensor` | | +| `Temperature` | `Sensor` | | +| `Error` | `Sensor` | Displays any error messages. `Note:` Even though it isn't really an error, An error message is displayed by PetKit if the purifier is in standby mode. | +| `Liquid` | `Sensor` | Percent of purifying liquid that remains in the purifier. | +| `RSSI` | `Sensor` | WiFi connection strength. | + +
+ ## Pets ___