Skip to content

Conversation

@bouwew
Copy link
Contributor

@bouwew bouwew commented Oct 15, 2025

Summary by CodeRabbit

  • New Features

    • None.
  • Bug Fixes

    • Restored reliable automatic removal of deleted devices and reduced “ghost” entries.
    • Improved detection of deleted devices with clearer warnings when gateway references are missing.
  • Refactor

    • Device tracking now aligns with the central device registry for more accurate lifecycle management.
  • Tests

    • Updated test to verify enabling a previously disabled sensor via the entity/registry.
  • Chores

    • Component version and dependency bumped; changelog updated.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 15, 2025

Walkthrough

Replaces set-based device ID tracking with DeviceEntry-based tracking and device-registry lookups; diffs registry-derived IDs against discovered IDs to compute additions/removals; changes _async_remove_devices to accept a DeviceRegistry and remove config-entry associations via device_registry.async_update_device. Tests now enable an entity via the entity registry. Minor pragma:no cover annotations added.

Changes

Cohort / File(s) Summary
Coordinator device tracking & removal refactor
custom_components/plugwise/coordinator.py
Import DeviceEntry/DeviceRegistry; change _current_devices from set[str]list[DeviceEntry]; obtain device entries via device_registry.async_entries_for_config_entry(...); compute current_device_ids from DeviceEntry.identifiers; compute new_devices and removed_ids by diffing discovered IDs vs registry IDs; update _async_remove_devices signature to accept (device_registry: DeviceRegistry, removed_ids: set[str]); locate gateway device, use gateway_device.id as via_device_id, identify matching devices by domain/via_device_id/identifiers and call device_registry.async_update_device(..., remove_config_entry_id=...) to clear association; add defensive logging and retain # pragma: no cover where present.
Test: enable entity via entity registry
tests/components/plugwise/test_sensor.py
Replace state/config-reload approach with entity-registry assertions: fetch registry entry for sensor.p1_voltage_phase_one, assert unique_id and disabled_by, call entity_registry.async_update_entity(..., disabled_by=None), and assert the entry is enabled.
Minor annotations & manifest bump
custom_components/plugwise/__init__.py, custom_components/plugwise/climate.py, custom_components/plugwise/manifest.json
Add # pragma: no cover to specific branches (no behavioral change); bump requirements from plugwise==1.8.0plugwise==1.8.1 and component version from 0.58.00.58.1 in manifest.json.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant HA as Home Assistant
    participant Coord as PlugwiseCoordinator
    participant DR as DeviceRegistry
    participant GW as Gateway

    HA->>Coord: Trigger device sync (_async_add_remove_devices)
    Coord->>DR: async_entries_for_config_entry(config_entry)
    DR-->>Coord: List[DeviceEntry]

    Note over Coord: Build _current_devices (list of DeviceEntry) and current_ids from identifiers

    Coord->>GW: Obtain discovered device IDs
    GW-->>Coord: discovered IDs

    rect rgba(200,230,255,0.25)
    Note over Coord: Compute deltas
    Coord->>Coord: new_devices = discovered_ids - current_ids
    Coord->>Coord: removed_ids = current_ids - discovered_ids
    end

    alt Removed devices present
        Coord->>DR: async_get_device(via_device_identifiers)
        DR-->>Coord: gateway_device
        Coord->>Coord: find device entries with via_device_id == gateway_device.id and identifiers ∩ removed_ids
        Coord->>DR: device_registry.async_update_device(device_id, remove_config_entry_id=entry_id)
        DR-->>Coord: DeviceEntry updated (association removed)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

require-dev-pass

Suggested reviewers

  • CoMPaTech

Poem

I nibble through the registry neat,
I list each device, new and old,
I follow gateways' tiny feet,
I unlink those who've lost their hold—
A tidy hop, a tidy code. 🐇

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Correct device-removal" is directly related to the main change in the pull request, which centers on refactoring the device removal logic in the coordinator. The primary modifications involve changing _current_devices from a set of strings to device entries, updating the _async_remove_devices method signature, and implementing a corrected removal flow that properly handles device registry lookups. This aligns with the changelog entry documenting "Repair automatic device-removal via PR #940." The title is specific enough that reviewers scanning the history would understand the PR is about fixing/correcting device removal functionality.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch improve-delete

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89f4ee3 and 65126d8.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (dev)
  • GitHub Check: Setup for HA-core (release/master)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1cb4f4 and f77bf82.

📒 Files selected for processing (1)
  • custom_components/plugwise/coordinator.py (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Prepare and validate pre-commit
🔇 Additional comments (3)
custom_components/plugwise/coordinator.py (3)

28-28: LGTM!

The DeviceEntry import is correctly added to support the new device tracking implementation.


77-78: Type changes align with new DeviceEntry-based tracking.

The type changes from set[str] to list[DeviceEntry] for _current_devices and from set[str] to list[str] for new_devices are appropriate for the new device tracking approach using Home Assistant's device registry.


136-139: LGTM!

Correctly initializes the device registry reference and populates _current_devices with the current device entries for this config entry.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca26a9 and 46aa590.

📒 Files selected for processing (1)
  • custom_components/plugwise/coordinator.py (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Setup for HA-core (release/master)
  • GitHub Check: Setup for HA-core (dev)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46aa590 and b84fbf1.

📒 Files selected for processing (1)
  • custom_components/plugwise/coordinator.py (3 hunks)
🧰 Additional context used
🪛 GitHub Actions: Validate with hassfest
custom_components/plugwise/coordinator.py

[error] 161-161: IndentationError: unindent does not match any outer indentation level.

🪛 Ruff (0.14.0)
custom_components/plugwise/coordinator.py

161-161: unindent does not match any outer indentation level

(invalid-syntax)


162-162: Unexpected indentation

(invalid-syntax)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
custom_components/plugwise/coordinator.py (4)

136-147: Compute IDs once, sort new_devices, and pass removed_ids to the remover

Slightly simplify and make ordering deterministic; also pass only the needed IDs to the remover.

-        self.device_registry = dr.async_get(self.hass)
-        self._current_devices = dr.async_entries_for_config_entry(
-            self.device_registry, self.config_entry.entry_id
-        )
-        current_device_ids = {
+        self.device_registry = dr.async_get(self.hass)
+        self._current_devices = dr.async_entries_for_config_entry(
+            self.device_registry, self.config_entry.entry_id
+        )
+        data_ids = set(data)
+        current_device_ids = {
             identifier[1]
             for device_entry in self._current_devices
             for identifier in device_entry.identifiers
             if identifier[0] == DOMAIN
         }
-        self.new_devices = list(set(data) - current_device_ids)
-        removed_devices = list(current_device_ids - set(data))
+        self.new_devices = sorted(data_ids - current_device_ids)
+        removed_ids = current_device_ids - data_ids
@@
-        if removed_devices:
-            await self._async_remove_devices(data)
+        if removed_ids:
+            await self._async_remove_devices(removed_ids)

Also applies to: 149-151


152-152: Narrow removal loop to removed_ids and avoid duplicate updates

Pass removed_ids and break after the first identifier match to prevent redundant updates.

-    async def _async_remove_devices(self, data: dict[str, GwEntityData]) -> None:
+    async def _async_remove_devices(self, removed_ids: set[str]) -> None:
@@
-        via_device_id = gateway_device.id
-        for device_entry in self._current_devices:
-            for identifier in device_entry.identifiers:
-                if (
-                    identifier[0] == DOMAIN
-                    and device_entry.via_device_id == via_device_id
-                    and identifier[1] not in data
-                ):
-                    self.device_registry.async_update_device(
-                        device_entry.id, remove_config_entry_id=self.config_entry.entry_id
-                    )
-                    LOGGER.debug(
-                        "Removed %s device/zone %s %s from device_registry",
-                        DOMAIN,
-                        device_entry.model,
-                        identifier[1],
-                    )
+        via_device_id = gateway_device.id
+        for device_entry in self._current_devices:
+            if device_entry.via_device_id != via_device_id:
+                continue
+            for identifier in device_entry.identifiers:
+                if identifier[0] == DOMAIN and identifier[1] in removed_ids:
+                    self.device_registry.async_update_device(
+                        device_entry.id, remove_config_entry_id=self.config_entry.entry_id
+                    )
+                    LOGGER.debug(
+                        "Removed %s device/zone %s %s from device_registry",
+                        DOMAIN,
+                        device_entry.model,
+                        identifier[1],
+                    )
+                    break

Also applies to: 161-171


136-139: Avoid fetching the device registry on every update

Cache the registry in init to avoid repeated lookups.

Example outside this hunk:

# __init__
self.device_registry = dr.async_get(hass)

Then drop the assignment in _async_add_remove_devices.


77-79: Sort new_devices for deterministic ordering
new_devices is used across multiple _add_entities methods; replace the unordered list(set(data) - current_device_ids) with a sorted list (e.g.

self.new_devices = sorted(set(data) - current_device_ids)
```) to ensure consistent ordering.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used**: CodeRabbit UI

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b84fbf1b1a40f71faf209c850712b18b946286f1 and 3007cb1744c1293ade15e4659441b162e1e9ea90.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `custom_components/plugwise/coordinator.py` (3 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 GitHub Actions: Test PR against HA-core</summary>

<details>
<summary>custom_components/plugwise/coordinator.py</summary>

[error] 156-156: Ruff: 1 issue found and fixed by pre-commit hook; process exited with code 1 due to changes. Please commit again after reviewing the changes.

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>custom_components/plugwise/coordinator.py (1)</summary><blockquote>

`155-158`: **Gateway guard and logger usage look good**

Properly handles missing gateway and uses LOGGER correctly.

</blockquote></details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Error while testing for Development HA-core:

⚠️ Warning: Incompatible while testing against dev HA-core.
✔️ Success: No problem with testing against released HA-core.

@bouwew bouwew marked this pull request as ready for review October 17, 2025 05:56
@bouwew bouwew requested a review from a team as a code owner October 17, 2025 05:56
@sonarqubecloud
Copy link

@bouwew bouwew marked this pull request as draft October 17, 2025 10:17
@bouwew
Copy link
Contributor Author

bouwew commented Oct 17, 2025

No, this doesn't work :(

@bouwew bouwew closed this Oct 17, 2025
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