From 8b29e3011e8d7fe79c2a1bfe551ae48ab708de84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Mon, 25 Aug 2025 15:43:00 +0200 Subject: [PATCH] Matter Valve new attributes (#150788) --- .../components/matter/binary_sensor.py | 53 +++++++ homeassistant/components/matter/number.py | 17 ++ homeassistant/components/matter/sensor.py | 15 ++ homeassistant/components/matter/strings.json | 15 ++ homeassistant/components/matter/valve.py | 1 - .../matter/snapshots/test_binary_sensor.ambr | 147 ++++++++++++++++++ .../matter/snapshots/test_number.ambr | 58 +++++++ tests/components/matter/test_binary_sensor.py | 69 ++++++++ 8 files changed, 374 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/matter/binary_sensor.py b/homeassistant/components/matter/binary_sensor.py index ea74baab773..b36e826e711 100644 --- a/homeassistant/components/matter/binary_sensor.py +++ b/homeassistant/components/matter/binary_sensor.py @@ -407,6 +407,59 @@ DISCOVERY_SCHEMAS = [ required_attributes=(clusters.DishwasherAlarm.Attributes.State,), allow_multi=True, ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="ValveConfigurationAndControlValveFault_GeneralFault", + translation_key="valve_fault_general_fault", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + device_to_ha=lambda x: ( + x + == clusters.ValveConfigurationAndControl.Bitmaps.ValveFaultBitmap.kGeneralFault + ), + ), + entity_class=MatterBinarySensor, + required_attributes=( + clusters.ValveConfigurationAndControl.Attributes.ValveFault, + ), + allow_multi=True, + ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="ValveConfigurationAndControlValveFault_Blocked", + translation_key="valve_fault_blocked", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + device_to_ha=lambda x: ( + x + == clusters.ValveConfigurationAndControl.Bitmaps.ValveFaultBitmap.kBlocked + ), + ), + entity_class=MatterBinarySensor, + required_attributes=( + clusters.ValveConfigurationAndControl.Attributes.ValveFault, + ), + allow_multi=True, + ), + MatterDiscoverySchema( + platform=Platform.BINARY_SENSOR, + entity_description=MatterBinarySensorEntityDescription( + key="ValveConfigurationAndControlValveFault_Leaking", + translation_key="valve_fault_leaking", + device_class=BinarySensorDeviceClass.PROBLEM, + entity_category=EntityCategory.DIAGNOSTIC, + device_to_ha=lambda x: ( + x + == clusters.ValveConfigurationAndControl.Bitmaps.ValveFaultBitmap.kLeaking + ), + ), + entity_class=MatterBinarySensor, + required_attributes=( + clusters.ValveConfigurationAndControl.Attributes.ValveFault, + ), + ), MatterDiscoverySchema( platform=Platform.BINARY_SENSOR, entity_description=MatterBinarySensorEntityDescription( diff --git a/homeassistant/components/matter/number.py b/homeassistant/components/matter/number.py index d2184891dc1..4540c5bd2b3 100644 --- a/homeassistant/components/matter/number.py +++ b/homeassistant/components/matter/number.py @@ -313,6 +313,23 @@ DISCOVERY_SCHEMAS = [ clusters.OccupancySensing.Attributes.PIROccupiedToUnoccupiedDelay, ), ), + MatterDiscoverySchema( + platform=Platform.NUMBER, + entity_description=MatterNumberEntityDescription( + key="ValveConfigurationAndControlDefaultOpenDuration", + entity_category=EntityCategory.CONFIG, + translation_key="valve_configuration_and_control_default_open_duration", + native_max_value=65534, + native_min_value=1, + native_unit_of_measurement=UnitOfTime.SECONDS, + mode=NumberMode.BOX, + ), + entity_class=MatterNumber, + required_attributes=( + clusters.ValveConfigurationAndControl.Attributes.DefaultOpenDuration, + ), + allow_multi=True, + ), MatterDiscoverySchema( platform=Platform.NUMBER, entity_description=MatterRangeNumberEntityDescription( diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 18bd7f84da3..d8e55b7b1ff 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -1370,4 +1370,19 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterSensor, required_attributes=(clusters.PumpConfigurationAndControl.Attributes.Speed,), ), + MatterDiscoverySchema( + platform=Platform.SENSOR, + entity_description=MatterSensorEntityDescription( + key="ValveConfigurationAndControlAutoCloseTime", + translation_key="auto_close_time", + device_class=SensorDeviceClass.TIMESTAMP, + state_class=None, + device_to_ha=(lambda x: dt_util.utc_from_timestamp(x) if x > 0 else None), + ), + entity_class=MatterSensor, + featuremap_contains=clusters.ValveConfigurationAndControl.Bitmaps.Feature.kTimeSync, + required_attributes=( + clusters.ValveConfigurationAndControl.Attributes.AutoCloseTime, + ), + ), ] diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index e9c023cd74e..9a0bb77adfa 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -94,6 +94,15 @@ }, "alarm_door": { "name": "Door alarm" + }, + "valve_fault_blocked": { + "name": "Valve blocked" + }, + "valve_fault_general_fault": { + "name": "General fault" + }, + "valve_fault_leaking": { + "name": "Valve leaking" } }, "button": { @@ -206,6 +215,9 @@ }, "led_indicator_intensity_on": { "name": "LED on intensity" + }, + "valve_configuration_and_control_default_open_duration": { + "name": "Default open duration" } }, "light": { @@ -292,6 +304,9 @@ "activated_carbon_filter_condition": { "name": "Activated carbon filter condition" }, + "auto_close_time": { + "name": "Auto-close time" + }, "contamination_state": { "name": "Contamination state", "state": { diff --git a/homeassistant/components/matter/valve.py b/homeassistant/components/matter/valve.py index 4cedec74bf2..715cdc2a09e 100644 --- a/homeassistant/components/matter/valve.py +++ b/homeassistant/components/matter/valve.py @@ -21,7 +21,6 @@ from .helpers import get_matter from .models import MatterDiscoverySchema ValveConfigurationAndControl = clusters.ValveConfigurationAndControl - ValveStateEnum = ValveConfigurationAndControl.Enums.ValveStateEnum diff --git a/tests/components/matter/snapshots/test_binary_sensor.ambr b/tests/components/matter/snapshots/test_binary_sensor.ambr index 8756efdfbd2..da199afd3a6 100644 --- a/tests/components/matter/snapshots/test_binary_sensor.ambr +++ b/tests/components/matter/snapshots/test_binary_sensor.ambr @@ -1124,3 +1124,150 @@ 'state': 'off', }) # --- +# name: test_binary_sensors[valve][binary_sensor.valve_general_fault-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.valve_general_fault', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'General fault', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'valve_fault_general_fault', + 'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-ValveConfigurationAndControlValveFault_GeneralFault-129-9', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[valve][binary_sensor.valve_general_fault-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Valve General fault', + }), + 'context': , + 'entity_id': 'binary_sensor.valve_general_fault', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_binary_sensors[valve][binary_sensor.valve_valve_blocked-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.valve_valve_blocked', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve blocked', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'valve_fault_blocked', + 'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-ValveConfigurationAndControlValveFault_Blocked-129-9', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[valve][binary_sensor.valve_valve_blocked-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Valve Valve blocked', + }), + 'context': , + 'entity_id': 'binary_sensor.valve_valve_blocked', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_binary_sensors[valve][binary_sensor.valve_valve_leaking-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'binary_sensor', + 'entity_category': , + 'entity_id': 'binary_sensor.valve_valve_leaking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': 'Valve leaking', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'valve_fault_leaking', + 'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-ValveConfigurationAndControlValveFault_Leaking-129-9', + 'unit_of_measurement': None, + }) +# --- +# name: test_binary_sensors[valve][binary_sensor.valve_valve_leaking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'device_class': 'problem', + 'friendly_name': 'Valve Valve leaking', + }), + 'context': , + 'entity_id': 'binary_sensor.valve_valve_leaking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- diff --git a/tests/components/matter/snapshots/test_number.ambr b/tests/components/matter/snapshots/test_number.ambr index 24a92799082..0273c83ac5c 100644 --- a/tests/components/matter/snapshots/test_number.ambr +++ b/tests/components/matter/snapshots/test_number.ambr @@ -2366,3 +2366,61 @@ 'state': '4.0', }) # --- +# name: test_numbers[valve][number.valve_default_open_duration-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'max': 65534, + 'min': 1, + 'mode': , + 'step': 1.0, + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'number', + 'entity_category': , + 'entity_id': 'number.valve_default_open_duration', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Default open duration', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'valve_configuration_and_control_default_open_duration', + 'unique_id': '00000000000004D2-000000000000004B-MatterNodeDevice-1-ValveConfigurationAndControlDefaultOpenDuration-129-1', + 'unit_of_measurement': , + }) +# --- +# name: test_numbers[valve][number.valve_default_open_duration-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Valve Default open duration', + 'max': 65534, + 'min': 1, + 'mode': , + 'step': 1.0, + 'unit_of_measurement': , + }), + 'context': , + 'entity_id': 'number.valve_default_open_duration', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '0', + }) +# --- diff --git a/tests/components/matter/test_binary_sensor.py b/tests/components/matter/test_binary_sensor.py index fcfd4da84c8..06055af8c9d 100644 --- a/tests/components/matter/test_binary_sensor.py +++ b/tests/components/matter/test_binary_sensor.py @@ -275,3 +275,72 @@ async def test_dishwasher_alarm( state = hass.states.get("binary_sensor.dishwasher_door_alarm") assert state assert state.state == "on" + + +@pytest.mark.parametrize("node_fixture", ["valve"]) +async def test_water_valve( + hass: HomeAssistant, + matter_client: MagicMock, + matter_node: MatterNode, +) -> None: + """Test valve alarms.""" + # ValveFault default state + state = hass.states.get("binary_sensor.valve_general_fault") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_blocked") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_leaking") + assert state + assert state.state == "off" + + # ValveFault general_fault test + set_node_attribute(matter_node, 1, 129, 9, 1) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("binary_sensor.valve_general_fault") + assert state + assert state.state == "on" + + state = hass.states.get("binary_sensor.valve_valve_blocked") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_leaking") + assert state + assert state.state == "off" + + # ValveFault valve_blocked test + set_node_attribute(matter_node, 1, 129, 9, 2) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("binary_sensor.valve_general_fault") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_blocked") + assert state + assert state.state == "on" + + state = hass.states.get("binary_sensor.valve_valve_leaking") + assert state + assert state.state == "off" + + # ValveFault valve_leaking test + set_node_attribute(matter_node, 1, 129, 9, 4) + await trigger_subscription_callback(hass, matter_client) + + state = hass.states.get("binary_sensor.valve_general_fault") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_blocked") + assert state + assert state.state == "off" + + state = hass.states.get("binary_sensor.valve_valve_leaking") + assert state + assert state.state == "on"