From d5135d49564c6af18058eff4edffbeafa5e29bfd Mon Sep 17 00:00:00 2001 From: jvmahon Date: Thu, 4 Jul 2024 12:29:10 -0400 Subject: [PATCH] Add support for the Select platform in Matter (#119769) * Add support for ModeSelect Cluster * Update discovery.py * Add files via upload * refactor part 1 * Update discovery.py * add remaining mode discovery schemas * add test * type alias --------- Co-authored-by: Marcel van der Veldt --- homeassistant/components/matter/discovery.py | 5 +- homeassistant/components/matter/select.py | 199 +++++++++ homeassistant/components/matter/strings.json | 5 + .../matter/fixtures/nodes/dimmable-light.json | 143 ++++++- .../matter/fixtures/nodes/microwave-oven.json | 405 ++++++++++++++++++ tests/components/matter/test_select.py | 109 +++++ 6 files changed, 863 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/matter/select.py create mode 100644 tests/components/matter/fixtures/nodes/microwave-oven.json create mode 100644 tests/components/matter/test_select.py diff --git a/homeassistant/components/matter/discovery.py b/homeassistant/components/matter/discovery.py index 510b77c24c71a..774b67258f100 100644 --- a/homeassistant/components/matter/discovery.py +++ b/homeassistant/components/matter/discovery.py @@ -2,10 +2,9 @@ from __future__ import annotations -from collections.abc import Generator - from chip.clusters.Objects import ClusterAttributeDescriptor from matter_server.client.models.node import MatterEndpoint +from typing_extensions import Generator from homeassistant.const import Platform from homeassistant.core import callback @@ -19,6 +18,7 @@ from .lock import DISCOVERY_SCHEMAS as LOCK_SCHEMAS from .models import MatterDiscoverySchema, MatterEntityInfo from .number import DISCOVERY_SCHEMAS as NUMBER_SCHEMAS +from .select import DISCOVERY_SCHEMAS as SELECT_SCHEMAS from .sensor import DISCOVERY_SCHEMAS as SENSOR_SCHEMAS from .switch import DISCOVERY_SCHEMAS as SWITCH_SCHEMAS @@ -33,6 +33,7 @@ Platform.NUMBER: NUMBER_SCHEMAS, Platform.SENSOR: SENSOR_SCHEMAS, Platform.SWITCH: SWITCH_SCHEMAS, + Platform.SELECT: SELECT_SCHEMAS, } SUPPORTED_PLATFORMS = tuple(DISCOVERY_SCHEMAS) diff --git a/homeassistant/components/matter/select.py b/homeassistant/components/matter/select.py new file mode 100644 index 0000000000000..bf528077b32ea --- /dev/null +++ b/homeassistant/components/matter/select.py @@ -0,0 +1,199 @@ +"""Matter ModeSelect Cluster Support.""" + +from __future__ import annotations + +from chip.clusters import Objects as clusters + +from homeassistant.components.select import SelectEntity, SelectEntityDescription +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import EntityCategory, Platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .entity import MatterEntity +from .helpers import get_matter +from .models import MatterDiscoverySchema + +type SelectCluster = ( + clusters.ModeSelect + | clusters.OvenMode + | clusters.LaundryWasherMode + | clusters.RefrigeratorAndTemperatureControlledCabinetMode + | clusters.RvcRunMode + | clusters.RvcCleanMode + | clusters.DishwasherMode + | clusters.MicrowaveOvenMode + | clusters.EnergyEvseMode + | clusters.DeviceEnergyManagementMode +) + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up Matter ModeSelect from Config Entry.""" + matter = get_matter(hass) + matter.register_platform_handler(Platform.SELECT, async_add_entities) + + +class MatterModeSelectEntity(MatterEntity, SelectEntity): + """Representation of a select entity from Matter (Mode) Cluster attribute(s).""" + + async def async_select_option(self, option: str) -> None: + """Change the selected mode.""" + cluster: SelectCluster = self._endpoint.get_cluster( + self._entity_info.primary_attribute.cluster_id + ) + # select the mode ID from the label string + for mode in cluster.supportedModes: + if mode.label != option: + continue + await self.matter_client.send_device_command( + node_id=self._endpoint.node.node_id, + endpoint_id=self._endpoint.endpoint_id, + command=cluster.Commands.ChangeToMode(newMode=mode.mode), + ) + break + + @callback + def _update_from_device(self) -> None: + """Update from device.""" + # NOTE: cluster can be ModeSelect or a variant of that, + # such as DishwasherMode. They all have the same characteristics. + cluster: SelectCluster = self._endpoint.get_cluster( + self._entity_info.primary_attribute.cluster_id + ) + modes = {mode.mode: mode.label for mode in cluster.supportedModes} + self._attr_options = list(modes.values()) + self._attr_current_option = modes[cluster.currentMode] + # handle optional Description attribute as descriptive name for the mode + if desc := getattr(cluster, "description", None): + self._attr_name = desc + + +# Discovery schema(s) to map Matter Attributes to HA entities +DISCOVERY_SCHEMAS = [ + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterModeSelect", + entity_category=EntityCategory.CONFIG, + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.ModeSelect.Attributes.CurrentMode, + clusters.ModeSelect.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterOvenMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.OvenMode.Attributes.CurrentMode, + clusters.OvenMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterLaundryWasherMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.LaundryWasherMode.Attributes.CurrentMode, + clusters.LaundryWasherMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterRefrigeratorAndTemperatureControlledCabinetMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.CurrentMode, + clusters.RefrigeratorAndTemperatureControlledCabinetMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterRvcRunMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.RvcRunMode.Attributes.CurrentMode, + clusters.RvcRunMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterRvcCleanMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.RvcCleanMode.Attributes.CurrentMode, + clusters.RvcCleanMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterDishwasherMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.DishwasherMode.Attributes.CurrentMode, + clusters.DishwasherMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterMicrowaveOvenMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.MicrowaveOvenMode.Attributes.CurrentMode, + clusters.MicrowaveOvenMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterEnergyEvseMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.EnergyEvseMode.Attributes.CurrentMode, + clusters.EnergyEvseMode.Attributes.SupportedModes, + ), + ), + MatterDiscoverySchema( + platform=Platform.SELECT, + entity_description=SelectEntityDescription( + key="MatterDeviceEnergyManagementMode", + translation_key="mode", + ), + entity_class=MatterModeSelectEntity, + required_attributes=( + clusters.DeviceEnergyManagementMode.Attributes.CurrentMode, + clusters.DeviceEnergyManagementMode.Attributes.SupportedModes, + ), + ), +] diff --git a/homeassistant/components/matter/strings.json b/homeassistant/components/matter/strings.json index 3389a4bfe81db..0a823d5aa801c 100644 --- a/homeassistant/components/matter/strings.json +++ b/homeassistant/components/matter/strings.json @@ -125,6 +125,11 @@ "name": "[%key:component::lock::title%]" } }, + "select": { + "mode": { + "name": "Mode" + } + }, "sensor": { "activated_carbon_filter_condition": { "name": "Activated carbon filter condition" diff --git a/tests/components/matter/fixtures/nodes/dimmable-light.json b/tests/components/matter/fixtures/nodes/dimmable-light.json index 74f132a88a9b0..aad0afdfdcd4e 100644 --- a/tests/components/matter/fixtures/nodes/dimmable-light.json +++ b/tests/components/matter/fixtures/nodes/dimmable-light.json @@ -365,7 +365,148 @@ "1/29/65533": 1, "1/29/65528": [], "1/29/65529": [], - "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "6/80/0": "LED Color", + "6/80/1": 0, + "6/80/2": [ + { + "0": "Red", + "1": 0, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Orange", + "1": 1, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Lemon", + "1": 2, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Lime", + "1": 3, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Green", + "1": 4, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Teal", + "1": 5, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Cyan", + "1": 6, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Aqua", + "1": 7, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Blue", + "1": 8, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Violet", + "1": 9, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Magenta", + "1": 10, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "Pink", + "1": 11, + "2": [ + { + "0": 0, + "1": 0 + } + ] + }, + { + "0": "White", + "1": 12, + "2": [ + { + "0": 0, + "1": 0 + } + ] + } + ], + "6/80/3": 7, + "6/80/65532": 0, + "6/80/65533": 1, + "6/80/65528": [], + "6/80/65529": [0], + "6/80/65530": [], + "6/80/65531": [0, 1, 2, 3, 65528, 65529, 65530, 65531, 65532, 65533] }, "available": true, "attribute_subscriptions": [] diff --git a/tests/components/matter/fixtures/nodes/microwave-oven.json b/tests/components/matter/fixtures/nodes/microwave-oven.json new file mode 100644 index 0000000000000..ed0a4accd6ae9 --- /dev/null +++ b/tests/components/matter/fixtures/nodes/microwave-oven.json @@ -0,0 +1,405 @@ +{ + "node_id": 157, + "date_commissioned": "2024-07-04T12:31:22.759270", + "last_interview": "2024-07-04T12:31:22.759275", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 44, 48, 49, 51, 54, 60, 62, 63], + "0/29/2": [], + "0/29/3": [1], + "0/29/65532": 0, + "0/29/65533": 2, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/31/0": [ + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 1 + } + ], + "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/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/40/0": 17, + "0/40/1": "Mock", + "0/40/2": 65521, + "0/40/3": "Microwave Oven", + "0/40/4": 32769, + "0/40/5": "", + "0/40/6": "**REDACTED**", + "0/40/7": 0, + "0/40/8": "TEST_VERSION", + "0/40/9": 1, + "0/40/10": "1.0", + "0/40/11": "20200101", + "0/40/12": "", + "0/40/13": "", + "0/40/14": "", + "0/40/15": "TEST_SN", + "0/40/16": false, + "0/40/18": "D5908CF5E1382F42", + "0/40/19": { + "0": 3, + "1": 65535 + }, + "0/40/20": null, + "0/40/21": 16973824, + "0/40/22": 1, + "0/40/65532": 0, + "0/40/65533": 3, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, + 22, 65528, 65529, 65531, 65532, 65533 + ], + "0/44/0": 0, + "0/44/65532": 0, + "0/44/65533": 1, + "0/44/65528": [], + "0/44/65529": [], + "0/44/65531": [0, 65528, 65529, 65531, 65532, 65533], + "0/48/0": 0, + "0/48/1": { + "0": 60, + "1": 900 + }, + "0/48/2": 0, + "0/48/3": 2, + "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/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/49/0": 1, + "0/49/1": [ + { + "0": "ZW5kMA==", + "1": true + } + ], + "0/49/2": 0, + "0/49/3": 0, + "0/49/4": true, + "0/49/5": null, + "0/49/6": null, + "0/49/7": null, + "0/49/65532": 4, + "0/49/65533": 2, + "0/49/65528": [], + "0/49/65529": [], + "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/51/0": [ + { + "0": "vethd3cc78a", + "1": true, + "2": null, + "3": null, + "4": "RiMoOM7I", + "5": [], + "6": ["/oAAAAAAAABEIyj//jjOyA=="], + "7": 0 + }, + { + "0": "veth86f4b74", + "1": true, + "2": null, + "3": null, + "4": "ehLA7XI6", + "5": [], + "6": ["/oAAAAAAAAB4EsD//u1yOg=="], + "7": 0 + }, + { + "0": "veth36c1460", + "1": true, + "2": null, + "3": null, + "4": "0sdiwOO7", + "5": [], + "6": ["/oAAAAAAAADQx2L//sDjuw=="], + "7": 0 + }, + { + "0": "veth55a0982", + "1": true, + "2": null, + "3": null, + "4": "fuu5VpgB", + "5": [], + "6": ["/oAAAAAAAAB867n//laYAQ=="], + "7": 0 + }, + { + "0": "vethd446fa5", + "1": true, + "2": null, + "3": null, + "4": "QsY5wCp1", + "5": [], + "6": ["/oAAAAAAAABAxjn//sAqdQ=="], + "7": 0 + }, + { + "0": "vethfc6e4d6", + "1": true, + "2": null, + "3": null, + "4": "IsHWia4E", + "5": [], + "6": ["/oAAAAAAAAAgwdb//omuBA=="], + "7": 0 + }, + { + "0": "veth4b35142", + "1": true, + "2": null, + "3": null, + "4": "RizM/XJz", + "5": [], + "6": ["/oAAAAAAAABELMz//v1ycw=="], + "7": 0 + }, + { + "0": "vetha0a808d", + "1": true, + "2": null, + "3": null, + "4": "JrxkpiTq", + "5": [], + "6": ["/oAAAAAAAAAkvGT//qYk6g=="], + "7": 0 + }, + { + "0": "hassio", + "1": true, + "2": null, + "3": null, + "4": "AkL+6fKF", + "5": ["rB4gAQ=="], + "6": ["/oAAAAAAAAAAQv7//unyhQ=="], + "7": 0 + }, + { + "0": "docker0", + "1": true, + "2": null, + "3": null, + "4": "AkKzcIpP", + "5": ["rB7oAQ=="], + "6": ["/oAAAAAAAAAAQrP//nCKTw=="], + "7": 0 + }, + { + "0": "end0", + "1": true, + "2": null, + "3": null, + "4": "5F8BoroJ", + "5": ["wKgBAg=="], + "6": [ + "KgKkZACnAAHGF8Tinim+lQ==", + "/XH1Cm7wY08fhLPRgO32Uw==", + "/oAAAAAAAAAENYnD2gV25w==" + ], + "7": 2 + }, + { + "0": "lo", + "1": true, + "2": null, + "3": null, + "4": "AAAAAAAA", + "5": ["fwAAAQ=="], + "6": ["AAAAAAAAAAAAAAAAAAAAAQ=="], + "7": 0 + } + ], + "0/51/1": 1, + "0/51/2": 16, + "0/51/3": 0, + "0/51/4": 0, + "0/51/5": [], + "0/51/6": [], + "0/51/7": [], + "0/51/8": false, + "0/51/65532": 0, + "0/51/65533": 2, + "0/51/65528": [2], + "0/51/65529": [0, 1], + "0/51/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 + ], + "0/54/0": null, + "0/54/1": null, + "0/54/2": null, + "0/54/3": null, + "0/54/4": null, + "0/54/5": null, + "0/54/6": null, + "0/54/7": null, + "0/54/8": null, + "0/54/9": null, + "0/54/10": null, + "0/54/11": null, + "0/54/12": null, + "0/54/65532": 3, + "0/54/65533": 1, + "0/54/65528": [], + "0/54/65529": [0], + "0/54/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 65528, 65529, 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, 2], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/62/0": [ + { + "1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVASQRnRgkBwEkCAEwCUEEleMInA+X+lZO6bSa7ysHaAvYS13Fg9GoRuhiFk+wvtjLUrouyH+DUp3p3purrVdfUWTp03damVsxp9Lv48goDzcKNQEoARgkAgE2AwQCBAEYMAQUrD2d44zyVXjKbyYgNaEibaXFI7IwBRTphWiJ/NqGe3Cx3Nj8H02NgGioSRgwC0CaASOOwmsHE8cNw7FhQDtRhh0ztvwdfZKANU93vrX/+ww8UifrTjUIgvobgixpCGxmGvEmk3RN7TX6lgX4Qz7MGA==", + "2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEEYztrLK2UY1ORHUEFLO7PDfVjw/MnMDNX5kjdHHDU7npeITnSyg/kxxUM+pD7ccxfDuHQKHbBq9+qbJi8oGik8DcKNQEpARgkAmAwBBTphWiJ/NqGe3Cx3Nj8H02NgGioSTAFFMnf5ZkBCRaBluhSmLJkvcVXxHxTGDALQOOcZAL8XEktvE5sjrUmFNhkP2g3Ef+4BHtogItdZYyA9E/WbzW25E0UxZInwjjIzH3YimDUZVoEWGML8NV2kCEY", + "254": 1 + } + ], + "0/62/1": [ + { + "1": "BAg5aeR7RuFKZhukCxMGglCd00dKlhxGq8BbjeyZClKz5kN2Ytzav0xWsiWEEb3s9uvMIYFoQYULnSJvOMTcD14=", + "2": 65521, + "3": 1, + "4": 157, + "5": "", + "254": 1 + } + ], + "0/62/2": 16, + "0/62/3": 1, + "0/62/4": [ + "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEECDlp5HtG4UpmG6QLEwaCUJ3TR0qWHEarwFuN7JkKUrPmQ3Zi3Nq/TFayJYQRvez268whgWhBhQudIm84xNwPXjcKNQEpARgkAmAwBBTJ3+WZAQkWgZboUpiyZL3FV8R8UzAFFMnf5ZkBCRaBluhSmLJkvcVXxHxTGDALQO9QSAdvJkM6b/wIc07MCw1ma46lTyGYG8nvpn0ICI73nuD3QeaWwGIQTkVGEpzF+TuDK7gtTz7YUrR+PSnvMk8Y" + ], + "0/62/5": 1, + "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/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 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/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/3/0": 0, + "1/3/1": 0, + "1/3/65532": 0, + "1/3/65533": 4, + "1/3/65528": [], + "1/3/65529": [0, 64], + "1/3/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "1/29/0": [ + { + "0": 121, + "1": 1 + } + ], + "1/29/1": [3, 29, 94, 95, 96], + "1/29/2": [], + "1/29/3": [], + "1/29/65532": 0, + "1/29/65533": 2, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "1/94/0": [ + { + "0": "Normal", + "1": 0, + "2": [ + { + "1": 16384 + } + ] + }, + { + "0": "Defrost", + "1": 1, + "2": [ + { + "1": 16385 + } + ] + } + ], + "1/94/1": 0, + "1/94/65532": 0, + "1/94/65533": 1, + "1/94/65528": [], + "1/94/65529": [], + "1/94/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "1/95/0": 30, + "1/95/1": 86400, + "1/95/2": 90, + "1/95/3": 20, + "1/95/4": 90, + "1/95/5": 10, + "1/95/8": 1000, + "1/95/65532": 5, + "1/95/65533": 1, + "1/95/65528": [], + "1/95/65529": [0, 1], + "1/95/65531": [0, 1, 2, 3, 4, 5, 8, 65528, 65529, 65531, 65532, 65533], + "1/96/0": null, + "1/96/1": null, + "1/96/2": 30, + "1/96/3": [ + { + "0": 0 + }, + { + "0": 1 + }, + { + "0": 2 + }, + { + "0": 3 + } + ], + "1/96/4": 0, + "1/96/5": { + "0": 0 + }, + "1/96/65532": 0, + "1/96/65533": 2, + "1/96/65528": [4], + "1/96/65529": [0, 1, 2, 3], + "1/96/65531": [0, 1, 2, 3, 4, 5, 65528, 65529, 65531, 65532, 65533] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/test_select.py b/tests/components/matter/test_select.py new file mode 100644 index 0000000000000..0d4d5e71b81e9 --- /dev/null +++ b/tests/components/matter/test_select.py @@ -0,0 +1,109 @@ +"""Test Matter select entities.""" + +from unittest.mock import MagicMock, call + +from chip.clusters import Objects as clusters +from matter_server.client.models.node import MatterNode +import pytest + +from homeassistant.core import HomeAssistant + +from .common import ( + set_node_attribute, + setup_integration_with_node_fixture, + trigger_subscription_callback, +) + + +@pytest.fixture(name="light_node") +async def dimmable_light_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a dimmable light node.""" + return await setup_integration_with_node_fixture( + hass, "dimmable-light", matter_client + ) + + +@pytest.fixture(name="microwave_oven_node") +async def microwave_oven_node_fixture( + hass: HomeAssistant, matter_client: MagicMock +) -> MatterNode: + """Fixture for a microwave oven node.""" + return await setup_integration_with_node_fixture( + hass, "microwave-oven", matter_client + ) + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_mode_select_entities( + hass: HomeAssistant, + matter_client: MagicMock, + light_node: MatterNode, +) -> None: + """Test select entities are created for the ModeSelect cluster attributes.""" + state = hass.states.get("select.mock_dimmable_light_led_color") + assert state + assert state.state == "Aqua" + assert state.attributes["options"] == [ + "Red", + "Orange", + "Lemon", + "Lime", + "Green", + "Teal", + "Cyan", + "Aqua", + "Blue", + "Violet", + "Magenta", + "Pink", + "White", + ] + # name should be derived from description attribute + assert state.attributes["friendly_name"] == "Mock Dimmable Light LED Color" + set_node_attribute(light_node, 6, 80, 3, 1) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("select.mock_dimmable_light_led_color") + assert state.state == "Orange" + # test select option + await hass.services.async_call( + "select", + "select_option", + { + "entity_id": "select.mock_dimmable_light_led_color", + "option": "Lime", + }, + blocking=True, + ) + + assert matter_client.send_device_command.call_count == 1 + assert matter_client.send_device_command.call_args == call( + node_id=light_node.node_id, + endpoint_id=6, + command=clusters.ModeSelect.Commands.ChangeToMode(newMode=3), + ) + + +# This tests needs to be adjusted to remove lingering tasks +@pytest.mark.parametrize("expected_lingering_tasks", [True]) +async def test_microwave_select_entities( + hass: HomeAssistant, + matter_client: MagicMock, + microwave_oven_node: MatterNode, +) -> None: + """Test select entities are created for the MicrowaveOvenMode cluster attributes.""" + state = hass.states.get("select.microwave_oven_mode") + assert state + assert state.state == "Normal" + assert state.attributes["options"] == [ + "Normal", + "Defrost", + ] + # name should just be Mode (from the translation key) + assert state.attributes["friendly_name"] == "Microwave Oven Mode" + set_node_attribute(microwave_oven_node, 1, 94, 1, 1) + await trigger_subscription_callback(hass, matter_client) + state = hass.states.get("select.microwave_oven_mode") + assert state.state == "Defrost"