Skip to content

Instantly share code, notes, and snippets.

@tvwerkhoven
Last active December 10, 2015 01:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tvwerkhoven/4363703 to your computer and use it in GitHub Desktop.
Save tvwerkhoven/4363703 to your computer and use it in GitHub Desktop.
Plot coconutBattery.app output
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
@file coconut_plot.py -- plot Macbook battery data
@author Tim van Werkhoven
@date 20121223
@copyright Copyright (c) 2012 Tim van Werkhoven <timvanwerkhoven@gmail.com>
This scripts plots data exported from [CoconutBattery.app][1] as CSV. This
can be useful to graphically investigate your battery performance.
Besides plotting the data, the script also fits a straight line to the
number of cycles and capacity as function of time, and calculates the
correlation between capacicty and cycles.
Because you might have started logging data later, adding the age in months
with --age will add a zero-point to the data. This adds a tuple (age=0,
capacity=max, cycles=0) to the dataset.
[1]: http://www.coconut-flavour.com/coconutbattery/
This file is licensed under the Creative Commons Attribution-Share Alike
license versions 3.0 or higher, see
http://creativecommons.org/licenses/by-sa/3.0/
"""
#############################################################################
### PREAMBLE
#############################################################################
# Libs
import numpy as np
import scipy as sp
import scipy.stats
import pylab as plt
from matplotlib.dates import datestr2num, epoch2num
import time
import os, sys
import argparse
AUTHOR = "Tim van Werkhoven (timvanwerkhoven@gmail.com)"
DATE = "20121223"
def main():
# Parse & check options
(parser, args) = parsopts()
# Load data: skip one row (header), use comma as delimiter, convert
# first column to data, convert '-' to 0 in last column
cocodat = np.loadtxt(args.datafile, skiprows=1, delimiter=',', dtype=int, converters={0: datestr2num, 2: lambda c: c.strip('-') or -1})
# If the age of the battery is given, add zero-point data to dataset
if (args.age):
# Date in months, average month is 365.25/12 days
secpmon = (24*3600*365.25/12)
zerotime = int(0.5+epoch2num(time.time()-args.age*secpmon))
cocodat = np.vstack([np.r_[zerotime, args.capacity, 0], cocodat])
# Plot capacity as function of time
# =======================================================================
# Calc regression between time and capacity
slope, icept, rval, pval, stderr = scipy.stats.linregress(cocodat[:,0], cocodat[:,1])
print "Plotting Battery capacity vs time (%.2g%%/day)" % (slope*100./args.capacity)
fig = plt.figure(10); fig.clf();
ax1 = fig.add_subplot(111)
ax1.set_title("Battery capacity vs time (%.2g%%/day)" % (slope*100./args.capacity))
# Plot data
ax1.plot_date(cocodat[:,0], cocodat[:,1], 'o')
ax1.set_ylabel("Capacity [mAh]")
# Plot fit
ax1.plot_date(cocodat[:,0], cocodat[:,0]*slope + icept, '--')
# Add second y-axis for percentage data
ax2 = ax1.twinx()
ax2.set_yticks(np.linspace(0,1, len(ax1.get_yticks())))
ax2.set_yticklabels(["%.0f" % x for x in ax1.get_yticks()*100./args.capacity])
ax2.set_ylabel("Capacity [%]")
fig.autofmt_xdate()
fig.savefig("%s_capacity_vs_time.pdf" % (args.plotname))
# Plot cycles as function of time
# =======================================================================
# Select data
cocodat2 = cocodat[cocodat[:,2] >= 0]
# Calc regression between time and cycles
slope, icept, rval, pval, stderr = scipy.stats.linregress(cocodat2[:,0], cocodat2[:,2])
print "Plotting Charging cycles vs time (%.2g/day)" % (slope)
fig = plt.figure(20); fig.clf();
ax1 = fig.add_subplot(111)
ax1.set_title("Charging cycles vs time (%.2g/day)" % (slope))
# Plot data
ax1.plot_date(cocodat2[:,0], cocodat2[:,2], 'o')
# Plot fit
ax1.plot_date(cocodat2[:,0], cocodat2[:,0]*slope + icept, '--')
# Format axes
ax1.set_ylabel("Cycles [N]")
fig.autofmt_xdate()
fig.savefig("%s_cycles_vs_time.pdf" % (args.plotname))
# Plot capacity vs cycles
# =======================================================================
# Calc regression between cycles and capacity
slope, icept, rval, pval, stderr = scipy.stats.linregress(cocodat2[:,2], cocodat2[:,1])
print "Charging cycles vs capacity (R=%.2g)" % (rval)
fig = plt.figure(30); fig.clf();
ax1 = fig.add_subplot(111)
ax1.set_title("Charging cycles vs capacity (R^2=%.2f)" % (rval**2.0))
# Plot data
ax1.plot(cocodat2[:,2], cocodat2[:,1], 'o')
# Plot fit
fitx = np.r_[cocodat2[:,2].min(), cocodat2[:,2].max()]
ax1.plot(fitx, fitx*slope + icept, '--')
# Format axes
ax1.set_ylabel("Capacity [mAh]")
ax1.set_xlabel("Cycles [N]")
# Add second y-axis for percentage data
ax2 = ax1.twinx()
ax2.set_yticks(np.linspace(0,1, len(ax1.get_yticks())))
ax2.set_yticklabels(["%.0f" % x for x in ax1.get_yticks()*100./args.capacity])
ax2.set_ylabel("Capacity [%]")
fig.savefig("%s_capacity_vs_cycles.pdf" % (args.plotname))
def parsopts():
### Parse program options and return results
parser = argparse.ArgumentParser(description='Plot Coconut battery data.', epilog='Comments & bugreports to %s' % (AUTHOR), prog='coconut_plot')
parser.add_argument('datafile',
help='Coconut datafile to process')
parser.add_argument('--capacity', metavar='C', default=5770, type=int,
help='Design capacity of battery (5770 mAh)')
parser.add_argument('--plotname', metavar='name', default='coconut',
help='Names for plot (None)')
parser.add_argument('--age', type=float, help='Battery age in months')
g4 = parser.add_argument_group("Miscellaneous options")
g4.add_argument('-v', dest='debug', action='append_const', const=1,
help='increase verbosity')
g4.add_argument('-q', dest='debug', action='append_const', const=-1,
help='decrease verbosity')
args = parser.parse_args()
# Check & fix some options
checkopts(parser, args)
# Return results
return (parser, args)
# Check command-line options
def checkopts(parser, args):
args.verb = 0
if (args.debug):
args.verb = sum(args.debug)
if (args.verb > 1):
# Print some debug output
print "Running program %s for tgt %s" % (sys.argv[0], args.tgtname)
print args
# In case of errors
if (False):
parser.print_usage()
exit(-1)
# Run main program, must be at end
if __name__ == "__main__":
sys.exit(main())
# EOF
date capacity loadcycles
2012-02-01 5342 -
2012-02-27 5452 -
2012-04-11 5293 -
2012-05-16 5271 -
2012-09-11 5161 -
2012-09-25 5272 -
2012-12-04 5083 -
2012-12-04 5083 139
2012-12-05 5154 141
2012-12-06 4836 141
2012-12-07 5079 141
2012-12-10 4981 142
2012-12-12 5087 143
2012-12-13 5243 143
2012-12-16 5055 146
2012-12-17 4976 146
2012-12-19 5119 147
2012-12-20 5281 148
2012-12-21 5208 149
2012-12-22 5224 149
2012-12-23 5060 149
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment