1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-24 21:06:19 +00:00

Add Matter OperationalError sensor (#151991)

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
This commit is contained in:
Ludovic BOUÉ
2025-10-24 21:44:16 +02:00
committed by GitHub
parent 750a7c9797
commit 447fb68085
5 changed files with 590 additions and 0 deletions

View File

@@ -123,6 +123,9 @@
"evse_fault_state": {
"default": "mdi:ev-station"
},
"operational_error": {
"default": "mdi:alert-circle"
},
"pump_control_mode": {
"default": "mdi:pipe-wrench"
},

View File

@@ -86,6 +86,14 @@ OPERATIONAL_STATE_MAP = {
clusters.OperationalState.Enums.OperationalStateEnum.kError: "error",
}
OPERATIONAL_STATE_ERROR_MAP = {
# enum with known Error state values which we can translate
clusters.OperationalState.Enums.ErrorStateEnum.kNoError: "no_error",
clusters.OperationalState.Enums.ErrorStateEnum.kUnableToStartOrResume: "unable_to_start_or_resume",
clusters.OperationalState.Enums.ErrorStateEnum.kUnableToCompleteOperation: "unable_to_complete_operation",
clusters.OperationalState.Enums.ErrorStateEnum.kCommandInvalidInState: "command_invalid_in_state",
}
RVC_OPERATIONAL_STATE_MAP = {
# enum with known Operation state values which we can translate
**OPERATIONAL_STATE_MAP,
@@ -94,6 +102,29 @@ RVC_OPERATIONAL_STATE_MAP = {
clusters.RvcOperationalState.Enums.OperationalStateEnum.kDocked: "docked",
}
RVC_OPERATIONAL_STATE_ERROR_MAP = {
# enum with known Error state values which we can translate
clusters.RvcOperationalState.Enums.ErrorStateEnum.kNoError: "no_error",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kUnableToStartOrResume: "unable_to_start_or_resume",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kUnableToCompleteOperation: "unable_to_complete_operation",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kCommandInvalidInState: "command_invalid_in_state",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kFailedToFindChargingDock: "failed_to_find_charging_dock",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kStuck: "stuck",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kDustBinMissing: "dust_bin_missing",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kDustBinFull: "dust_bin_full",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankEmpty: "water_tank_empty",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankMissing: "water_tank_missing",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kWaterTankLidOpen: "water_tank_lid_open",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kMopCleaningPadMissing: "mop_cleaning_pad_missing",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kLowBattery: "low_battery",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kCannotReachTargetArea: "cannot_reach_target_area",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kDirtyWaterTankFull: "dirty_water_tank_full",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kDirtyWaterTankMissing: "dirty_water_tank_missing",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kWheelsJammed: "wheels_jammed",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kBrushJammed: "brush_jammed",
clusters.RvcOperationalState.Enums.ErrorStateEnum.kNavigationSensorObscured: "navigation_sensor_obscured",
}
BOOST_STATE_MAP = {
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kInactive: "inactive",
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive: "active",
@@ -1101,6 +1132,19 @@ DISCOVERY_SCHEMAS = [
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="OperationalStateOperationalError",
translation_key="operational_error",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
options=list(OPERATIONAL_STATE_ERROR_MAP.values()),
device_to_ha=lambda x: OPERATIONAL_STATE_ERROR_MAP.get(x.errorStateID),
),
entity_class=MatterSensor,
required_attributes=(clusters.OperationalState.Attributes.OperationalError,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterListSensorEntityDescription(
@@ -1194,6 +1238,19 @@ DISCOVERY_SCHEMAS = [
# don't discover this entry if the supported state list is empty
secondary_value_is_not=[],
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="RvcOperationalStateOperationalError",
translation_key="operational_error",
device_class=SensorDeviceClass.ENUM,
entity_category=EntityCategory.DIAGNOSTIC,
options=list(RVC_OPERATIONAL_STATE_ERROR_MAP.values()),
device_to_ha=lambda x: RVC_OPERATIONAL_STATE_ERROR_MAP.get(x.errorStateID),
),
entity_class=MatterSensor,
required_attributes=(clusters.RvcOperationalState.Attributes.OperationalError,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterOperationalStateSensorEntityDescription(

View File

@@ -441,6 +441,30 @@
"evse_soc": {
"name": "State of charge"
},
"operational_error": {
"name": "Operational error",
"state": {
"no_error": "No error",
"unable_to_start_or_resume": "Unable to start or resume",
"unable_to_complete_operation": "Unable to complete operation",
"command_invalid_in_state": "Command invalid in current state",
"failed_to_find_charging_dock": "Failed to find charging dock",
"stuck": "Stuck",
"dust_bin_missing": "Dust bin missing",
"dust_bin_full": "Dust bin full",
"water_tank_empty": "Water tank empty",
"water_tank_missing": "Water tank missing",
"water_tank_lid_open": "Water tank lid open",
"mop_cleaning_pad_missing": "Mop cleaning pad missing",
"low_battery": "Low battery",
"cannot_reach_target_area": "Cannot reach target area",
"dirty_water_tank_full": "Dirty water tank full",
"dirty_water_tank_missing": "Dirty water tank missing",
"wheels_jammed": "Wheels jammed",
"brush_jammed": "Brush jammed",
"navigation_sensor_obscured": "Navigation sensor obscured"
}
},
"pi_heating_demand": {
"name": "Heating demand"
},

View File

@@ -5118,6 +5118,68 @@
'state': 'pre-soak',
})
# ---
# name: test_sensors[laundry_dryer][sensor.mock_laundrydryer_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.mock_laundrydryer_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-0000000000000008-MatterNodeDevice-1-OperationalStateOperationalError-96-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[laundry_dryer][sensor.mock_laundrydryer_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Mock Laundrydryer Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.mock_laundrydryer_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[laundry_dryer][sensor.mock_laundrydryer_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -5282,6 +5344,68 @@
'state': '2025-01-01T14:00:30+00:00',
})
# ---
# name: test_sensors[microwave_oven][sensor.microwave_oven_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.microwave_oven_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-000000000000009D-MatterNodeDevice-1-OperationalStateOperationalError-96-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[microwave_oven][sensor.microwave_oven_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Microwave Oven Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.microwave_oven_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[microwave_oven][sensor.microwave_oven_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -6300,6 +6424,68 @@
'state': '0.0',
})
# ---
# name: test_sensors[silabs_dishwasher][sensor.dishwasher_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.dishwasher_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-0000000000000036-MatterNodeDevice-1-OperationalStateOperationalError-96-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[silabs_dishwasher][sensor.dishwasher_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Dishwasher Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.dishwasher_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[silabs_dishwasher][sensor.dishwasher_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -7161,6 +7347,68 @@
'state': '0.0',
})
# ---
# name: test_sensors[silabs_laundrywasher][sensor.laundrywasher_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.laundrywasher_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-000000000000001D-MatterNodeDevice-1-OperationalStateOperationalError-96-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[silabs_laundrywasher][sensor.laundrywasher_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'LaundryWasher Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.laundrywasher_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[silabs_laundrywasher][sensor.laundrywasher_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -8199,6 +8447,98 @@
'state': '234.899',
})
# ---
# name: test_sensors[switchbot_k11_plus][sensor.k11_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.k11_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-0000000000000061-MatterNodeDevice-1-RvcOperationalStateOperationalError-97-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[switchbot_k11_plus][sensor.k11_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'K11+ Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.k11_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[switchbot_k11_plus][sensor.k11_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -8593,6 +8933,98 @@
'state': '2025-08-29T21:00:00+00:00',
})
# ---
# name: test_sensors[vacuum_cleaner][sensor.mock_vacuum_operational_error-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'sensor.mock_vacuum_operational_error',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
'original_icon': None,
'original_name': 'Operational error',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'operational_error',
'unique_id': '00000000000004D2-0000000000000042-MatterNodeDevice-1-RvcOperationalStateOperationalError-97-5',
'unit_of_measurement': None,
})
# ---
# name: test_sensors[vacuum_cleaner][sensor.mock_vacuum_operational_error-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'enum',
'friendly_name': 'Mock Vacuum Operational error',
'options': list([
'no_error',
'unable_to_start_or_resume',
'unable_to_complete_operation',
'command_invalid_in_state',
'failed_to_find_charging_dock',
'stuck',
'dust_bin_missing',
'dust_bin_full',
'water_tank_empty',
'water_tank_missing',
'water_tank_lid_open',
'mop_cleaning_pad_missing',
'low_battery',
'cannot_reach_target_area',
'dirty_water_tank_full',
'dirty_water_tank_missing',
'wheels_jammed',
'brush_jammed',
'navigation_sensor_obscured',
]),
}),
'context': <ANY>,
'entity_id': 'sensor.mock_vacuum_operational_error',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'no_error',
})
# ---
# name: test_sensors[vacuum_cleaner][sensor.mock_vacuum_operational_state-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -374,6 +374,31 @@ async def test_operational_state_sensor(
assert state.state == "extra_state"
@pytest.mark.parametrize("node_fixture", ["silabs_dishwasher"])
async def test_operational_error_sensor(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test Operational Error sensor, using a dishwasher fixture."""
# OperationalState Cluster / OperationalError attribute (1/96/5)
state = hass.states.get("sensor.dishwasher_operational_error")
assert state
assert state.state == "no_error"
assert state.attributes["options"] == [
"no_error",
"unable_to_start_or_resume",
"unable_to_complete_operation",
"command_invalid_in_state",
]
set_node_attribute(matter_node, 1, 96, 5, {0: 1})
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.dishwasher_operational_error")
assert state
assert state.state == "unable_to_start_or_resume"
@pytest.mark.parametrize("node_fixture", ["yandex_smart_socket"])
async def test_draft_electrical_measurement_sensor(
hass: HomeAssistant,
@@ -623,3 +648,52 @@ async def test_vacuum_actions(
state = hass.states.get("sensor.mock_vacuum_estimated_end_time")
assert state
assert state.state == "2025-08-29T21:13:20+00:00"
@pytest.mark.parametrize("node_fixture", ["vacuum_cleaner"])
async def test_vacuum_operational_error_sensor(
hass: HomeAssistant,
matter_client: MagicMock,
matter_node: MatterNode,
) -> None:
"""Test RVC Operational Error sensor, using a vacuum cleaner fixture."""
# RvcOperationalState Cluster / OperationalError attribute (1/97/5)
state = hass.states.get("sensor.mock_vacuum_operational_error")
assert state
assert state.state == "no_error"
assert state.attributes["options"] == [
"no_error",
"unable_to_start_or_resume",
"unable_to_complete_operation",
"command_invalid_in_state",
"failed_to_find_charging_dock",
"stuck",
"dust_bin_missing",
"dust_bin_full",
"water_tank_empty",
"water_tank_missing",
"water_tank_lid_open",
"mop_cleaning_pad_missing",
"low_battery",
"cannot_reach_target_area",
"dirty_water_tank_full",
"dirty_water_tank_missing",
"wheels_jammed",
"brush_jammed",
"navigation_sensor_obscured",
]
# test Rvc error
set_node_attribute(matter_node, 1, 97, 5, {0: 66})
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.mock_vacuum_operational_error")
assert state
assert state.state == "dust_bin_missing"
# test unknown errorStateID == 192 (0xC0)
set_node_attribute(matter_node, 1, 97, 5, {0: 192})
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("sensor.mock_vacuum_operational_error")
assert state
assert state.state == "unknown"