diff --git a/homeassistant/components/homekit_controller/light.py b/homeassistant/components/homekit_controller/light.py index 2b82f27022b..2e851376f8a 100644 --- a/homeassistant/components/homekit_controller/light.py +++ b/homeassistant/components/homekit_controller/light.py @@ -10,9 +10,10 @@ from homeassistant.components.light import ( ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, - SUPPORT_BRIGHTNESS, - SUPPORT_COLOR, - SUPPORT_COLOR_TEMP, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_ONOFF, LightEntity, ) from homeassistant.config_entries import ConfigEntry @@ -79,23 +80,43 @@ class HomeKitLight(HomeKitEntity, LightEntity): return self.service.value(CharacteristicsTypes.COLOR_TEMPERATURE) @property - def supported_features(self) -> int: - """Flag supported features.""" - features = 0 - - if self.service.has(CharacteristicsTypes.BRIGHTNESS): - features |= SUPPORT_BRIGHTNESS + def color_mode(self) -> str: + """Return the color mode of the light.""" + # aiohomekit does not keep track of the light's color mode, report + # hs for light supporting both hs and ct + if self.service.has(CharacteristicsTypes.HUE) or self.service.has( + CharacteristicsTypes.SATURATION + ): + return COLOR_MODE_HS if self.service.has(CharacteristicsTypes.COLOR_TEMPERATURE): - features |= SUPPORT_COLOR_TEMP + return COLOR_MODE_COLOR_TEMP - if self.service.has(CharacteristicsTypes.HUE): - features |= SUPPORT_COLOR + if self.service.has(CharacteristicsTypes.BRIGHTNESS): + return COLOR_MODE_BRIGHTNESS - if self.service.has(CharacteristicsTypes.SATURATION): - features |= SUPPORT_COLOR + return COLOR_MODE_ONOFF - return features + @property + def supported_color_modes(self) -> set[str] | None: + """Flag supported color modes.""" + color_modes = set() + + if self.service.has(CharacteristicsTypes.HUE) or self.service.has( + CharacteristicsTypes.SATURATION + ): + color_modes.add(COLOR_MODE_HS) + + if self.service.has(CharacteristicsTypes.COLOR_TEMPERATURE): + color_modes.add(COLOR_MODE_COLOR_TEMP) + + if not color_modes and self.service.has(CharacteristicsTypes.BRIGHTNESS): + color_modes.add(COLOR_MODE_BRIGHTNESS) + + if not color_modes: + color_modes.add(COLOR_MODE_ONOFF) + + return color_modes async def async_turn_on(self, **kwargs: Any) -> None: """Turn the specified light on.""" diff --git a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py index b75c8993904..b2428cdc42b 100644 --- a/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py +++ b/tests/components/homekit_controller/specific_devices/test_aqara_gateway.py @@ -8,7 +8,6 @@ from homeassistant.components.alarm_control_panel import ( SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, ) -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR from homeassistant.components.number import NumberMode from homeassistant.helpers.entity import EntityCategory @@ -52,7 +51,7 @@ async def test_aqara_gateway_setup(hass): "light.aqara_hub_1563", friendly_name="Aqara Hub-1563", unique_id="homekit-0000000123456789-65792", - supported_features=SUPPORT_BRIGHTNESS | SUPPORT_COLOR, + supported_features=0, capabilities={"supported_color_modes": ["hs"]}, state="off", ), diff --git a/tests/components/homekit_controller/specific_devices/test_arlo_baby.py b/tests/components/homekit_controller/specific_devices/test_arlo_baby.py index 9f05baf2a60..9afe152c7b3 100644 --- a/tests/components/homekit_controller/specific_devices/test_arlo_baby.py +++ b/tests/components/homekit_controller/specific_devices/test_arlo_baby.py @@ -1,6 +1,5 @@ """Make sure that an Arlo Baby can be setup.""" -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -75,7 +74,7 @@ async def test_arlo_baby_setup(hass): entity_id="light.arlobabya0", unique_id="homekit-00A0000000000-1100", friendly_name="ArloBabyA0", - supported_features=SUPPORT_BRIGHTNESS | SUPPORT_COLOR, + supported_features=0, capabilities={"supported_color_modes": ["hs"]}, state="off", ), diff --git a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py index 33a1ebdbafe..88d483bd5bc 100644 --- a/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py +++ b/tests/components/homekit_controller/specific_devices/test_koogeek_ls1.py @@ -8,7 +8,6 @@ from aiohomekit.model import CharacteristicsTypes, ServicesTypes from aiohomekit.testing import FakePairing import pytest -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR from homeassistant.helpers.entity import EntityCategory import homeassistant.util.dt as dt_util @@ -47,7 +46,7 @@ async def test_koogeek_ls1_setup(hass): entity_id="light.koogeek_ls1_20833f", friendly_name="Koogeek-LS1-20833F", unique_id="homekit-AAAA011111111111-7", - supported_features=SUPPORT_BRIGHTNESS | SUPPORT_COLOR, + supported_features=0, capabilities={"supported_color_modes": ["hs"]}, state="off", ), diff --git a/tests/components/homekit_controller/specific_devices/test_mysa_living.py b/tests/components/homekit_controller/specific_devices/test_mysa_living.py index 1d99e9358c8..5829bd4e165 100644 --- a/tests/components/homekit_controller/specific_devices/test_mysa_living.py +++ b/tests/components/homekit_controller/specific_devices/test_mysa_living.py @@ -1,7 +1,6 @@ """Make sure that Mysa Living is enumerated properly.""" from homeassistant.components.climate import SUPPORT_TARGET_TEMPERATURE -from homeassistant.components.light import SUPPORT_BRIGHTNESS from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE, TEMP_CELSIUS @@ -64,7 +63,7 @@ async def test_mysa_living_setup(hass): entity_id="light.mysa_85dda9", friendly_name="Mysa-85dda9", unique_id="homekit-AAAAAAA000-40", - supported_features=SUPPORT_BRIGHTNESS, + supported_features=0, capabilities={"supported_color_modes": ["brightness"]}, state="off", ), diff --git a/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py b/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py index 16bd3830dfc..6fa9ff63690 100644 --- a/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py +++ b/tests/components/homekit_controller/specific_devices/test_vocolinc_flowerbud.py @@ -1,7 +1,6 @@ """Make sure that Vocolinc Flowerbud is enumerated properly.""" from homeassistant.components.humidifier.const import SUPPORT_MODES -from homeassistant.components.light import SUPPORT_BRIGHTNESS, SUPPORT_COLOR from homeassistant.components.number import NumberMode from homeassistant.components.sensor import SensorStateClass from homeassistant.const import PERCENTAGE @@ -50,7 +49,7 @@ async def test_vocolinc_flowerbud_setup(hass): entity_id="light.vocolinc_flowerbud_0d324b", friendly_name="VOCOlinc-Flowerbud-0d324b", unique_id="homekit-AM01121849000327-9", - supported_features=SUPPORT_BRIGHTNESS | SUPPORT_COLOR, + supported_features=0, capabilities={"supported_color_modes": ["hs"]}, state="on", ), diff --git a/tests/components/homekit_controller/test_diagnostics.py b/tests/components/homekit_controller/test_diagnostics.py index 8d22f7ff4bc..72ef571e214 100644 --- a/tests/components/homekit_controller/test_diagnostics.py +++ b/tests/components/homekit_controller/test_diagnostics.py @@ -250,7 +250,7 @@ async def test_config_entry(hass: HomeAssistant, hass_client: ClientSession, utc "attributes": { "supported_color_modes": ["hs"], "friendly_name": "Koogeek-LS1-20833F", - "supported_features": 17, + "supported_features": 0, }, "last_changed": "2023-01-01T00:00:00+00:00", "last_updated": "2023-01-01T00:00:00+00:00", @@ -520,7 +520,7 @@ async def test_device(hass: HomeAssistant, hass_client: ClientSession, utcnow): "attributes": { "supported_color_modes": ["hs"], "friendly_name": "Koogeek-LS1-20833F", - "supported_features": 17, + "supported_features": 0, }, "last_changed": "2023-01-01T00:00:00+00:00", "last_updated": "2023-01-01T00:00:00+00:00", diff --git a/tests/components/homekit_controller/test_light.py b/tests/components/homekit_controller/test_light.py index 8d094c4eb70..51e11feda60 100644 --- a/tests/components/homekit_controller/test_light.py +++ b/tests/components/homekit_controller/test_light.py @@ -3,7 +3,14 @@ from aiohomekit.model.characteristics import CharacteristicsTypes from aiohomekit.model.services import ServicesTypes from homeassistant.components.homekit_controller.const import KNOWN_DEVICES -from homeassistant.const import STATE_UNAVAILABLE +from homeassistant.components.light import ( + ATTR_COLOR_MODE, + ATTR_SUPPORTED_COLOR_MODES, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, +) +from homeassistant.const import ATTR_SUPPORTED_FEATURES, STATE_UNAVAILABLE from tests.components.homekit_controller.common import setup_test_component @@ -98,13 +105,79 @@ async def test_switch_change_light_state_color_temp(hass, utcnow): ) -async def test_switch_read_light_state(hass, utcnow): +async def test_switch_read_light_state_dimmer(hass, utcnow): + """Test that we can read the state of a HomeKit light accessory.""" + helper = await setup_test_component(hass, create_lightbulb_service) + + # Initial state is that the light is off + state = await helper.poll_and_get_state() + assert state.state == "off" + assert ATTR_COLOR_MODE not in state.attributes + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_BRIGHTNESS] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 + + # Simulate that someone switched on the device in the real world not via HA + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + }, + ) + assert state.state == "on" + assert state.attributes["brightness"] == 255 + assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_BRIGHTNESS + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_BRIGHTNESS] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 + + # Simulate that device switched off in the real world not via HA + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: False, + }, + ) + assert state.state == "off" + + +async def test_switch_push_light_state_dimmer(hass, utcnow): + """Test that we can read the state of a HomeKit light accessory.""" + helper = await setup_test_component(hass, create_lightbulb_service) + + # Initial state is that the light is off + state = hass.states.get(LIGHT_BULB_ENTITY_ID) + assert state.state == "off" + + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: True, + CharacteristicsTypes.BRIGHTNESS: 100, + }, + ) + assert state.state == "on" + assert state.attributes["brightness"] == 255 + + # Simulate that device switched off in the real world not via HA + state = await helper.async_update( + ServicesTypes.LIGHTBULB, + { + CharacteristicsTypes.ON: False, + }, + ) + assert state.state == "off" + + +async def test_switch_read_light_state_hs(hass, utcnow): """Test that we can read the state of a HomeKit light accessory.""" helper = await setup_test_component(hass, create_lightbulb_service_with_hs) # Initial state is that the light is off state = await helper.poll_and_get_state() assert state.state == "off" + assert ATTR_COLOR_MODE not in state.attributes + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_HS] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 # Simulate that someone switched on the device in the real world not via HA state = await helper.async_update( @@ -119,6 +192,9 @@ async def test_switch_read_light_state(hass, utcnow): assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["hs_color"] == (4, 5) + assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_HS + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_HS] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 # Simulate that device switched off in the real world not via HA state = await helper.async_update( @@ -130,7 +206,7 @@ async def test_switch_read_light_state(hass, utcnow): assert state.state == "off" -async def test_switch_push_light_state(hass, utcnow): +async def test_switch_push_light_state_hs(hass, utcnow): """Test that we can read the state of a HomeKit light accessory.""" helper = await setup_test_component(hass, create_lightbulb_service_with_hs) @@ -168,6 +244,9 @@ async def test_switch_read_light_state_color_temp(hass, utcnow): # Initial state is that the light is off state = await helper.poll_and_get_state() assert state.state == "off" + assert ATTR_COLOR_MODE not in state.attributes + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_COLOR_TEMP] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 # Simulate that someone switched on the device in the real world not via HA state = await helper.async_update( @@ -181,6 +260,9 @@ async def test_switch_read_light_state_color_temp(hass, utcnow): assert state.state == "on" assert state.attributes["brightness"] == 255 assert state.attributes["color_temp"] == 400 + assert state.attributes[ATTR_COLOR_MODE] == COLOR_MODE_COLOR_TEMP + assert state.attributes[ATTR_SUPPORTED_COLOR_MODES] == [COLOR_MODE_COLOR_TEMP] + assert state.attributes[ATTR_SUPPORTED_FEATURES] == 0 async def test_switch_push_light_state_color_temp(hass, utcnow):