From d0091f1f2b68dcafac0a2753fbe3cfd8f5a74b6b Mon Sep 17 00:00:00 2001 From: Rolf Brocke Date: Wed, 9 Sep 2026 01:38:48 +0200 Subject: [PATCH] feat: create sections, sort options --- .../adaptive_lighting/config_flow.py | 57 +++++++++---- custom_components/adaptive_lighting/const.py | 84 ++++++++++++++----- .../adaptive_lighting/translations/de.json | 80 ++++++++++++------ 3 files changed, 163 insertions(+), 58 deletions(-) diff --git a/custom_components/adaptive_lighting/config_flow.py b/custom_components/adaptive_lighting/config_flow.py index 3ffe5400..4771cb5b 100644 --- a/custom_components/adaptive_lighting/config_flow.py +++ b/custom_components/adaptive_lighting/config_flow.py @@ -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", diff --git a/custom_components/adaptive_lighting/const.py b/custom_components/adaptive_lighting/const.py index afead25c..ec887bd0 100644 --- a/custom_components/adaptive_lighting/const.py +++ b/custom_components/adaptive_lighting/const.py @@ -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, diff --git a/custom_components/adaptive_lighting/translations/de.json b/custom_components/adaptive_lighting/translations/de.json index 4885e753..d65edca7 100644 --- a/custom_components/adaptive_lighting/translations/de.json +++ b/custom_components/adaptive_lighting/translations/de.json @@ -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." } } -} +} \ No newline at end of file