mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-16 00:34:04 +02:00
Fix OptionsFlowWithReload incompatibility with add_update_listener
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.
This commit is contained in:
parent
e59ccfa571
commit
249d18689e
3 changed files with 326 additions and 13 deletions
315
.ha_snapshot_pre_cdit.json
Normal file
315
.ha_snapshot_pre_cdit.json
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
{
|
||||
"master_switches": [
|
||||
"switch.adaptive_lighting_brightness_ent",
|
||||
"switch.adaptive_lighting_color_ent",
|
||||
"switch.adaptive_lighting_dining_mvp",
|
||||
"switch.adaptive_lighting_ent",
|
||||
"switch.adaptive_lighting_ko_circadian_lights",
|
||||
"switch.adaptive_lighting_lr_dark_lights",
|
||||
"switch.adaptive_lighting_no_brightness_control",
|
||||
"switch.adaptive_lighting_other_circadian_lights",
|
||||
"switch.adaptive_lighting_pre_release",
|
||||
"switch.adaptive_lighting_sleep_ent"
|
||||
],
|
||||
"configurations": {
|
||||
"switch.adaptive_lighting_brightness_ent": {},
|
||||
"switch.adaptive_lighting_color_ent": {},
|
||||
"switch.adaptive_lighting_dining_mvp": {
|
||||
"lights": [
|
||||
"light.liv_lt_lights",
|
||||
"light.kit_lt_tablelamp"
|
||||
],
|
||||
"interval": 90.0,
|
||||
"transition": 45.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 49,
|
||||
"max_brightness": 82,
|
||||
"min_color_temp": 2000,
|
||||
"max_color_temp": 4000,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 1,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "tanh",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": true,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "dining_mvp"
|
||||
},
|
||||
"switch.adaptive_lighting_ent": {
|
||||
"lights": [
|
||||
"light.hlu_lt_1"
|
||||
],
|
||||
"interval": 90.0,
|
||||
"transition": 45.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 10,
|
||||
"max_brightness": 100,
|
||||
"min_color_temp": 2000,
|
||||
"max_color_temp": 5500,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 1,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "tanh",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": true,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "entrance_lights"
|
||||
},
|
||||
"switch.adaptive_lighting_ko_circadian_lights": {
|
||||
"lights": [
|
||||
"light.hll_lt_main",
|
||||
"light.oke_lt_terrarium",
|
||||
"light.oke_lt_strip",
|
||||
"light.oke_lt_ceil",
|
||||
"light.oke_lt_bs"
|
||||
],
|
||||
"interval": 90.0,
|
||||
"transition": 15.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 40,
|
||||
"max_brightness": 70,
|
||||
"min_color_temp": 2000,
|
||||
"max_color_temp": 5996,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 5,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "tanh",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": true,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "ko_circadian_lights"
|
||||
},
|
||||
"switch.adaptive_lighting_lr_dark_lights": {
|
||||
"lights": [
|
||||
"light.liv_massive_standing_li",
|
||||
"light.liv_lt_ceiling"
|
||||
],
|
||||
"interval": 90.0,
|
||||
"transition": 45.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 18,
|
||||
"max_brightness": 34,
|
||||
"min_color_temp": 2000,
|
||||
"max_color_temp": 5500,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 1,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "default",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": false,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "lr dark lights"
|
||||
},
|
||||
"switch.adaptive_lighting_no_brightness_control": {
|
||||
"lights": [
|
||||
"light.hwr_ceiling",
|
||||
"light.wc_lt_1"
|
||||
],
|
||||
"interval": 60.0,
|
||||
"transition": 5.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 100,
|
||||
"max_brightness": 100,
|
||||
"min_color_temp": 2000,
|
||||
"max_color_temp": 5500,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 100,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "tanh",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": true,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "no_brightness_control"
|
||||
},
|
||||
"switch.adaptive_lighting_other_circadian_lights": {
|
||||
"lights": [
|
||||
"light.oem_lt_closet"
|
||||
],
|
||||
"interval": 90.0,
|
||||
"transition": 15.0,
|
||||
"initial_transition": 1.0,
|
||||
"min_brightness": 10,
|
||||
"max_brightness": 100,
|
||||
"min_color_temp": 2200,
|
||||
"max_color_temp": 4000,
|
||||
"prefer_rgb_color": false,
|
||||
"sleep_brightness": 1,
|
||||
"sleep_rgb_or_color_temp": "color_temp",
|
||||
"sleep_color_temp": 1000,
|
||||
"sleep_rgb_color": [
|
||||
255,
|
||||
56,
|
||||
0
|
||||
],
|
||||
"sleep_transition": 1.0,
|
||||
"transition_until_sleep": false,
|
||||
"sunrise_time": null,
|
||||
"min_sunrise_time": null,
|
||||
"max_sunrise_time": null,
|
||||
"sunrise_offset": 0.0,
|
||||
"sunset_time": null,
|
||||
"min_sunset_time": null,
|
||||
"max_sunset_time": null,
|
||||
"sunset_offset": 0.0,
|
||||
"brightness_mode": "tanh",
|
||||
"brightness_mode_time_dark": 900.0,
|
||||
"brightness_mode_time_light": 3600.0,
|
||||
"take_over_control": false,
|
||||
"take_over_control_mode": "pause_all",
|
||||
"detect_non_ha_changes": false,
|
||||
"autoreset_control_seconds": 0,
|
||||
"only_once": false,
|
||||
"adapt_only_on_bare_turn_on": false,
|
||||
"separate_turn_on_commands": false,
|
||||
"send_split_delay": 0,
|
||||
"adapt_delay": 0.0,
|
||||
"skip_redundant_commands": true,
|
||||
"intercept": true,
|
||||
"multi_light_intercept": true,
|
||||
"include_config_in_attributes": true,
|
||||
"name": "other_circadian_lights"
|
||||
},
|
||||
"switch.adaptive_lighting_pre_release": {},
|
||||
"switch.adaptive_lighting_sleep_ent": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,6 @@ from .const import (
|
|||
CONF_NAME,
|
||||
CONFIG_ENTRY_VERSION,
|
||||
DOMAIN,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
|
@ -116,7 +115,12 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Set up the component."""
|
||||
"""Set up the component.
|
||||
|
||||
Note: `OptionsFlowWithReload` (in config_flow.py) handles reload-on-save
|
||||
automatically — HA rejects `config_entry.add_update_listener` when used
|
||||
with `OptionsFlowWithReload`, so we deliberately do NOT register one.
|
||||
"""
|
||||
_remove_orphan_sleep_entities(hass, config_entry)
|
||||
|
||||
data = hass.data.setdefault(DOMAIN, {})
|
||||
|
|
@ -125,18 +129,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||
# and explicit `hass.reload_config_entry` calls).
|
||||
hass.bus.async_listen("hass.config.entry_updated", reload_configuration_yaml)
|
||||
|
||||
undo_listener = config_entry.add_update_listener(async_update_options)
|
||||
data[config_entry.entry_id] = {UNDO_UPDATE_LISTENER: undo_listener}
|
||||
data[config_entry.entry_id] = {}
|
||||
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||
"""Update options."""
|
||||
await hass.config_entries.async_reload(config_entry.entry_id)
|
||||
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
unload_ok = await hass.config_entries.async_forward_entry_unload(
|
||||
|
|
@ -144,9 +142,8 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
|
|||
"switch",
|
||||
)
|
||||
data = hass.data[DOMAIN]
|
||||
data[config_entry.entry_id][UNDO_UPDATE_LISTENER]()
|
||||
if unload_ok:
|
||||
data.pop(config_entry.entry_id)
|
||||
data.pop(config_entry.entry_id, None)
|
||||
|
||||
if len(data) == 1 and ATTR_ADAPTIVE_LIGHTING_MANAGER in data:
|
||||
# no more config_entries
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ from custom_components.adaptive_lighting.const import (
|
|||
CONFIG_ENTRY_VERSION,
|
||||
DEFAULT_NAME,
|
||||
DOMAIN,
|
||||
UNDO_UPDATE_LISTENER,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
@ -35,7 +34,9 @@ async def test_successful_setup_on_current_version(hass) -> None:
|
|||
entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
assert entry.state == ConfigEntryState.LOADED
|
||||
assert UNDO_UPDATE_LISTENER in hass.data[DOMAIN][entry.entry_id]
|
||||
# OptionsFlowWithReload handles reload internally — we don't register
|
||||
# an update listener (HA rejects the combination).
|
||||
assert entry.entry_id in hass.data[DOMAIN]
|
||||
|
||||
|
||||
async def test_stale_version_raises_config_entry_error(hass, caplog) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue