Fix Matter entity names (#120038)

pull/118047/head
Marcel van der Veldt 2024-06-21 16:42:22 +02:00 committed by GitHub
parent 180c244a78
commit 4110f4f393
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 1911 additions and 129 deletions

View File

@ -321,7 +321,7 @@ DISCOVERY_SCHEMAS = [
platform=Platform.CLIMATE,
entity_description=ClimateEntityDescription(
key="MatterThermostat",
name=None,
translation_key="thermostat",
),
entity_class=MatterClimate,
required_attributes=(clusters.Thermostat.Attributes.LocalTemperature,),

View File

@ -200,7 +200,9 @@ class MatterCover(MatterEntity, CoverEntity):
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.COVER,
entity_description=CoverEntityDescription(key="MatterCover", name=None),
entity_description=CoverEntityDescription(
key="MatterCover", translation_key="cover"
),
entity_class=MatterCover,
required_attributes=(
clusters.WindowCovering.Attributes.OperationalStatus,
@ -214,7 +216,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.COVER,
entity_description=CoverEntityDescription(
key="MatterCoverPositionAwareLift", name=None
key="MatterCoverPositionAwareLift", translation_key="cover"
),
entity_class=MatterCover,
required_attributes=(
@ -229,7 +231,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.COVER,
entity_description=CoverEntityDescription(
key="MatterCoverPositionAwareTilt", name=None
key="MatterCoverPositionAwareTilt", translation_key="cover"
),
entity_class=MatterCover,
required_attributes=(
@ -244,7 +246,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.COVER,
entity_description=CoverEntityDescription(
key="MatterCoverPositionAwareLiftAndTilt", name=None
key="MatterCoverPositionAwareLiftAndTilt", translation_key="cover"
),
entity_class=MatterCover,
required_attributes=(

View File

@ -5,9 +5,11 @@ from __future__ import annotations
from abc import abstractmethod
from collections.abc import Callable
from dataclasses import dataclass
from functools import cached_property
import logging
from typing import TYPE_CHECKING, Any, cast
from chip.clusters import Objects as clusters
from chip.clusters.Objects import ClusterAttributeDescriptor, NullValue
from matter_server.common.helpers.util import create_attribute_path
from matter_server.common.models import EventType, ServerInfoMessage
@ -15,6 +17,7 @@ from matter_server.common.models import EventType, ServerInfoMessage
from homeassistant.core import callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import Entity, EntityDescription
from homeassistant.helpers.typing import UndefinedType
from .const import DOMAIN, ID_TYPE_DEVICE_ID
from .helpers import get_device_id
@ -41,6 +44,7 @@ class MatterEntity(Entity):
"""Entity class for Matter devices."""
_attr_has_entity_name = True
_name_postfix: str | None = None
def __init__(
self,
@ -71,6 +75,35 @@ class MatterEntity(Entity):
identifiers={(DOMAIN, f"{ID_TYPE_DEVICE_ID}_{node_device_id}")}
)
self._attr_available = self._endpoint.node.available
# mark endpoint postfix if the device has the primary attribute on multiple endpoints
if not self._endpoint.node.is_bridge_device and any(
ep
for ep in self._endpoint.node.endpoints.values()
if ep != self._endpoint
and ep.has_attribute(None, entity_info.primary_attribute)
):
self._name_postfix = str(self._endpoint.endpoint_id)
# prefer the label attribute for the entity name
# Matter has a way for users and/or vendors to specify a name for an endpoint
# which is always preferred over a standard HA (generated) name
for attr in (
clusters.FixedLabel.Attributes.LabelList,
clusters.UserLabel.Attributes.LabelList,
):
if not (labels := self.get_matter_attribute_value(attr)):
continue
for label in labels:
if label.label not in ["Label", "Button"]:
continue
# fixed or user label found: use it
label_value: str = label.value
# in the case the label is only the label id, use it as postfix only
if label_value.isnumeric():
self._name_postfix = label_value
else:
self._attr_name = label_value
break
# make sure to update the attributes once
self._update_from_device()
@ -105,6 +138,17 @@ class MatterEntity(Entity):
)
)
@cached_property
def name(self) -> str | UndefinedType | None:
"""Return the name of the entity."""
if hasattr(self, "_attr_name"):
# an explicit entity name was defined, we use that
return self._attr_name
name = super().name
if name and self._name_postfix:
name = f"{name} ({self._name_postfix})"
return name
@callback
def _on_matter_event(self, event: EventType, data: Any = None) -> None:
"""Call on update from the device."""

View File

@ -49,8 +49,6 @@ async def async_setup_entry(
class MatterEventEntity(MatterEntity, EventEntity):
"""Representation of a Matter Event entity."""
_attr_translation_key = "push"
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize the entity."""
super().__init__(*args, **kwargs)
@ -72,21 +70,6 @@ class MatterEventEntity(MatterEntity, EventEntity):
event_types.append("multi_press_ongoing")
event_types.append("multi_press_complete")
self._attr_event_types = event_types
# the optional label attribute could be used to identify multiple buttons
# e.g. in case of a dimmer switch with 4 buttons, each button
# will have its own name, prefixed by the device name.
if labels := self.get_matter_attribute_value(
clusters.FixedLabel.Attributes.LabelList
):
for label in labels:
if label.label in ["Label", "Button"]:
label_value: str = label.value
# in the case the label is only the label id, prettify it a bit
if label_value.isnumeric():
self._attr_name = f"Button {label_value}"
else:
self._attr_name = label_value
break
async def async_added_to_hass(self) -> None:
"""Handle being added to Home Assistant."""
@ -122,7 +105,9 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.EVENT,
entity_description=EventEntityDescription(
key="GenericSwitch", device_class=EventDeviceClass.BUTTON, name=None
key="GenericSwitch",
device_class=EventDeviceClass.BUTTON,
translation_key="button",
),
entity_class=MatterEventEntity,
required_attributes=(

View File

@ -421,7 +421,9 @@ class MatterLight(MatterEntity, LightEntity):
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.LIGHT,
entity_description=LightEntityDescription(key="MatterLight", name=None),
entity_description=LightEntityDescription(
key="MatterLight", translation_key="light"
),
entity_class=MatterLight,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
optional_attributes=(
@ -445,7 +447,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.LIGHT,
entity_description=LightEntityDescription(
key="MatterHSColorLightFallback", name=None
key="MatterHSColorLightFallback", translation_key="light"
),
entity_class=MatterLight,
required_attributes=(
@ -465,7 +467,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.LIGHT,
entity_description=LightEntityDescription(
key="MatterXYColorLightFallback", name=None
key="MatterXYColorLightFallback", translation_key="light"
),
entity_class=MatterLight,
required_attributes=(
@ -485,7 +487,7 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.LIGHT,
entity_description=LightEntityDescription(
key="MatterColorTemperatureLightFallback", name=None
key="MatterColorTemperatureLightFallback", translation_key="light"
),
entity_class=MatterLight,
required_attributes=(

View File

@ -176,7 +176,9 @@ class MatterLock(MatterEntity, LockEntity):
DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.LOCK,
entity_description=LockEntityDescription(key="MatterLock", name=None),
entity_description=LockEntityDescription(
key="MatterLock", translation_key="lock"
),
entity_class=MatterLock,
required_attributes=(clusters.DoorLock.Attributes.LockState,),
optional_attributes=(clusters.DoorLock.Attributes.DoorState,),

View File

@ -107,6 +107,3 @@ class MatterDiscoverySchema:
# [optional] bool to specify if this primary value may be discovered
# by multiple platforms
allow_multi: bool = False
# [optional] bool to specify if this primary value should be polled
should_poll: bool = False

View File

@ -45,8 +45,19 @@
}
},
"entity": {
"climate": {
"thermostat": {
"name": "Thermostat"
}
},
"cover": {
"cover": {
"name": "[%key:component::cover::title%]"
}
},
"event": {
"push": {
"button": {
"name": "Button",
"state_attributes": {
"event_type": {
"state": {
@ -64,6 +75,7 @@
},
"fan": {
"fan": {
"name": "[%key:component::fan::title%]",
"state_attributes": {
"preset_mode": {
"state": {
@ -92,6 +104,16 @@
"name": "On/Off transition time"
}
},
"light": {
"light": {
"name": "[%key:component::light::title%]"
}
},
"lock": {
"lock": {
"name": "[%key:component::lock::title%]"
}
},
"sensor": {
"activated_carbon_filter_condition": {
"name": "Activated carbon filter condition"
@ -114,6 +136,14 @@
"hepa_filter_condition": {
"name": "Hepa filter condition"
}
},
"switch": {
"switch": {
"name": "[%key:component::switch::title%]"
},
"power": {
"name": "Power"
}
}
},
"services": {

View File

@ -64,7 +64,9 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=SwitchEntityDescription(
key="MatterPlug", device_class=SwitchDeviceClass.OUTLET, name=None
key="MatterPlug",
device_class=SwitchDeviceClass.OUTLET,
translation_key="switch",
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
@ -73,7 +75,38 @@ DISCOVERY_SCHEMAS = [
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=SwitchEntityDescription(
key="MatterSwitch", device_class=SwitchDeviceClass.SWITCH, name=None
key="MatterPowerToggle",
device_class=SwitchDeviceClass.SWITCH,
translation_key="power",
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
device_type=(
device_types.AirPurifier,
device_types.BasicVideoPlayer,
device_types.CastingVideoPlayer,
device_types.CookSurface,
device_types.Cooktop,
device_types.Dishwasher,
device_types.ExtractorHood,
device_types.HeatingCoolingUnit,
device_types.LaundryDryer,
device_types.LaundryWasher,
device_types.Oven,
device_types.Pump,
device_types.PumpController,
device_types.Refrigerator,
device_types.RoboticVacuumCleaner,
device_types.RoomAirConditioner,
device_types.Speaker,
),
),
MatterDiscoverySchema(
platform=Platform.SWITCH,
entity_description=SwitchEntityDescription(
key="MatterSwitch",
device_class=SwitchDeviceClass.OUTLET,
translation_key="switch",
),
entity_class=MatterSwitch,
required_attributes=(clusters.OnOff.Attributes.OnOff,),
@ -83,6 +116,23 @@ DISCOVERY_SCHEMAS = [
device_types.ExtendedColorLight,
device_types.ColorDimmerSwitch,
device_types.OnOffLight,
device_types.AirPurifier,
device_types.BasicVideoPlayer,
device_types.CastingVideoPlayer,
device_types.CookSurface,
device_types.Cooktop,
device_types.Dishwasher,
device_types.ExtractorHood,
device_types.HeatingCoolingUnit,
device_types.LaundryDryer,
device_types.LaundryWasher,
device_types.Oven,
device_types.Pump,
device_types.PumpController,
device_types.Refrigerator,
device_types.RoboticVacuumCleaner,
device_types.RoomAirConditioner,
device_types.Speaker,
),
),
]

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@
"0/40/0": 1,
"0/40/1": "Nabu Casa",
"0/40/2": 65521,
"0/40/3": "Mock OnOffPluginUnit (powerplug/switch)",
"0/40/3": "Mock OnOffPluginUnit",
"0/40/4": 32768,
"0/40/5": "",
"0/40/6": "XX",

View File

@ -81,7 +81,7 @@ async def test_device_registry_single_node_device_alt(
assert entry is not None
# test name is derived from productName (because nodeLabel is absent)
assert entry.name == "Mock OnOffPluginUnit (powerplug/switch)"
assert entry.name == "Mock OnOffPluginUnit"
# test serial id NOT present as additional identifier
assert (DOMAIN, "serial_TEST_SN") not in entry.identifiers
@ -163,13 +163,13 @@ async def test_node_added_subscription(
)
)
entity_state = hass.states.get("light.mock_onoff_light")
entity_state = hass.states.get("light.mock_onoff_light_light")
assert not entity_state
node_added_callback(EventType.NODE_ADDED, node)
await hass.async_block_till_done()
entity_state = hass.states.get("light.mock_onoff_light")
entity_state = hass.states.get("light.mock_onoff_light_light")
assert entity_state
@ -187,6 +187,24 @@ async def test_device_registry_single_node_composed_device(
assert len(dev_reg.devices) == 1
async def test_multi_endpoint_name(
hass: HomeAssistant,
matter_client: MagicMock,
) -> None:
"""Test that the entity name gets postfixed if the device has multiple primary endpoints."""
await setup_integration_with_node_fixture(
hass,
"multi-endpoint-light",
matter_client,
)
entity_state = hass.states.get("light.inovelli_light_1")
assert entity_state
assert entity_state.name == "Inovelli Light (1)"
entity_state = hass.states.get("light.inovelli_light_6")
assert entity_state
assert entity_state.name == "Inovelli Light (6)"
async def test_get_clean_name_() -> None:
"""Test get_clean_name helper.

View File

@ -44,7 +44,7 @@ async def test_thermostat_base(
) -> None:
"""Test thermostat base attributes and state updates."""
# test entity attributes
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["min_temp"] == 7
assert state.attributes["max_temp"] == 35
@ -66,7 +66,7 @@ async def test_thermostat_base(
set_node_attribute(thermostat, 1, 513, 5, 1600)
set_node_attribute(thermostat, 1, 513, 6, 3000)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["min_temp"] == 16
assert state.attributes["max_temp"] == 30
@ -80,56 +80,56 @@ async def test_thermostat_base(
# test system mode update from device
set_node_attribute(thermostat, 1, 513, 28, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.state == HVACMode.OFF
# test running state update from device
set_node_attribute(thermostat, 1, 513, 41, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.HEATING
set_node_attribute(thermostat, 1, 513, 41, 8)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.HEATING
set_node_attribute(thermostat, 1, 513, 41, 2)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.COOLING
set_node_attribute(thermostat, 1, 513, 41, 16)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.COOLING
set_node_attribute(thermostat, 1, 513, 41, 4)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.FAN
set_node_attribute(thermostat, 1, 513, 41, 32)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.FAN
set_node_attribute(thermostat, 1, 513, 41, 64)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.FAN
set_node_attribute(thermostat, 1, 513, 41, 66)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["hvac_action"] == HVACAction.OFF
@ -137,7 +137,7 @@ async def test_thermostat_base(
set_node_attribute(thermostat, 1, 513, 28, 4)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.state == HVACMode.HEAT
@ -145,7 +145,7 @@ async def test_thermostat_base(
set_node_attribute(thermostat, 1, 513, 18, 2000)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.attributes["temperature"] == 20
@ -159,14 +159,14 @@ async def test_thermostat_service_calls(
) -> None:
"""Test climate platform service calls."""
# test single-setpoint temperature adjustment when cool mode is active
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.state == HVACMode.COOL
await hass.services.async_call(
"climate",
"set_temperature",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"temperature": 25,
},
blocking=True,
@ -187,7 +187,7 @@ async def test_thermostat_service_calls(
"climate",
"set_temperature",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"temperature": 25,
},
blocking=True,
@ -199,7 +199,7 @@ async def test_thermostat_service_calls(
# test single-setpoint temperature adjustment when heat mode is active
set_node_attribute(thermostat, 1, 513, 28, 4)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.state == HVACMode.HEAT
@ -207,7 +207,7 @@ async def test_thermostat_service_calls(
"climate",
"set_temperature",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"temperature": 20,
},
blocking=True,
@ -224,7 +224,7 @@ async def test_thermostat_service_calls(
# test dual setpoint temperature adjustments when heat_cool mode is active
set_node_attribute(thermostat, 1, 513, 28, 1)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.longan_link_hvac")
state = hass.states.get("climate.longan_link_hvac_thermostat")
assert state
assert state.state == HVACMode.HEAT_COOL
@ -232,7 +232,7 @@ async def test_thermostat_service_calls(
"climate",
"set_temperature",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"target_temp_low": 10,
"target_temp_high": 30,
},
@ -257,7 +257,7 @@ async def test_thermostat_service_calls(
"climate",
"set_hvac_mode",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"hvac_mode": HVACMode.HEAT,
},
blocking=True,
@ -281,7 +281,7 @@ async def test_thermostat_service_calls(
"climate",
"set_temperature",
{
"entity_id": "climate.longan_link_hvac",
"entity_id": "climate.longan_link_hvac_thermostat",
"temperature": 22,
"hvac_mode": HVACMode.COOL,
},
@ -312,7 +312,7 @@ async def test_room_airconditioner(
room_airconditioner: MatterNode,
) -> None:
"""Test if a climate entity is created for a Room Airconditioner device."""
state = hass.states.get("climate.room_airconditioner")
state = hass.states.get("climate.room_airconditioner_thermostat")
assert state
assert state.attributes["current_temperature"] == 20
assert state.attributes["min_temp"] == 16
@ -335,13 +335,13 @@ async def test_room_airconditioner(
# test fan-only hvac mode
set_node_attribute(room_airconditioner, 1, 513, 28, 7)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.room_airconditioner")
state = hass.states.get("climate.room_airconditioner_thermostat")
assert state
assert state.state == HVACMode.FAN_ONLY
# test dry hvac mode
set_node_attribute(room_airconditioner, 1, 513, 28, 8)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("climate.room_airconditioner")
state = hass.states.get("climate.room_airconditioner_thermostat")
assert state
assert state.state == HVACMode.DRY

View File

@ -27,11 +27,11 @@ from .common import (
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_lift", "cover.mock_lift_window_covering"),
("window-covering_pa-lift", "cover.longan_link_wncv_da01"),
("window-covering_tilt", "cover.mock_tilt_window_covering"),
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering"),
("window-covering_full", "cover.mock_full_window_covering"),
("window-covering_lift", "cover.mock_lift_window_covering_cover"),
("window-covering_pa-lift", "cover.longan_link_wncv_da01_cover"),
("window-covering_tilt", "cover.mock_tilt_window_covering_cover"),
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering_cover"),
("window-covering_full", "cover.mock_full_window_covering_cover"),
],
)
async def test_cover(
@ -105,9 +105,9 @@ async def test_cover(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_lift", "cover.mock_lift_window_covering"),
("window-covering_pa-lift", "cover.longan_link_wncv_da01"),
("window-covering_full", "cover.mock_full_window_covering"),
("window-covering_lift", "cover.mock_lift_window_covering_cover"),
("window-covering_pa-lift", "cover.longan_link_wncv_da01_cover"),
("window-covering_full", "cover.mock_full_window_covering_cover"),
],
)
async def test_cover_lift(
@ -162,7 +162,7 @@ async def test_cover_lift(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_lift", "cover.mock_lift_window_covering"),
("window-covering_lift", "cover.mock_lift_window_covering_cover"),
],
)
async def test_cover_lift_only(
@ -207,7 +207,7 @@ async def test_cover_lift_only(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_pa-lift", "cover.longan_link_wncv_da01"),
("window-covering_pa-lift", "cover.longan_link_wncv_da01_cover"),
],
)
async def test_cover_position_aware_lift(
@ -259,9 +259,9 @@ async def test_cover_position_aware_lift(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_tilt", "cover.mock_tilt_window_covering"),
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering"),
("window-covering_full", "cover.mock_full_window_covering"),
("window-covering_tilt", "cover.mock_tilt_window_covering_cover"),
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering_cover"),
("window-covering_full", "cover.mock_full_window_covering_cover"),
],
)
async def test_cover_tilt(
@ -317,7 +317,7 @@ async def test_cover_tilt(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_tilt", "cover.mock_tilt_window_covering"),
("window-covering_tilt", "cover.mock_tilt_window_covering_cover"),
],
)
async def test_cover_tilt_only(
@ -360,7 +360,7 @@ async def test_cover_tilt_only(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering"),
("window-covering_pa-tilt", "cover.mock_pa_tilt_window_covering_cover"),
],
)
async def test_cover_position_aware_tilt(
@ -410,7 +410,7 @@ async def test_cover_full_features(
"window-covering_full",
matter_client,
)
entity_id = "cover.mock_full_window_covering"
entity_id = "cover.mock_full_window_covering_cover"
state = hass.states.get(entity_id)
assert state

View File

@ -34,7 +34,7 @@ async def test_lock(
"lock",
"unlock",
{
"entity_id": "lock.mock_door_lock",
"entity_id": "lock.mock_door_lock_lock",
},
blocking=True,
)
@ -52,7 +52,7 @@ async def test_lock(
"lock",
"lock",
{
"entity_id": "lock.mock_door_lock",
"entity_id": "lock.mock_door_lock_lock",
},
blocking=True,
)
@ -66,35 +66,35 @@ async def test_lock(
)
matter_client.send_device_command.reset_mock()
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_LOCKED
set_node_attribute(door_lock, 1, 257, 0, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_UNLOCKING
set_node_attribute(door_lock, 1, 257, 0, 2)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_UNLOCKED
set_node_attribute(door_lock, 1, 257, 0, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_LOCKING
set_node_attribute(door_lock, 1, 257, 0, None)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_UNKNOWN
@ -122,7 +122,7 @@ async def test_lock_requires_pin(
await hass.services.async_call(
"lock",
"lock",
{"entity_id": "lock.mock_door_lock", ATTR_CODE: "1234"},
{"entity_id": "lock.mock_door_lock_lock", ATTR_CODE: "1234"},
blocking=True,
)
@ -131,7 +131,7 @@ async def test_lock_requires_pin(
await hass.services.async_call(
"lock",
"lock",
{"entity_id": "lock.mock_door_lock", ATTR_CODE: code},
{"entity_id": "lock.mock_door_lock_lock", ATTR_CODE: code},
blocking=True,
)
assert matter_client.send_device_command.call_count == 1
@ -145,13 +145,13 @@ async def test_lock_requires_pin(
# Lock door using default code
default_code = "7654321"
entity_registry.async_update_entity_options(
"lock.mock_door_lock", "lock", {"default_code": default_code}
"lock.mock_door_lock_lock", "lock", {"default_code": default_code}
)
await trigger_subscription_callback(hass, matter_client)
await hass.services.async_call(
"lock",
"lock",
{"entity_id": "lock.mock_door_lock"},
{"entity_id": "lock.mock_door_lock_lock"},
blocking=True,
)
assert matter_client.send_device_command.call_count == 2
@ -171,7 +171,7 @@ async def test_lock_with_unbolt(
door_lock_with_unbolt: MatterNode,
) -> None:
"""Test door lock."""
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_LOCKED
assert state.attributes["supported_features"] & LockEntityFeature.OPEN
@ -180,7 +180,7 @@ async def test_lock_with_unbolt(
"lock",
"unlock",
{
"entity_id": "lock.mock_door_lock",
"entity_id": "lock.mock_door_lock_lock",
},
blocking=True,
)
@ -198,7 +198,7 @@ async def test_lock_with_unbolt(
"lock",
"open",
{
"entity_id": "lock.mock_door_lock",
"entity_id": "lock.mock_door_lock_lock",
},
blocking=True,
)
@ -213,6 +213,6 @@ async def test_lock_with_unbolt(
set_node_attribute(door_lock_with_unbolt, 1, 257, 3, 0)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("lock.mock_door_lock")
state = hass.states.get("lock.mock_door_lock_lock")
assert state
assert state.state == STATE_OPEN

View File

@ -40,11 +40,10 @@ async def test_generic_switch_node(
generic_switch_node: MatterNode,
) -> None:
"""Test event entity for a GenericSwitch node."""
state = hass.states.get("event.mock_generic_switch")
state = hass.states.get("event.mock_generic_switch_button")
assert state
assert state.state == "unknown"
# the switch endpoint has no label so the entity name should be the device itself
assert state.name == "Mock Generic Switch"
assert state.name == "Mock Generic Switch Button"
# check event_types from featuremap 30
assert state.attributes[ATTR_EVENT_TYPES] == [
"initial_press",
@ -71,7 +70,7 @@ async def test_generic_switch_node(
data=None,
),
)
state = hass.states.get("event.mock_generic_switch")
state = hass.states.get("event.mock_generic_switch_button")
assert state.attributes[ATTR_EVENT_TYPE] == "initial_press"
# trigger firing a multi press event
await trigger_subscription_callback(
@ -90,7 +89,7 @@ async def test_generic_switch_node(
data={"NewPosition": 3},
),
)
state = hass.states.get("event.mock_generic_switch")
state = hass.states.get("event.mock_generic_switch_button")
assert state.attributes[ATTR_EVENT_TYPE] == "multi_press_ongoing"
assert state.attributes["NewPosition"] == 3
@ -106,8 +105,8 @@ async def test_generic_switch_multi_node(
state_button_1 = hass.states.get("event.mock_generic_switch_button_1")
assert state_button_1
assert state_button_1.state == "unknown"
# name should be 'DeviceName Button 1' due to the label set to just '1'
assert state_button_1.name == "Mock Generic Switch Button 1"
# name should be 'DeviceName Button (1)' due to the label set to just '1'
assert state_button_1.name == "Mock Generic Switch Button (1)"
# check event_types from featuremap 14
assert state_button_1.attributes[ATTR_EVENT_TYPES] == [
"initial_press",

View File

@ -45,7 +45,7 @@ async def test_fan_base(
air_purifier: MatterNode,
) -> None:
"""Test Fan platform."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
state = hass.states.get(entity_id)
assert state
assert state.attributes["preset_modes"] == [
@ -100,7 +100,7 @@ async def test_fan_turn_on_with_percentage(
air_purifier: MatterNode,
) -> None:
"""Test turning on the fan with a specific percentage."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
@ -121,7 +121,7 @@ async def test_fan_turn_on_with_preset_mode(
air_purifier: MatterNode,
) -> None:
"""Test turning on the fan with a specific preset mode."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_ON,
@ -193,7 +193,7 @@ async def test_fan_turn_off(
air_purifier: MatterNode,
) -> None:
"""Test turning off the fan."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
await hass.services.async_call(
FAN_DOMAIN,
SERVICE_TURN_OFF,
@ -235,7 +235,7 @@ async def test_fan_oscillate(
air_purifier: MatterNode,
) -> None:
"""Test oscillating the fan."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
for oscillating, value in ((True, 1), (False, 0)):
await hass.services.async_call(
FAN_DOMAIN,
@ -258,7 +258,7 @@ async def test_fan_set_direction(
air_purifier: MatterNode,
) -> None:
"""Test oscillating the fan."""
entity_id = "fan.air_purifier"
entity_id = "fan.air_purifier_fan"
for direction, value in ((DIRECTION_FORWARD, 0), (DIRECTION_REVERSE, 1)):
await hass.services.async_call(
FAN_DOMAIN,

View File

@ -69,7 +69,7 @@ async def test_entry_setup_unload(
assert matter_client.connect.call_count == 1
assert entry.state is ConfigEntryState.LOADED
entity_state = hass.states.get("light.mock_onoff_light")
entity_state = hass.states.get("light.mock_onoff_light_light")
assert entity_state
assert entity_state.state != STATE_UNAVAILABLE
@ -77,7 +77,7 @@ async def test_entry_setup_unload(
assert matter_client.disconnect.call_count == 1
assert entry.state is ConfigEntryState.NOT_LOADED
entity_state = hass.states.get("light.mock_onoff_light")
entity_state = hass.states.get("light.mock_onoff_light_light")
assert entity_state
assert entity_state.state == STATE_UNAVAILABLE
@ -625,7 +625,7 @@ async def test_remove_config_entry_device(
device_entry = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)[0]
entity_id = "light.m5stamp_lighting_app"
entity_id = "light.m5stamp_lighting_app_light"
assert device_entry
assert entity_registry.async_get(entity_id)

View File

@ -22,17 +22,17 @@ from .common import (
[
(
"extended-color-light",
"light.mock_extended_color_light",
"light.mock_extended_color_light_light",
["color_temp", "hs", "xy"],
),
(
"color-temperature-light",
"light.mock_color_temperature_light",
"light.mock_color_temperature_light_light",
["color_temp"],
),
("dimmable-light", "light.mock_dimmable_light", ["brightness"]),
("onoff-light", "light.mock_onoff_light", ["onoff"]),
("onoff-light-with-levelcontrol-present", "light.d215s", ["onoff"]),
("dimmable-light", "light.mock_dimmable_light_light", ["brightness"]),
("onoff-light", "light.mock_onoff_light_light", ["onoff"]),
("onoff-light-with-levelcontrol-present", "light.d215s_light", ["onoff"]),
],
)
async def test_light_turn_on_off(
@ -113,10 +113,10 @@ async def test_light_turn_on_off(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("extended-color-light", "light.mock_extended_color_light"),
("color-temperature-light", "light.mock_color_temperature_light"),
("dimmable-light", "light.mock_dimmable_light"),
("dimmable-plugin-unit", "light.dimmable_plugin_unit"),
("extended-color-light", "light.mock_extended_color_light_light"),
("color-temperature-light", "light.mock_color_temperature_light_light"),
("dimmable-light", "light.mock_dimmable_light_light"),
("dimmable-plugin-unit", "light.dimmable_plugin_unit_light"),
],
)
async def test_dimmable_light(
@ -189,8 +189,8 @@ async def test_dimmable_light(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("extended-color-light", "light.mock_extended_color_light"),
("color-temperature-light", "light.mock_color_temperature_light"),
("extended-color-light", "light.mock_extended_color_light_light"),
("color-temperature-light", "light.mock_color_temperature_light_light"),
],
)
async def test_color_temperature_light(
@ -287,7 +287,7 @@ async def test_color_temperature_light(
@pytest.mark.parametrize(
("fixture", "entity_id"),
[
("extended-color-light", "light.mock_extended_color_light"),
("extended-color-light", "light.mock_extended_color_light_light"),
],
)
async def test_extended_color_light(

View File

@ -41,7 +41,7 @@ async def test_turn_on(
powerplug_node: MatterNode,
) -> None:
"""Test turning on a switch."""
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
state = hass.states.get("switch.mock_onoffpluginunit_switch")
assert state
assert state.state == "off"
@ -49,7 +49,7 @@ async def test_turn_on(
"switch",
"turn_on",
{
"entity_id": "switch.mock_onoffpluginunit_powerplug_switch",
"entity_id": "switch.mock_onoffpluginunit_switch",
},
blocking=True,
)
@ -64,7 +64,7 @@ async def test_turn_on(
set_node_attribute(powerplug_node, 1, 6, 0, True)
await trigger_subscription_callback(hass, matter_client)
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
state = hass.states.get("switch.mock_onoffpluginunit_switch")
assert state
assert state.state == "on"
@ -77,7 +77,7 @@ async def test_turn_off(
powerplug_node: MatterNode,
) -> None:
"""Test turning off a switch."""
state = hass.states.get("switch.mock_onoffpluginunit_powerplug_switch")
state = hass.states.get("switch.mock_onoffpluginunit_switch")
assert state
assert state.state == "off"
@ -85,7 +85,7 @@ async def test_turn_off(
"switch",
"turn_off",
{
"entity_id": "switch.mock_onoffpluginunit_powerplug_switch",
"entity_id": "switch.mock_onoffpluginunit_switch",
},
blocking=True,
)
@ -109,7 +109,23 @@ async def test_switch_unit(
# A switch entity should be discovered as fallback for ANY Matter device (endpoint)
# that has the OnOff cluster and does not fall into an explicit discovery schema
# by another platform (e.g. light, lock etc.).
state = hass.states.get("switch.mock_switchunit")
state = hass.states.get("switch.mock_switchunit_switch")
assert state
assert state.state == "off"
assert state.attributes["friendly_name"] == "Mock SwitchUnit"
assert state.attributes["friendly_name"] == "Mock SwitchUnit Switch"
# This tests needs to be adjusted to remove lingering tasks
@pytest.mark.parametrize("expected_lingering_tasks", [True])
async def test_power_switch(
hass: HomeAssistant,
matter_client: MagicMock,
) -> None:
"""Test if a Power switch entity is created for a device that supports that."""
await setup_integration_with_node_fixture(
hass, "room-airconditioner", matter_client
)
state = hass.states.get("switch.room_airconditioner_power")
assert state
assert state.state == "off"
assert state.attributes["friendly_name"] == "Room AirConditioner Power"