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.
9.8 KiB
runtime-range-controls Specification
Purpose
TBD - created by archiving change add-runtime-range-controls. Update Purpose after archive.
Requirements
Requirement: Each AL profile exposes four runtime range entities
For each Adaptive Lighting config entry, the integration SHALL create exactly four number entities, one for each of min_brightness, max_brightness, min_color_temp, max_color_temp. The entities SHALL be registered on the number platform during async_setup_entry and torn down during async_unload_entry. Each entity SHALL share the same device record as the profile's three switches.
| Field | unique_id suffix | native_min | native_max | step | unit | mode |
|---|---|---|---|---|---|---|
min_brightness |
_min_brightness |
1 | 100 | 1 | % |
SLIDER |
max_brightness |
_max_brightness |
1 | 100 | 1 | % |
SLIDER |
min_color_temp |
_min_color_temp |
1000 | 10000 | 100 | K |
SLIDER |
max_color_temp |
_max_color_temp |
1000 | 10000 | 100 | K |
SLIDER |
The full unique_id SHALL be <entry.entry_id>_<suffix>. The device record SHALL be the same (DOMAIN, entry.entry_id) identifier used by the profile's switches.
Scenario: A new config entry produces four number entities
- WHEN the user creates a new Adaptive Lighting config entry
- AND
async_setup_entrycompletes - THEN the entity registry SHALL contain four
numberentities owned by this entry - AND their unique_ids SHALL end with
_min_brightness,_max_brightness,_min_color_temp,_max_color_temprespectively - AND all four entities SHALL be attached to the same device as the profile's switches
Scenario: Number entity bounds match the options-flow selector bounds
- WHEN any of the four range number entities are inspected
- THEN the brightness entities SHALL declare
native_min_value=1,native_max_value=100,native_step=1,native_unit_of_measurement="%", andmode=NumberMode.SLIDER - AND the color-temperature entities SHALL declare
native_min_value=1000,native_max_value=10000,native_step=100,native_unit_of_measurement="K", andmode=NumberMode.SLIDER
Requirement: Entities persist their value across Home Assistant restarts
Each of the four range entities SHALL extend homeassistant.components.number.RestoreNumber so that its last known value is preserved across Home Assistant restarts without an explicit Store helper. On async_added_to_hass, each entity SHALL prefer in order: (1) the value present in entry.options[CONF_*] if that value is newer than the restored value (which is the case immediately after an options-flow save), (2) the restored value from RestoreNumber.async_get_last_number_data(), (3) the value present in entry.options[CONF_*] as the first-creation fallback.
Scenario: Slider value survives an HA restart
- GIVEN the user has moved
number.adaptive_lighting_<name>_min_brightnessto 30 via the dashboard - WHEN Home Assistant is restarted
- AND the integration re-runs
async_setup_entry - THEN the entity's state SHALL be 30 once it finishes loading
Scenario: Options-flow save value wins over restored value
- GIVEN the user has previously moved the slider to 30 (restored state)
- WHEN the user opens the options flow, sets
min_brightnessto 50, and saves - AND the integration reloads
- THEN the entity's state SHALL be 50, not 30
Requirement: Curve math reads runtime ranges from the number entities
The brightness and color-temperature curve evaluation SHALL read its four bound values (min_brightness, max_brightness, min_color_temp, max_color_temp) by calling hass.states.get(<entity_id>).state for the corresponding number entity, casting to int, on every curve evaluation. The curve evaluation SHALL NOT read these four values from entry.options during normal operation.
When hass.states.get(<entity_id>) returns None or the state is unavailable or unknown, the curve evaluation SHALL fall back to the value in entry.options[CONF_*] and SHALL log the fallback at DEBUG level.
Scenario: Slider change takes effect on next curve tick
- GIVEN the integration is running with
number.adaptive_lighting_<name>_max_brightnessat 100 - WHEN the user moves the slider to 70
- AND the next curve evaluation tick fires
- THEN the brightness curve SHALL be computed with
value_max = 70
Scenario: Entity-unavailable fallback uses entry.options
- GIVEN the four number entities are not yet available (e.g., during early setup race)
- WHEN the curve evaluation runs
- THEN the curve SHALL be computed using the values from
entry.options[CONF_*] - AND a
DEBUGlog entry SHALL be emitted naming the missing entity
Requirement: Slider changes do not trigger an integration reload
Moving a slider on any of the four range entities SHALL NOT call hass.config_entries.async_update_entry for the owning config entry. The entity's new value SHALL take effect on the next curve evaluation tick without any reload of the integration, the device, or other entities.
Scenario: Slider drag does not reload the integration
- GIVEN the integration is loaded
- WHEN the user moves
number.adaptive_lighting_<name>_min_brightnessfrom 10 to 20 via the dashboard - THEN
async_unload_entrySHALL NOT be invoked - AND
async_setup_entrySHALL NOT be invoked - AND the profile's switch entity IDs SHALL remain unchanged
- AND the entity's state SHALL update to 20
Requirement: Options-flow save propagates new range values to the entities
When the user saves the options flow with new values for any of the four range fields, the resulting integration reload SHALL cause the four entities to be recreated with the just-saved values as their initial state. Once the reload completes, each entity's state SHALL match the value submitted in the options flow.
Scenario: Saving updated ranges in the options flow updates the sliders
- GIVEN
number.adaptive_lighting_<name>_min_brightnessis currently at 30 - WHEN the user opens the options flow, sets
min_brightnessto 55, and saves - THEN the integration SHALL reload
- AND after the reload, the entity state SHALL be 55
Requirement: Options-flow open seeds range fields from current entity state
When the options dialog is rendered, the default values shown for the four range fields (min_brightness, max_brightness, min_color_temp, max_color_temp) SHALL be read from the corresponding number entity's current state via hass.states.get(<entity_id>).state cast to int. The dialog SHALL NOT seed these four fields from entry.options[CONF_*] when entities exist and are available. If an entity is unavailable, the dialog SHALL fall back to entry.options[CONF_*] for that field.
The other ~14 fields in the options dialog SHALL continue to seed from entry.options as defined by the options-flow capability.
Scenario: Open options after live tuning shows live values
- GIVEN the user has moved
number.adaptive_lighting_<name>_max_brightnessfrom 100 (default) to 80 via the dashboard - WHEN the user opens the options flow
- THEN the
max_brightnessfield in the Daytime curve section SHALL show 80 as its default - AND the other fields in the dialog SHALL show their
entry.optionsvalues
Requirement: All AL entities use HA's has_entity_name composition
Every entity created by this integration — the three switches (AdaptiveSwitch, AdaptBrightnessSwitch, AdaptColorSwitch) and the four range number entities — SHALL set _attr_has_entity_name = True and SHALL register under a device whose name matches the profile's display name (entry.title). The per-entity _attr_name SHALL carry only the entity's role, not the integration name or the profile name. The master switch (AdaptiveSwitch) SHALL set _attr_name = None so HA renders its friendly name as the device name alone.
The resulting friendly names SHALL follow this table for a profile named Dining MVP:
| Entity | _attr_name |
Friendly name |
|---|---|---|
| Master switch | None |
Dining MVP |
| Adapt-brightness switch | "Brightness" |
Dining MVP Brightness |
| Adapt-color switch | "Color" |
Dining MVP Color |
| Min brightness number | "Min brightness" |
Dining MVP Min brightness |
| Max brightness number | "Max brightness" |
Dining MVP Max brightness |
| Min color temp number | "Min color temp" |
Dining MVP Min color temp |
| Max color temp number | "Max color temp" |
Dining MVP Max color temp |
Existing unique_ids SHALL remain unchanged; the entity registry SHALL preserve existing entity_ids for any deployed install.
Scenario: Friendly names compose from device name + entity role
- GIVEN an AL profile is configured with display name "Dining MVP"
- WHEN the integration is loaded
- THEN the master switch's friendly name SHALL be exactly "Dining MVP"
- AND the adapt-brightness switch's friendly name SHALL be exactly "Dining MVP Brightness"
- AND the adapt-color switch's friendly name SHALL be exactly "Dining MVP Color"
- AND the four range number entities' friendly names SHALL be "Dining MVP Min brightness", "Dining MVP Max brightness", "Dining MVP Min color temp", "Dining MVP Max color temp"
Scenario: Existing entity_ids survive the rename
- GIVEN an entity registry contains a pre-existing
switch.adaptive_lighting_adapt_brightness_dining_mvp_lightsowned by this integration - WHEN the integration is upgraded to a version that ships this
has_entity_namechange - AND HA reloads the config entry
- THEN the entity's
entity_idSHALL remainswitch.adaptive_lighting_adapt_brightness_dining_mvp_lights(preserved by the registry via stableunique_id) - AND only the entity's friendly name SHALL update to follow the new composition