Splitting this out of #656 because the bug there is confirmed and the open part is a decision: where should it be fixed — here in ZHA, or in the Home Assistant Core ZHA integration? I've prepared both fixes as branches so they can be compared directly. Happy to fold this back into #656 if you'd rather keep it in one place.
The bug (recap of #656)
A device_config platform override in HA's YAML is silently ignored unless the IEEE address is written in lower case:
zha:
device_config:
A4:C1:38:64:XX:XX:XX:XX-1: # ignored
type: switch
a4:c1:38:64:XX:XX:XX:XX-1: # works
type: switch
The cause is that the two sides of the config never agree on a canonical form. HA validates the key as a free-form cv.string and passes it through verbatim into ZHAConfiguration.device_overrides (homeassistant/components/zha/__init__.py, helpers.py::create_zha_config), while ZHA looks it up as f"{device.ieee}-{endpoint.id}" in zha/application/discovery.py — and str(EUI64) is always lower case. A mismatched key is not an error anywhere: the override is just dropped, with no log line and no config error, so the user sees "my override does nothing".
Users hit this because IEEE addresses are commonly displayed in upper case, so a copy-paste produces a key that never matches. It has come up often enough that the docs carry an explicit warning, which was just reworded again because people read it as descriptive rather than as an instruction: home-assistant/home-assistant.io#47347 (and the thread it links, https://community.home-assistant.io/t/zigbee-modifying-the-device-type/349830/20).
Option A — fix it here, in ZHA
Branch: dev...zigpy-bot/device-override-ieee-case-insensitive
discover_entities_for_endpoint matches override keys case-insensitively. It is a ~6 line change plus a test, at the only place that reads device_overrides.
- Fixes it for every consumer of the library, not just HA.
- Lookup time rather than config-construction time is deliberate:
device_overrides is a plain dict that HA (and the tests) assign wholesale after ZHAConfiguration is constructed, so normalizing in __post_init__ would be bypassed.
- Downside: the library can only ever be lenient. It still cannot tell a user why an override did nothing — a typo'd endpoint ID, a wrong-length address or a bogus
type: stays a silent no-op.
Option B — fix it in HA Core, at the YAML schema
Branch: home-assistant/core@dev...TheJulianJES:core:zigpy-bot/zha-device-config-key-normalization
A key validator replaces the bare cv.string, canonicalizing <ieee_address>-<endpoint_id> via EUI64.convert() and rejecting anything that is not a valid pair.
- HA owns the user-facing config format, so this is arguably where the canonicalization belongs, and it is the only layer that can turn a broken key into a visible config error instead of a silent no-op.
- It also catches the neighbouring silent failures: a wrong-length address, a non-numeric or out-of-range endpoint ID, and two keys that normalize to the same endpoint (which would otherwise let one entry quietly overwrite the other).
- Downside: HA-only, and it is a (mild) breaking change — a malformed key that was previously accepted-and-ignored now fails validation of the whole
zha: section at startup. Since every newly rejected key was already a no-op, no working configuration can break, but it needs a note on the PR.
The two compose fine if you want both: the schema normalizes before the library's lookup ever sees the key.
Option C — neither
discovery.py carries a TODO: deprecate device platform overrides (the only remaining use case is swapping light / switch for devices with an incorrect device type). If the plan is to remove the feature, doing nothing beyond the docs reword is defensible too.
Which would you prefer?
@TheJulianJES and the other maintainers — I'd like a steer on A, B, C or A+B before opening any PR. My own lean is B (or A+B), because the silent-drop is the actual usability problem and only the schema layer can make it loud, but this is a judgement call about where you want the config contract enforced.
Two related things noticed while writing the branches, worth splitting off if you'd rather not bundle them:
create_zha_config does override_data["type"], but the schema has vol.Optional(CONF_TYPE) — a device_config entry without type: raises KeyError during setup rather than a config error.
type: is validated as cv.string, while DeviceOverridesConfiguration.type is typed Platform. A value that isn't a real platform (type: lightt) is accepted and then silently matches nothing.
Splitting this out of #656 because the bug there is confirmed and the open part is a decision: where should it be fixed — here in ZHA, or in the Home Assistant Core ZHA integration? I've prepared both fixes as branches so they can be compared directly. Happy to fold this back into #656 if you'd rather keep it in one place.
The bug (recap of #656)
A
device_configplatform override in HA's YAML is silently ignored unless the IEEE address is written in lower case:The cause is that the two sides of the config never agree on a canonical form. HA validates the key as a free-form
cv.stringand passes it through verbatim intoZHAConfiguration.device_overrides(homeassistant/components/zha/__init__.py,helpers.py::create_zha_config), while ZHA looks it up asf"{device.ieee}-{endpoint.id}"inzha/application/discovery.py— andstr(EUI64)is always lower case. A mismatched key is not an error anywhere: the override is just dropped, with no log line and no config error, so the user sees "my override does nothing".Users hit this because IEEE addresses are commonly displayed in upper case, so a copy-paste produces a key that never matches. It has come up often enough that the docs carry an explicit warning, which was just reworded again because people read it as descriptive rather than as an instruction: home-assistant/home-assistant.io#47347 (and the thread it links, https://community.home-assistant.io/t/zigbee-modifying-the-device-type/349830/20).
Option A — fix it here, in ZHA
Branch: dev...zigpy-bot/device-override-ieee-case-insensitive
discover_entities_for_endpointmatches override keys case-insensitively. It is a ~6 line change plus a test, at the only place that readsdevice_overrides.device_overridesis a plain dict that HA (and the tests) assign wholesale afterZHAConfigurationis constructed, so normalizing in__post_init__would be bypassed.type:stays a silent no-op.Option B — fix it in HA Core, at the YAML schema
Branch: home-assistant/core@dev...TheJulianJES:core:zigpy-bot/zha-device-config-key-normalization
A key validator replaces the bare
cv.string, canonicalizing<ieee_address>-<endpoint_id>viaEUI64.convert()and rejecting anything that is not a valid pair.zha:section at startup. Since every newly rejected key was already a no-op, no working configuration can break, but it needs a note on the PR.The two compose fine if you want both: the schema normalizes before the library's lookup ever sees the key.
Option C — neither
discovery.pycarries aTODO: deprecate device platform overrides(the only remaining use case is swappinglight/switchfor devices with an incorrect device type). If the plan is to remove the feature, doing nothing beyond the docs reword is defensible too.Which would you prefer?
@TheJulianJES and the other maintainers — I'd like a steer on A, B, C or A+B before opening any PR. My own lean is B (or A+B), because the silent-drop is the actual usability problem and only the schema layer can make it loud, but this is a judgement call about where you want the config contract enforced.
Two related things noticed while writing the branches, worth splitting off if you'd rather not bundle them:
create_zha_configdoesoverride_data["type"], but the schema hasvol.Optional(CONF_TYPE)— adevice_configentry withouttype:raisesKeyErrorduring setup rather than a config error.type:is validated ascv.string, whileDeviceOverridesConfiguration.typeis typedPlatform. A value that isn't a real platform (type: lightt) is accepted and then silently matches nothing.