mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-09-16 00:34:04 +02:00
Rename range sliders to lower/upper + friendlier new-profile defaults
Device page sorts entities alphabetically by name, so 'Min/Max …' always
listed max above min ('a' < 'i'). Rename the four range numbers to
'Brightness lower/upper' and 'Color temp lower/upper' so each quantity's
lower bound sorts first. Pin suggested_object_id to the field key so new
profiles keep slugging number.<profile>_min_brightness etc., identical to
profiles created before the rename; existing entity_ids are registry-
preserved via unchanged unique_ids.
New-profile defaults: brightness 5-100% -> 10-90% (5% proved too dim on
gloomy days; 90% leaves manual-boost headroom), min color temp 2200K ->
2000K (deeper warm tone won in practice). Existing profiles unaffected —
saved options and restored slider values take precedence.
Specs synced: runtime-range-controls naming table + scenarios,
output-sensors collision references. Two new tests cover the slug pin
and the lower/upper friendly-name composition.
This commit is contained in:
parent
48e9f9770d
commit
ada3538449
8 changed files with 73 additions and 27 deletions
|
|
@ -51,20 +51,22 @@ DOCS[CONF_INITIAL_TRANSITION] = "Fade time when a light first turns on, in secon
|
|||
CONF_INTERVAL, DEFAULT_INTERVAL = "interval", 90
|
||||
DOCS[CONF_INTERVAL] = "How often to recompute and re-apply the curve, in seconds."
|
||||
|
||||
CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 100
|
||||
# CDiT default: 90 instead of upstream's 100. Leaves headroom so a manual
|
||||
# "brighter please" bump above the curve is always possible.
|
||||
CONF_MAX_BRIGHTNESS, DEFAULT_MAX_BRIGHTNESS = "max_brightness", 90
|
||||
DOCS[CONF_MAX_BRIGHTNESS] = "Brightness at the peak of the day, in percent."
|
||||
|
||||
CONF_MAX_COLOR_TEMP, DEFAULT_MAX_COLOR_TEMP = "max_color_temp", 5500
|
||||
DOCS[CONF_MAX_COLOR_TEMP] = "Color temperature at the peak of the day, in Kelvin."
|
||||
|
||||
# CDiT default: 5 instead of upstream's 1. 1% reads as off on most bulbs;
|
||||
# 5% is the dim-but-visible floor.
|
||||
CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS = "min_brightness", 5
|
||||
# CDiT default: 10 instead of upstream's 1. 1% reads as off on most bulbs
|
||||
# (and 5% proved too dim on gloomy days); 10% is the usable floor.
|
||||
CONF_MIN_BRIGHTNESS, DEFAULT_MIN_BRIGHTNESS = "min_brightness", 10
|
||||
DOCS[CONF_MIN_BRIGHTNESS] = "Brightness during the night, in percent."
|
||||
|
||||
# CDiT default: 2200 K instead of upstream's 2000 K. 2000 K reads as
|
||||
# sodium-vapor orange; 2200 K is a softer warm-lamp tone.
|
||||
CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2200
|
||||
# CDiT default: 2000 K (matches upstream). Earlier the fork used 2200 K
|
||||
# ("sodium-vapor orange" worry), but in practice the deeper warm tone won.
|
||||
CONF_MIN_COLOR_TEMP, DEFAULT_MIN_COLOR_TEMP = "min_color_temp", 2000
|
||||
DOCS[CONF_MIN_COLOR_TEMP] = "Color temperature during the night, in Kelvin."
|
||||
|
||||
CONF_PREFER_RGB_COLOR, DEFAULT_PREFER_RGB_COLOR = "prefer_rgb_color", False
|
||||
|
|
@ -165,12 +167,14 @@ UNDO_UPDATE_LISTENER = "undo_update_listener"
|
|||
# `field_key` becomes the entity's unique-id suffix; the `conf_key` ties
|
||||
# the entity back to its initial-seed value in `entry.options`.
|
||||
# Design decisions 5, 6, 11 — see add-runtime-range-controls/design.md.
|
||||
# Naming: "lower/upper" (not "min/max") so the device page — which sorts
|
||||
# entities alphabetically by name — lists min before max for each quantity.
|
||||
RANGE_ENTITIES: list[dict[str, Any]] = [
|
||||
{
|
||||
"field_key": "min_brightness",
|
||||
"conf_key": CONF_MIN_BRIGHTNESS,
|
||||
"default": DEFAULT_MIN_BRIGHTNESS,
|
||||
"name": "Min brightness",
|
||||
"name": "Brightness lower",
|
||||
"native_min": 1,
|
||||
"native_max": 100,
|
||||
"step": 1,
|
||||
|
|
@ -181,7 +185,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [
|
|||
"field_key": "max_brightness",
|
||||
"conf_key": CONF_MAX_BRIGHTNESS,
|
||||
"default": DEFAULT_MAX_BRIGHTNESS,
|
||||
"name": "Max brightness",
|
||||
"name": "Brightness upper",
|
||||
"native_min": 1,
|
||||
"native_max": 100,
|
||||
"step": 1,
|
||||
|
|
@ -192,7 +196,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [
|
|||
"field_key": "min_color_temp",
|
||||
"conf_key": CONF_MIN_COLOR_TEMP,
|
||||
"default": DEFAULT_MIN_COLOR_TEMP,
|
||||
"name": "Min color temp",
|
||||
"name": "Color temp lower",
|
||||
"native_min": 1000,
|
||||
"native_max": 10000,
|
||||
"step": 100,
|
||||
|
|
@ -203,7 +207,7 @@ RANGE_ENTITIES: list[dict[str, Any]] = [
|
|||
"field_key": "max_color_temp",
|
||||
"conf_key": CONF_MAX_COLOR_TEMP,
|
||||
"default": DEFAULT_MAX_COLOR_TEMP,
|
||||
"name": "Max color temp",
|
||||
"name": "Color temp upper",
|
||||
"native_min": 1000,
|
||||
"native_max": 10000,
|
||||
"step": 100,
|
||||
|
|
|
|||
|
|
@ -100,6 +100,16 @@ class AdaptiveRangeNumber(RestoreNumber):
|
|||
# async_added_to_hass overrides with a restored or just-saved value.
|
||||
self._attr_native_value = self._options_value()
|
||||
|
||||
@property
|
||||
def suggested_object_id(self) -> str | None:
|
||||
"""Pin entity_id slugs to the field key (e.g. ``_min_brightness``).
|
||||
|
||||
The display names use "lower/upper" wording (device-page sort order),
|
||||
but entity_ids stay on the min/max field keys so new profiles slug
|
||||
identically to ones created before the rename.
|
||||
"""
|
||||
return self._field_key
|
||||
|
||||
@property
|
||||
def device_info(self) -> DeviceInfo:
|
||||
"""Group with the profile's switches under one device."""
|
||||
|
|
|
|||
|
|
@ -259,16 +259,16 @@
|
|||
"entity": {
|
||||
"number": {
|
||||
"min_brightness": {
|
||||
"name": "Min brightness"
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"max_brightness": {
|
||||
"name": "Max brightness"
|
||||
"name": "Brightness upper"
|
||||
},
|
||||
"min_color_temp": {
|
||||
"name": "Min color temp"
|
||||
"name": "Color temp lower"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Max color temp"
|
||||
"name": "Color temp upper"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
|
|
|||
|
|
@ -247,16 +247,16 @@
|
|||
"entity": {
|
||||
"number": {
|
||||
"min_brightness": {
|
||||
"name": "Min brightness"
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"max_brightness": {
|
||||
"name": "Max brightness"
|
||||
"name": "Brightness upper"
|
||||
},
|
||||
"min_color_temp": {
|
||||
"name": "Min color temp"
|
||||
"name": "Color temp lower"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Max color temp"
|
||||
"name": "Color temp upper"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ The resulting friendly names SHALL follow this table for a profile named `Dining
|
|||
| Output-color-temp sensor | `"Output color temp"` | `Dining MVP Output color temp` |
|
||||
| Sun-elevation sensor | `"Sun elevation"` | `Dining MVP Sun elevation` |
|
||||
|
||||
The chosen role labels SHALL NOT collide with any existing entity's `_attr_name` on the same device (specifically: not `"Brightness"`, which is the adapt-brightness switch's role per `add-runtime-range-controls`, and not `"Min color temp"` / `"Max color temp"`, which are the range-number roles).
|
||||
The chosen role labels SHALL NOT collide with any existing entity's `_attr_name` on the same device (specifically: not `"Brightness"`, which is the adapt-brightness switch's role per `add-runtime-range-controls`, and not `"Color temp lower"` / `"Color temp upper"`, which are the range-number roles).
|
||||
|
||||
#### Scenario: Friendly names compose from device name + sensor role
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ The chosen role labels SHALL NOT collide with any existing entity's `_attr_name`
|
|||
- **WHEN** all ten entities' friendly names are inspected
|
||||
- **THEN** no two entities SHALL share the same friendly name
|
||||
- **AND** specifically the adapt-brightness switch ("Dining MVP Brightness") and the output-brightness sensor ("Dining MVP Output brightness") SHALL be distinguishable strings
|
||||
- **AND** the output-color-temp sensor ("Dining MVP Output color temp") SHALL be distinguishable from the "Min color temp" and "Max color temp" number entities
|
||||
- **AND** the output-color-temp sensor ("Dining MVP Output color temp") SHALL be distinguishable from the "Color temp lower" and "Color temp upper" number entities
|
||||
|
||||
### Requirement: Ambient lux sensor mirrors the configured lux sensor's reading
|
||||
|
||||
|
|
|
|||
|
|
@ -116,10 +116,12 @@ The resulting friendly names SHALL follow this table for a profile named `Dining
|
|||
| 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` |
|
||||
| Min brightness number | `"Brightness lower"` | `Dining MVP Brightness lower` |
|
||||
| Max brightness number | `"Brightness upper"` | `Dining MVP Brightness upper` |
|
||||
| Min color temp number | `"Color temp lower"` | `Dining MVP Color temp lower` |
|
||||
| Max color temp number | `"Color temp upper"` | `Dining MVP Color temp upper` |
|
||||
|
||||
The range numbers use "lower/upper" wording (not "Min/Max") so the HA device page — which sorts entities alphabetically by friendly name — lists each quantity's lower bound before its upper bound. The number entities SHALL additionally pin `suggested_object_id` to their field key (`min_brightness`, `max_brightness`, `min_color_temp`, `max_color_temp`) so newly created profiles slug the same entity_ids as profiles created before the rename.
|
||||
|
||||
Existing `unique_id`s SHALL remain unchanged; the entity registry SHALL preserve existing `entity_id`s for any deployed install.
|
||||
|
||||
|
|
@ -130,7 +132,8 @@ Existing `unique_id`s SHALL remain unchanged; the entity registry SHALL preserve
|
|||
- **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"
|
||||
- **AND** the four range number entities' friendly names SHALL be "Dining MVP Brightness lower", "Dining MVP Brightness upper", "Dining MVP Color temp lower", "Dining MVP Color temp upper"
|
||||
- **AND** the four range number entities' entity_ids SHALL slug from the field keys (e.g. `number.dining_mvp_min_brightness`), not from the display names
|
||||
|
||||
#### Scenario: Existing entity_ids survive the rename
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ T_SUNSET = dt.datetime(2026, 3, 21, 18, 0, 0, tzinfo=UTC)
|
|||
|
||||
@pytest.fixture
|
||||
def settings() -> SunLightSettings:
|
||||
"""Standard CDiT defaults — 5–100 % brightness, 2200–5500 K."""
|
||||
"""Fixed test range — 5-100 % brightness, 2200-5500 K (explicit, not DEFAULT_*)."""
|
||||
return SunLightSettings(
|
||||
name="test",
|
||||
min_brightness=5,
|
||||
|
|
|
|||
|
|
@ -79,6 +79,35 @@ async def test_four_range_entities_registered(hass) -> None:
|
|||
assert eid.startswith("number.")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Naming — "lower/upper" display names, but entity_ids slug from field keys
|
||||
# (suggested_object_id pin keeps new profiles consistent with pre-rename ones)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
async def test_entity_ids_slug_from_field_keys(hass) -> None:
|
||||
"""Renamed display names must not leak into entity_id slugs."""
|
||||
entry = await _setup_entry(hass)
|
||||
for field_key in FIELD_KEYS:
|
||||
eid = _resolve_entity_id(hass, entry, field_key)
|
||||
assert eid == f"number.{PROFILE_NAME}_{field_key}"
|
||||
|
||||
|
||||
async def test_friendly_names_use_lower_upper_wording(hass) -> None:
|
||||
"""Device page sorts alphabetically; lower/upper puts min above max."""
|
||||
expected_roles = {
|
||||
"min_brightness": "Brightness lower",
|
||||
"max_brightness": "Brightness upper",
|
||||
"min_color_temp": "Color temp lower",
|
||||
"max_color_temp": "Color temp upper",
|
||||
}
|
||||
entry = await _setup_entry(hass)
|
||||
for field_key, role in expected_roles.items():
|
||||
eid = _resolve_entity_id(hass, entry, field_key)
|
||||
state = hass.states.get(eid)
|
||||
assert state.attributes["friendly_name"] == f"{PROFILE_NAME} {role}"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 7.3 — Same device as the profile's switches
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue