Figure 1: A professional-grade DIY LiFePO4 battery pack featuring active cell balancing and real-time thermal monitoring via ESP32.
⚠️ CRITICAL SAFETY WARNING: LITHIUM BATTERY FIRE HAZARD
- Thermal Runaway Risk: Even LiFePO4 cells, while much safer than Li-Ion, can vent gases or catch fire if short-circuited, overcharged, or physically damaged. Work in a ventilated area away from flammable materials.
- High Current Danger: A 100Ah LiFePO4 pack can deliver 500A+ in a short circuit. Always use insulated tools, remove jewelry, and work on a non-conductive surface.
- BMS is Mandatory: Never operate a multi-cell LiFePO4 pack without a properly rated BMS. The BMS protects against overvoltage, undervoltage, overcurrent, and cell imbalance — all of which can lead to catastrophic failure.
- Emergency Preparedness: Keep a Class D fire extinguisher or large bucket of sand nearby. Never use water on a lithium battery fire.
Welcome to TechFix Hub. You've decided to build your own LiFePO4 battery pack — a wise choice for 2026 with the explosive growth of EVs, home energy storage, and off-grid solar systems. But here's the hard truth: a pack without proper cell management is a ticking time bomb.
Most commercial BMS units use passive balancing, which wastes energy as heat and is painfully slow. In this comprehensive guide, we'll build a DIY LiFePO4 BMS with active balancing that transfers energy between cells (not wastes it), paired with an ESP32 thermal monitoring system that watches every cell's temperature 24/7 and alerts you before problems escalate. Whether you call it BMS équilibrage actif or active balancing BMS DIY, the result is the same: a safer, longer-lasting, smarter battery pack.
🔋 1. Why Active Balancing Changes Everything
Let's start with the fundamental question: why spend more on active balancing when passive balancing "works"?
The bottom line: For packs under 50Ah, passive balancing is acceptable. For packs 100Ah and above (solar storage, EV conversions, marine), active balancing is not optional — it's essential for safety and longevity.
🛠️ 2. Bill of Materials: Building a Pro-Grade BMS
This BOM is designed for a 4S 100Ah 12V LiFePO4 pack (the most common DIY configuration). Scale accordingly for 8S/16S systems.
Total estimated cost: ~$660 for a 12V 100Ah pack with full active balancing and ESP32 monitoring — roughly 40% less than equivalent commercial units, with 3x the intelligence.
🔧 3. Step-by-Step Assembly: From Cells to Smart Pack
Phase 1: Cell Preparation (The Most Critical Step)
- Top Balancing: Before assembly, charge each cell individually to exactly 3.65V using a lab power supply. Hold at 3.65V until current drops below 0.5A. This ensures all cells start at the same state of charge.
- Capacity Testing: Discharge each cell at 0.5C (50A for 100Ah cells) down to 2.5V using your DIY Electronic Load. Record actual capacity — cells within 5% of each other are acceptable. Reject cells with >10% variance.
- Internal Resistance Check: Use an AC impedance meter or battery analyzer. Cells should have internal resistance within 0.1mΩ of each other. High-IR cells will become weak links.
Phase 2: Mechanical Assembly
- Compression: LiFePO4 cells perform best under slight compression (~150kg force). Use threaded rods with end plates to apply even pressure across the cell stack.
- Busbar Torque: Tighten M6 terminal bolts to exactly 5-6 N·m using a torque wrench. Under-torque = hot spots = fire risk. Over-torque = stripped aluminum terminals.
- Series Connection: Connect cells in series: Cell 1 negative → Cell 2 positive → Cell 2 negative → Cell 3 positive → etc. Total nominal voltage: 4 × 3.2V = 12.8V.
Phase 3: BMS and Active Balancer Installation
- BMS Sense Wires: Connect balance leads in exact order: B- (pack negative) → C1 (between cell 1 & 2) → C2 → C3 → B+ (pack positive). Verify voltage at each wire before connecting to BMS. Wrong order = instant BMS destruction.
- Active Balancer Connection: Most active balancers connect in parallel with the BMS sense wires. They monitor cell voltages and automatically transfer energy from higher cells to lower cells during charge AND discharge.
- Main Power Path: BMS negative output → Class T fuse → main negative terminal. BMS positive connects directly to main positive terminal.
🌡️ 4. ESP32 Thermal Monitoring: Your 24/7 Battery Guardian
This is where the LiFePO4 ESP32 monitor truly shines. While the BMS handles protection, the ESP32 provides intelligence, logging, and remote alerts.
Sensor Placement Strategy
- One DS18B20 per cell: Attach directly to the center of each cell's flat surface using thermal epoxy or Kapton tape. This catches hot spots before they propagate.
- BMS temperature sensor: Most BMS units have built-in temp sensors. Monitor these too — they detect ambient pack temperature.
- Busbar temperature (optional): Add a thermocouple to the hottest busbar connection to detect loose terminals before they fail.
The Complete ESP32 Monitoring Code
// TechFix Hub - LiFePO4 ESP32 Monitor with Active Balancing Status
// Full surveillance batterie ESP32 system with MQTT alerts
#include <WiFi.h>
#include <PubSubClient.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <HardwareSerial.h>
// WiFi & MQTT Configuration
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "192.168.1.100"; // Home Assistant IP
WiFiClient espClient;
PubSubClient mqtt(espClient);
// DS18B20 Temperature Sensors (OneWire bus on GPIO 4)
#define ONE_WIRE_BUS 4
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature tempSensors(&oneWire);
DeviceAddress tempCell1, tempCell2, tempCell3, tempCell4;
// BMS UART Communication (JBD protocol)
HardwareSerial BMS_Serial(2); // UART2 on GPIO 16/17
// Safety Thresholds
const float TEMP_WARNING = 45.0; // °C - Send alert
const float TEMP_CRITICAL = 55.0; // °C - Disconnect loads
const float VOLT_CELL_MAX = 3.65; // V - Per cell
const float VOLT_CELL_MIN = 2.80; // V - Per cell
const float BALANCE_DELTA = 0.020; // V - Active balancer trigger
// Timing
unsigned long lastTempRead = 0;
unsigned long lastBMSRead = 0;
unsigned long lastMQTTPublish = 0;
// Cell data structure
struct CellData {
float voltage;
float temperature;
bool isBalancing;
} cells[4];
float packVoltage = 0;
float packCurrent = 0;
float packSOC = 0;
void setup() {
Serial.begin(115200);
Serial.println("🔋 TechFix Hub LiFePO4 Monitor Starting...");
// Initialize temperature sensors
tempSensors.begin();
tempSensors.getAddress(tempCell1, 0);
tempSensors.getAddress(tempCell2, 1);
tempSensors.getAddress(tempCell3, 2);
tempSensors.getAddress(tempCell4, 3);
tempSensors.setResolution(12); // High precision
// Initialize BMS UART (9600 baud for JBD protocol)
BMS_Serial.begin(9600, SERIAL_8N1, 16, 17);
// Connect WiFi
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n✅ WiFi Connected");
// Connect MQTT
mqtt.setServer(mqtt_server, 1883);
mqtt.setCallback(mqttCallback);
Serial.println("🛡️ Battery monitoring active. Safety thresholds loaded.");
}
void loop() {
// Maintain MQTT connection
if (!mqtt.connected()) {
reconnectMQTT();
}
mqtt.loop();
unsigned long now = millis();
// Read temperatures every 2 seconds
if (now - lastTempRead > 2000) {
readTemperatures();
lastTempRead = now;
}
// Read BMS data every 1 second
if (now - lastBMSRead > 1000) {
readBMSData();
lastBMSRead = now;
}
// Publish to MQTT every 5 seconds
if (now - lastMQTTPublish > 5000) {
publishToMQTT();
lastMQTTPublish = now;
}
// Safety checks (continuous)
checkSafetyLimits();
}
void readTemperatures() {
tempSensors.requestTemperatures();
cells[0].temperature = tempSensors.getTempC(tempCell1);
cells[1].temperature = tempSensors.getTempC(tempCell2);
cells[2].temperature = tempSensors.getTempC(tempCell3);
cells[3].temperature = tempSensors.getTempC(tempCell4);
// Debug output
for (int i = 0; i < 4; i++) {
Serial.print("Cell ");
Serial.print(i+1);
Serial.print(" Temp: ");
Serial.print(cells[i].temperature);
Serial.println("°C");
}
}
void readBMSData() {
// Send read command to JBD BMS (0xDD 0xA5 0x03 0x00 0xFF 0xFD 0x77)
byte readCmd[] = {0xDD, 0xA5, 0x03, 0x00, 0xFF, 0xFD, 0x77};
BMS_Serial.write(readCmd, 7);
// Wait for response
if (BMS_Serial.available()) {
byte response[40];
int len = BMS_Serial.readBytes(response, 40);
// Parse JBD protocol (simplified)
if (len > 10 && response[0] == 0xDD && response[1] == 0x03) {
packVoltage = (response[4] << 8 | response[5]) / 100.0;
packCurrent = (response[6] << 8 | response[7]) / 100.0;
packSOC = response[23]; // State of Charge %
// Read individual cell voltages
for (int i = 0; i < 4; i++) {
cells[i].voltage = (response[8 + i*2] << 8 | response[9 + i*2]) / 1000.0;
}
// Check balancing status (bitmask in response)
byte balanceStatus = response[12];
for (int i = 0; i < 4; i++) {
cells[i].isBalancing = (balanceStatus & (1 << i)) != 0;
}
}
}
}
void publishToMQTT() {
char topic[50], payload[20];
// Pack-level data
mqtt.publish("battery/pack/voltage", String(packVoltage, 2).c_str());
mqtt.publish("battery/pack/current", String(packCurrent, 2).c_str());
mqtt.publish("battery/pack/soc", String(packSOC).c_str());
// Per-cell data
for (int i = 0; i < 4; i++) {
sprintf(topic, "battery/cell%d/voltage", i+1);
mqtt.publish(topic, String(cells[i].voltage, 3).c_str());
sprintf(topic, "battery/cell%d/temperature", i+1);
mqtt.publish(topic, String(cells[i].temperature, 1).c_str());
sprintf(topic, "battery/cell%d/balancing", i+1);
mqtt.publish(topic, cells[i].isBalancing ? "1" : "0");
}
// Cell voltage delta (imbalance indicator)
float maxV = cells[0].voltage, minV = cells[0].voltage;
for (int i = 1; i < 4; i++) {
if (cells[i].voltage > maxV) maxV = cells[i].voltage;
if (cells[i].voltage < minV) minV = cells[i].voltage;
}
float delta = maxV - minV;
mqtt.publish("battery/pack/cell_delta", String(delta, 3).c_str());
}
void checkSafetyLimits() {
// Temperature checks
for (int i = 0; i < 4; i++) {
if (cells[i].temperature > TEMP_CRITICAL) {
mqtt.publish("battery/alert/critical",
String("CRITICAL: Cell ") + (i+1) + " at " + cells[i].temperature + "°C!");
// TODO: Trigger emergency contactor disconnect via GPIO
} else if (cells[i].temperature > TEMP_WARNING) {
mqtt.publish("battery/alert/warning",
String("WARNING: Cell ") + (i+1) + " at " + cells[i].temperature + "°C");
}
}
// Voltage checks
for (int i = 0; i < 4; i++) {
if (cells[i].voltage > VOLT_CELL_MAX || cells[i].voltage < VOLT_CELL_MIN) {
mqtt.publish("battery/alert/voltage",
String("ALERT: Cell ") + (i+1) + " voltage out of range: " + cells[i].voltage + "V");
}
}
// Cell imbalance check (indicates balancer failure)
float maxV = cells[0].voltage, minV = cells[0].voltage;
for (int i = 1; i < 4; i++) {
if (cells[i].voltage > maxV) maxV = cells[i].voltage;
if (cells[i].voltage < minV) minV = cells[i].voltage;
}
if ((maxV - minV) > 0.100) { // 100mV imbalance
mqtt.publish("battery/alert/imbalance",
String("ALERT: Cell imbalance ") + (maxV - minV) + "V - check active balancer!");
}
}
void reconnectMQTT() {
while (!mqtt.connected()) {
Serial.print("Connecting to MQTT...");
String clientId = "ESP32-Battery-" + String(random(0xffff), HEX);
if (mqtt.connect(clientId.c_str(), "mqtt_user", "mqtt_password")) {
Serial.println("connected");
mqtt.subscribe("battery/command/#");
} else {
Serial.print("failed, rc=");
Serial.print(mqtt.state());
Serial.println(" retry in 5s");
delay(5000);
}
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
// Handle remote commands (e.g., reset BMS, change thresholds)
String cmd = String(topic);
if (cmd == "battery/command/reset") {
Serial.println("Remote reset requested");
// TODO: Implement BMS reset via UART
}
}
🏠 5. Home Assistant Integration: Visualizing Your Battery
With MQTT publishing every 5 seconds, Home Assistant can create a beautiful, actionable dashboard:
- Cell Voltage Graph: Use the "ApexCharts Card" to plot all 4 cell voltages over time. Watch them converge during active balancing — it's deeply satisfying.
- Temperature Heatmap: Create a visual grid showing each cell's temperature. Color-code cells: green (<35 red="" yellow="">45°C).35>
- SOC Gauge: A circular gauge showing pack state of charge, with color transitions (green >50%, yellow 20-50%, red <20 li=""> 20>
- Automation Examples:
- "If cell_delta > 50mV for 1 hour → send Telegram alert: 'Active balancer may be failing'"
- "If any cell temp > 50°C → turn off solar charger via MQTT command"
- "If SOC < 15% AND time is off-peak → enable grid charger to protect battery"
🚀 6. The "TechFix Hub" Upgrade: Predictive Battery Health
Take your surveillance batterie ESP32 system from reactive to predictive with these advanced features:
- SOH (State of Health) Estimation: Once a month, trigger a controlled discharge test using your DIY Electronic Load. Compare actual capacity vs. rated capacity. Log the degradation curve to predict remaining lifespan.
- Thermal Camera Integration: Add an MLX90640 thermal camera (I2C) pointed at the cell stack. The ESP32 can detect hot spots invisible to point sensors, catching failing connections days before they become dangerous.
- Machine Learning Anomaly Detection: Use Edge Impulse to train a TinyML model on normal battery behavior. The ESP32 can detect subtle anomalies (unusual voltage sag, temperature spikes) that indicate impending failure.
- Redundant Safety Contactor: Add a high-current relay (e.g., Gigavac GV200) controlled by the ESP32. If the BMS fails to disconnect during a fault, the ESP32 acts as a backup safety system.
🔧 7. Troubleshooting Common Issues
- Cells won't balance despite active balancer: Check that the balancer's current rating matches your cell capacity. A 2A balancer on 280Ah cells will take DAYS to balance. Consider upgrading to a 5A+ balancer or adding multiple units in parallel.
- ESP32 keeps rebooting: The DS18B20 sensors and BMS UART can draw enough current to cause brownout on weak USB power. Use a dedicated 5V 2A regulator for the ESP32, separate from the pack power.
- Temperature readings erratic or -127°C: Check OneWire wiring. DS18B20 requires a 4.7kΩ pull-up resistor on the data line. Verify sensor addresses match physical placement.
- BMS UART not responding: JBD BMS uses 9600 baud. Daly BMS uses 9600 but different protocol. Verify you're using the correct protocol parser. Some BMS units ship with UART disabled — enable via the manufacturer's Bluetooth app first.
- One cell heats more than others: This indicates higher internal resistance in that cell. Check busbar torque on that cell's terminals. If torque is correct, the cell may be defective — isolate and test separately.
🔗 Complete Your Energy Storage System on TechFix Hub
This BMS pairs perfectly with our other power electronics guides:
✅ Conclusion
Building a DIY LiFePO4 BMS with active balancing and ESP32 thermal monitoring is the ultimate project for any serious energy storage enthusiast. You've moved beyond basic battery assembly into the realm of intelligent, self-aware power systems. The active balancer ensures your cells age evenly, extending pack life by years. The ESP32 monitor provides real-time visibility into every cell's health, catching problems before they become emergencies. In 2026, with the rise of home energy storage and EV conversions, these skills are more valuable than ever. Build safe, build smart, and let your battery pack last a decade or more.
🛠️ Building your own LiFePO4 battery pack?
Questions about active balancer selection, DS18B20 placement, or ESP32 MQTT integration? Drop your questions in the comments below, and the TechFix Hub community will help you build a safe, intelligent battery system!