Siddhant Kumar
Project 072 · Energy

Battery Management Monitor.

Watches every cell in a battery pack — voltage, temperature and balance — so weak cells and thermal danger are caught before they turn a storage battery into a hazard.

Advanced 14–22 hours 32 min read BMSBatterySafety
Jump to source Bill of materials
Battery Management Monitor — reference build illustration MCU VCC · GND · SIG · NC
Difficulty
Advanced
Build time
14–22 hours
Indicative cost
₹3,500 – ₹6,500
Platform
ESP32 DevKit V1 (ESP-WROOM-32)
Category
Energy
Last updated
28 July 2026
Contents — 26 sections

Project Overview

Watches every cell in a battery pack — voltage, temperature and balance — so weak cells and thermal danger are caught before they turn a storage battery into a hazard.

A battery pack is only as safe and healthy as its weakest cell, and the whole-pack voltage that a simple gauge shows hides everything that matters. A pack reading a healthy total can contain one cell drifting low, one running hot, or a growing imbalance that will slowly destroy the pack and, in lithium chemistries, can lead to overcharge, over-discharge or thermal events. Serious battery systems therefore monitor each cell individually. This project builds that monitor: it measures per-cell voltage, pack temperature(s) and current, tracks the balance between cells, and warns of the conditions — a weak cell, an imbalance, a thermal excursion — that precede failure, so a storage battery stays safe and lasts.

The core is per-cell voltage sensing, because cells in a series pack must be kept within a safe window individually: over-charging or over-discharging any single cell damages it and, for lithium, is a safety risk, and the pack must be managed to the most extreme cell, not the average. The monitor measures each cell, watches for any cell straying toward its limits, and tracks the spread between the highest and lowest cell — a growing spread (imbalance) is an early sign of a weak or ageing cell and of a pack that needs balancing. Temperature is watched because heat is the enemy of batteries and the herald of danger: an abnormally hot cell, or a rising rate of temperature, is a warning that must be heeded. Current (charge/discharge) lets it estimate state of charge and enforce safe limits.

On top of monitoring, it supports balancing — bleeding charge from the highest cells so the pack charges evenly and no cell runs ahead — which extends pack life and keeps cells matched. It logs and trends every cell so degradation is visible, and it alarms on any unsafe condition. This is safety-critical work, so the project is clear: a battery management system that actually protects a pack must reliably cut off charge/discharge on fault, and lithium packs in particular demand a proper, ideally purpose-built or certified, BMS — a homebrew monitor is an educational build of the sensing and logic and a supplement, not a replacement for protection that a real pack's safety depends on. But as a per-cell voltage/temperature/balance monitor, it teaches and demonstrates exactly what keeps a battery pack safe: watch every cell, catch the weak one and the hot one early, and keep the pack in balance.

An 18650 lithium-ion cell
A pack is only as safe as its weakest cell — per-cell monitoring reveals what the total hides. Photograph sourced from Wikimedia Commons — 18650 Li-ion battery.jpg. Reused under the licence stated on that page; please check it before republishing.

What this project does

  • Measures per-cell voltage across a series pack
  • Monitors pack temperature(s) and charge/discharge current
  • Tracks cell imbalance (highest-to-lowest spread)
  • Warns of weak cells, over/under-voltage and thermal danger
  • Supports balancing to keep cells matched and extend life
  • Estimates state of charge and enforces safe limits
  • Logs and trends every cell so degradation is visible

Real-World Applications

SettingHow it is used
Home/solar storage batteriesKeeping a storage pack safe and healthy with per-cell visibility and balancing.
DIY / repurposed packsMonitoring 18650/LiFePO4 packs for imbalance and thermal safety.
E-mobility / robotics packsWatching cells in EV/robot batteries for weak cells and heat.
Battery education / testingLearning cell balancing, SoC and thermal management.

Deployment contexts where a build of this kind earns its keep.

Features & Capabilities

  • Per-cell voltage monitoring (manage to the worst cell)
  • Imbalance tracking for early weak-cell detection
  • Temperature and rate-of-rise thermal warning
  • Cell balancing support
  • State-of-charge estimation and safe-limit enforcement
  • Per-cell trending for degradation
  • Honest: monitoring/logic, not a replacement for protective BMS

Difficulty, Time & Required Skills

AttributeValue
Difficulty levelAdvanced
Estimated completion time14–22 hours
Indicative build cost₹3,500 – ₹6,500
Primary disciplineEnergy
Reference platformESP32 DevKit V1 (ESP-WROOM-32)

Skills you should have (or will pick up)

  • Per-cell voltage measurement (mux/isolation)
  • Temperature and current sensing
  • Imbalance and state-of-charge estimation
  • Cell balancing
  • Safe-limit enforcement and alarming

Bill of Materials

Every part below is commonly available from Indian and international hobby-electronics suppliers. Prices are indicative 2026 retail figures in Indian rupees and will drift — treat them as a budgeting guide, not a quotation.

ComponentKey specificationQtyApprox. cost
ESP32 DevKit V1 (ESP-WROOM-32)
Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC1₹450
INA219 high-side DC current/power monitor
Reports bus voltage, shunt voltage, current and power directly — no maths needed.
0–26 V bus, ±3.2 A with 0.1 Ω shunt, 12-bit, ±0.5 % gain error1₹260
DS18B20 waterproof temperature probe
Dozens can share one GPIO — you address them by ROM code.
−55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID1₹160
ACS712 hall-effect current sensor (20 A)
Zero-offset drifts with temperature — re-zero at boot with no load.
±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation1₹180
0.96″ SSD1306 OLED display
Static images burn in — invert or scroll the screen periodically.
128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C1₹250
microSD card 32 GB A1 class
For 24/7 loggers buy a high-endurance card — normal cards die in months.
A1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended1₹450
5 V 3 A regulated SMPS adapter
Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A.
100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection1₹350
Per-cell voltage sensing
A dedicated multi-cell monitor IC is safest/most accurate
Isolated/mux per-cell taps or a battery-monitor IC (e.g. multi-cell AFE)1₹1,200
Balancing circuitryPassive (bleed resistors + FETs) or active balancing per cell1₹800
Pack temperature sensorsMultiple DS18B20/thermistors across the pack2₹400
Protection (for a real BMS)
A monitor alone does not protect; protection cuts off on fault
Charge/discharge cutoff (FETs/contactor) — required for protection1₹1,000

Estimated total: ₹5,500, excluding tools, shipping and consumables.

Tools and consumables

  • Soldering iron (temperature controlled, 350 °C) with 0.8 mm 60/40 or lead-free solder
  • Digital multimeter — continuity, DC volts and current ranges
  • Wire strippers, flush cutters and a small set of precision screwdrivers
  • Heat-shrink tubing and a heat gun (or a lighter, carefully)
  • A laptop with a USB port and the toolchain listed above

Hardware Specifications

PartSpecificationSupplyInterfaceReference
ESP32 DevKit V1 (ESP-WROOM-32)Dual-core Xtensa LX6 @ 240 MHz, 520 KB SRAM, 4 MB flash, Wi-Fi 802.11 b/g/n + BLE 4.2, 34 GPIO, 18× 12-bit ADC, 2× 8-bit DAC3.3 V logic / 5 V USBUART, SPI, I²C, I²S, CAN, PWMDatasheet
INA219 high-side DC current/power monitor0–26 V bus, ±3.2 A with 0.1 Ω shunt, 12-bit, ±0.5 % gain error3–5.5 VI²C (0x40–0x4F)Datasheet
DS18B20 waterproof temperature probe−55 to +125 °C, ±0.5 °C from −10 to +85 °C, 9–12-bit resolution, unique 64-bit ROM ID3.0–5.5 V1-Wire (multi-drop)Datasheet
ACS712 hall-effect current sensor (20 A)±20 A, 100 mV/A, 80 kHz bandwidth, 1.2 mΩ internal resistance, 2.1 kV isolation5 VAnalogueDatasheet
0.96″ SSD1306 OLED display128 × 64 monochrome, 1.3–3.3 V logic, 100 kHz–400 kHz I²C3.3–5 VI²C (0x3C)Datasheet
microSD card 32 GB A1 classA1 rated, 10 MB/s random write, UHS-I, endurance-grade recommended3.3 VSDIO / SPIDatasheet
5 V 3 A regulated SMPS adapter100–240 VAC in, 5 V ±5 % out, 3 A, short-circuit and over-voltage protection5 VDC barrel / USBDatasheet

Consolidated electrical and interface specifications for every active part in the build.

Power Budget & Supply Sizing

Add up the typical active current of every part, then size the supply with at least 50 % headroom so transmit bursts and motor inrush never brown out the controller.

LoadSupply railTypical current (mA)Notes
ESP32 DevKit V1 (ESP-WROOM-32)3.3 V logic / 5 V USB160Wi-Fi transmit bursts peak near 500 mA — size the regulator accordingly.
INA219 high-side DC current/power monitor3–5.5 V1Reports bus voltage, shunt voltage, current and power directly — no maths needed.
DS18B20 waterproof temperature probe3.0–5.5 V1.5Dozens can share one GPIO — you address them by ROM code.
ACS712 hall-effect current sensor (20 A)5 V10Zero-offset drifts with temperature — re-zero at boot with no load.
0.96″ SSD1306 OLED display3.3–5 V20Static images burn in — invert or scroll the screen periodically.
microSD card 32 GB A1 class3.3 V100For 24/7 loggers buy a high-endurance card — normal cards die in months.
5 V 3 A regulated SMPS adapter5 V3000Measure the real output — many "3 A" adapters sag below 4.7 V at 2 A.

Summed typical draw is 3292.5 mA. With a 1.5× design margin the supply should deliver at least 5000 mA continuously at the stated rail voltage.

Software Requirements & Development Environment

Reference toolchain: Arduino IDE 2.3.x with the ESP32 board package 3.x (or PlatformIO on VS Code). Anything newer normally works; anything older may lack the board definitions used here.

  • Install the Arduino IDE 2.3.x (or PlatformIO if you prefer a real editor and dependency locking).
  • Add https://espressif.github.io/arduino-esp32/package_esp32_index.json under File → Preferences → Additional Board Manager URLs, then install esp32 from the Boards Manager.
  • Set the correct port under Tools → Port. On Linux add yourself to the dialout group: sudo usermod -aG dialout $USER and log out and back in.
  • Open the Serial Monitor at 115200 baud — every sketch here logs its state there.
  • Keep File → Preferences → Show verbose output during: compilation switched on while you are debugging build errors.

Required libraries

LibraryWhy it is neededInstall
WiFi (ESP32 core) bundledStation/AP connection management for the ESP32.Bundled with the ESP32 Arduino core
PubSubClient 2.8Lightweight MQTT 3.1.1 client for constrained devices.Library Manager → "PubSubClient" by Nick O'Leary
ina219lib
OneWire + DallasTemperature 2.3.x / 3.9.xBus enumeration and conversion commands for DS18B20 probes.Library Manager → "DallasTemperature" (pulls OneWire)
Adafruit SSD1306 + GFX 2.5.xFramebuffer and text/graphics primitives for the OLED.Library Manager → "Adafruit SSD1306"
InfluxDB 2.x + Telegraf 2.7Time-series storage with retention policies and downsampling.docker run -p 8086:8086 influxdb:2.7
ArduinoJson 7.xZero-allocation JSON serialisation and parsing.Library Manager → "ArduinoJson" by Benoit Blanchon

Block Diagram

The block diagram shows the functional decomposition of the system — what senses, what decides, what acts, and where the data ends up.

Battery Management Monitor — system block diagramFunctional block diagram of the Battery Management Monitor system. Sense cellsPer-cell Vworst cellTemperatureacross packCurrentcharge/dischargeAssessESP32imbalance/SoCSafe windowper-cell limitsActBalancebleed high cellsProtectcutoff (real BMS)ReportDashboardper-cell trendAlarmweak/hot cellrightrightnone
Battery Management Monitor — system block diagram

Circuit Diagram & Wiring

Every signal line in the build is shown below, followed by a pin-by-pin connection table you can work through with a multimeter in hand.

Battery Management Monitor — wiring schematicConnection schematic showing which controller pin drives each peripheral. Sensors / InputsControllerActuators / OutputsESP32 DevKit V1(ESP-WROOM-32)3.3 V logic / 5 V USBPer-cell taps / AFEADC / busPer-cell voltageINA219 (current)GPIO 21/22Pack current (I²C)Temp sensorsGPIO 4Pack temperature(s)Balancing FETsGPIOBleed high cellsProtection cutoffGPIO 26Charge/dischargedisable (real BMS)OLEDGPIO 21/22Cell voltages/tempWi-FiDashboard
Battery Management Monitor — wiring schematic
PeripheralPeripheral pinController pinSignal
Per-cell taps / AFEmux/SPIADC / busPer-cell voltage
INA219 (current)SDA/SCLGPIO 21/22Pack current (I²C)
Temp sensorsDQGPIO 4Pack temperature(s)
Balancing FETsctrlGPIOBleed high cells
Protection cutoffctrlGPIO 26Charge/discharge disable (real BMS)
OLEDSDA/SCLGPIO 21/22Cell voltages/temp
Wi-Fion-chipDashboard

Wire one row at a time and tick it off — most "it does not work" reports trace back to a single swapped pair.

Wiring explanation

  • Sense each cell's voltage safely — a dedicated multi-cell monitor IC (analog front end) is the accurate, safe way; ad-hoc dividers on a high-voltage series stack are error-prone and risky.
  • Manage the pack to the most extreme cell; a healthy total can hide an out-of-limit cell.
  • Place multiple temperature sensors across the pack; a single sensor can miss a hot cell.
  • For a protective BMS, include a charge/discharge cutoff (FETs/contactor) that disconnects on fault — a monitor that only warns does not protect.
  • Battery work is hazardous — guard against shorts and high current, and test safely.
An ESP32 development board with the ESP-WROOM-32 module and USB connector
ESP32 module reading every cell's voltage, temperature and current and managing balance. Photograph sourced from Wikimedia Commons — ESP32 Espressif ESP-WROOM-32 Dev Board.jpg. Reused under the licence stated on that page; please check it before republishing.

System Architecture

Read the stack from the bottom up: physical hardware, the firmware that drives it, the transport that moves data off the device, and the software a human actually looks at.

Battery Management Monitor — architecture stackLayered architecture from hardware to user interface. Hardware layerESP32 DevKit V1 (ESP-WROOM-32) · DS18B20 waterproof temperature probe ·ACS712 hall-effect current sensor (20 A)Driver layerwifi · pubsub · ina219lib · onewireApplication logicsampling loop · filtering · thresholds · state machineTransport layerWi-Fi + MQTT → battery dashboard · TLS · retry and backoffPresentation layerdashboard · mobile notifications · historical charts
Battery Management Monitor — architecture stack

Working Principle

The defining principle of battery management is that a series pack must be managed to its worst cell, not its average, because the cells are in series and share the same current but are not identical. The whole-pack voltage — the only thing a simple gauge shows — is a sum that can look perfectly healthy while one cell inside is over-charged, another over-discharged, or one drifting weak. And in lithium chemistries these single-cell excursions are not merely inefficient; over-charging a cell can drive it into thermal instability and over-discharging can damage it irreversibly. So real battery management measures every cell individually and keeps each within its safe voltage window, treating the most extreme cell as the constraint. This per-cell visibility is the foundation everything else builds on.

From per-cell voltages comes the single most useful health signal: imbalance, the spread between the highest and lowest cell. A fresh, healthy pack's cells track each other closely; as the pack ages, or if a cell is weak, the cells drift apart — one reaches full or empty before the others, limiting the whole pack's usable capacity and, if unmanaged, being repeatedly pushed to its limits and degrading faster. A growing imbalance is therefore an early warning of a weak or ageing cell, and the cue for balancing: bleeding charge from the highest cells (passive balancing) or moving charge between cells (active balancing) so they finish charging together. Balancing keeps the pack matched, restores usable capacity, and protects the weak cell from being over-driven — one of the most important things a battery system does for longevity.

Temperature is the safety-critical channel, because heat is both the enemy of battery life and the signature of danger. Batteries age faster hot, and — crucially — an abnormally hot cell, or a rising rate of temperature, can be the leading edge of a thermal event (in the worst case, thermal runaway). So the monitor watches temperature at multiple points (a single sensor can miss a localised hot cell), warns on both absolute temperature and rate of rise, and treats a thermal excursion as an alarm that must halt charging/discharging. Alongside, current measurement enables state-of-charge estimation (by integrating charge in and out, refined by cell voltages) and the enforcement of safe charge/discharge limits.

The crucial honesty is the difference between a monitor and a protective battery management system. A monitor senses and warns; a BMS that actually keeps a pack safe must be able to act — to reliably cut off charge or discharge the moment any cell goes out of its safe window or the pack overheats, using FETs or a contactor, independent of any network or human. For lithium packs especially, where the consequences of an unmanaged fault include fire, this protective cutoff is not optional, and it is why serious systems use purpose-built or certified BMS hardware. This project builds the sensing and logic — per-cell voltage, temperature, imbalance, balancing, SoC — which is the heart of understanding battery management and a valuable supplementary monitor, but it is emphatic that a monitor which only warns does not protect, and that a real pack's safety depends on protection that disconnects on fault. Built and used with that clarity, it teaches and demonstrates exactly what keeps a battery pack safe and healthy: see every cell, catch the weak one and the hot one early, keep the pack in balance, and — in a true BMS — cut off before damage is done.

The maths behind it

Per-cell safe window + imbalance

plainPer-cell safe window + imbalance
Each cell must stay in [V_min, V_max] (chemistry-specific,
e.g. LiFePO4 ~2.5–3.65 V):

  fault if any cell < V_min or > V_max
  imbalance = max(cell) − min(cell)
  balance when imbalance > threshold (during charge)

Manage to the WORST cell, not the pack average.

Thermal warning

plainThermal warning
Watch absolute temperature and rate of rise:

  warn if T_cell > T_warn  OR  dT/dt > R_warn
  fault/cutoff if T_cell > T_max

Rising temperature can precede a thermal event → halt
charge/discharge (in a real BMS).

State of charge (coulomb counting + voltage)

plainState of charge (coulomb counting + voltage)
SoC by integrating current, anchored by voltage:

  SoC += (I · dt) / capacity        (coulomb counting)
  correct near full/empty using cell voltages (OCV)

Enforce safe charge/discharge current and SoC limits.

Program Flowchart

The firmware is a single cooperative loop. Nothing blocks for long, so networking, sensing and the user interface all stay responsive.

Battery Management Monitor — firmware flowchartControl flow through the main program loop. Read all cells, temp, currentAny cell out of window ortoo hot?Alarm (+ protective cutoff on real BMS)Check balanceAlarm (+ protective cutoff onreal BMS)Check balanceImbalance high (charging)?Balance (bleed high cells)Update SoC/trendBalance (bleed high cells)Update SoC/trendReport; repeat
Battery Management Monitor — firmware flowchart

Assembly Instructions

Build on a breadboard first and only commit to solder once the whole system has run for an hour without a fault.

  1. Set up safe per-cell sensing

    Use a dedicated multi-cell monitor IC (or carefully-isolated per-cell taps) to measure each cell's voltage accurately and safely, plus multiple temperature sensors and a current sensor.

  2. Add balancing and (for a BMS) protection

    Add balancing circuitry (passive bleed or active) per cell, and — for a protective BMS — a charge/discharge cutoff (FETs/contactor) that disconnects on any fault.

  3. Set up logic and reporting

    Compute imbalance, SoC and thermal state; enforce safe limits; log/trend per cell; and report/alarm.

Step-by-Step Implementation Guide

Work through these in order. Each step ends in something you can observe, so a failure is always localised to the step you just finished.

  1. Evaluate cells, balance and enforce safety

    Read all cells/temperatures/current, check each cell against its window and the thermal limits, balance during charge when imbalance is high, and (in a real BMS) cut off on fault.

    cppbms-logic.ino
    #define V_MIN 2.5f
    #define V_MAX 3.65f          // LiFePO4 example
    #define IMBAL_THRESH 0.05f   // 50 mV spread triggers balancing
    #define T_WARN 45.0f
    #define T_MAX  60.0f
    #define NCELLS 8
    
    const char* evaluate(float *v, float *t, int nT, bool charging){
      float hi=-1, lo=9; int hiIdx=-1;
      for(int i=0;i<NCELLS;i++){
        if (v[i] > V_MAX) return "cell over-voltage";       // manage to worst cell
        if (v[i] < V_MIN) return "cell under-voltage";
        if (v[i]>hi){ hi=v[i]; hiIdx=i; }
        if (v[i]<lo) lo=v[i];
      }
      for(int i=0;i<nT;i++){
        if (t[i] > T_MAX) return "over-temperature (cutoff)"; // thermal fault
        if (t[i] > T_WARN) return "temperature high (warn)";
      }
      float imbalance = hi - lo;
      if (charging && imbalance > IMBAL_THRESH) balanceCell(hiIdx);  // bleed highest
      return nullptr;
    }
    if (v[i] > V_MAX) return "cell over-voltage"Every cell is checked against its safe window individually — an over- or under-voltage on any single cell is a fault, because the pack must be managed to its worst cell.
    if (t[i] > T_MAX) return "over-temperature (cutoff)"A cell exceeding its thermal limit is a fault that, in a protective BMS, must cut off charge/discharge — heat is the safety-critical channel.
    float imbalance = hi - loThe spread between the highest and lowest cell is the imbalance, the early-warning signal for a weak or ageing cell.
    if (charging && imbalance > IMBAL_THRESH) balanceCell(hiIdx)When charging and the imbalance is high, charge is bled from the highest cell so the cells finish together — balancing that protects the weak cell and restores capacity.
  2. Estimate SoC, log and report

    Integrate current for state of charge (anchored by cell voltages), log and trend per cell for degradation, and report/alarm — cutting off on fault in a real BMS.

Complete Source Code

The listing below is complete and compiles as written — there are no elided sections. Read the annotations under each block before you upload it.

cppbattery-management-monitor.ino
/* ═══════════════════════════════════════════════════════════════
   Battery Management Monitor — ESP32 (sensing + logic; EDUCATIONAL)

   Measures per-cell voltage, pack temperature(s) and current; tracks
   imbalance and SoC; warns of weak/hot cells; supports balancing.
   A real protective BMS must CUT OFF on fault — a monitor that only
   warns is a supplement, not a replacement (lithium needs protection).
   ══════════════════════════════════════════════════════════════════ */

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define NCELLS 8
#define NTEMP  2
#define V_MIN 2.5f
#define V_MAX 3.65f
#define IMBAL_THRESH 0.05f
#define T_WARN 45.0f
#define T_MAX  60.0f
#define PIN_CUTOFF 26      // charge/discharge disable (real BMS)

Adafruit_INA219 ina; OneWire ow(4); DallasTemperature temp(&ow);
WiFiClient net; PubSubClient mqtt(net);
float soc = 50.0f; uint32_t lastMs=0;

float readCell(int i){ /* mux/AFE read of cell i */ return readCellVoltage(i); }
void balanceCell(int i){ setBalanceFet(i, true); }   // bleed highest cell

void setup(){
  Serial.begin(115200);
  pinMode(PIN_CUTOFF, OUTPUT); digitalWrite(PIN_CUTOFF, HIGH); // enabled
  Wire.begin(21,22); ina.begin(); temp.begin();
  WiFi.begin(WIFI_SSID,WIFI_PASS); mqtt.setServer(MQTT_HOST,1883);
  lastMs = millis();
}

void loop(){
  if(!mqtt.connected()&&WiFi.status()==WL_CONNECTED) mqtt.connect("bms-1");
  mqtt.loop();
  uint32_t now=millis(); float dtHr=(now-lastMs)/3600000.0f; lastMs=now;

  float v[NCELLS], hi=-1, lo=9; int hiIdx=-1;
  for(int i=0;i<NCELLS;i++){ v[i]=readCell(i);
    if(v[i]>hi){hi=v[i];hiIdx=i;} if(v[i]<lo)lo=v[i]; }

  float t[NTEMP]; temp.requestTemperatures();
  for(int i=0;i<NTEMP;i++) t[i]=temp.getTempCByIndex(i);

  float current = ina.getCurrent_mA()/1000.0f;      // + charge / - discharge
  soc += (current * dtHr) / PACK_AH * 100.0f;       // coulomb counting
  soc = constrain(soc, 0, 100);

  // safety evaluation (manage to worst cell)
  const char *fault=nullptr;
  for(int i=0;i<NCELLS;i++){
    if(v[i]>V_MAX){fault="cell over-voltage";} if(v[i]<V_MIN){fault="cell under-voltage";}
  }
  for(int i=0;i<NTEMP;i++) if(t[i]>T_MAX) fault="over-temperature";

  if (fault){
    digitalWrite(PIN_CUTOFF, LOW);                   // PROTECT: disconnect
    mqtt.publish("bms/1/fault", fault);
  } else {
    digitalWrite(PIN_CUTOFF, HIGH);
    float imbalance = hi - lo;
    bool charging = current > 0.1f;
    if (charging && imbalance > IMBAL_THRESH) balanceCell(hiIdx);  // balance
    if (t[0] > T_WARN) mqtt.publish("bms/1/warn","temperature high");
  }

  char m[260];
  int n = snprintf(m,sizeof m,
    "{\"soc\":%.0f,\"imbalance_mV\":%.0f,\"tmax\":%.1f,\"cells\":[",
    soc,(hi-lo)*1000, t[0]);
  for(int i=0;i<NCELLS;i++) n+=snprintf(m+n,sizeof m-n,"%.3f%s",v[i],i<NCELLS-1?",":"");
  snprintf(m+n,sizeof m-n,"]}");
  mqtt.publish("bms/1/cells", m);

  delay(1000);
}
if (fault){ digitalWrite(PIN_CUTOFF, LOW); // PROTECT: disconnectOn any cell over/under-voltage or over-temperature, a protective BMS disconnects charge/discharge — the action that a monitor which only warns cannot provide.
for(int i=0;i<NCELLS;i++){ v[i]=readCell(i);Every cell is read individually so the worst cell — not a flattering pack average — governs the safety decision.
soc += (current * dtHr) / PACK_AH * 100.0f;State of charge is estimated by coulomb counting (integrating current), refined near the extremes by cell voltages.
if (charging && imbalance > IMBAL_THRESH) balanceCell(hiIdx)During charge, the highest cell is bled when the imbalance grows, keeping the cells matched and protecting the weak one.
A real protective BMS must CUT OFF on faultThe header states the scope: this builds the sensing/logic and a protective cutoff, but a real pack's safety depends on protection that reliably disconnects — lithium needs a proper BMS.

Configuration & Calibration

Configuration steps

  • Set the chemistry-specific per-cell V_min/V_max, thermal limits and imbalance threshold.
  • Configure per-cell sensing (AFE/mux), temperature/current sensing, balancing and (for a BMS) the cutoff.
  • Set pack capacity for SoC and safe charge/discharge limits.
  • Configure logging/trending and alarms.

Calibration procedure

An uncalibrated sensor produces confident, precise, wrong numbers. Do this once per physical unit and record the constants.

  1. Cell voltage accuracy

    Verify per-cell voltage readings against a reference meter; accuracy matters near the safe limits.

  2. SoC

    Calibrate coulomb counting against known full/empty points; anchor with cell voltages.

  3. Thermal

    Verify temperature sensors and set warn/max limits for the chemistry; test rate-of-rise warning.

Network Architecture & Connectivity

Battery Management Monitor — network topologyPath taken by telemetry from field node to end user. Edge nodesGatewayCloudClientsBMS monitorESP32Packsper-packWi-Fi 2.4 GHzRouterto serverMQTT 1883Battery dashboardcells + faultsDashboardper-cellPhonefault alerts
Battery Management Monitor — network topology

Communication protocol

Per-cell voltages, imbalance, temperatures, current and SoC publish continuously; faults publish immediately. Protective cutoff (real BMS) is local and independent of the network.

Topic / endpointDirectionPayload
bms/1/cellsnode → dashboardper-cell V, imbalance, temp, SoC
bms/1/faultnode → ownerover/under-voltage, over-temp (cutoff)
bms/1/warnnode → ownertemperature high / imbalance

Message contract between the device and the broker.

Cloud platform configuration

A dashboard trends every cell for degradation, shows imbalance/temperature/SoC, and alarms on faults; protective cutoff stays local for safety.

Dashboard setup

Per-cell voltage bars and trends, imbalance, temperatures, current and SoC, with fault/warn markers.

Mobile app integration

Immediate fault alerts (over/under-voltage, over-temp) and warnings (imbalance, high temp).

Security considerations

  • Keep protective cutoff local and independent of the network.
  • Authenticate nodes; secure battery data.
  • Alert on monitor silence — a blind pack is a risk.

Testing Procedure & Expected Output

Test from the bottom up. Confirm power, then each sensor in isolation, then the integrated loop — the first failing step tells you exactly where to look.

TestWhat you should see
Drive a cell toward V_max/V_minFault; (real BMS) cutoff; alarm — managed to worst cell
Create an imbalance during chargeHighest cell balanced (bled); imbalance reduces
Heat a cellTemperature warn, then over-temp cutoff/alarm
Charge/discharge a known amountSoC tracks; anchored by voltages near extremes
Trend cells over cyclesA weak/ageing cell drifts/sags — visible for action
Fault with a monitor-only buildWarns but cannot protect — confirming a real BMS needs cutoff

Bench-test checklist. If a row fails, stop and fix it before moving on.

Expected output

The dashboard shows each cell's voltage, the imbalance, temperatures, current and SoC, with per-cell trends and alarms for weak/hot cells; a real BMS shows cutoff events.

jsonbms-cells.json
{
  "soc": 68,
  "imbalance_mV": 72,
  "tmax": 41.0,
  "cells": [3.31, 3.33, 3.28, 3.34, 3.32, 3.33, 3.26, 3.33]
}

Cell 7 (3.26 V) is the lowest and the 72 mV imbalance flags a weak cell developing — visible per-cell before the pack total would ever reveal it, and the cue to balance and watch that cell.

A Grafana time-series dashboard
A dashboard trends each cell so a weak or hot one is caught before it becomes a hazard. Photograph sourced from Wikimedia Commons — Grafana dashboard.png. Reused under the licence stated on that page; please check it before republishing.

Troubleshooting: Common Errors & Fixes

Pack total fine but cells damaged

Likely cause. Only whole-pack monitoring

Fix. Monitor per cell; manage to the worst cell

Growing imbalance

Likely cause. Weak/ageing cell; no balancing

Fix. Enable balancing; identify and (if needed) replace the weak cell

Missed a hot cell

Likely cause. Single temperature sensor

Fix. Use multiple sensors; watch rate of rise

SoC drifts

Likely cause. Coulomb-count drift

Fix. Anchor with cell voltages near full/empty; calibrate capacity

Monitor did not protect

Likely cause. Monitor without cutoff

Fix. A real BMS must cut off on fault; use protective hardware for a real pack

The sketch will not upload — "Failed to connect" or "avrdude: stk500_recv()"

Likely cause. The bootloader is not being reached: wrong port, wrong board, a serial monitor holding the port open, or a USB cable that only carries power.

Fix. Close every serial monitor, confirm Tools → Board and Port, and swap to a known data-capable USB cable. On an ESP32 hold BOOT while the IDE prints "Connecting…", then release. If a peripheral is wired to the UART pins (GPIO 1/3 on ESP32, D0/D1 on Uno) unplug it — it fights the programmer.

The board resets in a loop, or the serial monitor prints "Brownout detector was triggered"

Likely cause. The supply cannot deliver peak current. Wi-Fi transmit bursts, relay coils and servos all pull far more than their average draw.

Fix. Power peripherals from a separate regulated supply with a common ground rather than from the board 5 V pin. Add a 470–1000 µF electrolytic capacitor across the supply near the load, and use a real power adapter rather than a laptop USB port.

Serial monitor shows garbage characters

Likely cause. Baud rate mismatch between Serial.begin() and the monitor, or a floating/shared UART line.

Fix. Set the monitor to 115200 to match the sketch. If it still garbles, the crystal or the USB bridge is being confused by noise — shorten the cable and keep motor wiring away from the USB lead.

An I²C device is not detected

Likely cause. Wrong address, missing pull-ups, swapped SDA/SCL, or a bus too long for the pull-up value.

Fix. Run an I²C scanner sketch first — it should print the device address. Most breakout boards include 4.7 kΩ pull-ups, but if you have chained four of them the parallel resistance is too low; remove the pull-ups from all but one board. Keep the bus under 30 cm at 100 kHz.

Wi-Fi connects but MQTT never does (state -2)

Likely cause. Wrong broker address or port, a firewall in the way, or the broker requiring credentials the sketch is not sending.

Fix. Test from a laptop on the same network first: mosquitto_sub -h <broker> -t "#" -v. If that works, the problem is on the device — check the IP literal, port 1883 (or 8883 for TLS), and that client.setServer() runs before connect(). PubSubClient state codes are documented in its header.

Readings arrive for a while and then stop

Likely cause. The Wi-Fi or MQTT session dropped and the sketch never reconnects, or the broker dropped the client on keep-alive timeout.

Fix. Never assume the link stays up. Check WiFi.status() and client.connected() at the top of every loop and reconnect with exponential backoff. Add a watchdog so a wedged network stack reboots the device instead of going silent.

Performance Optimisation

  • Read all cells each cycle; per-cell visibility is the whole point.
  • Keep protective cutoff local and fast; never depend on the network for safety.
  • Balance during charge when imbalance is high.
  • Trend per cell for degradation.
  • Replace every delay() with a millis() comparison — blocking delays are the single most common cause of dropped readings.
  • Sample sensors on a fixed cadence and publish on a slower one; you almost never need to transmit at the sampling rate.
  • Move networking into its own FreeRTOS task so a slow DNS lookup cannot stall the control loop.
  • Use uint8_t / uint16_t where the range allows; on an 8-bit AVR a 32-bit add costs four times as much.
  • Batch several samples into one MQTT publish. Radio time, not CPU time, dominates the energy budget.
  • Set the MQTT keep-alive to a value that matches your reporting interval so the broker does not churn reconnections.
  • For battery builds use deep sleep between samples: an ESP32 drops from ~160 mA awake to about 10 µA asleep, which is the difference between days and months of runtime.

Safety Precautions

  • A real protective BMS must cut off charge/discharge on fault; a monitor that only warns is a supplement, not a replacement — lithium packs require proper protection.
  • Manage the pack to the worst cell; over/under-voltage or over-temperature of any cell is a fault.
  • Battery work is hazardous (fire, shorts, high current) — build/test with appropriate safety and use proper multi-cell front ends.
  • For real packs, use purpose-built/certified BMS hardware; this is educational sensing/logic.
  • Wear eye protection when soldering or cutting, and solder in a ventilated space — rosin flux fumes are a respiratory irritant.
  • Power the circuit through a bench supply with a current limit while you are testing. A 300 mA limit turns a wiring mistake into a beep instead of a dead board.
  • Disconnect power before changing any wiring. Hot-plugging a sensor onto a live bus is the fastest way to lose a controller.

Maintenance

  • Verify per-cell voltage/temperature calibration periodically.
  • Act on imbalance/weak-cell trends; balance or replace cells.
  • Test the protective cutoff (real BMS) regularly.
  • Re-calibrate SoC/capacity as the pack ages.
  • Re-check every screw terminal and header after the first week — thermal cycling loosens connections that felt tight on day one.
  • Rotate the microSD card annually and keep an image of the working system. Cards used as loggers wear out silently.
  • Keep the broker and dashboard containers patched, and rotate device credentials at least once a year.
  • Recalibrate at the interval given in the calibration section, and keep the constants in a text file next to the firmware — not only in flash.
  • Keep a short logbook of firmware versions and what changed. Six months later you will not remember why that constant is 1.083.

Future Improvements & Upgrades

A working v1 is a platform, not a finish line. These are the upgrades that add the most capability for the least rework.

  • Add active balancing for efficiency.
  • Add impedance/health estimation per cell.
  • Integrate with charger/inverter for coordinated protection.
  • Add state-of-health and remaining-life estimation.
  • Design a proper PCB. Once the breadboard version has run for a month, moving to a two-layer board removes the intermittent-contact failures that dominate prototype faults.
  • Add over-the-air firmware updates so you never have to physically reach a deployed node again.
  • Add persistent local storage (microSD or the on-chip flash) so a network outage does not create a hole in your data.
  • Move configuration out of the source: a captive-portal setup page or a JSON config file makes the build reusable without a recompile.
  • Add a battery and solar option so the unit survives a power cut and can be sited away from a socket.
  • Write a small test harness that feeds synthetic sensor values through the decision logic, so you can validate thresholds without physically triggering the event.

Frequently Asked Questions

Why monitor every cell instead of the pack total?

Because a series pack must be managed to its worst cell. The total can look healthy while one cell is over-charged, over-discharged or overheating — all of which damage the pack and, for lithium, are safety risks that only per-cell monitoring reveals.

What is balancing and why does it matter?

Bleeding charge from the highest cells (or moving charge between cells) so they finish charging together. It keeps cells matched, restores usable capacity, and protects a weak cell from being repeatedly over-driven — key to pack longevity.

Why is temperature so important?

Heat ages batteries and, more seriously, an abnormally hot cell or a rising temperature can precede a thermal event. Watching temperature (and its rate of rise) at multiple points is the safety-critical channel.

Is this a real BMS I can trust to protect my pack?

It builds the sensing and logic (and a protective cutoff), which is the heart of battery management — but a real pack's safety depends on protection that reliably cuts off on fault. For lithium packs, use a purpose-built or certified BMS; treat this as educational and supplementary.

How does it estimate state of charge?

Mainly by coulomb counting — integrating the current in and out over the pack capacity — anchored near full and empty by the cell voltages to correct drift.

References & Learning Resources

These are the primary sources worth reading in full. Manufacturer datasheets always outrank forum posts when the two disagree.

  1. Battery management system (BMS)Reference
  2. Cell balancingReference
  3. Lithium-ion safety and thermal runawayReference
  4. State of charge estimationReference
  5. INA219 current/power monitor (datasheet)TI