From 8230bfcf8f58ddfbd52b548c1cd5cfeb9e472736 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Wed, 1 May 2024 11:46:52 +0200 Subject: [PATCH] Some fixes for the Matter light discovery schema (#116108) * Fix discovery schema for light platform * fix switch platform discovery schema * extend light tests * Update switch.py * clarify comment * use parameter for supported_color_modes --- homeassistant/components/matter/light.py | 41 +-- homeassistant/components/matter/switch.py | 6 +- ...onoff-light-with-levelcontrol-present.json | 244 ++++++++++++++++++ tests/components/matter/test_light.py | 29 ++- 4 files changed, 273 insertions(+), 47 deletions(-) create mode 100644 tests/components/matter/fixtures/nodes/onoff-light-with-levelcontrol-present.json diff --git a/homeassistant/components/matter/light.py b/homeassistant/components/matter/light.py index fce780896a4..c9556fd2e2e 100644 --- a/homeassistant/components/matter/light.py +++ b/homeassistant/components/matter/light.py @@ -295,7 +295,10 @@ class MatterLight(MatterEntity, LightEntity): # brightness support if self._entity_info.endpoint.has_attribute( None, clusters.LevelControl.Attributes.CurrentLevel - ): + ) and self._entity_info.endpoint.device_types != {device_types.OnOffLight}: + # We need to filter out the OnOffLight device type here because + # that can have an optional LevelControl cluster present + # which we should ignore. supported_color_modes.add(ColorMode.BRIGHTNESS) self._supports_brightness = True # colormode(s) @@ -406,11 +409,11 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterLight, required_attributes=( clusters.OnOff.Attributes.OnOff, - clusters.LevelControl.Attributes.CurrentLevel, clusters.ColorControl.Attributes.CurrentHue, clusters.ColorControl.Attributes.CurrentSaturation, ), optional_attributes=( + clusters.LevelControl.Attributes.CurrentLevel, clusters.ColorControl.Attributes.ColorTemperatureMireds, clusters.ColorControl.Attributes.ColorMode, clusters.ColorControl.Attributes.CurrentX, @@ -426,11 +429,11 @@ DISCOVERY_SCHEMAS = [ entity_class=MatterLight, required_attributes=( clusters.OnOff.Attributes.OnOff, - clusters.LevelControl.Attributes.CurrentLevel, clusters.ColorControl.Attributes.CurrentX, clusters.ColorControl.Attributes.CurrentY, ), optional_attributes=( + clusters.LevelControl.Attributes.CurrentLevel, clusters.ColorControl.Attributes.ColorTemperatureMireds, clusters.ColorControl.Attributes.ColorMode, clusters.ColorControl.Attributes.CurrentHue, @@ -451,36 +454,4 @@ DISCOVERY_SCHEMAS = [ ), optional_attributes=(clusters.ColorControl.Attributes.ColorMode,), ), - # Additional schema to match generic dimmable lights with incorrect/missing device type - MatterDiscoverySchema( - platform=Platform.LIGHT, - entity_description=LightEntityDescription( - key="MatterDimmableLightFallback", name=None - ), - entity_class=MatterLight, - required_attributes=( - clusters.OnOff.Attributes.OnOff, - clusters.LevelControl.Attributes.CurrentLevel, - ), - optional_attributes=( - clusters.ColorControl.Attributes.ColorMode, - clusters.ColorControl.Attributes.CurrentHue, - clusters.ColorControl.Attributes.CurrentSaturation, - clusters.ColorControl.Attributes.CurrentX, - clusters.ColorControl.Attributes.CurrentY, - clusters.ColorControl.Attributes.ColorTemperatureMireds, - ), - # important: make sure to rule out all device types that are also based on the - # onoff and levelcontrol clusters ! - not_device_type=( - device_types.Fan, - device_types.GenericSwitch, - device_types.OnOffPlugInUnit, - device_types.HeatingCoolingUnit, - device_types.Pump, - device_types.CastingVideoClient, - device_types.VideoRemoteControl, - device_types.Speaker, - ), - ), ] diff --git a/homeassistant/components/matter/switch.py b/homeassistant/components/matter/switch.py index 9bc858d40c0..f148102cfcd 100644 --- a/homeassistant/components/matter/switch.py +++ b/homeassistant/components/matter/switch.py @@ -81,12 +81,8 @@ DISCOVERY_SCHEMAS = [ device_types.ColorTemperatureLight, device_types.DimmableLight, device_types.ExtendedColorLight, - device_types.OnOffLight, - device_types.DoorLock, device_types.ColorDimmerSwitch, - device_types.DimmerSwitch, - device_types.Thermostat, - device_types.RoomAirConditioner, + device_types.OnOffLight, ), ), ] diff --git a/tests/components/matter/fixtures/nodes/onoff-light-with-levelcontrol-present.json b/tests/components/matter/fixtures/nodes/onoff-light-with-levelcontrol-present.json new file mode 100644 index 00000000000..c1264f5b7ea --- /dev/null +++ b/tests/components/matter/fixtures/nodes/onoff-light-with-levelcontrol-present.json @@ -0,0 +1,244 @@ +{ + "node_id": 8, + "date_commissioned": "2024-03-07T01:39:20.590755", + "last_interview": "2024-04-02T14:16:31.045880", + "interview_version": 6, + "available": true, + "is_bridge": false, + "attributes": { + "0/29/0": [ + { + "0": 22, + "1": 1 + } + ], + "0/29/1": [29, 31, 40, 42, 43, 44, 48, 49, 50, 51, 52, 54, 60, 62, 63], + "0/29/2": [41], + "0/29/3": [1], + "0/29/65532": 0, + "0/29/65533": 1, + "0/29/65528": [], + "0/29/65529": [], + "0/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/31/0": [ + { + "254": 1 + }, + { + "1": 5, + "2": 2, + "3": [112233], + "4": null, + "254": 2 + } + ], + "0/31/1": [], + "0/31/2": 4, + "0/31/3": 3, + "0/31/4": 3, + "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": 1, + "0/40/1": "Leviton", + "0/40/2": 4251, + "0/40/3": "D215S", + "0/40/4": 4097, + "0/40/5": "", + "0/40/6": "**REDACTED**", + "0/40/7": 1, + "0/40/8": "1.0", + "0/40/9": 131365, + "0/40/10": "2.1.25", + "0/40/15": "12345678", + "0/40/18": "abcdefgh", + "0/40/19": { + "0": 3, + "1": 3 + }, + "0/40/65532": 0, + "0/40/65533": 1, + "0/40/65528": [], + "0/40/65529": [], + "0/40/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 18, 19, 65528, 65529, 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/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/43/0": "en-US", + "0/43/1": [ + "en-US", + "de-DE", + "fr-FR", + "en-GB", + "es-ES", + "zh-CN", + "it-IT", + "ja-JP" + ], + "0/43/65532": 0, + "0/43/65533": 1, + "0/43/65528": [], + "0/43/65529": [], + "0/43/65531": [0, 1, 65528, 65529, 65531, 65532, 65533], + "0/44/0": 0, + "0/44/1": 0, + "0/44/2": [0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 7], + "0/44/65532": 0, + "0/44/65533": 1, + "0/44/65528": [], + "0/44/65529": [], + "0/44/65531": [0, 1, 2, 65528, 65529, 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/65531": [0, 1, 2, 3, 4, 65528, 65529, 65531, 65532, 65533], + "0/49/0": 1, + "0/49/2": 10, + "0/49/3": 20, + "0/49/4": true, + "0/49/5": 0, + "0/49/7": null, + "0/49/65532": 1, + "0/49/65533": 1, + "0/49/65528": [1, 5, 7], + "0/49/65529": [0, 2, 4, 6, 8], + "0/49/65531": [0, 1, 2, 3, 4, 5, 6, 7, 65528, 65529, 65531, 65532, 65533], + "0/50/65532": 0, + "0/50/65533": 1, + "0/50/65528": [1], + "0/50/65529": [0], + "0/50/65531": [65528, 65529, 65531, 65532, 65533], + "0/51/1": 1, + "0/51/2": 2380987, + "0/51/3": 661, + "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/65531": [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 65528, 65529, 65531, 65532, 65533 + ], + "0/52/1": 49792, + "0/52/2": 262528, + "0/52/3": 272704, + "0/52/65532": 1, + "0/52/65533": 1, + "0/52/65528": [], + "0/52/65529": [0], + "0/52/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533], + "0/54/0": "blah", + "0/54/1": 4, + "0/54/2": 3, + "0/54/3": 11, + "0/54/4": -43, + "0/54/65532": 0, + "0/54/65533": 1, + "0/54/65528": [], + "0/54/65529": [], + "0/54/65531": [0, 1, 2, 3, 4, 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, 1, 2], + "0/60/65531": [0, 1, 2, 65528, 65529, 65531, 65532, 65533], + "0/62/5": 2, + "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": 3, + "0/63/3": 3, + "0/63/65532": 0, + "0/63/65533": 1, + "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": 2, + "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/4/0": 128, + "1/4/65532": 1, + "1/4/65533": 4, + "1/4/65528": [0, 1, 2, 3], + "1/4/65529": [0, 1, 2, 3, 4, 5], + "1/4/65531": [0, 65528, 65529, 65531, 65532, 65533], + "1/6/0": false, + "1/6/16384": true, + "1/6/16385": 0, + "1/6/16386": 0, + "1/6/16387": null, + "1/6/65532": 1, + "1/6/65533": 4, + "1/6/65528": [], + "1/6/65529": [0, 1, 2, 64, 65, 66], + "1/6/65531": [ + 0, 16384, 16385, 16386, 16387, 65528, 65529, 65531, 65532, 65533 + ], + "1/8/0": 254, + "1/8/1": 0, + "1/8/2": 1, + "1/8/3": 254, + "1/8/15": 0, + "1/8/16": 0, + "1/8/17": null, + "1/8/20": 50, + "1/8/16384": null, + "1/8/65532": 3, + "1/8/65533": 5, + "1/8/65528": [], + "1/8/65529": [0, 1, 2, 3, 4, 5, 6, 7], + "1/8/65531": [ + 0, 1, 2, 3, 15, 16, 17, 20, 16384, 65528, 65529, 65531, 65532, 65533 + ], + "1/29/0": [ + { + "0": 256, + "1": 1 + } + ], + "1/29/1": [3, 4, 6, 8, 29], + "1/29/2": [], + "1/29/3": [], + "1/29/65532": 0, + "1/29/65533": 1, + "1/29/65528": [], + "1/29/65529": [], + "1/29/65531": [0, 1, 2, 3, 65528, 65529, 65531, 65532, 65533] + }, + "attribute_subscriptions": [] +} diff --git a/tests/components/matter/test_light.py b/tests/components/matter/test_light.py index 9c3c2610d92..775790701d1 100644 --- a/tests/components/matter/test_light.py +++ b/tests/components/matter/test_light.py @@ -18,21 +18,31 @@ from .common import ( # This tests needs to be adjusted to remove lingering tasks @pytest.mark.parametrize("expected_lingering_tasks", [True]) @pytest.mark.parametrize( - ("fixture", "entity_id"), + ("fixture", "entity_id", "supported_color_modes"), [ - ("extended-color-light", "light.mock_extended_color_light"), - ("color-temperature-light", "light.mock_color_temperature_light"), - ("dimmable-light", "light.mock_dimmable_light"), - ("onoff-light", "light.mock_onoff_light"), + ( + "extended-color-light", + "light.mock_extended_color_light", + ["color_temp", "hs", "xy"], + ), + ( + "color-temperature-light", + "light.mock_color_temperature_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"]), ], ) -async def test_on_off_light( +async def test_light_turn_on_off( hass: HomeAssistant, matter_client: MagicMock, fixture: str, entity_id: str, + supported_color_modes: list[str], ) -> None: - """Test an on/off light.""" + """Test basic light discovery and turn on/off.""" light_node = await setup_integration_with_node_fixture( hass, @@ -48,6 +58,11 @@ async def test_on_off_light( assert state is not None assert state.state == "off" + # check the supported_color_modes + # especially important is the onoff light device type that does have + # a levelcontrol cluster present which we should ignore + assert state.attributes["supported_color_modes"] == supported_color_modes + # Test that the light is on set_node_attribute(light_node, 1, 6, 0, True) await trigger_subscription_callback(hass, matter_client)