feat: create sections, sort options

This commit is contained in:
Rolf Brocke 2026-09-09 01:38:48 +02:00
commit d0091f1f2b
3 changed files with 163 additions and 58 deletions

View file

@ -10,7 +10,11 @@ from homeassistant.core import callback
from homeassistant.helpers.selector import EntitySelector, EntitySelectorConfig
from .const import ( # pylint: disable=unused-import
BASIC_OPTIONS,
SECTION_SLEEP,
SECTION_SUNRISE,
SECTION_SUNSET,
SECTION_TRANSITION,
SECTION_MISC,
CONF_LIGHTS,
DOMAIN,
EXTRA_VALIDATION,
@ -25,7 +29,6 @@ OPTIONS_FLOW_DESCRIPTION_PLACEHOLDERS = {
"webapp_url": "https://basnijholt.github.io/adaptive-lighting",
"docs_url": "https://github.com/basnijholt/adaptive-lighting#readme",
}
ADVANCED_OPTIONS_SECTION = "advanced"
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
@ -129,11 +132,24 @@ def validate_options(user_input: dict[str, Any], errors: dict[str, str]) -> None
class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle a option flow for Adaptive Lighting."""
SECTION_MAP: dict[str, set[str]] = {
"sleep": SECTION_SLEEP,
"sunrise": SECTION_SUNRISE,
"sunset": SECTION_SUNSET,
"transition": SECTION_TRANSITION,
"misc": SECTION_MISC,
}
# Reverse lookup, avoids looping through sections for each(!) option
OPTION_TO_SECTION: dict[str, str] = {
name: section for section, names in SECTION_MAP.items() for name in names
}
def _flatten_section_input(self, user_input: dict[str, Any]) -> dict[str, Any]:
"""Flatten section input by merging nested 'advanced' dict into top level."""
"""Flatten section input by merging nested section dicts into top level."""
flat_input: dict[str, Any] = {}
for key, value in user_input.items():
if key == ADVANCED_OPTIONS_SECTION and isinstance(value, dict):
if key in self.SECTION_MAP and isinstance(value, dict):
flat_input.update(value)
else:
flat_input[key] = value
@ -179,20 +195,31 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
),
}
basic_schema: dict[vol.Marker, Any] = {}
advanced_schema: dict[vol.Marker, Any] = {}
full_schema: dict[vol.Marker, Any] = {}
section_schemas: dict[str, dict[vol.Marker, Any]] = {
name: {} for name in self.SECTION_MAP
}
for name, default, validation in VALIDATION_TUPLES:
key = vol.Optional(name, default=form_data.get(name, default))
schema = basic_schema if name in BASIC_OPTIONS else advanced_schema
schema[key] = to_replace.get(name, validation)
field_schema = to_replace.get(name, validation)
full_schema = {
**basic_schema,
vol.Required(ADVANCED_OPTIONS_SECTION): data_entry_flow.section(
vol.Schema(advanced_schema),
data_entry_flow.SectionConfig(collapsed=True),
),
}
section_name = self.OPTION_TO_SECTION.get(name)
if section_name:
# Option is part of section, put into section schema
section_schemas[section_name][key] = field_schema
else:
# Option is NOT part of section, place at top level
full_schema[key] = field_schema
# Create collapsible sections for each section set
for section_name in self.SECTION_MAP:
if section_schemas[section_name]:
full_schema[vol.Required(section_name)] = data_entry_flow.section(
vol.Schema(section_schemas[section_name]),
data_entry_flow.SectionConfig(collapsed=True),
)
return self.async_show_form(
step_id="init",

View file

@ -341,36 +341,77 @@ DOCS_APPLY = {
CONF_LIGHTS: "A light (or list of lights) to apply the settings to. 💡",
}
# Basic options shown at top level in options flow (not in collapsed section)
BASIC_OPTIONS: set[str] = {
CONF_LIGHTS,
CONF_MIN_BRIGHTNESS,
CONF_MAX_BRIGHTNESS,
CONF_MIN_COLOR_TEMP,
CONF_MAX_COLOR_TEMP,
# Items not put in a section will be put at top level and be immediately visible.
# Item order is determined by VALIDATION_TUPLES.
SECTION_SLEEP: set[str] = {
CONF_SLEEP_BRIGHTNESS,
CONF_SLEEP_COLOR_TEMP,
CONF_TRANSITION,
CONF_INTERVAL,
CONF_ADAPT_UNTIL_SLEEP,
CONF_SLEEP_TRANSITION,
CONF_SLEEP_RGB_OR_COLOR_TEMP,
CONF_SLEEP_RGB_COLOR,
}
SECTION_SUNRISE: set[str] = {
CONF_SUNRISE_TIME,
CONF_MIN_SUNRISE_TIME,
CONF_MAX_SUNRISE_TIME,
CONF_SUNRISE_OFFSET,
}
SECTION_SUNSET: set[str] = {
CONF_SUNSET_TIME,
CONF_MIN_SUNSET_TIME,
CONF_MAX_SUNSET_TIME,
CONF_SUNSET_OFFSET,
}
SECTION_TRANSITION: set[str] = {
CONF_INITIAL_TRANSITION,
CONF_TRANSITION,
CONF_BRIGHTNESS_MODE,
CONF_BRIGHTNESS_MODE_TIME_DARK,
CONF_BRIGHTNESS_MODE_TIME_LIGHT,
}
SECTION_MISC: set[str] = {
CONF_PREFER_RGB_COLOR,
CONF_INTERVAL,
CONF_TAKE_OVER_CONTROL,
CONF_TAKE_OVER_CONTROL_MODE,
CONF_DETECT_NON_HA_CHANGES,
CONF_AUTORESET_CONTROL,
CONF_ONLY_ONCE,
CONF_ADAPT_ONLY_ON_BARE_TURN_ON,
CONF_MANUAL_CONTROL_ON_EXTERNAL_TURN_ON,
CONF_RESET_MANUAL_CONTROL_ON_SLEEP_MODE_CHANGE,
CONF_SEPARATE_TURN_ON_COMMANDS,
CONF_SEND_SPLIT_DELAY,
CONF_ADAPT_DELAY,
CONF_SKIP_REDUNDANT_COMMANDS,
CONF_INTERCEPT,
CONF_MULTI_LIGHT_INTERCEPT,
CONF_INCLUDE_CONFIG_IN_ATTRIBUTES,
CONF_EXPAND_LIGHT_GROUPS,
}
def int_between(min_int: int, max_int: int) -> vol.All:
"""Return an integer between 'min_int' and 'max_int'."""
return vol.All(vol.Coerce(int), vol.Range(min=min_int, max=max_int))
VALIDATION_TUPLES: list[tuple[str, Any, Any]] = [
# Basic
(CONF_LIGHTS, DEFAULT_LIGHTS, cv.entity_ids), # type: ignore[arg-type]
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
(CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS, int_between(1, 100)),
(CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP, int_between(1000, 10000)),
(CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP, int_between(1000, 10000)),
(CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool),
(CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS, int_between(1, 100)),
(CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP, int_between(1000, 10000)),
# Sleep
(CONF_SLEEP_BRIGHTNESS, DEFAULT_SLEEP_BRIGHTNESS, int_between(1, 100)),
(CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP, int_between(1000, 10000)),
(CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP, bool),
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
(
CONF_SLEEP_RGB_OR_COLOR_TEMP,
DEFAULT_SLEEP_RGB_OR_COLOR_TEMP,
@ -382,22 +423,24 @@ VALIDATION_TUPLES: list[tuple[str, Any, Any]] = [
),
),
),
(CONF_SLEEP_COLOR_TEMP, DEFAULT_SLEEP_COLOR_TEMP, int_between(1000, 10000)),
(
CONF_SLEEP_RGB_COLOR,
DEFAULT_SLEEP_RGB_COLOR,
selector.ColorRGBSelector(selector.ColorRGBSelectorConfig()), # type: ignore[arg-type]
),
(CONF_SLEEP_TRANSITION, DEFAULT_SLEEP_TRANSITION, VALID_TRANSITION),
(CONF_ADAPT_UNTIL_SLEEP, DEFAULT_ADAPT_UNTIL_SLEEP, bool),
# Sunrise
(CONF_SUNRISE_TIME, NONE_STR, str),
(CONF_MIN_SUNRISE_TIME, NONE_STR, str),
(CONF_MAX_SUNRISE_TIME, NONE_STR, str),
(CONF_SUNRISE_OFFSET, DEFAULT_SUNRISE_OFFSET, int),
# Sunset
(CONF_SUNSET_TIME, NONE_STR, str),
(CONF_MIN_SUNSET_TIME, NONE_STR, str),
(CONF_MAX_SUNSET_TIME, NONE_STR, str),
(CONF_SUNSET_OFFSET, DEFAULT_SUNSET_OFFSET, int),
# Transition
(CONF_INITIAL_TRANSITION, DEFAULT_INITIAL_TRANSITION, VALID_TRANSITION),
(CONF_TRANSITION, DEFAULT_TRANSITION, VALID_TRANSITION),
(
CONF_BRIGHTNESS_MODE,
DEFAULT_BRIGHTNESS_MODE,
@ -411,6 +454,9 @@ VALIDATION_TUPLES: list[tuple[str, Any, Any]] = [
),
(CONF_BRIGHTNESS_MODE_TIME_DARK, DEFAULT_BRIGHTNESS_MODE_TIME_DARK, int),
(CONF_BRIGHTNESS_MODE_TIME_LIGHT, DEFAULT_BRIGHTNESS_MODE_TIME_LIGHT, int),
# Misc
(CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR, bool),
(CONF_INTERVAL, DEFAULT_INTERVAL, cv.positive_int),
(CONF_TAKE_OVER_CONTROL, DEFAULT_TAKE_OVER_CONTROL, bool),
(
CONF_TAKE_OVER_CONTROL_MODE,

View file

@ -41,23 +41,70 @@
"interval": "Häufigkeit der Lichtanpassung in Sekunden. 🔄",
"transition": "Dauer des Übergangs beim Lichtwechsel in Sekunden. 🕑",
"sleep_brightness": "Helligkeit der Lichter im Schlafmodus in Prozent. 😴",
"sleep_color_temp": "Farbtemperatur im Schlafmodus in Kelvin (wird verwendet, wenn `sleep_rgb_or_color_temp` `color_temp` ist) . 😴"
"sleep_color_temp": "Farbtemperatur im Schlafmodus in Kelvin (wird verwendet, wenn `sleep_rgb_or_color_temp` `color_temp` ist) . 😴",
"min_brightness": "min_brightness: Minimale Helligkeit in Prozent. 💡",
"max_brightness": "max_brightness: Maximale Helligkeit in Prozent. 💡",
"min_color_temp": "min_color_temp: Wärmste Farbtemperatur in Kelvin. 🔥",
"max_color_temp": "max_color_temp: Kälteste Farbtemperatur in Kelvin. ❄️"
},
"sections": {
"advanced": {
"sleep": {
"name": "Schlafmodus",
"data": {
"initial_transition": "initial_transition, wenn Lichter von 'off' zu 'on' wechseln oder wenn 'sleep_state' wechselt",
"prefer_rgb_color": "prefer_rgb_color: Ob die RGB-Farbanpassung der Farbtemperaturanpassung vorgezogen werden soll, wenn möglich. 🌈",
"sleep_rgb_or_color_temp": "sleep_rgb_or_color_temp, nutze 'rgb_color' oder 'color_temp'",
"sleep_rgb_color": "sleep_rgb_color, in RGB",
"sleep_transition": "sleep_transition: Wenn 'sleep_state' sich ändert. (Sekunden)",
"transition_until_sleep": "transition_until_sleep: Wenn diese Option aktiviert ist, behandelt die adaptive Beleuchtung die Schlafeinstellungen als Minimum und geht nach Sonnenuntergang zu diesen Werten über. 🌙",
"sleep_transition": "sleep_transition: Wenn 'sleep_state' sich ändert. (Sekunden)"
},
"data_description": {
"sleep_rgb_or_color_temp": "Verwende entweder `rgb_color` oder `color_temp` im Schlafmodus. 🌙",
"sleep_rgb_color": "RGB-Farbe im Schlafmodus (wird verwendet, wenn `sleep_rgb_or_color_temp` `rgb_color` ist). 🌈",
"sleep_transition": "Dauer des Übergangs, wenn der \"Schlafmodus\" umgeschaltet wird, in Sekunden. 😴"
}
},
"sunrise": {
"name": "Sonnenaufgang",
"data": {
"sunrise_time": "sunrise_time, Sonnenaufgangszeit in 'HH:MM:SS' Format (wenn 'None' wird die aktuelle Zeit des Sonnenaufgangs an deiner Position verwendet)",
"max_sunrise_time": "max_sunrise_time: Manuelles Überschreiben der max. sunrise_time. Falls 'None', wird die tatsächliche sunrise_time an deiner Position verwendet (HH:MM:SS)",
"sunrise_offset": "sunrise_offset, Sonnenaufgang Verschiebung in +/- Sekunden",
"sunrise_offset": "sunrise_offset, Sonnenaufgang Verschiebung in +/- Sekunden"
},
"data_description": {
"sunrise_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenaufgang ein. 🌅",
"min_sunrise_time": "Lege die früheste virtuelle Sonnenaufgangszeit (HH:MM:SS) fest, um einen späteren Sonnenaufgang zu ermöglichen. 🌅",
"max_sunrise_time": "Lege die späteste virtuelle Sonnenaufgangszeit (HH:MM:SS) fest, um einen früheren Sonnenaufgang zu ermöglichen. 🌅",
"sunrise_offset": "Anpassung der Sonnenaufgangszeit mit positivem oder negativem Versatz in Sekunden. ⏰"
}
},
"sunset": {
"name": "Sonnenuntergang",
"data": {
"sunset_time": "sunset_time, Sonnenuntergangszeit in 'HH:MM:SS' Format (wenn 'None' wird die aktuelle Zeit des Sonnenuntergangs an deiner Position verwendet)",
"min_sunset_time": "min_sunset_time: Manuelles Überschreiben der min. sunset_time. Falls 'None', wird die tatsächliche sunset_time an deiner Position verwendet (HH:MM:SS)",
"sunset_offset": "sunset_offset, Sonnenuntergang Verschiebung in +/- Sekunden",
"sunset_offset": "sunset_offset, Sonnenuntergang Verschiebung in +/- Sekunden"
},
"data_description": {
"sunset_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenuntergang ein. 🌇",
"min_sunset_time": "Lege die früheste virtuelle Sonnenuntergangszeit (HH:MM:SS) fest, um spätere Sonnenuntergänge zu ermöglichen. 🌇",
"max_sunset_time": "Lege die späteste virtuelle Sonnenuntergangszeit (HH:MM:SS) fest, um frühere Sonnenuntergänge zu ermöglichen. 🌇",
"sunset_offset": "Anpassung der Sonnenuntergangszeit mit positivem oder negativem Versatz in Sekunden. ⏰"
}
},
"transition": {
"name": "Übergang",
"data": {
"initial_transition": "initial_transition, wenn Lichter von 'off' zu 'on' wechseln oder wenn 'sleep_state' wechselt"
},
"data_description": {
"initial_transition": "Dauer des ersten Übergangs, wenn das Licht von `off` auf `on` schaltet, in Sekunden. ⏲️",
"brightness_mode": "Helligkeitsmodus, der verwendet werden soll. Mögliche Werte sind `default`, `linear` und `tanh` (verwendet `brightness_mode_time_dark` und `brightness_mode_time_light`). 📈",
"brightness_mode_time_dark": "(Wird ignoriert, wenn `brightness_mode='default'`) Die Dauer in Sekunden, um die Helligkeit vor/nach Sonnenaufgang/Sonnenuntergang hoch/runter zu fahren. 📈📉.",
"brightness_mode_time_light": "(Wird ignoriert, wenn `brightness_mode='default'`) Die Dauer in Sekunden, um die Helligkeit nach/vor Sonnenaufgang/Sonnenuntergang hoch/runter zu fahren. 📈📉."
}
},
"misc": {
"name": "Erweitert",
"data": {
"prefer_rgb_color": "prefer_rgb_color: Ob die RGB-Farbanpassung der Farbtemperaturanpassung vorgezogen werden soll, wenn möglich. 🌈",
"take_over_control": "take_over_control: Deaktiviere die adaptive Beleuchtung, wenn eine andere Quelle `light.turn_on` aufruft, während die Beleuchtung eingeschaltet ist und angepasst wird. Beachte, dass dies `homeassistant.update_entity` jedes `Intervall` aufruft! 🔒",
"detect_non_ha_changes": "detect_non_ha_changes: Erkennt und stoppt Anpassungen für nicht-`light.turn_on`-Zustandsänderungen. Benötigt, dass `take_over_control` aktiviert ist. 🕵️ Vorsicht: ⚠️ Einige Lichter können fälschlicherweise einen 'an'-Zustand anzeigen, was dazu führen kann, dass Lichter unerwartet eingeschaltet werden. Deaktiviere diese Funktion, wenn solche Probleme auftreten.",
"only_once": "only_once: Lichter nur einmalig anpassen, wenn sie eingeschaltet werden (`true`) oder sie immer wieder anpassen (`false`). 🔄",
@ -72,21 +119,6 @@
"expand_light_groups": "expand_light_groups: Lichtgruppen auf einzelne Mitgliedsentitäten erweitern (`true`) oder die Gruppenentität direkt steuern (`false`)."
},
"data_description": {
"initial_transition": "Dauer des ersten Übergangs, wenn das Licht von `off` auf `on` schaltet, in Sekunden. ⏲️",
"sleep_rgb_or_color_temp": "Verwende entweder `rgb_color` oder `color_temp` im Schlafmodus. 🌙",
"sleep_rgb_color": "RGB-Farbe im Schlafmodus (wird verwendet, wenn `sleep_rgb_or_color_temp` `rgb_color` ist). 🌈",
"sleep_transition": "Dauer des Übergangs, wenn der \"Schlafmodus\" umgeschaltet wird, in Sekunden. 😴",
"sunrise_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenaufgang ein. 🌅",
"min_sunrise_time": "Lege die früheste virtuelle Sonnenaufgangszeit (HH:MM:SS) fest, um einen späteren Sonnenaufgang zu ermöglichen. 🌅",
"max_sunrise_time": "Lege die späteste virtuelle Sonnenaufgangszeit (HH:MM:SS) fest, um einen früheren Sonnenaufgang zu ermöglichen. 🌅",
"sunrise_offset": "Anpassung der Sonnenaufgangszeit mit positivem oder negativem Versatz in Sekunden. ⏰",
"sunset_time": "Stelle eine feste Zeit (HH:MM:SS) für den Sonnenuntergang ein. 🌇",
"min_sunset_time": "Lege die früheste virtuelle Sonnenuntergangszeit (HH:MM:SS) fest, um spätere Sonnenuntergänge zu ermöglichen. 🌇",
"max_sunset_time": "Lege die späteste virtuelle Sonnenuntergangszeit (HH:MM:SS) fest, um frühere Sonnenuntergänge zu ermöglichen. 🌇",
"sunset_offset": "Anpassung der Sonnenuntergangszeit mit positivem oder negativem Versatz in Sekunden. ⏰",
"brightness_mode": "Helligkeitsmodus, der verwendet werden soll. Mögliche Werte sind `default`, `linear` und `tanh` (verwendet `brightness_mode_time_dark` und `brightness_mode_time_light`). 📈",
"brightness_mode_time_dark": "(Wird ignoriert, wenn `brightness_mode='default'`) Die Dauer in Sekunden, um die Helligkeit vor/nach Sonnenaufgang/Sonnenuntergang hoch/runter zu fahren. 📈📉.",
"brightness_mode_time_light": "(Wird ignoriert, wenn `brightness_mode='default'`) Die Dauer in Sekunden, um die Helligkeit nach/vor Sonnenaufgang/Sonnenuntergang hoch/runter zu fahren. 📈📉.",
"autoreset_control_seconds": "Setzt die manuelle Steuerung nach einer bestimmten Anzahl von Sekunden automatisch zurück. Zum Deaktivieren auf 0 setzen. ⏲️",
"send_split_delay": "Verzögerung (ms) zwischen `separate_turn_on_commands` für Leuchten, die keine gleichzeitige Einstellung von Helligkeit und Farbe unterstützen. ⏲️",
"adapt_delay": "Wartezeit (Sekunden) zwischen dem Einschalten des Lichts und der Anwendung der adaptiven Beleuchtung. Könnte helfen, Flackern zu vermeiden. ⏲️",
@ -235,4 +267,4 @@
"description": "Markiere, ob ein Licht \"manuell gesteuert\" ist."
}
}
}
}