Fix several issues with the Matter Generic Switch Cluster (#122191)

This commit is contained in:
Marcel van der Veldt 2024-07-23 10:10:32 +02:00 committed by GitHub
parent d3d91a83e5
commit 632dec614a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 92 additions and 40 deletions

View File

@ -58,17 +58,32 @@ class MatterEventEntity(MatterEntity, EventEntity):
self.get_matter_attribute_value(clusters.Switch.Attributes.FeatureMap)
)
if feature_map & SwitchFeature.kLatchingSwitch:
# a latching switch only supports switch_latched event
event_types.append("switch_latched")
if feature_map & SwitchFeature.kMomentarySwitch:
elif feature_map & SwitchFeature.kMomentarySwitchMultiPress:
# Momentary switch with multi press support
# NOTE: We ignore 'multi press ongoing' as it doesn't make a lot
# of sense and many devices do not support it.
# Instead we we report on the 'multi press complete' event with the number
# of presses.
max_presses_supported = self.get_matter_attribute_value(
clusters.Switch.Attributes.MultiPressMax
)
max_presses_supported = min(max_presses_supported or 1, 8)
for i in range(max_presses_supported):
event_types.append(f"multi_press_{i + 1}") # noqa: PERF401
elif feature_map & SwitchFeature.kMomentarySwitch:
# momentary switch without multi press support
event_types.append("initial_press")
if feature_map & SwitchFeature.kMomentarySwitchRelease:
event_types.append("short_release")
if feature_map & SwitchFeature.kMomentarySwitchRelease:
# momentary switch without multi press support can optionally support release
event_types.append("short_release")
# a momentary switch can optionally support long press
if feature_map & SwitchFeature.kMomentarySwitchLongPress:
event_types.append("long_press")
event_types.append("long_release")
if feature_map & SwitchFeature.kMomentarySwitchMultiPress:
event_types.append("multi_press_ongoing")
event_types.append("multi_press_complete")
self._attr_event_types = event_types
async def async_added_to_hass(self) -> None:
@ -96,7 +111,20 @@ class MatterEventEntity(MatterEntity, EventEntity):
"""Call on NodeEvent."""
if data.endpoint_id != self._endpoint.endpoint_id:
return
self._trigger_event(EVENT_TYPES_MAP[data.event_id], data.data)
if data.event_id == clusters.Switch.Events.MultiPressComplete.event_id:
# multi press event
presses = (data.data or {}).get("totalNumberOfPressesCounted", 1)
event_type = f"multi_press_{presses}"
else:
event_type = EVENT_TYPES_MAP[data.event_id]
if event_type not in self.event_types:
# this should not happen, but guard for bad things
# some remotes send events that they do not report as supported (sigh...)
return
# pass the rest of the data as-is (such as the advanced Position data)
self._trigger_event(event_type, data.data)
self.async_write_ha_state()
@ -119,5 +147,6 @@ DISCOVERY_SCHEMAS = [
clusters.Switch.Attributes.NumberOfPositions,
clusters.FixedLabel.Attributes.LabelList,
),
allow_multi=True, # also used for sensor (current position) entity
),
]

View File

@ -448,4 +448,18 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterSensor,
required_attributes=(NeoCluster.Attributes.Current,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SwitchCurrentPosition",
native_unit_of_measurement=None,
device_class=None,
state_class=SensorStateClass.MEASUREMENT,
translation_key="switch_current_position",
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterSensor,
required_attributes=(clusters.Switch.Attributes.CurrentPosition,),
allow_multi=True, # also used for event entity
),
]

View File

@ -73,12 +73,18 @@
"event_type": {
"state": {
"switch_latched": "Switch latched",
"initial_press": "Initial press",
"long_press": "Long press",
"short_release": "Short release",
"long_release": "Long release",
"multi_press_ongoing": "Multi press ongoing",
"multi_press_complete": "Multi press complete"
"initial_press": "Pressed",
"long_press": "Hold down",
"short_release": "Released after being pressed",
"long_release": "Released after being held down",
"multi_press_1": "Pressed once",
"multi_press_2": "Pressed twice",
"multi_press_3": "Pressed 3 times",
"multi_press_4": "Pressed 4 times",
"multi_press_5": "Pressed 5 times",
"multi_press_6": "Pressed 6 times",
"multi_press_7": "Pressed 7 times",
"multi_press_8": "Pressed 8 times"
}
}
}
@ -151,6 +157,9 @@
},
"hepa_filter_condition": {
"name": "Hepa filter condition"
},
"switch_current_position": {
"name": "Current switch position"
}
},
"switch": {

View File

@ -72,8 +72,9 @@
"1/59/0": 2,
"1/59/65533": 1,
"1/59/1": 0,
"1/59/2": 2,
"1/59/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/59/65532": 14,
"1/59/65532": 30,
"1/59/65528": [],
"1/64/0": [
{
@ -101,8 +102,9 @@
"2/59/0": 2,
"2/59/65533": 1,
"2/59/1": 0,
"2/59/2": 2,
"2/59/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"2/59/65532": 14,
"2/59/65532": 30,
"2/59/65528": [],
"2/64/0": [
{

View File

@ -73,7 +73,7 @@
"1/59/65533": 1,
"1/59/1": 0,
"1/59/65531": [0, 1, 65528, 65529, 65531, 65532, 65533],
"1/59/65532": 30,
"1/59/65532": 14,
"1/59/65528": []
},
"available": true,

View File

@ -50,8 +50,6 @@ async def test_generic_switch_node(
"short_release",
"long_press",
"long_release",
"multi_press_ongoing",
"multi_press_complete",
]
# trigger firing a new event from the device
await trigger_subscription_callback(
@ -72,26 +70,6 @@ async def test_generic_switch_node(
)
state = hass.states.get("event.mock_generic_switch_button")
assert state.attributes[ATTR_EVENT_TYPE] == "initial_press"
# trigger firing a multi press event
await trigger_subscription_callback(
hass,
matter_client,
EventType.NODE_EVENT,
MatterNodeEvent(
node_id=generic_switch_node.node_id,
endpoint_id=1,
cluster_id=59,
event_id=5,
event_number=0,
priority=1,
timestamp=0,
timestamp_type=0,
data={"NewPosition": 3},
),
)
state = hass.states.get("event.mock_generic_switch_button")
assert state.attributes[ATTR_EVENT_TYPE] == "multi_press_ongoing"
assert state.attributes["NewPosition"] == 3
# This tests needs to be adjusted to remove lingering tasks
@ -109,8 +87,8 @@ async def test_generic_switch_multi_node(
assert state_button_1.name == "Mock Generic Switch Button (1)"
# check event_types from featuremap 14
assert state_button_1.attributes[ATTR_EVENT_TYPES] == [
"initial_press",
"short_release",
"multi_press_1",
"multi_press_2",
"long_press",
"long_release",
]
@ -120,3 +98,23 @@ async def test_generic_switch_multi_node(
assert state_button_1.state == "unknown"
# name should be 'DeviceName Fancy Button' due to the label set to 'Fancy Button'
assert state_button_1.name == "Mock Generic Switch Fancy Button"
# trigger firing a multi press event
await trigger_subscription_callback(
hass,
matter_client,
EventType.NODE_EVENT,
MatterNodeEvent(
node_id=generic_switch_multi_node.node_id,
endpoint_id=1,
cluster_id=59,
event_id=6,
event_number=0,
priority=1,
timestamp=0,
timestamp_type=0,
data={"totalNumberOfPressesCounted": 2},
),
)
state = hass.states.get("event.mock_generic_switch_button_1")
assert state.attributes[ATTR_EVENT_TYPE] == "multi_press_2"