Skip to content

device_config overrides silently ignored for upper case IEEE addresses — fix in ZHA or in HA Core? #866

Description

@zigpy-review-bot

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions