Skip to content

Fix quirks v2 entity non-values#758

Draft
TheJulianJES wants to merge 3 commits intozigpy:devfrom
TheJulianJES:tjj/fix-quirks-v2-sensor-non-value
Draft

Fix quirks v2 entity non-values#758
TheJulianJES wants to merge 3 commits intozigpy:devfrom
TheJulianJES:tjj/fix-quirks-v2-sensor-non-value

Conversation

@TheJulianJES
Copy link
Copy Markdown
Contributor

@TheJulianJES TheJulianJES commented May 4, 2026

Proposed change

Move super().__init__() in the Sensor class, so _attribute_name is set by the base class before we set _attr_def based on that in the Sensor class.

TODO:

  • Maybe clean up comments a bit
  • Review test / switch to using Frient sensor device from diagnostics
  • Add test coverage for the now missed line of if attr_def is None in _is_non_value
  • Check if other classes/entities are also affected

AI summary

ZCL numeric measurement attributes use a sentinel value to mean "no measurement available" (e.g. 0xFFFF for uint16, -32768 for int16). Sensor._is_non_value is supposed to map those to None so the entity reports as Unknown in HA. For specialized sensor classes (Temperature, Humidity, etc.) this works, but for quirks v2 entities — which use the generic sensor.Sensor class — the sentinel was leaking through as the raw integer state.

Reported via diagnostics on a frient AQSZB-110: the device's humidity sensor (standard RelativeHumidity cluster, specialized Humidity class) correctly reported None when the measurement was unavailable, but its VOC sensor (custom develco_voc_level cluster 0xfc03, generic Sensor class via quirks v2) reported 65535 instead of None.

Root cause

Sensor.__init__ looked up the attribute definition before calling super().__init__():

if self._attribute_name is not None:
    self._attr_def = self._cluster_handler.cluster.find_attribute(self._attribute_name)

super().__init__(cluster_handlers, endpoint, device, **kwargs)

For specialized subclasses, _attribute_name = "measured_value" is a class attribute, so this works. For quirks v2 entities, _attribute_name is set inside _init_from_quirks_metadata, which runs from super().__init__() — i.e. after the lookup. So _attr_def stayed None, and _is_non_value short-circuited to False (no attr def → can't know the type's non_value), letting the sentinel through.

Fix

Move the find_attribute lookup after super().__init__() so _attribute_name is resolved (whether from a class attribute or from quirks v2 metadata) before we use it.

Tests

  • test_ignore_non_value_quirks_v2 — builds a quirks v2 humidity sensor via QuirkBuilder, sends 0xFFFF as the measured value, asserts the entity state is None. Confirmed to fail without the fix.

For quirks v2 entities, `Sensor._attribute_name` is set by
`_init_from_quirks_metadata`, which runs inside `super().__init__()`.
The attribute definition lookup ran before `super().__init__()`, so
`_attr_def` stayed `None` and `_is_non_value` always returned `False`,
letting ZCL non-value sentinels (e.g. 0xFFFF for uint16) through as
the entity state instead of being mapped to `None`.

Move the `find_attribute` lookup after `super().__init__()` so it
works for both class-attribute defined `_attribute_name` (specialized
sensors like Humidity/Temperature) and quirks v2 entities.
Verifies that a quirks v2 sensor (using the generic `sensor.Sensor`
class with `_attribute_name` set via metadata) maps the ZCL uint16
non-value sentinel (0xFFFF) to `None` rather than reporting it as
the raw state.
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.63%. Comparing base (f443146) to head (99efd3f).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #758      +/-   ##
==========================================
- Coverage   97.64%   97.63%   -0.01%     
==========================================
  Files          62       62              
  Lines       10873    10873              
==========================================
- Hits        10617    10616       -1     
- Misses        256      257       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

self._attribute_name
)

super().__init__(cluster_handlers, endpoint, device, **kwargs)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't rely on self._attr_def anywhere within __init__, right?

Copy link
Copy Markdown
Contributor Author

@TheJulianJES TheJulianJES May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's only assigned there and then used exclusively in _is_non_value. Are you suggesting to always re-find the attribute definition in _is_non_value instead? (and it's not used in the base class at all.)

(Side note: I think at some point in the future, we may want to (and be able to?) provide ZCL entities directly with the attribute definition, instead of a string?)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, just checking. I remember there being some reason for __init__ not being at the very top of the overriden __init__ but I think we're refactored that reason away at some point in the past.

Match the pattern used by `test_ignore_non_value` and
`test_ignore_nan_value` (load a real device via `zigpy_device_from_json`)
instead of building a synthetic quirks v2 entity inline. The fixture
exercises the actual `develco_voc_level` cluster (0xfc03) — the same
device and code path that triggered the original report.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants