Monitoring the health and performance of lithium-ion battery packs is essential for any hardware or embedded project — from portable IoT devices to electric vehicles. Yet many developers focus on software and neglect battery telemetry, overlooking factors that impact safety, longevity, and efficiency.
This article provides a step-by-step walkthrough of how I built a battery monitoring system using Python running on a Raspberry Pi. It covers:
- Hardware components and wiring
- Gathering measurements via analog-to-digital conversion (ADC)
- Implementing safe voltage, current, and temperature readings
- Storing and visualizing data reliably
- Conducting analysis and extracting actionable insights
By the end, you’ll have a fully functional, extendable monitor for almost any battery-powered application.
1. Why Monitor a Lithium Battery?
Lithium-ion chemistry is sensitive: overcharging, deep discharge, high current, or temperature extremes can degrade performance or cause total failure. For battery-powered systems, continuous monitoring provides three major benefits:
- Safety — Detect over-voltage, over-current, over-temperature conditions early.
- Health — Track State of Charge (SoC) and capacity over time to identify aging.
- Efficiency — Log charge/discharge cycles and optimize consumption in your software.
- For applications ranging from remote sensors to robotics, battery telemetry is essential.
2. Components and Hardware Setup
Here’s a summary of the components used in the project:
| Component | Purpose |
| -------------------------- | ------------------------------------- |
| Raspberry Pi 4 (or Zero W) | Core controller and network interface |
| INA219 I²C current sensor | Measures voltage and current |
| DS18B20 temperature sensor | Monitors battery temperature |
| Breadboard, jumpers, etc. | For wiring and prototyping |
| Python 3 environment | Runs monitoring scripts |
Because the Pi lacks analog GPIO pins, the INA219 offers built-in ADC and I²C support, while the DS18B20 provides a low-cost digital temperature interface.
2.1 Wiring the INA219
- Connect VIN+ input to battery positive.
- Connect VIN- input to battery’s load/charge side.
- Connect GND to Raspberry Pi ground.
- Wire SDA and SCL to Pi’s 1 and 3 I²C pins.
- Power the INA219 via Pi’s 3.3 V pin.
2.2 Wiring the DS18B20
- Connect the DS18B20 sensor’s data pin to GPIO4 (pin 7).
- Use a 4.7 kΩ pull-up resistor between data and 3.3 V.
- Ground the sensor via Pi GND.
With power and ground shared, both sensors integrate on the same I²C bus and 1-Wire interface.
3. Software Setup on Raspberry Pi
3.1 Environment Preparation
Begin with a standard Raspbian or Raspberry Pi OS installation:
sudo apt update
sudo apt install python3-pip i2c-tools
sudo pip3 install adafruit-circuitpython-ina219 w1thermsensor pandas matplotlib flask
Enable I²C and 1-Wire:
sudo raspi-config
# Activate “Interface Options” → “I²C” and “1-Wire”
sudo reboot
Verify I²C via:
i2cdetect -y 1
You should see the INA219 at address 0x40.
3.2 Python Script: battery_monitor.py
Here’s a high-level outline of the script:
from ina219 import INA219, BusVoltageRange, INA219Error
from w1thermsensor import W1ThermSensor
import time, csv, datetime
import pandas as pd
# INA219 constants
SHUNT_OHMS = 0.1
MAX_EXPECTED_AMPS = 3.0 # adjust for your pack
ina219 = INA219(SHUNT_OHMS, MAX_EXPECTED_AMPS)
ina219.bus_voltage_range = BusVoltageRange.RANGE_16V
temp_sensor = W1ThermSensor()
CSV_FILE = 'battery_log.csv'
# Write header if missing
with open(CSV_FILE, 'a') as f:
if f.tell() == 0:
f.write('timestamp,voltage_V,current_mA,power_mW,temperature_C\n')
def read_sensors():
v = ina219.voltage()
i = ina219.current() # mA
p = ina219.power() # mW
t = temp_sensor.get_temperature()
ts = datetime.datetime.utcnow().isoformat()
return [ts, v, i, p, t]
# Continuous logging
try:
while True:
data = read_sensors()
with open(CSV_FILE, 'a') as f:
f.write(','.join(map(str, data)) + '\n')
print(data)
time.sleep(5)
except KeyboardInterrupt:
print("Stopping monitoring.")
This script logs timestamped voltage, current, power, and temperature readings to CSV every five seconds and prints them to the console.
4. Visualizing and Analyzing Data
A structured notebook is ideal for post-processing. Here’s a simplified Jupyter/Pandas/Matplotlib workflow:
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv('battery_log.csv', parse_dates=['timestamp'])
df.set_index('timestamp', inplace=True)
# Battery pack characteristics (example)
CAPACITY_mAh = 2000
# Calculate cumulative amp-hours
df['Ah'] = (df.current_mA / 1000).cumsum() * (df.index.to_series().diff().dt.total_seconds() / 3600)
# Plot voltage and temperature over time
fig, ax1 = plt.subplots()
ax2 = ax1.twinx()
df.voltage.plot(ax=ax1, color='b', label='Voltage (V)')
df.temperature_C.plot(ax=ax2, color='r', label='Temperature (°C)')
ax1.set_ylabel('Voltage')
ax2.set_ylabel('Temperature')
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')
plt.title('Battery Voltage & Temperature Over Time')
plt.show()
# State of Charge over time
df['SoC_%'] = 100 - df.Ah / (CAPACITY_mAh / 1000) * 100
df.SoC_%.plot()
plt.title('Estimated State of Charge (%)')
plt.ylabel('%')
plt.show()
With live data, this analysis reveals trends like voltage drops under load, thermal rise during charging, and gradual SoC reduction over cycles.
5. Building a Web Dashboard (Optional)
For remote data access, an interactive web dashboard can be extremely useful. Here’s a minimal Flask example:
from flask import Flask, jsonify
import pandas as pd
app = Flask(__name__)
CSV_FILE = 'battery_log.csv'
@app.route('/metrics')
def metrics():
df = pd.read_csv(CSV_FILE, parse_dates=['timestamp'])
latest = df.iloc[-1].to_dict()
return jsonify(latest)
@app.route('/')
def index():
return '''
<html><body>
<h1>Battery Metrics</h1>
<div id="stats"></div>
<script>
async function fetchMetrics(){
const resp = await fetch('/metrics');
const data = await resp.json();
document.getElementById('stats').innerText = JSON.stringify(data, null, 2);
}
setInterval(fetchMetrics, 5000);
fetchMetrics();
</script>
</body></html>
'''
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
Now you can view current stats via http://raspberrypi.local:8000/ on your local network.
6. Accuracy, Calibration, and Safety
- Shunt resistor calibration: Use a known current load to validate INA219 readings; adjust Python script if necessary.
- Temperature sensor logging: Place the sensor near the battery surface, not in ambient air, for better accuracy.
- Ensure electrical safety: Use proper fuses and isolate high currents from the Pi itself.
- Consider BMS integration: For multi-cell packs, hardware Balancing and overcurrent protection add robustness.
7. Advanced Extensions
This base system is easily extended:
- Cyclic charge/discharge cycles: Automate charging via external relay/H‑bridge and log full-cycle behavior.
- Predictive analytics: Use linear regression or ML models to forecast battery aging.
- MQTT integration: Push telemetry to cloud platforms like Node‑RED, Grafana, or AWS IoT.
- Alerting: Trigger email or SMS notifications when thresholds are exceeded.
8. Lessons Learned
Sampling rate matters — Fast load changes may be missed at low sampling rates.
Cell balancing awareness — Voltage readings reflect pack average, but individual cells may differ significantly.
Data normalization is critical — Compute SoC in mAh rather than raw mA to accommodate variable intervals.
Thermally-aware design — Even small temperature increases under load can compound long-term degradation risks.
Conclusion
Monitoring a lithium-ion battery pack with a Raspberry Pi and Python offers deep insights into real-world battery behavior — and helps ensure your devices are safe, efficient, and reliable. The combination of low-cost sensors, intuitive libraries, and easy-to-analyze data makes this a compelling platform for developers.
By adding data logging, visualization, alerting, and analytics, this system can evolve into a professional-grade battery telemetry and management framework supporting everything from research to production IoT.
Top comments (0)