diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index c63a31f0bb5..0b7b517dca5 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -153,7 +153,7 @@ SUPPORTED_PLATFORMS_YAML: Final = { Platform.WEATHER, } -SUPPORTED_PLATFORMS_UI: Final = {Platform.SWITCH} +SUPPORTED_PLATFORMS_UI: Final = {Platform.SWITCH, Platform.LIGHT} # Map KNX controller modes to HA modes. This list might not be complete. CONTROLLER_MODES: Final = { diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index b1c1681a817..425640a9915 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -19,14 +19,41 @@ from homeassistant.components.light import ( LightEntity, ) from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform -from homeassistant.core import HomeAssistant +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import ConfigType import homeassistant.util.color as color_util -from .const import DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS, ColorTempModes +from . import KNXModule +from .const import CONF_SYNC_STATE, DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS, ColorTempModes from .knx_entity import KnxEntity from .schema import LightSchema +from .storage.const import ( + CONF_COLOR_TEMP_MAX, + CONF_COLOR_TEMP_MIN, + CONF_DEVICE_INFO, + CONF_DPT, + CONF_ENTITY, + CONF_GA_BLUE_BRIGHTNESS, + CONF_GA_BLUE_SWITCH, + CONF_GA_BRIGHTNESS, + CONF_GA_COLOR, + CONF_GA_COLOR_TEMP, + CONF_GA_GREEN_BRIGHTNESS, + CONF_GA_GREEN_SWITCH, + CONF_GA_HUE, + CONF_GA_PASSIVE, + CONF_GA_RED_BRIGHTNESS, + CONF_GA_RED_SWITCH, + CONF_GA_SATURATION, + CONF_GA_STATE, + CONF_GA_SWITCH, + CONF_GA_WHITE_BRIGHTNESS, + CONF_GA_WHITE_SWITCH, + CONF_GA_WRITE, +) +from .storage.entity_store_schema import LightColorMode async def async_setup_entry( @@ -35,13 +62,31 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up light(s) for KNX platform.""" - xknx: XKNX = hass.data[DOMAIN].xknx - config: list[ConfigType] = hass.data[DATA_KNX_CONFIG][Platform.LIGHT] + knx_module: KNXModule = hass.data[DOMAIN] - async_add_entities(KNXLight(xknx, entity_config) for entity_config in config) + entities: list[KnxEntity] = [] + if yaml_config := hass.data[DATA_KNX_CONFIG].get(Platform.LIGHT): + entities.extend( + KnxYamlLight(knx_module.xknx, entity_config) + for entity_config in yaml_config + ) + if ui_config := knx_module.config_store.data["entities"].get(Platform.LIGHT): + entities.extend( + KnxUiLight(knx_module, unique_id, config) + for unique_id, config in ui_config.items() + ) + if entities: + async_add_entities(entities) + + @callback + def add_new_ui_light(unique_id: str, config: dict[str, Any]) -> None: + """Add KNX entity at runtime.""" + async_add_entities([KnxUiLight(knx_module, unique_id, config)]) + + knx_module.config_store.async_add_entity[Platform.LIGHT] = add_new_ui_light -def _create_light(xknx: XKNX, config: ConfigType) -> XknxLight: +def _create_yaml_light(xknx: XKNX, config: ConfigType) -> XknxLight: """Return a KNX Light device to be used within XKNX.""" def individual_color_addresses(color: str, feature: str) -> Any | None: @@ -151,29 +196,111 @@ def _create_light(xknx: XKNX, config: ConfigType) -> XknxLight: ) -class KNXLight(KnxEntity, LightEntity): +def _create_ui_light(xknx: XKNX, knx_config: ConfigType, name: str) -> XknxLight: + """Return a KNX Light device to be used within XKNX.""" + + def get_write(key: str) -> str | None: + """Get the write group address.""" + return knx_config[key][CONF_GA_WRITE] if key in knx_config else None + + def get_state(key: str) -> list[Any] | None: + """Get the state group address.""" + return ( + [knx_config[key][CONF_GA_STATE], *knx_config[key][CONF_GA_PASSIVE]] + if key in knx_config + else None + ) + + def get_dpt(key: str) -> str | None: + """Get the DPT.""" + return knx_config[key].get(CONF_DPT) if key in knx_config else None + + group_address_tunable_white = None + group_address_tunable_white_state = None + group_address_color_temp = None + group_address_color_temp_state = None + color_temperature_type = ColorTemperatureType.UINT_2_BYTE + if ga_color_temp := knx_config.get(CONF_GA_COLOR_TEMP): + if ga_color_temp[CONF_DPT] == ColorTempModes.RELATIVE: + group_address_tunable_white = ga_color_temp[CONF_GA_WRITE] + group_address_tunable_white_state = [ + ga_color_temp[CONF_GA_STATE], + *ga_color_temp[CONF_GA_PASSIVE], + ] + else: + # absolute uint or float + group_address_color_temp = ga_color_temp[CONF_GA_WRITE] + group_address_color_temp_state = [ + ga_color_temp[CONF_GA_STATE], + *ga_color_temp[CONF_GA_PASSIVE], + ] + if ga_color_temp[CONF_DPT] == ColorTempModes.ABSOLUTE_FLOAT: + color_temperature_type = ColorTemperatureType.FLOAT_2_BYTE + + _color_dpt = get_dpt(CONF_GA_COLOR) + return XknxLight( + xknx, + name=name, + group_address_switch=get_write(CONF_GA_SWITCH), + group_address_switch_state=get_state(CONF_GA_SWITCH), + group_address_brightness=get_write(CONF_GA_BRIGHTNESS), + group_address_brightness_state=get_state(CONF_GA_BRIGHTNESS), + group_address_color=get_write(CONF_GA_COLOR) + if _color_dpt == LightColorMode.RGB + else None, + group_address_color_state=get_state(CONF_GA_COLOR) + if _color_dpt == LightColorMode.RGB + else None, + group_address_rgbw=get_write(CONF_GA_COLOR) + if _color_dpt == LightColorMode.RGBW + else None, + group_address_rgbw_state=get_state(CONF_GA_COLOR) + if _color_dpt == LightColorMode.RGBW + else None, + group_address_hue=get_write(CONF_GA_HUE), + group_address_hue_state=get_state(CONF_GA_HUE), + group_address_saturation=get_write(CONF_GA_SATURATION), + group_address_saturation_state=get_state(CONF_GA_SATURATION), + group_address_xyy_color=get_write(CONF_GA_COLOR) + if _color_dpt == LightColorMode.XYY + else None, + group_address_xyy_color_state=get_write(CONF_GA_COLOR) + if _color_dpt == LightColorMode.XYY + else None, + group_address_tunable_white=group_address_tunable_white, + group_address_tunable_white_state=group_address_tunable_white_state, + group_address_color_temperature=group_address_color_temp, + group_address_color_temperature_state=group_address_color_temp_state, + group_address_switch_red=get_write(CONF_GA_RED_SWITCH), + group_address_switch_red_state=get_state(CONF_GA_RED_SWITCH), + group_address_brightness_red=get_write(CONF_GA_RED_BRIGHTNESS), + group_address_brightness_red_state=get_state(CONF_GA_RED_BRIGHTNESS), + group_address_switch_green=get_write(CONF_GA_GREEN_SWITCH), + group_address_switch_green_state=get_state(CONF_GA_GREEN_SWITCH), + group_address_brightness_green=get_write(CONF_GA_GREEN_BRIGHTNESS), + group_address_brightness_green_state=get_state(CONF_GA_GREEN_BRIGHTNESS), + group_address_switch_blue=get_write(CONF_GA_BLUE_SWITCH), + group_address_switch_blue_state=get_state(CONF_GA_BLUE_SWITCH), + group_address_brightness_blue=get_write(CONF_GA_BLUE_BRIGHTNESS), + group_address_brightness_blue_state=get_state(CONF_GA_BLUE_BRIGHTNESS), + group_address_switch_white=get_write(CONF_GA_WHITE_SWITCH), + group_address_switch_white_state=get_state(CONF_GA_WHITE_SWITCH), + group_address_brightness_white=get_write(CONF_GA_WHITE_BRIGHTNESS), + group_address_brightness_white_state=get_state(CONF_GA_WHITE_BRIGHTNESS), + color_temperature_type=color_temperature_type, + min_kelvin=knx_config[CONF_COLOR_TEMP_MIN], + max_kelvin=knx_config[CONF_COLOR_TEMP_MAX], + sync_state=knx_config[CONF_SYNC_STATE], + ) + + +class _KnxLight(KnxEntity, LightEntity): """Representation of a KNX light.""" + _attr_max_color_temp_kelvin: int + _attr_min_color_temp_kelvin: int _device: XknxLight - def __init__(self, xknx: XKNX, config: ConfigType) -> None: - """Initialize of KNX light.""" - super().__init__(_create_light(xknx, config)) - self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN] - self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN] - self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) - self._attr_unique_id = self._device_unique_id() - - def _device_unique_id(self) -> str: - """Return unique id for this device.""" - if self._device.switch.group_address is not None: - return f"{self._device.switch.group_address}" - return ( - f"{self._device.red.brightness.group_address}_" - f"{self._device.green.brightness.group_address}_" - f"{self._device.blue.brightness.group_address}" - ) - @property def is_on(self) -> bool: """Return true if light is on.""" @@ -392,3 +519,53 @@ class KNXLight(KnxEntity, LightEntity): async def async_turn_off(self, **kwargs: Any) -> None: """Turn the light off.""" await self._device.set_off() + + +class KnxYamlLight(_KnxLight): + """Representation of a KNX light.""" + + _device: XknxLight + + def __init__(self, xknx: XKNX, config: ConfigType) -> None: + """Initialize of KNX light.""" + super().__init__(_create_yaml_light(xknx, config)) + self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN] + self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN] + self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY) + self._attr_unique_id = self._device_unique_id() + + def _device_unique_id(self) -> str: + """Return unique id for this device.""" + if self._device.switch.group_address is not None: + return f"{self._device.switch.group_address}" + return ( + f"{self._device.red.brightness.group_address}_" + f"{self._device.green.brightness.group_address}_" + f"{self._device.blue.brightness.group_address}" + ) + + +class KnxUiLight(_KnxLight): + """Representation of a KNX light.""" + + _device: XknxLight + _attr_has_entity_name = True + + def __init__( + self, knx_module: KNXModule, unique_id: str, config: ConfigType + ) -> None: + """Initialize of KNX light.""" + super().__init__( + _create_ui_light( + knx_module.xknx, config[DOMAIN], config[CONF_ENTITY][CONF_NAME] + ) + ) + self._attr_max_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MAX] + self._attr_min_color_temp_kelvin: int = config[DOMAIN][CONF_COLOR_TEMP_MIN] + + self._attr_entity_category = config[CONF_ENTITY][CONF_ENTITY_CATEGORY] + self._attr_unique_id = unique_id + if device_info := config[CONF_ENTITY].get(CONF_DEVICE_INFO): + self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_info)}) + + knx_module.config_store.entities[unique_id] = self diff --git a/homeassistant/components/knx/storage/const.py b/homeassistant/components/knx/storage/const.py index 6453b77ed3b..42b76a5a0fd 100644 --- a/homeassistant/components/knx/storage/const.py +++ b/homeassistant/components/knx/storage/const.py @@ -10,5 +10,19 @@ CONF_GA_STATE: Final = "state" CONF_GA_PASSIVE: Final = "passive" CONF_DPT: Final = "dpt" - CONF_GA_SWITCH: Final = "ga_switch" +CONF_GA_COLOR_TEMP: Final = "ga_color_temp" +CONF_COLOR_TEMP_MIN: Final = "color_temp_min" +CONF_COLOR_TEMP_MAX: Final = "color_temp_max" +CONF_GA_BRIGHTNESS: Final = "ga_brightness" +CONF_GA_COLOR: Final = "ga_color" +CONF_GA_RED_BRIGHTNESS: Final = "ga_red_brightness" +CONF_GA_RED_SWITCH: Final = "ga_red_switch" +CONF_GA_GREEN_BRIGHTNESS: Final = "ga_green_brightness" +CONF_GA_GREEN_SWITCH: Final = "ga_green_switch" +CONF_GA_BLUE_BRIGHTNESS: Final = "ga_blue_brightness" +CONF_GA_BLUE_SWITCH: Final = "ga_blue_switch" +CONF_GA_WHITE_BRIGHTNESS: Final = "ga_white_brightness" +CONF_GA_WHITE_SWITCH: Final = "ga_white_switch" +CONF_GA_HUE: Final = "ga_hue" +CONF_GA_SATURATION: Final = "ga_saturation" diff --git a/homeassistant/components/knx/storage/entity_store_schema.py b/homeassistant/components/knx/storage/entity_store_schema.py index e2f9e786300..84854d2ec85 100644 --- a/homeassistant/components/knx/storage/entity_store_schema.py +++ b/homeassistant/components/knx/storage/entity_store_schema.py @@ -1,5 +1,7 @@ """KNX entity store schema.""" +from enum import StrEnum, unique + import voluptuous as vol from homeassistant.const import ( @@ -19,9 +21,33 @@ from ..const import ( CONF_SYNC_STATE, DOMAIN, SUPPORTED_PLATFORMS_UI, + ColorTempModes, ) from ..validation import sync_state_validator -from .const import CONF_DATA, CONF_DEVICE_INFO, CONF_ENTITY, CONF_GA_SWITCH +from .const import ( + CONF_COLOR_TEMP_MAX, + CONF_COLOR_TEMP_MIN, + CONF_DATA, + CONF_DEVICE_INFO, + CONF_ENTITY, + CONF_GA_BLUE_BRIGHTNESS, + CONF_GA_BLUE_SWITCH, + CONF_GA_BRIGHTNESS, + CONF_GA_COLOR, + CONF_GA_COLOR_TEMP, + CONF_GA_GREEN_BRIGHTNESS, + CONF_GA_GREEN_SWITCH, + CONF_GA_HUE, + CONF_GA_PASSIVE, + CONF_GA_RED_BRIGHTNESS, + CONF_GA_RED_SWITCH, + CONF_GA_SATURATION, + CONF_GA_STATE, + CONF_GA_SWITCH, + CONF_GA_WHITE_BRIGHTNESS, + CONF_GA_WHITE_SWITCH, + CONF_GA_WRITE, +) from .knx_selector import GASelector BASE_ENTITY_SCHEMA = vol.All( @@ -49,6 +75,25 @@ BASE_ENTITY_SCHEMA = vol.All( ), ) + +def optional_ga_schema(key: str, ga_selector: GASelector) -> VolDictType: + """Validate group address schema or remove key if no address is set.""" + # frontend will return {key: {"write": None, "state": None}} for unused GA sets + # -> remove this entirely for optional keys + # if one GA is set, validate as usual + return { + vol.Optional(key): ga_selector, + vol.Remove(key): vol.Schema( + { + vol.Optional(CONF_GA_WRITE): None, + vol.Optional(CONF_GA_STATE): None, + vol.Optional(CONF_GA_PASSIVE): vol.IsFalse(), # None or empty list + }, + extra=vol.ALLOW_EXTRA, + ), + } + + SWITCH_SCHEMA = vol.Schema( { vol.Required(CONF_ENTITY): BASE_ENTITY_SCHEMA, @@ -62,6 +107,98 @@ SWITCH_SCHEMA = vol.Schema( ) +@unique +class LightColorMode(StrEnum): + """Enum for light color mode.""" + + RGB = "232.600" + RGBW = "251.600" + XYY = "242.600" + + +@unique +class LightColorModeSchema(StrEnum): + """Enum for light color mode.""" + + DEFAULT = "default" + INDIVIDUAL = "individual" + HSV = "hsv" + + +_LIGHT_COLOR_MODE_SCHEMA = "_light_color_mode_schema" + +_COMMON_LIGHT_SCHEMA = vol.Schema( + { + vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator, + **optional_ga_schema( + CONF_GA_COLOR_TEMP, GASelector(write_required=True, dpt=ColorTempModes) + ), + vol.Optional(CONF_COLOR_TEMP_MIN, default=2700): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_COLOR_TEMP_MAX, default=6000): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + }, + extra=vol.REMOVE_EXTRA, +) + +_DEFAULT_LIGHT_SCHEMA = _COMMON_LIGHT_SCHEMA.extend( + { + vol.Required(_LIGHT_COLOR_MODE_SCHEMA): LightColorModeSchema.DEFAULT.value, + vol.Required(CONF_GA_SWITCH): GASelector(write_required=True), + **optional_ga_schema(CONF_GA_BRIGHTNESS, GASelector(write_required=True)), + **optional_ga_schema( + CONF_GA_COLOR, + GASelector(write_required=True, dpt=LightColorMode), + ), + } +) + +_INDIVIDUAL_LIGHT_SCHEMA = _COMMON_LIGHT_SCHEMA.extend( + { + vol.Required(_LIGHT_COLOR_MODE_SCHEMA): LightColorModeSchema.INDIVIDUAL.value, + **optional_ga_schema(CONF_GA_SWITCH, GASelector(write_required=True)), + **optional_ga_schema(CONF_GA_BRIGHTNESS, GASelector(write_required=True)), + vol.Required(CONF_GA_RED_BRIGHTNESS): GASelector(write_required=True), + **optional_ga_schema(CONF_GA_RED_SWITCH, GASelector(write_required=False)), + vol.Required(CONF_GA_GREEN_BRIGHTNESS): GASelector(write_required=True), + **optional_ga_schema(CONF_GA_GREEN_SWITCH, GASelector(write_required=False)), + vol.Required(CONF_GA_BLUE_BRIGHTNESS): GASelector(write_required=True), + **optional_ga_schema(CONF_GA_BLUE_SWITCH, GASelector(write_required=False)), + **optional_ga_schema(CONF_GA_WHITE_BRIGHTNESS, GASelector(write_required=True)), + **optional_ga_schema(CONF_GA_WHITE_SWITCH, GASelector(write_required=False)), + } +) + +_HSV_LIGHT_SCHEMA = _COMMON_LIGHT_SCHEMA.extend( + { + vol.Required(_LIGHT_COLOR_MODE_SCHEMA): LightColorModeSchema.HSV.value, + vol.Required(CONF_GA_SWITCH): GASelector(write_required=True), + vol.Required(CONF_GA_BRIGHTNESS): GASelector(write_required=True), + vol.Required(CONF_GA_HUE): GASelector(write_required=True), + vol.Required(CONF_GA_SATURATION): GASelector(write_required=True), + } +) + + +LIGHT_KNX_SCHEMA = cv.key_value_schemas( + _LIGHT_COLOR_MODE_SCHEMA, + default_schema=_DEFAULT_LIGHT_SCHEMA, + value_schemas={ + LightColorModeSchema.DEFAULT: _DEFAULT_LIGHT_SCHEMA, + LightColorModeSchema.INDIVIDUAL: _INDIVIDUAL_LIGHT_SCHEMA, + LightColorModeSchema.HSV: _HSV_LIGHT_SCHEMA, + }, +) + +LIGHT_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY): BASE_ENTITY_SCHEMA, + vol.Required(DOMAIN): LIGHT_KNX_SCHEMA, + } +) + ENTITY_STORE_DATA_SCHEMA: VolSchemaType = vol.All( vol.Schema( { @@ -79,6 +216,9 @@ ENTITY_STORE_DATA_SCHEMA: VolSchemaType = vol.All( Platform.SWITCH: vol.Schema( {vol.Required(CONF_DATA): SWITCH_SCHEMA}, extra=vol.ALLOW_EXTRA ), + Platform.LIGHT: vol.Schema( + {vol.Required("data"): LIGHT_SCHEMA}, extra=vol.ALLOW_EXTRA + ), }, ), ) diff --git a/homeassistant/components/knx/storage/knx_selector.py b/homeassistant/components/knx/storage/knx_selector.py index 396cde67fbd..1ac99d192b8 100644 --- a/homeassistant/components/knx/storage/knx_selector.py +++ b/homeassistant/components/knx/storage/knx_selector.py @@ -76,6 +76,6 @@ class GASelector: def _add_dpt(self, schema: dict[vol.Marker, Any]) -> None: """Add DPT validator to the schema.""" if self.dpt is not None: - schema[vol.Required(CONF_DPT)] = vol.In([item.value for item in self.dpt]) + schema[vol.Required(CONF_DPT)] = vol.In({item.value for item in self.dpt}) else: schema[vol.Remove(CONF_DPT)] = object diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index 94f5592db90..0a8a1dff964 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -28,6 +28,7 @@ from . import KNXModule from .const import ( CONF_INVERT, CONF_RESPOND_TO_READ, + CONF_SYNC_STATE, DATA_KNX_CONFIG, DOMAIN, KNX_ADDRESS, @@ -141,6 +142,7 @@ class KnxUiSwitch(_KnxSwitch): *config[DOMAIN][CONF_GA_SWITCH][CONF_GA_PASSIVE], ], respond_to_read=config[DOMAIN][CONF_RESPOND_TO_READ], + sync_state=config[DOMAIN][CONF_SYNC_STATE], invert=config[DOMAIN][CONF_INVERT], ) ) diff --git a/tests/components/knx/conftest.py b/tests/components/knx/conftest.py index 749d1c4252a..76f1b6f3ebc 100644 --- a/tests/components/knx/conftest.py +++ b/tests/components/knx/conftest.py @@ -337,7 +337,7 @@ async def create_ui_entity( ) res = await ws_client.receive_json() assert res["success"], res - assert res["result"]["success"] is True + assert res["result"]["success"] is True, res["result"] entity_id = res["result"]["entity_id"] entity = entity_registry.async_get(entity_id) diff --git a/tests/components/knx/test_light.py b/tests/components/knx/test_light.py index a14d1bb32ae..0c7a37979a8 100644 --- a/tests/components/knx/test_light.py +++ b/tests/components/knx/test_light.py @@ -7,7 +7,7 @@ from datetime import timedelta from xknx.core import XknxConnectionState from xknx.devices.light import Light as XknxLight -from homeassistant.components.knx.const import CONF_STATE_ADDRESS, KNX_ADDRESS +from homeassistant.components.knx.const import CONF_STATE_ADDRESS, KNX_ADDRESS, Platform from homeassistant.components.knx.schema import LightSchema from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -21,6 +21,7 @@ from homeassistant.const import CONF_NAME, STATE_OFF, STATE_ON from homeassistant.core import HomeAssistant from homeassistant.util import dt as dt_util +from . import KnxEntityGenerator from .conftest import KNXTestKit from tests.common import async_fire_time_changed @@ -1151,3 +1152,26 @@ async def test_light_rgbw_brightness(hass: HomeAssistant, knx: KNXTestKit) -> No knx.assert_state( "light.test", STATE_ON, brightness=50, rgbw_color=(100, 200, 55, 12) ) + + +async def test_light_ui_create( + hass: HomeAssistant, + knx: KNXTestKit, + create_ui_entity: KnxEntityGenerator, +) -> None: + """Test creating a switch.""" + await knx.setup_integration({}) + await create_ui_entity( + platform=Platform.LIGHT, + entity_data={"name": "test"}, + knx_data={ + "ga_switch": {"write": "1/1/1", "state": "2/2/2"}, + "_light_color_mode_schema": "default", + "sync_state": True, + }, + ) + # created entity sends read-request to KNX bus + await knx.assert_read("2/2/2") + await knx.receive_response("2/2/2", True) + state = hass.states.get("light.test") + assert state.state is STATE_ON