Fix quirks v2 entity non-values#758
Conversation
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 Report✅ All modified and coverable lines are covered by tests. 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. 🚀 New features to boost your workflow:
|
| self._attribute_name | ||
| ) | ||
|
|
||
| super().__init__(cluster_handlers, endpoint, device, **kwargs) |
There was a problem hiding this comment.
We don't rely on self._attr_def anywhere within __init__, right?
There was a problem hiding this comment.
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?)
There was a problem hiding this comment.
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.
Proposed change
Move
super().__init__()in theSensorclass, so_attribute_nameis set by the base class before we set_attr_defbased on that in theSensorclass.TODO:
if attr_def is Nonein_is_non_valueAI summary
ZCL numeric measurement attributes use a sentinel value to mean "no measurement available" (e.g.
0xFFFFforuint16,-32768forint16).Sensor._is_non_valueis supposed to map those toNoneso the entity reports as Unknown in HA. For specialized sensor classes (Temperature, Humidity, etc.) this works, but for quirks v2 entities — which use the genericsensor.Sensorclass — the sentinel was leaking through as the raw integer state.Reported via diagnostics on a frient AQSZB-110: the device's humidity sensor (standard
RelativeHumiditycluster, specializedHumidityclass) correctly reportedNonewhen the measurement was unavailable, but its VOC sensor (customdevelco_voc_levelcluster0xfc03, genericSensorclass via quirks v2) reported65535instead ofNone.Root cause
Sensor.__init__looked up the attribute definition before callingsuper().__init__():For specialized subclasses,
_attribute_name = "measured_value"is a class attribute, so this works. For quirks v2 entities,_attribute_nameis set inside_init_from_quirks_metadata, which runs fromsuper().__init__()— i.e. after the lookup. So_attr_defstayedNone, and_is_non_valueshort-circuited toFalse(no attr def → can't know the type's non_value), letting the sentinel through.Fix
Move the
find_attributelookup aftersuper().__init__()so_attribute_nameis 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 viaQuirkBuilder, sends0xFFFFas the measured value, asserts the entity state isNone. Confirmed to fail without the fix.