Add Matter lock event changed_by (#149861)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
jvmahon
2025-08-27 11:48:55 -04:00
committed by GitHub
parent 2ef335f403
commit cd5bfd6baf
3 changed files with 87 additions and 1 deletions

View File

@@ -6,6 +6,7 @@ import asyncio
from typing import Any
from chip.clusters import Objects as clusters
from matter_server.common.models import EventType, MatterNodeEvent
from homeassistant.components.lock import (
LockEntity,
@@ -22,6 +23,22 @@ from .entity import MatterEntity
from .helpers import get_matter
from .models import MatterDiscoverySchema
DOOR_LOCK_OPERATION_SOURCE = {
# mapping from operation source id's to textual representation
0: "Unspecified",
1: "Manual", # [Optional]
2: "Proprietary Remote", # [Optional]
3: "Keypad", # [Optional]
4: "Auto", # [Optional]
5: "Button", # [Optional]
6: "Schedule", # [HDSCH]
7: "Remote", # [M]
8: "RFID", # [RID]
9: "Biometric", # [USR]
10: "Aliro", # [Aliro]
}
DoorLockFeature = clusters.DoorLock.Bitmaps.Feature
@@ -41,6 +58,52 @@ class MatterLock(MatterEntity, LockEntity):
_feature_map: int | None = None
_optimistic_timer: asyncio.TimerHandle | None = None
_platform_translation_key = "lock"
_attr_changed_by = "Unknown"
async def async_added_to_hass(self) -> None:
"""Subscribe to events."""
await super().async_added_to_hass()
# subscribe to NodeEvent events
self._unsubscribes.append(
self.matter_client.subscribe_events(
callback=self._on_matter_node_event,
event_filter=EventType.NODE_EVENT,
node_filter=self._endpoint.node.node_id,
)
)
@callback
def _on_matter_node_event(
self,
event: EventType,
node_event: MatterNodeEvent,
) -> None:
"""Call on NodeEvent."""
if (node_event.endpoint_id != self._endpoint.endpoint_id) or (
node_event.cluster_id != clusters.DoorLock.id
):
return
LOGGER.debug(
"Received node_event: event type %s, event id %s for %s with data %s",
event,
node_event.event_id,
self.entity_id,
node_event.data,
)
# handle the DoorLock events
node_event_data: dict[str, int] = node_event.data or {}
match node_event.event_id:
case (
clusters.DoorLock.Events.LockOperation.event_id
): # Lock cluster event 2
# update the changed_by attribute to indicate lock operation source
operation_source: int = node_event_data.get("operationSource", -1)
self._attr_changed_by = DOOR_LOCK_OPERATION_SOURCE.get(
operation_source, "Unknown"
)
self.async_write_ha_state()
@property
def code_format(self) -> str | None:

View File

@@ -37,6 +37,7 @@
# name: test_locks[door_lock][lock.mock_door_lock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'changed_by': 'Unknown',
'friendly_name': 'Mock Door Lock',
'supported_features': <LockEntityFeature: 0>,
}),
@@ -86,6 +87,7 @@
# name: test_locks[door_lock_with_unbolt][lock.mock_door_lock-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'changed_by': 'Unknown',
'friendly_name': 'Mock Door Lock',
'supported_features': <LockEntityFeature: 1>,
}),

View File

@@ -4,10 +4,11 @@ from unittest.mock import MagicMock, call
from chip.clusters import Objects as clusters
from matter_server.client.models.node import MatterNode
from matter_server.common.models import EventType, MatterNodeEvent
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.lock import LockEntityFeature, LockState
from homeassistant.components.lock import ATTR_CHANGED_BY, LockEntityFeature, LockState
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
@@ -112,6 +113,26 @@ async def test_lock(
state = hass.states.get("lock.mock_door_lock")
assert state.attributes["supported_features"] & LockEntityFeature.OPEN
# test handling of a node LockOperation event
await trigger_subscription_callback(
hass,
matter_client,
EventType.NODE_EVENT,
MatterNodeEvent(
node_id=matter_node.node_id,
endpoint_id=1,
cluster_id=257,
event_id=2,
event_number=0,
priority=1,
timestamp=0,
timestamp_type=0,
data={"operationSource": 3},
),
)
state = hass.states.get("lock.mock_door_lock")
assert state.attributes[ATTR_CHANGED_BY] == "Keypad"
@pytest.mark.parametrize("node_fixture", ["door_lock"])
async def test_lock_requires_pin(