Add homekit pm type sensor (#46060)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
e1b57d83c7
commit
56abd5f2cf
6 changed files with 220 additions and 7 deletions
|
@ -173,9 +173,19 @@ def get_accessory(hass, driver, state, aid, config): # noqa: C901
|
|||
a_type = "TemperatureSensor"
|
||||
elif device_class == SensorDeviceClass.HUMIDITY and unit == PERCENTAGE:
|
||||
a_type = "HumiditySensor"
|
||||
elif (
|
||||
device_class == SensorDeviceClass.PM10
|
||||
or SensorDeviceClass.PM10 in state.entity_id
|
||||
):
|
||||
a_type = "PM10Sensor"
|
||||
elif (
|
||||
device_class == SensorDeviceClass.PM25
|
||||
or SensorDeviceClass.PM25 in state.entity_id
|
||||
):
|
||||
a_type = "PM25Sensor"
|
||||
elif (
|
||||
device_class == SensorDeviceClass.GAS
|
||||
or SensorDeviceClass.GAS in state.entity_id
|
||||
):
|
||||
a_type = "AirQualitySensor"
|
||||
elif device_class == SensorDeviceClass.CO:
|
||||
|
|
|
@ -150,6 +150,8 @@ SERV_WINDOW_COVERING = "WindowCovering"
|
|||
CHAR_ACTIVE = "Active"
|
||||
CHAR_ACTIVE_IDENTIFIER = "ActiveIdentifier"
|
||||
CHAR_AIR_PARTICULATE_DENSITY = "AirParticulateDensity"
|
||||
CHAR_PM25_DENSITY = "PM2.5Density"
|
||||
CHAR_PM10_DENSITY = "PM10Density"
|
||||
CHAR_AIR_QUALITY = "AirQuality"
|
||||
CHAR_BATTERY_LEVEL = "BatteryLevel"
|
||||
CHAR_BRIGHTNESS = "Brightness"
|
||||
|
@ -235,7 +237,6 @@ PROP_MIN_VALUE = "minValue"
|
|||
PROP_MIN_STEP = "minStep"
|
||||
PROP_CELSIUS = {"minValue": -273, "maxValue": 999}
|
||||
PROP_VALID_VALUES = "ValidValues"
|
||||
|
||||
# #### Thresholds ####
|
||||
THRESHOLD_CO = 25
|
||||
THRESHOLD_CO2 = 1000
|
||||
|
|
|
@ -35,6 +35,8 @@ from .const import (
|
|||
CHAR_LEAK_DETECTED,
|
||||
CHAR_MOTION_DETECTED,
|
||||
CHAR_OCCUPANCY_DETECTED,
|
||||
CHAR_PM10_DENSITY,
|
||||
CHAR_PM25_DENSITY,
|
||||
CHAR_SMOKE_DETECTED,
|
||||
PROP_CELSIUS,
|
||||
SERV_AIR_QUALITY_SENSOR,
|
||||
|
@ -51,7 +53,13 @@ from .const import (
|
|||
THRESHOLD_CO,
|
||||
THRESHOLD_CO2,
|
||||
)
|
||||
from .util import convert_to_float, density_to_air_quality, temperature_to_homekit
|
||||
from .util import (
|
||||
convert_to_float,
|
||||
density_to_air_quality,
|
||||
density_to_air_quality_pm10,
|
||||
density_to_air_quality_pm25,
|
||||
temperature_to_homekit,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -156,6 +164,15 @@ class AirQualitySensor(HomeAccessory):
|
|||
"""Initialize a AirQualitySensor accessory object."""
|
||||
super().__init__(*args, category=CATEGORY_SENSOR)
|
||||
state = self.hass.states.get(self.entity_id)
|
||||
|
||||
self.create_services()
|
||||
|
||||
# Set the state so it is in sync on initial
|
||||
# GET to avoid an event storm after homekit startup
|
||||
self.async_update_state(state)
|
||||
|
||||
def create_services(self):
|
||||
"""Initialize a AirQualitySensor accessory object."""
|
||||
serv_air_quality = self.add_preload_service(
|
||||
SERV_AIR_QUALITY_SENSOR, [CHAR_AIR_PARTICULATE_DENSITY]
|
||||
)
|
||||
|
@ -163,9 +180,6 @@ class AirQualitySensor(HomeAccessory):
|
|||
self.char_density = serv_air_quality.configure_char(
|
||||
CHAR_AIR_PARTICULATE_DENSITY, value=0
|
||||
)
|
||||
# Set the state so it is in sync on initial
|
||||
# GET to avoid an event storm after homekit startup
|
||||
self.async_update_state(state)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, new_state):
|
||||
|
@ -179,6 +193,60 @@ class AirQualitySensor(HomeAccessory):
|
|||
_LOGGER.debug("%s: Set air_quality to %d", self.entity_id, air_quality)
|
||||
|
||||
|
||||
@TYPES.register("PM10Sensor")
|
||||
class PM10Sensor(AirQualitySensor):
|
||||
"""Generate a PM10Sensor accessory as PM 10 sensor."""
|
||||
|
||||
def create_services(self):
|
||||
"""Override the init function for PM 10 Sensor."""
|
||||
serv_air_quality = self.add_preload_service(
|
||||
SERV_AIR_QUALITY_SENSOR, [CHAR_PM10_DENSITY]
|
||||
)
|
||||
self.char_quality = serv_air_quality.configure_char(CHAR_AIR_QUALITY, value=0)
|
||||
self.char_density = serv_air_quality.configure_char(CHAR_PM10_DENSITY, value=0)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, new_state):
|
||||
"""Update accessory after state change."""
|
||||
density = convert_to_float(new_state.state)
|
||||
if not density:
|
||||
return
|
||||
if self.char_density.value != density:
|
||||
self.char_density.set_value(density)
|
||||
_LOGGER.debug("%s: Set density to %d", self.entity_id, density)
|
||||
air_quality = density_to_air_quality_pm10(density)
|
||||
if self.char_quality.value != air_quality:
|
||||
self.char_quality.set_value(air_quality)
|
||||
_LOGGER.debug("%s: Set air_quality to %d", self.entity_id, air_quality)
|
||||
|
||||
|
||||
@TYPES.register("PM25Sensor")
|
||||
class PM25Sensor(AirQualitySensor):
|
||||
"""Generate a PM25Sensor accessory as PM 2.5 sensor."""
|
||||
|
||||
def create_services(self):
|
||||
"""Override the init function for PM 2.5 Sensor."""
|
||||
serv_air_quality = self.add_preload_service(
|
||||
SERV_AIR_QUALITY_SENSOR, [CHAR_PM25_DENSITY]
|
||||
)
|
||||
self.char_quality = serv_air_quality.configure_char(CHAR_AIR_QUALITY, value=0)
|
||||
self.char_density = serv_air_quality.configure_char(CHAR_PM25_DENSITY, value=0)
|
||||
|
||||
@callback
|
||||
def async_update_state(self, new_state):
|
||||
"""Update accessory after state change."""
|
||||
density = convert_to_float(new_state.state)
|
||||
if not density:
|
||||
return
|
||||
if self.char_density.value != density:
|
||||
self.char_density.set_value(density)
|
||||
_LOGGER.debug("%s: Set density to %d", self.entity_id, density)
|
||||
air_quality = density_to_air_quality_pm25(density)
|
||||
if self.char_quality.value != air_quality:
|
||||
self.char_quality.set_value(air_quality)
|
||||
_LOGGER.debug("%s: Set air_quality to %d", self.entity_id, air_quality)
|
||||
|
||||
|
||||
@TYPES.register("CarbonMonoxideSensor")
|
||||
class CarbonMonoxideSensor(HomeAccessory):
|
||||
"""Generate a CarbonMonoxidSensor accessory as CO sensor."""
|
||||
|
|
|
@ -407,6 +407,32 @@ def density_to_air_quality(density):
|
|||
return 5
|
||||
|
||||
|
||||
def density_to_air_quality_pm10(density):
|
||||
"""Map PM10 density to HomeKit AirQuality level."""
|
||||
if density <= 40:
|
||||
return 1
|
||||
if density <= 80:
|
||||
return 2
|
||||
if density <= 120:
|
||||
return 3
|
||||
if density <= 300:
|
||||
return 4
|
||||
return 5
|
||||
|
||||
|
||||
def density_to_air_quality_pm25(density):
|
||||
"""Map PM2.5 density to HomeKit AirQuality level."""
|
||||
if density <= 25:
|
||||
return 1
|
||||
if density <= 50:
|
||||
return 2
|
||||
if density <= 100:
|
||||
return 3
|
||||
if density <= 300:
|
||||
return 4
|
||||
return 5
|
||||
|
||||
|
||||
def get_persist_filename_for_entry_id(entry_id: str):
|
||||
"""Determine the filename of the homekit state file."""
|
||||
return f"{DOMAIN}.{entry_id}.state"
|
||||
|
|
|
@ -212,8 +212,20 @@ def test_type_media_player(type_name, entity_id, state, attrs, config):
|
|||
("BinarySensor", "binary_sensor.opening", "on", {ATTR_DEVICE_CLASS: "opening"}),
|
||||
("BinarySensor", "device_tracker.someone", "not_home", {}),
|
||||
("BinarySensor", "person.someone", "home", {}),
|
||||
("AirQualitySensor", "sensor.air_quality_pm25", "40", {}),
|
||||
("AirQualitySensor", "sensor.air_quality", "40", {ATTR_DEVICE_CLASS: "pm25"}),
|
||||
("PM10Sensor", "sensor.air_quality_pm10", "30", {}),
|
||||
(
|
||||
"PM10Sensor",
|
||||
"sensor.air_quality",
|
||||
"30",
|
||||
{ATTR_DEVICE_CLASS: "pm10"},
|
||||
),
|
||||
("PM25Sensor", "sensor.air_quality_pm25", "40", {}),
|
||||
(
|
||||
"PM25Sensor",
|
||||
"sensor.air_quality",
|
||||
"40",
|
||||
{ATTR_DEVICE_CLASS: "pm25"},
|
||||
),
|
||||
(
|
||||
"CarbonMonoxideSensor",
|
||||
"sensor.co",
|
||||
|
|
|
@ -15,6 +15,8 @@ from homeassistant.components.homekit.type_sensors import (
|
|||
CarbonMonoxideSensor,
|
||||
HumiditySensor,
|
||||
LightSensor,
|
||||
PM10Sensor,
|
||||
PM25Sensor,
|
||||
TemperatureSensor,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
|
@ -132,6 +134,100 @@ async def test_air_quality(hass, hk_driver):
|
|||
assert acc.char_quality.value == 5
|
||||
|
||||
|
||||
async def test_pm10(hass, hk_driver):
|
||||
"""Test if accessory is updated after state change."""
|
||||
entity_id = "sensor.air_quality_pm10"
|
||||
|
||||
hass.states.async_set(entity_id, None)
|
||||
await hass.async_block_till_done()
|
||||
acc = PM10Sensor(hass, hk_driver, "PM10 Sensor", entity_id, 2, None)
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.category == 10 # Sensor
|
||||
|
||||
assert acc.char_density.value == 0
|
||||
assert acc.char_quality.value == 0
|
||||
|
||||
hass.states.async_set(entity_id, STATE_UNKNOWN)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 0
|
||||
assert acc.char_quality.value == 0
|
||||
|
||||
hass.states.async_set(entity_id, "34")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 34
|
||||
assert acc.char_quality.value == 1
|
||||
|
||||
hass.states.async_set(entity_id, "70")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 70
|
||||
assert acc.char_quality.value == 2
|
||||
|
||||
hass.states.async_set(entity_id, "110")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 110
|
||||
assert acc.char_quality.value == 3
|
||||
|
||||
hass.states.async_set(entity_id, "200")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 200
|
||||
assert acc.char_quality.value == 4
|
||||
|
||||
hass.states.async_set(entity_id, "400")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 400
|
||||
assert acc.char_quality.value == 5
|
||||
|
||||
|
||||
async def test_pm25(hass, hk_driver):
|
||||
"""Test if accessory is updated after state change."""
|
||||
entity_id = "sensor.air_quality_pm25"
|
||||
|
||||
hass.states.async_set(entity_id, None)
|
||||
await hass.async_block_till_done()
|
||||
acc = PM25Sensor(hass, hk_driver, "PM25 Sensor", entity_id, 2, None)
|
||||
await acc.run()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert acc.aid == 2
|
||||
assert acc.category == 10 # Sensor
|
||||
|
||||
assert acc.char_density.value == 0
|
||||
assert acc.char_quality.value == 0
|
||||
|
||||
hass.states.async_set(entity_id, STATE_UNKNOWN)
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 0
|
||||
assert acc.char_quality.value == 0
|
||||
|
||||
hass.states.async_set(entity_id, "23")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 23
|
||||
assert acc.char_quality.value == 1
|
||||
|
||||
hass.states.async_set(entity_id, "34")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 34
|
||||
assert acc.char_quality.value == 2
|
||||
|
||||
hass.states.async_set(entity_id, "90")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 90
|
||||
assert acc.char_quality.value == 3
|
||||
|
||||
hass.states.async_set(entity_id, "200")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 200
|
||||
assert acc.char_quality.value == 4
|
||||
|
||||
hass.states.async_set(entity_id, "400")
|
||||
await hass.async_block_till_done()
|
||||
assert acc.char_density.value == 400
|
||||
assert acc.char_quality.value == 5
|
||||
|
||||
|
||||
async def test_co(hass, hk_driver):
|
||||
"""Test if accessory is updated after state change."""
|
||||
entity_id = "sensor.co"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue