Commit graph

50 commits

Author SHA1 Message Date
Casey
1c56674379 Clean up all remaining ruff errors; lint is now fully green
Config:
- .ruff.toml target-version py310 -> py312, matching pyproject's
  requires-python (>=3.12) and the 3.12 'type' aliases switch.py
  already ships. Unblocks two false invalid-syntax errors.
- Ignore D102 in tests (test-class methods), consistent with the
  existing D100/D103 test ignores.

Code (no behavior changes):
- COM812 trailing commas + UP017 dt.UTC via ruff --fix, black re-wrap
- RUF002: unicode minus/en-dash -> ASCII hyphen in docstrings
- SIM105: contextlib.suppress for the lux-reading parse in config_flow
- TRY300: move return out of try in _read_lux_sensor
- PLC0415: hoist inline asyncio/logging imports to module top
- E741: rename ambiguous 'l' comprehension variable to light_id
- PT006/PT018: parametrize tuple + split compound assertion
- D102: docstring for OptionsFlow.async_step_init
- ARG001/ARG002: noqa with justification on HA-required signatures
- PLR0912/PLR0915: noqa on prepare_adaptation_data and
  _update_attrs_and_maybe_adapt_lights (upstream-inherited complexity;
  splitting would hurt readability more than it helps)

./scripts/lint passes clean; 148 tests pass.
2026-06-05 12:16:40 +02:00
Casey
48e9f9770d Apply ruff/black auto-fixes from ./scripts/lint
Formatter collateral only (trailing commas, line wrapping) — no logic
changes. These files failed the hooks before this branch touched them.
2026-06-05 10:49:30 +02:00
Casey
11fe23253c Implement add-lux-target: reduce-only ambient lux gate
Optional per-profile lux sensor binding that dims lights when daylight
alone exceeds a user-set target (factor = target/current). Lights turn
off entirely below min_brightness. Two config fields, live lux reading
in the options flow, two conditional output sensors, sensor state
subscription with 5pp significance guard. 26/26 tasks, 138 tests green.
2026-05-25 16:11:52 +02:00
Casey
1b21228b30 Implement add-output-sensors: 36/41 tasks complete
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.
2026-05-21 10:11:52 +02:00
Casey
d5d3ecc9d4 Implement add-runtime-range-controls
Adds four live-tunable `number` entities per AL profile (min/max
brightness, min/max color temp) that own the runtime curve values.
Slider changes take effect on the next curve tick — no integration
reload. State persists across HA restart via `RestoreNumber`.

Curve math now reads `min_brightness`, `max_brightness`,
`min_color_temp`, `max_color_temp` from the four runtime entities
via the entity registry, falling back to `entry.options` when an
entity is unavailable. The options flow seeds its four range fields
from the current entity state so the dialog matches reality.

Also fixes entity friendly names via HA's `has_entity_name`
composition: profile "Dining MVP" now reads as "Dining MVP",
"Dining MVP Brightness", "Dining MVP Color" — short enough for
HA's tightest cards. `unique_id`s are unchanged so existing
entity_ids stay stable.

Manifest bumped to 2.1.0-cdit.1 (minor, no breaking changes).
14 new tests in `tests/test_number_platform.py`; 108 passing
overall. OpenSpec change archived once 9.x live-HA verification
completes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-05-16 22:24:39 +02:00
Casey
249d18689e 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.
2026-05-16 16:23:23 +02:00
Casey
03d2748cd4 Group 7: tests — 94 passing in 0.57s (53/53 + 9.4 deferred)
Test infrastructure:
- Add `pytest-homeassistant-custom-component` (PHACC) as a `test`
  dependency group. PHACC ships `hass`, `enable_custom_integrations`,
  `MockConfigEntry`, and friends without needing to clone HA core as a
  sibling directory. Modernizes the test setup from upstream's
  `setup-symlinks` pattern.
- conftest.py: add `auto_enable_custom_integrations` autouse fixture
  so HA discovers the integration under `custom_components/` during
  tests. Keep the upstream template-deprecation no-op.
- Import paths: all tests now import from `custom_components.adaptive_lighting`
  (not `homeassistant.components.adaptive_lighting`) and from
  `pytest_homeassistant_custom_component.common` (not `tests.common`).

New tests (groups 7.2-7.16):
- tests/test_color_and_brightness.py (16 tests): TestBrightnessCurve
  asserts min-before-sunrise, midpoint-at-event, max-during-day, sunset
  ramp symmetry. TestColorTempCurve verifies the same shape applies to
  K. TestSunPosition checks the synthetic +1/-1/0 derivation.
  TestTanhDayCurveDirect exercises the helper directly.
- tests/test_config_flow.py (12 tests): six sections in order, each
  section contains only its specified fields, conditional visibility
  of send_split_delay, default sun entities, strict-typed entity
  selectors, NumberSelector slider/box configs, BooleanSelector for
  every flag, full user→create-entry flow, YAML-managed entry aborts
  with `yaml_managed` reason, options flow renders the sectioned schema.
- tests/test_init.py (6 tests): successful setup on current version,
  stale version raises ConfigEntryError via async_migrate_entry,
  unload is clean, tombstone removes orphan sleep entity + logs INFO,
  tombstone is idempotent, tombstone respects config_entry ownership.

Source modernizations driven by the tests:
- __init__.py: add `async_migrate_entry` that surfaces the
  "incompatible — delete and recreate" message and sets the entry to
  MIGRATION_ERROR. Removed direct `ConfigEntryError` from
  `async_setup_entry` (HA routes version mismatches through the
  migration handler now).
- switch.py: removed upstream's YAML-managed-entry auto-remove hack.
  YAML profiles now load normally and the options flow handles the
  "you must edit configuration.yaml" message (spec R7).

Existing tests updated to PHACC paths: test_adaptation_utils.py
(38 tests), test_hass_utils.py (22 tests). Both pass without
modification beyond the import fix.

Deleted: tests/test_switch.py (2,999 LOC of upstream tests, most
covering sleep mode / take-over-control / manual-control state
machines that no longer exist; CDiT-specific switch tests deferred
to a follow-up change).

Result: `uv run --group test pytest tests/` → 94 passed in 0.57s.

Deferred from this change:
- 4.3: manual UI test that toggling a field and saving reloads cleanly
  (requires a real HA instance, can't be done from CLI).
- 9.4: GitHub repo description / topics update (do via `gh repo edit`
  outside the change scope).

openspec status: 4/4 artifacts complete; strict-validate green.
2026-05-16 15:11:49 +02:00
Casey
501e3d639e Implement cdit-config-redesign groups 1-6 (30/53 tasks)
const.py:
- Drop 21 retired CONF_*/DEFAULT_* (sleep cluster, manual sun timing,
  brightness curve variants, take-over-control cluster).
- Add CONF_SUNRISE_ENTITY / CONF_SUNSET_ENTITY (default
  sensor.sun_next_rising / _setting).
- Add RAMP_HALF_WIDTH_SECONDS = 1800.
- Add CONFIG_ENTRY_VERSION = 2 (gate for strict version-break).
- Re-default DEFAULT_MIN_BRIGHTNESS 1→5, DEFAULT_MIN_COLOR_TEMP 2000→2200.
- Update ICON_MAIN / _BRIGHTNESS / _COLOR_TEMP to the CDiT picks.

manifest.json: bump to 2.0.0-cdit.1, pin homeassistant: 2025.1.0,
re-point codeowners/documentation/issue_tracker to the CaseyRo fork.

switch.py:
- Drop sleep_mode_switch creation in async_setup_entry; integration
  now creates 3 switches per profile (master, adapt_color, adapt_brightness).
- Strip sleep-mode state machine from AdaptiveSwitch (sleep_mode_switch
  attribute, sleep_transition, adapt_until_sleep, the state-change
  listener, the _sleep_mode_switch_state_event_action method).
- Bridge-stub _take_over_control / _detect_non_ha_changes /
  _adapt_only_on_bare_turn_on / _only_once / _auto_reset_manual_control_time
  as class-level False/0 so Manager-side branches become dead code without
  needing a full rewrite of AdaptiveLightingManager.
- Remove handle_set_manual_control service + its registration.
- Replace astral-driven curve init with the new SunLightSettings call
  shape; add AdaptiveSwitch._today_sun_events() helper that reads the
  configured sunrise/sunset entities.
- Set _attr_icon on AdaptiveSwitch.

color_and_brightness.py:
- Rewrite SunLightSettings as a pure curve-math wrapper (5 fields:
  name + bounds + ramp half-width); methods take t_sunrise/t_sunset
  as args, never reads HA state.
- Implement piecewise tanh ramp curve per spec R4 / design D11 in
  _tanh_day_curve(). Both brightness and color-temp use the same shape.
- Drop SunEvents, sleep-mode branches, brightness_mode-switch, astral
  dependency, force_rgb_color, lerp_color_hsv sleep-tinted color blending.

config_flow.py:
- Rewrite as sectioned schema (Targets / Daytime curve / Sun schedule /
  Light control / Advanced / Diagnostics) via HA's section() helper.
- Native HA selectors throughout (NumberSelector, EntitySelector,
  BooleanSelector). Strict typing on sun-event entity pickers.
- Conditional visibility for send_split_delay (driver:
  separate_turn_on_commands).
- Extend OptionsFlowWithReload (with ImportError fallback for HA < 2025.1
  dev environment); async_abort(reason="yaml_managed") for SOURCE_IMPORT.
- VERSION = CONFIG_ENTRY_VERSION.

__init__.py:
- Reject older config entries with ConfigEntryError ("recreate the entry")
  per spec R8 / design D4.
- Add _remove_orphan_sleep_entities tombstone helper that scans the
  entity registry for sleep-mode entities owned by this entry and
  removes them on first setup. Idempotent and config_entry-scoped.

_docs_helpers.py: stub the removed DOCS_MANUAL_CONTROL / SET_MANUAL_CONTROL_SCHEMA
so the docs-generator script keeps importing.

Validation:
- ALL modules import cleanly under HA 2024.12.5 (dev env).
- openspec validate cdit-config-redesign --strict: green.
- ruff check --select=F: clean.

Remaining: groups 7 (16 tests), 8 (strings + plain-language pass), 9 (docs).
2026-05-16 14:27:39 +02:00
Mario Guggenberger
5886eee04c
Code cleanup (#1348) 2025-12-12 13:37:42 -08:00
Dobby
19d30430bb
Friendly names (#1258) 2025-11-27 12:01:05 -08:00
ams2990
32719eabae
Fix some type hint issues (#1280) 2025-11-27 09:35:09 -08:00
Daniel
bf7d109c98
fix: async entry setup (#1141)
* Update __init__.py

* Update strings.json
2025-01-01 14:19:31 -08:00
pre-commit-ci[bot]
95a59438b6
[pre-commit.ci] pre-commit autoupdate (#882)
* [pre-commit.ci] pre-commit autoupdate

updates:
- [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.2.0)
- [github.com/psf/black: 23.11.0 → 24.1.1](https://github.com/psf/black/compare/23.11.0...24.1.1)

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-02-05 14:13:13 -08:00
Bas Nijholt
bb84684bce
Style with ruff and logging (#643)
* Formatting with ruff

* more logging

* Require on_only

* Different implementation

* More ruff

* Remove new logging statements

* Remove 'pylint: disable=protected'

* style

* fixes

* ruff

* Switch to ruff

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Fix .github

* Fix

* Add ignores

* fix arg

* fix B905

* Fix D

* Fix more

* Fix more

* order

* use path

* moer path

* Remove unused ignores

* fix tes deps

* fix typo

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-07-23 14:24:22 -07:00
Bas Nijholt
69592938db
Rename TurnOnOffListener to AdaptiveLightingManager (#654)
* Rename TurnOnOffListener to AdaptiveLightingManager

* Bump Python version
2023-07-22 17:57:49 -07:00
Mario Guggenberger
ed80bd7829
feat: service call adaptation (#628)
* feat: service call adaptation

* feat: toggle-on service call adaptation

* feat: prefer service call transition
2023-07-19 09:10:21 +02:00
Benjamin Auquite
adf0d0cf30
Auto reload YAML config changes (#573)
* Update __init__.py

* Update __init__.py

* skip installing codecov

* remove in CI

* test 2023.4.6

---------

Co-authored-by: Bas Nijholt <bas@nijho.lt>
2023-04-26 19:10:52 -07:00
Bas Nijholt
1ca3bf0f6a
Retroactive review changes for #443 (#485)
* Retroactive review changes for #443

* Move up __init__

* Add test for test_change_switch_settings_service

* Add docs

* Add test for defaults

* Remove unused function

* Add skip var

* move pylint marker

* Refactor validate

* Update README.md

---------

Co-authored-by: Benjamin Auquite <halomastar@gmail.com>
2023-03-27 20:56:54 -05:00
Benjamin Auquite
c05e0cc244 Change attributes in AdaptiveSwitch via service call (#443) 2023-03-27 09:05:24 -07:00
Benjamin Auquite
08ff04141e
Feature requests: Switch now optional for service calls | New default icons | Reload .yaml w/o restart | adapt_delay ms (#459)
* added #274

* attempt getSwitchFromLightId()

* Update switch.py

* changed from register_entity_service to hass.services.async_register

* Update switch.py

* Update switch.py

* Update switch.py

* Update switch.py

* Update switch.py

* Update switch.py

* Update services.yaml

* Update switch.py

* test

* test builds ready

* target selector may not be possible with current syntax

priority is backwards-compatibility as I know most people will smash that update button

* expanded light groups

* test builds ready

* add feature #104

* Update custom_components/adaptive_lighting/switch.py

Co-authored-by: Chris <firstof9@gmail.com>

* Might as well type both. No reason not to.

Co-authored-by: Chris <firstof9@gmail.com>

* Might as well type both. No reason not to.

Co-authored-by: Chris <firstof9@gmail.com>

* Reformatted debug messages.

Reimported ServiceCall as suggested

* Multiple switches allowed again in services.

Apparently this was possible before.

With this change, the `lights` argument must not be passed with multiple switches, or the integration has no way of knowing what the user wants to do. Integration did not make this check in prior versions.

Also reformatted the debug messages.
Removed `automerge.yaml` (my apologies)

* Reload config without restart.

You can now reload any changes to the yaml file without restarting your home assistant. Should show a 'reload' button in your integrations page or you can call the homeassistant reload integration service call.

See https://community.home-assistant.io/t/how-to-allow-custom-compontent-for-yaml-configuration-reloading/391190/4

* Use snake_case for function name 'parseServiceArgs'

* Slight rephrase

* Small style changes

* Rephrased debug messages.

Removed `integration_entities` as we rewrote the code from that function already in parse_service_args. Reference function now above parse_service_args.

* removed: `these_switches = data = None`

* Factor out _find_switch_with_lights

* Rename function and add log statement

* Remove pylint marker

* Handle multiple switches found

* Small changes

* Rename _parse_service_args to _get_switches_from_service_call

* Rephrase log messages

* Add type hint

---------

Co-authored-by: Chris <firstof9@gmail.com>
Co-authored-by: Bas Nijholt <bas@nijho.lt>
2023-03-25 20:22:05 -07:00
Bas Nijholt
01fd7f96e2 Run all pre-commit filters 2022-08-28 15:08:03 -07:00
Bas Nijholt
b6419bd09e remove unused imports 2022-04-17 19:51:44 -07:00
Bas Nijholt
44a7e41e80 use pyupgrade 2022-04-17 19:50:55 -07:00
Bas Nijholt
4b31281cf5 remove long doc-string 2020-10-17 16:54:15 +02:00
Bas Nijholt
7ce7f4e2c1 fix tz bug 2020-10-17 15:43:24 +02:00
Bas Nijholt
313c1addf4 track state_changed 2020-10-05 23:41:57 +02:00
Bas Nijholt
7f6deaa7da bug fixes 2020-10-03 17:36:26 +02:00
Bas Nijholt
09b835c72c fix unsubs 2020-09-30 00:42:27 +02:00
Bas Nijholt
297539adda fixes 2020-09-29 13:13:08 +02:00
Bas Nijholt
58b4c639dd define and use switch._unsub_trackers 2020-09-29 10:48:35 +02:00
Bas Nijholt
d18b2a9da6 fix unsub sub 2020-09-28 23:39:55 +02:00
Bas Nijholt
6a5ded6981 update to latest PR 2020-09-28 13:10:41 +02:00
Bas Nijholt
52fa727c75 sync with HA/core PR 2020-09-26 17:39:17 +02:00
Bas Nijholt
5be04625b6 fix ha/core pre-commit issues and submit this code as PR 2020-09-26 16:38:22 +02:00
Bas Nijholt
2109921eee rename function 2020-09-26 13:13:44 +02:00
Bas Nijholt
685e4cd39b rephase some text 2020-09-26 13:12:06 +02:00
Bas Nijholt
34acd03f46 rename replace_none -> replace_none_str 2020-09-26 13:01:43 +02:00
Bas Nijholt
59f8f743ab simplify setting up DOMAIN_SCHEMA 2020-09-24 23:42:21 +02:00
Bas Nijholt
e9ee147e20 fix data in switch 2020-09-24 20:56:01 +02:00
Bas Nijholt
b67b25da23 coerce to serializable type 2020-09-24 20:41:24 +02:00
Bas Nijholt
d4123ee20a use defaults 2020-09-23 19:09:35 +02:00
Bas Nijholt
3b275e1dd3 start with YAML config validation 2020-09-23 18:20:25 +02:00
Bas Nijholt
5ff1da3c1b simplify schema creation 2020-09-23 13:11:50 +02:00
Bas Nijholt
d33a96fd04 simplify validation 2020-09-23 12:59:58 +02:00
Bas Nijholt
5a4da9512a use VALIDATION in switch.py 2020-09-23 12:35:57 +02:00
Bas Nijholt
a18a312ea9 styling 2020-09-20 18:38:01 +02:00
Bas Nijholt
d63abb4d28 commit working version but not with all options 2020-09-20 18:33:36 +02:00
Bas Nijholt
aea2e83a20 cleanup unused imports 2020-09-19 11:35:28 +02:00
Bas Nijholt
9d06be0883 WIP 2020-09-19 11:10:54 +02:00
Bas Nijholt
f444a8a142 need to change the name of folder to get strings.json working 2020-09-13 10:46:33 +02:00
Renamed from custom_components/circadian_lighting/__init__.py (Browse further)