mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-27 04:14:47 +02:00
- openspec/ — three changes scoped:
• cdit-config-redesign: full artifact set (proposal + design + specs +
tasks), strict-validate green. Prunes 21 fields from the upstream
options flow, switches sun timing to entity-driven sources, hardcodes
a tanh curve, and breaks compat with upstream config entries.
• add-runtime-range-controls: proposal stub for promoting the 4
brightness/color-temp ranges to live number entities.
• house-mode-modes: proposal stub for per-AL house-mode behavior matrix
driving the runtime switches.
- .claude/ — opsx slash commands and openspec skill bundles for driving
the artifact-driven workflow.
- CLAUDE.md — fork orientation, remote topology, dev commands, and the
active change pointer.
10 KiB
10 KiB
1. Foundation — const.py and manifest.json
- 1.1 Remove the 21 retired
CONF_*/DEFAULT_*constants fromconst.py:sleep_brightness,sleep_rgb_or_color_temp,sleep_color_temp,sleep_rgb_color,sleep_transition,adapt_until_sleep,sunrise_time,min_sunrise_time,max_sunrise_time,sunrise_offset,sunset_time,min_sunset_time,max_sunset_time,sunset_offset,brightness_mode,brightness_mode_time_dark,brightness_mode_time_light,take_over_control,take_over_control_mode,detect_non_ha_changes,autoreset_control,only_once,adapt_only_on_bare_turn_on. [R8] - 1.2 Trim
VALIDATION_TUPLESto the 16 retained upstream entries plus 2 new sun-entity entries (target: 18 total). Also pruneEXTRA_VALIDATIONof orphaned keys. [R8] - 1.3 Add
CONF_SUNRISE_ENTITY = "sunrise_entity"/DEFAULT_SUNRISE_ENTITY = "sensor.sun_next_rising"and the_SUNSET_pair. [R3] - 1.4 Add
RAMP_HALF_WIDTH_SECONDS = 1800with an inline comment naming the design choice (30-min eye-friendly transition). [R4, D15] - 1.5 Bump
manifest.jsontoversion: "2.0.0-cdit.1", add"homeassistant": "2025.1.0", updatecodeownersto CDiT-dev maintainers, pointdocumentation/issue_trackerat the fork URL. [R8, D4, D13]
2. Foundation — strip dead code from switch.py
- 2.1 Delete the sleep-mode switch entity class and its
unique_idpattern fromswitch.py. The integration now creates 3 switches per AL config (master, adapt_brightness, adapt_color), not 4. [D7] - 2.2 Remove sleep-mode state machine from the master
AdaptiveSwitchclass (sleep transition logic, sleep brightness override,adapt_until_sleephandling). [D7] - 2.3 Remove the take-over-control state machine:
_manual_controltracking,_autoreset_handle,detect_non_ha_changespolling,only_onceshort-circuits,adapt_only_on_bare_turn_onchecks. [D8] - 2.4 Audit remaining
switch.pyfor references to removedCONF_*keys and delete dead branches. Lint must pass. [D7, D8]
3. Config flow — sectioned schema
- 3.1 Replace the flat
VALIDATION_TUPLESloop inconfig_flow.pywith a hand-shaped builder that returns avol.Schemacontaining sixsection()-wrapped subschemas: Targets, Daytime curve, Sun schedule, Light control, Advanced (collapsed), Diagnostics (collapsed). [R1, D1] - 3.2 Wire each field's selector per the table in R5:
NumberSelector(slider mode for brightness 1–100 / step 1 / %; box-or-slider for color temp 1000–10000 / step 100 / K),BooleanSelector,EntitySelector(domain="light", multiple=True)forlights,EntitySelector(domain="sensor", device_class="timestamp")for the two sun entities. [R5, D5, D14] - 3.3 Implement conditional visibility for
send_split_delay(driver:separate_turn_on_commands). The schema builder reads the current options/draft state and omitssend_split_delaywhen the driver is false. [R2, D9] - 3.4 Move
include_config_in_attributesinto the Diagnostics collapsed subsection. [R1] - 3.5 Verify that no field appears in more than one section (spec R1 scenario 2). [R1]
4. Config flow — reload-on-save and YAML-managed abort
- 4.1 Change
OptionsFlow→OptionsFlowWithReloadinconfig_flow.py. Drop any customasync_reload/async_unload_entryplumbing that exists today for reload purposes. [R6, D6] - 4.2 In the options flow's
async_step_init, detectconfig_entry.source == SOURCE_IMPORTand returnself.async_abort(reason="yaml_managed"). [R7] - 4.3 Confirm via manual test that toggling a field and saving reloads the integration with no "restart HA" prompt, and that entity IDs of the AL switches are preserved across the reload. [R6]
5. Curve math — entity reads + synthetic tanh
- 5.1 Add a
today_sun_events(hass, entry)helper in__init__.py(or a newsun.pymodule) that readsentry.options[CONF_SUNRISE_ENTITY]and_SUNSET_ENTITY, fetches their state viahass.states.get(...), parses the timestamp, and handles the "next_rising flipped to tomorrow after sunrise" case by anchoring today's curve from whichever event is in the past. Returns(today_sunrise_dt, today_sunset_dt). [R3, D2] - 5.2 Add a pure
tanh_curve(now, t_start, t_end, value_min, value_max, half_width=RAMP_HALF_WIDTH_SECONDS)function. Returnsvalue_minoutside the active window, ramps viatanhbetween (t_start - half_width,t_start + half_width), holds atvalue_maxbetween (t_start + half_width,t_end - half_width), ramps back viatanhbetween (t_end - half_width,t_end + half_width). [R4, D11] - 5.3 Replace upstream's
astral-driven brightness and color-temp computation with two calls totanh_curve— one for brightness usingmin_brightness/max_brightness, one for color temp usingmin_color_temp/max_color_temp. Same(t_sunrise, t_sunset)inputs for both. [R4] - 5.4 Verify the curve evaluation path no longer imports
astral.sun. (astralmay remain a transitive dep for now; pruning it is a follow-up.) [D2]
6. async_setup_entry guards
- 6.1 At the top of
async_setup_entryin__init__.py, checkconfig_entry.versionagainst the current major (2). If older, raiseConfigEntryErrorwith a user-facing message: "Adaptive Lighting v2 (CDiT fork) is incompatible with the existing config entry. Delete and recreate the entry from Settings → Devices & Services." [R8, D4] - 6.2 Add a sleep-switch tombstone helper: scan
entity_registryfor entities whoseunique_idmatches the historical<entry.entry_id>_sleep_mode_*pattern, callentity_registry.async_remove(entity_id)on each match, logINFOper removal with the entity ID. [R9, D12] - 6.3 Ensure the tombstone helper is idempotent — a second
async_setup_entrycall finds nothing and emits no log lines. [R9] - 6.4 Confirm the helper only removes entities whose
config_entry_idmatches the current entry (does not touch foreign entities matching the name pattern). [R9, D12]
7. Tests
- 7.1 Delete obsolete test files:
tests/test_*sleep*,tests/test_*take_over*,tests/test_*manual_control*. Updatetests/conftest.pyto drop fixtures that referenced those features. [D7, D8] - 7.2 Add test: section layout — opening options on a UI-managed entry returns a flow result with six labeled sections in the specified order; Advanced and Diagnostics collapsed. [R1]
- 7.3 Add test: each field appears in exactly one section, matching the R1 table. [R1]
- 7.4 Add test:
send_split_delayis absent from the schema whenseparate_turn_on_commandsis false; present when true. [R2] - 7.5 Add test:
sunrise_entity/sunset_entitydefault tosensor.sun_next_rising/sensor.sun_next_settingon a freshly created entry. [R3] - 7.6 Add test: entity selectors for the two sun fields are configured with
domain="sensor"anddevice_class="timestamp". [R3, D14] - 7.7 Add test:
tanh_curvereturnsvalue_minmore thanhalf_widthbeforet_start, midpoint at exactlyt_start,value_maxmore thanhalf_widthaftert_start(and the symmetric trio aroundt_end). [R4] - 7.8 Add test: color temperature uses the same curve shape as brightness, with
min_color_temp/max_color_tempas bounds. [R4] - 7.9 Add test:
NumberSelectortypes, ranges, units, and modes match the R5 table for brightness, color temp, durations, and milliseconds. [R5] - 7.10 Add test: saving valid options invokes
async_unload_entryandasync_setup_entryexactly once each (use mock spies) and produces no "restart HA" prompt. [R6] - 7.11 Add test: opening the options flow on a
SOURCE_IMPORTconfig entry returnsasync_abort(reason="yaml_managed"). [R7] - 7.12 Add test:
async_setup_entryraisesConfigEntryErrorwhenconfig_entry.version == 1and current is 2. [R8] - 7.13 Add test:
async_setup_entrysucceeds and runs no tombstone log line when entry version is current. [R8] - 7.14 Add test: the tombstone helper removes a seeded
switch.adaptive_lighting_sleep_mode_<name>entity owned by this config entry, emits one INFO log line. [R9, D12] - 7.15 Add test: tombstone helper is idempotent — second run finds nothing, no log line. [R9]
- 7.16 Add test: tombstone helper does not remove a foreign-owned entity matching the name pattern (different
config_entry_id). [R9, D12]
8. Strings and translations
- 8.1 In
strings.json, add section labels (section.targets.name,section.daytime_curve.name, etc.) and short descriptions per section. Add field labels forsunrise_entity/sunset_entity. [R1, R3] - 8.2 In
strings.json, add theyaml_managedabort reason text: "This Adaptive Lighting config entry is managed fromconfiguration.yaml. Edit it there to change options." [R7] - 8.3 In
strings.json, add the version-incompatible error: "This entry was created with an older, incompatible version. Delete it and create a new one." [R8] - 8.4 Remove
strings.jsonkeys for the 21 deleted fields and the sleep switch entity. [R1, D7] - 8.5 Mirror the additions/removals in
translations/en.json. Other locales (de, fr, etc.) are out of scope and may diverge until a follow-up change. [R1]
9. Docs — README and CHANGELOG
- 9.1 Replace the upstream README's "Installation" / "Configuration" sections (or add a CDiT-specific preamble at the top) explicitly stating: "This is a CDiT fork. Existing upstream config entries WILL NOT load; recreate them after upgrade." Reference the migration steps from design.md §Migration Plan. [D4]
- 9.2 Add a "Recommended companions" section to README mentioning Sun2 as a HACS-installable source for precise civil / nautical / astronomical twilight sensors. Show the example of pointing
sunrise_entityatsensor.sun2_astro_dawn. [D2, D14] - 9.3 Write
CHANGELOG.md(or append to existing) entry for2.0.0-cdit.1: the 21 removed fields by name, the 2 added fields, the 1 removed entity, the breaking config-entry behavior, and the explicit recreate workflow. [D4] - 9.4 Update the fork's GitHub repo description and topics to mark it as opinionated/fork (not a drop-in replacement). [D4]