Add discovery schemas for Matter Smoke and CO Alarm Cluster (#126622)

Co-authored-by: Joostlek <joostlek@outlook.com>
This commit is contained in:
Marcel van der Veldt 2024-09-24 18:23:45 +02:00 committed by GitHub
parent c8964a1c80
commit ffa76dfd24
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 513 additions and 1 deletions

View File

@ -160,4 +160,105 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterBinarySensor,
required_attributes=(clusters.DoorLock.Attributes.DoorState,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmDeviceMutedSensor",
measurement_to_ha=lambda x: (
x == clusters.SmokeCoAlarm.Enums.MuteStateEnum.kMuted
),
translation_key="muted",
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.DeviceMuted,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmEndfOfServiceSensor",
measurement_to_ha=lambda x: (
x == clusters.SmokeCoAlarm.Enums.EndOfServiceEnum.kExpired
),
translation_key="end_of_service",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.EndOfServiceAlert,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmBatteryAlertSensor",
measurement_to_ha=lambda x: (
x != clusters.SmokeCoAlarm.Enums.AlarmStateEnum.kNormal
),
translation_key="battery_alert",
device_class=BinarySensorDeviceClass.BATTERY,
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.BatteryAlert,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmTestInProgressSensor",
translation_key="test_in_progress",
device_class=BinarySensorDeviceClass.RUNNING,
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.TestInProgress,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmHardwareFaultAlertSensor",
translation_key="hardware_fault",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.HardwareFaultAlert,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmSmokeStateSensor",
device_class=BinarySensorDeviceClass.SMOKE,
measurement_to_ha=lambda x: (
x != clusters.SmokeCoAlarm.Enums.AlarmStateEnum.kNormal
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.SmokeState,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmInterconnectSmokeAlarmSensor",
device_class=BinarySensorDeviceClass.SMOKE,
measurement_to_ha=lambda x: (
x != clusters.SmokeCoAlarm.Enums.AlarmStateEnum.kNormal
),
translation_key="interconnected_smoke_alarm",
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.InterconnectSmokeAlarm,),
),
MatterDiscoverySchema(
platform=Platform.BINARY_SENSOR,
entity_description=MatterBinarySensorEntityDescription(
key="SmokeCoAlarmInterconnectCOAlarmSensor",
device_class=BinarySensorDeviceClass.CO,
measurement_to_ha=lambda x: (
x != clusters.SmokeCoAlarm.Enums.AlarmStateEnum.kNormal
),
translation_key="interconnected_co_alarm",
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.InterconnectCOAlarm,),
),
]

View File

@ -1,5 +1,10 @@
{
"entity": {
"binary_sensor": {
"muted": {
"default": "mdi:bell-off"
}
},
"fan": {
"fan": {
"state_attributes": {
@ -18,6 +23,9 @@
}
},
"sensor": {
"contamination_state": {
"default": "mdi:air-filter"
},
"air_quality": {
"default": "mdi:air-filter"
},

View File

@ -245,4 +245,25 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterSelectEntity,
required_attributes=(clusters.OnOff.Attributes.StartUpOnOff,),
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterSelectEntityDescription(
key="SmokeCOSmokeSensitivityLevel",
entity_category=EntityCategory.CONFIG,
translation_key="sensitivity_level",
options=["high", "standard", "low"],
measurement_to_ha={
0: "high",
1: "standard",
2: "low",
}.get,
ha_to_native_value={
"high": 0,
"standard": 1,
"low": 2,
}.get,
),
entity_class=MatterSelectEntity,
required_attributes=(clusters.SmokeCoAlarm.Attributes.SmokeSensitivityLevel,),
),
]

View File

@ -3,6 +3,7 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from chip.clusters import Objects as clusters
from chip.clusters.Types import Nullable, NullValue
@ -52,6 +53,13 @@ AIR_QUALITY_MAP = {
clusters.AirQuality.Enums.AirQualityEnum.kUnknownEnumValue: None,
}
CONTAMINATION_STATE_MAP = {
clusters.SmokeCoAlarm.Enums.ContaminationStateEnum.kNormal: "normal",
clusters.SmokeCoAlarm.Enums.ContaminationStateEnum.kLow: "low",
clusters.SmokeCoAlarm.Enums.ContaminationStateEnum.kWarning: "warning",
clusters.SmokeCoAlarm.Enums.ContaminationStateEnum.kCritical: "critical",
}
async def async_setup_entry(
hass: HomeAssistant,
@ -568,4 +576,29 @@ DISCOVERY_SCHEMAS = [
clusters.ElectricalEnergyMeasurement.Attributes.CumulativeEnergyImported,
),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SmokeCOAlarmContaminationState",
translation_key="contamination_state",
device_class=SensorDeviceClass.ENUM,
# convert to set first to remove the duplicate unknown value
options=list(set(CONTAMINATION_STATE_MAP.values())),
measurement_to_ha=CONTAMINATION_STATE_MAP.get,
),
entity_class=MatterSensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.ContaminationState,),
),
MatterDiscoverySchema(
platform=Platform.SENSOR,
entity_description=MatterSensorEntityDescription(
key="SmokeCOAlarmExpiryDate",
translation_key="expiry_date",
device_class=SensorDeviceClass.TIMESTAMP,
# raw value is epoch seconds
measurement_to_ha=datetime.fromtimestamp,
),
entity_class=MatterSensor,
required_attributes=(clusters.SmokeCoAlarm.Attributes.ExpiryDate,),
),
]

View File

@ -46,6 +46,24 @@
},
"entity": {
"binary_sensor": {
"battery_alert": {
"name": "Battery alert"
},
"end_of_service": {
"name": "End of service"
},
"hardware_fault": {
"name": "Hardware fault"
},
"interconnected_smoke_alarm": {
"name": "Interconnected smoke alarm"
},
"interconnected_co_alarm": {
"name": "Interconnected CO alarm"
},
"test_in_progress": {
"name": "Test in progress"
},
"water_leak": {
"name": "Water leak"
},
@ -54,6 +72,9 @@
},
"rain": {
"name": "Rain"
},
"muted": {
"name": "Muted"
}
},
"climate": {
@ -138,6 +159,14 @@
"mode": {
"name": "Mode"
},
"sensitivity_level": {
"name": "Sensitivity",
"state": {
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
"standard": "Standard",
"high": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::high%]"
}
},
"startup_on_off": {
"name": "Power-on behavior on startup",
"state": {
@ -152,6 +181,15 @@
"activated_carbon_filter_condition": {
"name": "Activated carbon filter condition"
},
"contamination_state": {
"name": "Contamination state",
"state": {
"normal": "Normal",
"low": "[%key:component::matter::entity::fan::fan::state_attributes::preset_mode::state::low%]",
"warning": "Warning",
"critical": "Critical"
}
},
"air_quality": {
"name": "Air quality",
"state": {
@ -163,6 +201,9 @@
"moderate": "Moderate"
}
},
"expiry_date": {
"name": "Expiry date"
},
"flow": {
"name": "Flow"
},

View File

@ -78,6 +78,16 @@ async def door_lock_fixture(
return await setup_integration_with_node_fixture(hass, "door-lock", matter_client)
@pytest.fixture(name="smoke_detector")
async def smoke_detector_fixture(
hass: HomeAssistant, matter_client: MagicMock
) -> MatterNode:
"""Fixture for a smoke detector node."""
return await setup_integration_with_node_fixture(
hass, "smoke-detector", matter_client
)
@pytest.fixture(name="door_lock_with_unbolt")
async def door_lock_with_unbolt_fixture(
hass: HomeAssistant, matter_client: MagicMock

View File

@ -0,0 +1,238 @@
{
"node_id": 1,
"date_commissioned": "2024-09-13T20:07:21.672257",
"last_interview": "2024-09-13T21:10:36.026041",
"interview_version": 6,
"available": true,
"is_bridge": false,
"attributes": {
"0/29/0": [
{
"0": 22,
"1": 2
}
],
"0/29/1": [29, 31, 40, 42, 48, 49, 51, 60, 62, 63, 70],
"0/29/2": [41],
"0/29/3": [1],
"0/29/65532": 0,
"0/29/65533": 2,
"0/29/65528": [],
"0/29/65529": [],
"0/29/65530": [],
"0/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/31/0": [
{
"1": 5,
"2": 2,
"3": [112233],
"4": null,
"254": 3
}
],
"0/31/1": [],
"0/31/2": 4,
"0/31/3": 3,
"0/31/4": 4,
"0/31/65532": 0,
"0/31/65533": 1,
"0/31/65528": [],
"0/31/65529": [],
"0/31/65530": [0, 1],
"0/31/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
"0/40/0": 17,
"0/40/1": "HEIMAN",
"0/40/2": 4619,
"0/40/3": "Smoke sensor",
"0/40/4": 4099,
"0/40/5": "",
"0/40/6": "**REDACTED**",
"0/40/7": 0,
"0/40/8": "0.0",
"0/40/9": 16,
"0/40/10": "1.0",
"0/40/11": "20240403",
"0/40/14": "",
"0/40/15": "2404034099000007",
"0/40/16": false,
"0/40/18": "redacted",
"0/40/19": {
"0": 3,
"1": 3
},
"0/40/65532": 0,
"0/40/65533": 2,
"0/40/65528": [],
"0/40/65529": [],
"0/40/65530": [0, 2],
"0/40/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 14, 15, 16, 18, 19, 65528, 65529,
65530, 65531, 65532, 65533
],
"0/42/0": [],
"0/42/1": true,
"0/42/2": 1,
"0/42/3": null,
"0/42/65532": 0,
"0/42/65533": 1,
"0/42/65528": [],
"0/42/65529": [0],
"0/42/65530": [0, 1, 2],
"0/42/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/48/0": 0,
"0/48/1": {
"0": 60,
"1": 900
},
"0/48/2": 0,
"0/48/3": 0,
"0/48/4": true,
"0/48/65532": 0,
"0/48/65533": 1,
"0/48/65528": [1, 3, 5],
"0/48/65529": [0, 2, 4],
"0/48/65530": [],
"0/48/65531": [0, 1, 2, 3, 4, 65528, 65529, 65530, 65531, 65532, 65533],
"0/49/0": 1,
"0/49/1": [
{
"0": "+uApc5vSQm4=",
"1": true
}
],
"0/49/2": 10,
"0/49/3": 20,
"0/49/4": true,
"0/49/5": 0,
"0/49/6": "+uApc5vSQm4=",
"0/49/7": null,
"0/49/65532": 2,
"0/49/65533": 1,
"0/49/65528": [1, 5, 7],
"0/49/65529": [0, 3, 4, 6, 8],
"0/49/65530": [],
"0/49/65531": [
0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/51/0": [],
"0/51/1": 1,
"0/51/2": 247340,
"0/51/4": 0,
"0/51/5": [],
"0/51/6": [],
"0/51/7": [],
"0/51/8": false,
"0/51/65532": 0,
"0/51/65533": 1,
"0/51/65528": [],
"0/51/65529": [0],
"0/51/65530": [3],
"0/51/65531": [
0, 1, 2, 4, 5, 6, 7, 8, 65528, 65529, 65530, 65531, 65532, 65533
],
"0/60/0": 0,
"0/60/1": null,
"0/60/2": null,
"0/60/65532": 0,
"0/60/65533": 1,
"0/60/65528": [],
"0/60/65529": [0, 1, 2],
"0/60/65530": [],
"0/60/65531": [0, 1, 2, 65528, 65529, 65530, 65531, 65532, 65533],
"0/62/0": [],
"0/62/1": [],
"0/62/2": 5,
"0/62/3": 3,
"0/62/4": [],
"0/62/5": 3,
"0/62/65532": 0,
"0/62/65533": 1,
"0/62/65528": [1, 3, 5, 8],
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11],
"0/62/65530": [],
"0/62/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65530, 65531, 65532, 65533],
"0/63/0": [],
"0/63/1": [],
"0/63/2": 4,
"0/63/3": 3,
"0/63/65532": 0,
"0/63/65533": 2,
"0/63/65528": [2, 5],
"0/63/65529": [0, 1, 3, 4],
"0/63/65530": [],
"0/63/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"0/70/0": 300,
"0/70/1": 6000,
"0/70/2": 500,
"0/70/3": [],
"0/70/4": 0,
"0/70/5": 2,
"0/70/65532": 1,
"0/70/65533": 1,
"0/70/65528": [1],
"0/70/65529": [0, 2, 3],
"0/70/65530": [],
"0/70/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65530, 65531, 65532, 65533],
"1/3/0": 0,
"1/3/1": 2,
"1/3/65532": 0,
"1/3/65533": 4,
"1/3/65528": [],
"1/3/65529": [0],
"1/3/65530": [],
"1/3/65531": [0, 1, 65528, 65529, 65530, 65531, 65532, 65533],
"1/29/0": [
{
"0": 118,
"1": 1
}
],
"1/29/1": [3, 29, 47, 92],
"1/29/2": [],
"1/29/3": [],
"1/29/65532": 0,
"1/29/65533": 2,
"1/29/65528": [],
"1/29/65529": [],
"1/29/65530": [],
"1/29/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533],
"1/47/0": 0,
"1/47/1": 2,
"1/47/2": "B2",
"1/47/11": 0,
"1/47/12": 188,
"1/47/14": 0,
"1/47/15": false,
"1/47/16": 0,
"1/47/19": "CR123A",
"1/47/20": 0,
"1/47/24": 0,
"1/47/25": 0,
"1/47/31": [],
"1/47/65532": 10,
"1/47/65533": 2,
"1/47/65528": [],
"1/47/65529": [],
"1/47/65530": [1],
"1/47/65531": [
0, 1, 2, 11, 12, 14, 15, 16, 19, 20, 24, 25, 31, 65528, 65529, 65530,
65531, 65532, 65533
],
"1/92/0": 0,
"1/92/1": 0,
"1/92/3": 0,
"1/92/4": 0,
"1/92/5": false,
"1/92/6": false,
"1/92/7": 0,
"1/92/65532": 1,
"1/92/65533": 1,
"1/92/65528": [],
"1/92/65529": [0],
"1/92/65530": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
"1/92/65531": [
0, 1, 3, 4, 5, 6, 7, 65528, 65529, 65530, 65531, 65532, 65533
]
},
"attribute_subscriptions": []
}

View File

@ -9,7 +9,7 @@ import pytest
from homeassistant.components.matter.binary_sensor import (
DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS,
)
from homeassistant.const import EntityCategory, Platform
from homeassistant.const import STATE_OFF, EntityCategory, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -128,3 +128,43 @@ async def test_battery_sensor(
assert entry
assert entry.entity_category == EntityCategory.DIAGNOSTIC
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_smoke_alarm(
hass: HomeAssistant,
matter_client: MagicMock,
smoke_detector: MatterNode,
) -> None:
"""Test smoke detector."""
# Muted
state = hass.states.get("binary_sensor.smoke_sensor_muted")
assert state
assert state.state == STATE_OFF
# End of service
state = hass.states.get("binary_sensor.smoke_sensor_end_of_service")
assert state
assert state.state == STATE_OFF
# Battery alert
state = hass.states.get("binary_sensor.smoke_sensor_battery_alert")
assert state
assert state.state == STATE_OFF
# Test in progress
state = hass.states.get("binary_sensor.smoke_sensor_test_in_progress")
assert state
assert state.state == STATE_OFF
# Hardware fault
state = hass.states.get("binary_sensor.smoke_sensor_hardware_fault")
assert state
assert state.state == STATE_OFF
# Smoke
state = hass.states.get("binary_sensor.smoke_sensor_smoke")
assert state
assert state.state == STATE_OFF

View File

@ -602,3 +602,23 @@ async def test_air_purifier_sensor(
assert state.state == "100"
assert state.attributes["state_class"] == "measurement"
assert state.attributes["unit_of_measurement"] == "%"
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_smoke_alarm(
hass: HomeAssistant,
matter_client: MagicMock,
smoke_detector: MatterNode,
) -> None:
"""Test smoke detector."""
# Battery
state = hass.states.get("sensor.smoke_sensor_battery")
assert state
assert state.state == "94"
# Voltage
state = hass.states.get("sensor.smoke_sensor_voltage")
assert state
assert state.state == "0.0"