Matter sensors for air quality measurements (#108173)

* Matter sensors for air quality measurements
Add sensors for CO2, PM1, PM2.5, PM10 and TVOC

* Add initial tests for matter air quality sensor

* Remove VOC data as requires unit extraction from cluster
This commit is contained in:
matt7aylor 2024-01-27 07:46:24 +00:00 committed by GitHub
parent f35e0d8a55
commit dec9eb9ae4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 409 additions and 0 deletions

View File

@ -15,6 +15,8 @@ from homeassistant.components.sensor import (
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
LIGHT_LUX,
PERCENTAGE,
EntityCategory,
@ -207,4 +209,56 @@ DISCOVERY_SCHEMAS = [
optional_attributes=(clusters.OnOff.Attributes.OnOff,),
should_poll=True,
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="CarbonDioxideSensor",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
device_class=SensorDeviceClass.CO2,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(
clusters.CarbonDioxideConcentrationMeasurement.Attributes.MeasuredValue,
),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM1Sensor",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(
clusters.Pm1ConcentrationMeasurement.Attributes.MeasuredValue,
),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM25Sensor",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(
clusters.Pm25ConcentrationMeasurement.Attributes.MeasuredValue,
),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="PM10Sensor",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM10,
state_class=SensorStateClass.MEASUREMENT,
),
entity_class=MatterSensor,
required_attributes=(
clusters.Pm10ConcentrationMeasurement.Attributes.MeasuredValue,
),
),
]

View File

@ -0,0 +1,288 @@
{
"node_id": 1,
"date_commissioned": "2024-01-13T20:12:42.853855",
"last_interview": "2024-01-13T20:12:42.853862",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/29/0": [
{
"0": 22,
"1": 1
}
],
"0/29/1": [29, 31, 40, 42, 48, 49, 51, 60, 62, 63],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 2,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/31/0": [
{
"254": 2
},
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 3
}
],
"0/31/1": [],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 1,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/40/0": 17,
"0/40/1": "Nordic Semiconductor ASA",
"0/40/2": 65521,
"0/40/3": "lightfi-aq1-air-quality-sensor",
"0/40/4": 32768,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 0,
"0/40/8": "prerelease",
"0/40/9": 0,
"0/40/10": "prerelease",
"0/40/19": {
"caseSessionsPerFabric": 3,
"subscriptionsPerFabric": 3
},
"0/40/21": null,
"0/40/22": null,
"0/40/65532": 0,
"0/40/65533": 3,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 19, 21, 22, 65528, 65529, 65531, 65532,
65533
],
"0/42/0": [],
"0/42/1": true,
"0/42/2": 1,
"0/42/3": null,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [],
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 0,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 1,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533],
"0/49/0": 1,
"0/49/1": [
{
"0": "q/UPJlJtKwk=",
"1": true
}
],
"0/49/4": true,
"0/49/5": 0,
"0/49/6": "q/UPJlJtKwk=",
"0/49/7": null,
"0/49/65532": 2,
"0/49/65533": 1,
"0/49/65528": [1, 5, 7],
"0/49/65529": [0, 3, 4, 6, 8],
"0/49/65531": [0, 1, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533],
"0/51/0": [
{
"0": "ieee802154",
"1": true,
"2": null,
"3": null,
"4": "SrFuqi7GOA0=",
"5": [],
"6": [
"/oAAAAAAAABIsW6qLsY4DQ==",
"/a2BRTrZUShjZ6Plq5dszA==",
"/R/knXM6xXGK6bPqGglOHw=="
],
"7": 4
}
],
"0/51/1": 12,
"0/51/8": false,
"0/51/65532": 0,
"0/51/65533": 1,
"0/51/65528": [],
"0/51/65529": [],
"0/51/65531": [0, 1, 8, 65528, 65529, 65531, 65532, 65533],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 0,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 2],
"0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"0/62/0": [
{
"254": 2
},
{
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEEVW22H4oSAH8ygJxeAehOa+Fy5OvrpZP1+OsUuhMOHK8xRrUY021wlmdjeKyX3Fnax3+QU5eXXNyJl8B4KDS8wTcKNQEoARgkAgE2AwQCBAEYMAQUwvHAaN24tRt6l5HJQbyntNkVQZIwBRRje8c2OVfVDK5m9OcVHaS51jcEChgwC0A5oKtEonnnHfT+Ut+H359m/kiVNMmVkroDCeBWKItO6T28kladkvO0iHB8J1L7QFLEsDxv9YuCBOPa0T7fUHb6GA==",
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEa/1FpraZtnACK49eqDofsCGh7KwwBIKj28CVug2c1v7Nk+jy6Alq83vfKRc1MR6+9Lp31clVfzhOpCsX0vYiXjcKNQEpARgkAmAwBBRje8c2OVfVDK5m9OcVHaS51jcECjAFFBM9Maievp4UKHYUW3dZjm1BsGxwGDALQPa5FY9kZ/Ii83D0I4eCXwdSUEQWPIYeCyodb/eO1p0gtUzTbRxYmYFBTaE2bMVQHoZ7KFnC1uBvv6T/ELrxTD0Y",
"254": 3
}
],
"0/62/1": [
{
"1": "BBfkzpqcInPpJmIWMy2yckRYs4V/CHeqvr7ObEtvRywP9sQSuJ2vIIJer+Af5gA/5sld0ZRaOCdBksUK3b5g4/w=",
"2": 24582,
"3": 4448312386606703954,
"4": 11636151610245023439,
"5": "",
"254": 2
},
{
"1": "BJSwLCRLiMCNDkJINo2xgNg4Q4DQOnPH/UjP6AVITT6YFza4r9itL7nPg3TJo7quKWfdZ1aksO7doJZvFo5WyUU=",
"2": 65521,
"3": 1,
"4": 1,
"5": "",
"254": 3
}
],
"0/62/2": 5,
"0/62/3": 2,
"0/62/4": [
"FTABAQEkAgE3AycU2fdVLSD8xXAYJgSAVCstJgWAWsNSNwYnFNn3VS0g/MVwGCQHASQIATAJQQQX5M6anCJz6SZiFjMtsnJEWLOFfwh3qr6+zmxLb0csD/bEEridryCCXq/gH+YAP+bJXdGUWjgnQZLFCt2+YOP8Nwo1ASkBGCQCYDAEFEMcdLs9Y7GImXEqgx7gT7WdJXEmMAUUQxx0uz1jsYiZcSqDHuBPtZ0lcSYYMAtA8OCKQmQJSw32MwkiKh2yCqXwqPo5ZFqC5KIju6EhVyic45AZqc8XooMha/G87qtjpG4X6zh4aEdwOJGgMVoewxg=",
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEElLAsJEuIwI0OQkg2jbGA2DhDgNA6c8f9SM/oBUhNPpgXNriv2K0vuc+DdMmjuq4pZ91nVqSw7t2glm8WjlbJRTcKNQEpARgkAmAwBBQTPTGonr6eFCh2FFt3WY5tQbBscDAFFBM9Maievp4UKHYUW3dZjm1BsGxwGDALQEoomYkckgebSt7QekvI/9ZPf9y5pCFq7Vi+3bWwduTHa560n9hFZ01anVu4UxOsx1cn8erdu/dVdkHIBtfCKrcY"
],
"0/62/5": 3,
"0/62/65532": 0,
"0/62/65533": 1,
"0/62/65528": [1, 3, 5, 8],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533],
"0/63/0": [],
"0/63/1": [],
"0/63/2": 4,
"0/63/3": 3,
"0/63/65532": 0,
"0/63/65533": 2,
"0/63/65528": [2, 5],
"0/63/65529": [0, 1, 3, 4],
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"1/3/0": 0,
"1/3/1": 0,
"1/3/65532": 0,
"1/3/65533": 4,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/29/0": [
{
"0": 44,
"1": 1
}
],
"1/29/1": [3, 29, 91, 1026, 1029, 1037, 1043, 1066, 1068, 1069, 1070],
"1/29/2": [],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 2,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533],
"1/91/0": null,
"1/91/65532": null,
"1/91/65533": 1,
"1/91/65528": [],
"1/91/65529": [],
"1/91/65531": [0, 65528, 65529, 65531, 65532, 65533],
"1/1026/0": 2008,
"1/1026/1": null,
"1/1026/2": null,
"1/1026/65532": 0,
"1/1026/65533": 1,
"1/1026/65528": [],
"1/1026/65529": [],
"1/1026/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1029/0": 2875,
"1/1029/1": 0,
"1/1029/2": 0,
"1/1029/65532": 0,
"1/1029/65533": 3,
"1/1029/65528": [],
"1/1029/65529": [],
"1/1029/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1037/0": 678.0,
"1/1037/1": 0.0,
"1/1037/2": 5000.0,
"1/1037/65532": 1,
"1/1037/65533": 3,
"1/1037/65528": [],
"1/1037/65529": [],
"1/1037/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1043/0": 0.0,
"1/1043/1": 0.0,
"1/1043/2": 500.0,
"1/1043/65532": 1,
"1/1043/65533": 3,
"1/1043/65528": [],
"1/1043/65529": [],
"1/1043/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1066/0": 3.0,
"1/1066/1": 0.0,
"1/1066/2": 1000.0,
"1/1066/65532": 1,
"1/1066/65533": 3,
"1/1066/65528": [],
"1/1066/65529": [],
"1/1066/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1068/0": 3.0,
"1/1068/1": 0.0,
"1/1068/2": 1000.0,
"1/1068/65532": 1,
"1/1068/65533": 3,
"1/1068/65528": [],
"1/1068/65529": [],
"1/1068/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1069/0": 3.0,
"1/1069/1": 0.0,
"1/1069/2": 1000.0,
"1/1069/65532": 1,
"1/1069/65533": 3,
"1/1069/65528": [],
"1/1069/65529": [],
"1/1069/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533],
"1/1070/0": 189.0,
"1/1070/1": 0.0,
"1/1070/2": 500.0,
"1/1070/65532": 1,
"1/1070/65533": 3,
"1/1070/65528": [],
"1/1070/65529": [],
"1/1070/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533]
},
"attribute_subscriptions": [
[1, 1037, 0],
[1, 1070, 0],
[1, 1066, 0],
[1, 1068, 0],
[1, 1069, 0],
[1, 1026, 0],
[1, 1029, 0]
]
}

View File

@ -76,6 +76,16 @@ async def eve_energy_plug_node_fixture(
)
@pytest.fixture(name="air_quality_sensor_node")
async def air_quality_sensor_node_fixture(
hass: HomeAssistant, matter_client: MagicMock
) -> MatterNode:
"""Fixture for an air quality sensor (LightFi AQ1) node."""
return await setup_integration_with_node_fixture(
hass, "air-quality-sensor", matter_client
)
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_sensor_null_value(
@ -288,3 +298,60 @@ async def test_eve_energy_sensors(
state = hass.states.get(entity_id)
assert state
assert state.state == "5.0"
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_air_quality_sensor(
hass: HomeAssistant,
matter_client: MagicMock,
air_quality_sensor_node: MatterNode,
) -> None:
"""Test air quality sensor."""
# Carbon Dioxide
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_carbon_dioxide")
assert state
assert state.state == "678.0"
set_node_attribute(air_quality_sensor_node, 1, 1037, 0, 789)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_carbon_dioxide")
assert state
assert state.state == "789.0"
# PM1
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm1")
assert state
assert state.state == "3.0"
set_node_attribute(air_quality_sensor_node, 1, 1068, 0, 50)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm1")
assert state
assert state.state == "50.0"
# PM2.5
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm2_5")
assert state
assert state.state == "3.0"
set_node_attribute(air_quality_sensor_node, 1, 1066, 0, 50)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm2_5")
assert state
assert state.state == "50.0"
# PM10
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm10")
assert state
assert state.state == "3.0"
set_node_attribute(air_quality_sensor_node, 1, 1069, 0, 50)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.lightfi_aq1_air_quality_sensor_pm10")
assert state
assert state.state == "50.0"