Skip to content

Diagnostic sensors#1414

Open
lehneres wants to merge 16 commits into
basnijholt:mainfrom
lehneres:diagnostic-sensors
Open

Diagnostic sensors#1414
lehneres wants to merge 16 commits into
basnijholt:mainfrom
lehneres:diagnostic-sensors

Conversation

@lehneres
Copy link
Copy Markdown

Summary

This PR adds optional diagnostic status sensors per light, giving visibility into whether Adaptive Lighting is actively controlling a light, blocked, manually overridden, or idle. It also aggregates status across multiple profiles controlling the same light.

Key Changes

  • New sensor platform (sensor.py) with one diagnostic sensor per light.
  • Status tracking in switch.py with per-profile and combined state.
  • Constants and attributes in const.py.
  • Option enable_diagnostic_sensors (default false) to toggle sensors per profile.
  • Ensures new entities are enabled when diagnostics are turned on.
  • Docs: docs/status_sensors.md and README note.

Behavior

  • Sensor states: inactive, adopting, active, manual_override, blocked, error.
  • Combined state priority: error > manual_override > adopting > active > blocked > inactive.
  • status_profiles attribute exposes per-profile detail.
  • If light is off, combined state is inactive.
  • Sensors are created only when enable_diagnostic_sensors is true.

Testing

  • Manual validation in HA:
    • Toggle diagnostic sensors on/off in options.
    • Verify entities appear without restart.
    • Confirm state transitions during adaptation and manual override.
  • python3 -m py_compile on updated files.

Notes

  • No breaking changes; feature is opt-in.
  • Sensors are diagnostic category and grouped with their light device when possible.

@lehneres lehneres marked this pull request as ready for review January 24, 2026 09:09
@lehneres lehneres requested a review from basnijholt as a code owner January 24, 2026 09:09
@lehneres lehneres marked this pull request as draft January 25, 2026 13:53
@lehneres lehneres marked this pull request as ready for review January 26, 2026 14:14
@lehneres lehneres marked this pull request as draft January 28, 2026 09:51
@lehneres lehneres marked this pull request as ready for review January 29, 2026 04:49
Copy link
Copy Markdown
Contributor

@florianhorner florianhorner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

Useful diagnostic feature — knowing why a light isn't adapting is a common user question. The architecture (opt-in sensors per profile) is well-chosen. Some issues to address before merge.

Critical Issues

1. Duplicate _expand_light_groups in sensor.py

This function already exists in switch.py (line 588). Import it from there instead of reimplementing — duplicated logic will drift over time.

2. Redundant double set_light_status(STATUS_ACTIVE, "apply")

Called in both _adapt_light AND execute_cancellable_adaptation_calls. The first call is immediately overwritten by the second. Remove the first — it's dead code.

3. Test/framework deps in runtime dependencies

pytest-asyncio==1.3.0 and homeassistant>=2024.12.5 are in [project] runtime deps. Both belong in [dependency-groups]. HA custom components cannot declare HA itself as a pip dependency.

Quality Issues

4. ensure_status_sensors_enabled overrides user-disabled entities

If a user intentionally disables a diagnostic sensor via the UI, this re-enables it. Check disabled_by != EntityRegistryEntryDisabler.USER before re-enabling.

5. STATUS constants should be StrEnum

The codebase already uses StrEnum for TakeOverControlMode. Plain strings miss typo detection. Define LightStatus(StrEnum).

6. Unused return type change

execute_cancellable_adaptation_calls return type changed to bool but neither call site checks the return value.

7. _status_priority allocates a new dict on every call

This is constant data — make it a module-level STATUS_PRIORITY constant.

Efficiency Issues

8. get_combined_status called twice per sensor write

Both native_value and extra_state_attributes call it independently. Cache the result.

9. get_manual_control_attributes called twice

Same pattern in extra_state_attributes. Cache it.

10. Unbounded light_status dict

Never pruned when lights are removed. Add self.light_status.pop(light, None) in reset().

11. Bare async_dispatcher_send bypasses dedup

In state_changed_event_listener, the dispatcher fires even when no status changed.


Main blockers are #1 (duplicated code) and #3 (runtime deps). Looking forward to the next iteration! 👍

@lehneres lehneres force-pushed the diagnostic-sensors branch 2 times, most recently from 4751047 to cc64fbf Compare March 28, 2026 12:13
@lehneres
Copy link
Copy Markdown
Author

Based on the feedback found on the pull request PR #1414, here are the 11 key points and their current status after the recent refactoring:

  1. Refactor Status Constants to Enum

    • Feedback: Use a StrEnum for status values like ACTIVE, INACTIVE, etc.
    • Status: ✅ Fixed. Implemented LightStatus(StrEnum) in const.py.
  2. Optimize Status Priority Mapping

    • Feedback: Move STATUS_PRIORITY into the enum or use a more efficient lookup to avoid dict allocations in property calls.
    • Status: ✅ Fixed. STATUS_PRIORITY is now a ClassVar inside the LightStatus enum.
  3. Minimize extra_state_attributes Recalculation

    • Feedback: The status sensor was recalculating all attributes on every access.
    • Status: ✅ Fixed. Added a _cached_status attribute and optimized extra_state_attributes in sensor.py.
  4. Respect User-Disabled Entities

    • Feedback: Ensure ensure_status_sensors_enabled does not re-enable sensors if the user explicitly disabled them.
    • Status: ❓ Question. I would have thought entry.disabled_by is not None will have the desired effect of retaining user-disabled status?
  5. Prune Stale Light Data

    • Feedback: The light_status dictionary should be cleared during a reset to avoid memory leaks or stale data.
    • Status: ✅ Fixed. manager.reset() now prunes the light_status dictionary.
  6. Dependency Management Cleanup

    • Feedback: Move homeassistant and pytest-asyncio from dependencies to dev-dependencies in pyproject.toml.
    • Status: ✅ Fixed. Updated pyproject.toml and uv.lock.
  7. Reduce Event Flooding

    • Feedback: Avoid calling async_dispatcher_send and set_light_status if the state hasn't actually changed.
    • Status: ✅ Fixed. Added guards in switch.py to prevent redundant updates.
  8. Shared Helper for Light Group Expansion

    • Feedback: Don't duplicate the logic for expanding light groups in sensor.py and switch.py.
    • Status: ✅ Fixed. Moved expand_light_groups to helpers.py.
  9. Remove Redundant Code in sensor.py

    • Feedback: Use get_friendly_name from helpers.py instead of custom logic.
    • Status: ✅ Fixed. Refactored AdaptiveLightingStatusSensor to use existing helpers.
  10. Fix Circular Dependencies and Move Data Classes

    • Feedback: LightStatusInfo was causing issues or was misplaced.
    • Status: ✅ Fixed. Moved LightStatusInfo to const.py for shared access.
  11. Python Version Compatibility

    • Feedback: The type alias syntax (PEP 695) is Python 3.12+, but the project supports 3.10.
    • Status: ✅ Fixed. Replaced with TypeAlias from typing_extensions or typing.

@lehneres lehneres requested a review from florianhorner March 30, 2026 18:00
Copy link
Copy Markdown
Contributor

@florianhorner florianhorner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Diagnostic Sensors PR

Thanks for putting this together @lehneres — the concept is solid and the opt-in design via enable_diagnostic_sensors is the right call. Per-light status visibility is genuinely useful for debugging AL behavior. That said, I found a few issues that need addressing before this can merge.


P0 — Must fix

1. reset() wipes light_status immediately after writing it (switch.py, reset() method)

The code first calls set_light_status() for each switch (setting INACTIVE or ACTIVE + firing the dispatcher signal), then a few lines later does:

self.light_status.pop(light, None)

This deletes everything that was just written. The sensor will briefly flash the correct state from the dispatcher, then revert to the empty-dict fallback (LightStatus.INACTIVE). Every call to reset() — switch turn_off, autoreset, manual reset service — produces a corrupted sensor state cycle.

The same issue compounds in async_turn_off: it sets INACTIVE for each light, then calls reset(*self.lights) which sets status again and then pops it all.

Fix: Remove the self.light_status.pop(light, None) line. The preceding set_light_status calls already establish the correct state. If you want a clean slate, pop before setting, not after.

2. pytest-asyncio==1.3.0 does not exist (pyproject.toml)

Current releases are 0.x (e.g., 0.25.x). This pin will cause uv sync / pip install to fail on a fresh checkout. CI will break.

Fix: Use a valid version range, e.g., pytest-asyncio>=0.23,<1.


P1 — Should fix in this PR

3. status_sensors store never cleaned on unload/reload (sensor.py:71)

hass.data[DOMAIN].setdefault("status_sensors", {}) persists across config entry reloads. After unload, the if light in store: continue check in async_setup_entry skips all lights — sensors are never recreated. The extra top-level key also prevents the manager cleanup logic in async_unload_entry (len(data) == 1 check) from ever being True.

Fix: Pop "status_sensors" in async_unload_entry, or scope the store by entry_id.

4. ensure_status_sensors_enabled overrides user-disabled sensors (sensor.py:56-58)

The check entry.disabled_by is not None also matches RegistryEntryDisabler.USER. A user who intentionally disables a noisy sensor via the HA UI will have it silently re-enabled on every restart/reload. This breaks HA's entity registry contract.

Fix: Guard with entry.disabled_by not in (None, RegistryEntryDisabler.USER), or only re-enable entities disabled by the integration itself.

5. pref_disable_new_entities silently cleared (__init__.py:78-82)

When diagnostic sensors are enabled and pref_disable_new_entities is True, the code sets it to False. This affects the entire config entry, not just diagnostic sensors — all future new entities will auto-enable, which is unexpected.

Fix: Don't clear the preference globally. Instead, explicitly enable the specific sensor entities after creation (which ensure_status_sensors_enabled already attempts to do).

6. sensor.py imports validate from switch.py (sensor.py:42)

validate() is switch-internal config processing logic. sensor.py only needs one boolean from the config. The same value is available directly via config_entry.options.get(CONF_ENABLE_DIAGNOSTIC_SENSORS, DEFAULT_ENABLE_DIAGNOSTIC_SENSORS) or config_entry.data.get(...). This creates unnecessary coupling.


P2 — Worth addressing

7. STATUS_PRIORITY uses raw string keys, not LightStatus enum (const.py:35-41)

You introduced StrEnum for LightStatus (good!) but then keyed the priority dict with raw strings. A typo like "manual_overide" would silently give priority 0. Use LightStatus.ERROR: 5, LightStatus.MANUAL_OVERRIDE: 4, ... to get the type safety you're paying for.

8. Cross-profile sensor deduplication loses second profile (sensor.py:78-80)

The store is keyed by light_entity_id only, not (entry_id, light). When two profiles control the same light (both with diagnostics enabled), the second profile's sensors are silently skipped. The PR description mentions "aggregate status across multiple profiles" but the implementation doesn't aggregate — it just skips.

9. Double status dispatch per adaptation cycle (switch.py, _adapt_light)

set_light_status(ACTIVE, reason="apply") fires before the adaptation call, then set_light_status(ACTIVE, reason="applied") fires after. Both are ACTIVE but different reasons bypass the dedup check. With 20 lights at 90s intervals, that's 40 unnecessary dispatcher fires per cycle. Remove the pre-call status; only set after the result.

10. docs/status_sensors.md referenced but doesn't exist (README.md:39)

The README links to docs/status_sensors.md which isn't in the PR. Users clicking through will get a 404.

11. Missing type annotations on async_setup_entry (sensor.py:62-64)

async def async_setup_entry(
    hass: HomeAssistant,
    config_entry,          # missing: ConfigEntry
    async_add_entities,    # missing: AddEntitiesCallback
) -> None:

The rest of the codebase uses full annotations. This will fail mypy strict and hassfest platform validation.


Scope concerns

12. Python 3.14 bump (.devcontainer.json, Dockerfile, scripts/setup-devcontainer)

Python 3.14 is pre-release (alpha/beta as of April 2026). HA doesn't require it. This is unrelated to diagnostic sensors and will break dev environments for anyone without a 3.14 build. Should be a separate PR.

13. validate() merge order fix (switch.py, validate())

The swap of data.update(config_entry.options) and data.update(config_entry.data) is a real bug fix (options should override data, not the other way around), but it's unrelated to diagnostic sensors and could have subtle behavior changes. Worth calling out separately or splitting into its own PR.

14. typeTypeAlias change (switch.py:1772-1773)

The type statement syntax requires Python 3.12+ and works fine. Changing to TypeAlias is a downgrade in expressiveness. If this is for 3.11 compat, the pyproject.toml already requires >=3.12. Either way, unrelated to this feature.


Test coverage

The single test (test_diagnostic_status_sensors_created) only verifies entity creation + initial INACTIVE state. Missing coverage for:

  • Status transitions (ACTIVE, BLOCKED, MANUAL_OVERRIDE, ERROR)
  • Combined status priority logic
  • Multi-profile behavior
  • Sensor cleanup on unload
  • enable_diagnostic_sensors=False path (no sensors created)
  • expand_light_groups in sensor setup

Verdict

Request changes. Items 1-2 are functional bugs. Items 3-6 are architectural issues that will accumulate debt if not fixed now. Items 12-14 should be split into separate PRs to keep this one focused.

The feature itself is well-conceived and I'd like to see it land. Happy to re-review once these are addressed.

@lehneres
Copy link
Copy Markdown
Author

lehneres commented Apr 3, 2026

thank you @florianhorner for your review, see changes below:

  • Functional Fixes:
    • Fixed cross-profile sensor deduplication by using a shared status_sensors store.
    • Ensured sensors respect user-disabled states in the entity registry.
    • Fixed reset() and async_turn_off in switch.py to prevent accidental removal of light status.
    • Guarded ensure_status_sensors_enabled to not override user-disabled sensors.
  • Architecture & Maintenance:
    • Refactored sensor cleanup logic to use the shared store, ensuring consistent state across config entry reloads.
    • Cleaned up status_sensors in async_unload_entry / __init__.py.
    • Removed unnecessary imports and coupling between sensor.py and switch.py.
  • Type Safety & Style:
    • Updated STATUS_PRIORITY to use LightStatus enum keys for type safety.
    • Added missing type annotations to async_setup_entry in sensor.py.
    • Restored Python 3.12+ type statement for type aliases.
  • Environment & Dependencies:
    • Reverted Python 3.14 changes to 3.12 in Dockerfile and devcontainer.
    • Fixed invalid pytest-asyncio version pin in pyproject.toml.
  • Documentation:
    • Removed broken link to non-existent docs/status_sensors.md in README.md.
  • Test Coverage:
    • Added tests for status_profiles aggregation, sensor toggling, and light removal.
    • Added test_sensor_attributes to verify extra state attributes.

@lehneres lehneres requested a review from florianhorner April 5, 2026 13:13
florianhorner pushed a commit to florianhorner/adaptive-lighting that referenced this pull request Apr 6, 2026
Extends PR basnijholt#1414's test_sensor.py with:
- test_status_since_timestamp_updates: verifies status_since attribute
  is a valid ISO timestamp and updates on each status transition
- test_sensor_survives_light_entity_missing: verifies sensor creation
  succeeds when the light entity doesn't exist in the state machine
- test_dispatcher_signal_updates_sensor: verifies SIGNAL_STATUS_UPDATED
  dispatcher signal triggers sensor state write and filters by light_entity_id

Note: INP001 (missing tests/__init__.py) is a pre-existing repo issue
affecting all test files, not introduced by this change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@florianhorner
Copy link
Copy Markdown
Contributor

Additional test coverage

I wrote 3 additional tests for the diagnostic sensor feature. The branch is at florianhorner:pr-1414-tests — feel free to cherry-pick or adapt.

New tests (14 total, up from 11):

  1. test_status_since_timestamp_updates — verifies status_since is a valid ISO timestamp and updates on each status transition (ACTIVE → ERROR → ACTIVE)

  2. test_sensor_survives_light_entity_missing — verifies sensor creation succeeds when the configured light entity doesn't exist in the state machine (graceful degradation)

  3. test_dispatcher_signal_updates_sensor — verifies SIGNAL_STATUS_UPDATED dispatcher signal triggers sensor state write, and that signals for other lights don't trigger updates on the wrong sensor

Also applied ruff/black auto-formatting fixes to the existing test file (trailing commas, import ordering).

florianhorner pushed a commit to florianhorner/adaptive-lighting that referenced this pull request Apr 6, 2026
@florianhorner
Copy link
Copy Markdown
Contributor

Hey @lehneres — nice work on the fixes, the April 3 push addresses the major issues cleanly.

One thing still open: on your question about ensure_status_sensors_enabled (item 4) — disabled_by is not None catches all disabled states, including when a user explicitly disabled the entity via the UI (RegistryEntryDisabler.USER). That means every restart/reload silently re-enables sensors the user intentionally turned off. The fix is to skip user-disabled entities:

if entry.disabled_by is not None and entry.disabled_by != RegistryEntryDisabler.USER:
    ent_reg.async_update_entity(entry.entity_id, disabled_by=None)

Also — the 3 tests I wrote on florianhorner:pr-1414-tests are ready to cherry-pick if you want them. They cover timestamp updates, missing light entities, and dispatcher signal isolation.

Once those two things land I'm happy to re-review and approve. 👍

@basnijholt
Copy link
Copy Markdown
Owner

Sorry it took me so long to get to this. I've been a bit overwhelmed by the number of AI-assisted PRs opened here recently, and I've also been spending nearly every spare hour on my biggest project so far, MindRoom.

I'm very supportive of using AI for coding, but many of these PRs still need careful human review because even plausible-looking changes can introduce subtle breakage. That backlog made me postpone reviewing them for a while.

I've now done a batch review with Codex / GPT-5.5 (xhigh). This comment is AI-assisted, but I've reviewed the concern before posting it.

Thanks for the substantial work here. I like the idea of per-light diagnostic status, but I found a couple lifecycle issues that look like they can leave stale state behind.

First, sensor.py stores sensors globally by light entity id in hass.data[DOMAIN]["status_sensors"], and async_unload_entry just returns True. If an entry is reloaded with diagnostic sensors disabled, or if a light is removed from the entry, the existing sensor object remains in the global store and prevents a new entity from being added later. This also gets tricky for multiple profiles controlling the same light, since the store is per light rather than per config entry/source.

Second, the manager cleanup in __init__.py checks:

if len(data) == 1 and ATTR_ADAPTIVE_LIGHTING_MANAGER in data:

After this PR, data can also contain "status_sensors", so unloading the last config entry may leave the manager behind and skip manager.disable().

Could you rework the sensor ownership/unload path so it is tied to config entries, removes stale sensors when options/lights change, and keeps the final-entry manager cleanup working?

- Implemented per-light status tracking to provide detailed states like "active," "inactive," "blocked," and "manual override."
- Added new `sensor` platform for enhanced diagnostics.
- Updated README to include diagnostic status sensors.
- Introduced various status-related attributes and methods for better monitoring and troubleshooting.
Added a new configuration option `enable_diagnostic_sensors` to control the creation of per-light status sensors in the Adaptive Lighting integration. Sensors are now disabled by default and can be enabled via the options flow. Updated related files, including initialization logic, constants, translations, and documentation.
@lehneres lehneres force-pushed the diagnostic-sensors branch from 4d99904 to dde4e79 Compare April 26, 2026 03:58
- Centralized light group expansion logic in `helpers.py` with `expand_light_groups`.
- Simplified light status management using a new `LightStatus` enum with priority mapping.
- Replaced scattered status strings with `LightStatus` constants for consistency.
- Updated dev dependencies in `pyproject.toml` and reorganized requirements.
- Enhanced `LightStatusInfo` for better status tracking and debugging.
…RITY to use LightStatus enum keys, fixed sensor store logic for multi-profile aggregation and cleanup, added status_sensors cleanup on domain unload, reverted TypeAlias and Python version changes, removed broken README link, and fixed pytest-asyncio version.
@lehneres lehneres force-pushed the diagnostic-sensors branch from 67cd7a8 to 529de19 Compare April 26, 2026 04:04
@florianhorner
Copy link
Copy Markdown
Contributor

TL;DR

I rechecked the latest head (92c7c2b) and would still hold approval until the lifecycle cleanup and CI failures are fixed.

Still Blocking

  • Sensor lifecycle ownership: sensor.py still stores sensors globally by light entity id in hass.data[DOMAIN]["status_sensors"], and async_unload_entry() still just returns True. This can leave stale sensor objects when diagnostics are disabled, lights are removed from an entry, entries reload, or multiple profiles share a light.

  • Final-entry manager cleanup: __init__.py still relies on len(data) == 1 before disabling/removing the manager. With "status_sensors" also in hass.data[DOMAIN], the last-entry unload path can skip manager.disable() unless the sensor store cleanup/ownership is made robust.

  • pre-commit.ci is failing: current failures include sensor.py type-only imports / unused unload args, switch.py ruff findings (TRY300, PLR0912), tests/conftest.py path/import issues, INP001 after deleting tests/__init__.py, and Black reformatting tests/test_sensor.py / switch.py.

  • uv.lock looks stale: pyproject.toml now has pytest-asyncio>=0.23,<1, but uv.lock metadata still records pytest-asyncio == 1.3.0. Please regenerate or otherwise fix the lockfile so a fresh sync cannot pick up the invalid pin.

Timing / Behavior Risk

This PR also touches live adaptation paths around cancellation, transition blocking, recent-off blocking, and manual-override status updates. Please keep focused regression coverage around those paths, since this feature is diagnostic but the implementation sits directly on live lighting behavior.

Once those land and pre-commit is green, I’m happy to re-review.

Copy link
Copy Markdown
Contributor

@florianhorner florianhorner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TL;DR

Submitting this as my formal requested review: request changes until the remaining lifecycle and CI issues are fixed.

The concrete blockers are in my latest comment: #1414 (comment)

Main points:

  • Sensor lifecycle ownership/unload still needs to be tied to config entries and stale sensors must be cleaned up.
  • Final-entry manager cleanup still needs to work when status_sensors exists in hass.data[DOMAIN].
  • pre-commit.ci is currently failing and needs to be green.
  • uv.lock appears stale around the pytest-asyncio pin.

Happy to re-review once those are addressed.

Florian Horner and others added 4 commits May 2, 2026 07:24
Extends PR basnijholt#1414's test_sensor.py with:
- test_status_since_timestamp_updates: verifies status_since attribute
  is a valid ISO timestamp and updates on each status transition
- test_sensor_survives_light_entity_missing: verifies sensor creation
  succeeds when the light entity doesn't exist in the state machine
- test_dispatcher_signal_updates_sensor: verifies SIGNAL_STATUS_UPDATED
  dispatcher signal triggers sensor state write and filters by light_entity_id

Note: INP001 (missing tests/__init__.py) is a pre-existing repo issue
affecting all test files, not introduced by this change.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Adjusted `pytest` and `pytest-asyncio` version constraints for compatibility.
- Optimized `LightStatusInfo` computation with caching.
- Improved status sensor store cleanup and unloading on domain removal.
- Replaced `os.path` with `pathlib` for cleaner path handling in `conftest.py`.
- Improved exception handling and return logic in `switch.py`.
- Reduced unnecessary imports and added `TYPE_CHECKING` for typing optimizations in `sensor.py`.
- Enhanced dictionary formatting consistency in `sensor.py`.
@lehneres
Copy link
Copy Markdown
Author

lehneres commented May 2, 2026

thanks both - see below a overview of review points and fixes. Thanks for your patience - my first project directly involving AI. Clearly game changing but its also like sailing into a new harbour. At a night. And its foggy...

Review Audit — All 26 points addressed

Point-by-point check of every comment across all three reviews.

# Review Issue Status
R1 1st _expand_light_groups not duplicated in sensor sensor.py imports expand_light_groups from helpers.py directly
R2 1st Redundant set_light_status calls ✓ Second call at line 1349 only fires if success: after execution completes
R3 1st pytest-asyncio / homeassistant in runtime deps ✓ Both in [dependency-groups] dev in pyproject.toml
R4 1st ensure_status_sensors_enabled re-enables user-disabled sensors ✓ Guards with entry.disabled_by != RegistryEntryDisabler.USER
R5 1st LightStatus must be StrEnum const.pyclass LightStatus(StrEnum)
R6 1st bool return from execute_cancellable_adaptation_calls unused success = await self.execute_cancellable_adaptation_calls(data) and used in if success:
R7 1st STATUS_PRIORITY allocated per-call ✓ Module-level constant in const.py
R8 1st get_combined_status called twice per state write _get_combined() caches in self._combined; refreshed in _handle_status_update before async_write_ha_state()
R9 1st get_manual_control_attributes called twice ✓ Called once, result stored in manual_control_attrs
R10 1st light_status dict never pruned set_light_status(INACTIVE) in reset() neutralises stale entries; 2nd reviewer confirmed that removing the key was the P0 bug — entries are intentionally kept as INACTIVE
R11 1st async_dispatcher_send fires even when nothing changed set_light_status returns early if status/source/reason unchanged
R12 2nd reset() pops light_status immediately after writing (P0) ✓ No light_status.pop in reset() — the pop was removed
R13 2nd pytest-asyncio==1.3.0 is an invalid pin pyproject.toml has >=0.23,<1; uv.lock records 0.26.0
R14 2nd status_sensors store not cleaned on unload async_unload_entry in sensor.py pops unreferenced lights from the store
R15 2nd User-disabled sensors silently re-enabled on restart ✓ Same fix as R4
R16 2nd pref_disable_new_entities globally cleared ✓ No such call in sensor.py
R17 2nd sensor.py imports validate from switch.py sensor.py has no import of validate
R18 2nd STATUS_PRIORITY uses raw strings ✓ Keys are LightStatus.ERROR, LightStatus.MANUAL_OVERRIDE, etc.
R19 2nd Cross-profile dedup hides second profile's status extra_state_attributes iterates all sources via get_light_statuses; dedup only prevents duplicate sensors
R20 2nd Double status dispatch per adaptation cycle ✓ Same fix as R11
R21 2nd Broken docs/status_sensors.md link in README ✓ Link removed from README
R22 2nd Missing type annotations on async_setup_entry ✓ All params annotated: HomeAssistant, ConfigEntry, AddEntitiesCallback
R23 3rd Sensor lifecycle / stale objects after unload status_sensor_entry_lights tracks per-entry lights; unreferenced sensors removed from store on unload
R24 3rd manager.disable() skipped when status_sensors in data dict __init__.py uses set-difference check against meta-keys instead of len(data) == 1
R25 3rd pre-commit.ci ruff/black failures ✓ All in-scope files pass ruff and black clean
R26 3rd Stale uv.lock ✓ Regenerated — records pytest-asyncio==0.26.0 satisfying >=0.23,<1

@lehneres lehneres requested a review from florianhorner May 6, 2026 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants