fix: handle multi-gang dimmer lights across all platforms (#681) - #683
Conversation
Multi-gang dimmers (2GANG/DIMMER/2) share one device id across their channels. get_by_id() returns a list when several lights resolve to the same id, so WiserLight.__init__ received a list instead of a single device and the light platform crashed during setup (0 lights). Key each entity by the unique per-channel light_id and resolve it via get_by_light_id(), which always returns a single object. Derive the (possibly shared) physical device id from the resolved light so device grouping still works. Base name and unique_id on the per-channel light so the two channels of a multi-gang dimmer no longer collide.
…, sensor (asantaga#681) The same shared-device-id problem that broke light.py affects every other platform that builds entities from lights. - select.py: get_by_id() returns a list for a multi-gang dimmer, so .available_modes raised and the whole select platform failed to set up (Mode / Power On Behaviour / LED Indicator selects for all devices were lost). Resolve the three light selects by the unique per-channel light_id and make name/unique_id per channel. - binary_sensor.py: the four light capability sensors collided on unique_id, dropping the second channel. Give WiserStateIsDimmable a light-aware __init__ and per-channel name so BaseBinarySensor stays generic. - switch.py: WiserLightAwayActionSwitch is per-channel now (light_id + per-channel name/unique_id). Device Lock and Identify act on the physical device, so they are emitted once per device id instead of once per channel. - sensor.py: signal strength is a property of the physical device, so the signal sensor is emitted once per device id. Verified on a hub with five 2-gang dimmers (12 light channels): before, the select platform crashed and the second channel lost its select/switch/binary sensor entities; after, every channel has its own entities and single-channel lights are unaffected.
|
Extended this PR beyond |
|
thank you , let me review the PR and then I'll merge it in.. Alas I dont have a V2 hub , or lights, so I'll just be doing a general review @msp1974 Mark, did u upgrade your hub? |
Will we break any existing integrations? If we will then yes we'll need the async shim, otherwise I dont think its an issue.. |
|
Yes — existing setups are affected. The per-channel entities (light, select, switch, binary_sensor) change from name-based to I ran into exactly this upgrading my own hub: 35 entities went stale (28 capability binary_sensors + 7 away-mode switches). The device-level entities I de-duplicated (Device Lock, Identify, Signal) keep their unique IDs, so those do not migrate — only the per-channel ones do. So yes, we need the shim. I'll add an |
switch.py began importing ENTITY_PREFIX from .const in the multi-gang fix (a4acfc6), but the module stub in test_switch_hot_water.py did not expose it, so loading the switch module raised ImportError and the whole test errored. Add ENTITY_PREFIX to the stub to restore the test.
…santaga#681) Keying light entities on the per-channel light_id instead of the physical device id fixes multi-gang dimmers, but light_id (hub Lighting section) and id (hub Devices section) are different id spaces even for single-gang lights. So the unique_ids of every light-derived entity change on update -- the light, its mode/LED/power-on selects, its away-mode switch and its four capability binary_sensors -- orphaning the existing entities and losing their history and dashboard references. Add build_light_unique_id_migration(), which rebuilds each pre-fix unique_id from live hub data and maps it to the new one, and run it from async_setup_entry (before the platforms create entities) via async_migrate_light_unique_ids(). The callback guards against a pre-existing target unique_id, since async_update_entity raises on collision and would otherwise abort config entry setup. Multi-gang dimmer channels are skipped: before the fix their light and select platforms crashed and their other entities collided on unique_id, so their pre-fix registry state is ambiguous; they are left to orphan. Adds tests/test_light_unique_id_migration.py covering the single-gang mapping, the multi-gang skip and the no-op filter.
|
Added the migration shim in Scope — it's a bit wider than my earlier comment. I traced the library:
The shim ( One deliberate limitation: multi-gang dimmer channels created before the fix are left to orphan. On a hub with a 2-gang dimmer, Testing: added |
Fixes #681.
Problem
With a multi-gang dimmer (
2GANG/DIMMER/2) both channels share the same deviceid. Theidproperty inaioWiserHeatAPIonly disambiguates2GANG/SWITCH/2(id * 1000 + endpoint), not2GANG/DIMMER/2, solights.get_by_id(id)matches more than one light and returns a list:This breaks every platform that builds entities from lights:
light.py—WiserLight.__init__stored the list inself._deviceandthe next line (
self._device.schedule) raised, aborting light setup (nolights at all).
select.py— same list,.available_modesraised → the whole selectplatform failed to set up (Mode / Power On Behaviour / LED Indicator selects
for all devices, not just lights, were lost).
binary_sensor.py,switch.py,sensor.py— no crash, but the secondchannel's entities collided on
unique_idand were dropped(
… does not generate unique IDs … already exists - ignoring): the four lightcapability sensors, the "Away Mode Turns Off" / "Device Lock" / "Identify"
switches, and the Signal sensor.
Fix
Two shapes, depending on whether the entity belongs to the channel or the
physical device:
Per-channel entities (independently controllable): key by the unique
per-channel
light_id, resolve vialights.get_by_light_id()(always returns asingle object), derive the shared physical
device_idfrom the resolved lightfor grouping, and base
name/unique_idon the per-channel light.light.py:WiserLight/WiserDimmableLightselect.py:WiserLightModeSelect,WiserLightPowerOnBehaviourSelect,WiserLightLedIndicatorSelectbinary_sensor.py:WiserStateIsDimmable(given a light-aware__init__sothe generic
BaseBinarySensorstays untouched for rooms/shutters)switch.py:WiserLightAwayActionSwitchDevice-level entities (one physical device, one entity): emit once per
physical device
idinstead of once per channel.switch.py: "Device Lock" and "Identify" (WiserDeviceSwitchloop)sensor.py: the Signal sensorEntity migration
_WiserLight.id(hub Devices section) andlight_id(hub Lighting section) aredifferent id spaces even for single-gang lights, so on upgrade the
unique_idof every per-channel light entity changes — the light, the mode/LED/power-on
selects, the away-mode switch and the four capability binary_sensors.
241f547addsbuild_light_unique_id_migration()(inhelpers.py), run fromasync_setup_entrybefore the platforms create entities, which rebuilds eachold
unique_idfrom live hub data and renames the matching registry entry tothe new
light_id-based one, soentity_ids, history and dashboards carry over.The rename callback checks
async_get_entity_idfirst, becauseasync_update_entityraises on a pre-existing target and would otherwise abortconfig-entry setup.
Multi-gang dimmer channels created before the fix are deliberately left to
orphan: their light/select platforms had crashed (nothing to migrate) and their
binary_sensor/switch entities were keyed on the shared device
id(one entityfor two channels), so there is no unambiguous target. Single-gang-only setups
migrate completely. The device-level dedupe (Device Lock, Identify, Signal)
keeps its
unique_ids and does not migrate.Testing
Tested on a live hub with five 2-gang dimmers (12 light channels) plus
single-channel lights, on integration
3.4.20withaioWiserHeatAPI==1.7.3.second channel of every dimmer lost its select / switch / binary-sensor
entities.
entity registry has a Mode select for all 12 channels (both channels of
every 2-gang dimmer), device-level switches/sensors appear once per device,
and single-channel lights are unaffected.
unique_idbuild_light_unique_id_migration()computes matches an existing entity.tests/test_light_unique_id_migration.py(single-gang mapping,multi-gang skip, no-op filter).
71cc6f7also repairstest_switch_hot_water.py,whose
wiser.conststub lacked theENTITY_PREFIXthis PR now imports inswitch.py. Fullunittestsuite green.