From 31a1ad8409855f1505e7cf113e52eeb616841bc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Tue, 24 Sep 2024 17:59:58 +0200 Subject: [PATCH] Add Pressure and Altitude discovery schemas for Matter Eve Weather device (#125690) * Update number.py to add EveWeatherAltitude attribute * Update sensor.py to add EveCluster Pressure Attribute * Update strings.json * Create eve-weather-sensor.json * Update test_sensor.py * Update eve-weather-sensor.json * Update test_sensor.py Pressure AttributeId: 319422484 (0x00130a0014) - Value type: float32 * Update test_sensor.py * Update test_sensor.py * Update test_sensor.py * Update manifest.json Bump to python-matter-server==6.5.0 * Update requirements_all.txt Bump requirements to python-matter-server 6.5.0 * Update requirements_test_all.txt Bump requirements to python-matter-server 6.5.0 * Update test_sensor.py * Update test_sensor.py * Update sensor.py * Update sensor.py * Update test_sensor.py * Update sensor.py * Update test_sensor.py * Update test_sensor.py * Update test_sensor.py * fix test fixture * Update requirements_all.txt * Update requirements_test_all.txt * Update manifest.json * fix tests * Update test_sensor.py * add device class --------- Co-authored-by: Marcel van der Veldt --- homeassistant/components/matter/number.py | 20 +- homeassistant/components/matter/sensor.py | 12 + homeassistant/components/matter/strings.json | 3 + .../fixtures/nodes/eve-weather-sensor.json | 322 ++++++++++++++++++ tests/components/matter/test_number.py | 52 ++- tests/components/matter/test_sensor.py | 68 ++-- 6 files changed, 455 insertions(+), 22 deletions(-) create mode 100644 tests/components/matter/fixtures/nodes/eve-weather-sensor.json diff --git a/homeassistant/components/matter/number.py b/homeassistant/components/matter/number.py index c9b40ef71a0..cc312cdc66a 100644 --- a/homeassistant/components/matter/number.py +++ b/homeassistant/components/matter/number.py @@ -5,15 +5,17 @@ from __future__ import annotations from dataclasses import dataclass from chip.clusters import Objects as clusters +from matter_server.common import custom_clusters from matter_server.common.helpers.util import create_attribute_path_from_attribute from homeassistant.components.number import ( + NumberDeviceClass, NumberEntity, NumberEntityDescription, NumberMode, ) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import EntityCategory, Platform, UnitOfTime +from homeassistant.const import EntityCategory, Platform, UnitOfLength, UnitOfTime from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -137,4 +139,20 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterNumber, required_attributes=(clusters.LevelControl.Attributes.OnOffTransitionTime,), ), + MatterDiscoverySchema( + platform=Platform.NUMBER, + entity_description=MatterNumberEntityDescription( + key="EveWeatherAltitude", + device_class=NumberDeviceClass.DISTANCE, + entity_category=EntityCategory.CONFIG, + translation_key="altitude", + native_max_value=9000, + native_min_value=0, + native_unit_of_measurement=UnitOfLength.METERS, + native_step=1, + mode=NumberMode.BOX, + ), + entity_class=MatterNumber, + required_attributes=(custom_clusters.EveCluster.Attributes.Altitude,), + ), ] diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 9bd21e1a95d..1d6d7ac77f3 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -242,6 +242,18 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterSensor, required_attributes=(EveCluster.Attributes.ValvePosition,), ), + MatterDiscoverySchema( + platform=Platform.SENSOR, + entity_description=MatterSensorEntityDescription( + key="EveWeatherPressure", + device_class=SensorDeviceClass.PRESSURE, + native_unit_of_measurement=UnitOfPressure.HPA, + suggested_display_precision=1, + state_class=SensorStateClass.MEASUREMENT, + ), + entity_class=MatterSensor, + required_attributes=(EveCluster.Attributes.Pressure,), + ), MatterDiscoverySchema( platform=Platform.SENSOR, entity_description=MatterSensorEntityDescription( diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index dd01da56d7f..f75695cc3bc 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -119,6 +119,9 @@ }, "on_off_transition_time": { "name": "On/Off transition time" + }, + "altitude": { + "name": "Altitude above Sea Level" } }, "light": { diff --git a/tests/components/matter/fixtures/nodes/eve-weather-sensor.json b/tests/components/matter/fixtures/nodes/eve-weather-sensor.json new file mode 100644 index 00000000000..dacba8d336b --- /dev/null +++ b/tests/components/matter/fixtures/nodes/eve-weather-sensor.json @@ -0,0 +1,322 @@ +{ + "node_id": 29, + "date_commissioned": "2024-09-10T13:34:48.252332", + "last_interview": "2024-09-10T13:34:48.252334", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 42, 47, 48, 49, 51, 53, 60, 62, 63], + "0/29/2": [41], + "0/29/3": [1, 2], + "0/29/65532": 0, + "0/29/65533": 1, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/31/0": [ + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 4 + } + ], + "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": 1, + "0/40/1": "Eve Systems", + "0/40/2": 4874, + "0/40/3": "Eve Weather", + "0/40/4": 87, + "0/40/5": "", + "0/40/6": "**REDACTED**", + "0/40/7": 1, + "0/40/8": "1.1", + "0/40/9": 7143, + "0/40/10": "3.3.0", + "0/40/15": "**REDACTED**", + "0/40/18": "**REDACTED**", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/65532": 0, + "0/40/65533": 1, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 19, 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], + "0/42/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/47/0": 1, + "0/47/1": 0, + "0/47/2": "Battery", + "0/47/11": 2956, + "0/47/12": 200, + "0/47/14": 0, + "0/47/15": false, + "0/47/16": 2, + "0/47/18": [], + "0/47/19": "", + "0/47/25": 1, + "0/47/65532": 10, + "0/47/65533": 1, + "0/47/65528": [], + "0/47/65529": [], + "0/47/65531": [ + 0, 1, 2, 11, 12, 14, 15, 16, 18, 19, 25, 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/49/2": 10, + "0/49/3": 20, + "0/49/4": true, + "0/49/5": 0, + "0/49/6": "**REDACTED**", + "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, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/51/0": [], + "0/51/1": 1, + "0/51/2": 3416207, + "0/51/3": 948, + "0/51/5": [], + "0/51/6": [], + "0/51/7": [], + "0/51/8": false, + "0/51/65532": 0, + "0/51/65533": 1, + "0/51/65528": [], + "0/51/65529": [0], + "0/51/65531": [0, 1, 2, 3, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533], + "0/53/0": 25, + "0/53/1": 2, + "0/53/2": "**REDACTED**", + "0/53/3": 4660, + "0/53/4": 12054125955590472924, + "0/53/5": "**REDACTED**", + "0/53/6": 0, + "0/53/7": [], + "0/53/8": [], + "0/53/9": 867525816, + "0/53/10": 68, + "0/53/11": 127, + "0/53/12": 197, + "0/53/13": 17, + "0/53/14": 244, + "0/53/15": 243, + "0/53/16": 0, + "0/53/17": 0, + "0/53/18": 334, + "0/53/19": 6, + "0/53/20": 0, + "0/53/21": 221, + "0/53/22": 1814103, + "0/53/23": 1812208, + "0/53/24": 1895, + "0/53/25": 1812220, + "0/53/26": 1806871, + "0/53/27": 1895, + "0/53/28": 144123, + "0/53/29": 1670020, + "0/53/30": 0, + "0/53/31": 0, + "0/53/32": 0, + "0/53/33": 515245, + "0/53/34": 1061, + "0/53/35": 0, + "0/53/36": 25, + "0/53/37": 0, + "0/53/38": 0, + "0/53/39": 310675, + "0/53/40": 180775, + "0/53/41": 783, + "0/53/42": 171240, + "0/53/43": 0, + "0/53/44": 4, + "0/53/45": 0, + "0/53/46": 0, + "0/53/47": 0, + "0/53/48": 110041, + "0/53/49": 10200, + "0/53/50": 818, + "0/53/51": 11698, + "0/53/52": 0, + "0/53/53": 114, + "0/53/54": 6189, + "0/53/55": 371, + "0/53/59": { + "0": 672, + "1": 8335 + }, + "0/53/60": "AB//4A==", + "0/53/61": { + "0": true, + "1": false, + "2": true, + "3": true, + "4": true, + "5": true, + "6": false, + "7": true, + "8": true, + "9": true, + "10": true, + "11": true + }, + "0/53/62": [0, 0, 0, 0], + "0/53/65532": 15, + "0/53/65533": 1, + "0/53/65528": [], + "0/53/65529": [0], + "0/53/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 59, + 60, 61, 62, 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, 1, 2], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/62/0": [], + "0/62/1": [], + "0/62/2": 5, + "0/62/3": 4, + "0/62/4": [], + "0/62/5": 4, + "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": 1, + "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": 4, + "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": 770, + "1": 2 + } + ], + "1/29/1": [3, 29, 1026, 319486977], + "1/29/2": [], + "1/29/3": [], + "1/29/65532": 0, + "1/29/65533": 1, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/1026/0": 1603, + "1/1026/1": -4000, + "1/1026/2": 8500, + "1/1026/65532": 0, + "1/1026/65533": 4, + "1/1026/65528": [], + "1/1026/65529": [], + "1/1026/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "1/319486977/319422464": "AAFXCwIAAAMC/xsEDFNWNDNMMUEwMzg2MJwBAP8EAQJsNPkBAR0BACUE9griHksEfgeAA1EBAA==", + "1/319486977/319422466": "Ps00AOODMwBqe48sBgECAgIDAicBLwEjAlAPABAABwAA6gERAAEA", + "1/319486977/319422467": "EiMTAACLYy0AH74Fwx88JwQOEiQTAADjZS0AH7wFzB87JwQOEiUTAAA7aC0AH7oF1B86JwQOEiYTAACTai0AH7kF5x86JwQOEicTAADrbC0AH7sF8B85JwQOEigTAABDby0AH7wFAiA4JwQOEikTAACbcS0AH7sFFCA3JwQOEioTAADzcy0AH7EFMiA1JwQOEisTAABLdi0AH6gFVyA0JwQOEiwTAACjeC0AH6gFaiAzJwQOEi0TAAD7ei0AH6YFfCAyJwQOEi4TAABTfS0AH6YFgCAzJwQOEi8TAACrfy0AH6MFhyA0JwQOEjATAAADgi0AH58FnSA1JwQOEjETAABbhC0AH58FtSA1JwQOEjITAACzhi0AH5wFwSA0JwQOEjMTAAALiS0AH5cF1SA0JwQOEjQTAABjiy0AH58F3yA0JwIGEjUTAAC7jS0AH6EF7yA0JwIGEjYTAAATkC0AH60F+yAzJwIGEjcTAABrki0AH68FAiEyJwIGEjgTAADDlC0AH7kFACEyJwIGEjkTAAAbly0AH8QF7SAyJwIGEjoTAABzmS0AH9QF1SAzJwIGEjsTAADLmy0AH98FvyAzJwIG", + "1/319486977/319422482": 13420, + "1/319486977/319422483": 40.0, + "1/319486977/319422484": 1008.5, + "1/319486977/319422485": 6, + "1/319486977/319422486": 0, + "1/319486977/65533": 1, + "1/319486977/65528": [], + "1/319486977/65529": [], + "1/319486977/65531": [ + 65528, 65529, 65531, 319422464, 319422465, 319422466, 319422467, + 319422468, 319422469, 319422482, 319422483, 319422484, 319422485, + 319422486, 65533 + ], + "2/3/0": 0, + "2/3/1": 4, + "2/3/65532": 0, + "2/3/65533": 4, + "2/3/65528": [], + "2/3/65529": [0], + "2/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "2/29/0": [ + { + "0": 775, + "1": 2 + } + ], + "2/29/1": [3, 29, 1029], + "2/29/2": [], + "2/29/3": [], + "2/29/65532": 0, + "2/29/65533": 1, + "2/29/65528": [], + "2/29/65529": [], + "2/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "2/1029/0": 8066, + "2/1029/1": 0, + "2/1029/2": 10000, + "2/1029/65532": 0, + "2/1029/65533": 3, + "2/1029/65528": [], + "2/1029/65529": [], + "2/1029/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/test_number.py b/tests/components/matter/test_number.py index 917f8138c7a..257875d6715 100644 --- a/tests/components/matter/test_number.py +++ b/tests/components/matter/test_number.py @@ -1,8 +1,10 @@ """Test Matter number entities.""" -from unittest.mock import MagicMock +from unittest.mock import MagicMock, call from matter_server.client.models.node import MatterNode +from matter_server.common import custom_clusters +from matter_server.common.helpers.util import create_attribute_path_from_attribute import pytest from homeassistant.core import HomeAssistant @@ -24,6 +26,16 @@ async def dimmable_light_node_fixture( ) +@pytest.fixture(name="eve_weather_sensor_node") +async def eve_weather_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a Eve Weather sensor node.""" + return await setup_integration_with_node_fixture( + hass, "eve-weather-sensor", matter_client + ) + + # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) async def test_level_control_config_entities( @@ -54,3 +66,41 @@ async def test_level_control_config_entities( state = hass.states.get("number.mock_dimmable_light_on_level") assert state assert state.state == "20" + + +async def test_eve_weather_sensor_altitude( + hass: HomeAssistant, + matter_client: MagicMock, + eve_weather_sensor_node: MatterNode, +) -> None: + """Test weather sensor created from (Eve) custom cluster.""" + # pressure sensor on Eve custom cluster + state = hass.states.get("number.eve_weather_altitude_above_sea_level") + assert state + assert state.state == "40.0" + + set_node_attribute(eve_weather_sensor_node, 1, 319486977, 319422483, 800) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("number.eve_weather_altitude_above_sea_level") + assert state + assert state.state == "800.0" + + # test set value + await hass.services.async_call( + "number", + "set_value", + { + "entity_id": "number.eve_weather_altitude_above_sea_level", + "value": 500, + }, + blocking=True, + ) + assert matter_client.write_attribute.call_count == 1 + assert matter_client.write_attribute.call_args_list[0] == call( + node_id=eve_weather_sensor_node.node_id, + attribute_path=create_attribute_path_from_attribute( + endpoint_id=1, + attribute=custom_clusters.EveCluster.Attributes.Altitude, + ), + value=500, + ) diff --git a/tests/components/matter/test_sensor.py b/tests/components/matter/test_sensor.py index d887ff4d233..0d0429f785f 100644 --- a/tests/components/matter/test_sensor.py +++ b/tests/components/matter/test_sensor.py @@ -92,6 +92,16 @@ async def eve_energy_plug_patched_node_fixture( ) +@pytest.fixture(name="eve_weather_sensor_node") +async def eve_weather_sensor_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a Eve Weather sensor node.""" + return await setup_integration_with_node_fixture( + hass, "eve-weather-sensor", matter_client + ) + + @pytest.fixture(name="air_quality_sensor_node") async def air_quality_sensor_node_fixture( hass: HomeAssistant, matter_client: MagicMock @@ -192,26 +202,6 @@ async def test_light_sensor( assert state.state == "2.0" -# This tests needs to be adjusted to remove lingering tasks -@pytest.mark.parametrize("expected_lingering_tasks", [True]) -async def test_pressure_sensor( - hass: HomeAssistant, - matter_client: MagicMock, - pressure_sensor_node: MatterNode, -) -> None: - """Test pressure sensor.""" - state = hass.states.get("sensor.mock_pressure_sensor_pressure") - assert state - assert state.state == "0.0" - - set_node_attribute(pressure_sensor_node, 1, 1027, 0, 1010) - await trigger_subscription_callback(hass, matter_client) - - state = hass.states.get("sensor.mock_pressure_sensor_pressure") - assert state - assert state.state == "101.0" - - # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) async def test_temperature_sensor( @@ -413,6 +403,44 @@ async def test_eve_thermo_sensor( assert state.state == "0" +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_pressure_sensor( + hass: HomeAssistant, + matter_client: MagicMock, + pressure_sensor_node: MatterNode, +) -> None: + """Test pressure sensor.""" + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "0.0" + + set_node_attribute(pressure_sensor_node, 1, 1027, 0, 1010) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("sensor.mock_pressure_sensor_pressure") + assert state + assert state.state == "101.0" + + +async def test_eve_weather_sensor_custom_cluster( + hass: HomeAssistant, + matter_client: MagicMock, + eve_weather_sensor_node: MatterNode, +) -> None: + """Test weather sensor created from (Eve) custom cluster.""" + # pressure sensor on Eve custom cluster + state = hass.states.get("sensor.eve_weather_pressure") + assert state + assert state.state == "1008.5" + + set_node_attribute(eve_weather_sensor_node, 1, 319486977, 319422484, 800) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("sensor.eve_weather_pressure") + assert state + assert state.state == "800.0" + + # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) async def test_air_quality_sensor(