mirror of
https://github.com/home-assistant/core.git
synced 2025-12-09 17:38:51 +00:00
Compare commits
6 Commits
whirlpool_
...
door_lock_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4195be15bb | ||
|
|
b9f2ff39fb | ||
|
|
26a0eaaf36 | ||
|
|
ae6e79af0d | ||
|
|
415fc33f67 | ||
|
|
cdf07665e8 |
@@ -183,6 +183,47 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
|
||||
self._attr_name = desc
|
||||
|
||||
|
||||
class MatterDoorLockOperatingModeSelectEntity(MatterAttributeSelectEntity):
|
||||
"""Representation of a Door Lock Operating Mode select entity.
|
||||
|
||||
This entity dynamically filters available operating modes based on the device's
|
||||
`SupportedOperatingModes` bitmap attribute. In this bitmap, bit=0 indicates a
|
||||
supported mode and bit=1 indicates unsupported (inverted from typical bitmap conventions).
|
||||
If the bitmap is unavailable, only mandatory modes are included. The mapping from
|
||||
bitmap bits to operating mode values is defined by the Matter specification.
|
||||
"""
|
||||
|
||||
entity_description: MatterMapSelectEntityDescription
|
||||
|
||||
@callback
|
||||
def _update_from_device(self) -> None:
|
||||
"""Update from device."""
|
||||
# Get the bitmap of supported operating modes
|
||||
supported_modes_bitmap = self.get_matter_attribute_value(
|
||||
self.entity_description.list_attribute
|
||||
)
|
||||
|
||||
# Convert bitmap to list of supported mode values
|
||||
# NOTE: The Matter spec inverts the usual meaning: bit=0 means supported,
|
||||
# bit=1 means not supported, undefined bits must be 1. Mandatory modes are
|
||||
# bits 0 (Normal) and 3 (NoRemoteLockUnlock).
|
||||
supported_modes = [
|
||||
bit_position
|
||||
for bit_position in range(5) # Operating modes are bits 0-4
|
||||
if not supported_modes_bitmap & (1 << bit_position)
|
||||
]
|
||||
|
||||
# Map supported mode values to their string representations
|
||||
self._attr_options = [
|
||||
mapped_value
|
||||
for mode_value in supported_modes
|
||||
if (mapped_value := self.entity_description.device_to_ha(mode_value))
|
||||
]
|
||||
|
||||
# Use base implementation to set the current option
|
||||
super()._update_from_device()
|
||||
|
||||
|
||||
class MatterListSelectEntity(MatterEntity, SelectEntity):
|
||||
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
|
||||
|
||||
@@ -594,15 +635,18 @@ DISCOVERY_SCHEMAS = [
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SELECT,
|
||||
entity_description=MatterSelectEntityDescription(
|
||||
entity_description=MatterMapSelectEntityDescription(
|
||||
key="DoorLockOperatingMode",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
translation_key="door_lock_operating_mode",
|
||||
options=list(DOOR_LOCK_OPERATING_MODE_MAP.values()),
|
||||
list_attribute=clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
|
||||
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
|
||||
),
|
||||
entity_class=MatterAttributeSelectEntity,
|
||||
required_attributes=(clusters.DoorLock.Attributes.OperatingMode,),
|
||||
entity_class=MatterDoorLockOperatingModeSelectEntity,
|
||||
required_attributes=(
|
||||
clusters.DoorLock.Attributes.OperatingMode,
|
||||
clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -241,10 +241,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -282,10 +279,7 @@
|
||||
'friendly_name': 'Aqara Smart Lock U200 Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -684,10 +678,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -725,10 +716,7 @@
|
||||
'friendly_name': 'Mock Door Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -869,10 +857,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -910,10 +895,7 @@
|
||||
'friendly_name': 'Mock Door Lock with unbolt Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -2454,10 +2436,7 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -2495,10 +2474,7 @@
|
||||
'friendly_name': 'Mock Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -3657,10 +3633,8 @@
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
@@ -3698,10 +3672,8 @@
|
||||
'friendly_name': 'Secuyou Smart Lock Operating mode',
|
||||
'options': list([
|
||||
'normal',
|
||||
'vacation',
|
||||
'privacy',
|
||||
'no_remote_lock_unlock',
|
||||
'passage',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -8,7 +8,6 @@ from matter_server.common.helpers.util import create_attribute_path_from_attribu
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.matter.select import DOOR_LOCK_OPERATING_MODE_MAP
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
@@ -314,22 +313,28 @@ async def test_door_lock_operating_mode_select(
|
||||
"""Test Door Lock Operating Mode select entity discovery and interaction.
|
||||
|
||||
Verifies:
|
||||
- Options match mapping in DOOR_LOCK_OPERATING_MODE_MAP
|
||||
- Options are filtered based on SupportedOperatingModes bitmap
|
||||
- Attribute updates reflect current option
|
||||
- Selecting an option writes correct enum value
|
||||
"""
|
||||
entity_id = "select.secuyou_smart_lock_operating_mode"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state, "Missing operating mode select entity"
|
||||
assert state.attributes["options"] == list(DOOR_LOCK_OPERATING_MODE_MAP.values())
|
||||
# Initial state should be one of the allowed options
|
||||
assert state.state in state.attributes["options"]
|
||||
# According to the spec, bit=0 means supported and bit=1 means not supported.
|
||||
# The fixture bitmap clears bits 0, 2, and 3, so the supported modes are
|
||||
# Normal, Privacy, and NoRemoteLockUnlock; the other bits are set (not
|
||||
# supported).
|
||||
assert set(state.attributes["options"]) == {
|
||||
"normal",
|
||||
"privacy",
|
||||
"no_remote_lock_unlock",
|
||||
}
|
||||
|
||||
# Dynamically obtain ids instead of hardcoding
|
||||
door_lock_cluster_id = clusters.DoorLock.Attributes.OperatingMode.cluster_id
|
||||
operating_mode_attr_id = clusters.DoorLock.Attributes.OperatingMode.attribute_id
|
||||
|
||||
# Change OperatingMode attribute on the node to 'privacy'
|
||||
# Change OperatingMode attribute on the node to a supported mode ('privacy')
|
||||
set_node_attribute(
|
||||
matter_node,
|
||||
1,
|
||||
@@ -341,12 +346,12 @@ async def test_door_lock_operating_mode_select(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state.state == "privacy"
|
||||
|
||||
# Select another option (vacation) via service to validate mapping
|
||||
# Select another supported option (NoRemoteLockUnlock) via service to validate mapping
|
||||
matter_client.write_attribute.reset_mock()
|
||||
await hass.services.async_call(
|
||||
"select",
|
||||
"select_option",
|
||||
{"entity_id": entity_id, "option": "vacation"},
|
||||
{"entity_id": entity_id, "option": "no_remote_lock_unlock"},
|
||||
blocking=True,
|
||||
)
|
||||
assert matter_client.write_attribute.call_count == 1
|
||||
@@ -356,5 +361,5 @@ async def test_door_lock_operating_mode_select(
|
||||
endpoint_id=1,
|
||||
attribute=clusters.DoorLock.Attributes.OperatingMode,
|
||||
),
|
||||
value=clusters.DoorLock.Enums.OperatingModeEnum.kVacation,
|
||||
value=clusters.DoorLock.Enums.OperatingModeEnum.kNoRemoteLockUnlock,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user