#!/usr/bin/env python2.7 # encoding: utf-8 ## # @file proc_iphone_batt.py # @brief process iphone battery data # @author Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl) # @date 20111001 # # Created by Tim van Werkhoven on 20111001 # Copyright (c) 2011 Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl) # # This work is licensed under the Creative Commons Attribution-Share Alike 3.0 # Unported License. To view a copy of this license, visit # http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative # Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, # USA. # Import libs import argparse import sys, os import numpy as N import scipy as S import scipy.optimize import pylab as plt # Define some contants AUTHOR = "Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl)" DATE = "20111001" # Start functions def datestr2num(datestr): """Convert HH:MM string to number of minutes""" datenum = datestr.split(':') return int(datenum[0])*60 + int(datenum[1]) def plot_data(xdat, desc=None, usage=None, standby=None, do_fit=True, clf=True, nsig=3): """Plot battery usage. If , also fit data and overplot. If , first clear the plot.""" if (usage == None and standby == None): return if (clf): plt.clf() suptit = "iPhone 3GS battery usage" if (desc): suptit += u" -- " + desc plt.suptitle(suptit, fontsize=12) subtit = u'' plt.xlabel("Time [hrs]") plt.ylabel("Battery [%]") plt.ylim(0, 100) plt.xlim(0, 35) if (usage != None): plt.plot(usage, xdat, 'b+', label='Usage') if (do_fit): p1, s1 = fit_lindat(xdat, usage) print "Fit %s: A=%g ± %g" % (desc, p1, s1) subtit += u"Usage: %.3g ± %.3g hrs, " % (100.0/p1, nsig*100*s1/(p1**2.)) # Plot fits, with +- nsig sigma bounds plot_fit(p1, s1, nsig, 'b-') if (standby != None): plt.plot(standby, xdat, 'g.', label='Standby') if (do_fit): p1, s1 = fit_lindat(xdat, standby) print "Fit %s: A=%g ± %g" % (desc, p1, s1) subtit += u"Standy: %.3g ± %.3g hrs, " % (100.0/p1, nsig*100*s1/(p1**2.)) plot_fit(p1, s1, nsig, 'g-') subtit += "errors: %d$\sigma$" % (nsig) plt.title(subtit, fontsize=10) print desc + " " + subtit if (usage != None and standby != None): plt.legend() plt.draw() def plot_fit(par1, sig1, nsig=2, *args): """Plot the model y = 100 - A*x with a=+-*""" fitfunc1 = lambda p, x: 100 - p*x fitx1 = N.linspace(0, 120/(par1-(nsig*sig1)), 100) plt.plot(fitx1, fitfunc1(par1, fitx1), *args) plt.plot(fitx1, fitfunc1(par1+nsig*sig1, fitx1), *args) plt.plot(fitx1, fitfunc1(par1-nsig*sig1, fitx1), *args) def fit_lindat(xdat, ydat): """ Given and , fit the model y = 100 - A*x. Return the parameter A including error as tuple. """ fitfunc1 = lambda p, x: 100 - p*x fitout1 = S.optimize.curve_fit(fitfunc1, ydat, xdat) # Parameter and standard deviation par1 = fitout1[0][0] sig1 = N.sqrt(fitout1[1][0]) return (par1, sig1) def main(): # Parse & check options (parser, args) = parsopts() # Print some debug output print "Running program %s" % (sys.argv[0]) print args print sys.argv # Load data, convert HH:MM formats to minutes rawdat = N.loadtxt(args.datafile, dtype=int, delimiter=',', converters = {0: lambda x: x[4:-4], 1: datestr2num, 2: datestr2num}) # Sort data per measurement mode set1sl = slice(0, N.argwhere(rawdat[:,0] == 85)[0,0]) set2sl = slice(set1sl.stop, N.argwhere(rawdat[:,0] == 128)[0,0]) set3sl = slice(set2sl.stop, N.argwhere(rawdat[:,0] == 257)[0,0]) print set1sl, set2sl, set3sl rawdatsets = {'All': rawdat, '2G iOS 4': rawdat[set1sl], '3G iOS 4': rawdat[set2sl], '3G iOS 5': rawdat[set3sl]} # Loop over different measurement sets for (key, thisdat) in rawdatsets.iteritems(): print "Processing dataset '%s' shape:" % (key), thisdat.shape # Make aliases for data dat_use = thisdat[:,1]/60. dat_stdby = thisdat[:,2]/60. dat_perc = thisdat[:,3] # Filter different datasets (i.e. strictly monotonic declining data) data_mask = N.where((dat_perc[1:] - dat_perc[:-1]) > 0) data_sets = [] prev_mask = 0 for mask in data_mask[0]: # Only select sets which cover 35% battery usage range if (thisdat[prev_mask, 2] - thisdat[mask, 2] > 30): data_sets.append(thisdat[prev_mask:mask+1]) prev_mask = mask+1 # Plot all data sets fnamebase = "iphone_batt_%s" % key.replace(" ","_") if (args.wofits): plot_data(dat_perc, key, usage=dat_use, standby=dat_stdby, do_fit=False) plt.savefig(fnamebase + "-both.pdf") plot_data(dat_perc, key, usage=dat_use, standby=dat_stdby, do_fit=True) plt.savefig(fnamebase + "-both+fits.pdf") if (args.sep): if (args.wofits): plot_data(dat_perc, key, standby=dat_stdby, do_fit=False) plt.savefig(fnamebase + "-standby.pdf") plot_data(dat_perc, key, standby=dat_stdby, do_fit=True) plt.savefig(fnamebase + "-standby+fits.pdf") if (args.wofits): plot_data(dat_perc, key, usage=dat_use, do_fit=False) plt.savefig(fnamebase + "-use.pdf") plot_data(dat_perc, key, usage=dat_use, do_fit=True) plt.savefig(fnamebase + "-use+fits.pdf") # Plot individual datasets # if (args.indiv): # plt.clf() # for dset in data_sets: # plot_data(dset[:,2], key, usage=dset[:,2]/60., standby=dset[:,1]/60., do_fit=True, clf=False) # # plt.savefig(fnamebase + "-both-sep+fits.pdf") def parsopts(): ### Parse program options and return results parser = argparse.ArgumentParser(description='This program parses iphone_batt.txt as csv and plots the results.', epilog='Comments & bugreports to %s' % (AUTHOR)) parser.add_argument('datafile', type=str, default="iphone_batt.txt", help='datafile to process (iphone_batt.txt)') parser.add_argument('--wofits', action='store_true', default=False, help='Also plot data without fits (False)') parser.add_argument('--sep', action='store_true', default=False, help='Also plot usage and standby times seperately (False)') # parser.add_argument('--indiv', action='store_true', default=False, # help='Also plot datasets individually (per charge cycle) (False)') parser.add_argument('--version', action='version', version='%(prog)s 0.1') args = parser.parse_args() # Check & fix some options checkopts(parser, args) # Return results return (parser, args) def checkopts(parser, args): # File should exist if (not os.path.exists(args.datafile) or not os.path.isfile(args.datafile)): print "Error: datafile should exist and be a regular file!" parser.print_usage() exit(-1) if __name__ == "__main__": sys.exit(main())