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)