Skip to content

Device diagnostics round-trip fails for quirks with a manufacturer-specific attribute sharing a ZCL attribute's ID #798

Description

@zigpy-review-bot

Summary

When a quirk defines a manufacturer-specific attribute that reuses the same attribute ID as a standard ZCL attribute (distinguished only by manufacturer_code), the device-diagnostics snapshot for that device can no longer round-trip: tests/test_discover.py::test_devices_from_files fails its assert loaded_device_data == device_data check. This affects both newly regenerated snapshots and existing ones once the quirk becomes active.

Concrete example

zigpy/zha-device-handlers#5082 (Parse manufacturer-framed metering reports on Innr SP 120) adds, on the smartenergy_metering cluster (0x0702):

class AttributeDefs(Metering.AttributeDefs):
    # standard ZCL current_summ_delivered is id=0x0000 (no manufacturer code)
    current_summ_delivered_mfg = ZCLAttributeDef(
        id=0x0000,            # same ID as ZCL current_summ_delivered
        type=t.uint48_t,
        manufacturer_code=0x1166,
    )

The quirk mirrors the manufacturer-framed report onto the standard current_summ_delivered (also id 0x0000).

What happens

Regenerating tests/data/devices/innr-sp-120.json with this quirk installed produces a snapshot where:

  • the summation_delivered sensor's serialized state is 145.26 (the mirrored value), but
  • the metering cluster's serialized cached attributes contain no 0x0000 entry at all — the source value is dropped.

Reloading that snapshot therefore can't reproduce the state (the mirror has no source value to copy → None), so the round-trip assertion fails:

assert loaded_device_data == device_data
# zha_lib_entities ... summation_delivered: state None  !=  state 145.26

This is not a regen artifact — dev's existing innr-sp-120.json also fails the round-trip once the quirk is installed. So when zha bumps zha-quirks to a version including the PR, this snapshot's test breaks regardless of regeneration.

Root cause

The attribute cache and its (de)serialization are keyed by attribute ID, so two attributes sharing id 0x0000 (one ZCL, one manufacturer-specific) collapse to a single slot that can't be faithfully represented or restored. Relevant code paths:

  • round-trip assertion — tests/test_discover.py (assert loaded_device_data == device_data)
  • rebuild from snapshot — tests/common.py zigpy_device_from_device_datareal_cluster.find_attribute(attr_name)
  • name/ID resolution — zigpy zigpy/zcl/__init__.py find_attributes (cls.attributes_by_name[name_or_id])

(The same ID/name resolution fragility surfaces elsewhere as KeyErrors during regeneration when an attribute name on a quirked cluster no longer matches a cached name.)

Impact

Any quirk that legitimately models a manufacturer-framed variant of a standard attribute at the same ID currently can't have a valid diagnostics snapshot, which blocks CI coverage for such devices.

Possible directions

  • Key the diagnostics attribute serialization by (ID, manufacturer_code) rather than ID alone, so co-located attributes round-trip.
  • Or have the serializer/round-trip tolerate derived state whose source is a manufacturer-specific attribute at a shared ID.

Context PR: zigpy/zha-device-handlers#5082

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed bug

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions