private@homelab: ~/latest
local-first guides · privacy-aware · no noisy tracking
private@homelab:~$ cat guides/article.md
· ·
6–10 minutes
read

Zigbee2MQTT Battery Device Config Fails? It’s Asleep

A Zigbee2MQTT config change on a battery device reverts because it was asleep. The parent holds it 7.68 seconds. How to land the command first time.

You change a reporting interval on a battery sensor, hit save, and the frontend reverts it a few seconds later. Or you try to bind a battery-powered remote to a light group and the request just times out. The value snaps back, nothing obvious appears in the log, and the configuration you typed looks exactly as reasonable as it did a minute ago.

If this only happens on battery-powered devices, and never on your mains-powered switches or bulbs, the cause isn’t your configuration. The device was asleep when Zigbee2MQTT tried to deliver the command.

Why the change doesn’t stick

Battery-powered Zigbee devices — sleepy end devices, in the protocol’s general vocabulary rather than Zigbee2MQTT’s own — spend almost all their time in a low-power state so a coin cell can last a year or more. They wake briefly, check in with their parent router or the coordinator, then go back to sleep. Outside that window the radio isn’t listening.

Zigbee2MQTT’s documentation does tell you about this, in both places it matters. The binding guide notes that binding a battery-powered device usually fails because it’s asleep, and that the fix is to wake it — press a button on a remote, trip a motion sensor — before sending the request. The MQTT topics reference carries the same instruction for reporting configuration, on the same page that documents the endpoint: if configure reporting fails on a battery device, “wake it up right before sending the command.”

So the instruction isn’t hidden. What the docs don’t give you is the part that makes it actionable — why waking the device works, how much time you actually have, and what happens to the command if you miss. That last one is the genuine gap, and it took an open discussion thread to state it plainly: Zigbee2MQTT does not queue these commands. Send a configure_reporting request while the device is asleep and it fails outright, with no attempt to reapply it when the device next wakes. The frontend showing your value reverting is what that looks like from the outside, which is why it reads as a rejected input rather than an undelivered one.

What actually happens to the command

The command isn’t thrown away the instant it’s sent, which is the detail that makes the workaround make sense. Zigbee already has a mechanism for talking to devices that aren’t listening: the parent holds the message on the child’s behalf and waits for the child to come and get it. End devices don’t receive unsolicited traffic — they poll their parent, and anything queued is handed over on that poll.

The catch is how long the parent is willing to wait. On Silicon Labs’ Zigbee stack the indirect transmission timeout defaults to 7.68 seconds, and a sleepy child is expected to poll faster than that if it wants to reliably receive incoming messages. A device that only transmits when something happens to it — a door opens, motion is detected, a button is pressed — can go minutes or hours between polls, so a message queued at the parent expires long before it’s collected.

That’s the whole failure. Not an application that forgets to try, but a protocol-level buffer with a short fuse and a child that shows up too late. Zigbee2MQTT’s contribution to the problem is that once that buffer expires, nothing above it tries again.

Not the same thing as the 25-hour check-in

This is worth separating from something adjacent in the docs that’s easy to conflate with it. Zigbee2MQTT’s device-availability feature treats passive (battery) devices as available as long as they’ve checked in within roughly 25 hours, against 10 minutes for active mains-powered devices. That setting governs whether the UI marks a device offline. It says nothing about whether a command sent right now will reach it.

A battery sensor can show as perfectly available under the 25-hour rule and still drop your reporting-configuration change because it happened to be asleep at that moment.

How to tell it failed, instead of guessing

Before reaching for the workaround, get yourself a real signal. A reverting frontend field is a symptom, not a diagnosis — it looks identical whether the device was asleep or your payload was malformed.

Reporting configuration goes through Zigbee2MQTT’s bridge request interface, and bridge requests answer. Publish to zigbee2mqtt/bridge/request/device/reporting/configure and Zigbee2MQTT replies on the matching zigbee2mqtt/bridge/response/... topic with the values it applied and a status of ok or error. Subscribe to the response topic before you publish. This is different from a device’s /set topic, which gives you no acknowledgement at all, as our MQTT topics and attribute output explainer covers.

Note · topic names

An older topic, zigbee2mqtt/bridge/request/device/configure_reporting, is kept as a deprecated alias and still works, so a snippet you find on a forum using it isn’t wrong, just dated. Prefer zigbee2mqtt/bridge/request/device/reporting/configure in anything you write yourself. Our reporting configuration explainer covers the payload fields and what each interval does.

An error status with the device unreachable is the sleeping-device case. An error naming a cluster or attribute is a payload problem, and no amount of button-pressing will fix it.

The workaround: wake it, then send

Since there’s no queueing above the protocol layer, the practical fix is to make the device poll its parent inside the window the command is sitting there. For a battery remote or button, press a button. For a contact sensor, open or close the door. For a motion sensor, trigger it. Whatever action makes the device transmit is what brings it to its parent.

Knowing the buffer is roughly seven seconds changes how you go about this. You don’t have to land the command in some invisible instant of wakefulness — send it, then wake the device within a few seconds, or wake it first and send immediately. Either order works if both land inside the same window. It also means a retry loop firing every ten seconds, which is the cadence that circulates in forum threads, is tuned slightly wrong: each attempt can expire before the next one starts. Retrying every four or five seconds while repeatedly triggering the device covers the gap properly.

When waking the device doesn’t reliably work

Some devices won’t cooperate. A closed feature request on exactly this problem opens with the reporter’s Sonoff SNZB-02 sensors, which he reports cannot be forced awake at all — neither a button press nor pulling the battery does it. His workaround is to wait until the device sends a message of its own accord and then hit Apply as fast as possible, which is a fair description of what’s left when the wake gesture isn’t available.

His proposal was a user-editable file of custom reporting settings per device model, applied automatically during pairing or reconfiguration, so the values land while the device is demonstrably awake and talking. A second suggestion in the same thread was the one people usually remember: if a frontend change fails, offer to apply it automatically the next time the device sends anything. Neither shipped. The request is closed as not planned.

Warning

If your device family resists manual waking, treat the standard workaround as unreliable rather than broken, and plan on several attempts across normal check-in cycles. Don’t escalate to a re-pair on the strength of a failed configure — re-pairing costs you the device’s automations and, on some models, its reporting configuration along with them.

Where this stands

Nothing here is a regression. It’s a design gap that’s been openly acknowledged, in an open discussion where the maintainer confirmed the current behaviour and called automatic queueing “a good idea,” with no committed timeline attached.

I think that instinct is right, and the mechanism above is the reason why. Zigbee already assumes end devices won’t be listening — that’s what the parent’s buffer is for. The stack isn’t unaware of sleepy devices; it just gives them 7.68 seconds. What’s missing isn’t an understanding of battery hardware, it’s a retry above the protocol layer that notices the buffer expired and tries again on the next poll. That’s a smaller ask than it looks from the outside, which is probably why it keeps getting raised.

What this covers, what stays local, and what to check

This is built from Zigbee2MQTT’s binding and MQTT topics documentation, Silicon Labs’ Zigbee stack reference for the sleepy-device timing, and two current items in the project’s tracker. Nothing here rests on bench testing; it’s a synthesis of documentation and issue-tracker material. The 7.68-second figure is a stack default rather than a Zigbee2MQTT setting, so treat it as the right order of magnitude on your coordinator rather than an exact number.

What stays local: entirely. Configure-reporting and bind commands are Zigbee-layer messages sent from your coordinator, requested and confirmed over your own MQTT broker. Nothing about this touches a cloud service.

What to check on your own setup: subscribe to the bridge/response topic before you conclude anything, since an error naming an attribute means the payload is wrong and no wake gesture will help. Confirm your device has a usable wake gesture before spending twenty minutes on a configuration that was never the problem. And if a battery reading rather than a reporting interval is what brought you here, that’s a different mechanism — our battery percentage explainer covers why those values go wrong on their own.

local-firstHome Assistantno-cloud