Fix operational state and vacuum state for matter vacuum (#147466)

This commit is contained in:
ocrease 2025-06-25 14:23:38 +01:00 committed by GitHub
parent c54ce7eabd
commit 977e8adbfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 49 additions and 23 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations
from dataclasses import dataclass
from dataclasses import dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, cast
@ -74,6 +74,11 @@ OPERATIONAL_STATE_MAP = {
clusters.OperationalState.Enums.OperationalStateEnum.kRunning: "running",
clusters.OperationalState.Enums.OperationalStateEnum.kPaused: "paused",
clusters.OperationalState.Enums.OperationalStateEnum.kError: "error",
}
RVC_OPERATIONAL_STATE_MAP = {
# enum with known Operation state values which we can translate
**OPERATIONAL_STATE_MAP,
clusters.RvcOperationalState.Enums.OperationalStateEnum.kSeekingCharger: "seeking_charger",
clusters.RvcOperationalState.Enums.OperationalStateEnum.kCharging: "charging",
clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked: "docked",
@ -171,6 +176,10 @@ class MatterOperationalStateSensorEntityDescription(MatterSensorEntityDescriptio
state_list_attribute: type[ClusterAttributeDescriptor] = (
clusters.OperationalState.Attributes.OperationalStateList
)
state_attribute: type[ClusterAttributeDescriptor] = (
clusters.OperationalState.Attributes.OperationalState
)
state_map: dict[int, str] = field(default_factory=lambda: OPERATIONAL_STATE_MAP)
class MatterSensor(MatterEntity, SensorEntity):
@ -245,15 +254,15 @@ class MatterOperationalStateSensor(MatterSensor):
for state in operational_state_list:
# prefer translateable (known) state from mapping,
# fallback to the raw state label as given by the device/manufacturer
states_map[state.operationalStateID] = OPERATIONAL_STATE_MAP.get(
state.operationalStateID, slugify(state.operationalStateLabel)
states_map[state.operationalStateID] = (
self.entity_description.state_map.get(
state.operationalStateID, slugify(state.operationalStateLabel)
)
)
self.states_map = states_map
self._attr_options = list(states_map.values())
self._attr_native_value = states_map.get(
self.get_matter_attribute_value(
clusters.OperationalState.Attributes.OperationalState
)
self.get_matter_attribute_value(self.entity_description.state_attribute)
)
@ -999,6 +1008,8 @@ DISCOVERY_SCHEMAS = [
device_class=SensorDeviceClass.ENUM,
translation_key="operational_state",
state_list_attribute=clusters.RvcOperationalState.Attributes.OperationalStateList,
state_attribute=clusters.RvcOperationalState.Attributes.OperationalState,
state_map=RVC_OPERATIONAL_STATE_MAP,
),
entity_class=MatterOperationalStateSensor,
required_attributes=(
@ -1016,6 +1027,7 @@ DISCOVERY_SCHEMAS = [
device_class=SensorDeviceClass.ENUM,
translation_key="operational_state",
state_list_attribute=clusters.OvenCavityOperationalState.Attributes.OperationalStateList,
state_attribute=clusters.OvenCavityOperationalState.Attributes.OperationalState,
),
entity_class=MatterOperationalStateSensor,
required_attributes=(

View File

@ -30,10 +30,10 @@ class OperationalState(IntEnum):
Combination of generic OperationalState and RvcOperationalState.
"""
NO_ERROR = 0x00
UNABLE_TO_START_OR_RESUME = 0x01
UNABLE_TO_COMPLETE_OPERATION = 0x02
COMMAND_INVALID_IN_STATE = 0x03
STOPPED = 0x00
RUNNING = 0x01
PAUSED = 0x02
ERROR = 0x03
SEEKING_CHARGER = 0x40
CHARGING = 0x41
DOCKED = 0x42
@ -95,7 +95,7 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
async def async_pause(self) -> None:
"""Pause the cleaning task."""
await self.send_device_command(clusters.OperationalState.Commands.Pause())
await self.send_device_command(clusters.RvcOperationalState.Commands.Pause())
@callback
def _update_from_device(self) -> None:
@ -120,11 +120,10 @@ class MatterVacuum(MatterEntity, StateVacuumEntity):
state = VacuumActivity.DOCKED
elif operational_state == OperationalState.SEEKING_CHARGER:
state = VacuumActivity.RETURNING
elif operational_state in (
OperationalState.UNABLE_TO_COMPLETE_OPERATION,
OperationalState.UNABLE_TO_START_OR_RESUME,
):
elif operational_state == OperationalState.ERROR:
state = VacuumActivity.ERROR
elif operational_state == OperationalState.PAUSED:
state = VacuumActivity.PAUSED
elif (run_mode := self._supported_run_modes.get(run_mode_raw)) is not None:
tags = {x.value for x in run_mode.modeTags}
if ModeTag.CLEANING in tags:
@ -201,7 +200,7 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterVacuum,
required_attributes=(
clusters.RvcRunMode.Attributes.CurrentMode,
clusters.RvcOperationalState.Attributes.CurrentPhase,
clusters.RvcOperationalState.Attributes.OperationalState,
),
optional_attributes=(
clusters.RvcCleanMode.Attributes.CurrentMode,

View File

@ -3775,7 +3775,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
'state': 'running',
})
# ---
# name: test_sensors[oven][sensor.mock_oven_temperature_2-entry]
@ -6433,7 +6433,7 @@
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'unknown',
'state': 'stopped',
})
# ---
# name: test_sensors[window_covering_full][sensor.mock_full_window_covering_target_opening_position-entry]

View File

@ -93,7 +93,7 @@ async def test_vacuum_actions(
assert matter_client.send_device_command.call_args == call(
node_id=matter_node.node_id,
endpoint_id=1,
command=clusters.OperationalState.Commands.Pause(),
command=clusters.RvcOperationalState.Commands.Pause(),
)
matter_client.send_device_command.reset_mock()
@ -168,19 +168,26 @@ async def test_vacuum_updates(
assert state
assert state.state == "returning"
# confirm state is 'error' by setting the operational state to 0x01
# confirm state is 'idle' by setting the operational state to 0x01 (running) but mode is idle
set_node_attribute(matter_node, 1, 97, 4, 0x01)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "error"
assert state.state == "idle"
# confirm state is 'error' by setting the operational state to 0x02
# confirm state is 'idle' by setting the operational state to 0x01 (running) but mode is cleaning
set_node_attribute(matter_node, 1, 97, 4, 0x01)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "idle"
# confirm state is 'paused' by setting the operational state to 0x02
set_node_attribute(matter_node, 1, 97, 4, 0x02)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "error"
assert state.state == "paused"
# confirm state is 'cleaning' by setting;
# - the operational state to 0x00
@ -211,3 +218,11 @@ async def test_vacuum_updates(
state = hass.states.get(entity_id)
assert state
assert state.state == "unknown"
# confirm state is 'error' by setting;
# - the operational state to 0x03
set_node_attribute(matter_node, 1, 97, 4, 3)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get(entity_id)
assert state
assert state.state == "error"