diff --git a/.github/workflows/hassfest.yaml b/.github/workflows/hassfest.yaml index e18163ce..92110df3 100644 --- a/.github/workflows/hassfest.yaml +++ b/.github/workflows/hassfest.yaml @@ -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: diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index ffb34768..4b100c19 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -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: diff --git a/README.md b/README.md index 3d8e21cc..4839fa81 100644 --- a/README.md +++ b/README.md @@ -67,8 +67,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 diff --git a/custom_components/adaptive_lighting/number.py b/custom_components/adaptive_lighting/number.py index 72c2d952..be304708 100644 --- a/custom_components/adaptive_lighting/number.py +++ b/custom_components/adaptive_lighting/number.py @@ -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"] diff --git a/custom_components/adaptive_lighting/sensor.py b/custom_components/adaptive_lighting/sensor.py index 9b995856..501f58e3 100644 --- a/custom_components/adaptive_lighting/sensor.py +++ b/custom_components/adaptive_lighting/sensor.py @@ -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 diff --git a/custom_components/adaptive_lighting/strings.json b/custom_components/adaptive_lighting/strings.json index d5a94352..be445a63 100644 --- a/custom_components/adaptive_lighting/strings.json +++ b/custom_components/adaptive_lighting/strings.json @@ -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" } } } diff --git a/custom_components/adaptive_lighting/translations/en.json b/custom_components/adaptive_lighting/translations/en.json index 68ab99b9..cbd61af6 100644 --- a/custom_components/adaptive_lighting/translations/en.json +++ b/custom_components/adaptive_lighting/translations/en.json @@ -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" },