diff --git a/homeassistant/components/matter/event.py b/homeassistant/components/matter/event.py index dcb67d50523..885ba83ce07 100644 --- a/homeassistant/components/matter/event.py +++ b/homeassistant/components/matter/event.py @@ -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 ), ] diff --git a/homeassistant/components/matter/sensor.py b/homeassistant/components/matter/sensor.py index 9c19be7ee08..c3ab18072f0 100644 --- a/homeassistant/components/matter/sensor.py +++ b/homeassistant/components/matter/sensor.py @@ -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 + ), ] diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index 0a823d5aa80..3c50ccbaa21 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -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": { diff --git a/tests/components/matter/fixtures/nodes/generic-switch-multi.json b/tests/components/matter/fixtures/nodes/generic-switch-multi.json index f564e91a1ce..8923198c31e 100644 --- a/tests/components/matter/fixtures/nodes/generic-switch-multi.json +++ b/tests/components/matter/fixtures/nodes/generic-switch-multi.json @@ -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": [ { diff --git a/tests/components/matter/fixtures/nodes/generic-switch.json b/tests/components/matter/fixtures/nodes/generic-switch.json index 80773915748..9b334c5fb54 100644 --- a/tests/components/matter/fixtures/nodes/generic-switch.json +++ b/tests/components/matter/fixtures/nodes/generic-switch.json @@ -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, diff --git a/tests/components/matter/test_event.py b/tests/components/matter/test_event.py index a7bd7c91f7b..183867642f5 100644 --- a/tests/components/matter/test_event.py +++ b/tests/components/matter/test_event.py @@ -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"