Main

Why the Garage Sensor Dies Every January: Cold-Weather Battery Sag and What the Voltage Log Shows

The garage door sensor goes unavailable every January. You replace the battery, it works again, and you file it under “winter is hard on batteries.” That story is plausible, but it is not a diagnosis. The sensor might be sagging under cold, or the cell might be aging on a schedule that happens to land in winter, or the radio path might be failing for reasons that have nothing to do with the battery. The voltage log is how you tell those apart before you spend another January guessing.

Why the voltage log is the first instrument

Home Assistant keeps long-term statistics for a sensor only if the entity has a state_class of measurement, total, or total_increasing. A sensor without a state class has no long-term statistics at all. That matters here because the whole point is to compare July against January, and short-term statistics are purged after a default of 10 days while long-term statistics are never purged. If you want a year-over-year view, the entity has to be set up for it. See the Home Assistant sensor documentation for the state class and statistics behavior.

Home Assistant’s sensor device classes include battery, described as percentage of battery left in %, and voltage, described as voltage in V, mV, µV, kV, or MV. A voltage entity is more useful than a percentage entity for this problem because percentage is often a firmware estimate derived from a lookup table, while voltage is the raw measurement the estimate is based on. If your sensor exposes only a percentage, you are reading someone else’s interpretation of the cell, not the cell itself.

Step 1: Confirm the sensor actually exposes a voltage entity

Do not assume a voltage entity exists. Many battery devices report only a percentage, and some report nothing until you query them. In Zigbee2MQTT, published messages are always JSON, and each device produces a different JSON message, so the attributes available depend on the device definition. Check the device’s Exposes section in the Zigbee2MQTT frontend or the supported devices list to see whether voltage, battery, or battery_voltage is exposed. The Zigbee2MQTT MQTT topics documentation describes the topic structure and the JSON payload format.

If the device exposes only a percentage and you want voltage, you have three options: check whether the device firmware has a hidden attribute you can read via a ZCL read request, add a template sensor that converts percentage to an approximate voltage using the device’s known curve (which is an estimate, not a measurement), or accept that you are working with percentage and adjust the procedure accordingly. The third option is honest. The first is better if the attribute exists.

Once you have a voltage entity, verify it has a state class. If it does not, add one via customization or create a template sensor that copies the value with state_class: measurement. Without that, the graph you are about to rely on will not exist.

Step 2: Read the winter curve against the summer curve

Open the entity’s history in Home Assistant and set the range to cover at least one full year. You are looking for the shape of the curve, not a single number.

A gradual decline from summer to winter that continues into spring points at cell aging. A step down that tracks temperature, where the voltage drops on cold nights and recovers on warmer days, points at temperature-dependent behavior. A flat line that ends abruptly points at a reporting failure or a radio problem, not a battery problem.

Here is a hypothetical example to illustrate the procedure, not a measured result: a garage door sensor’s voltage entity sits near 3.0 V in July and drifts toward 2.6 V in January, with availability dropping out on the coldest nights. The same chart shows the voltage recovering on warmer days. That pattern points at temperature rather than at a dead cell. A different hypothetical: the voltage graph is flat and the device simply stops reporting. Checking the availability topic shows the device going offline rather than the voltage falling, which redirects the investigation to the Zigbee path instead of the battery.

Step 3: Separate “low voltage” from “no report”

An unavailable entity and a low-voltage entity are different failures. If the device is publishing a low voltage but still reporting, the cell is weak but the radio path is working. If the device is not reporting at all, the voltage value you last saw is stale and tells you nothing about the current state.

In Zigbee2MQTT, if Device-Availability is configured, the online/offline status is published when it changes. That gives you a separate signal from the voltage. Check the availability topic for the device and compare its last-changed timestamp against the voltage entity’s last-changed timestamp. If availability went offline before the voltage dropped, the battery is not the first suspect.

Home Assistant’s MQTT integration supports Birth and Last Will and Testament (LWT) messages, and the LWT message is sent both on a clean disconnect and on an unclean disconnect such as crashing or losing network connection. That means an LWT-based availability signal can tell you the device dropped, but it cannot tell you why. You still need the voltage log to distinguish a dead cell from a lost route.

Step 4: Rule out the radio path before blaming the battery

A battery device that is also a poor Zigbee router, or that has fallen off the network, can look like a battery problem. The symptom is the same: the entity goes unavailable. The cause is different.

Zigbee2MQTT’s coordinator_check is only supported for Texas Instruments based adapters such as CC2652/CC1352, and it checks whether any routers are missing from the coordinator memory. The documentation lists “Devices falling of the network” as a problem that may be experienced when routers are missing. If your coordinator supports it, run the check. If it does not, you are left with the network map and the device’s own availability history.

The network map is not free. Zigbee2MQTT’s documentation warns that during the networkmap scan your network will be not or less responsive, and depending on size it can take between 10 seconds and 2 minutes. Run it manually, not on a schedule.

If the device is a battery-powered end device, it does not route for others. If it is a mains-powered device that happens to be in the garage, it might be routing, and its failure could take other devices with it. Check whether other devices in the same area also went unavailable at the same time. If they did, the problem is upstream of the battery.

Step 5: The repeatable winter procedure

Once you have confirmed the sensor exposes a voltage entity with a state class, and you have at least one winter of data, the procedure is:

  1. Log voltage, temperature, and availability on one dashboard. Put the garage sensor’s voltage entity, a nearby temperature sensor, and the device’s availability entity on the same history graph. The correlation is the diagnosis.
  2. Set a voltage threshold alert above the coldest observed floor. If the voltage floor in January was 2.6 V, set the alert at 2.7 V or 2.8 V. The point is to replace the cell before the cold snap, not during it. This is a recommendation, not a research finding. Your floor is your floor.
  3. Replace the cell before the cold snap, not after the failure. If the voltage log shows a seasonal sag that recovers, the cell may still be healthy. If the log shows a sag that does not recover, the cell is aging. Either way, a fresh cell going into winter removes one variable.
  4. Re-check the radio path after the battery change. If the device still goes unavailable with a fresh cell, the problem was never the battery. Go back to Step 4.

This procedure does not assert a specific voltage cutoff, a specific battery chemistry curve, or a specific temperature at which any given device fails. Those numbers depend on the cell, the device, and the board. The procedure is about measuring your own system rather than applying someone else’s threshold.

Graceful degradation when the sensor is unavailable

A dead sensor that reads as “closed” is worse than a sensor that reads as offline. If your garage door automation assumes the door is closed when the sensor is unavailable, you have a security problem disguised as a reliability problem.

Write the automation to fail safe. Treat unknown as unknown. If the sensor is unavailable, the automation should not assume the door is closed, and it should not assume the door is open. It should either do nothing or alert you that the state is unknown. The specific action depends on what the automation controls, but the principle is the same: an unavailable sensor is not a closed door.

If you are using an ESPHome-based sensor for the garage, the ADC sensor can measure VCC on the ESP8266, which the documentation describes as useful in situations where you want to shut down the chip if the voltage is low when using a battery. Note that on the ESP8266 the ADC measures the voltage at the chip, not at the VCC pin of the board, and it should usually be around 3.3 V. The ESPHome ADC documentation covers the sampling options, including samples for the number of readings per update and sampling_mode with avg (default), min, and max. If you are measuring a battery voltage through a divider, the min sampling mode can be useful for catching the lowest point of a sag.

When a mechanical timer or plain switch is the smarter choice

If the garage sensor is only there to turn on a light when the door opens, and the light is on a schedule anyway, a mechanical timer or a plain switch might be the better answer. The sensor adds a battery, a radio, a network dependency, and a failure mode. If the automation does not need the sensor’s state to make a decision, removing the sensor removes the problem. This is not a failure of the smart-home approach. It is a tradeoff, and sometimes the tradeoff favors the simpler device.

What this procedure does not claim

It does not claim that cold weather is the confirmed cause of your January failure. It does not claim that Zigbee2MQTT or Home Assistant automatically detects or compensates for cold-weather battery sag. It does not claim that a voltage entity exists by default on any specific sensor. It does not claim a specific alkaline battery discharge curve or datasheet figure, because the battery datasheet source could not be retrieved for this article. It does not cover Z-Wave JS behavior, because the Z-Wave JS documentation source could not be retrieved. If you are running Z-Wave battery devices, the same diagnostic logic applies, but the specific topics and attributes will differ.

The voltage log is the instrument. The procedure is the method. The numbers are yours.

FAQ

My sensor only reports a percentage, not voltage. Can I still do this?

Yes, but with a caveat. Percentage is often a firmware estimate derived from voltage, so it may be less granular and may not show the same sag. If the percentage drops in winter and recovers in spring, that is still a useful signal. If it drops and does not recover, the cell is aging. The procedure works with percentage, but voltage is better if you can get it.

How do I know if my sensor’s voltage entity has a state class?

Go to the entity’s settings in Home Assistant and look for the state class attribute. If it is not set, you can add it via customization or create a template sensor that copies the value with state_class: measurement. Without a state class, the entity will not have long-term statistics, and you will not be able to graph it over a full year.

What if the voltage log shows a flat line and the device stops reporting?

That points at a reporting failure or a radio problem, not a battery problem. Check the device’s availability topic and last-seen time. If the device went offline before the voltage dropped, the battery is not the first suspect. Go to Step 4 and check the radio path.

Should I replace the battery every winter regardless of voltage?

That is a valid strategy if the battery is cheap and the sensor is hard to reach. It is not a diagnosis, but it is a maintenance routine. The voltage log tells you whether the battery actually needed replacing. If you replace it every year and the old cell still measures near its summer voltage, you are replacing healthy cells. If you replace it every year and the old cell measures well below its summer voltage, you are replacing cells that were on their way out. Either way, the log tells you which one you are doing.

Does this apply to Z-Wave sensors?

The diagnostic logic applies, but the specific topics and attributes differ. The Z-Wave JS documentation source could not be retrieved for this article, so I am not going to state specific Z-Wave JS behavior. Check your Z-Wave JS integration’s documentation for the equivalent voltage and availability entities.

For a broader look at auditing the small systems that quietly run your week, see How to Audit the Small Systems That Quietly Run Your Week.