A battery-powered ESP32 sensor can report a reassuring 3.8 V one minute and shut down unexpectedly the next. The problem is not necessarily a bad battery: lithium-cell voltage changes with load, temperature, rest time and where the cell sits on its discharge curve. An ESP32 analog input measures voltage, but it cannot turn a single reading into a dependable battery-percentage estimate. The MAX17043 fuel gauge addresses that problem by measuring a single lithium cell and estimating its state of charge using Analog Devices’ ModelGauge algorithm.
This guide builds a single-cell Li-ion/LiPo monitor using an ESP32, a MAX17043 breakout, ESPHome and Home Assistant. You will wire the sensor safely, publish battery voltage and percentage, create a low-battery alert and add deep sleep without accidentally resetting the fuel gauge every time the ESP32 wakes. The MAX17043 does not charge the cell, protect it from overdischarge or regulate the supply to the ESP32; those are separate hardware jobs.
What the MAX17043 measures—and what it does not
The MAX17043 is a single-cell fuel-gauge IC. It reports the cell voltage and an estimated relative state of charge (SoC), normally presented as a percentage. Its I²C address is fixed at 0x36. It does not need an external current-sense shunt, which makes it different from the INA226 and INA3221 current and power monitors covered elsewhere in this series. A fuel gauge estimates remaining battery fraction; a current monitor measures current through a particular shunt. Neither substitutes for a lithium charging and protection circuit.
ModelGauge uses a model of lithium-cell discharge behaviour rather than a simple voltage-to-percentage lookup. That usually provides a more useful indication under changing loads, although it remains an estimate: cell chemistry, ageing, temperature, intermittent high-current Wi-Fi bursts and the specific module design can all affect what you observe. A displayed “20%” is not a guarantee that an ESP32 can continue to operate until exactly 0%.
Use the MAX17043 with one compatible Li-ion/LiPo cell, including a 1S pack in which cells are paralleled appropriately by the pack manufacturer. Do not connect it directly across a 2S or 3S battery pack or a 12 V battery. The related MAX17044 is a different two-cell part; a MAX17044 board is not a drop-in replacement for the MAX17043 ESPHome platform.
Parts and electrical limits
- An ESP32 development board with accessible I²C pins; the example uses a classic ESP32 board with GPIO21 as SDA and GPIO22 as SCL. Check your exact board before using those pins.
- A verified MAX17043 breakout designed for a single-cell lithium battery. Check its own schematic or silkscreen: breakouts do not all use the same pin names or power connections.
- A suitable, correctly polarised 1S Li-ion or LiPo battery, preferably a protected pack where appropriate, plus a charger and protection arrangement suited to that cell.
- A regulator or power-management board that provides an appropriate stable supply to the ESP32 from the battery. Do not connect a 4.2 V fully charged cell directly to the ESP32’s 3.3 V pin.
- Short I²C leads, a shared ground, and a USB connection for initial flashing and serial troubleshooting.
According to the manufacturer’s MAX17043 documentation, the IC’s recommended supply range is 2.5–4.5 V. That is an IC specification, not permission to put 4.5 V on ESP32 GPIOs. The ESP32’s SDA and SCL lines must be pulled up to a compatible 3.3 V logic rail, not to the battery’s variable 4.2 V peak or an arbitrary 5 V rail. The gauge’s CELL input measures the battery; the board’s VCC pin may have a different purpose, depending on the breakout.
Wiring the ESP32 and MAX17043
On the current SparkFun MAX17043 breakout, for example, the battery input supplies the gauge while its separate VCC pin supplies the I²C/alert pull-up resistors. Supplying that pull-up pin from the ESP32’s regulated 3.3 V rail keeps the digital bus at a safe logic voltage. Do not transfer that assumption to a differently labelled module without checking its schematic. If a breakout ties SDA/SCL pull-ups to the cell or 5 V, resolve that electrical incompatibility before plugging it into the ESP32.
| Connection | ESP32 / system side | MAX17043 breakout side |
|---|---|---|
| Battery positive | Positive of the single lithium cell, through the intended battery connector/wiring | BAT+, CELL, or battery + connector as marked on your specific board |
| Battery negative | Battery/system ground | BAT− or GND on battery connector |
| Logic reference | Regulated 3.3 V only when this pin feeds 3.3 V I²C pull-ups | VCC / 3V3 on a breakout designed with a separate logic pull-up rail |
| I²C data | GPIO21 in this example | SDA |
| I²C clock | GPIO22 in this example | SCL |
| Shared ground | ESP32 GND, regulator ground and cell negative according to the power-board design | GND |
| Alert output | Leave disconnected for the initial ESPHome build | ALRT, if present |
The battery supplies the fuel gauge, while a charger/power-management board feeds the ESP32 through a suitable regulator. The ESP32 is not placed in series with the MAX17043 and the gauge is not in the load-current path. If the board has a JST battery socket, verify its polarity with a meter: physically similar JST connectors from different vendors are not guaranteed to have identical positive and negative positions. Disconnect power while wiring and never short a lithium cell.
Why battery percentage is not just a voltage lookup
Lithium-ion voltage can appear fairly flat through much of a discharge, particularly after the load settles. The ESP32’s Wi-Fi radio then creates brief current peaks that temporarily lower the terminal voltage. A voltage-only chart can interpret the same cell as nearly empty under load and noticeably fuller at rest. The MAX17043 follows its battery model over time and exposes both measured voltage and estimated SoC, so you can examine them together.
After the gauge first powers up, its initial estimate may be imperfect. ESPHome’s MAX17043 documentation explains that the gauge makes an initial assumption about the cell being at rest; its estimate can improve as it observes operation. Do not repeatedly remove power from the fuel gauge to “reset” its percentage every time the ESP32 sleeps. A persistent 100% or a sudden jump is a troubleshooting clue, not necessarily evidence that the actual battery gained charge.
Complete ESPHome configuration for an always-on monitor
Start with the ESP32 on USB power while keeping the MAX17043 connected to the battery it is measuring. Replace the Wi-Fi credentials, board type and API encryption key with the values from your own ESPHome installation. This example uses ESPHome’s native max17043 sensor component and reports directly to Home Assistant through the native API. The i2c.scan option makes it easier to confirm that address 0x36 responds during setup.
esphome:
name: lipo-battery-monitor
friendly_name: LiPo Battery Monitor
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
encryption:
key: !secret api_encryption_key
ota:
- platform: esphome
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Battery Monitor Fallback"
password: !secret fallback_ap_password
captive_portal:
i2c:
id: battery_i2c
sda: GPIO21
scl: GPIO22
frequency: 100kHz
scan: true
sensor:
- platform: max17043
id: lipo_gauge
i2c_id: battery_i2c
update_interval: 30s
battery_voltage:
name: "LiPo Battery Voltage"
id: lipo_voltage
accuracy_decimals: 3
battery_level:
name: "LiPo Battery Level"
id: lipo_level
accuracy_decimals: 1
For a new deployment, ESPHome generates an API key when you create a device; you may also place your own valid key in secrets.yaml. The example deliberately shows no OTA password or additional automation: make sure the baseline sensor is detected and sends stable values before introducing sleep and recovery behaviour. If your installed ESPHome version uses a different OTA authentication configuration, retain the known-working OTA settings for the running device and follow the official migration guidance rather than changing authentication and firmware in one untested step.
The update_interval: 30s controls how often the ESPHome component publishes an updated sample. It does not change the physical battery chemistry or make the Wi-Fi connection power-efficient. A constantly connected ESP32 development board can consume more energy than the gauge by orders of magnitude. To extend battery life meaningfully, measure the whole board’s sleep and wake currents rather than treating a 30-second sensor interval as a power-saving feature.
Verify the readings before adding deep sleep
- Open the ESPHome device logs. The I²C scan should discover a device at
0x36; if it does not, check actual pin assignments, ground, digital pull-up voltage and battery connection. - With the cell resting, compare the displayed battery voltage against a multimeter reading directly across the battery terminals. A small difference is possible; a large discrepancy points to wiring, power or module issues.
- Add the device to Home Assistant using the ESPHome integration. Confirm that the voltage and percentage entities appear and update while the ESP32 is awake.
- Observe a few charge/discharge cycles in a safe test environment. Note how percentages behave during Wi-Fi activity, after a rest period and near the regulator’s low-voltage operating limit.
Do not “calibrate” the percentage by adding a fixed offset until it happens to match a voltage chart at one state of charge. That can make the displayed value less informative elsewhere. Instead first check for correct cell chemistry, stable power, a genuine MAX17043 device and uninterrupted gauge operation.
Add deep sleep without wiping the gauge’s history
An always-on Wi-Fi ESP32 is convenient but inefficient for many remote battery nodes. ESPHome’s deep sleep component lets the device wake, take readings, send them over Wi-Fi and return to sleep. While sleeping, it cannot serve the native API or accept an OTA update. It is also normal for a sleeping node to show unavailable in some Home Assistant setups until its integration and reporting behaviour are adjusted.
The crucial hardware detail is that the MAX17043 must continue receiving battery power while the ESP32 sleeps. A GPIO-controlled switch that disconnects power from every sensor during sleep would restart the fuel gauge on every wake, reducing the benefit of its model-based tracking. Route the gauge’s battery supply independently of any switchable ESP32 sensor rail. On breakouts with a separate I²C pull-up rail, powering off that logic rail can be acceptable only if the board and bus are designed to avoid back-powering or floating-pin problems; verify the actual schematic.
Once the always-on configuration works, add this block to the same YAML. The ESP32 stays awake for 90 seconds, then sleeps for 15 minutes. Adjust the awake window for your Wi-Fi association time and how reliably Home Assistant receives readings. This is a starting point, not a guarantee of 15-minute reporting: an association failure or unexpected reset can prevent successful publication.
# Add to the working configuration above:
deep_sleep:
id: battery_deep_sleep
run_duration: 90s
sleep_duration: 15min
For the first deep-sleep test, keep USB access available. A 90-second awake window makes logs and OTA access far easier than an aggressively short window. If an over-the-air update misses the wake interval, wait for the next wake or temporarily deploy a firmware version without deep sleep using USB. Do not assume a device asleep on purpose is a failed Wi-Fi node.
Should the fuel gauge enter its own sleep mode?
The ESPHome MAX17043 integration includes a max17043.sleep_mode action. The manufacturer specifies very low current in the gauge’s sleep state, and ESPHome’s documentation recommends using that action just before the ESP itself enters deep sleep when squeezing out additional battery life. However, ESPHome also notes that the gauge stops recalculating voltage and percentage after that action until it wakes on the next ESP startup. That is a power-versus-tracking trade-off, not a free improvement in both battery life and measurement continuity.
Start with the gauge awake while the ESP32 sleeps, measure your complete system’s quiescent current, and consider gauge sleep only if its contribution matters. The component documentation gives a general sequence of max17043.sleep_mode followed immediately by deep_sleep.enter. If you adopt that advanced sequence, use one deliberate sleep pathway rather than combining multiple independent timers and scripts that can race each other. Never call max17043.sleep_mode merely to pause reporting on an ESP32 that stays awake: subsequent values can remain stale until a restart.
Home Assistant low-battery notifications
The gauge publishes a voltage sensor and an estimated battery-level sensor. Display both on a dashboard: percentage is convenient for alerts, while the voltage trace helps diagnose unexpected shutdowns or charging faults. Use the battery entity’s actual entity ID from Developer Tools in Home Assistant; it may differ from the sample name below.
This sample Home Assistant automation sends a notification when the reported battery level crosses below 20%. Replace sensor.lipo_battery_level and the notify service with the entities in your own system. With a sleeping node, a threshold-crossing alert might arrive only on the next successful wake and may not fire a second time if the device stays below 20%. Add a separate daily low-battery status check if you require repeated reminders.
alias: LiPo monitor low battery
mode: single
triggers:
- trigger: numeric_state
entity_id: sensor.lipo_battery_level
below: 20
actions:
- action: notify.mobile_app_your_phone
data:
title: "ESP32 battery low"
message: "The LiPo monitor reports less than 20% battery."
Do not use a Home Assistant notification as the only protection against lithium overdischarge: Wi-Fi, the router, Home Assistant or the ESP32 can be unavailable at exactly the wrong moment. The charging/protection hardware and the regulator’s cut-off behaviour must keep the cell within the manufacturer’s safe operating limits without depending on software.
What happens when the ESP32 runs out of usable voltage?
A single lithium cell commonly charges near 4.2 V and falls as it discharges, but its usable lower limit depends on the specific chemistry, cell datasheet, load and protection arrangement. An ESP32 requires a suitable regulated input; a regulator can fall out of regulation before the fuel gauge reports zero. Conversely, repeated attempts to reboot a node on an almost empty cell can waste remaining energy while accomplishing no useful measurement.
Measure the minimum battery voltage at which your entire assembled device still boots, associates with Wi-Fi and publishes data. Choose your warning threshold with reserve above that point, while following the cell and protection-board specifications. A software-estimated 0% is not a safe physical cut-off specification. Never bypass a battery protection circuit to get a few additional minutes of operation.
Troubleshooting an I²C device that does not appear at 0x36
The scanner shows no devices: confirm that the cell is connected in the polarity indicated on the breakout, the ESP32 and gauge share ground, and you have used pins suitable for I²C on your particular ESP32 board. Measure SDA and SCL idle voltage; if either is stuck at 0 V or pulled to 4.2–5 V, correct the wiring before proceeding. Some modules need their own power input as well as the battery measurement input.
The scanner shows another address: verify the chip marking and model instead of guessing that the module is compatible. ESPHome’s native max17043 platform targets MAX17043 at 0x36; another fuel-gauge family can need a different driver even if its address looks familiar.
The gauge works on USB but fails from the cell: inspect the battery-to-regulator path and the regulator’s startup behaviour under Wi-Fi bursts. The USB cable can conceal a weak battery, insufficient supply decoupling or an undersized regulator. Use serial logs and a meter or scope on the power rail during startup; do not attribute every restart to the I²C driver.
Troubleshooting percentage readings
The battery is “100%” despite substantial use: check that the gauge is measuring the actual cell terminals, not the regulator’s fixed 3.3 V output or a charger’s output with no cell attached. Compare raw battery voltage with a multimeter and leave the gauge powered through normal operating cycles so its estimate can settle.
The percentage jumps every wake: determine whether a switched sensor rail or a deep-sleep power board is also cutting power to the MAX17043. Keep the gauge alive between ESP32 wake cycles. Sudden changes can still occur after a large transient load or a fresh gauge power-on; review voltage and load history before concluding the battery is defective.
The reading stays frozen after a sleep experiment: make sure the ESP32 actually entered deep sleep after invoking max17043.sleep_mode. ESPHome documents that the gauge stops updating in this state and normally wakes through a subsequent ESP startup. Remove the sleep-mode action while debugging, power-cycle safely if necessary, and re-test the always-on example.
The voltage is reasonable but the ESP32 shuts down early: measure voltage at the regulator input and at the ESP32 supply pins under load. The cause may be regulator headroom, battery internal resistance, cabling losses or an undersized power supply—not an inaccurate gauge. The MAX17043 reports a battery estimate; it does not ensure that every downstream circuit can continue operating.
MAX17043 versus INA226, INA3221 and a voltage divider
| Method | What it provides | Main limitation for a 1S battery node |
|---|---|---|
| MAX17043 | Cell voltage and model-based estimated state of charge | Not a charger, protection circuit, true capacity test or current meter |
| ESP32 ADC + divider | Battery voltage with a suitable input network | No built-in lithium state-of-charge model; divider may create standby drain |
| INA226 | Bus voltage, current and power for one shunt path | Current monitor needs correctly selected shunt and calibration; not a direct SoC percentage |
| INA3221 | Current/bus monitoring across three shunt channels | Useful for multi-load diagnostics, not a substitute for a battery fuel-gauge algorithm |
Choose the MAX17043 when the central question is “How much battery is likely left in this one-cell node?” Choose a current monitor when the central question is “Which load is consuming the energy?” For a complete field-node power budget, you might instrument a prototype with a current monitor and deploy the finished node with a fuel gauge. Both approaches have value, but they answer different questions.
Practical field-node checklist
- Validate cell type, charger and protection arrangement; check the battery connector polarity before first power-up.
- Verify the MAX17043 breakout’s actual pull-up rail and keep the ESP32’s I²C signals at 3.3 V.
- Check that the fuel gauge measures the raw cell rather than a regulated 3.3 V or 5 V rail.
- Confirm I²C address
0x36, then publish both battery voltage and estimated SoC before adding sleep. - Keep the gauge powered across ESP32 deep-sleep cycles; measure total board sleep current rather than relying only on chip-level figures.
- Test alert timing, Wi-Fi reconnects and OTA recovery with a comfortable awake window before deploying an inaccessible sensor.
- Treat the notification as an early warning; use independent hardware protection and the cell maker’s electrical limits for battery safety.
Official documentation and further reading
- ESPHome MAX17043 sensor — supported YAML keys, fixed I²C address, fuel-gauge behaviour and sleep-mode action.
- ESPHome deep sleep — wake and sleep configuration and the effect on connectivity and OTA updates.
- Analog Devices MAX17043 product documentation — IC capabilities, single-cell scope and device electrical limits.
- SparkFun MAX1704x hookup guide — breakout-specific pinout, pull-up supply and battery connector wiring; always confirm the revision of your own board.
The practical takeaway: let the MAX17043 monitor the cell, let the regulator power the ESP32, and let the charger/protection hardware protect the battery. With those jobs separated, ESPHome can publish useful battery percentage and voltage to Home Assistant while deep sleep reduces the ESP32’s duty cycle without restarting the gauge on every wake.