mirror of
https://github.com/basnijholt/adaptive-lighting.git
synced 2026-10-28 14:17:07 +01:00
fix: entity translations were dead, and both validators disabled themselves (#4)
- Entity translations: HA's Entity._name_internal returns _attr_name before it consults translation_key, and sensor.py/number.py set both, so every entity.sensor.* and entity.number.* string was unreachable. Dropped _attr_name (and the dead display_name parameter); filled ambient_lux, lux_reduction and ramp_half_width into strings.json and translations/en.json. - ambient_lux gets SensorDeviceClass.ILLUMINANCE; the curve outputs keep none. - The five number entities move to EntityCategory.CONFIG. - validate.yml and hassfest.yaml lose their schedule: GitHub auto-disabled both on 2026-09-08 after 60 days of inactivity, which also stops their push and pull_request triggers. - README: the tanh half-width is live-tunable (5-120 min), not a fixed 30 min; the dead All-Contributors badge is gone, and .all-contributorsrc with it (the fork does not run the bot and no longer merges from upstream). - CHANGELOG.md covers 2.3.0-cdit.1 through 2.5.0-cdit.1 (2.2.0 was never tagged; its content shipped in 2.3.0) and gives 2.1.0 its real date. - CLAUDE.md matches the fork as it is: number.py, sensor.py and helpers.py in the layout, three switches, no active openspec change, add-runtime-range-controls shipped in v2.1.0-cdit.1, and the translation_key-only naming rule. 164 tests pass, ruff clean.
This commit is contained in:
parent
d5dc129c11
commit
af57bfb872
10 changed files with 133 additions and 1284 deletions
1232
.all-contributorsrc
1232
.all-contributorsrc
File diff suppressed because it is too large
Load diff
6
.github/workflows/hassfest.yaml
vendored
6
.github/workflows/hassfest.yaml
vendored
|
|
@ -4,9 +4,11 @@ on:
|
|||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
# No schedule: GitHub auto-disables any workflow containing one after 60
|
||||
# days of repository inactivity, and a disabled workflow stops answering
|
||||
# push and pull_request too. Both of this repo's validators were taken
|
||||
# out that way on 2026-09-08.
|
||||
|
||||
jobs:
|
||||
validate_hassfest:
|
||||
|
|
|
|||
6
.github/workflows/validate.yml
vendored
6
.github/workflows/validate.yml
vendored
|
|
@ -4,9 +4,11 @@ on:
|
|||
push:
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
# No schedule: GitHub auto-disables any workflow containing one after 60
|
||||
# days of repository inactivity, and a disabled workflow stops answering
|
||||
# push and pull_request too. Both of this repo's validators were taken
|
||||
# out that way on 2026-09-08.
|
||||
|
||||
jobs:
|
||||
validate_hacs:
|
||||
|
|
|
|||
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -26,7 +26,55 @@ maintainers who can actually fix them for everyone.
|
|||
|
||||
---
|
||||
|
||||
## [2.2.0-cdit.1] — Unreleased
|
||||
## [2.5.0-cdit.1] — 2026-06-07
|
||||
|
||||
### Added
|
||||
|
||||
- **Live ramp half-width** (`add-runtime-ramp-width`) — `number.<profile>_ramp_half_width`,
|
||||
a 5–120 minute slider replacing the previously fixed 30-minute tanh ramp. The
|
||||
curve re-derives on change; no reload needed.
|
||||
|
||||
## [2.4.1-cdit.1] — 2026-06-07
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Sun-event day-anchoring** — the curve collapsed to its minimum during summer
|
||||
afternoons because the two sun events were being anchored to the wrong day.
|
||||
|
||||
## [2.4.0-cdit.1] — 2026-06-05
|
||||
|
||||
### Changed
|
||||
|
||||
- **Range sliders renamed** to lower/upper, with friendlier defaults for a newly
|
||||
created profile.
|
||||
- Ruff is fully green across the fork; lint no longer carries inherited errors.
|
||||
|
||||
## [2.3.1-cdit.1] — 2026-05-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- New-group defaults, and section collapse defaults in the options flow.
|
||||
|
||||
### Removed
|
||||
|
||||
- The `main`-to-`master` sync workflow, upstream cruft — this fork uses `main`.
|
||||
|
||||
## [2.3.0-cdit.1] — 2026-05-31
|
||||
|
||||
Released as `v2.3.0-cdit.1`; there was never a `v2.2.0` tag, so the entries
|
||||
below (originally drafted under 2.2.0) shipped here.
|
||||
|
||||
### Added
|
||||
|
||||
- **Ambient-lux gate** (`add-lux-target`) — a reduce-only brightness gate
|
||||
driven by an optional lux sensor, plus a `lux_reduction` sensor reporting
|
||||
the reduction as a percentage rather than the retained factor.
|
||||
- **Fork identity** — own HACS name, own version badge, explicit upstream link.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Options-flow conditional reveal, and saving with an empty lux sensor.
|
||||
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -69,7 +117,7 @@ card or `apexcharts-card` to start graphing.
|
|||
|
||||
---
|
||||
|
||||
## [2.1.0-cdit.1] — Unreleased
|
||||
## [2.1.0-cdit.1] — 2026-05-16
|
||||
|
||||
### Added
|
||||
|
||||
|
|
|
|||
41
CLAUDE.md
41
CLAUDE.md
|
|
@ -48,9 +48,17 @@ custom_components/adaptive_lighting/
|
|||
│ and the curve math (sun-position + brightness/CT calc)
|
||||
├── config_flow.py UI flow for setup and reconfiguration. Iterates
|
||||
│ VALIDATION_TUPLES from const.py to build the schema.
|
||||
├── switch.py the 4 switch entities per AL config (master, sleep,
|
||||
│ adapt_color, adapt_brightness). After
|
||||
│ cdit-config-redesign lands: 3 switches (no sleep).
|
||||
├── switch.py the 3 switch entities per AL config (master,
|
||||
│ adapt_color, adapt_brightness). Sleep mode was
|
||||
│ removed in cdit-config-redesign.
|
||||
├── number.py 5 live sliders: the 4 brightness/CT range values
|
||||
│ (RANGE_ENTITIES) plus ramp half-width. RestoreNumber,
|
||||
│ EntityCategory.CONFIG.
|
||||
├── sensor.py 5 read-only curve outputs (OUTPUT_SENSORS):
|
||||
│ output_brightness, output_color_temp, sun_elevation,
|
||||
│ and — only with a lux sensor configured —
|
||||
│ ambient_lux, lux_reduction.
|
||||
├── helpers.py small shared utilities used across the platforms.
|
||||
├── const.py CONF_/DEFAULT_ constants + VALIDATION_TUPLES — the
|
||||
│ single source for what fields exist in the schema.
|
||||
├── color_and_brightness.py pure math: tanh, sun-elevation curve, color-temp
|
||||
|
|
@ -64,11 +72,15 @@ custom_components/adaptive_lighting/
|
|||
└── strings.json UI labels for the config flow, errors, abort reasons.
|
||||
```
|
||||
|
||||
The CDiT fork is also planning two follow-on changes that layer cleanly on top:
|
||||
- `add-runtime-range-controls` — adds a `number` platform with live entities for the 4 brightness/CT ranges.
|
||||
- `house-mode-modes` — adds an `input_select`-driven behavior matrix that drives the 3 runtime switches on house-mode change.
|
||||
Both follow-on changes that used to be listed here are resolved:
|
||||
`add-runtime-range-controls` **shipped** (v2.1.0-cdit.1, archived
|
||||
2026-05-17) and `house-mode-modes` was **deleted** in `70983a8` — do not
|
||||
re-propose it without checking why it was dropped.
|
||||
|
||||
Neither is implemented yet; both have proposal stubs in `openspec/changes/`.
|
||||
Entity naming across `number.py` and `sensor.py` goes through
|
||||
`_attr_translation_key`, never `_attr_name`: `Entity._name_internal` returns
|
||||
`_attr_name` before it ever consults the translation key, so setting both
|
||||
silently kills every translation.
|
||||
|
||||
## Planning workflow — OpenSpec under `opsx`
|
||||
|
||||
|
|
@ -76,18 +88,15 @@ All non-trivial changes go through OpenSpec before code lands. The experimental
|
|||
|
||||
```
|
||||
openspec/
|
||||
├── config.yaml (project context lives here once filled in)
|
||||
├── specs/ long-lived capability specs (empty until a change archives)
|
||||
├── config.yaml
|
||||
├── specs/ 4 long-lived capability specs (options-flow,
|
||||
│ runtime-range-controls, lux-feedback, output-sensors)
|
||||
└── changes/
|
||||
├── cdit-config-redesign/ active, 4/4 artifacts, strict-validate green
|
||||
│ ├── proposal.md why + what changes + capabilities + impact
|
||||
│ ├── design.md 15 decisions with rejected alternatives, risks, migration
|
||||
│ ├── specs/options-flow/spec.md 9 requirements, 22 Given/When/Then scenarios
|
||||
│ └── tasks.md 9 task groups, 36 checkboxes with [R, D] annotations
|
||||
├── add-runtime-range-controls/ stub, proposal only
|
||||
└── house-mode-modes/ stub, proposal only
|
||||
└── archive/ 7 completed changes — nothing is currently active
|
||||
```
|
||||
|
||||
There is no active change. `changes/` holds only `archive/`.
|
||||
|
||||
Validate any active change with:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@
|
|||
[](https://github.com/hacs/integration)
|
||||
[](https://github.com/CaseyRo/adaptive-lighting/releases)
|
||||
[](https://github.com/basnijholt/adaptive-lighting)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
[](#contributors-)
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -67,8 +64,9 @@
|
|||
> (`sensor.sun2_dawn`, `sensor.sun2_astro_dawn`, etc.) work without code
|
||||
> changes — see "Recommended companions" below.
|
||||
> 3. **Synthetic tanh curve** — brightness and color temperature follow a
|
||||
> smooth tanh ramp anchored at the two sun events with a fixed 30-minute
|
||||
> half-width. No `brightness_mode` selector to fiddle with.
|
||||
> smooth tanh ramp anchored at the two sun events, with a half-width you can
|
||||
> tune live (5-120 min, default 30). No `brightness_mode` selector to fiddle
|
||||
> with.
|
||||
> 4. **Fewer features, on purpose** — sleep mode, take-over-control, manual
|
||||
> sun-time overrides, `only_once`, and `adapt_only_on_bare_turn_on` are
|
||||
> **removed**. Manual overrides are expected to live at the
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ from homeassistant.components.number import (
|
|||
NumberMode,
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.entity import DeviceInfo
|
||||
|
||||
|
|
@ -56,7 +57,6 @@ async def async_setup_entry(
|
|||
field_key=row["field_key"],
|
||||
conf_key=row["conf_key"],
|
||||
default=row["default"],
|
||||
display_name=row["name"],
|
||||
native_min=row["native_min"],
|
||||
native_max=row["native_max"],
|
||||
step=row["step"],
|
||||
|
|
@ -75,6 +75,8 @@ class AdaptiveRangeNumber(RestoreNumber):
|
|||
_attr_has_entity_name = True
|
||||
_attr_mode = NumberMode.SLIDER
|
||||
_attr_should_poll = False
|
||||
# Behaviour knobs, not state — keep them off the primary device page.
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -83,7 +85,6 @@ class AdaptiveRangeNumber(RestoreNumber):
|
|||
field_key: str,
|
||||
conf_key: str,
|
||||
default: float,
|
||||
display_name: str,
|
||||
native_min: float,
|
||||
native_max: float,
|
||||
step: float,
|
||||
|
|
@ -95,7 +96,7 @@ class AdaptiveRangeNumber(RestoreNumber):
|
|||
self._field_key = field_key
|
||||
self._conf_key = conf_key
|
||||
self._default = default
|
||||
self._attr_name = display_name
|
||||
# No _attr_name — see the note in sensor.py.
|
||||
self._attr_translation_key = field_key
|
||||
self._attr_unique_id = f"{entry.entry_id}_{field_key}"
|
||||
self._attr_native_min_value = native_min
|
||||
|
|
@ -197,13 +198,14 @@ class AdaptiveRampWidthNumber(RestoreNumber):
|
|||
_attr_has_entity_name = True
|
||||
_attr_mode = NumberMode.SLIDER
|
||||
_attr_should_poll = False
|
||||
# Behaviour knobs, not state — keep them off the primary device page.
|
||||
_attr_entity_category = EntityCategory.CONFIG
|
||||
|
||||
def __init__(self, *, entry: ConfigEntry) -> None:
|
||||
"""Initialise the ramp half-width entity from its const declaration."""
|
||||
self._entry = entry
|
||||
self._field_key: str = RAMP_WIDTH_ENTITY["field_key"]
|
||||
self._default: float = float(RAMP_WIDTH_ENTITY["default"])
|
||||
self._attr_name = RAMP_WIDTH_ENTITY["name"]
|
||||
self._attr_translation_key = self._field_key
|
||||
self._attr_unique_id = f"{entry.entry_id}_{self._field_key}"
|
||||
self._attr_native_min_value = RAMP_WIDTH_ENTITY["native_min"]
|
||||
|
|
|
|||
|
|
@ -18,7 +18,11 @@ from __future__ import annotations
|
|||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from homeassistant.components.sensor import SensorEntity, SensorStateClass
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import DeviceEntryType
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
|
@ -49,7 +53,6 @@ async def async_setup_entry(
|
|||
hass=hass,
|
||||
entry=config_entry,
|
||||
output_key=row["key"],
|
||||
display_name=row["name"],
|
||||
unit=row["unit"],
|
||||
icon=row["icon"],
|
||||
)
|
||||
|
|
@ -59,13 +62,17 @@ async def async_setup_entry(
|
|||
async_add_entities(entities)
|
||||
|
||||
|
||||
# Only ambient_lux maps onto a standard HA device class. The rest are AL curve
|
||||
# outputs with no equivalent, and claiming one would break their unit handling.
|
||||
_SENSOR_DEVICE_CLASSES = {"ambient_lux": SensorDeviceClass.ILLUMINANCE}
|
||||
|
||||
|
||||
class AdaptiveOutputSensor(SensorEntity):
|
||||
"""One read-only output value from an AL profile's curve tick."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
_attr_should_poll = False
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
_attr_device_class = None # explicit — no fitting HA device class
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -73,7 +80,6 @@ class AdaptiveOutputSensor(SensorEntity):
|
|||
hass: HomeAssistant,
|
||||
entry: ConfigEntry,
|
||||
output_key: str,
|
||||
display_name: str,
|
||||
unit: str,
|
||||
icon: str,
|
||||
) -> None:
|
||||
|
|
@ -81,8 +87,10 @@ class AdaptiveOutputSensor(SensorEntity):
|
|||
self._hass = hass
|
||||
self._entry = entry
|
||||
self._output_key = output_key
|
||||
self._attr_name = display_name
|
||||
# No _attr_name: Entity._name_internal returns it before it ever
|
||||
# consults translation_key, which would leave every translation dead.
|
||||
self._attr_translation_key = output_key
|
||||
self._attr_device_class = _SENSOR_DEVICE_CLASSES.get(output_key)
|
||||
self._attr_unique_id = f"{entry.entry_id}_{output_key}"
|
||||
self._attr_native_unit_of_measurement = unit
|
||||
self._attr_icon = icon
|
||||
|
|
|
|||
|
|
@ -258,20 +258,29 @@
|
|||
},
|
||||
"entity": {
|
||||
"number": {
|
||||
"min_brightness": {
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"max_brightness": {
|
||||
"name": "Brightness upper"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Color temp upper"
|
||||
},
|
||||
"min_brightness": {
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"min_color_temp": {
|
||||
"name": "Color temp lower"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Color temp upper"
|
||||
"ramp_half_width": {
|
||||
"name": "Ramp half-width"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"ambient_lux": {
|
||||
"name": "Ambient lux"
|
||||
},
|
||||
"lux_reduction": {
|
||||
"name": "Lux reduction"
|
||||
},
|
||||
"output_brightness": {
|
||||
"name": "Output brightness"
|
||||
},
|
||||
|
|
@ -280,12 +289,6 @@
|
|||
},
|
||||
"sun_elevation": {
|
||||
"name": "Sun elevation"
|
||||
},
|
||||
"ambient_lux": {
|
||||
"name": "Ambient lux"
|
||||
},
|
||||
"lux_reduction": {
|
||||
"name": "Lux reduction"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -246,20 +246,29 @@
|
|||
},
|
||||
"entity": {
|
||||
"number": {
|
||||
"min_brightness": {
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"max_brightness": {
|
||||
"name": "Brightness upper"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Color temp upper"
|
||||
},
|
||||
"min_brightness": {
|
||||
"name": "Brightness lower"
|
||||
},
|
||||
"min_color_temp": {
|
||||
"name": "Color temp lower"
|
||||
},
|
||||
"max_color_temp": {
|
||||
"name": "Color temp upper"
|
||||
"ramp_half_width": {
|
||||
"name": "Ramp half-width"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"ambient_lux": {
|
||||
"name": "Ambient lux"
|
||||
},
|
||||
"lux_reduction": {
|
||||
"name": "Lux reduction"
|
||||
},
|
||||
"output_brightness": {
|
||||
"name": "Output brightness"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue