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, entity_class=MatterBinarySensor,
required_attributes=(clusters.DoorLock.Attributes.DoorState,), 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": { "entity": {
"binary_sensor": {
"muted": {
"default": "mdi:bell-off"
}
},
"fan": { "fan": {
"fan": { "fan": {
"state_attributes": { "state_attributes": {
@ -18,6 +23,9 @@
} }
}, },
"sensor": { "sensor": {
"contamination_state": {
"default": "mdi:air-filter"
},
"air_quality": { "air_quality": {
"default": "mdi:air-filter" "default": "mdi:air-filter"
}, },

View File

@ -245,4 +245,25 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterSelectEntity, entity_class=MatterSelectEntity,
required_attributes=(clusters.OnOff.Attributes.StartUpOnOff,), 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 __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime
from chip.clusters import Objects as clusters from chip.clusters import Objects as clusters
from chip.clusters.Types import Nullable, NullValue from chip.clusters.Types import Nullable, NullValue
@ -52,6 +53,13 @@ AIR_QUALITY_MAP = {
clusters.AirQuality.Enums.AirQualityEnum.kUnknownEnumValue: None, 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( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
@ -568,4 +576,29 @@ DISCOVERY_SCHEMAS = [
clusters.ElectricalEnergyMeasurement.Attributes.CumulativeEnergyImported, 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": { "entity": {
"binary_sensor": { "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": { "water_leak": {
"name": "Water leak" "name": "Water leak"
}, },
@ -54,6 +72,9 @@
}, },
"rain": { "rain": {
"name": "Rain" "name": "Rain"
},
"muted": {
"name": "Muted"
} }
}, },
"climate": { "climate": {
@ -138,6 +159,14 @@
"mode": { "mode": {
"name": "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": { "startup_on_off": {
"name": "Power-on behavior on startup", "name": "Power-on behavior on startup",
"state": { "state": {
@ -152,6 +181,15 @@
"activated_carbon_filter_condition": { "activated_carbon_filter_condition": {
"name": "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": { "air_quality": {
"name": "Air quality", "name": "Air quality",
"state": { "state": {
@ -163,6 +201,9 @@
"moderate": "Moderate" "moderate": "Moderate"
} }
}, },
"expiry_date": {
"name": "Expiry date"
},
"flow": { "flow": {
"name": "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) 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") @pytest.fixture(name="door_lock_with_unbolt")
async def door_lock_with_unbolt_fixture( async def door_lock_with_unbolt_fixture(
hass: HomeAssistant, matter_client: MagicMock 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 ( from homeassistant.components.matter.binary_sensor import (
DISCOVERY_SCHEMAS as BINARY_SENSOR_SCHEMAS, 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.core import HomeAssistant
from homeassistant.helpers import entity_registry as er from homeassistant.helpers import entity_registry as er
@ -128,3 +128,43 @@ async def test_battery_sensor(
assert entry assert entry
assert entry.entity_category == EntityCategory.DIAGNOSTIC 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.state == "100"
assert state.attributes["state_class"] == "measurement" assert state.attributes["state_class"] == "measurement"
assert state.attributes["unit_of_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"