Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for homekit air quality sensors #30510

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions homeassistant/components/homekit_controller/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ def _setup_characteristic(self, char):
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."""
Expand Down
98 changes: 98 additions & 0 deletions homeassistant/components/homekit_controller/air_quality.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions homeassistant/components/homekit_controller/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
"smoke": "binary_sensor",
"fan": "fan",
"fanv2": "fan",
"air-quality": "air_quality",
}
47 changes: 47 additions & 0 deletions tests/components/homekit_controller/test_air_quality.py
Original file line number Diff line number Diff line change
@@ -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