Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions homeassistant/components/matter/lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ class MatterLock(MatterEntity, LockEntity):

features: int | None = None

@property
def code_format(self) -> str | None:
"""Regex for code format or None if no code is required."""
if self.get_matter_attribute_value(
clusters.DoorLock.Attributes.RequirePINforRemoteOperation
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this is dynamic value (e.g. configurable on the lock) or just static ?
In case its dynamic, these attributes should be added to the "optional_attributes" property of the Lock platform discovery schema to setup the subscription for state updates. If its just static the value will come (once) with the interview of the device.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specs are a bit unclear about it but it seems to be a static device-specific value

):
min_pincode_length = int(
self.get_matter_attribute_value(
clusters.DoorLock.Attributes.MinPINCodeLength
)
)
max_pincode_length = int(
self.get_matter_attribute_value(
clusters.DoorLock.Attributes.MaxPINCodeLength
)
)
return f"^\\d{{{min_pincode_length},{max_pincode_length}}}$"

return None

@property
def supports_door_position_sensor(self) -> bool:
"""Return True if the lock supports door position sensor."""
Expand Down
45 changes: 44 additions & 1 deletion tests/components/matter/test_door_lock.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
STATE_UNLOCKED,
STATE_UNLOCKING,
)
from homeassistant.const import STATE_UNKNOWN
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant

from .common import (
Expand Down Expand Up @@ -104,3 +104,46 @@ async def test_lock(
state = hass.states.get("lock.mock_door_lock")
assert state
assert state.state == STATE_UNKNOWN


# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_lock_requires_pin(
hass: HomeAssistant,
matter_client: MagicMock,
door_lock: MatterNode,
) -> None:
"""Test door lock with PINCode."""

code = "1234567"

# set RequirePINforRemoteOperation
set_node_attribute(door_lock, 1, 257, 51, True)
# set door state to unlocked
set_node_attribute(door_lock, 1, 257, 0, 2)

with pytest.raises(ValueError):
# Lock door using invalid code format
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
"lock",
"lock",
{"entity_id": "lock.mock_door_lock", ATTR_CODE: "1234"},
blocking=True,
)

# Lock door using valid code
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
"lock",
"lock",
{"entity_id": "lock.mock_door_lock", ATTR_CODE: code},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=door_lock.node_id,
endpoint_id=1,
command=clusters.DoorLock.Commands.LockDoor(code.encode()),
timed_request_timeout_ms=1000,
)