Skip to content

DeviceTemperature sensor applies a /100 divisor, but ZCL CurrentTemperature is whole degrees Celsius #787

Description

@zigpy-review-bot

The problem

The DeviceTemperature sensor hardcodes _divisor = 100:

https://github.com/zigpy/zha/blob/dev/zha/application/platforms/sensor/__init__.pyclass DeviceTemperature(Sensor) sets _divisor = 100, and the base Sensor applies value /= self._divisor.

But per ZCL R8 §3.4.2.2.1.1 (CurrentTemperature attribute):

The Current Temperature attribute is 16 bits in length and specifies the current internal temperature, in degrees Celsius, of the device. This attribute SHALL be specified in the range −200 to +200.

So CurrentTemperature (0x0000 on the Device Temperature Configuration cluster, 0x0002) is a signed int16 in whole degrees Celsius, with no scaling (range −200…+200 °C). The /100 divisor is therefore wrong for a spec-compliant device: one reporting 41 (= 41 °C) is displayed as 0.41 °C.

This has been masked by two things: a number of devices (mostly Xiaomi/Aqara) report centidegrees instead of the spec's whole degrees, and a body of existing zha-device-handlers quirks multiply current_temperature by 100 specifically to cancel this divisor. So the current default is "correct" only for devices that are themselves non-compliant or that have a compensating quirk — and it is silently wrong for every compliant device without one.

This is not a one-line fix

Flipping _divisor to 1 (spec-correct) would invert the breakage: every device currently relying on the divisor (native-centidegree devices, and the quirks below that pre-scale ×100) would then read 100× too high. Any change here has to be coordinated with the quirks side. The three buckets below are the full regression surface.

Bucket A — spec-compliant, no compensation → currently displayed 100× too low (the bug)

Raw current_temperature from tests/data/devices/ snapshots with quirk_applied: false (so these are unmodified device reports):

Device raw value ZHA displays
AEOTEC ZGA004 41 0.41 °C
LUMI lumi.plug.maeu01 29 0.29 °C
LUMI lumi.switch.b1laus01 20 0.20 °C
LUMI lumi.switch.b1naus01 23 0.23 °C
LUMI lumi.switch.b2nc01 24 0.24 °C
LUMI lumi.switch.l1aeu1 30 0.30 °C
LUMI lumi.switch.l2aeu1 30 0.30 °C
LUMI lumi.switch.n1aeu1 28 0.28 °C
frient A/S SPLZB-141 (ep 2) 32 0.32 °C

Bucket B — existing quirks that already multiply current_temperature ×100 to cancel the divisor

These would need the ×100 removed if the core divisor is fixed (otherwise they'd read 100× too high):

Quirk file Mechanism Devices covered
zhaquirks/sinope/__init__.py (CustomDeviceTemperatureCluster, used by sinope/switch.py + sinope/light.py) _update_attribute: value * 100 Sinope load controllers / switches / dimmers (e.g. RM3250ZB, RM3500ZB, SW2500ZB, SW2500ZB-G2, …)
zhaquirks/xiaomi/__init__.py (XiaomiCluster, ~line 272) aggregate-report handler writes attributes[TEMPERATURE] * 100 into device_temperature broad Xiaomi/Aqara set that routes the 0xFF01 manufacturer report through XiaomiCluster
zhaquirks/xiaomi/aqara/airm_fhac01.py (CustomDeviceTemperature) _update_attribute: value * 100 LUMI lumi.airm.fhac01 (air-quality monitor)
zhaquirks/tuya/ts0601_rcbo.py (TuyaRCBODeviceTemperature) DP converter lambda x: x * 100 Tuya TS0601 RCBO breaker

(Note the airm comment literally reads "The device reports temperature divided by 100, so multiply by 100" — i.e. the author was compensating for this divisor.)

Bucket C — native-centidegree devices with no quirk → currently correct by accident

Raw values (quirk_applied: false) that happen to match the /100 divisor; these would need a new compensating quirk if the divisor were removed:

Device raw value ZHA displays
LUMI lumi.ctrl_neutral2 2300 23 °C
LUMI lumi.switch.b1lacn02 1700 17 °C
LUMI lumi.switch.b2naus01 1400 14 °C

That the same vendor (Xiaomi) appears in both Bucket A (whole degrees) and Bucket C (centidegrees) is the core difficulty — there is no single divisor correct for the whole fleet.

Anomalies: Namron 4512785 reports 352 (→ 3.52 °C with the divisor; fits neither whole nor centidegrees cleanly). Develco ZHEMI and aqara lumi.sensor_occupy.agl1 (FP1E) report None.

Why this surfaced now

Quirk PR zigpy/zha-device-handlers#5023 (Heiman HS1RM-E) adds yet another DeviceTemperature cluster that multiplies current_temperature by 100 purely to cancel this /100. That's the de-facto pattern for spec-compliant devices today (see Bucket B), but it means every compliant device needs a quirk to display a spec-defined attribute correctly.

Possible directions (for discussion, not prescriptive)

  • Make the divisor device-overridable (e.g. settable from a quirk declaratively) so neither population needs a custom cluster + _update_attribute, and the core default can stay where it is during migration.
  • Flip the core default to _divisor = 1 (spec-correct), remove the ×100 from the Bucket B quirks, and add quirks for the Bucket C devices. Spec-aligned but the largest blast radius.
  • Status quo: keep compensating compliant devices in quirks (means every correct device needs one).

Caveat on the evidence: values are read from ZHA's tests/data/devices/ snapshots. For quirk_applied: true devices a cached value can be post-quirk, so Buckets A and C are drawn only from quirk_applied: false snapshots to keep them unambiguous; Bucket B is sourced from the quirk code itself.

cc @TheJulianJES

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