The Battery Whisperer's Guide: Squeezing Every mAh from Your Tracker
Building a tracker with Wi-Fi, Bluetooth, LoRa, and GPS doesn't have to mean living on a charger. This post walks through the exact moves that took our ESP32-S3 design from "dies by dinner" to "runs for weeks," from hunting down sneaky always-on leaks to letting each radio speak only when it truly should.
You'll see how we establish a truthful power baseline, not the brochure numbers, carve the hardware into sane power domains, and schedule radios with intent, GPS fixes gated by motion, Wi-Fi sessions that reconnect fast and sleep deeper, and LoRa that adapts to link quality instead of blasting at a fixed spread factor. We'll go into deep sleep as the default, use the ULP for cheap background sensing, and hard-gate peripherals so standby currents don't quietly drain your pack.

The hidden power vampires: understanding your baseline
Start with measurement, not brochures. It's common for a "50 microamp sleep current" target to land closer to 850 microamps once the whole stack, rails, sensors, and radios, is assembled. Every unmeasured path is a quiet drain on the pack, so the first win is a truthful baseline.
Three culprits worth auditing early. The GPS backup or standby rail, around 150 microamps, since many GNSS modules keep a backup domain alive, treat it as a separate load and gate it with a load switch or ensure it's only powered when fast reacquisition is truly needed. Regulator quiescent and reverse leakage, around 200 microamps, since some LDOs behave beautifully under load but sip more than expected at idle, check Iq across temperature and voltage, verify reverse isolation, and pick parts per rail role, burst-friendly on the main rail, ultra-low-Iq on always-on. And I2C pull-ups into unpowered sensors, around 500 microamps, if pull-ups sit on an always-on domain while devices are power-gated, current can back-feed through protection diodes, place pull-ups on the same domain as the sensors, or add bus switches and FET gating.
A quick procedure to nail the baseline: partition by domain (always-on, sensors, radios), measure at the battery and per rail using an inline meter or power profiler sampled across key states, toggle domains deliberately, disabling one at a time and logging the delta, validate that sleep truly sleeps, issuing deep-power-down commands to flash and verifying wake sources don't spuriously fire, and record before/after values in a table with conditions like voltage, temperature, and firmware hash.
Cleaning up just these baseline leaks often transforms endurance, think an idle estimate moving from roughly 98 days to 545 days, before any higher-level scheduling or radio logic gets applied. Establish the baseline once, keep it in CI with a smoke test, and let everything else build on numbers you trust.
Architecture for micro-power: the foundation
Think in power domains, not just peripherals. A simple three-domain layout keeps control clean and measurable. Always-on covers the ESP32-S3 plus RTC domain on an ultra-low-Iq LDO, like a TPS7A05 at 25 nanoamps. Sensor domain covers the IMU, barometer, and microphone behind a P-MOSFET load switch. Radio domain covers GPS and LoRa on independent enable lines for selective bring-up.
This structure lets you cut power completely to entire blocks instead of relying on standby modes. For example, a microphone AFE idling at around 50 microamps doesn't seem like much per second, but hard-gating it saves roughly 1.2 mAh per day, which compounds over multi-week runtimes.
Regulators matter as much as topology. Match each LDO to the rail's behavior, ultra-low-Iq is ideal on always-on rails, but bursty 3.3V radio rails can see around 250mA peaks where transient performance dominates. Choosing a transient-strong LDO with a modest Iq, around 50 microamps versus around 1 microamp, prevents dropout, ripple, and slow recovery, shortening TX windows, avoiding retries, and delivering roughly 15% net battery savings when radios drive the duty cycle.

Radio discipline: the 80% solution
Radios make or break your battery. And it's not just how often you use them, it's how intelligently you time them.
For GPS, cold starts took up to 90 seconds at 42mA. We implemented AGPS preload over Wi-Fi, stored ephemeris in RTC RAM, and used IMU data to throttle fix rates, if the dog wasn't moving, no GPS, if it sprinted, faster fixes kicked in.
For Wi-Fi, static IP, DNS caching, and DTIM interval control dropped our reconnect time from 5 seconds to 1.6 seconds. Connection reuse beat aggressive polling. For infrequent updates we disconnected fully, for fast syncs we stayed in modem sleep.
For LoRa, we dynamically tuned spread factors 7 through 10 based on RSSI/SNR, cutting TX power by up to 70% on short-range syncs. We logged SNR metrics and adapted on the fly, no more always-on SF10 "just in case."
Our radio state machine became smarter than our state diagram: motion-gated GPS, battery-aware Wi-Fi upload intervals, and LoRa-only fallback logic when other radios failed.

MCU power states: the deep sleep religion
The ESP32-S3 deep sleep mode is the only mode that matters in low-duty devices. We achieved 11 microamp baseline draw using an RTC timer plus GPIO interrupt for scheduled and reactive wake, and a ULP coprocessor running a sub-5kHz polling loop for battery voltage and IMU events.
Wake intervals got tuned based on activity, during periods of user motion detected via IMU, the RTC timer fired every 2 minutes, in inactivity states we backed it off to 10 or even 30 minutes. The ULP let us avoid full wakeups entirely for 50-75% of cycles, its 150 microamp active draw, running at a 1% duty cycle, cost less than 2 microamps average.

Sensors and peripherals: death by a thousand cuts
Peripherals are sneaky. One 50 microamp standby line doesn't seem like much, until you multiply it by three sensors, 24 hours, and 15 days. Our IMU runs in accelerometer-only mode most of the time at 8 microamps, full 9-axis activation is conditional on real movement or a calibration window. And audio is off by default, powered via MOSFET only for 2-second windows during specific user-defined scenarios like barking analysis or sound classification.

Firmware architecture: orchestrating efficiency
Firmware is the glue between all these savings. Our scheduler divides each wake cycle into discrete slices: RTC restore plus wake source check, fast sensors (IMU, barometer), a decision tree for GPS fix, logging, transmit (Wi-Fi/LoRa), and store context plus sleep.
All blocking operations, like GPS acquisition, have hardcoded timeouts, if GPS doesn't fix in 60 seconds we drop, if Wi-Fi isn't connected in 10 seconds we defer. Everything adapts: below 3.4V radio updates drop to once per hour, no motion means skip GPS, and a full flash switches to low-resolution logging.
Charging system optimization
Charging should be smart, not fast. Our charger runs at 300mA, maximizing lithium battery longevity. Thermistor-controlled charge cutoffs above 45 degrees C avoid thermal degradation. We also disabled charging during radio-intensive sessions, since flash writes plus GPS plus charging equals heat plus brownouts, better to charge gently when idle.
Key takeaways
Measure aggressively. Use deep sleep or nothing. Treat GPS like a tiger, powerful, expensive, timing-sensitive. Schedule radios like a network architect. Use the ULP as a real processor, not an afterthought. Power off what you don't need, literally. And build firmware to adapt, fail gracefully, and recover predictably.
Everything here was built to support Hoomanely's core goal: seamless, non-invasive devices. We build systems that stay quiet when they should, alert when they must, and never ask users to micromanage charge cycles. This battery-first design flows into all of our work, from environmental sensors to wellness wearables. The same architecture that lets a pet tracker last weeks is what will someday let a health patch run a full recovery cycle, or an environmental monitor live untethered for months.