A Zigbee2MQTT battery value that has read 100% for eight months, or shows 105%, or dropped to 1% the day after you put in a fresh cell, looks like a bug. It’s rarely one you can fix, and it’s almost never a Zigbee2MQTT bug at all. It’s a byproduct of how battery percentage gets calculated — and that calculation happens differently depending on the device, which is why the same weirdness shows up on Xiaomi door sensors, Tuya climate sensors, and half a dozen other unrelated products.
This is a mechanism explainer: what battery, voltage, and battery_low each actually represent, why the percentage specifically is the unreliable one, and how to read the numbers you already have instead of waiting for a fix that isn’t coming. None of it affects whether your setup stays local.
This is a cross-device mechanism explainer built from Zigbee2MQTT’s official docs, the Zigbee cluster specification, and independent GitHub issue threads spanning Xiaomi and Tuya devices — not a single-device troubleshooting guide or a bench-tested claim about one specific model.
What battery, voltage, and battery_low actually measure
Zigbee2MQTT exposes three separate properties on battery-powered devices, and it’s easy to assume they’re the same number in different clothes. They aren’t.
battery— a 0–100 percentage.voltage— the raw battery voltage in millivolts.battery_low— a boolean flag raised when the device, or its Zigbee2MQTT converter, considers the cell near empty. Not every device exposes it.
These come from the Zigbee2MQTT MQTT topics and messages documentation, and the split matters because each one can disagree with the other two at the same moment. A device can report battery: 100 and battery_low: true simultaneously — that’s not a contradiction in Zigbee2MQTT, it’s the firmware saying two different things about two different thresholds.
{
"battery": 100,
"voltage": 3035,
"battery_low": false,
"linkquality": 63,
"last_seen": "2026-08-18T09:12:03+08:00"
}
Identifying which physical cell a device takes and how to swap it is a separate question, covered in our Aqara and Xiaomi Zigbee battery types guide — this article is only about the number Zigbee2MQTT reports. It’s also not the only place Zigbee2MQTT exposes two similarly-named properties that measure different things; our device_temperature vs temperature explainer covers the other pair that trips people up the same way.
Why percentage gets stuck near 100% for months
Percentage arrives via one of two paths, and the path matters. Some devices report batteryPercentageRemaining directly from firmware, and Zigbee2MQTT passes that number through. Others don’t expose a percentage at all, so it gets derived from the raw voltage using an assumed discharge curve — in conversion functions that live in zigbee-herdsman-converters, the device-definition library, rather than in Zigbee2MQTT itself.
The problem is that discharge curves are not flat lines, and they vary a lot by chemistry. Lithium coin cells — CR2032, CR2450, the kind used in most Xiaomi and Aqara sensors — hold close to their nominal voltage for most of their working life, then fall off a cliff near the end. Alkaline AA/AAA cells discharge more gradually. If a percentage curve is calibrated against the wrong assumption, or against a rough linear approximation instead of the real curve, the result is a battery value that sits near 100% for the long flat part of the cell’s life, then crashes to single digits once the voltage actually starts to drop.
This isn’t only a Zigbee2MQTT-side observation. Our battery guide documents the same coarse mapping from the device side, and long-running threads — issue #8499, open since 2021, and issue #248 before it — describe the symptom across that brand family. What no single source states is a formal, project-wide root cause, so treat the mechanism as the consistent reading of those reports rather than a settled spec.
Why it jumps, reads over 100%, or stops updating at all
A few distinct failure modes get lumped into “battery reporting is broken,” and they have different causes:
No reading right after pairing. Some devices don’t send their first battery report for up to 24 hours after joining the network — Zigbee2MQTT’s own TS0201 device page notes this explicitly, and the pattern repeats across other device pages. A blank or missing battery value in the first day is normal, not a fault.
Reports stop updating entirely. Battery-powered devices are “sleepy” — they spend most of their time in a low-power state and only wake briefly to transmit. Not all of them honor a Zigbee “configure reporting” request for the battery attribute unless the device happens to be awake (typically triggered by a button press) at the moment the request is sent. This is a documented general limitation of sleepy end devices, not a bug specific to any one model or to Zigbee2MQTT itself.
Reads over 100%, or exactly half what it should. The Zigbee cluster specification doesn’t define batteryPercentageRemaining as a plain 0–100 figure. It’s specified in half-percent steps — a range of 0–200, where 200 means 100% — so Zigbee2MQTT halves the raw value it receives. Devices that ignore that convention and report a plain 0–100 number therefore surface at half their real charge, which is the documented direction of the problem: it’s on file against a Sunricher/ROBB wall switch as zigbee-herdsman-converters issue #2411. The same disagreement running the other way, where the halving doesn’t happen, is what puts a percentage above 100. A value that’s exactly double or exactly half what the voltage implies is pointing at this, not at a dying cell.
Percentage jumps between implausible values. A reading swinging from 1% to 100% and back within hours shows up against multiple unrelated device brands. One illustrative case: issue #21611, opened February 2024, describes a Tuya TS0203 stuck at 1% that briefly recovers after the device is renamed or its pairing button is pressed, then reverts. That’s a single device’s behavior, not a general fix — don’t expect the same workaround to hold for other models.
Don’t build low-battery automations off the battery percentage alone. Because percentage can sit near 100% until a lithium coin cell fails abruptly, an automation that triggers at “battery below 20” may never fire before the device goes dark. Trigger on battery_low where the device exposes it — and check first, because plenty don’t.
That check matters more than it sounds. battery_low is missing from a lot of the hardware covered on this site: the original Aqara door and window sensor and its T1 successor don’t expose it, nor does the original Mi motion sensor or the Tuya TS0041 button, while the E1 door sensor and the P1 motion sensor do. Where it isn’t available, the workable substitutes are the voltage trend and plain device availability — a sensor that has stopped reporting at all is itself the alarm.
Is this a Zigbee2MQTT bug or a device bug?
The strongest evidence points at the device side, not Zigbee2MQTT. The same categories of weirdness — stuck-then-crashed percentage, implausible jumps, delayed first reports — recur across product lines from different manufacturers (Xiaomi, Tuya) that share nothing in common except being Zigbee end devices reporting through the same generic battery attributes. If Zigbee2MQTT’s own parsing were the fault, you’d expect the pattern to cluster around specific versions of Zigbee2MQTT itself, or to have been fixed once and stayed fixed. Instead it keeps reappearing on new, unrelated devices years apart.
Issue #568, filed in 2018 and long since closed, is the clearest illustration in the archive. It was filed against the WSDCGQ01LM, the original Mi temperature and humidity sensor, and the reporter’s two units published 99% and 27% while sitting at 3005 mV and 2955 mV — a 50 mV gap producing a 72-point difference in the percentage. That’s the coarse-curve problem stated in a single payload. The fix that followed touched a conversion routine named toPercentageCR2032, which is the voltage-to-percentage mapping doing exactly what this article describes. Read it as evidence the pattern is old and mechanical, not as a current-state bug report.
The practical read: use voltage as the sanity check
Percentage is the number in the dashboard tile; voltage is the number worth actually watching. A voltage reading trending downward over weeks, even while battery sits at 100%, is a real signal. A voltage reading that’s flat is a real “don’t worry about it” signal, regardless of what the percentage says.
| What you’re seeing | Likely cause | What to check |
|---|---|---|
| Stuck at 100% for months | Coin-cell voltage curve stays flat for most of its life | Voltage trend, not the percentage |
| Reads over 100%, or exactly half | Device and converter disagree on the spec’s half-percent scale | Halve it (or double a suspiciously low one) and see if that matches voltage |
| No battery value right after pairing | First report can take up to 24 hours on some devices | Wait 24h before troubleshooting |
| Percentage jumps (1% → 100% → 1%) | Reporting-cadence/firmware quirk, recurs across unrelated brands | Treat repeated jumps as a “replace soon” signal |
battery_low true while percentage still shows high |
battery_low is a separate firmware threshold, not derived from the displayed percentage |
Trust battery_low over percentage for automations |
The same instinct — don’t take one health-adjacent property at face value — applies elsewhere in Zigbee2MQTT. Our explainer on why last_seen looks stale covers a different property with the same underlying lesson: the number that looks alarming isn’t always measuring what you assume it’s measuring.
When to actually worry vs. when to ignore it
Ignore it: battery has read 100% (or 95%, or whatever) for a long stretch, voltage is stable, and battery_low is false. That’s a coin cell doing exactly what coin cells do — nothing to act on.
Pay attention: voltage has been trending down over several weeks, even gradually. That’s the actual discharge curve showing up, ahead of any percentage change.
Act: battery_low flips to true, or the device stops reporting/responding entirely. At that point the percentage number is irrelevant — replace the cell.
Replace on a schedule regardless: for anything you’d actually notice failing — a leak sensor, a door sensor on an entry you arm — a fixed annual swap is more dependable than any of these signals, because none of them reliably give you much runway.
Don’t expect a fix: percentage jumping around, reading over 100%, or being briefly “fixed” by a re-pair that reverts a day later. These are device-firmware quirks with no general remedy in Zigbee2MQTT itself.
What this covers, what stays local, and what to check
This explainer is built from Zigbee2MQTT’s MQTT topic documentation, its TS0201 device-page notes, the Zigbee cluster specification’s half-percent definition of batteryPercentageRemaining, and four issue threads (#8499, #568, #21611, and zigbee-herdsman-converters #2411) that together establish a consistent, cross-brand pattern rather than a single confirmed root cause.
What stays local: everything here. battery, voltage, and battery_low all arrive over your own Zigbee2MQTT/MQTT broker regardless of how accurate the percentage math turns out to be. None of this reporting quirk touches cloud dependency, pairing method, or local control.
What still depends on the vendor: the accuracy of the percentage calculation itself, and whether that math lives in device firmware or in the converter’s voltage-derived estimate for a given model. Neither this site nor Zigbee2MQTT controls what a given manufacturer’s firmware reports as batteryPercentageRemaining, or whether it reports one at all.
What to check on your own setup: whether your device reports percentage directly or Zigbee2MQTT is deriving it (the device’s page on the Zigbee2MQTT supported-devices list will tell you), the voltage trend over the last few weeks rather than a single reading, and whether battery_low exists on that model at all before you write an automation against it.