Allow removing entity-less devices; evict telemetry dedup map on entity removal#297
Merged
awolden merged 1 commit intoJun 26, 2026
Conversation
Why: Follow-up to meshcore-dev#247 / meshcore-dev#229. meshcore-dev#247 made orphaned repeater/client devices removable and, per review, refused contact/unknown devices while live to avoid leaving stale entity references. Two gaps remained: 1. A device whose entities were already removed was still refused while the contact is live, so an empty device shell could not be deleted (HA renders the rejection as an unhelpful "[object Object]" dialog). 2. The telemetry/GPS dedup maps (discovered_sensors / discovered_trackers) were never evicted when HA removed an entity, so the managers kept updating a deregistered entity and would not recreate it until a restart -- the same desync already patched in the untracked-contact-discard and data-only-demote flows. This allows removing a device that has no entities (the hub is excluded, identifier == entry_id), and registers an async_on_remove eviction on each telemetry sensor and GPS tracker so removal (UI entity delete, device-delete cascade, or platform unload) pops the manager's dedup key. The populated-device guards meshcore-dev#247 added (hub, subscribed repeater/client, live contact/unknown) are unchanged; the empty-device allowance is inserted ahead of them and is only reached when the device has no entities. Tests: - Unit (tests/test_device_removal.py): 3 added -- empty contact device removable while the contact is live, empty hub still refused, populated live contact still refused. Existing refusal/orphan guards retained via an autouse non-empty entity-count default (no inversions). Full unit tier: 216 passed. - Integration (tests_integration/test_dedup_eviction.py): new regression drives the real telemetry/GPS manager handlers under real Home Assistant, removes the entity via the entity registry, and asserts the dedup key is evicted -- riding the real async_on_remove lifecycle. Teeth-checked: fails without the eviction. Full integration tier: 14 passed. - ruff check / ruff format clean on the changed files.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #247 / #229. #247 made orphaned repeater/client devices removable from the HA UI and, per review, deliberately refused
contact/unknowndevices while the contact is still live — to avoid deleting a device while it is producing live data and leaving stale entity references behind. Two gaps remain:[object Object]dialog and the empty shell can't be removed. This is the entity-less half of Remove old devices in HA #229.TelemetrySensorManager.discovered_sensorsandDeviceTrackerManager.discovered_trackerscache the live entity objects by key. Nothing pops those caches when HA removes an entity (UI delete, device-delete cascade, or platform unload), so the manager keeps updating a deregistered entity and won't recreate it until an HA restart — the same desync already patched in the untracked-contact-discard (coordinator.py) and data-only-demote (services.py) flows; the UI-removal path just never reached either eviction.Changes
__init__.py(async_remove_config_entry_device): insert an early "device has no entities → allow removal" branch, excluding the hub (identifier == entry_id). The existing feat: allow removing orphan repeater and client devices from HA UI #247 guards (hub, subscribed repeater/client, live contact/unknown) are unchanged and still govern populated devices — the new branch is only reached whener.async_entries_for_device(..., include_disabled_entities=True)returns empty.telemetry_sensor.py/device_tracker.py: registerasync_on_remove(partial(self.discovered_*.pop, key, None))on each telemetry sensor and GPS tracker at the manager's creation site, so removal pops the dedup key and a later telemetry/GPS event recreates the entity instead of updating a deregistered one.Tests
tests/test_device_removal.py): 3 added — an emptied contact device is removable even while the contact is live; the hub stays refused even when empty; a populated live contact stays refused (the feat: allow removing orphan repeater and client devices from HA UI #247 guard, preserved). Existing refusal/orphan tests run under an autouse non-empty entity-count default, so feat: allow removing orphan repeater and client devices from HA UI #247's behavior is unchanged.tests_integration/test_dedup_eviction.py): new regression — drives the real telemetry/GPS manager handlers under real Home Assistant, removes the entity via the entity registry, and asserts the dedup key is evicted (riding the realasync_on_removelifecycle). Fails without the eviction.Compatibility / scope
entity_registryis already imported;async_on_removeis standard HA. No dependency or version bump; the populated-device guards are preserved.Files
custom_components/meshcore/__init__.py— entity-less-device removal allowance (hub-excluded).custom_components/meshcore/telemetry_sensor.py—async_on_removeeviction ofdiscovered_sensors.custom_components/meshcore/device_tracker.py—async_on_removeeviction ofdiscovered_trackers.tests/test_device_removal.py— empty-device unit coverage.tests_integration/test_dedup_eviction.py— eviction regression (new).