mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 15:47:12 +00:00
Refactor KNX UI conditional selectors and migrate store data (#146067)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
2f6c0a1b7f
commit
9fd2ad425c
@ -33,6 +33,7 @@ from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
|||||||
from .knx_module import KNXModule
|
from .knx_module import KNXModule
|
||||||
from .schema import LightSchema
|
from .schema import LightSchema
|
||||||
from .storage.const import (
|
from .storage.const import (
|
||||||
|
CONF_COLOR,
|
||||||
CONF_COLOR_TEMP_MAX,
|
CONF_COLOR_TEMP_MAX,
|
||||||
CONF_COLOR_TEMP_MIN,
|
CONF_COLOR_TEMP_MIN,
|
||||||
CONF_ENTITY,
|
CONF_ENTITY,
|
||||||
@ -223,7 +224,7 @@ def _create_ui_light(xknx: XKNX, knx_config: ConfigType, name: str) -> XknxLight
|
|||||||
if _color_temp_dpt == ColorTempModes.ABSOLUTE_FLOAT.value:
|
if _color_temp_dpt == ColorTempModes.ABSOLUTE_FLOAT.value:
|
||||||
color_temperature_type = ColorTemperatureType.FLOAT_2_BYTE
|
color_temperature_type = ColorTemperatureType.FLOAT_2_BYTE
|
||||||
|
|
||||||
color_dpt = conf.get_dpt(CONF_GA_COLOR)
|
color_dpt = conf.get_dpt(CONF_COLOR, CONF_GA_COLOR)
|
||||||
|
|
||||||
return XknxLight(
|
return XknxLight(
|
||||||
xknx,
|
xknx,
|
||||||
@ -232,59 +233,77 @@ def _create_ui_light(xknx: XKNX, knx_config: ConfigType, name: str) -> XknxLight
|
|||||||
group_address_switch_state=conf.get_state_and_passive(CONF_GA_SWITCH),
|
group_address_switch_state=conf.get_state_and_passive(CONF_GA_SWITCH),
|
||||||
group_address_brightness=conf.get_write(CONF_GA_BRIGHTNESS),
|
group_address_brightness=conf.get_write(CONF_GA_BRIGHTNESS),
|
||||||
group_address_brightness_state=conf.get_state_and_passive(CONF_GA_BRIGHTNESS),
|
group_address_brightness_state=conf.get_state_and_passive(CONF_GA_BRIGHTNESS),
|
||||||
group_address_color=conf.get_write(CONF_GA_COLOR)
|
group_address_color=(
|
||||||
if color_dpt == LightColorMode.RGB
|
conf.get_write(CONF_COLOR, CONF_GA_COLOR)
|
||||||
else None,
|
if color_dpt == LightColorMode.RGB
|
||||||
group_address_color_state=conf.get_state_and_passive(CONF_GA_COLOR)
|
else None
|
||||||
if color_dpt == LightColorMode.RGB
|
),
|
||||||
else None,
|
group_address_color_state=(
|
||||||
group_address_rgbw=conf.get_write(CONF_GA_COLOR)
|
conf.get_state_and_passive(CONF_COLOR, CONF_GA_COLOR)
|
||||||
if color_dpt == LightColorMode.RGBW
|
if color_dpt == LightColorMode.RGB
|
||||||
else None,
|
else None
|
||||||
group_address_rgbw_state=conf.get_state_and_passive(CONF_GA_COLOR)
|
),
|
||||||
if color_dpt == LightColorMode.RGBW
|
group_address_rgbw=(
|
||||||
else None,
|
conf.get_write(CONF_COLOR, CONF_GA_COLOR)
|
||||||
group_address_hue=conf.get_write(CONF_GA_HUE),
|
if color_dpt == LightColorMode.RGBW
|
||||||
group_address_hue_state=conf.get_state_and_passive(CONF_GA_HUE),
|
else None
|
||||||
group_address_saturation=conf.get_write(CONF_GA_SATURATION),
|
),
|
||||||
group_address_saturation_state=conf.get_state_and_passive(CONF_GA_SATURATION),
|
group_address_rgbw_state=(
|
||||||
group_address_xyy_color=conf.get_write(CONF_GA_COLOR)
|
conf.get_state_and_passive(CONF_COLOR, CONF_GA_COLOR)
|
||||||
if color_dpt == LightColorMode.XYY
|
if color_dpt == LightColorMode.RGBW
|
||||||
else None,
|
else None
|
||||||
group_address_xyy_color_state=conf.get_write(CONF_GA_COLOR)
|
),
|
||||||
if color_dpt == LightColorMode.XYY
|
group_address_hue=conf.get_write(CONF_COLOR, CONF_GA_HUE),
|
||||||
else None,
|
group_address_hue_state=conf.get_state_and_passive(CONF_COLOR, CONF_GA_HUE),
|
||||||
|
group_address_saturation=conf.get_write(CONF_COLOR, CONF_GA_SATURATION),
|
||||||
|
group_address_saturation_state=conf.get_state_and_passive(
|
||||||
|
CONF_COLOR, CONF_GA_SATURATION
|
||||||
|
),
|
||||||
|
group_address_xyy_color=(
|
||||||
|
conf.get_write(CONF_COLOR, CONF_GA_COLOR)
|
||||||
|
if color_dpt == LightColorMode.XYY
|
||||||
|
else None
|
||||||
|
),
|
||||||
|
group_address_xyy_color_state=(
|
||||||
|
conf.get_state_and_passive(CONF_COLOR, CONF_GA_COLOR)
|
||||||
|
if color_dpt == LightColorMode.XYY
|
||||||
|
else None
|
||||||
|
),
|
||||||
group_address_tunable_white=group_address_tunable_white,
|
group_address_tunable_white=group_address_tunable_white,
|
||||||
group_address_tunable_white_state=group_address_tunable_white_state,
|
group_address_tunable_white_state=group_address_tunable_white_state,
|
||||||
group_address_color_temperature=group_address_color_temp,
|
group_address_color_temperature=group_address_color_temp,
|
||||||
group_address_color_temperature_state=group_address_color_temp_state,
|
group_address_color_temperature_state=group_address_color_temp_state,
|
||||||
group_address_switch_red=conf.get_write(CONF_GA_RED_SWITCH),
|
group_address_switch_red=conf.get_write(CONF_COLOR, CONF_GA_RED_SWITCH),
|
||||||
group_address_switch_red_state=conf.get_state_and_passive(CONF_GA_RED_SWITCH),
|
group_address_switch_red_state=conf.get_state_and_passive(
|
||||||
group_address_brightness_red=conf.get_write(CONF_GA_RED_BRIGHTNESS),
|
CONF_COLOR, CONF_GA_RED_SWITCH
|
||||||
group_address_brightness_red_state=conf.get_state_and_passive(
|
|
||||||
CONF_GA_RED_BRIGHTNESS
|
|
||||||
),
|
),
|
||||||
group_address_switch_green=conf.get_write(CONF_GA_GREEN_SWITCH),
|
group_address_brightness_red=conf.get_write(CONF_COLOR, CONF_GA_RED_BRIGHTNESS),
|
||||||
|
group_address_brightness_red_state=conf.get_state_and_passive(
|
||||||
|
CONF_COLOR, CONF_GA_RED_BRIGHTNESS
|
||||||
|
),
|
||||||
|
group_address_switch_green=conf.get_write(CONF_COLOR, CONF_GA_GREEN_SWITCH),
|
||||||
group_address_switch_green_state=conf.get_state_and_passive(
|
group_address_switch_green_state=conf.get_state_and_passive(
|
||||||
CONF_GA_GREEN_SWITCH
|
CONF_COLOR, CONF_GA_GREEN_SWITCH
|
||||||
),
|
),
|
||||||
group_address_brightness_green=conf.get_write(CONF_GA_GREEN_BRIGHTNESS),
|
group_address_brightness_green=conf.get_write(CONF_GA_GREEN_BRIGHTNESS),
|
||||||
group_address_brightness_green_state=conf.get_state_and_passive(
|
group_address_brightness_green_state=conf.get_state_and_passive(
|
||||||
CONF_GA_GREEN_BRIGHTNESS
|
CONF_COLOR, CONF_GA_GREEN_BRIGHTNESS
|
||||||
),
|
),
|
||||||
group_address_switch_blue=conf.get_write(CONF_GA_BLUE_SWITCH),
|
group_address_switch_blue=conf.get_write(CONF_GA_BLUE_SWITCH),
|
||||||
group_address_switch_blue_state=conf.get_state_and_passive(CONF_GA_BLUE_SWITCH),
|
group_address_switch_blue_state=conf.get_state_and_passive(CONF_GA_BLUE_SWITCH),
|
||||||
group_address_brightness_blue=conf.get_write(CONF_GA_BLUE_BRIGHTNESS),
|
group_address_brightness_blue=conf.get_write(CONF_GA_BLUE_BRIGHTNESS),
|
||||||
group_address_brightness_blue_state=conf.get_state_and_passive(
|
group_address_brightness_blue_state=conf.get_state_and_passive(
|
||||||
CONF_GA_BLUE_BRIGHTNESS
|
CONF_COLOR, CONF_GA_BLUE_BRIGHTNESS
|
||||||
),
|
),
|
||||||
group_address_switch_white=conf.get_write(CONF_GA_WHITE_SWITCH),
|
group_address_switch_white=conf.get_write(CONF_COLOR, CONF_GA_WHITE_SWITCH),
|
||||||
group_address_switch_white_state=conf.get_state_and_passive(
|
group_address_switch_white_state=conf.get_state_and_passive(
|
||||||
CONF_GA_WHITE_SWITCH
|
CONF_COLOR, CONF_GA_WHITE_SWITCH
|
||||||
|
),
|
||||||
|
group_address_brightness_white=conf.get_write(
|
||||||
|
CONF_COLOR, CONF_GA_WHITE_BRIGHTNESS
|
||||||
),
|
),
|
||||||
group_address_brightness_white=conf.get_write(CONF_GA_WHITE_BRIGHTNESS),
|
|
||||||
group_address_brightness_white_state=conf.get_state_and_passive(
|
group_address_brightness_white_state=conf.get_state_and_passive(
|
||||||
CONF_GA_WHITE_BRIGHTNESS
|
CONF_COLOR, CONF_GA_WHITE_BRIGHTNESS
|
||||||
),
|
),
|
||||||
color_temperature_type=color_temperature_type,
|
color_temperature_type=color_temperature_type,
|
||||||
min_kelvin=knx_config[CONF_COLOR_TEMP_MIN],
|
min_kelvin=knx_config[CONF_COLOR_TEMP_MIN],
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"xknx==3.8.0",
|
"xknx==3.8.0",
|
||||||
"xknxproject==3.8.2",
|
"xknxproject==3.8.2",
|
||||||
"knx-frontend==2025.4.1.91934"
|
"knx-frontend==2025.6.13.181749"
|
||||||
],
|
],
|
||||||
"single_config_entry": true
|
"single_config_entry": true
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,11 @@ from homeassistant.util.ulid import ulid_now
|
|||||||
|
|
||||||
from ..const import DOMAIN
|
from ..const import DOMAIN
|
||||||
from .const import CONF_DATA
|
from .const import CONF_DATA
|
||||||
|
from .migration import migrate_1_to_2
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
STORAGE_VERSION: Final = 1
|
STORAGE_VERSION: Final = 2
|
||||||
STORAGE_KEY: Final = f"{DOMAIN}/config_store.json"
|
STORAGE_KEY: Final = f"{DOMAIN}/config_store.json"
|
||||||
|
|
||||||
type KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
|
type KNXPlatformStoreModel = dict[str, dict[str, Any]] # unique_id: configuration
|
||||||
@ -45,6 +46,20 @@ class PlatformControllerBase(ABC):
|
|||||||
"""Update an existing entities configuration."""
|
"""Update an existing entities configuration."""
|
||||||
|
|
||||||
|
|
||||||
|
class _KNXConfigStoreStorage(Store[KNXConfigStoreModel]):
|
||||||
|
"""Storage handler for KNXConfigStore."""
|
||||||
|
|
||||||
|
async def _async_migrate_func(
|
||||||
|
self, old_major_version: int, old_minor_version: int, old_data: dict[str, Any]
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Migrate to the new version."""
|
||||||
|
if old_major_version == 1:
|
||||||
|
# version 2 introduced in 2025.8
|
||||||
|
migrate_1_to_2(old_data)
|
||||||
|
|
||||||
|
return old_data
|
||||||
|
|
||||||
|
|
||||||
class KNXConfigStore:
|
class KNXConfigStore:
|
||||||
"""Manage KNX config store data."""
|
"""Manage KNX config store data."""
|
||||||
|
|
||||||
@ -56,7 +71,7 @@ class KNXConfigStore:
|
|||||||
"""Initialize config store."""
|
"""Initialize config store."""
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.config_entry = config_entry
|
self.config_entry = config_entry
|
||||||
self._store = Store[KNXConfigStoreModel](hass, STORAGE_VERSION, STORAGE_KEY)
|
self._store = _KNXConfigStoreStorage(hass, STORAGE_VERSION, STORAGE_KEY)
|
||||||
self.data = KNXConfigStoreModel(entities={})
|
self.data = KNXConfigStoreModel(entities={})
|
||||||
self._platform_controllers: dict[Platform, PlatformControllerBase] = {}
|
self._platform_controllers: dict[Platform, PlatformControllerBase] = {}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from typing import Final
|
from typing import Final
|
||||||
|
|
||||||
|
# Common
|
||||||
CONF_DATA: Final = "data"
|
CONF_DATA: Final = "data"
|
||||||
CONF_ENTITY: Final = "entity"
|
CONF_ENTITY: Final = "entity"
|
||||||
CONF_DEVICE_INFO: Final = "device_info"
|
CONF_DEVICE_INFO: Final = "device_info"
|
||||||
@ -12,10 +13,22 @@ CONF_DPT: Final = "dpt"
|
|||||||
|
|
||||||
CONF_GA_SENSOR: Final = "ga_sensor"
|
CONF_GA_SENSOR: Final = "ga_sensor"
|
||||||
CONF_GA_SWITCH: Final = "ga_switch"
|
CONF_GA_SWITCH: Final = "ga_switch"
|
||||||
CONF_GA_COLOR_TEMP: Final = "ga_color_temp"
|
|
||||||
|
# Cover
|
||||||
|
CONF_GA_UP_DOWN: Final = "ga_up_down"
|
||||||
|
CONF_GA_STOP: Final = "ga_stop"
|
||||||
|
CONF_GA_STEP: Final = "ga_step"
|
||||||
|
CONF_GA_POSITION_SET: Final = "ga_position_set"
|
||||||
|
CONF_GA_POSITION_STATE: Final = "ga_position_state"
|
||||||
|
CONF_GA_ANGLE: Final = "ga_angle"
|
||||||
|
|
||||||
|
# Light
|
||||||
CONF_COLOR_TEMP_MIN: Final = "color_temp_min"
|
CONF_COLOR_TEMP_MIN: Final = "color_temp_min"
|
||||||
CONF_COLOR_TEMP_MAX: Final = "color_temp_max"
|
CONF_COLOR_TEMP_MAX: Final = "color_temp_max"
|
||||||
CONF_GA_BRIGHTNESS: Final = "ga_brightness"
|
CONF_GA_BRIGHTNESS: Final = "ga_brightness"
|
||||||
|
CONF_GA_COLOR_TEMP: Final = "ga_color_temp"
|
||||||
|
# Light/color
|
||||||
|
CONF_COLOR: Final = "color"
|
||||||
CONF_GA_COLOR: Final = "ga_color"
|
CONF_GA_COLOR: Final = "ga_color"
|
||||||
CONF_GA_RED_BRIGHTNESS: Final = "ga_red_brightness"
|
CONF_GA_RED_BRIGHTNESS: Final = "ga_red_brightness"
|
||||||
CONF_GA_RED_SWITCH: Final = "ga_red_switch"
|
CONF_GA_RED_SWITCH: Final = "ga_red_switch"
|
||||||
@ -27,9 +40,3 @@ CONF_GA_WHITE_BRIGHTNESS: Final = "ga_white_brightness"
|
|||||||
CONF_GA_WHITE_SWITCH: Final = "ga_white_switch"
|
CONF_GA_WHITE_SWITCH: Final = "ga_white_switch"
|
||||||
CONF_GA_HUE: Final = "ga_hue"
|
CONF_GA_HUE: Final = "ga_hue"
|
||||||
CONF_GA_SATURATION: Final = "ga_saturation"
|
CONF_GA_SATURATION: Final = "ga_saturation"
|
||||||
CONF_GA_UP_DOWN: Final = "ga_up_down"
|
|
||||||
CONF_GA_STOP: Final = "ga_stop"
|
|
||||||
CONF_GA_STEP: Final = "ga_step"
|
|
||||||
CONF_GA_POSITION_SET: Final = "ga_position_set"
|
|
||||||
CONF_GA_POSITION_STATE: Final = "ga_position_state"
|
|
||||||
CONF_GA_ANGLE: Final = "ga_angle"
|
|
||||||
|
@ -29,6 +29,7 @@ from ..const import (
|
|||||||
)
|
)
|
||||||
from ..validation import sync_state_validator
|
from ..validation import sync_state_validator
|
||||||
from .const import (
|
from .const import (
|
||||||
|
CONF_COLOR,
|
||||||
CONF_COLOR_TEMP_MAX,
|
CONF_COLOR_TEMP_MAX,
|
||||||
CONF_COLOR_TEMP_MIN,
|
CONF_COLOR_TEMP_MIN,
|
||||||
CONF_DATA,
|
CONF_DATA,
|
||||||
@ -43,23 +44,20 @@ from .const import (
|
|||||||
CONF_GA_GREEN_BRIGHTNESS,
|
CONF_GA_GREEN_BRIGHTNESS,
|
||||||
CONF_GA_GREEN_SWITCH,
|
CONF_GA_GREEN_SWITCH,
|
||||||
CONF_GA_HUE,
|
CONF_GA_HUE,
|
||||||
CONF_GA_PASSIVE,
|
|
||||||
CONF_GA_POSITION_SET,
|
CONF_GA_POSITION_SET,
|
||||||
CONF_GA_POSITION_STATE,
|
CONF_GA_POSITION_STATE,
|
||||||
CONF_GA_RED_BRIGHTNESS,
|
CONF_GA_RED_BRIGHTNESS,
|
||||||
CONF_GA_RED_SWITCH,
|
CONF_GA_RED_SWITCH,
|
||||||
CONF_GA_SATURATION,
|
CONF_GA_SATURATION,
|
||||||
CONF_GA_SENSOR,
|
CONF_GA_SENSOR,
|
||||||
CONF_GA_STATE,
|
|
||||||
CONF_GA_STEP,
|
CONF_GA_STEP,
|
||||||
CONF_GA_STOP,
|
CONF_GA_STOP,
|
||||||
CONF_GA_SWITCH,
|
CONF_GA_SWITCH,
|
||||||
CONF_GA_UP_DOWN,
|
CONF_GA_UP_DOWN,
|
||||||
CONF_GA_WHITE_BRIGHTNESS,
|
CONF_GA_WHITE_BRIGHTNESS,
|
||||||
CONF_GA_WHITE_SWITCH,
|
CONF_GA_WHITE_SWITCH,
|
||||||
CONF_GA_WRITE,
|
|
||||||
)
|
)
|
||||||
from .knx_selector import GASelector
|
from .knx_selector import GASelector, GroupSelect
|
||||||
|
|
||||||
BASE_ENTITY_SCHEMA = vol.All(
|
BASE_ENTITY_SCHEMA = vol.All(
|
||||||
{
|
{
|
||||||
@ -87,24 +85,6 @@ 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,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BINARY_SENSOR_SCHEMA = vol.Schema(
|
BINARY_SENSOR_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
vol.Required(CONF_ENTITY): BASE_ENTITY_SCHEMA,
|
vol.Required(CONF_ENTITY): BASE_ENTITY_SCHEMA,
|
||||||
@ -134,16 +114,14 @@ COVER_SCHEMA = vol.Schema(
|
|||||||
vol.Required(DOMAIN): vol.All(
|
vol.Required(DOMAIN): vol.All(
|
||||||
vol.Schema(
|
vol.Schema(
|
||||||
{
|
{
|
||||||
**optional_ga_schema(CONF_GA_UP_DOWN, GASelector(state=False)),
|
vol.Optional(CONF_GA_UP_DOWN): GASelector(state=False),
|
||||||
vol.Optional(CoverConf.INVERT_UPDOWN): selector.BooleanSelector(),
|
vol.Optional(CoverConf.INVERT_UPDOWN): selector.BooleanSelector(),
|
||||||
**optional_ga_schema(CONF_GA_STOP, GASelector(state=False)),
|
vol.Optional(CONF_GA_STOP): GASelector(state=False),
|
||||||
**optional_ga_schema(CONF_GA_STEP, GASelector(state=False)),
|
vol.Optional(CONF_GA_STEP): GASelector(state=False),
|
||||||
**optional_ga_schema(CONF_GA_POSITION_SET, GASelector(state=False)),
|
vol.Optional(CONF_GA_POSITION_SET): GASelector(state=False),
|
||||||
**optional_ga_schema(
|
vol.Optional(CONF_GA_POSITION_STATE): GASelector(write=False),
|
||||||
CONF_GA_POSITION_STATE, GASelector(write=False)
|
|
||||||
),
|
|
||||||
vol.Optional(CoverConf.INVERT_POSITION): selector.BooleanSelector(),
|
vol.Optional(CoverConf.INVERT_POSITION): selector.BooleanSelector(),
|
||||||
**optional_ga_schema(CONF_GA_ANGLE, GASelector()),
|
vol.Optional(CONF_GA_ANGLE): GASelector(),
|
||||||
vol.Optional(CoverConf.INVERT_ANGLE): selector.BooleanSelector(),
|
vol.Optional(CoverConf.INVERT_ANGLE): selector.BooleanSelector(),
|
||||||
vol.Optional(
|
vol.Optional(
|
||||||
CoverConf.TRAVELLING_TIME_DOWN, default=25
|
CoverConf.TRAVELLING_TIME_DOWN, default=25
|
||||||
@ -208,72 +186,111 @@ class LightColorModeSchema(StrEnum):
|
|||||||
HSV = "hsv"
|
HSV = "hsv"
|
||||||
|
|
||||||
|
|
||||||
_LIGHT_COLOR_MODE_SCHEMA = "_light_color_mode_schema"
|
_hs_color_inclusion_msg = (
|
||||||
|
"'Hue', 'Saturation' and 'Brightness' addresses are required for HSV configuration"
|
||||||
|
)
|
||||||
|
|
||||||
_COMMON_LIGHT_SCHEMA = vol.Schema(
|
|
||||||
{
|
LIGHT_KNX_SCHEMA = vol.All(
|
||||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
vol.Schema(
|
||||||
**optional_ga_schema(
|
{
|
||||||
CONF_GA_COLOR_TEMP, GASelector(write_required=True, dpt=ColorTempModes)
|
vol.Optional(CONF_GA_SWITCH): GASelector(write_required=True),
|
||||||
|
vol.Optional(CONF_GA_BRIGHTNESS): GASelector(write_required=True),
|
||||||
|
vol.Optional(CONF_GA_COLOR_TEMP): GASelector(
|
||||||
|
write_required=True, dpt=ColorTempModes
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_COLOR): GroupSelect(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_GA_COLOR): GASelector(
|
||||||
|
write_required=True, dpt=LightColorMode
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_GA_RED_BRIGHTNESS): GASelector(
|
||||||
|
write_required=True
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_GA_RED_SWITCH): GASelector(
|
||||||
|
write_required=False
|
||||||
|
),
|
||||||
|
vol.Required(CONF_GA_GREEN_BRIGHTNESS): GASelector(
|
||||||
|
write_required=True
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_GA_GREEN_SWITCH): GASelector(
|
||||||
|
write_required=False
|
||||||
|
),
|
||||||
|
vol.Required(CONF_GA_BLUE_BRIGHTNESS): GASelector(
|
||||||
|
write_required=True
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_GA_BLUE_SWITCH): GASelector(
|
||||||
|
write_required=False
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_GA_WHITE_BRIGHTNESS): GASelector(
|
||||||
|
write_required=True
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_GA_WHITE_SWITCH): GASelector(
|
||||||
|
write_required=False
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_GA_HUE): GASelector(write_required=True),
|
||||||
|
vol.Required(CONF_GA_SATURATION): GASelector(
|
||||||
|
write_required=True
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
# msg="error in `color` config",
|
||||||
|
),
|
||||||
|
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||||
|
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)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
vol.Any(
|
||||||
|
vol.Schema(
|
||||||
|
{vol.Required(CONF_GA_SWITCH): object},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_COLOR_TEMP_MIN, default=2700): vol.All(
|
vol.Schema( # brightness addresses are required in INDIVIDUAL_COLOR_SCHEMA
|
||||||
vol.Coerce(int), vol.Range(min=1)
|
{vol.Required(CONF_COLOR): {vol.Required(CONF_GA_RED_BRIGHTNESS): object}},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
),
|
),
|
||||||
vol.Optional(CONF_COLOR_TEMP_MAX, default=6000): vol.All(
|
msg="either 'address' or 'individual_colors' is required",
|
||||||
vol.Coerce(int), vol.Range(min=1)
|
),
|
||||||
|
vol.Any(
|
||||||
|
vol.Schema( # 'brightness' is non-optional for hs-color
|
||||||
|
{
|
||||||
|
vol.Required(CONF_GA_BRIGHTNESS, msg=_hs_color_inclusion_msg): object,
|
||||||
|
vol.Required(CONF_COLOR): {
|
||||||
|
vol.Required(CONF_GA_HUE, msg=_hs_color_inclusion_msg): object,
|
||||||
|
vol.Required(
|
||||||
|
CONF_GA_SATURATION, msg=_hs_color_inclusion_msg
|
||||||
|
): object,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
),
|
),
|
||||||
},
|
vol.Schema( # hs-colors not used
|
||||||
extra=vol.REMOVE_EXTRA,
|
{
|
||||||
)
|
vol.Optional(CONF_COLOR): {
|
||||||
|
vol.Optional(CONF_GA_HUE): None,
|
||||||
_DEFAULT_LIGHT_SCHEMA = _COMMON_LIGHT_SCHEMA.extend(
|
vol.Optional(CONF_GA_SATURATION): None,
|
||||||
{
|
},
|
||||||
vol.Required(_LIGHT_COLOR_MODE_SCHEMA): LightColorModeSchema.DEFAULT.value,
|
},
|
||||||
vol.Required(CONF_GA_SWITCH): GASelector(write_required=True),
|
extra=vol.ALLOW_EXTRA,
|
||||||
**optional_ga_schema(CONF_GA_BRIGHTNESS, GASelector(write_required=True)),
|
|
||||||
**optional_ga_schema(
|
|
||||||
CONF_GA_COLOR,
|
|
||||||
GASelector(write_required=True, dpt=LightColorMode),
|
|
||||||
),
|
),
|
||||||
}
|
msg=_hs_color_inclusion_msg,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
_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(
|
LIGHT_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""Selectors for KNX."""
|
"""Selectors for KNX."""
|
||||||
|
|
||||||
|
from collections.abc import Hashable, Iterable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -9,6 +10,31 @@ from ..validation import ga_validator, maybe_ga_validator
|
|||||||
from .const import CONF_DPT, CONF_GA_PASSIVE, CONF_GA_STATE, CONF_GA_WRITE
|
from .const import CONF_DPT, CONF_GA_PASSIVE, CONF_GA_STATE, CONF_GA_WRITE
|
||||||
|
|
||||||
|
|
||||||
|
class GroupSelect(vol.Any):
|
||||||
|
"""Use the first validated value.
|
||||||
|
|
||||||
|
This is a version of vol.Any with custom error handling to
|
||||||
|
show proper invalid markers for sub-schema items in the UI.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _exec(self, funcs: Iterable, v: Any, path: list[Hashable] | None = None) -> Any:
|
||||||
|
"""Execute the validation functions."""
|
||||||
|
errors: list[vol.Invalid] = []
|
||||||
|
for func in funcs:
|
||||||
|
try:
|
||||||
|
if path is None:
|
||||||
|
return func(v)
|
||||||
|
return func(path, v)
|
||||||
|
except vol.Invalid as e:
|
||||||
|
errors.append(e)
|
||||||
|
if errors:
|
||||||
|
raise next(
|
||||||
|
(err for err in errors if "extra keys not allowed" not in err.msg),
|
||||||
|
errors[0],
|
||||||
|
)
|
||||||
|
raise vol.AnyInvalid(self.msg or "no valid value found", path=path)
|
||||||
|
|
||||||
|
|
||||||
class GASelector:
|
class GASelector:
|
||||||
"""Selector for a KNX group address structure."""
|
"""Selector for a KNX group address structure."""
|
||||||
|
|
||||||
|
42
homeassistant/components/knx/storage/migration.py
Normal file
42
homeassistant/components/knx/storage/migration.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"""Migration functions for KNX config store schema."""
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
from . import const as store_const
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_1_to_2(data: dict[str, Any]) -> None:
|
||||||
|
"""Migrate from schema 1 to schema 2."""
|
||||||
|
if lights := data.get("entities", {}).get(Platform.LIGHT):
|
||||||
|
for light in lights.values():
|
||||||
|
_migrate_light_schema_1_to_2(light["knx"])
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_light_schema_1_to_2(light_knx_data: dict[str, Any]) -> None:
|
||||||
|
"""Migrate light color mode schema."""
|
||||||
|
# Remove no more needed helper data from schema
|
||||||
|
light_knx_data.pop("_light_color_mode_schema", None)
|
||||||
|
|
||||||
|
# Move color related group addresses to new "color" key
|
||||||
|
color: dict[str, Any] = {}
|
||||||
|
for color_key in (
|
||||||
|
# optional / required and exclusive keys are the same in old and new schema
|
||||||
|
store_const.CONF_GA_COLOR,
|
||||||
|
store_const.CONF_GA_HUE,
|
||||||
|
store_const.CONF_GA_SATURATION,
|
||||||
|
store_const.CONF_GA_RED_BRIGHTNESS,
|
||||||
|
store_const.CONF_GA_RED_SWITCH,
|
||||||
|
store_const.CONF_GA_GREEN_BRIGHTNESS,
|
||||||
|
store_const.CONF_GA_GREEN_SWITCH,
|
||||||
|
store_const.CONF_GA_BLUE_BRIGHTNESS,
|
||||||
|
store_const.CONF_GA_BLUE_SWITCH,
|
||||||
|
store_const.CONF_GA_WHITE_BRIGHTNESS,
|
||||||
|
store_const.CONF_GA_WHITE_SWITCH,
|
||||||
|
):
|
||||||
|
if color_key in light_knx_data:
|
||||||
|
color[color_key] = light_knx_data.pop(color_key)
|
||||||
|
|
||||||
|
if color:
|
||||||
|
light_knx_data[store_const.CONF_COLOR] = color
|
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@ -1304,7 +1304,7 @@ kiwiki-client==0.1.1
|
|||||||
knocki==0.4.2
|
knocki==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2025.4.1.91934
|
knx-frontend==2025.6.13.181749
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
|
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@ -1126,7 +1126,7 @@ kegtron-ble==0.4.0
|
|||||||
knocki==0.4.2
|
knocki==0.4.2
|
||||||
|
|
||||||
# homeassistant.components.knx
|
# homeassistant.components.knx
|
||||||
knx-frontend==2025.4.1.91934
|
knx-frontend==2025.6.13.181749
|
||||||
|
|
||||||
# homeassistant.components.konnected
|
# homeassistant.components.konnected
|
||||||
konnected==1.2.0
|
konnected==1.2.0
|
||||||
|
@ -76,6 +76,7 @@ class KNXTestKit:
|
|||||||
yaml_config: ConfigType | None = None,
|
yaml_config: ConfigType | None = None,
|
||||||
config_store_fixture: str | None = None,
|
config_store_fixture: str | None = None,
|
||||||
add_entry_to_hass: bool = True,
|
add_entry_to_hass: bool = True,
|
||||||
|
state_updater: bool = True,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Create the KNX integration."""
|
"""Create the KNX integration."""
|
||||||
|
|
||||||
@ -118,14 +119,24 @@ class KNXTestKit:
|
|||||||
self.mock_config_entry.add_to_hass(self.hass)
|
self.mock_config_entry.add_to_hass(self.hass)
|
||||||
|
|
||||||
knx_config = {DOMAIN: yaml_config or {}}
|
knx_config = {DOMAIN: yaml_config or {}}
|
||||||
with patch(
|
with (
|
||||||
"xknx.xknx.knx_interface_factory",
|
patch(
|
||||||
return_value=knx_ip_interface_mock(),
|
"xknx.xknx.knx_interface_factory",
|
||||||
side_effect=fish_xknx,
|
return_value=knx_ip_interface_mock(),
|
||||||
|
side_effect=fish_xknx,
|
||||||
|
),
|
||||||
):
|
):
|
||||||
|
state_updater_patcher = patch(
|
||||||
|
"xknx.xknx.StateUpdater.register_remote_value"
|
||||||
|
)
|
||||||
|
if not state_updater:
|
||||||
|
state_updater_patcher.start()
|
||||||
|
|
||||||
await async_setup_component(self.hass, DOMAIN, knx_config)
|
await async_setup_component(self.hass, DOMAIN, knx_config)
|
||||||
await self.hass.async_block_till_done()
|
await self.hass.async_block_till_done()
|
||||||
|
|
||||||
|
state_updater_patcher.stop()
|
||||||
|
|
||||||
########################
|
########################
|
||||||
# Telegram counter tests
|
# Telegram counter tests
|
||||||
########################
|
########################
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"minor_version": 1,
|
"minor_version": 1,
|
||||||
"key": "knx/config_store.json",
|
"key": "knx/config_store.json",
|
||||||
"data": {
|
"data": {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"minor_version": 1,
|
"minor_version": 1,
|
||||||
"key": "knx/config_store.json",
|
"key": "knx/config_store.json",
|
||||||
"data": {
|
"data": {
|
||||||
|
142
tests/components/knx/fixtures/config_store_light.json
Normal file
142
tests/components/knx/fixtures/config_store_light.json
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
{
|
||||||
|
"version": 2,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "knx/config_store.json",
|
||||||
|
"data": {
|
||||||
|
"entities": {
|
||||||
|
"light": {
|
||||||
|
"knx_es_01JWDFHP1ZG6NT62BX6ENR3MG7": {
|
||||||
|
"entity": {
|
||||||
|
"name": "rgbw",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "1/0/1",
|
||||||
|
"state": "1/0/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_brightness": {
|
||||||
|
"write": "1/1/1",
|
||||||
|
"state": "1/1/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000,
|
||||||
|
"color": {
|
||||||
|
"ga_color": {
|
||||||
|
"write": "1/2/1",
|
||||||
|
"dpt": "251.600",
|
||||||
|
"state": "1/2/0",
|
||||||
|
"passive": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFKBG3PYPPRQDJZ3N3PMCB": {
|
||||||
|
"entity": {
|
||||||
|
"name": "individual colors",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000,
|
||||||
|
"color": {
|
||||||
|
"ga_red_brightness": {
|
||||||
|
"write": "2/1/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_red_switch": {
|
||||||
|
"write": "2/1/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_green_brightness": {
|
||||||
|
"write": "2/2/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_green_switch": {
|
||||||
|
"write": "2/2/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_blue_brightness": {
|
||||||
|
"write": "2/3/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_blue_switch": {
|
||||||
|
"write": "2/3/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFMSYYRDBDJYJR1K29ABEE": {
|
||||||
|
"entity": {
|
||||||
|
"name": "hsv",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "3/0/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_brightness": {
|
||||||
|
"write": "3/1/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000,
|
||||||
|
"color": {
|
||||||
|
"ga_hue": {
|
||||||
|
"write": "3/2/1",
|
||||||
|
"state": "3/2/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_saturation": {
|
||||||
|
"write": "3/3/1",
|
||||||
|
"state": "3/3/0",
|
||||||
|
"passive": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFP1RH50JXP5D2SSSRKWWT": {
|
||||||
|
"entity": {
|
||||||
|
"name": "ct",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "4/0/1",
|
||||||
|
"state": "4/0/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_color_temp": {
|
||||||
|
"write": "4/1/1",
|
||||||
|
"dpt": "7.600",
|
||||||
|
"state": "4/1/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"color_temp_max": 4788,
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 2,
|
||||||
"minor_version": 1,
|
"minor_version": 1,
|
||||||
"key": "knx/config_store.json",
|
"key": "knx/config_store.json",
|
||||||
"data": {
|
"data": {
|
||||||
@ -33,7 +33,6 @@
|
|||||||
"knx": {
|
"knx": {
|
||||||
"color_temp_min": 2700,
|
"color_temp_min": 2700,
|
||||||
"color_temp_max": 6000,
|
"color_temp_max": 6000,
|
||||||
"_light_color_mode_schema": "default",
|
|
||||||
"ga_switch": {
|
"ga_switch": {
|
||||||
"write": "1/1/21",
|
"write": "1/1/21",
|
||||||
"state": "1/0/21",
|
"state": "1/0/21",
|
||||||
|
140
tests/components/knx/fixtures/config_store_light_v1.json
Normal file
140
tests/components/knx/fixtures/config_store_light_v1.json
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"minor_version": 1,
|
||||||
|
"key": "knx/config_store.json",
|
||||||
|
"data": {
|
||||||
|
"entities": {
|
||||||
|
"light": {
|
||||||
|
"knx_es_01JWDFHP1ZG6NT62BX6ENR3MG7": {
|
||||||
|
"entity": {
|
||||||
|
"name": "rgbw",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"_light_color_mode_schema": "default",
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "1/0/1",
|
||||||
|
"state": "1/0/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_brightness": {
|
||||||
|
"write": "1/1/1",
|
||||||
|
"state": "1/1/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_color": {
|
||||||
|
"write": "1/2/1",
|
||||||
|
"dpt": "251.600",
|
||||||
|
"state": "1/2/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFKBG3PYPPRQDJZ3N3PMCB": {
|
||||||
|
"entity": {
|
||||||
|
"name": "individual colors",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"_light_color_mode_schema": "individual",
|
||||||
|
"ga_red_switch": {
|
||||||
|
"write": "2/1/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_red_brightness": {
|
||||||
|
"write": "2/1/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_green_switch": {
|
||||||
|
"write": "2/2/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_green_brightness": {
|
||||||
|
"write": "2/2/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_blue_switch": {
|
||||||
|
"write": "2/3/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_blue_brightness": {
|
||||||
|
"write": "2/3/2",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFMSYYRDBDJYJR1K29ABEE": {
|
||||||
|
"entity": {
|
||||||
|
"name": "hsv",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"_light_color_mode_schema": "hsv",
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "3/0/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_brightness": {
|
||||||
|
"write": "3/1/1",
|
||||||
|
"state": null,
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_hue": {
|
||||||
|
"write": "3/2/1",
|
||||||
|
"state": "3/2/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_saturation": {
|
||||||
|
"write": "3/3/1",
|
||||||
|
"state": "3/3/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700,
|
||||||
|
"color_temp_max": 6000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"knx_es_01JWDFP1RH50JXP5D2SSSRKWWT": {
|
||||||
|
"entity": {
|
||||||
|
"name": "ct",
|
||||||
|
"device_info": null,
|
||||||
|
"entity_category": null
|
||||||
|
},
|
||||||
|
"knx": {
|
||||||
|
"_light_color_mode_schema": "default",
|
||||||
|
"ga_switch": {
|
||||||
|
"write": "4/0/1",
|
||||||
|
"state": "4/0/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"ga_color_temp": {
|
||||||
|
"write": "4/1/1",
|
||||||
|
"dpt": "7.600",
|
||||||
|
"state": "4/1/0",
|
||||||
|
"passive": []
|
||||||
|
},
|
||||||
|
"color_temp_max": 4788,
|
||||||
|
"sync_state": true,
|
||||||
|
"color_temp_min": 2700
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ from homeassistant.helpers import entity_registry as er
|
|||||||
from . import KnxEntityGenerator
|
from . import KnxEntityGenerator
|
||||||
from .conftest import KNXTestKit
|
from .conftest import KNXTestKit
|
||||||
|
|
||||||
|
from tests.common import async_load_json_object_fixture
|
||||||
from tests.typing import WebSocketGenerator
|
from tests.typing import WebSocketGenerator
|
||||||
|
|
||||||
|
|
||||||
@ -379,6 +380,7 @@ async def test_validate_entity(
|
|||||||
await knx.setup_integration()
|
await knx.setup_integration()
|
||||||
client = await hass_ws_client(hass)
|
client = await hass_ws_client(hass)
|
||||||
|
|
||||||
|
# valid data
|
||||||
await client.send_json_auto_id(
|
await client.send_json_auto_id(
|
||||||
{
|
{
|
||||||
"type": "knx/validate_entity",
|
"type": "knx/validate_entity",
|
||||||
@ -410,3 +412,49 @@ async def test_validate_entity(
|
|||||||
assert res["result"]["errors"][0]["path"] == ["data", "knx", "ga_switch", "write"]
|
assert res["result"]["errors"][0]["path"] == ["data", "knx", "ga_switch", "write"]
|
||||||
assert res["result"]["errors"][0]["error_message"] == "required key not provided"
|
assert res["result"]["errors"][0]["error_message"] == "required key not provided"
|
||||||
assert res["result"]["error_base"].startswith("required key not provided")
|
assert res["result"]["error_base"].startswith("required key not provided")
|
||||||
|
|
||||||
|
# invalid group_select data
|
||||||
|
await client.send_json_auto_id(
|
||||||
|
{
|
||||||
|
"type": "knx/validate_entity",
|
||||||
|
"platform": Platform.LIGHT,
|
||||||
|
"data": {
|
||||||
|
"entity": {"name": "test_name"},
|
||||||
|
"knx": {
|
||||||
|
"color": {
|
||||||
|
"ga_red_brightness": {"write": "1/2/3"},
|
||||||
|
"ga_green_brightness": {"write": "1/2/4"},
|
||||||
|
# ga_blue_brightness is missing - which is required
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
res = await client.receive_json()
|
||||||
|
assert res["success"], res
|
||||||
|
assert res["result"]["success"] is False
|
||||||
|
# This shall test that a required key of the second GroupSelect schema is missing
|
||||||
|
# and not yield the "extra keys not allowed" error of the first GroupSelect Schema
|
||||||
|
assert res["result"]["errors"][0]["path"] == [
|
||||||
|
"data",
|
||||||
|
"knx",
|
||||||
|
"color",
|
||||||
|
"ga_blue_brightness",
|
||||||
|
]
|
||||||
|
assert res["result"]["errors"][0]["error_message"] == "required key not provided"
|
||||||
|
assert res["result"]["error_base"].startswith("required key not provided")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migration_1_to_2(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
knx: KNXTestKit,
|
||||||
|
hass_storage: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
|
"""Test migration from schema 1 to schema 2."""
|
||||||
|
await knx.setup_integration(
|
||||||
|
config_store_fixture="config_store_light_v1.json", state_updater=False
|
||||||
|
)
|
||||||
|
new_data = await async_load_json_object_fixture(
|
||||||
|
hass, "config_store_light.json", "knx"
|
||||||
|
)
|
||||||
|
assert hass_storage[KNX_CONFIG_STORAGE_KEY] == new_data
|
||||||
|
@ -1182,7 +1182,6 @@ async def test_light_ui_create(
|
|||||||
entity_data={"name": "test"},
|
entity_data={"name": "test"},
|
||||||
knx_data={
|
knx_data={
|
||||||
"ga_switch": {"write": "1/1/1", "state": "2/2/2"},
|
"ga_switch": {"write": "1/1/1", "state": "2/2/2"},
|
||||||
"_light_color_mode_schema": "default",
|
|
||||||
"sync_state": True,
|
"sync_state": True,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1223,7 +1222,6 @@ async def test_light_ui_color_temp(
|
|||||||
"write": "3/3/3",
|
"write": "3/3/3",
|
||||||
"dpt": color_temp_mode,
|
"dpt": color_temp_mode,
|
||||||
},
|
},
|
||||||
"_light_color_mode_schema": "default",
|
|
||||||
"sync_state": True,
|
"sync_state": True,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1257,7 +1255,6 @@ async def test_light_ui_multi_mode(
|
|||||||
knx_data={
|
knx_data={
|
||||||
"color_temp_min": 2700,
|
"color_temp_min": 2700,
|
||||||
"color_temp_max": 6000,
|
"color_temp_max": 6000,
|
||||||
"_light_color_mode_schema": "default",
|
|
||||||
"ga_switch": {
|
"ga_switch": {
|
||||||
"write": "1/1/1",
|
"write": "1/1/1",
|
||||||
"passive": [],
|
"passive": [],
|
||||||
@ -1275,11 +1272,13 @@ async def test_light_ui_multi_mode(
|
|||||||
"state": "0/6/3",
|
"state": "0/6/3",
|
||||||
"passive": [],
|
"passive": [],
|
||||||
},
|
},
|
||||||
"ga_color": {
|
"color": {
|
||||||
"write": "0/6/4",
|
"ga_color": {
|
||||||
"dpt": "251.600",
|
"write": "0/6/4",
|
||||||
"state": "0/6/5",
|
"dpt": "251.600",
|
||||||
"passive": [],
|
"state": "0/6/5",
|
||||||
|
"passive": [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user