diff --git a/tests/components/knx/conftest.py b/tests/components/knx/conftest.py index 47e7b94c32d..ca6f81057e1 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -30,6 +30,13 @@ class KNXTestKit: # telegrams to an InternalGroupAddress won't be queued here self._outgoing_telegrams: asyncio.Queue = asyncio.Queue() + def assert_state(self, entity_id: str, state: str, **attributes) -> None: + """Assert the state of an entity.""" + test_state = self.hass.states.get(entity_id) + assert test_state.state == state + for attribute, value in attributes.items(): + assert test_state.attributes.get(attribute) == value + async def setup_integration(self, config): """Create the KNX integration.""" diff --git a/tests/components/knx/test_light.py b/tests/components/knx/test_light.py new file mode 100644 index 00000000000..bb7a67d6bb4 --- /dev/null +++ b/tests/components/knx/test_light.py @@ -0,0 +1,1042 @@ +"""Test KNX light.""" +from __future__ import annotations + +from homeassistant.components.knx.const import CONF_STATE_ADDRESS, KNX_ADDRESS +from homeassistant.components.knx.schema import LightSchema +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + ATTR_COLOR_NAME, + ATTR_COLOR_TEMP, + ATTR_HS_COLOR, + COLOR_MODE_BRIGHTNESS, + COLOR_MODE_COLOR_TEMP, + COLOR_MODE_HS, + COLOR_MODE_ONOFF, + COLOR_MODE_RGB, + COLOR_MODE_RGBW, + COLOR_MODE_XY, +) +from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON +from homeassistant.core import HomeAssistant + +from .conftest import KNXTestKit + + +async def test_light_simple(hass: HomeAssistant, knx: KNXTestKit): + """Test simple KNX light.""" + test_address = "1/1/1" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + } + } + ) + assert len(hass.states.async_all()) == 1 + + knx.assert_state("light.test", STATE_OFF) + # turn on light + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_address, True) + knx.assert_state( + "light.test", + STATE_ON, + color_mode=COLOR_MODE_ONOFF, + ) + # turn off light + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_address, False) + knx.assert_state("light.test", STATE_OFF) + # receive ON telegram + await knx.receive_write(test_address, True) + knx.assert_state("light.test", STATE_ON) + + # receive OFF telegram + await knx.receive_write(test_address, False) + knx.assert_state("light.test", STATE_OFF) + + # switch does not respond to read by default + await knx.receive_read(test_address) + await knx.assert_telegram_count(0) + + +async def test_light_brightness(hass: HomeAssistant, knx: KNXTestKit): + """Test dimmable KNX light.""" + test_address = "1/1/1" + test_brightness = "1/1/2" + test_brightness_state = "1/1/3" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + } + } + ) + # StateUpdater initialize state + await knx.assert_read(test_brightness_state) + # turn on light via brightness + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 80}, + blocking=True, + ) + await knx.assert_write(test_brightness, (80,)) + # state is still OFF until controller reports otherwise + knx.assert_state("light.test", STATE_OFF) + await knx.receive_write(test_address, True) + knx.assert_state( + "light.test", + STATE_ON, + brightness=80, + color_mode=COLOR_MODE_BRIGHTNESS, + ) + # receive brightness changes from KNX + await knx.receive_write(test_brightness_state, (255,)) + knx.assert_state("light.test", STATE_ON, brightness=255) + await knx.receive_write(test_brightness, (128,)) + knx.assert_state("light.test", STATE_ON, brightness=128) + # turn off light via brightness + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 0}, + blocking=True, + ) + await knx.assert_write(test_address, False) + knx.assert_state("light.test", STATE_OFF) + + +async def test_light_color_temp_absolute(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light color temperature adjustable in Kelvin.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_brightness = "1/1/3" + test_brightness_state = "1/1/4" + test_ct = "1/1/5" + test_ct_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + LightSchema.CONF_COLOR_TEMP_ADDRESS: test_ct, + LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS: test_ct_state, + LightSchema.CONF_COLOR_TEMP_MODE: "absolute", + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_brightness_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_brightness_state, (255,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_ct_state) + await knx.receive_response(test_ct_state, (0x0A, 0x8C)) # 2700 Kelvin - 370 Mired + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_COLOR_TEMP, + color_temp=370, + ) + # change color temperature from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_TEMP: 250}, # 4000 Kelvin - 0x0FA0 + blocking=True, + ) + await knx.assert_write(test_ct, (0x0F, 0xA0)) + knx.assert_state("light.test", STATE_ON, color_temp=250) + # change color temperature from KNX + await knx.receive_write(test_ct_state, (0x17, 0x70)) # 6000 Kelvin - 166 Mired + knx.assert_state("light.test", STATE_ON, color_temp=166) + + +async def test_light_color_temp_relative(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light color temperature adjustable in percent.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_brightness = "1/1/3" + test_brightness_state = "1/1/4" + test_ct = "1/1/5" + test_ct_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + LightSchema.CONF_COLOR_TEMP_ADDRESS: test_ct, + LightSchema.CONF_COLOR_TEMP_STATE_ADDRESS: test_ct_state, + LightSchema.CONF_COLOR_TEMP_MODE: "relative", + LightSchema.CONF_MIN_KELVIN: 3000, + LightSchema.CONF_MAX_KELVIN: 4000, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_brightness_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_brightness_state, (255,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_ct_state) + await knx.receive_response(test_ct_state, (0xFF,)) # 100 % - 4000 K - 250 Mired + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_COLOR_TEMP, + color_temp=250, + ) + # change color temperature from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_TEMP: 300}, # 3333 Kelvin - 33 % - 0x54 + blocking=True, + ) + await knx.assert_write(test_ct, (0x54,)) + knx.assert_state("light.test", STATE_ON, color_temp=300) + # change color temperature from KNX + await knx.receive_write(test_ct_state, (0xE6,)) # 3900 Kelvin - 90 % - 256 Mired + knx.assert_state("light.test", STATE_ON, color_temp=256) + + +async def test_light_hs_color(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with hs color.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_brightness = "1/1/3" + test_brightness_state = "1/1/4" + test_hue = "1/1/5" + test_hue_state = "1/1/6" + test_sat = "1/1/7" + test_sat_state = "1/1/8" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + LightSchema.CONF_HUE_ADDRESS: test_hue, + LightSchema.CONF_HUE_STATE_ADDRESS: test_hue_state, + LightSchema.CONF_SATURATION_ADDRESS: test_sat, + LightSchema.CONF_SATURATION_STATE_ADDRESS: test_sat_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_brightness_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_brightness_state, (255,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_hue_state) + await knx.assert_read(test_sat_state) + await knx.receive_response(test_hue_state, (0xFF,)) + await knx.receive_response(test_sat_state, (0xFF,)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_HS, + hs_color=(360, 100), + ) + # change color from HA - only hue + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "blue"}, # hue: 240, sat: 100 + blocking=True, + ) + await knx.assert_write(test_hue, (0xAA,)) + knx.assert_state("light.test", STATE_ON, brightness=255, hs_color=(240, 100)) + + # change color from HA - only saturation + await hass.services.async_call( + "light", + "turn_on", + { + "entity_id": "light.test", + ATTR_HS_COLOR: (240, 50), + }, # hue: 60, sat: 12.157 + blocking=True, + ) + await knx.assert_write(test_sat, (0x80,)) + knx.assert_state("light.test", STATE_ON, brightness=255, hs_color=(240, 50)) + + # change color from HA - hue and sat + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"}, # hue: 330, sat: 59 + blocking=True, + ) + await knx.assert_write(test_hue, (0xEA,)) + await knx.assert_write(test_sat, (0x96,)) + knx.assert_state("light.test", STATE_ON, brightness=255, hs_color=(330, 59)) + + # change color and brightness from KNX + await knx.receive_write(test_brightness, (0xB2,)) + knx.assert_state("light.test", STATE_ON, brightness=178, hs_color=(330, 59)) + await knx.receive_write(test_hue, (0x7D,)) + knx.assert_state("light.test", STATE_ON, brightness=178, hs_color=(176, 59)) + await knx.receive_write(test_sat, (0xD1,)) + knx.assert_state("light.test", STATE_ON, brightness=178, hs_color=(176, 82)) + + +async def test_light_xyy_color(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with xyy color.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_xyy = "1/1/5" + test_xyy_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_XYY_ADDRESS: test_xyy, + LightSchema.CONF_XYY_STATE_ADDRESS: test_xyy_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_xyy_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_xyy_state, (0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x03)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=204, + color_mode=COLOR_MODE_XY, + xy_color=(0.8, 0.8), + ) + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 139, ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_xyy, (179, 116, 76, 139, 139, 3)) + knx.assert_state("light.test", STATE_ON, brightness=139, xy_color=(0.701, 0.299)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 255}, + blocking=True, + ) + await knx.assert_write(test_xyy, (0, 0, 0, 0, 255, 1)) + knx.assert_state("light.test", STATE_ON, brightness=255, xy_color=(0.701, 0.299)) + + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + await knx.assert_write(test_xyy, (120, 16, 63, 59, 0, 2)) + knx.assert_state("light.test", STATE_ON, brightness=255, xy_color=(0.469, 0.247)) + + # change color and brightness from KNX + await knx.receive_write(test_xyy, (0x85, 0x1E, 0x4F, 0x5C, 0x19, 0x03)) + knx.assert_state("light.test", STATE_ON, brightness=25, xy_color=(0.52, 0.31)) + # change brightness from KNX + await knx.receive_write(test_xyy, (0x00, 0x00, 0x00, 0x00, 0x80, 0x01)) + knx.assert_state("light.test", STATE_ON, brightness=128, xy_color=(0.52, 0.31)) + # change color from KNX + await knx.receive_write(test_xyy, (0x2E, 0x14, 0x40, 0x00, 0x00, 0x02)) + knx.assert_state("light.test", STATE_ON, brightness=128, xy_color=(0.18, 0.25)) + + +async def test_light_xyy_color_with_brightness(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with xyy color and explicit brightness address.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_brightness = "1/1/3" + test_brightness_state = "1/1/4" + test_xyy = "1/1/5" + test_xyy_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + LightSchema.CONF_XYY_ADDRESS: test_xyy, + LightSchema.CONF_XYY_STATE_ADDRESS: test_xyy_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_brightness_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_brightness_state, (255,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_xyy_state) + await knx.receive_response(test_xyy_state, (0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x03)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, # brightness form xyy_color ignored when extra brightness GA is used + color_mode=COLOR_MODE_XY, + xy_color=(0.8, 0.8), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_xyy, (179, 116, 76, 139, 0, 2)) + knx.assert_state("light.test", STATE_ON, brightness=255, xy_color=(0.701, 0.299)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 139}, + blocking=True, + ) + await knx.assert_write(test_brightness, (0x8B,)) + knx.assert_state("light.test", STATE_ON, brightness=139, xy_color=(0.701, 0.299)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 255, ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + await knx.assert_write(test_xyy, (120, 16, 63, 59, 255, 3)) + # brightness relies on brightness_state GA + await knx.receive_write(test_brightness_state, (255,)) + knx.assert_state("light.test", STATE_ON, brightness=255, xy_color=(0.469, 0.247)) + + # change color and brightness from KNX + await knx.receive_write(test_xyy, (0x85, 0x1E, 0x4F, 0x5C, 0x00, 0x02)) + knx.assert_state("light.test", STATE_ON, brightness=255, xy_color=(0.52, 0.31)) + await knx.receive_write(test_brightness, (21,)) + knx.assert_state("light.test", STATE_ON, brightness=21, xy_color=(0.52, 0.31)) + + +async def test_light_rgb_individual(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with rgb color in individual GAs.""" + test_red = "1/1/3" + test_red_state = "1/1/4" + test_green = "1/1/5" + test_green_state = "1/1/6" + test_blue = "1/1/7" + test_blue_state = "1/1/8" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + LightSchema.CONF_INDIVIDUAL_COLORS: { + LightSchema.CONF_RED: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_red, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_red_state, + }, + LightSchema.CONF_GREEN: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_green, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_green_state, + }, + LightSchema.CONF_BLUE: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_blue, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_blue_state, + }, + }, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_red_state) + await knx.assert_read(test_green_state) + await knx.receive_response(test_red_state, (255,)) + await knx.receive_response(test_green_state, (255,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_blue_state) + await knx.receive_response(test_blue_state, (255,)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_RGB, + rgb_color=(255, 255, 255), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgb_color=(255, 0, 0)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 200}, + blocking=True, + ) + await knx.assert_write(test_red, (200,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(200, 0, 0)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + # + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (105,)) + await knx.assert_write(test_blue, (180,)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgb_color=(255, 105, 180)) + + # turn OFF from KNX + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (0,)) + await knx.receive_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_OFF) + # turn ON from KNX + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (180,)) + await knx.receive_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0)) + + # turn OFF from HA + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_red, (0,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_OFF) + + # turn ON from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test"}, + blocking=True, + ) + # color will not be restored - defaults to white + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (255,)) + await knx.assert_write(test_blue, (255,)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgb_color=(255, 255, 255)) + + # turn ON with brightness only from HA - defaults to white + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (0,)) + await knx.receive_write(test_blue, (0,)) + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 45}, + blocking=True, + ) + await knx.assert_write(test_red, (45,)) + await knx.assert_write(test_green, (45,)) + await knx.assert_write(test_blue, (45,)) + + +async def test_light_rgbw_individual(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with rgbw color in individual GAs.""" + test_red = "1/1/3" + test_red_state = "1/1/4" + test_green = "1/1/5" + test_green_state = "1/1/6" + test_blue = "1/1/7" + test_blue_state = "1/1/8" + test_white = "1/1/9" + test_white_state = "1/1/10" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + LightSchema.CONF_INDIVIDUAL_COLORS: { + LightSchema.CONF_RED: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_red, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_red_state, + }, + LightSchema.CONF_GREEN: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_green, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_green_state, + }, + LightSchema.CONF_BLUE: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_blue, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_blue_state, + }, + LightSchema.CONF_WHITE: { + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_white, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_white_state, + }, + }, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_red_state) + await knx.assert_read(test_green_state) + await knx.receive_response(test_red_state, (0,)) + await knx.receive_response(test_green_state, (0,)) + # # StateUpdater semaphore allows 2 concurrent requests + await knx.assert_read(test_blue_state) + await knx.assert_read(test_white_state) + await knx.receive_response(test_blue_state, (0,)) + await knx.receive_response(test_white_state, (255,)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_RGBW, + rgbw_color=(0, 0, 0, 255), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + await knx.assert_write(test_white, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgbw_color=(255, 0, 0, 0)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 200}, + blocking=True, + ) + await knx.assert_write(test_red, (200,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + await knx.assert_write(test_white, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (128,)) + await knx.assert_write(test_white, (178,)) + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + rgb_color=(255, 105, 180), + rgbw_color=(255, 0, 128, 178), + ) + + # turn OFF from KNX + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (0,)) + await knx.receive_write(test_blue, (0,)) + knx.assert_state("light.test", STATE_ON) + await knx.receive_write(test_white, (0,)) + knx.assert_state("light.test", STATE_OFF) + # turn ON from KNX + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (180,)) + await knx.receive_write(test_blue, (0,)) + await knx.receive_write(test_white, (0,)) + knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0)) + + # turn OFF from HA + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_red, (0,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + await knx.assert_write(test_white, (0,)) + knx.assert_state("light.test", STATE_OFF) + + # turn ON from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test"}, + blocking=True, + ) + # color will not be restored - defaults to 100% on all channels + await knx.assert_write(test_red, (255,)) + await knx.assert_write(test_green, (255,)) + await knx.assert_write(test_blue, (255,)) + await knx.assert_write(test_white, (255,)) + knx.assert_state( + "light.test", STATE_ON, brightness=255, rgbw_color=(255, 255, 255, 255) + ) + + # turn ON with brightness only from HA - defaults to white + await knx.receive_write(test_red, (0,)) + await knx.receive_write(test_green, (0,)) + await knx.receive_write(test_blue, (0,)) + await knx.receive_write(test_white, (0,)) + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 45}, + blocking=True, + ) + await knx.assert_write(test_red, (0,)) + await knx.assert_write(test_green, (0,)) + await knx.assert_write(test_blue, (0,)) + await knx.assert_write(test_white, (45,)) + + +async def test_light_rgb(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with rgb color.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_rgb = "1/1/5" + test_rgb_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_COLOR_ADDRESS: test_rgb, + LightSchema.CONF_COLOR_STATE_ADDRESS: test_rgb_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_rgb_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_rgb_state, (0xFF, 0xFF, 0xFF)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=255, + color_mode=COLOR_MODE_RGB, + rgb_color=(255, 255, 255), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_rgb, (255, 0, 0)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgb_color=(255, 0, 0)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 200}, + blocking=True, + ) + await knx.assert_write(test_rgb, (200, 0, 0)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgb_color=(200, 0, 0)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 128, ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + # + await knx.assert_write(test_rgb, (128, 52, 90)) + knx.assert_state("light.test", STATE_ON, brightness=128, rgb_color=(128, 52, 90)) + + # turn OFF from KNX + await knx.receive_write(test_address_state, False) + knx.assert_state("light.test", STATE_OFF) + # receive color update from KNX - still OFF + await knx.receive_write(test_rgb, (0, 180, 0)) + knx.assert_state("light.test", STATE_OFF) + # turn ON from KNX - include color update + await knx.receive_write(test_address_state, True) + knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0)) + + # turn OFF from HA + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_address, False) + knx.assert_state("light.test", STATE_OFF) + + # turn ON from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test"}, + blocking=True, + ) + # color will be restored in no other state was received + await knx.assert_write(test_address, True) + knx.assert_state("light.test", STATE_ON, brightness=180, rgb_color=(0, 180, 0)) + + +async def test_light_rgbw(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with rgbw color.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_rgbw = "1/1/5" + test_rgbw_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_RGBW_ADDRESS: test_rgbw, + LightSchema.CONF_RGBW_STATE_ADDRESS: test_rgbw_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_rgbw_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_rgbw_state, (0x64, 0x65, 0x66, 0x67, 0x00, 0x0F)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=103, + color_mode=COLOR_MODE_RGBW, + rgbw_color=(100, 101, 102, 103), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_rgbw, (0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgbw_color=(255, 0, 0, 0)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 200}, + blocking=True, + ) + await knx.assert_write(test_rgbw, (0xC8, 0x00, 0x00, 0x00, 0x00, 0x0F)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 128, ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + await knx.assert_write(test_rgbw, (128, 0, 64, 89, 0x00, 0x0F)) + knx.assert_state( + "light.test", + STATE_ON, + brightness=128, + rgb_color=(128, 52, 90), + rgbw_color=(128, 0, 64, 89), + ) + + # turn OFF from KNX + await knx.receive_write(test_address_state, False) + knx.assert_state("light.test", STATE_OFF) + # receive color update from KNX - still OFF + await knx.receive_write(test_rgbw, (0, 180, 0, 0, 0x00, 0x0F)) + knx.assert_state("light.test", STATE_OFF) + # turn ON from KNX - include color update + await knx.receive_write(test_address_state, True) + knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0)) + + # turn OFF from HA + await hass.services.async_call( + "light", + "turn_off", + {"entity_id": "light.test"}, + blocking=True, + ) + await knx.assert_write(test_address, False) + knx.assert_state("light.test", STATE_OFF) + + # turn ON from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test"}, + blocking=True, + ) + # color will be restored in no other state was received + await knx.assert_write(test_address, True) + knx.assert_state("light.test", STATE_ON, brightness=180, rgbw_color=(0, 180, 0, 0)) + + +async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit): + """Test KNX light with rgbw color with dedicated brightness.""" + test_address = "1/1/1" + test_address_state = "1/1/2" + test_brightness = "1/1/3" + test_brightness_state = "1/1/4" + test_rgbw = "1/1/5" + test_rgbw_state = "1/1/6" + await knx.setup_integration( + { + LightSchema.PLATFORM_NAME: [ + { + CONF_NAME: "test", + KNX_ADDRESS: test_address, + CONF_STATE_ADDRESS: test_address_state, + LightSchema.CONF_BRIGHTNESS_ADDRESS: test_brightness, + LightSchema.CONF_BRIGHTNESS_STATE_ADDRESS: test_brightness_state, + LightSchema.CONF_RGBW_ADDRESS: test_rgbw, + LightSchema.CONF_RGBW_STATE_ADDRESS: test_rgbw_state, + }, + ] + } + ) + # StateUpdater initialize state + await knx.assert_read(test_address_state) + await knx.assert_read(test_brightness_state) + await knx.receive_response(test_address_state, True) + await knx.receive_response(test_brightness_state, (0x67,)) + await knx.assert_read(test_rgbw_state) + await knx.receive_response(test_rgbw_state, (0x64, 0x65, 0x66, 0x67, 0x00, 0x0F)) + + knx.assert_state( + "light.test", + STATE_ON, + brightness=103, + color_mode=COLOR_MODE_RGBW, + rgbw_color=(100, 101, 102, 103), + ) + # change color from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_COLOR_NAME: "red"}, + blocking=True, + ) + await knx.assert_write(test_rgbw, (0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F)) + knx.assert_state("light.test", STATE_ON, brightness=103, rgbw_color=(255, 0, 0, 0)) + # # relies on dedicated brightness state + await knx.receive_write(test_brightness_state, (0xFF,)) + knx.assert_state("light.test", STATE_ON, brightness=255, rgbw_color=(255, 0, 0, 0)) + + # change brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 200}, + blocking=True, + ) + await knx.assert_write(test_brightness, (200,)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(255, 0, 0, 0)) + # # relies on dedicated rgbw state + await knx.receive_write(test_rgbw_state, (0xC8, 0x00, 0x00, 0x00, 0x00, 0x0F)) + knx.assert_state("light.test", STATE_ON, brightness=200, rgbw_color=(200, 0, 0, 0)) + + # change color and brightness from HA + await hass.services.async_call( + "light", + "turn_on", + {"entity_id": "light.test", ATTR_BRIGHTNESS: 128, ATTR_COLOR_NAME: "hotpink"}, + blocking=True, + ) + await knx.assert_write(test_rgbw, (255, 0, 128, 178, 0x00, 0x0F)) + await knx.assert_write(test_brightness, (128,)) + knx.assert_state( + "light.test", + STATE_ON, + brightness=128, + rgb_color=(255, 105, 180), + rgbw_color=(255, 0, 128, 178), + )