Created
November 6, 2011 22:29
-
-
Save tvwerkhoven/1343678 to your computer and use it in GitHub Desktop.
iPhone 3GS battery life analysis & plots
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
These files can be used to analyse the battery usage of an iPhone 3GS under real-life usage. | |
iphone_battery.csv: measurement data | |
proc_iphone_batt.py: process & plot data |
We can make this file beautiful and searchable if this error is corrected: It looks like row 3 should actually have 1 column, instead of 4 in line 2.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# iphone battery | |
# 20111001 Tim | |
# screenshot fname, usage time [hh:mm], standby time [hh:mm], battery [%] | |
IMG_0001.PNG, 0:59, 1:00, 92 | |
IMG_0002.PNG, 0:59, 1:04, 92 | |
IMG_0003.PNG, 1:53, 2:08, 82 | |
IMG_0004.PNG, 1:54, 2:20, 81 | |
IMG_0005.PNG, 2:00, 2:53, 80 | |
IMG_0006.PNG, 2:15, 3:44, 76 | |
IMG_0007.PNG, 2:18, 4:00, 75 | |
IMG_0008.PNG, 2:36, 5:55, 72 | |
IMG_0009.PNG, 2:57, 9:15, 66 | |
IMG_0010.PNG, 3:23, 16:46, 53 | |
IMG_0011.PNG, 4:01, 18:43, 45 | |
IMG_0012.PNG, 0:00, 0:07, 100 | |
IMG_0013.PNG, 0:19, 1:18, 95 | |
IMG_0014.PNG, 0:34, 2:10, 92 | |
IMG_0015.PNG, 0:45, 3:06, 90 | |
IMG_0016.PNG, 0:52, 4:22, 88 | |
IMG_0017.PNG, 1:13, 5:47, 83 | |
IMG_0018.PNG, 1:15, 6:08, 83 | |
IMG_0019.PNG, 1:43, 8:57, 77 | |
IMG_0020.PNG, 2:01, 10:57, 71 | |
IMG_0021.PNG, 2:32, 12:07, 66 | |
IMG_0022.PNG, 2:36, 12:21, 63 | |
IMG_0023.PNG, 2:40, 13:02, 61 | |
IMG_0024.PNG, 3:32, 16:31, 50 | |
IMG_0025.PNG, 2:58, 15:32, 57 | |
IMG_0026.PNG, 0:03, 0:46, 100 | |
IMG_0027.PNG, 0:14, 2:47, 100 | |
IMG_0028.PNG, 0:22, 2:55, 98 | |
IMG_0029.PNG, 0:01, 0:03, 100 | |
IMG_0030.PNG, 0:03, 0:19, 100 | |
IMG_0031.PNG, 0:38, 2:15, 96 | |
IMG_0032.PNG, 1:04, 3:24, 90 | |
IMG_0033.PNG, 0:36, 3:18, 90 | |
IMG_0034.PNG, 0:44, 4:13, 89 | |
IMG_0035.PNG, 1:31, 6:19, 80 | |
IMG_0036.PNG, 0:22, 0:51, 99 | |
IMG_0037.PNG, 0:39, 2:15, 95 | |
IMG_0038.PNG, 0:51, 2:31, 91 | |
IMG_0039.PNG, 1:18, 6:31, 86 | |
IMG_0043.PNG, 1:35, 8:46, 81 | |
IMG_0047.PNG, 2:44, 15:52, 62 | |
IMG_0048.PNG, 0:57, 1:44, 92 | |
IMG_0052.PNG, 1:11, 2:01, 89 | |
IMG_0053.PNG, 1:27, 2:35, 85 | |
IMG_0054.PNG, 1:43, 3:12, 83 | |
IMG_0055.PNG, 2:06, 4:01, 78 | |
IMG_0056.PNG, 2:10, 4:10, 78 | |
IMG_0057.PNG, 2:16, 4:46, 77 | |
IMG_0058.PNG, 2:31, 5:27, 72 | |
IMG_0060.PNG, 3:02, 6:56, 63 | |
IMG_0061.PNG, 3:05, 7:26, 61 | |
IMG_0062.PNG, 3:30, 7:51, 58 | |
IMG_0063.PNG, 3:40, 9:02, 57 | |
IMG_0065.PNG, 4:11, 11:00, 52 | |
IMG_0066.PNG, 5:19, 16:10, 35 | |
IMG_0067.PNG, 0:03, 0:08, 100 | |
IMG_0068.PNG, 0:28, 0:34, 95 | |
IMG_0073.PNG, 2:04, 3:22, 80 | |
IMG_0074.PNG, 2:40, 5:12, 72 | |
IMG_0076.PNG, 3:20, 8:36, 59 | |
IMG_0080.PNG, 4:27, 12:07, 48 | |
IMG_0083.PNG, 5:48, 17:08, 20 | |
IMG_0084.PNG, 5:55, 18:22, 18 | |
IMG_0085.PNG, 0:03, 0:30, 100 | |
# 3G starts | |
IMG_0086.PNG, 1:27, 3:05, 82 | |
IMG_0087.PNG, 1:47, 4:09, 76 | |
IMG_0088.PNG, 2:50, 6:40, 60 | |
IMG_0090.PNG, 0:11, 0:47, 97 | |
IMG_0091.PNG, 0:23, 1:52, 95 | |
IMG_0092.PNG, 1:13, 3:03, 80 | |
IMG_0093.PNG, 1:54, 5:57, 68 | |
IMG_0094.PNG, 2:06, 6:15, 62 | |
IMG_0095.PNG, 2:24, 10:14, 58 | |
IMG_0096.PNG, 0:31, 0:54, 94 | |
IMG_0110.PNG, 4:04, 13:35, 40 | |
IMG_0112.PNG, 5:14, 16:22, 19 | |
IMG_0125.PNG, 2:44, 13:21, 59 | |
IMG_0127.PNG, 0:05, 1:02, 98 | |
IMG_0128.PNG, 0:42, 1:27, 93 | |
# iOS 5 | |
IMG_0164.PNG, 0:51, 2:23, 88 | |
IMG_0165.PNG, 0:19, 0:39, 97 | |
IMG_0167.PNG, 1:07, 3:20, 85 | |
IMG_0168.PNG, 1:35, 6:52, 77 | |
IMG_0173.PNG, 1:32, 6:45, 71 | |
IMG_0174.PNG, 2:17, 18:39, 52 | |
IMG_0175.PNG, 3:10, 22:11, 38 | |
IMG_0177.PNG, 1:48, 7:10, 64 | |
IMG_0179.PNG, 2:59, 13:00, 40 | |
IMG_0195.PNG, 2:41, 20:30, 41 | |
IMG_0196.PNG, 3:01, 22:43, 35 | |
IMG_0219.PNG, 3:37, 15:55, 13 | |
IMG_0220.PNG, 3:50, 14:54, 7 | |
IMG_0224.PNG, 1:43, 7:03, 56 | |
IMG_0231.PNG, 3:23, 17:29, 12 | |
IMG_0232.PNG, 3:43, 19:09, 4 | |
IMG_0246.PNG, 3:05, 12:28, 43 | |
IMG_0257.PNG, 3:37, 16:04, 32 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python2.7 | |
# encoding: utf-8 | |
## | |
# @file proc_iphone_batt.py | |
# @brief process iphone battery data | |
# @author Tim van Werkhoven ([email protected]) | |
# @date 20111001 | |
# | |
# Created by Tim van Werkhoven on 20111001 | |
# Copyright (c) 2011 Tim van Werkhoven ([email protected]) | |
# | |
# 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 ([email protected])" | |
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 <do_fit>, also fit data and overplot. | |
If <clf>, 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=<par1>+-<nsig>*<sig1>""" | |
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 <xdat> and <ydat>, 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()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment