feat(zigbee2mqtt): 2.13.0 and debounce the WS90 report burst #559

Merged
k.renner merged 1 commits from worktree-z2m-2.13.0-ws90-debounce into master 2026-08-23 20:06:51 +00:00
Member

Upgrades Zigbee2MQTT to 2.13.0 and sets debounce: 2 on the Wetterstation (Ecowitt WS90, 0xc02cedfffe958f40). Both target the station's recurring zero-drop glitch, from opposite ends.

2.13.0

Carries zigbee-herdsman-converters#12748, which discards the ZCL non-value markers (0xffff/0xff/0xffffff) the WS90 emits when a reading is unavailable. Without it those markers are scaled and published as measurements — the 6553 m/s gust readings in Koenkk/zigbee2mqtt#31048. The release also recovers the rain rate after a counter reset, the same lifetime-counter dip packages/wetterstation.yaml compensates for on the HA side.

Only breaking change is dropping Node.js 20 for bare-metal installs; the container ships its own runtime. The amd64 manifest was verified pullable before commit.

debounce

2.13.0 does not fix the zero drops — #31048 is still open, and the author of #12748 says so explicitly: "a zero can be a real reading for these attributes, only the all-bits-set markers are provably invalid."

The station reports as a burst of seven per-cluster ZCL frames (verified in the logs: consecutive transactionSequenceNumber 0→6), and Z2M publishes its full cumulative state once per frame — one device report becomes seven MQTT messages. During a glitch the zeros appear progressively across those seven, and that is what makes the corruption unrecognisable downstream:

{"temperature":13.6,"humidity":92,"pressure":96.9,...}   <- last good
{"temperature":0,   "humidity":92,"pressure":96.9,...}   <- indistinguishable from a winter reading
{"temperature":0,   "humidity":92,"pressure":0,   ...}
{"temperature":0,   "humidity":0, "pressure":0,   ...}   <- only this one is recognisable

The second frame is internally consistent — Z2M recomputes dew_point from the false temperature — so no per-message rule can reject it. Merging the burst yields a single frame carrying the all-zero signature, which a consumer can reject.

Choosing 2

Bounded on both sides:

  • Above — the device's own reporting configuration is minRepIntval 10 / maxRepIntval 3600 on all seven WS90 clusters (1024/1026/1027/1029, 0xfc01/0xfc02/0xfc03), so no attribute repeats inside 10s.
  • Below — the recovery burst straddles a second boundary, so 1s splits it and emits a partial frame.

Staggering was checked rather than assumed: the 10s floor is per attribute and nine attributes report across those clusters, so a trailing-edge timer could in principle keep resetting and never flush. Over 6h of publishes the longest unbroken chain of sub-2s gaps was 5, bounding worst-case deferral under ~10s.

Reviewer notes

  • zigbee2mqtt-config.yaml is seed-only — a running Z2M owns its configuration.yaml on the PVC, so this does not change the live instance, it only makes a fresh PVC come up correctly.
  • debounce is requiresRestart. Applying it live needs the option set on the running instance; the image bump supplies the restart.
  • The WS90 was absent from the seeded device list (which predates it joining), so this adds its friendly_name too.
  • Both manifests pass kubectl apply --dry-run=server.

This is step 1 of 2. It collapses each glitch episode from ~14 dirty messages to 1, but that one all-zero frame still reaches HA. Rejecting it is a follow-up on the Home Assistant side.

🤖 Generated with Claude Code

Upgrades Zigbee2MQTT to 2.13.0 and sets `debounce: 2` on the Wetterstation (Ecowitt WS90, `0xc02cedfffe958f40`). Both target the station's recurring zero-drop glitch, from opposite ends. ## 2.13.0 Carries `zigbee-herdsman-converters#12748`, which discards the ZCL non-value markers (`0xffff`/`0xff`/`0xffffff`) the WS90 emits when a reading is unavailable. Without it those markers are scaled and published as measurements — the 6553 m/s gust readings in Koenkk/zigbee2mqtt#31048. The release also recovers the rain rate after a counter reset, the same lifetime-counter dip `packages/wetterstation.yaml` compensates for on the HA side. Only breaking change is dropping Node.js 20 for bare-metal installs; the container ships its own runtime. The amd64 manifest was verified pullable before commit. ## debounce 2.13.0 does **not** fix the zero drops — #31048 is still open, and the author of #12748 says so explicitly: *"a zero can be a real reading for these attributes, only the all-bits-set markers are provably invalid."* The station reports as a burst of seven per-cluster ZCL frames (verified in the logs: consecutive `transactionSequenceNumber` 0→6), and Z2M publishes its full cumulative state once per frame — one device report becomes seven MQTT messages. During a glitch the zeros appear progressively across those seven, and that is what makes the corruption unrecognisable downstream: ``` {"temperature":13.6,"humidity":92,"pressure":96.9,...} <- last good {"temperature":0, "humidity":92,"pressure":96.9,...} <- indistinguishable from a winter reading {"temperature":0, "humidity":92,"pressure":0, ...} {"temperature":0, "humidity":0, "pressure":0, ...} <- only this one is recognisable ``` The second frame is internally consistent — Z2M recomputes `dew_point` from the false temperature — so no per-message rule can reject it. Merging the burst yields a single frame carrying the all-zero signature, which a consumer *can* reject. ## Choosing 2 Bounded on both sides: - **Above** — the device's own reporting configuration is `minRepIntval 10` / `maxRepIntval 3600` on all seven WS90 clusters (1024/1026/1027/1029, 0xfc01/0xfc02/0xfc03), so no attribute repeats inside 10s. - **Below** — the recovery burst straddles a second boundary, so 1s splits it and emits a partial frame. Staggering was checked rather than assumed: the 10s floor is *per attribute* and nine attributes report across those clusters, so a trailing-edge timer could in principle keep resetting and never flush. Over 6h of publishes the longest unbroken chain of sub-2s gaps was 5, bounding worst-case deferral under ~10s. ## Reviewer notes - `zigbee2mqtt-config.yaml` is **seed-only** — a running Z2M owns its `configuration.yaml` on the PVC, so this does not change the live instance, it only makes a fresh PVC come up correctly. - `debounce` is `requiresRestart`. Applying it live needs the option set on the running instance; the image bump supplies the restart. - The WS90 was absent from the seeded device list (which predates it joining), so this adds its `friendly_name` too. - Both manifests pass `kubectl apply --dry-run=server`. This is step 1 of 2. It collapses each glitch episode from ~14 dirty messages to 1, but that one all-zero frame still reaches HA. Rejecting it is a follow-up on the Home Assistant side. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-08-23 20:03:54 +00:00
feat(zigbee2mqtt): 2.13.0 and debounce the WS90 report burst
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
91e55d3b56
Two changes to the Wetterstation (Ecowitt WS90, 0xc02cedfffe958f40), both
aimed at the station's recurring zero-drop glitch.

2.13.0 carries zigbee-herdsman-converters#12748, which discards the ZCL
non-value markers (0xffff / 0xff / 0xffffff) the WS90 emits when a reading
is unavailable. Without it those markers are scaled and published as
measurements — the 6553 m/s gust readings in Koenkk/zigbee2mqtt#31048. The
release also recovers the rain rate after a counter reset, which is the
same lifetime-counter dip that packages/wetterstation.yaml compensates for
on the Home Assistant side. Its only breaking change is dropping Node.js 20
for bare-metal installs; the container ships its own runtime. The amd64
manifest was verified pullable before this commit.

debounce: 2 addresses the part 2.13.0 does not fix. Koenkk/zigbee2mqtt#31048
is still open, and the author of #12748 states explicitly that it does not
cover the zero drops: "a zero can be a real reading for these attributes,
only the all-bits-set markers are provably invalid."

The station reports as a burst of seven per-cluster ZCL frames and Z2M
publishes its full cumulative state once per frame, so one device report
becomes seven MQTT messages. During a glitch the zeros appear progressively
across those seven, which is what makes the corruption unrecognisable
downstream: an intermediate frame carrying {"temperature":0,"humidity":92,
"pressure":96.9} is internally consistent (Z2M recomputes dew_point from the
false temperature) and cannot be told apart from a legitimate winter
reading. Merging the burst yields a single frame with the all-zero
signature, which a consumer can reject.

The window is bounded on both sides. Above: the device's own reporting
configuration sets minRepIntval 10 / maxRepIntval 3600 on all seven WS90
clusters (1024/1026/1027/1029 and 0xfc01/0xfc02/0xfc03), so no attribute
repeats inside 10s. Below: the recovery burst straddles a second boundary,
so 1s splits it and emits a partial frame.

Staggering was checked rather than assumed — the 10s floor is per attribute,
and nine attributes report across those clusters, so a trailing-edge timer
could in principle keep resetting and never flush. Over 6h the longest
unbroken chain of sub-2s gaps was 5, bounding worst-case deferral under
~10s.

The device is also absent from the seeded device list, which predates it
joining, so this adds the friendly_name too.

NOTE: this file is seed-only. A running Z2M owns its configuration.yaml on
the PVC, so this does not change the live instance — it only makes a fresh
PVC come up correctly. debounce is requiresRestart, so applying it live
needs the option set on the running instance and a restart; the image bump
supplies the restart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
review-bot left a comment
Member

🤖 Automated review — model claude-haiku-4-5-20251001 (complexity: trivial), commit 91e55d3b5698a490445bd1c86b63e97410e75309.

Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.


Summary

This PR upgrades Zigbee2MQTT from 2.12.1 to 2.13.0 and adds debounce configuration for the Ecowitt WS90 weather station to mitigate a firmware quirk that periodically drops all readings to zero. The changes are well-documented with clear reasoning for the chosen debounce value (2 seconds), and the configuration properly respects the seed-only nature of the ConfigMap.

🟢 Nits

  1. The comment block in zigbee2mqtt-config.yaml is extensive (35 lines), but given the non-obvious complexity of the debounce logic and the need to prevent future readers from changing the value without understanding the constraints, the length is justified. The explanation includes bounded reasoning on both sides (10s minimum from device config, 1s upper bound from burst window) and production validation data, making it a useful reference for future maintenance.

Tests

The change is infrastructure configuration rather than code, so manual validation is appropriate. The PR author reports:

  • Verified the amd64 image manifest is pullable before commit
  • Validated kubectl apply --dry-run=server on both manifests
  • Measured actual device behavior over 6 hours to bound worst-case debounce deferral under 10s

The configuration will only affect new PVC seeding; the running instance requires an explicit restart to adopt the new debounce setting (marked requiresRestart in Z2M). This is a reasonable operational expectation.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `claude-haiku-4-5-20251001` (complexity: trivial), commit `91e55d3b5698a490445bd1c86b63e97410e75309`. > Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review. --- ## Summary This PR upgrades Zigbee2MQTT from 2.12.1 to 2.13.0 and adds debounce configuration for the Ecowitt WS90 weather station to mitigate a firmware quirk that periodically drops all readings to zero. The changes are well-documented with clear reasoning for the chosen debounce value (2 seconds), and the configuration properly respects the seed-only nature of the ConfigMap. ## 🟢 Nits 1. The comment block in `zigbee2mqtt-config.yaml` is extensive (35 lines), but given the non-obvious complexity of the debounce logic and the need to prevent future readers from changing the value without understanding the constraints, the length is justified. The explanation includes bounded reasoning on both sides (10s minimum from device config, 1s upper bound from burst window) and production validation data, making it a useful reference for future maintenance. ## Tests The change is infrastructure configuration rather than code, so manual validation is appropriate. The PR author reports: - Verified the amd64 image manifest is pullable before commit - Validated `kubectl apply --dry-run=server` on both manifests - Measured actual device behavior over 6 hours to bound worst-case debounce deferral under 10s The configuration will only affect new PVC seeding; the running instance requires an explicit restart to adopt the new debounce setting (marked `requiresRestart` in Z2M). This is a reasonable operational expectation. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=91e55d3b5698a490445bd1c86b63e97410e75309 -->
k.renner merged commit 1ce3f449d4 into master 2026-08-23 20:06:51 +00:00
Sign in to join this conversation.