* Fix TypeError when 'light.turn_off' is called with a string transition
`EVENT_CALL_SERVICE` carries the *raw* service data, not the data
`light.turn_off`'s schema produced for the service handler, so its
`vol.Coerce(float)` never reaches `AdaptiveLightingManager`. A caller
passing `transition: "2"` — a template rendering to a string, or any
JSON payload where the value was quoted — therefore stores a `str` in
`turn_off_event`.
Both places that derive a delay from it compare it against an int:
delay = max(transition or 0, TURNING_OFF_DELAY) # during turn-off
delay = max(transition, TURNING_OFF_DELAY) # just_turned_off
which raises `TypeError: '>' not supported between instances of 'int'
and 'str'`. Because `just_turned_off` runs inside the state-change
listener task, the exception is swallowed: it surfaces only as
"Error doing job: Task exception was never retrieved (task: None)",
while the light quietly stops being adapted after that turn-off.
Read the transition through a helper that coerces to float. Schema
validation runs before the event fires, so whatever reaches the helper
is coercible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Normalize turn-off transitions with the light service validator
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
tests/README.md has developers clone Home Assistant core into ./core, but
Docker does not read .gitignore, so `COPY . /app/` shipped that ~300MB
checkout into the build context and into the image on every build.
It also changed what the build did. With /app/core already present as a
real directory, `ln -s /core /app/core` linked *inside* it — leaving a
stray /app/core/core -> /core — and scripts/setup-dependencies then
installed from the copied host checkout rather than the image's own
pinned clone.
Excluding core/ (plus local virtualenvs, VCS state and caches) takes the
build context from 412MB to 4.6MB and the image from 2.34GB to 2.1GB, and
makes a build with a local ./core behave like a clean one: /app/core is
the intended symlink to /core.
This does remove an accident. An image built while a local ./core existed
happened to run without `-v $(pwd):/app`, because the copied checkout
carried relative symlinks that still resolved inside /app. A clean-checkout
build never had that property — there the symlinks setup-symlinks writes
into /core dangle — and tests/README.md requires the mount either way.
479 passed, unchanged.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
`ln -fs` dereferences an existing symlink to a directory and creates the
new link *inside* it, so running the script a second time left two stray
symlinks in the working tree instead of replacing the existing ones:
tests/tests -> ../../../tests/
custom_components/adaptive_lighting/adaptive_lighting
-> ../../../custom_components/adaptive_lighting
Neither path is gitignored, so `git add -A` commits them.
Add `-n` so an existing symlink is treated as a file and replaced.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: add expand_light_groups option
Some light group entities act as a proxy that must receive a single combined
`light.turn_on` call to function correctly — virtual mixers like
<https://github.com/mion00/color-temperature-light-mixer> for instance,
that blend a warm and a cold white channel into one entity. In such setups the
individual member entities only expose `ColorMode.BRIGHTNESS`, so sending
separate per-member commands bypasses the mixing logic.
Setting `expand_light_groups: false` keeps the group entity in `self.lights`
instead of expanding it to its members. Adaptation commands go to the group,
and the interceptor no longer skips group entities for that switch.
Default is `true` — no behaviour change for existing configurations.
* tests: regression test for expand_light_groups=False
_switches_with_lights was expanding the incoming entity_id globally,
causing the switch to never be found when expand_light_groups=False
* Resolve group targets consistently across adaptation paths
* Discard delayed group events after target changes
* Stabilize delayed group target regression test
---------
Co-authored-by: Bas Nijholt <bas@nijho.lt>
* feat: add `adapt_only_on_ha_turn_on` to skip adapting externally turned-on lights
When a light turns on from `off` via a source outside Home Assistant — a
physical wall switch or a hub/manufacturer scene (e.g. Lutron) — and
`detect_non_ha_changes` is enabled, Adaptive Lighting adapts the light on the
resulting `off` → `on` event, overriding the brightness/color the external
source just set. Disabling `detect_non_ha_changes` avoids this but also stops
detection of manual changes to already-on lights; the two behaviors were
coupled to a single flag.
Add `adapt_only_on_ha_turn_on` (default `false`, requires `take_over_control`).
When enabled, an `off` → `on` transition with no matching HA `light.turn_on`
context is marked `manual_control` and left untouched, independent of
`detect_non_ha_changes`, decoupling the two behaviors.
The off→on guard reduces to the previous expression when the option is `false`,
so existing configurations are unaffected. Includes a parametrized regression
test, docs, and regenerated strings/services/README via
scripts/update-generated-content.
Refs #435
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* Shorten generated turn-on option description
* Document shared turn-on policy limitations
* Name external turn-on policy after manual-control behavior
* Clarify settings needed to adapt unmatched turn-ons
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
* Add minimum brightness automation and blueprint
* Ignore independent profile event order in test
* Add tested blueprints for sleep, schedules, and daylight
* Implement collapsible sections for options flow
Replace single-form options flow with collapsible sections:
- Basic options always visible (9 fields)
- Advanced options in collapsed section (collapsed by default)
- Single form, no multi-step navigation needed
Changes:
- const.py: Add BASIC_OPTIONS set defining which options are basic
- config_flow.py: Use section() from data_entry_flow to wrap advanced options
- strings.json: Restructure with sections.advanced for section translations
- tests: Update to handle nested section input format
* Update README.md, strings.json, and services.yaml
* Fix: Use vol.Required for section to render properly
* Fix section structure: separate basic and advanced fields in strings.json
* Remove accidentally added files
* Add local directories to gitignore
* Update README.md, strings.json, and services.yaml
* Preserve options behavior with collapsible sections
* Test serialized advanced options section
* fix: preserve config metadata when retrying options
* fix: keep options form defaults serializable
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Report line and branch coverage in CI
* docs: explain coverage reports and behavioral tests
* Calculate coverage percentages from counts for older versions
* Register service actions in async_setup for Bronze tier compliance
- Move 'apply' and 'set_manual_control' service registration from async_setup_entry to async_setup.
- Move service handlers to module-level functions in switch.py.
- Update apply_service_schema to support dynamic defaults for transition duration.
- Clean up related unused imports and fix Python 3.10 syntax compatibility.
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Clean up and add tests
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Fix lint errors
* Automated update of generated docs
* Re-add types
* Make transition not required again
* fix: validate global service targets
* fix: document optional apply transition
* fix: derive service docs from schema markers
* docs: clarify service target options
* fix: preserve entity service target handling
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
* Disable manual control reset on sleep mode change
* adapt test to new behavior
* fix comment
* add switch + test
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* Update auto-generated content
* fix: preserve existing sleep-toggle reset defaults
* fix: keep cancelling stale adaptations on sleep changes
---------
Co-authored-by: Bas Nijholt <basnijholt@users.noreply.github.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>