Add light port support

This commit is contained in:
2026-02-15 18:00:55 +01:00
parent c5ab9e854c
commit 7953cd25ba
48 changed files with 6122 additions and 495 deletions

View File

@@ -16,6 +16,42 @@ from .const import (
CONF_COMMAND_ADDRESS,
CONF_INVERT_OUTGOING,
CONF_KNX_ENTRY_ID,
CONF_LIGHT_ADDRESS,
CONF_LIGHT_STATE_ADDRESS,
CONF_LIGHT_BRIGHTNESS_ADDRESS,
CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_COLOR_ADDRESS,
CONF_LIGHT_COLOR_STATE_ADDRESS,
CONF_LIGHT_RGBW_ADDRESS,
CONF_LIGHT_RGBW_STATE_ADDRESS,
CONF_LIGHT_HUE_ADDRESS,
CONF_LIGHT_HUE_STATE_ADDRESS,
CONF_LIGHT_SATURATION_ADDRESS,
CONF_LIGHT_SATURATION_STATE_ADDRESS,
CONF_LIGHT_XYY_ADDRESS,
CONF_LIGHT_XYY_STATE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_MODE,
CONF_LIGHT_MIN_KELVIN,
CONF_LIGHT_MAX_KELVIN,
CONF_LIGHT_RED_ADDRESS,
CONF_LIGHT_RED_STATE_ADDRESS,
CONF_LIGHT_RED_BRIGHTNESS_ADDRESS,
CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_GREEN_ADDRESS,
CONF_LIGHT_GREEN_STATE_ADDRESS,
CONF_LIGHT_GREEN_BRIGHTNESS_ADDRESS,
CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_BLUE_ADDRESS,
CONF_LIGHT_BLUE_STATE_ADDRESS,
CONF_LIGHT_BLUE_BRIGHTNESS_ADDRESS,
CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_WHITE_ADDRESS,
CONF_LIGHT_WHITE_STATE_ADDRESS,
CONF_LIGHT_WHITE_BRIGHTNESS_ADDRESS,
CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS,
LIGHT_COLOR_TEMPERATURE_MODES,
CONF_MOVE_LONG_ADDRESS,
CONF_MOVE_SHORT_ADDRESS,
CONF_PORTS,
@@ -85,6 +121,9 @@ class HAKnxBridgeConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"cover": subentry_type(
name="Cover Port", flow_class=CoverPortSubentryFlow
),
"light": subentry_type(
name="Light Port", flow_class=LightPortSubentryFlow
),
}
@@ -99,6 +138,7 @@ class HAKnxBridgeOptionsFlow(config_entries.OptionsFlow):
"add_binary_sensor",
"add_switch",
"add_cover",
"add_light",
"edit_port",
"remove_port",
],
@@ -163,6 +203,19 @@ class HAKnxBridgeOptionsFlow(config_entries.OptionsFlow):
return self.async_show_form(step_id="add_cover", data_schema=_cover_schema())
async def async_step_add_light(self, user_input: dict | None = None):
if user_input is not None:
user_input, errors = _validate_knx_addresses(
user_input, _port_keys("light")
)
if errors:
return self.async_show_form(
step_id="add_light", data_schema=_light_schema(), errors=errors
)
return await self._async_store_port("light", user_input)
return self.async_show_form(step_id="add_light", data_schema=_light_schema())
async def async_step_remove_port(self, user_input: dict | None = None):
ports = list(self._config_entry.options.get(CONF_PORTS, []))
if not ports:
@@ -223,6 +276,11 @@ class HAKnxBridgeOptionsFlow(config_entries.OptionsFlow):
step_id="edit_cover",
data_schema=_cover_schema(defaults=data),
)
if port_type == "light":
return self.async_show_form(
step_id="edit_light",
data_schema=_light_schema(defaults=data),
)
return self.async_abort(reason="no_ports_to_edit")
options = [
@@ -253,6 +311,9 @@ class HAKnxBridgeOptionsFlow(config_entries.OptionsFlow):
async def async_step_edit_cover(self, user_input: dict | None = None):
return await self._async_edit_port("cover", user_input, _cover_schema)
async def async_step_edit_light(self, user_input: dict | None = None):
return await self._async_edit_port("light", user_input, _light_schema)
async def _async_store_port(self, port_type: str, user_input: dict):
ports = list(self._config_entry.options.get(CONF_PORTS, []))
title = _entity_title(self.hass, user_input[CONF_ENTITY_ID])
@@ -375,6 +436,23 @@ class CoverPortSubentryFlow(config_entries.ConfigSubentryFlow):
return self.async_show_form(step_id="user", data_schema=_cover_schema())
class LightPortSubentryFlow(config_entries.ConfigSubentryFlow):
VERSION = 1
async def async_step_user(self, user_input: dict | None = None):
if user_input is not None:
user_input, errors = _validate_knx_addresses(
user_input, _port_keys("light")
)
if errors:
return self.async_show_form(
step_id="user", data_schema=_light_schema(), errors=errors
)
title = _entity_title(self.hass, user_input[CONF_ENTITY_ID])
return self.async_create_entry(title=title, data=user_input)
return self.async_show_form(step_id="user", data_schema=_light_schema())
def _entity_title(hass, entity_id: str) -> str:
state = hass.states.get(entity_id)
if state is None:
@@ -517,6 +595,322 @@ def _switch_schema(defaults: dict | None = None) -> vol.Schema:
)
def _light_schema(defaults: dict | None = None) -> vol.Schema:
defaults = defaults or {}
color_temperature_options = [
{
"value": mode,
"label": mode.replace("_", " ").title(),
}
for mode in LIGHT_COLOR_TEMPERATURE_MODES
]
return vol.Schema(
{
vol.Required(
CONF_ENTITY_ID,
default=defaults.get(CONF_ENTITY_ID, ""),
): selector.EntitySelector(
selector.EntitySelectorConfig(domain=["light"])
),
vol.Optional(
CONF_LIGHT_ADDRESS,
default=defaults.get(CONF_LIGHT_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_STATE_ADDRESS),
default=bool(defaults.get(_invert_out_key(CONF_LIGHT_STATE_ADDRESS))),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_BRIGHTNESS_ADDRESS,
default=defaults.get(CONF_LIGHT_BRIGHTNESS_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_COLOR_ADDRESS,
default=defaults.get(CONF_LIGHT_COLOR_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_COLOR_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_COLOR_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_RGBW_ADDRESS,
default=defaults.get(CONF_LIGHT_RGBW_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_RGBW_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_RGBW_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_HUE_ADDRESS,
default=defaults.get(CONF_LIGHT_HUE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_HUE_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_HUE_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_SATURATION_ADDRESS,
default=defaults.get(CONF_LIGHT_SATURATION_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_SATURATION_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_SATURATION_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_SATURATION_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_SATURATION_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_XYY_ADDRESS,
default=defaults.get(CONF_LIGHT_XYY_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_XYY_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_XYY_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_COLOR_TEMPERATURE_ADDRESS,
default=defaults.get(CONF_LIGHT_COLOR_TEMPERATURE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS),
default=bool(
defaults.get(
_invert_out_key(CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS)
)
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_COLOR_TEMPERATURE_MODE,
default=defaults.get(CONF_LIGHT_COLOR_TEMPERATURE_MODE, "absolute"),
): selector.SelectSelector(
selector.SelectSelectorConfig(
options=color_temperature_options, mode="dropdown"
)
),
vol.Optional(
CONF_LIGHT_MIN_KELVIN,
default=defaults.get(CONF_LIGHT_MIN_KELVIN, 2000),
): selector.NumberSelector(
selector.NumberSelectorConfig(min=1000, max=20000, step=1, mode="box")
),
vol.Optional(
CONF_LIGHT_MAX_KELVIN,
default=defaults.get(CONF_LIGHT_MAX_KELVIN, 6500),
): selector.NumberSelector(
selector.NumberSelectorConfig(min=1000, max=20000, step=1, mode="box")
),
vol.Optional(
CONF_LIGHT_RED_ADDRESS,
default=defaults.get(CONF_LIGHT_RED_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_RED_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_RED_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_RED_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_RED_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_RED_BRIGHTNESS_ADDRESS,
default=defaults.get(CONF_LIGHT_RED_BRIGHTNESS_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS),
default=bool(
defaults.get(
_invert_out_key(CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS)
)
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_GREEN_ADDRESS,
default=defaults.get(CONF_LIGHT_GREEN_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_GREEN_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_GREEN_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_GREEN_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_GREEN_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_GREEN_BRIGHTNESS_ADDRESS,
default=defaults.get(CONF_LIGHT_GREEN_BRIGHTNESS_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS),
default=bool(
defaults.get(
_invert_out_key(CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS)
)
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_BLUE_ADDRESS,
default=defaults.get(CONF_LIGHT_BLUE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_BLUE_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_BLUE_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_BLUE_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_BLUE_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_BLUE_BRIGHTNESS_ADDRESS,
default=defaults.get(CONF_LIGHT_BLUE_BRIGHTNESS_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS),
default=bool(
defaults.get(
_invert_out_key(CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS)
)
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_WHITE_ADDRESS,
default=defaults.get(CONF_LIGHT_WHITE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_WHITE_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_WHITE_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_WHITE_STATE_ADDRESS),
default=bool(
defaults.get(_invert_out_key(CONF_LIGHT_WHITE_STATE_ADDRESS))
),
): selector.BooleanSelector(),
vol.Optional(
CONF_LIGHT_WHITE_BRIGHTNESS_ADDRESS,
default=defaults.get(CONF_LIGHT_WHITE_BRIGHTNESS_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS,
default=defaults.get(CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS, ""),
): selector.TextSelector(
selector.TextSelectorConfig(type="text")
),
vol.Optional(
_invert_out_key(CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS),
default=bool(
defaults.get(
_invert_out_key(CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS)
)
),
): selector.BooleanSelector(),
vol.Optional(CONF_ENABLED, default=bool(defaults.get(CONF_ENABLED, True))): (
selector.BooleanSelector()
),
}
)
def _port_keys(port_type: str) -> list[str]:
if port_type == "binary_sensor":
return [CONF_STATE_ADDRESS]
@@ -532,6 +926,41 @@ def _port_keys(port_type: str) -> list[str]:
CONF_ANGLE_ADDRESS,
CONF_ANGLE_STATE_ADDRESS,
]
if port_type == "light":
return [
CONF_LIGHT_ADDRESS,
CONF_LIGHT_STATE_ADDRESS,
CONF_LIGHT_BRIGHTNESS_ADDRESS,
CONF_LIGHT_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_COLOR_ADDRESS,
CONF_LIGHT_COLOR_STATE_ADDRESS,
CONF_LIGHT_RGBW_ADDRESS,
CONF_LIGHT_RGBW_STATE_ADDRESS,
CONF_LIGHT_HUE_ADDRESS,
CONF_LIGHT_HUE_STATE_ADDRESS,
CONF_LIGHT_SATURATION_ADDRESS,
CONF_LIGHT_SATURATION_STATE_ADDRESS,
CONF_LIGHT_XYY_ADDRESS,
CONF_LIGHT_XYY_STATE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_ADDRESS,
CONF_LIGHT_COLOR_TEMPERATURE_STATE_ADDRESS,
CONF_LIGHT_RED_ADDRESS,
CONF_LIGHT_RED_STATE_ADDRESS,
CONF_LIGHT_RED_BRIGHTNESS_ADDRESS,
CONF_LIGHT_RED_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_GREEN_ADDRESS,
CONF_LIGHT_GREEN_STATE_ADDRESS,
CONF_LIGHT_GREEN_BRIGHTNESS_ADDRESS,
CONF_LIGHT_GREEN_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_BLUE_ADDRESS,
CONF_LIGHT_BLUE_STATE_ADDRESS,
CONF_LIGHT_BLUE_BRIGHTNESS_ADDRESS,
CONF_LIGHT_BLUE_BRIGHTNESS_STATE_ADDRESS,
CONF_LIGHT_WHITE_ADDRESS,
CONF_LIGHT_WHITE_STATE_ADDRESS,
CONF_LIGHT_WHITE_BRIGHTNESS_ADDRESS,
CONF_LIGHT_WHITE_BRIGHTNESS_STATE_ADDRESS,
]
return []