From 0a3ce8692c4bbcbe0258ea8184aad0f84025ddb1 Mon Sep 17 00:00:00 2001 From: Mark Spatz Date: Sat, 26 Sep 2026 14:15:01 -0400 Subject: [PATCH] Add Matter door lock one-touch locking switch (#183153) Co-authored-by: Claude Opus 5.5 --- homeassistant/components/matter/icons.json | 3 + homeassistant/components/matter/strings.json | 3 + homeassistant/components/matter/switch.py | 10 + .../matter/snapshots/test_switch.ambr | 200 ++++++++++++++++++ tests/components/matter/test_switch.py | 43 ++++ 5 files changed, 259 insertions(+) diff --git a/homeassistant/components/matter/icons.json b/homeassistant/components/matter/icons.json index b8a4d041baa6..2e9ff91c5160 100644 --- a/homeassistant/components/matter/icons.json +++ b/homeassistant/components/matter/icons.json @@ -167,6 +167,9 @@ "evse_charging_switch": { "default": "mdi:ev-station" }, + "one_touch_locking": { + "default": "mdi:gesture-tap-button" + }, "privacy_mode_button": { "default": "mdi:shield-lock" }, diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index 772ad9fc9e69..66caa979a072 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -702,6 +702,9 @@ "evse_charging_switch": { "name": "Enable charging" }, + "one_touch_locking": { + "name": "One-touch locking" + }, "power": { "name": "Power" }, diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 2237cecd264c..0252efde5fe1 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -417,6 +417,16 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterNumericSwitch, required_attributes=(clusters.DoorLock.Attributes.EnablePrivacyModeButton,), ), + MatterDiscoverySchema( + platform=Platform.SWITCH, + entity_description=MatterNumericSwitchEntityDescription( + key="DoorLockEnableOneTouchLocking", + entity_category=EntityCategory.CONFIG, + translation_key="one_touch_locking", + ), + entity_class=MatterNumericSwitch, + required_attributes=(clusters.DoorLock.Attributes.EnableOneTouchLocking,), + ), MatterDiscoverySchema( platform=Platform.SWITCH, entity_description=MatterGenericCommandSwitchEntityDescription( diff --git a/tests/components/matter/snapshots/test_switch.ambr b/tests/components/matter/snapshots/test_switch.ambr index 8c51fb7c5538..46f539718a24 100644 --- a/tests/components/matter/snapshots/test_switch.ambr +++ b/tests/components/matter/snapshots/test_switch.ambr @@ -99,6 +99,56 @@ 'state': 'on', }) # --- +# name: test_switches[aqara_u200][switch.aqara_smart_lock_u200_one_touch_locking-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.aqara_smart_lock_u200_one_touch_locking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'One-touch locking', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'One-touch locking', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'one_touch_locking', + 'unique_id': '00000000000004D2-0000000000000014-MatterNodeDevice-1-DoorLockEnableOneTouchLocking-257-41', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[aqara_u200][switch.aqara_smart_lock_u200_one_touch_locking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Aqara Smart Lock U200 One-touch locking', + }), + 'context': , + 'entity_id': 'switch.aqara_smart_lock_u200_one_touch_locking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[eve_energy_20ecn4101][switch.eve_energy_20ecn4101_child_lock_top-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -908,6 +958,56 @@ 'state': 'off', }) # --- +# name: test_switches[mock_door_lock][switch.mock_door_lock_one_touch_locking-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.mock_door_lock_one_touch_locking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'One-touch locking', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'One-touch locking', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'one_touch_locking', + 'unique_id': '00000000000004D2-0000000000000010-MatterNodeDevice-1-DoorLockEnableOneTouchLocking-257-41', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[mock_door_lock][switch.mock_door_lock_one_touch_locking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Door Lock One-touch locking', + }), + 'context': , + 'entity_id': 'switch.mock_door_lock_one_touch_locking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[mock_door_lock][switch.mock_door_lock_privacy_mode_button-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -1009,6 +1109,56 @@ 'state': 'off', }) # --- +# name: test_switches[mock_door_lock_with_unbolt][switch.mock_door_lock_with_unbolt_one_touch_locking-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.mock_door_lock_with_unbolt_one_touch_locking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'One-touch locking', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'One-touch locking', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'one_touch_locking', + 'unique_id': '00000000000004D2-000000000000000F-MatterNodeDevice-1-DoorLockEnableOneTouchLocking-257-41', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[mock_door_lock_with_unbolt][switch.mock_door_lock_with_unbolt_one_touch_locking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Door Lock with unbolt One-touch locking', + }), + 'context': , + 'entity_id': 'switch.mock_door_lock_with_unbolt_one_touch_locking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[mock_door_lock_with_unbolt][switch.mock_door_lock_with_unbolt_privacy_mode_button-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ @@ -1110,6 +1260,56 @@ 'state': 'off', }) # --- +# name: test_switches[mock_lock][switch.mock_lock_one_touch_locking-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.mock_lock_one_touch_locking', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': 'One-touch locking', + 'options': dict({ + }), + 'original_device_class': None, + 'original_icon': None, + 'original_name': 'One-touch locking', + 'platform': 'matter', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'one_touch_locking', + 'unique_id': '00000000000004D2-0000000000000097-MatterNodeDevice-1-DoorLockEnableOneTouchLocking-257-41', + 'unit_of_measurement': None, + }) +# --- +# name: test_switches[mock_lock][switch.mock_lock_one_touch_locking-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + : 'Mock Lock One-touch locking', + }), + 'context': , + 'entity_id': 'switch.mock_lock_one_touch_locking', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'off', + }) +# --- # name: test_switches[mock_lock][switch.mock_lock_privacy_mode_button-entry] EntityRegistryEntrySnapshot({ 'aliases': list([ diff --git a/tests/components/matter/test_switch.py b/tests/components/matter/test_switch.py index ed224d10b123..cdc7130485f4 100644 --- a/tests/components/matter/test_switch.py +++ b/tests/components/matter/test_switch.py @@ -452,3 +452,46 @@ async def test_eve_child_lock( ), value=False, ) + + +@pytest.mark.parametrize("node_fixture", ["mock_door_lock"]) +async def test_door_lock_one_touch_locking( + hass: HomeAssistant, + matter_client: MagicMock, + matter_node: MatterNode, +) -> None: + """Test the DoorLock one-touch locking config switch.""" + entity_id = "switch.mock_door_lock_one_touch_locking" + state = hass.states.get(entity_id) + assert state + assert state.state == "off" + + set_node_attribute(matter_node, 1, 257, 41, True) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == "on" + + set_node_attribute(matter_node, 1, 257, 41, False) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state + assert state.state == "off" + + attribute_path = create_attribute_path_from_attribute( + endpoint_id=1, attribute=clusters.DoorLock.Attributes.EnableOneTouchLocking + ) + await hass.services.async_call( + "switch", "turn_on", {"entity_id": entity_id}, blocking=True + ) + assert matter_client.write_attribute.call_args_list == [ + call(node_id=matter_node.node_id, attribute_path=attribute_path, value=True) + ] + + await hass.services.async_call( + "switch", "turn_off", {"entity_id": entity_id}, blocking=True + ) + assert matter_client.write_attribute.call_args_list[1] == call( + node_id=matter_node.node_id, attribute_path=attribute_path, value=False + ) + assert matter_client.write_attribute.call_count == 2