* fix: reduce log verbosity for self-triggered off-to-on warning (#1433)
Move full event object dump from warning to debug level in
_off_to_on_state_event_is_from_turn_on() (switch.py:2717).
For lights with large effect_list attributes (e.g. Govee lights
with 130+ effects), the warning dumped the entire Event object
including both old_state and new_state, creating log entries
thousands of characters long.
The warning now logs only entity_id and context.id, while the
full event remains available at debug level for troubleshooting.
Fixes#1433
* fix: correct indentation for _LOGGER.debug block
* fix: correct indentation for _LOGGER.warning and _LOGGER.debug
* fix: remove unintended encoding corruption, keep only log level change
Reset switch.py to main and re-apply only the intended change:
move the full event object from warning to debug level in
_off_to_on_state_event_is_from_turn_on().
Addresses reviewer feedback about unintended Unicode corruption
(→ and ≈ characters were corrupted to mojibake).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Florian Horner <florianhorner@Mac.fritz.box>
Co-authored-by: Bas Nijholt <basnijholt@users.noreply.github.com>
* test: regression test — AL must not override manual brightness with separate_turn_on_commands
End-to-end scenario: user adjusts brightness via a directly-bound Zigbee
switch (e.g. IKEA RODRET). No HA service call is made; ZHA reports the new
brightness via async_update_entity. On the next adaptation interval AL must
detect the change and stop overriding the user's brightness.
The test verifies the user-visible symptom: after two adaptation cycles
following a simulated direct-Zigbee brightness change, the light's brightness
must still be the manually set value — not AL's own target.
NOTE: this test FAILS on the current code. It is committed here to document
the bug before the fix is applied in the next commit.
* fix: merge last_service_data across split calls to fix detect_non_ha_changes with separate_turn_on_commands
When separate_turn_on_commands=True, each adaptation cycle makes two
light.turn_on calls (brightness, then color_temp). Previously each call
overwrote last_service_data[light], so after the cycle only the color_temp
key remained. _attributes_have_changed() then saw old_brightness=None and
silently skipped the brightness comparison, so a manually-set brightness was
never detected and AL kept overriding it.
Fix: merge instead of overwrite so all split-call attributes accumulate:
self.manager.last_service_data[light] = {
**self.manager.last_service_data.get(light, {}),
**service_data,
}
* test: add intermediate assertions to regression test
Two assertions were promised in the PR description but missing:
1. After the force-adapt, assert that last_service_data contains BOTH
brightness AND color — directly proving the merge fix works.
2. After the first non-forced update, assert that BRIGHTNESS is in
manual_control — proving detection fired, not just that the final
state is right.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* refactor: remove spurious comments, trim test docstring and assertions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor: strip verbose comments from test, trim assert messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
* test: add message to bare assert
## Summary
- Fixes regression in v1.30.0 where lights turned on by automations were incorrectly marked as "manually controlled"
- Makes `adapt_only_on_bare_turn_on` respect individual attribute tracking from #1356
## Root Cause
PR #1356 added a call to `update_manually_controlled_from_event()` in the `turn_on_off_event_listener.on()` handler for ALL `light.turn_on` events, including when turning a light on from OFF state.
When an automation turns on a light with brightness/color attributes, this incorrectly marked the light as "manually controlled", preventing Adaptive Lighting from adapting it.
## Fix
1. Only call `update_manually_controlled_from_event()` when the light was **already ON** before the turn_on event. Turning on from OFF is handled by `_respond_to_off_to_on_event()`.
2. Make `adapt_only_on_bare_turn_on` respect `take_over_control_mode`:
- With `PAUSE_CHANGED`: Only pause adaptation of specified attributes, continue adapting unspecified ones
- With `PAUSE_ALL`: Pause all adaptation (existing behavior)
## Expected Behavior After Fix
| Scenario | `adapt_only_on_bare_turn_on` | `take_over_control_mode` | Result |
|----------|------------------------------|--------------------------|--------|
| Turn on from OFF with brightness | `false` | Either | NOT manually controlled |
| Turn on from OFF with brightness | `true` | `PAUSE_ALL` | All adaptation paused |
| Turn on from OFF with brightness | `true` | `PAUSE_CHANGED` | Only brightness paused, color adapts |
| Turn on from OFF without attributes | Either | Either | NOT manually controlled |
| Change brightness while ON | Either | Either | Brightness manually controlled |
## Test plan
- [x] Turn on light via automation with brightness/color (`adapt_only_on_bare_turn_on=false`) - should adapt
- [x] Turn on light via scene (`adapt_only_on_bare_turn_on=true`, `PAUSE_ALL`) - should pause all adaptation
- [x] Turn on light with brightness only (`adapt_only_on_bare_turn_on=true`, `PAUSE_CHANGED`) - should adapt color
- [x] Both intercept=True and intercept=False paths tested for consistency
- [x] CI tests pass
Fixes#1378
Co-authored-by: Mario Guggenberger <mg@protyposis.net>
* refactor: introduce light control parameter enum
* refactor: replace manual control flag with parameter enum
* test: update deprecated color temp attribute
* build: set execution bits on task scripts
* feat: individual manual control of brightness and color
* test: add tests for individual manual control evaluation
* fix: sequential manual changes not always detected
If multiple attributes of a light were changed within an interval, only the last change was detected because the check in the interval only used the latest event. For example, if there was a brightness change and a following color change, only the color attribute was detected as manually controlled. To fix this, the manual control attribute flags are now set directly from the event handler so that all events are processed.
* fix: invalid service description
* docs: fix missing space in config description
* refactor: pluralize multivalued bitmask enum name
* Add regression tests for SimpleSwitch initial state bug
Adds tests that verify SimpleSwitch._state is set immediately in __init__
rather than waiting for async_added_to_hass(). These tests currently FAIL
because _state is None after __init__, which causes an infinite loop in
_setup_listeners when the entity is disabled (since async_added_to_hass
is never called for disabled entities).
Regression tests for: https://github.com/basnijholt/adaptive-lighting/issues/1264
* Fix infinite loop when disabling SimpleSwitch entities
The issue was that SimpleSwitch._state was initialized to None in __init__,
but only set to a boolean value in async_added_to_hass(). When an entity
is disabled, async_added_to_hass() is never called, so _state stayed None.
The _setup_listeners() method has a while loop that waits for
_state is not None for all SimpleSwitch children (sleep_mode_switch,
adapt_brightness_switch, adapt_color_switch). With _state stuck at None,
this created an infinite loop.
The fix sets _state to initial_state directly in __init__ instead of
waiting for async_added_to_hass() to set it. The async_added_to_hass()
will still properly restore state from the last session or set based
on initial_state as before.
Fixes: https://github.com/basnijholt/adaptive-lighting/issues/1264
When start() creates a task with asyncio.create_task(), the task is scheduled
but not immediately executed. This means is_running() returns True (task exists
and not done), but start_time is still None because _run() hasn't executed yet.
This causes a TypeError when comparing event.time_fired > timer.start_time.
Fix by setting start_time in start() before creating the task.
Fixes#1272
* remove @bind_hass decorator
* attempt to pass hass
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
* suggested fix for HA2025 deprecation warnings
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
* Do not adapt lights that are turned on with an effect
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Bas Nijholt <basnijholt@gmail.com>
* Implement call intercept for multiple lights
* remove comment
* skip if no eids
* add comment
* fix type
* Fix skipped
* indentation
* Add logging and fix error
* Fix for HA ≤2023.04
* simplify
* remove unused ignores
* Debug mode
* Add test
* Make test failing
* rename switch
* rename lights
* Fix tests
* Rename lights in tests
* Remove unused dependencies
* Improve tests
* More tests
* Remove the DEBUG_MODE
* Add doc-string
* Extra test
* assert
* extra test
* Comments
* fix
* fix
* expand light groups
* more logging
* sort
* Revert is_proactively_adapting checks
This reverts commit 39fd8f2be0.
* simplify the mapping
* Revert "Revert is_proactively_adapting checks"
This reverts commit 18803e8e50.
* test
* no light groups
* do not expand
* Do not expand_light_groups in intercept
* more logging
* Fix
* add comment
* Add multi_light_intercept config option
* Update README.md, strings.json, and services.yaml
* add light group
* fix platform
* add simple test
* turn off again
* Test without take over control
* improve test and fix it in one way
* Fixes
* add cleanup fixture
* format
* Update test_switch.py
* add __str__
* remove unneeded call
* simplify service_data construction
* Generalize is_our_context
* Fix multi_light_intercept: false
* add comments
* add docs
* Update README.md, strings.json, and services.yaml
* Add feature line
* move function
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* Before scheduling turn_on do a last-minute check if lights are off
* Pass force
* add comment
* fix for proactive
* commetn
* No default for force
* rm newline
* no force
* Return bool