Add support for air purifiers to HomeKit Device (#109880)

This commit is contained in:
Jc2k 2024-02-07 15:19:42 +00:00 committed by GitHub
parent aea81a180c
commit 1ea9b1a158
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 192 additions and 5 deletions

View File

@ -55,6 +55,7 @@ HOMEKIT_ACCESSORY_DISPATCH = {
ServicesTypes.DOORBELL: "event", ServicesTypes.DOORBELL: "event",
ServicesTypes.STATELESS_PROGRAMMABLE_SWITCH: "event", ServicesTypes.STATELESS_PROGRAMMABLE_SWITCH: "event",
ServicesTypes.SERVICE_LABEL: "event", ServicesTypes.SERVICE_LABEL: "event",
ServicesTypes.AIR_PURIFIER: "fan",
} }
CHARACTERISTIC_PLATFORMS = { CHARACTERISTIC_PLATFORMS = {
@ -104,6 +105,8 @@ CHARACTERISTIC_PLATFORMS = {
CharacteristicsTypes.FILTER_LIFE_LEVEL: "sensor", CharacteristicsTypes.FILTER_LIFE_LEVEL: "sensor",
CharacteristicsTypes.VENDOR_AIRVERSA_SLEEP_MODE: "switch", CharacteristicsTypes.VENDOR_AIRVERSA_SLEEP_MODE: "switch",
CharacteristicsTypes.TEMPERATURE_UNITS: "select", CharacteristicsTypes.TEMPERATURE_UNITS: "select",
CharacteristicsTypes.AIR_PURIFIER_STATE_CURRENT: "sensor",
CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET: "select",
} }
STARTUP_EXCEPTIONS = ( STARTUP_EXCEPTIONS = (

View File

@ -206,6 +206,7 @@ class HomeKitFanV2(BaseHomeKitFan):
ENTITY_TYPES = { ENTITY_TYPES = {
ServicesTypes.FAN: HomeKitFanV1, ServicesTypes.FAN: HomeKitFanV1,
ServicesTypes.FAN_V2: HomeKitFanV2, ServicesTypes.FAN_V2: HomeKitFanV2,
ServicesTypes.AIR_PURIFIER: HomeKitFanV2,
} }

View File

@ -5,7 +5,10 @@ from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.characteristics.const import TemperatureDisplayUnits from aiohomekit.model.characteristics.const import (
TargetAirPurifierStateValues,
TemperatureDisplayUnits,
)
from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -47,6 +50,16 @@ SELECT_ENTITIES: dict[str, HomeKitSelectEntityDescription] = {
"fahrenheit": TemperatureDisplayUnits.FAHRENHEIT, "fahrenheit": TemperatureDisplayUnits.FAHRENHEIT,
}, },
), ),
CharacteristicsTypes.AIR_PURIFIER_STATE_TARGET: HomeKitSelectEntityDescription(
key="air_purifier_state_target",
translation_key="air_purifier_state_target",
name="Air Purifier Mode",
entity_category=EntityCategory.CONFIG,
choices={
"automatic": TargetAirPurifierStateValues.AUTOMATIC,
"manual": TargetAirPurifierStateValues.MANUAL,
},
),
} }
_ECOBEE_MODE_TO_TEXT = { _ECOBEE_MODE_TO_TEXT = {

View File

@ -3,10 +3,15 @@ from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from enum import IntEnum
from aiohomekit.model import Accessory, Transport from aiohomekit.model import Accessory, Transport
from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes from aiohomekit.model.characteristics import Characteristic, CharacteristicsTypes
from aiohomekit.model.characteristics.const import ThreadNodeCapabilities, ThreadStatus from aiohomekit.model.characteristics.const import (
CurrentAirPurifierStateValues,
ThreadNodeCapabilities,
ThreadStatus,
)
from aiohomekit.model.services import Service, ServicesTypes from aiohomekit.model.services import Service, ServicesTypes
from homeassistant.components.bluetooth import ( from homeassistant.components.bluetooth import (
@ -52,6 +57,7 @@ class HomeKitSensorEntityDescription(SensorEntityDescription):
probe: Callable[[Characteristic], bool] | None = None probe: Callable[[Characteristic], bool] | None = None
format: Callable[[Characteristic], str] | None = None format: Callable[[Characteristic], str] | None = None
enum: dict[IntEnum, str] | None = None
def thread_node_capability_to_str(char: Characteristic) -> str: def thread_node_capability_to_str(char: Characteristic) -> str:
@ -324,6 +330,18 @@ SIMPLE_SENSOR: dict[str, HomeKitSensorEntityDescription] = {
], ],
translation_key="thread_status", translation_key="thread_status",
), ),
CharacteristicsTypes.AIR_PURIFIER_STATE_CURRENT: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.AIR_PURIFIER_STATE_CURRENT,
name="Air Purifier Status",
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.ENUM,
enum={
CurrentAirPurifierStateValues.INACTIVE: "inactive",
CurrentAirPurifierStateValues.IDLE: "idle",
CurrentAirPurifierStateValues.ACTIVE: "purifying",
},
translation_key="air_purifier_state_current",
),
CharacteristicsTypes.VENDOR_NETATMO_NOISE: HomeKitSensorEntityDescription( CharacteristicsTypes.VENDOR_NETATMO_NOISE: HomeKitSensorEntityDescription(
key=CharacteristicsTypes.VENDOR_NETATMO_NOISE, key=CharacteristicsTypes.VENDOR_NETATMO_NOISE,
name="Noise", name="Noise",
@ -535,6 +553,8 @@ class SimpleSensor(CharacteristicEntity, SensorEntity):
) -> None: ) -> None:
"""Initialise a secondary HomeKit characteristic sensor.""" """Initialise a secondary HomeKit characteristic sensor."""
self.entity_description = description self.entity_description = description
if self.entity_description.enum:
self._attr_options = list(self.entity_description.enum.values())
super().__init__(conn, info, char) super().__init__(conn, info, char)
def get_characteristic_types(self) -> list[str]: def get_characteristic_types(self) -> list[str]:
@ -551,10 +571,11 @@ class SimpleSensor(CharacteristicEntity, SensorEntity):
@property @property
def native_value(self) -> str | int | float: def native_value(self) -> str | int | float:
"""Return the current sensor value.""" """Return the current sensor value."""
val = self._char.value if self.entity_description.enum:
return self.entity_description.enum[self._char.value]
if self.entity_description.format: if self.entity_description.format:
return self.entity_description.format(val) return self.entity_description.format(self._char)
return val return self._char.value
ENTITY_TYPES = { ENTITY_TYPES = {

View File

@ -108,6 +108,12 @@
"celsius": "Celsius", "celsius": "Celsius",
"fahrenheit": "Fahrenheit" "fahrenheit": "Fahrenheit"
} }
},
"air_purifier_state_target": {
"state": {
"automatic": "Automatic",
"manual": "Manual"
}
} }
}, },
"sensor": { "sensor": {
@ -131,6 +137,13 @@
"leader": "Leader", "leader": "Leader",
"router": "Router" "router": "Router"
} }
},
"air_purifier_state_current": {
"state": {
"inactive": "Inactive",
"idle": "Idle",
"purifying": "Purifying"
}
} }
} }
} }

View File

@ -101,6 +101,142 @@
'state': 'unknown', 'state': 'unknown',
}), }),
}), }),
dict({
'entry': dict({
'aliases': list([
]),
'area_id': None,
'capabilities': dict({
'preset_modes': None,
}),
'config_entry_id': 'TestData',
'device_class': None,
'disabled_by': None,
'domain': 'fan',
'entity_category': None,
'entity_id': 'fan.airversa_ap2_1808_airpurifier',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Airversa AP2 1808 AirPurifier',
'platform': 'homekit_controller',
'previous_unique_id': None,
'supported_features': <FanEntityFeature: 1>,
'translation_key': None,
'unique_id': '00:00:00:00:00:00_1_32832',
'unit_of_measurement': None,
}),
'state': dict({
'attributes': dict({
'friendly_name': 'Airversa AP2 1808 AirPurifier',
'percentage': 0,
'percentage_step': 20.0,
'preset_mode': None,
'preset_modes': None,
'supported_features': <FanEntityFeature: 1>,
}),
'entity_id': 'fan.airversa_ap2_1808_airpurifier',
'state': 'off',
}),
}),
dict({
'entry': dict({
'aliases': list([
]),
'area_id': None,
'capabilities': dict({
'options': list([
'automatic',
'manual',
]),
}),
'config_entry_id': 'TestData',
'device_class': None,
'disabled_by': None,
'domain': 'select',
'entity_category': <EntityCategory.CONFIG: 'config'>,
'entity_id': 'select.airversa_ap2_1808_air_purifier_mode',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Airversa AP2 1808 Air Purifier Mode',
'platform': 'homekit_controller',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'air_purifier_state_target',
'unique_id': '00:00:00:00:00:00_1_32832_32837',
'unit_of_measurement': None,
}),
'state': dict({
'attributes': dict({
'friendly_name': 'Airversa AP2 1808 Air Purifier Mode',
'options': list([
'automatic',
'manual',
]),
}),
'entity_id': 'select.airversa_ap2_1808_air_purifier_mode',
'state': 'automatic',
}),
}),
dict({
'entry': dict({
'aliases': list([
]),
'area_id': None,
'capabilities': dict({
'options': list([
'inactive',
'idle',
'purifying',
]),
}),
'config_entry_id': 'TestData',
'device_class': None,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.airversa_ap2_1808_air_purifier_status',
'has_entity_name': False,
'hidden_by': None,
'icon': None,
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Airversa AP2 1808 Air Purifier Status',
'platform': 'homekit_controller',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': 'air_purifier_state_current',
'unique_id': '00:00:00:00:00:00_1_32832_32836',
'unit_of_measurement': None,
}),
'state': dict({
'attributes': dict({
'device_class': 'enum',
'friendly_name': 'Airversa AP2 1808 Air Purifier Status',
'options': list([
'inactive',
'idle',
'purifying',
]),
}),
'entity_id': 'sensor.airversa_ap2_1808_air_purifier_status',
'state': 'inactive',
}),
}),
dict({ dict({
'entry': dict({ 'entry': dict({
'aliases': list([ 'aliases': list([