DEV Community

Docy
Docy

Posted on

How I Monitored My Lithium Battery Pack Using Python and a Raspberry Pi

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.

Image description
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
Enter fullscreen mode Exit fullscreen mode

Enable I²C and 1-Wire:

sudo raspi-config
# Activate “Interface Options” → “I²C” and “1-Wire”
sudo reboot
Enter fullscreen mode Exit fullscreen mode

Verify I²C via:

i2cdetect -y 1
Enter fullscreen mode Exit fullscreen mode

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.")
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)