Fix Matter 1.2 locks with specific unlatch/unbolt support (#103275)

This commit is contained in:
Marcel van der Veldt 2023-11-03 12:09:31 +01:00 committed by GitHub
parent ac1dc4eeea
commit 680162d494
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 605 additions and 22 deletions

View File

@ -1,12 +1,15 @@
"""Matter lock.""" """Matter lock."""
from __future__ import annotations from __future__ import annotations
from enum import IntFlag
from typing import Any from typing import Any
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from homeassistant.components.lock import LockEntity, LockEntityDescription from homeassistant.components.lock import (
LockEntity,
LockEntityDescription,
LockEntityFeature,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_CODE, Platform from homeassistant.const import ATTR_CODE, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -17,6 +20,8 @@ from .entity import MatterEntity
from .helpers import get_matter from .helpers import get_matter
from .models import MatterDiscoverySchema from .models import MatterDiscoverySchema
DoorLockFeature = clusters.DoorLock.Bitmaps.Feature
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -61,6 +66,14 @@ class MatterLock(MatterEntity, LockEntity):
return bool(self.features & DoorLockFeature.kDoorPositionSensor) return bool(self.features & DoorLockFeature.kDoorPositionSensor)
@property
def supports_unbolt(self) -> bool:
"""Return True if the lock supports unbolt."""
if self.features is None:
return False
return bool(self.features & DoorLockFeature.kUnbolt)
async def send_device_command( async def send_device_command(
self, self,
command: clusters.ClusterCommand, command: clusters.ClusterCommand,
@ -92,6 +105,25 @@ class MatterLock(MatterEntity, LockEntity):
self._lock_option_default_code, self._lock_option_default_code,
) )
code_bytes = code.encode() if code else None code_bytes = code.encode() if code else None
if self.supports_unbolt:
# if the lock reports it has separate unbolt support,
# the unlock command should unbolt only on the unlock command
# and unlatch on the HA 'open' command.
await self.send_device_command(
command=clusters.DoorLock.Commands.UnboltDoor(code_bytes)
)
else:
await self.send_device_command(
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
)
async def async_open(self, **kwargs: Any) -> None:
"""Open the door latch."""
code: str = kwargs.get(
ATTR_CODE,
self._lock_option_default_code,
)
code_bytes = code.encode() if code else None
await self.send_device_command( await self.send_device_command(
command=clusters.DoorLock.Commands.UnlockDoor(code_bytes) command=clusters.DoorLock.Commands.UnlockDoor(code_bytes)
) )
@ -104,6 +136,8 @@ class MatterLock(MatterEntity, LockEntity):
self.features = int( self.features = int(
self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap) self.get_matter_attribute_value(clusters.DoorLock.Attributes.FeatureMap)
) )
if self.supports_unbolt:
self._attr_supported_features = LockEntityFeature.OPEN
lock_state = self.get_matter_attribute_value( lock_state = self.get_matter_attribute_value(
clusters.DoorLock.Attributes.LockState clusters.DoorLock.Attributes.LockState
@ -144,26 +178,6 @@ class MatterLock(MatterEntity, LockEntity):
) )
class DoorLockFeature(IntFlag):
"""Temp enum that represents the features of a door lock.
Should be replaced by the library provided one once that is released.
"""
kPinCredential = 0x1 # noqa: N815
kRfidCredential = 0x2 # noqa: N815
kFingerCredentials = 0x4 # noqa: N815
kLogging = 0x8 # noqa: N815
kWeekDayAccessSchedules = 0x10 # noqa: N815
kDoorPositionSensor = 0x20 # noqa: N815
kFaceCredentials = 0x40 # noqa: N815
kCredentialsOverTheAirAccess = 0x80 # noqa: N815
kUser = 0x100 # noqa: N815
kNotification = 0x200 # noqa: N815
kYearDayAccessSchedules = 0x400 # noqa: N815
kHolidaySchedules = 0x800 # noqa: N815
DISCOVERY_SCHEMAS = [ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema( MatterDiscoverySchema(
platform=Platform.LOCK, platform=Platform.LOCK,

View File

@ -223,6 +223,16 @@ async def door_lock_fixture(
return await setup_integration_with_node_fixture(hass, "door-lock", matter_client) return await setup_integration_with_node_fixture(hass, "door-lock", matter_client)
@pytest.fixture(name="door_lock_with_unbolt")
async def door_lock_with_unbolt_fixture(
hass: HomeAssistant, matter_client: MagicMock
) -> MatterNode:
"""Fixture for a door lock node with unbolt feature."""
return await setup_integration_with_node_fixture(
hass, "door-lock-with-unbolt", matter_client
)
@pytest.fixture(name="eve_contact_sensor_node") @pytest.fixture(name="eve_contact_sensor_node")
async def eve_contact_sensor_node_fixture( async def eve_contact_sensor_node_fixture(
hass: HomeAssistant, matter_client: MagicMock hass: HomeAssistant, matter_client: MagicMock

View File

@ -0,0 +1,510 @@
{
"node_id": 1,
"date_commissioned": "2023-03-07T09:06:06.059454",
"last_interview": "2023-03-07T09:06:06.059456",
"interview_version": 2,
"available": true,
"attributes": {
"0/29/0": [
{
"deviceType": 22,
"revision": 1
}
],
"0/29/1": [
29, 31, 40, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 60, 62,
63, 64, 65
],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 1,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/31/0": [
{
"privilege": 5,
"authMode": 2,
"subjects": [112233],
"targets": null,
"fabricIndex": 1
}
],
"0/31/1": [],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 1,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
"0/40/0": 1,
"0/40/1": "TEST_VENDOR",
"0/40/2": 65521,
"0/40/3": "Mock Door Lock",
"0/40/4": 32769,
"0/40/5": "Mock Door Lock",
"0/40/6": "**REDACTED**",
"0/40/7": 0,
"0/40/8": "TEST_VERSION",
"0/40/9": 1,
"0/40/10": "1.0",
"0/40/11": "20200101",
"0/40/12": "",
"0/40/13": "",
"0/40/14": "",
"0/40/15": "TEST_SN",
"0/40/16": false,
"0/40/17": true,
"0/40/18": "mock-door-lock",
"0/40/19": {
"caseSessionsPerFabric": 3,
"subscriptionsPerFabric": 65535
},
"0/40/65532": 0,
"0/40/65533": 1,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
65528, 65529, 65530, 65531, 65532, 65533
],
"0/42/0": [],
"0/42/1": true,
"0/42/2": 0,
"0/42/3": 0,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [0],
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/43/0": "en-US",
"0/43/1": [
"en-US",
"de-DE",
"fr-FR",
"en-GB",
"es-ES",
"zh-CN",
"it-IT",
"ja-JP"
],
"0/43/65532": 0,
"0/43/65533": 1,
"0/43/65528": [],
"0/43/65529": [],
"0/43/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533],
"0/44/0": 0,
"0/44/1": 0,
"0/44/2": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7],
"0/44/65532": 0,
"0/44/65533": 1,
"0/44/65528": [],
"0/44/65529": [],
"0/44/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533],
"0/46/0": [0, 1],
"0/46/65532": 0,
"0/46/65533": 1,
"0/46/65528": [],
"0/46/65529": [],
"0/46/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
"0/47/0": 1,
"0/47/1": 0,
"0/47/2": "USB",
"0/47/6": 0,
"0/47/65532": 1,
"0/47/65533": 1,
"0/47/65528": [],
"0/47/65529": [],
"0/47/65531": [0, 1, 2, 6, 65528, 65529, 65530, 65531, 65532, 65533],
"0/48/0": 0,
"0/48/1": {
"failSafeExpiryLengthSeconds": 60,
"maxCumulativeFailsafeSeconds": 900
},
"0/48/2": 0,
"0/48/3": 2,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 1,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
"0/49/0": 1,
"0/49/1": [],
"0/49/2": 10,
"0/49/3": 20,
"0/49/4": true,
"0/49/5": null,
"0/49/6": null,
"0/49/7": null,
"0/49/65532": 2,
"0/49/65533": 1,
"0/49/65528": [1, 5, 7],
"0/49/65529": [0, 3, 4, 6, 8],
"0/49/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/50/65532": 0,
"0/50/65533": 1,
"0/50/65528": [1],
"0/50/65529": [0],
"0/50/65531": [65528, 65529, 65530, 65531, 65532, 65533],
"0/51/0": [
{
"name": "eth0",
"isOperational": true,
"offPremiseServicesReachableIPv4": null,
"offPremiseServicesReachableIPv6": null,
"hardwareAddress": "/mQDt/2Q",
"IPv4Addresses": ["CjwBaQ=="],
"IPv6Addresses": [
"/VqgxiAxQib8ZAP//rf9kA==",
"IAEEcLs7AAb8ZAP//rf9kA==",
"/oAAAAAAAAD8ZAP//rf9kA=="
],
"type": 2
},
{
"name": "lo",
"isOperational": true,
"offPremiseServicesReachableIPv4": null,
"offPremiseServicesReachableIPv6": null,
"hardwareAddress": "AAAAAAAA",
"IPv4Addresses": ["fwAAAQ=="],
"IPv6Addresses": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
"type": 0
}
],
"0/51/1": 1,
"0/51/2": 25,
"0/51/3": 0,
"0/51/4": 0,
"0/51/5": [],
"0/51/6": [],
"0/51/7": [],
"0/51/8": false,
"0/51/65532": 0,
"0/51/65533": 1,
"0/51/65528": [],
"0/51/65529": [0],
"0/51/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/52/0": [
{
"id": 26957,
"name": "26957",
"stackFreeCurrent": null,
"stackFreeMinimum": null,
"stackSize": null
},
{
"id": 26956,
"name": "26956",
"stackFreeCurrent": null,
"stackFreeMinimum": null,
"stackSize": null
},
{
"id": 26955,
"name": "26955",
"stackFreeCurrent": null,
"stackFreeMinimum": null,
"stackSize": null
},
{
"id": 26953,
"name": "26953",
"stackFreeCurrent": null,
"stackFreeMinimum": null,
"stackSize": null
},
{
"id": 26952,
"name": "26952",
"stackFreeCurrent": null,
"stackFreeMinimum": null,
"stackSize": null
}
],
"0/52/1": 351120,
"0/52/2": 529520,
"0/52/3": 529520,
"0/52/65532": 1,
"0/52/65533": 1,
"0/52/65528": [],
"0/52/65529": [0],
"0/52/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/53/0": null,
"0/53/1": null,
"0/53/2": null,
"0/53/3": null,
"0/53/4": null,
"0/53/5": null,
"0/53/6": 0,
"0/53/7": [],
"0/53/8": [],
"0/53/9": null,
"0/53/10": null,
"0/53/11": null,
"0/53/12": null,
"0/53/13": null,
"0/53/14": 0,
"0/53/15": 0,
"0/53/16": 0,
"0/53/17": 0,
"0/53/18": 0,
"0/53/19": 0,
"0/53/20": 0,
"0/53/21": 0,
"0/53/22": 0,
"0/53/23": 0,
"0/53/24": 0,
"0/53/25": 0,
"0/53/26": 0,
"0/53/27": 0,
"0/53/28": 0,
"0/53/29": 0,
"0/53/30": 0,
"0/53/31": 0,
"0/53/32": 0,
"0/53/33": 0,
"0/53/34": 0,
"0/53/35": 0,
"0/53/36": 0,
"0/53/37": 0,
"0/53/38": 0,
"0/53/39": 0,
"0/53/40": 0,
"0/53/41": 0,
"0/53/42": 0,
"0/53/43": 0,
"0/53/44": 0,
"0/53/45": 0,
"0/53/46": 0,
"0/53/47": 0,
"0/53/48": 0,
"0/53/49": 0,
"0/53/50": 0,
"0/53/51": 0,
"0/53/52": 0,
"0/53/53": 0,
"0/53/54": 0,
"0/53/55": 0,
"0/53/56": null,
"0/53/57": null,
"0/53/58": null,
"0/53/59": null,
"0/53/60": null,
"0/53/61": null,
"0/53/62": [],
"0/53/65532": 15,
"0/53/65533": 1,
"0/53/65528": [],
"0/53/65529": [0],
"0/53/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, 62, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/54/0": null,
"0/54/1": null,
"0/54/2": 3,
"0/54/3": null,
"0/54/4": null,
"0/54/5": null,
"0/54/6": null,
"0/54/7": null,
"0/54/8": null,
"0/54/9": null,
"0/54/10": null,
"0/54/11": null,
"0/54/12": null,
"0/54/65532": 3,
"0/54/65533": 1,
"0/54/65528": [],
"0/54/65529": [0],
"0/54/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 65530, 65531,
65532, 65533
],
"0/55/0": null,
"0/55/1": false,
"0/55/2": 823,
"0/55/3": 969,
"0/55/4": 0,
"0/55/5": 0,
"0/55/6": 0,
"0/55/7": null,
"0/55/8": 25,
"0/55/65532": 3,
"0/55/65533": 1,
"0/55/65528": [],
"0/55/65529": [0],
"0/55/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 0,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 1, 2],
"0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533],
"0/62/0": [
{
"noc": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRARgkBwEkCAEwCUEE55h6CbNLPZH/uM3/rDdA+jeuuD2QSPN8gBeEB0bmGJqWz/gCT4/ySB77rK3XiwVWVAmJhJ/eMcTIA0XXWMqKPDcKNQEoARgkAgE2AwQCBAEYMAQUqnKiC76YFhcTHt4AQ/kAbtrZ2MowBRSL6EWyWm8+uC0Puc2/BncMqYbpmhgwC0AA05Z+y1mcyHUeOFJ5kyDJJMN/oNCwN5h8UpYN/868iuQArr180/fbaN1+db9lab4D2lf0HK7wgHIR3HsOa2w9GA==",
"icac": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE5R1DrUQE/L8tx95WR1g1dZJf4d+6LEB7JAYZN/nw9ZBUg5VOHDrB1xIw5KguYJzt10K+0KqQBBEbuwW+wLLobTcKNQEpARgkAmAwBBSL6EWyWm8+uC0Puc2/BncMqYbpmjAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQHXLE0TgIDW6XOnvtsOJCyKoENts8d4TQWBgTKviv1LF/+MS9eFYi+kO+1Idq5mVgwN+lH7eyecShQR0iqq6WLUY",
"fabricIndex": 1
}
],
"0/62/1": [
{
"rootPublicKey": "BJ/jL2MdDrdq9TahKSa5c/dBc166NRCU0W9l7hK2kcuVtN915DLqiS+RAJ2iPEvWK5FawZHF/QdKLZmTkZHudxY=",
"vendorId": 65521,
"fabricId": 1,
"nodeId": 1,
"label": "",
"fabricIndex": 1
}
],
"0/62/2": 16,
"0/62/3": 1,
"0/62/4": [
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEn+MvYx0Ot2r1NqEpJrlz90FzXro1EJTRb2XuEraRy5W033XkMuqJL5EAnaI8S9YrkVrBkcX9B0otmZORke53FjcKNQEpARgkAmAwBBTNCOnzxc3zr9iFm19YbsW93ltH6jAFFM0I6fPFzfOv2IWbX1huxb3eW0fqGDALQILjpR3BTSHHl6DQtvwzWkjmA+i5jjXdc3qjemFGFjFVAnV6dPLQo7tctC8Y0uL4ZNERga2/NZAt1gRD72S0YR4Y"
],
"0/62/5": 1,
"0/62/65532": 0,
"0/62/65533": 1,
"0/62/65528": [1, 3, 5, 8],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65530, 65531, 65532, 65533],
"0/63/0": [],
"0/63/1": [],
"0/63/2": 4,
"0/63/3": 3,
"0/63/65532": 0,
"0/63/65533": 1,
"0/63/65528": [2, 5],
"0/63/65529": [0, 1, 3, 4],
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/64/0": [
{
"label": "room",
"value": "bedroom 2"
},
{
"label": "orientation",
"value": "North"
},
{
"label": "floor",
"value": "2"
},
{
"label": "direction",
"value": "up"
}
],
"0/64/65532": 0,
"0/64/65533": 1,
"0/64/65528": [],
"0/64/65529": [],
"0/64/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
"0/65/0": [],
"0/65/65532": 0,
"0/65/65533": 1,
"0/65/65528": [],
"0/65/65529": [],
"0/65/65531": [0, 65528, 65529, 65530, 65531, 65532, 65533],
"1/3/0": 0,
"1/3/1": 0,
"1/3/65532": 0,
"1/3/65533": 4,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533],
"1/6/0": false,
"1/6/16384": true,
"1/6/16385": 0,
"1/6/16386": 0,
"1/6/16387": 0,
"1/6/65532": 0,
"1/6/65533": 4,
"1/6/65528": [],
"1/6/65529": [0, 1, 2],
"1/6/65531": [
0, 16384, 16385, 16386, 16387, 65528, 65529, 65530, 65531, 65532, 65533
],
"1/29/0": [
{
"deviceType": 10,
"revision": 1
}
],
"1/29/1": [3, 6, 29, 47, 257],
"1/29/2": [],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 1,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"1/47/0": 1,
"1/47/1": 1,
"1/47/2": "Battery",
"1/47/14": 0,
"1/47/15": false,
"1/47/16": 0,
"1/47/19": "",
"1/47/65532": 10,
"1/47/65533": 1,
"1/47/65528": [],
"1/47/65529": [],
"1/47/65531": [
0, 1, 2, 14, 15, 16, 19, 65528, 65529, 65530, 65531, 65532, 65533
],
"1/257/0": 1,
"1/257/1": 0,
"1/257/2": true,
"1/257/3": 1,
"1/257/17": 10,
"1/257/18": 10,
"1/257/19": 10,
"1/257/20": 10,
"1/257/21": 10,
"1/257/22": 10,
"1/257/23": 8,
"1/257/24": 6,
"1/257/25": 20,
"1/257/26": 10,
"1/257/27": 1,
"1/257/28": 5,
"1/257/33": "en",
"1/257/35": 60,
"1/257/36": 0,
"1/257/37": 0,
"1/257/38": 65526,
"1/257/41": false,
"1/257/43": false,
"1/257/48": 3,
"1/257/49": 10,
"1/257/51": false,
"1/257/65532": 7603,
"1/257/65533": 6,
"1/257/65528": [12, 15, 18, 28, 35, 37],
"1/257/65529": [
0, 1, 3, 11, 12, 13, 14, 15, 16, 17, 18, 19, 26, 27, 29, 34, 36, 38
],
"1/257/65531": [
0, 1, 2, 3, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 33, 35, 36,
37, 38, 41, 43, 48, 49, 51, 65528, 65529, 65530, 65531, 65532, 65533
]
},
"attribute_subscriptions": []
}

View File

@ -10,6 +10,7 @@ from homeassistant.components.lock import (
STATE_LOCKING, STATE_LOCKING,
STATE_UNLOCKED, STATE_UNLOCKED,
STATE_UNLOCKING, STATE_UNLOCKING,
LockEntityFeature,
) )
from homeassistant.const import ATTR_CODE, STATE_UNKNOWN from homeassistant.const import ATTR_CODE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -135,3 +136,51 @@ async def test_lock_requires_pin(
command=clusters.DoorLock.Commands.LockDoor(code.encode()), command=clusters.DoorLock.Commands.LockDoor(code.encode()),
timed_request_timeout_ms=1000, timed_request_timeout_ms=1000,
) )
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_lock_with_unbolt(
hass: HomeAssistant,
matter_client: MagicMock,
door_lock_with_unbolt: MatterNode,
) -> None:
"""Test door lock."""
state = hass.states.get("lock.mock_door_lock")
assert state
assert state.state == STATE_LOCKED
assert state.attributes["supported_features"] & LockEntityFeature.OPEN
# test unlock/unbolt
await hass.services.async_call(
"lock",
"unlock",
{
"entity_id": "lock.mock_door_lock",
},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
# unlock should unbolt on a lock with unbolt feature
assert matter_client.send_device_command.call_args == call(
node_id=door_lock_with_unbolt.node_id,
endpoint_id=1,
command=clusters.DoorLock.Commands.UnboltDoor(),
timed_request_timeout_ms=1000,
)
matter_client.send_device_command.reset_mock()
# test open / unlatch
await hass.services.async_call(
"lock",
"open",
{
"entity_id": "lock.mock_door_lock",
},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
assert matter_client.send_device_command.call_args == call(
node_id=door_lock_with_unbolt.node_id,
endpoint_id=1,
command=clusters.DoorLock.Commands.UnlockDoor(),
timed_request_timeout_ms=1000,
)