Skip to content
Stand with Ukraine flag

Estimate Battery Days Remaining

  • Calculated Field
  • 0 installs
  • v1.0.0
  • Aug 13, 2026
  • MIT license
  • v4.3+

Estimate a battery’s remaining days and its percent-per-day drain from daily drain totals, so you can schedule swaps before remote sensors and trackers die.

Type
  • Script
Category
  • Statistical
  • Predictive
Use Cases
  • Predictive Maintenance
  • Asset Tracking
  • Environment Monitoring
  • Smart Metering

Who it’s for

Fleet and asset managers, remote-sensor and LoRaWAN operators, and maintenance planners asking “when will I have to send someone out to swap this battery?”, “which devices will die first?”, “is this one draining faster than it used to?” - when the battery percentage right now tells you the state but not the schedule.

What it does

Reads a ready-made daily drain total, averages the recent days into a rate, and publishes two numbers: batteryDrainPerDay (average percent lost per day) and batteryDaysRemaining (whole days until the level reaches batteryEmptyThreshold, 5 percent by default).

The daily total is not computed here. Two published calculated fields produce it upstream - one turns each battery reading into the drop since the previous reading, the other sums those drops into a calendar-day total - and this field reads the last 20 days of it as batteryDrainDayTotal, alongside the current batteryLevel:

Estimate Battery Days Remaining diagram

Both outputs are stored as time series, so dashboards, alarm rules, the REST API and downstream calculated fields read them like any other telemetry.

One point per calendar day is what makes the number trustworthy. A rolling window over raw readings holds at most 1000 points on a default tenant profile, so a device reporting every minute has less than a day of history inside a window that claims a fortnight, and nothing surfaces it. Twenty daily points sit inside a limit of 25 with room to spare, so here the window is what decides how much history the average sees. The daily grain also drops the intraday noise that temperature swing and charge cycles put into a raw series.

A few honest limits worth knowing:

  • It is silent for the first few days, by design. The upstream daily total writes one point per calendar day and does not backfill, so a newly installed chain produces nothing until batteryMinSamples complete days have passed - five by default. Installing it on a device with years of history does not shorten that wait. The estimate is worth the wait, but the wait is real.
  • Today is not counted. The upstream field reports the current day as it accumulates, so the newest daily total is usually a part-finished one. Averaging it in would understate the drain and overstate the days left, so it is left out.
  • Days remaining appears only while discharging. A charge is not negative drain - the first upstream field returns 0 when the level rises - so a day spent charging averages in as zero. If the average comes out at zero, batteryDrainPerDay is still reported and no days-remaining figure is emitted, because there is no meaningful time to empty.
  • It is an average, extended forward. Real cells hold steady and then drop off near the end, so treat the number as a planning signal, not a guarantee. A very slow drain produces a very large day count.
  • A daily total that arrives as text is skipped. Non-numeric values are excluded from the average and from the latest-level check, so one bad reading cannot publish a zero.

How to set up

Three calculated fields on the same device or asset - or on the device profile, to cover every device of that type at once. The first two are published templates used as they ship, with their keys pointed at the battery.

1. Turn each battery reading into a drop. Install the Script template below and change the Time series key on its level argument from fuelLevel to batteryLevel:

Calculate Delta from Tank Level Sensor Dmytro Shvaika

It emits readingsDelta, the amount the level fell since the previous reading. A falling battery percentage behaves exactly like a falling tank, and rises are returned as 0 rather than as a negative number, so charging never registers as gain.

2. Sum those drops into a calendar day. Install the Entity aggregation template below, change the Time series key on its source argument from energyKwhConsumed to readingsDelta, and change the Metric name from dailyEnergyConsumption to batteryDrainDayTotal:

Daily Energy Consumption Total Dmytro Shvaika

Leave Aggregate interval type on Day and set Time zone to wherever the operator reads the dashboard, because that is the boundary the daily totals land on. The rename matters only for readability - a battery device has no reason to carry a key named after energy consumption - and step 3 can be pointed at the original name instead.

3. Install this field. It reads batteryDrainDayTotal through a rolling-window argument covering 20 days with a limit of 25 points, and batteryLevel as a latest value. Neither argument falls back to a default, so both keys have to exist before this field can start - batteryLevel arrives from the device and batteryDrainDayTotal from step 2, which is why the first two fields have to be running first. Until the first daily total exists the platform reports Required arguments are missing: dayDrain and this field stays inactive - which is the correct state, not a fault.

4. Optionally tune the two attributes. Both have working defaults, so nothing else is required. To change them per device, set server-side attributes:

  1. Open the device details page (Entities -> Devices -> [device name]).
  2. Go to the Attributes tab and select the Server-side scope.
  3. Click + to open the Add attribute dialog.
  4. Add batteryEmptyThreshold (Double, the percent you treat as empty, e.g. 10) and batteryMinSamples (Long, e.g. 7).
  5. Click Add.

How to customize

  • To change how far back the average looks - adjust the Time window on the dayDrain argument. It covers 20 days by default with a limit of 25 points, which leaves room for the daily series plus a margin. A longer window smooths the estimate, a shorter one reacts faster to a change in drain. One daily total needs one slot, so raise the limit alongside the window if you extend it much past three weeks.
  • To change what counts as empty - set the batteryEmptyThreshold attribute, or change the argument Default value. Days remaining counts down to this level, not to zero.
  • To shorten or lengthen the warm-up - set the batteryMinSamples attribute. It is the number of complete days required before anything is emitted, so it is also how long a fresh chain stays silent. Raise it for erratic devices, lower it for an earlier first estimate.
  • To keep the upstream name for the daily total - if you would rather leave step 2 emitting dailyEnergyConsumption, change the Time series key on the dayDrain argument here to match it instead. The two only have to agree.
  • To read a battery that reports under a different key - change the Time series key on step 1’s level argument and on the latestLevel argument here to the same key, whatever the device calls its state of charge.
  • To rename the outputs - open the script and replace batteryDrainPerDay and batteryDaysRemaining in the return object with the keys your dashboards and alarms expect.

See also

Trend batteryDrainPerDay on a time-series chart to see a pack ageing - a drain rate that climbs month over month is a cell losing capacity, well before the days-remaining figure gets short. Raise an alarm on batteryDaysRemaining at whatever notice your field team needs, and bind it to a value card so the number is visible on the device dashboard.

Share Your Calculated Field with the Community

Built a useful data transformation? Export it as a JSON from ThingsBoard and publish it to the IoT Hub. Share it with thousands of ThingsBoard developers worldwide.