From a58c79664126040643233aa119aba4ceba4dd72c Mon Sep 17 00:00:00 2001 From: Jc2k Date: Mon, 6 Jan 2020 15:35:10 +0000 Subject: [PATCH] Add support for homekit air quality sensors (#30510) * Add air quality sensor * Fix comment from review * Fix comment from review * Lint fix --- .../components/homekit_controller/__init__.py | 10 ++ .../homekit_controller/air_quality.py | 98 +++++++++++++++++++ .../components/homekit_controller/const.py | 1 + .../homekit_controller/test_air_quality.py | 47 +++++++++ 4 files changed, 156 insertions(+) create mode 100644 homeassistant/components/homekit_controller/air_quality.py create mode 100644 tests/components/homekit_controller/test_air_quality.py diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py index 444f64b6f38..dc65796a569 100644 --- a/homeassistant/components/homekit_controller/__init__.py +++ b/homeassistant/components/homekit_controller/__init__.py @@ -109,6 +109,16 @@ class HomeKitEntity(Entity): return setup_fn(char) + def get_hk_char_value(self, characteristic_type): + """Return the value for a given characteristic type enum.""" + state = self._accessory.current_state.get(self._aid) + if not state: + return None + char = self._chars.get(CharacteristicsTypes.get_short(characteristic_type)) + if not char: + return None + return state.get(char, {}).get("value") + @callback def async_state_changed(self): """Collect new data from bridge and update the entity state in hass.""" diff --git a/homeassistant/components/homekit_controller/air_quality.py b/homeassistant/components/homekit_controller/air_quality.py new file mode 100644 index 00000000000..854c12e6f88 --- /dev/null +++ b/homeassistant/components/homekit_controller/air_quality.py @@ -0,0 +1,98 @@ +"""Support for HomeKit Controller air quality sensors.""" +from homekit.model.characteristics import CharacteristicsTypes + +from homeassistant.components.air_quality import AirQualityEntity + +from . import KNOWN_DEVICES, HomeKitEntity + +AIR_QUALITY_TEXT = { + 0: "unknown", + 1: "excellent", + 2: "good", + 3: "fair", + 4: "inferior", + 5: "poor", +} + + +class HomeAirQualitySensor(HomeKitEntity, AirQualityEntity): + """Representation of a HomeKit Controller Air Quality sensor.""" + + def get_characteristic_types(self): + """Define the homekit characteristics the entity cares about.""" + return [ + CharacteristicsTypes.AIR_QUALITY, + CharacteristicsTypes.DENSITY_PM25, + CharacteristicsTypes.DENSITY_PM10, + CharacteristicsTypes.DENSITY_OZONE, + CharacteristicsTypes.DENSITY_NO2, + CharacteristicsTypes.DENSITY_SO2, + CharacteristicsTypes.DENSITY_VOC, + ] + + @property + def particulate_matter_2_5(self): + """Return the particulate matter 2.5 level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM25) + + @property + def particulate_matter_10(self): + """Return the particulate matter 10 level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_PM10) + + @property + def ozone(self): + """Return the O3 (ozone) level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_OZONE) + + @property + def sulphur_dioxide(self): + """Return the SO2 (sulphur dioxide) level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_SO2) + + @property + def nitrogen_dioxide(self): + """Return the NO2 (nitrogen dioxide) level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_NO2) + + @property + def air_quality_text(self): + """Return the Air Quality Index (AQI).""" + air_quality = self.get_hk_char_value(CharacteristicsTypes.AIR_QUALITY) + return AIR_QUALITY_TEXT.get(air_quality, "unknown") + + @property + def volatile_organic_compounds(self): + """Return the volatile organic compounds (VOC) level.""" + return self.get_hk_char_value(CharacteristicsTypes.DENSITY_VOC) + + @property + def device_state_attributes(self): + """Return the device state attributes.""" + data = {"air_quality_text": self.air_quality_text} + + voc = self.volatile_organic_compounds + if voc: + data["volatile_organic_compounds"] = voc + + return data + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Legacy set up platform.""" + pass + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Homekit air quality sensor.""" + hkid = config_entry.data["AccessoryPairingID"] + conn = hass.data[KNOWN_DEVICES][hkid] + + def async_add_service(aid, service): + if service["stype"] != "air-quality": + return False + info = {"aid": aid, "iid": service["iid"]} + async_add_entities([HomeAirQualitySensor(conn, info)], True) + return True + + conn.add_listener(async_add_service) diff --git a/homeassistant/components/homekit_controller/const.py b/homeassistant/components/homekit_controller/const.py index fcd88563763..684f83ba5d4 100644 --- a/homeassistant/components/homekit_controller/const.py +++ b/homeassistant/components/homekit_controller/const.py @@ -29,4 +29,5 @@ HOMEKIT_ACCESSORY_DISPATCH = { "smoke": "binary_sensor", "fan": "fan", "fanv2": "fan", + "air-quality": "air_quality", } diff --git a/tests/components/homekit_controller/test_air_quality.py b/tests/components/homekit_controller/test_air_quality.py new file mode 100644 index 00000000000..41f39d7d7a3 --- /dev/null +++ b/tests/components/homekit_controller/test_air_quality.py @@ -0,0 +1,47 @@ +"""Basic checks for HomeKit air quality sensor.""" +from tests.components.homekit_controller.common import FakeService, setup_test_component + + +def create_air_quality_sensor_service(): + """Define temperature characteristics.""" + service = FakeService("public.hap.service.sensor.air-quality") + + cur_state = service.add_characteristic("air-quality") + cur_state.value = 5 + + cur_state = service.add_characteristic("density.ozone") + cur_state.value = 1111 + + cur_state = service.add_characteristic("density.no2") + cur_state.value = 2222 + + cur_state = service.add_characteristic("density.so2") + cur_state.value = 3333 + + cur_state = service.add_characteristic("density.pm25") + cur_state.value = 4444 + + cur_state = service.add_characteristic("density.pm10") + cur_state.value = 5555 + + cur_state = service.add_characteristic("density.voc") + cur_state.value = 6666 + + return service + + +async def test_air_quality_sensor_read_state(hass, utcnow): + """Test reading the state of a HomeKit temperature sensor accessory.""" + sensor = create_air_quality_sensor_service() + helper = await setup_test_component(hass, [sensor]) + + state = await helper.poll_and_get_state() + assert state.state == "4444" + + assert state.attributes["air_quality_text"] == "poor" + assert state.attributes["ozone"] == 1111 + assert state.attributes["nitrogen_dioxide"] == 2222 + assert state.attributes["sulphur_dioxide"] == 3333 + assert state.attributes["particulate_matter_2_5"] == 4444 + assert state.attributes["particulate_matter_10"] == 5555 + assert state.attributes["volatile_organic_compounds"] == 6666