diff --git a/homeassistant/components/matter/select.py b/homeassistant/components/matter/select.py index bf528077b32..350712061ba 100644 --- a/homeassistant/components/matter/select.py +++ b/homeassistant/components/matter/select.py @@ -2,7 +2,12 @@ from __future__ import annotations +from dataclasses import dataclass +from typing import TYPE_CHECKING + from chip.clusters import Objects as clusters +from chip.clusters.Types import Nullable +from matter_server.common.helpers.util import create_attribute_path_from_attribute from homeassistant.components.select import SelectEntity, SelectEntityDescription from homeassistant.config_entries import ConfigEntry @@ -10,7 +15,7 @@ from homeassistant.const import EntityCategory, Platform from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.entity_platform import AddEntitiesCallback -from .entity import MatterEntity +from .entity import MatterEntity, MatterEntityDescription from .helpers import get_matter from .models import MatterDiscoverySchema @@ -38,7 +43,41 @@ async def async_setup_entry( matter.register_platform_handler(Platform.SELECT, async_add_entities) -class MatterModeSelectEntity(MatterEntity, SelectEntity): +@dataclass(frozen=True) +class MatterSelectEntityDescription(SelectEntityDescription, MatterEntityDescription): + """Describe Matter select entities.""" + + +class MatterSelectEntity(MatterEntity, SelectEntity): + """Representation of a select entity from Matter Attribute read/write.""" + + entity_description: MatterSelectEntityDescription + + async def async_select_option(self, option: str) -> None: + """Change the selected mode.""" + value_convert = self.entity_description.ha_to_native_value + if TYPE_CHECKING: + assert value_convert is not None + await self.matter_client.write_attribute( + node_id=self._endpoint.node.node_id, + attribute_path=create_attribute_path_from_attribute( + self._endpoint.endpoint_id, self._entity_info.primary_attribute + ), + value=value_convert(option), + ) + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + value: Nullable | int | None + value = self.get_matter_attribute_value(self._entity_info.primary_attribute) + value_convert = self.entity_description.measurement_to_ha + if TYPE_CHECKING: + assert value_convert is not None + self._attr_current_option = value_convert(value) + + +class MatterModeSelectEntity(MatterSelectEntity): """Representation of a select entity from Matter (Mode) Cluster attribute(s).""" async def async_select_option(self, option: str) -> None: @@ -77,7 +116,7 @@ class MatterModeSelectEntity(MatterEntity, SelectEntity): DISCOVERY_SCHEMAS = [ MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterModeSelect", entity_category=EntityCategory.CONFIG, translation_key="mode", @@ -90,7 +129,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterOvenMode", translation_key="mode", ), @@ -102,7 +141,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterLaundryWasherMode", translation_key="mode", ), @@ -114,7 +153,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterRefrigeratorAndTemperatureControlledCabinetMode", translation_key="mode", ), @@ -126,7 +165,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterRvcRunMode", translation_key="mode", ), @@ -138,7 +177,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterRvcCleanMode", translation_key="mode", ), @@ -150,7 +189,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterDishwasherMode", translation_key="mode", ), @@ -162,7 +201,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterMicrowaveOvenMode", translation_key="mode", ), @@ -174,7 +213,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterEnergyEvseMode", translation_key="mode", ), @@ -186,7 +225,7 @@ DISCOVERY_SCHEMAS = [ ), MatterDiscoverySchema( platform=Platform.SELECT, - entity_description=SelectEntityDescription( + entity_description=MatterSelectEntityDescription( key="MatterDeviceEnergyManagementMode", translation_key="mode", ), @@ -196,4 +235,27 @@ DISCOVERY_SCHEMAS = [ clusters.DeviceEnergyManagementMode.Attributes.SupportedModes, ), ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=MatterSelectEntityDescription( + key="MatterStartUpOnOff", + entity_category=EntityCategory.CONFIG, + translation_key="startup_on_off", + options=["On", "Off", "Toggle", "Previous"], + measurement_to_ha=lambda x: { + 0: "Off", + 1: "On", + 2: "Toggle", + None: "Previous", + }[x], + ha_to_native_value=lambda x: { + "Off": 0, + "On": 1, + "Toggle": 2, + "Previous": None, + }[x], + ), + entity_class=MatterSelectEntity, + required_attributes=(clusters.OnOff.Attributes.StartUpOnOff,), + ), ] diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index c23a2d6fe94..e69c7ae3090 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -134,6 +134,9 @@ "select": { "mode": { "name": "Mode" + }, + "startup_on_off": { + "name": "Power-on behavior on Startup" } }, "sensor": { diff --git a/tests/components/matter/test_select.py b/tests/components/matter/test_select.py index 0d4d5e71b81..9b774f0430b 100644 --- a/tests/components/matter/test_select.py +++ b/tests/components/matter/test_select.py @@ -107,3 +107,25 @@ async def test_microwave_select_entities( await trigger_subscription_callback(hass, matter_client) state = hass.states.get("select.microwave_oven_mode") assert state.state == "Defrost" + + +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_attribute_select_entities( + hass: HomeAssistant, + matter_client: MagicMock, + light_node: MatterNode, +) -> None: + """Test select entities are created for attribute based discovery schema(s).""" + entity_id = "select.mock_dimmable_light_power_on_behavior_on_startup" + state = hass.states.get(entity_id) + assert state + assert state.state == "Previous" + assert state.attributes["options"] == ["On", "Off", "Toggle", "Previous"] + assert ( + state.attributes["friendly_name"] + == "Mock Dimmable Light Power-on behavior on Startup" + ) + set_node_attribute(light_node, 1, 6, 16387, 1) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get(entity_id) + assert state.state == "On"