From d62f136c58e2db09ff5abe0774d2cc0bf30f64e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Tue, 28 Apr 2026 19:55:37 +0200 Subject: [PATCH] Add child lock entity for Eve Matter devices (#169391) --- homeassistant/components/matter/switch.py | 10 + .../matter/snapshots/test_switch.ambr | 200 ++++++++++++++++++ tests/components/matter/test_switch.py | 52 +++++ 3 files changed, 262 insertions(+) diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index dc6a6bbcb5d..9b757d8bbf3 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -316,4 +316,14 @@ DISCOVERY_SCHEMAS = [ value_contains=clusters.EnergyEvse.Commands.EnableCharging.command_id, allow_multi=True, ), + MatterDiscoverySchema( + platform=Platform.SWITCH, + entity_description=MatterNumericSwitchEntityDescription( + key="EveChildLock", + entity_category=EntityCategory.CONFIG, + translation_key="child_lock", + ), + entity_class=MatterNumericSwitch, + required_attributes=(clusters.EveCluster.Attributes.ChildLock,), + ), ] diff --git a/tests/components/matter/snapshots/test_switch.ambr b/tests/components/matter/snapshots/test_switch.ambr index cf1d1aef783..c5d3446c64a 100644 --- a/tests/components/matter/snapshots/test_switch.ambr +++ b/tests/components/matter/snapshots/test_switch.ambr @@ -1,4 +1,54 @@ # serializer version: 1 +# name: test_switches[eve_energy_20ecn4101][switch.eve_energy_20ecn4101_child_lock_top-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.eve_energy_20ecn4101_child_lock_top', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Child lock (top)', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Child lock (top)', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '00000000000004D2-00000000000000C7-MatterNodeDevice-1-EveChildLock-319486977-319422481', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[eve_energy_20ecn4101][switch.eve_energy_20ecn4101_child_lock_top-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Eve Energy 20ECN4101 Child lock (top)', + }), + 'context': , + 'entity_id': 'switch.eve_energy_20ecn4101_child_lock_top', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[eve_energy_20ecn4101][switch.eve_energy_20ecn4101_switch_bottom-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -152,6 +202,56 @@ 'state': 'off', }) # --- +# name: test_switches[eve_energy_plug][switch.eve_energy_plug_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.eve_energy_plug_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Child lock', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Child lock', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '00000000000004D2-000000000000003D-MatterNodeDevice-1-EveChildLock-319486977-319422481', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[eve_energy_plug][switch.eve_energy_plug_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Eve Energy Plug Child lock', + }), + 'context': , + 'entity_id': 'switch.eve_energy_plug_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[eve_energy_plug_patched][switch.eve_energy_plug_patched-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -203,6 +303,106 @@ 'state': 'off', }) # --- +# name: test_switches[eve_energy_plug_patched][switch.eve_energy_plug_patched_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.eve_energy_plug_patched_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Child lock', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Child lock', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '00000000000004D2-00000000000000B7-MatterNodeDevice-1-EveChildLock-319486977-319422481', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[eve_energy_plug_patched][switch.eve_energy_plug_patched_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Eve Energy Plug Patched Child lock', + }), + 'context': , + 'entity_id': 'switch.eve_energy_plug_patched_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- +# name: test_switches[eve_shutter][switch.eve_shutter_switch_20eci1701_child_lock-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'switch', + 'entity_category': , + 'entity_id': 'switch.eve_shutter_switch_20eci1701_child_lock', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'Child lock', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'Child lock', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'child_lock', + 'unique_id': '00000000000004D2-0000000000000094-MatterNodeDevice-1-EveChildLock-319486977-319422481', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[eve_shutter][switch.eve_shutter_switch_20eci1701_child_lock-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Eve Shutter Switch 20ECI1701 Child lock', + }), + 'context': , + 'entity_id': 'switch.eve_shutter_switch_20eci1701_child_lock', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[eve_thermo_v4][switch.eve_thermo_20ebp1701_child_lock-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ diff --git a/tests/components/matter/test_switch.py b/tests/components/matter/test_switch.py index e89f3fc7fe6..c4578272b7d 100644 --- a/tests/components/matter/test_switch.py +++ b/tests/components/matter/test_switch.py @@ -285,3 +285,55 @@ async def test_speaker_mute_uses_onoff_commands( state = hass.states.get("switch.mock_speaker_mute") assert state assert state.state == "off" + + +@pytest.mark.parametrize("node_fixture", ["eve_energy_plug"]) +async def test_eve_child_lock( + hass: HomeAssistant, + matter_client: MagicMock, + matter_node: MatterNode, +) -> None: + """Test the Eve child lock switch entity.""" + state = hass.states.get("switch.eve_energy_plug_child_lock") + assert state + assert state.state == "off" + # test attribute changes + set_node_attribute(matter_node, 1, 319486977, 319422481, True) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("switch.eve_energy_plug_child_lock") + assert state.state == "on" + set_node_attribute(matter_node, 1, 319486977, 319422481, False) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("switch.eve_energy_plug_child_lock") + assert state.state == "off" + # test switch service + await hass.services.async_call( + "switch", + "turn_on", + {"entity_id": "switch.eve_energy_plug_child_lock"}, + blocking=True, + ) + assert matter_client.write_attribute.call_count == 1 + assert matter_client.write_attribute.call_args_list[0] == call( + node_id=matter_node.node_id, + attribute_path=create_attribute_path_from_attribute( + endpoint_id=1, + attribute=clusters.EveCluster.Attributes.ChildLock, + ), + value=True, + ) + await hass.services.async_call( + "switch", + "turn_off", + {"entity_id": "switch.eve_energy_plug_child_lock"}, + blocking=True, + ) + assert matter_client.write_attribute.call_count == 2 + assert matter_client.write_attribute.call_args_list[1] == call( + node_id=matter_node.node_id, + attribute_path=create_attribute_path_from_attribute( + endpoint_id=1, + attribute=clusters.EveCluster.Attributes.ChildLock, + ), + value=False, + )