diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index e7b18f308f7492418779acc26d2581acb7ab7fde..90a9cb3fcc88a69398ee2989f43f6dfe500d5ef1 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -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, + ), + ), ] diff --git a/tests/components/matter/fixtures/nodes/air-quality-sensor.json b/tests/components/matter/fixtures/nodes/air-quality-sensor.json new file mode 100644 index 0000000000000000000000000000000000000000..4a533f0a1661ff0dfb78c8f2b6eb8e5f7913f9f4 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/air-quality-sensor.json @@ -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] + ] +} diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index 5b343b8c4e54a1f7c6c2eef8db1aad1f0dc38a2b..579dd7d94c55d0c964f921da17847363b7526274 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -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"