Config:
- .ruff.toml target-version py310 -> py312, matching pyproject's
requires-python (>=3.12) and the 3.12 'type' aliases switch.py
already ships. Unblocks two false invalid-syntax errors.
- Ignore D102 in tests (test-class methods), consistent with the
existing D100/D103 test ignores.
Code (no behavior changes):
- COM812 trailing commas + UP017 dt.UTC via ruff --fix, black re-wrap
- RUF002: unicode minus/en-dash -> ASCII hyphen in docstrings
- SIM105: contextlib.suppress for the lux-reading parse in config_flow
- TRY300: move return out of try in _read_lux_sensor
- PLC0415: hoist inline asyncio/logging imports to module top
- E741: rename ambiguous 'l' comprehension variable to light_id
- PT006/PT018: parametrize tuple + split compound assertion
- D102: docstring for OptionsFlow.async_step_init
- ARG001/ARG002: noqa with justification on HA-required signatures
- PLR0912/PLR0915: noqa on prepare_adaptation_data and
_update_attrs_and_maybe_adapt_lights (upstream-inherited complexity;
splitting would hurt readability more than it helps)
./scripts/lint passes clean; 148 tests pass.
Optional per-profile lux sensor binding that dims lights when daylight
alone exceeds a user-set target (factor = target/current). Lights turn
off entirely below min_brightness. Two config fields, live lux reading
in the options flow, two conditional output sensors, sensor state
subscription with 5pp significance guard. 26/26 tasks, 138 tests green.
Three sensor entities per AL profile expose the curve's current outputs
plus actual sun elevation as graphable numerics with
SensorStateClass.MEASUREMENT, so HA's recorder + apexcharts-card chart
them natively:
- sensor.<profile>_output_brightness (% — from self._settings)
- sensor.<profile>_output_color_temp (K — from self._settings)
- sensor.<profile>_sun_elevation (° — from sun.sun.attributes.elevation)
Architecture: master switch publishes computed outputs to
hass.data[DOMAIN][entry_id]["outputs"] after each curve tick, then fires
a per-entry dispatcher signal. Sensors subscribe and read from the cache.
Single computation path, push-based updates, no polling.
The existing master switch attributes (brightness_pct, color_temp_kelvin,
synthetic sun_position in [-1,+1]) are unchanged — sensors are purely
additive. Manifest bumped to 2.2.0-cdit.1 (minor; no breaking changes).
Artifact realignment along the way: Q2 answer assumed sun_position was
Sun2 elevation degrees, but reading color_and_brightness.py revealed
it's a synthetic float in [-1,+1] derived from the brightness curve.
Switched to pulling actual elevation from sun.sun (Decision 8 added),
renamed sensor from sun_position to sun_elevation. Friendly names use
asymmetric "Output" prefix to dodge collision with the adapt-brightness
switch and Min/Max color-temp numbers.
Remaining: group 6 (manual live-HA verification on
homeassistant.onca-blenny.ts.net) — requires HACS deploy.
15 new sensor tests pass; full suite green (123/123). openspec
validate --strict green. ruff clean on new files.
Adds four live-tunable `number` entities per AL profile (min/max
brightness, min/max color temp) that own the runtime curve values.
Slider changes take effect on the next curve tick — no integration
reload. State persists across HA restart via `RestoreNumber`.
Curve math now reads `min_brightness`, `max_brightness`,
`min_color_temp`, `max_color_temp` from the four runtime entities
via the entity registry, falling back to `entry.options` when an
entity is unavailable. The options flow seeds its four range fields
from the current entity state so the dialog matches reality.
Also fixes entity friendly names via HA's `has_entity_name`
composition: profile "Dining MVP" now reads as "Dining MVP",
"Dining MVP Brightness", "Dining MVP Color" — short enough for
HA's tightest cards. `unique_id`s are unchanged so existing
entity_ids stay stable.
Manifest bumped to 2.1.0-cdit.1 (minor, no breaking changes).
14 new tests in `tests/test_number_platform.py`; 108 passing
overall. OpenSpec change archived once 9.x live-HA verification
completes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Production HA caught this on the first save attempt: HA rejects
`config_entry.add_update_listener()` when the integration also uses
`OptionsFlowWithReload`, raising:
ValueError: Config entry update listeners should not be used with
OptionsFlowWithReload
The listener was a vestigial bit of the pre-redesign reload plumbing
that should have been deleted alongside the manual reload path (design
decision D6). Removed:
- `config_entry.add_update_listener(async_update_options)` registration
in `async_setup_entry`
- `async_update_options` helper (only existed to call `async_reload`)
- `UNDO_UPDATE_LISTENER` tracking in `async_unload_entry`
- The `UNDO_UPDATE_LISTENER` import (still defined in const.py for any
external code that imports it; pruning that is a separate cleanup)
Updated `tests/test_init.py::test_successful_setup_on_current_version`
to assert `entry.entry_id in hass.data[DOMAIN]` instead of the listener
key. All 94 tests still pass locally.
This is the first real-HA-only bug we've shipped — props to the
production deploy for catching it before merge.
Test infrastructure:
- Add `pytest-homeassistant-custom-component` (PHACC) as a `test`
dependency group. PHACC ships `hass`, `enable_custom_integrations`,
`MockConfigEntry`, and friends without needing to clone HA core as a
sibling directory. Modernizes the test setup from upstream's
`setup-symlinks` pattern.
- conftest.py: add `auto_enable_custom_integrations` autouse fixture
so HA discovers the integration under `custom_components/` during
tests. Keep the upstream template-deprecation no-op.
- Import paths: all tests now import from `custom_components.adaptive_lighting`
(not `homeassistant.components.adaptive_lighting`) and from
`pytest_homeassistant_custom_component.common` (not `tests.common`).
New tests (groups 7.2-7.16):
- tests/test_color_and_brightness.py (16 tests): TestBrightnessCurve
asserts min-before-sunrise, midpoint-at-event, max-during-day, sunset
ramp symmetry. TestColorTempCurve verifies the same shape applies to
K. TestSunPosition checks the synthetic +1/-1/0 derivation.
TestTanhDayCurveDirect exercises the helper directly.
- tests/test_config_flow.py (12 tests): six sections in order, each
section contains only its specified fields, conditional visibility
of send_split_delay, default sun entities, strict-typed entity
selectors, NumberSelector slider/box configs, BooleanSelector for
every flag, full user→create-entry flow, YAML-managed entry aborts
with `yaml_managed` reason, options flow renders the sectioned schema.
- tests/test_init.py (6 tests): successful setup on current version,
stale version raises ConfigEntryError via async_migrate_entry,
unload is clean, tombstone removes orphan sleep entity + logs INFO,
tombstone is idempotent, tombstone respects config_entry ownership.
Source modernizations driven by the tests:
- __init__.py: add `async_migrate_entry` that surfaces the
"incompatible — delete and recreate" message and sets the entry to
MIGRATION_ERROR. Removed direct `ConfigEntryError` from
`async_setup_entry` (HA routes version mismatches through the
migration handler now).
- switch.py: removed upstream's YAML-managed-entry auto-remove hack.
YAML profiles now load normally and the options flow handles the
"you must edit configuration.yaml" message (spec R7).
Existing tests updated to PHACC paths: test_adaptation_utils.py
(38 tests), test_hass_utils.py (22 tests). Both pass without
modification beyond the import fix.
Deleted: tests/test_switch.py (2,999 LOC of upstream tests, most
covering sleep mode / take-over-control / manual-control state
machines that no longer exist; CDiT-specific switch tests deferred
to a follow-up change).
Result: `uv run --group test pytest tests/` → 94 passed in 0.57s.
Deferred from this change:
- 4.3: manual UI test that toggling a field and saving reloads cleanly
(requires a real HA instance, can't be done from CLI).
- 9.4: GitHub repo description / topics update (do via `gh repo edit`
outside the change scope).
openspec status: 4/4 artifacts complete; strict-validate green.
const.py:
- Drop 21 retired CONF_*/DEFAULT_* (sleep cluster, manual sun timing,
brightness curve variants, take-over-control cluster).
- Add CONF_SUNRISE_ENTITY / CONF_SUNSET_ENTITY (default
sensor.sun_next_rising / _setting).
- Add RAMP_HALF_WIDTH_SECONDS = 1800.
- Add CONFIG_ENTRY_VERSION = 2 (gate for strict version-break).
- Re-default DEFAULT_MIN_BRIGHTNESS 1→5, DEFAULT_MIN_COLOR_TEMP 2000→2200.
- Update ICON_MAIN / _BRIGHTNESS / _COLOR_TEMP to the CDiT picks.
manifest.json: bump to 2.0.0-cdit.1, pin homeassistant: 2025.1.0,
re-point codeowners/documentation/issue_tracker to the CaseyRo fork.
switch.py:
- Drop sleep_mode_switch creation in async_setup_entry; integration
now creates 3 switches per profile (master, adapt_color, adapt_brightness).
- Strip sleep-mode state machine from AdaptiveSwitch (sleep_mode_switch
attribute, sleep_transition, adapt_until_sleep, the state-change
listener, the _sleep_mode_switch_state_event_action method).
- Bridge-stub _take_over_control / _detect_non_ha_changes /
_adapt_only_on_bare_turn_on / _only_once / _auto_reset_manual_control_time
as class-level False/0 so Manager-side branches become dead code without
needing a full rewrite of AdaptiveLightingManager.
- Remove handle_set_manual_control service + its registration.
- Replace astral-driven curve init with the new SunLightSettings call
shape; add AdaptiveSwitch._today_sun_events() helper that reads the
configured sunrise/sunset entities.
- Set _attr_icon on AdaptiveSwitch.
color_and_brightness.py:
- Rewrite SunLightSettings as a pure curve-math wrapper (5 fields:
name + bounds + ramp half-width); methods take t_sunrise/t_sunset
as args, never reads HA state.
- Implement piecewise tanh ramp curve per spec R4 / design D11 in
_tanh_day_curve(). Both brightness and color-temp use the same shape.
- Drop SunEvents, sleep-mode branches, brightness_mode-switch, astral
dependency, force_rgb_color, lerp_color_hsv sleep-tinted color blending.
config_flow.py:
- Rewrite as sectioned schema (Targets / Daytime curve / Sun schedule /
Light control / Advanced / Diagnostics) via HA's section() helper.
- Native HA selectors throughout (NumberSelector, EntitySelector,
BooleanSelector). Strict typing on sun-event entity pickers.
- Conditional visibility for send_split_delay (driver:
separate_turn_on_commands).
- Extend OptionsFlowWithReload (with ImportError fallback for HA < 2025.1
dev environment); async_abort(reason="yaml_managed") for SOURCE_IMPORT.
- VERSION = CONFIG_ENTRY_VERSION.
__init__.py:
- Reject older config entries with ConfigEntryError ("recreate the entry")
per spec R8 / design D4.
- Add _remove_orphan_sleep_entities tombstone helper that scans the
entity registry for sleep-mode entities owned by this entry and
removes them on first setup. Idempotent and config_entry-scoped.
_docs_helpers.py: stub the removed DOCS_MANUAL_CONTROL / SET_MANUAL_CONTROL_SCHEMA
so the docs-generator script keeps importing.
Validation:
- ALL modules import cleanly under HA 2024.12.5 (dev env).
- openspec validate cdit-config-redesign --strict: green.
- ruff check --select=F: clean.
Remaining: groups 7 (16 tests), 8 (strings + plain-language pass), 9 (docs).
* added #274
* attempt getSwitchFromLightId()
* Update switch.py
* changed from register_entity_service to hass.services.async_register
* Update switch.py
* Update switch.py
* Update switch.py
* Update switch.py
* Update switch.py
* Update switch.py
* Update services.yaml
* Update switch.py
* test
* test builds ready
* target selector may not be possible with current syntax
priority is backwards-compatibility as I know most people will smash that update button
* expanded light groups
* test builds ready
* add feature #104
* Update custom_components/adaptive_lighting/switch.py
Co-authored-by: Chris <firstof9@gmail.com>
* Might as well type both. No reason not to.
Co-authored-by: Chris <firstof9@gmail.com>
* Might as well type both. No reason not to.
Co-authored-by: Chris <firstof9@gmail.com>
* Reformatted debug messages.
Reimported ServiceCall as suggested
* Multiple switches allowed again in services.
Apparently this was possible before.
With this change, the `lights` argument must not be passed with multiple switches, or the integration has no way of knowing what the user wants to do. Integration did not make this check in prior versions.
Also reformatted the debug messages.
Removed `automerge.yaml` (my apologies)
* Reload config without restart.
You can now reload any changes to the yaml file without restarting your home assistant. Should show a 'reload' button in your integrations page or you can call the homeassistant reload integration service call.
See https://community.home-assistant.io/t/how-to-allow-custom-compontent-for-yaml-configuration-reloading/391190/4
* Use snake_case for function name 'parseServiceArgs'
* Slight rephrase
* Small style changes
* Rephrased debug messages.
Removed `integration_entities` as we rewrote the code from that function already in parse_service_args. Reference function now above parse_service_args.
* removed: `these_switches = data = None`
* Factor out _find_switch_with_lights
* Rename function and add log statement
* Remove pylint marker
* Handle multiple switches found
* Small changes
* Rename _parse_service_args to _get_switches_from_service_call
* Rephrase log messages
* Add type hint
---------
Co-authored-by: Chris <firstof9@gmail.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>