mirror of
https://github.com/home-assistant/core.git
synced 2026-08-28 10:16:02 -05:00
Add GeneralDiagnostics sensors and fault binary sensors to Matter integration (#169830)
This commit is contained in:
@@ -556,4 +556,48 @@ DISCOVERY_SCHEMAS = [
|
||||
featuremap_contains=clusters.Thermostat.Bitmaps.Feature.kOccupancy,
|
||||
allow_multi=True,
|
||||
),
|
||||
# GeneralDiagnostics active fault sensors
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="GeneralDiagnosticsActiveHardwareFaults",
|
||||
translation_key="active_hardware_faults",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
device_to_ha=bool,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(
|
||||
clusters.GeneralDiagnostics.Attributes.ActiveHardwareFaults,
|
||||
),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="GeneralDiagnosticsActiveRadioFaults",
|
||||
translation_key="active_radio_faults",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
device_to_ha=bool,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(clusters.GeneralDiagnostics.Attributes.ActiveRadioFaults,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BINARY_SENSOR,
|
||||
entity_description=MatterBinarySensorEntityDescription(
|
||||
key="GeneralDiagnosticsActiveNetworkFaults",
|
||||
translation_key="active_network_faults",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
device_to_ha=bool,
|
||||
),
|
||||
entity_class=MatterBinarySensor,
|
||||
required_attributes=(
|
||||
clusters.GeneralDiagnostics.Attributes.ActiveNetworkFaults,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -137,6 +137,17 @@ RVC_OPERATIONAL_STATE_ERROR_MAP = {
|
||||
_rvc_err.kNavigationSensorObscured: ("navigation_sensor_obscured"),
|
||||
}
|
||||
|
||||
BOOT_REASON_MAP = {
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kUnspecified: "unspecified",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kPowerOnReboot: "power_on_reboot",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kBrownOutReset: "brown_out_reset",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kSoftwareWatchdogReset: "software_watchdog_reset",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kHardwareWatchdogReset: "hardware_watchdog_reset",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kSoftwareUpdateCompleted: "software_update_completed",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kSoftwareReset: "software_reset",
|
||||
clusters.GeneralDiagnostics.Enums.BootReasonEnum.kUnknownEnumValue: None,
|
||||
}
|
||||
|
||||
BOOST_STATE_MAP = {
|
||||
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kInactive: "inactive",
|
||||
clusters.WaterHeaterManagement.Enums.BoostStateEnum.kActive: "active",
|
||||
@@ -1575,4 +1586,46 @@ DISCOVERY_SCHEMAS = [
|
||||
required_attributes=(clusters.DoorLock.Attributes.DoorClosedEvents,),
|
||||
featuremap_contains=clusters.DoorLock.Bitmaps.Feature.kDoorPositionSensor,
|
||||
),
|
||||
# GeneralDiagnostics cluster sensors
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="GeneralDiagnosticsRebootCount",
|
||||
translation_key="reboot_count",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.GeneralDiagnostics.Attributes.RebootCount,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="GeneralDiagnosticsUpTime",
|
||||
translation_key="uptime",
|
||||
device_class=SensorDeviceClass.UPTIME,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
device_to_ha=lambda uptime: dt_util.utcnow() - timedelta(seconds=uptime),
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.GeneralDiagnostics.Attributes.UpTime,),
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.SENSOR,
|
||||
entity_description=MatterSensorEntityDescription(
|
||||
key="GeneralDiagnosticsBootReason",
|
||||
translation_key="boot_reason",
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_registry_enabled_default=False,
|
||||
options=[
|
||||
reason for reason in BOOT_REASON_MAP.values() if reason is not None
|
||||
],
|
||||
device_to_ha=BOOT_REASON_MAP.get,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
required_attributes=(clusters.GeneralDiagnostics.Attributes.BootReason,),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -47,6 +47,15 @@
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"active_hardware_faults": {
|
||||
"name": "Hardware faults"
|
||||
},
|
||||
"active_network_faults": {
|
||||
"name": "Network faults"
|
||||
},
|
||||
"active_radio_faults": {
|
||||
"name": "Radio faults"
|
||||
},
|
||||
"actuator": {
|
||||
"name": "Actuator"
|
||||
},
|
||||
@@ -408,6 +417,18 @@
|
||||
"battery_voltage": {
|
||||
"name": "Battery voltage"
|
||||
},
|
||||
"boot_reason": {
|
||||
"name": "Boot reason",
|
||||
"state": {
|
||||
"brown_out_reset": "Brownout reset",
|
||||
"hardware_watchdog_reset": "Hardware watchdog reset",
|
||||
"power_on_reboot": "Power-on reboot",
|
||||
"software_reset": "Software reset",
|
||||
"software_update_completed": "Software update completed",
|
||||
"software_watchdog_reset": "Software watchdog reset",
|
||||
"unspecified": "Unspecified"
|
||||
}
|
||||
},
|
||||
"contamination_state": {
|
||||
"name": "Contamination state",
|
||||
"state": {
|
||||
@@ -576,6 +597,9 @@
|
||||
"reactive_current": {
|
||||
"name": "Reactive current"
|
||||
},
|
||||
"reboot_count": {
|
||||
"name": "Reboot count"
|
||||
},
|
||||
"rms_current": {
|
||||
"name": "Effective current"
|
||||
},
|
||||
@@ -600,6 +624,9 @@
|
||||
"medium": "[%key:common::state::medium%]"
|
||||
}
|
||||
},
|
||||
"uptime": {
|
||||
"name": "Uptime"
|
||||
},
|
||||
"valve_position": {
|
||||
"name": "Valve position"
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,7 +12,7 @@ from syrupy.assertion import SnapshotAssertion
|
||||
from homeassistant.components.matter.binary_sensor import (
|
||||
DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS,
|
||||
)
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@@ -733,3 +733,101 @@ async def test_co_detector(
|
||||
state = hass.states.get("binary_sensor.smart_co_sensor_carbon_monoxide")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("node_fixture", ["device_diagnostics"])
|
||||
async def test_general_diagnostics_fault_sensors(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test GeneralDiagnostics active fault binary sensors."""
|
||||
# ActiveHardwareFaults (cluster 51, attr 5) = [] (no faults)
|
||||
entity_id = "binary_sensor.m5stamp_lighting_app_hardware_faults"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.entity_category is EntityCategory.DIAGNOSTIC
|
||||
|
||||
# Simulate hardware fault
|
||||
set_node_attribute(matter_node, 0, 51, 5, [1])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
# Clear faults
|
||||
set_node_attribute(matter_node, 0, 51, 5, [])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
# ActiveRadioFaults (cluster 51, attr 6) = [] (no faults)
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_radio_faults")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 6, [1])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_radio_faults")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 6, [])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_radio_faults")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
# ActiveNetworkFaults (cluster 51, attr 7) = [] (no faults)
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_network_faults")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 7, [1])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_network_faults")
|
||||
assert state
|
||||
assert state.state == "on"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 7, [])
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("binary_sensor.m5stamp_lighting_app_network_faults")
|
||||
assert state
|
||||
assert state.state == "off"
|
||||
|
||||
entry = entity_registry.async_get(
|
||||
"binary_sensor.m5stamp_lighting_app_hardware_faults"
|
||||
)
|
||||
assert entry
|
||||
assert entry.entity_category is EntityCategory.DIAGNOSTIC
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["device_diagnostics"])
|
||||
async def test_general_diagnostics_fault_sensors_disabled_by_default(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test GeneralDiagnostics fault binary sensors are disabled by default."""
|
||||
for entity_id in (
|
||||
"binary_sensor.m5stamp_lighting_app_hardware_faults",
|
||||
"binary_sensor.m5stamp_lighting_app_radio_faults",
|
||||
"binary_sensor.m5stamp_lighting_app_network_faults",
|
||||
):
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry, f"Expected {entity_id} to be registered"
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
@@ -885,3 +885,76 @@ async def test_bridged_device_reachable_updates_availability(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.state != STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-01-01T14:00:00+00:00")
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
@pytest.mark.parametrize("node_fixture", ["device_diagnostics"])
|
||||
async def test_general_diagnostics_sensors(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test GeneralDiagnostics cluster sensors."""
|
||||
# RebootCount (cluster 51, attr 1) = 3
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_reboot_count")
|
||||
assert state
|
||||
assert state.state == "3"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 1, 5)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_reboot_count")
|
||||
assert state
|
||||
assert state.state == "5"
|
||||
|
||||
entry = entity_registry.async_get("sensor.m5stamp_lighting_app_reboot_count")
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
|
||||
# UpTime (cluster 51, attr 2) = 213 seconds → boot at now - 213s
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_uptime")
|
||||
assert state
|
||||
assert state.state == "2025-01-01T13:56:27+00:00"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 2, 3600)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_uptime")
|
||||
assert state
|
||||
assert state.state == "2025-01-01T13:00:00+00:00"
|
||||
|
||||
# BootReason (cluster 51, attr 4) = 1 (PowerOnReboot)
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_boot_reason")
|
||||
assert state
|
||||
assert state.state == "power_on_reboot"
|
||||
|
||||
set_node_attribute(matter_node, 0, 51, 4, 6)
|
||||
await trigger_subscription_callback(hass, matter_client)
|
||||
|
||||
state = hass.states.get("sensor.m5stamp_lighting_app_boot_reason")
|
||||
assert state
|
||||
assert state.state == "software_reset"
|
||||
|
||||
entry = entity_registry.async_get("sensor.m5stamp_lighting_app_boot_reason")
|
||||
assert entry
|
||||
assert entry.entity_category == EntityCategory.DIAGNOSTIC
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["device_diagnostics"])
|
||||
async def test_general_diagnostics_sensors_disabled_by_default(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test GeneralDiagnostics sensors are disabled by default."""
|
||||
for entity_id in (
|
||||
"sensor.m5stamp_lighting_app_reboot_count",
|
||||
"sensor.m5stamp_lighting_app_uptime",
|
||||
"sensor.m5stamp_lighting_app_boot_reason",
|
||||
):
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry, f"Expected {entity_id} to be registered"
|
||||
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
|
||||
|
||||
Reference in New Issue
Block a user