How your iPad battery can (nearly) last forever

Following these steps, it should be possible to maintain the battery, so the overall capacity loss is under 30% within 10 years.

What do you need

  • Jailbroken iPad or Macbook
  • Controllable power plug (example: Osram Smart+ Plug) approx. 13€
  • Python installed / if not done yet you find details at the end of the post
What you get at the end

A nice piece of software that keeps your battery level in an optimal range. If you follow the additional tips you can an expected a lifetime of 10 years!

Introduction

I use my iPad Pro 4-5 hours a day. (95% of this time the iPad is stationary on my office desk).  That is why I started to be worried about the battery lifetime in the long run.

The lithium-ion battery works on ion movement between the positive and negative electrodes. In theory such a mechanism should work forever, but cycling, elevated temperature and aging decrease the performance over time. Manufacturers take a conservative approach and specify the life of Li-ion in most consumer products as being between 300 and 500 discharge/charge cycles.

Luckily the battery can last much longer if we use it in a certain voltage range and make sure temperature gets not to high.

Let us have a look at battery lifetime figure from this paper [1] first:

What we see here is a dynamic stress test (DST) for lithium batteries. The black curve shows the capacity decrease when the batterie is charged to 100% and recharged when it drops below 25% over the number of cycles on the x axis.

The best results are achieved for a charging profile between 75%-65% (yellow curve). The clear downside of this is: we are using only 10% of the capacity. But that should not be a problem if the device is on your office desk most of the time.

So how can we keep the battery level in an optimal range?
  • Step 1: Use an external switchable power outlet
  • Step 2: Monitor the battery level
  • Step 3: Turn the power switch on when the power drops below 67% and stop charging when it reached 70% again by turning the switch off.

Step 1: Controlling external plug

As an external power switch, I use the OSRAM Smart+ Plug. It is zigbee protocol-based power outlet that can be controlled by the popular HUE system from Philipps. Any other wifi controllable plug will work too.

Unfortunately, the HUE system requires HTTP post message for controlling the plug. The easiest way is to use a little python script for turning the plug on or off

import urllib.request
import ssl
import time
import subprocess
import re


# constant values


DATA_ON  = b'{"on":true}'
DATA_OFF = b'{"on":false}'

url='https://philips-hue.fritz.box/api/zHOKjohzHiPvlH2NU0uGlChh-GbWL-73iYQdw2er/lights/12/state'


req = urllib.request.Request(url=url, data=DATA_OFF,method='PUT')
try:
	with urllib.request.urlopen(req, context=ssl._create_unverified_context()) as f:
	    pass
except:
	pass


	
 api/zHOKjohzHiPvlH2NU0uGlChh-GbWL-73iYQdw2er 

You need an api key for accessing the hue bridge [3]. The easiest way is to follow the tutorial on the hue developer homepage.
Don’t worry this step is really simple.

/lights/12/state

Hue connects to the Smart+ Plug as a usual light bulb that you can turn on and off.
In my setup the light with the number 12 is the Smart+ Plug. For your setup this needs to be adjusted.

ssl._create_unverified_context()

the bridge is not using a ssl certificate that is trusted by browsers. We have to tell python this is ok.

Step 3 monitoring (the most interesting part)

Getting the battery information from your apple device. This requires a little bit of research because this is completely undocumented.
Maybe you must dig around a bit to find settings that are suitable for your device.
In a terminal window on your iOS device run the following command:

ioreg -w0 -p IOPower -c AppleARMPMUCharger

As a result, you should see an output like this

{    
       "built-in" = Yes
       "CurrentCapacity" = 68
       "IOProbeScore" = 1000
       "IOClass" = "AppleARMPMUCharger"
       "ChargerConfiguration" = 0
       "TimeRemaining" = 19197
       "AtCriticalLevel" = No
       "AppleRawCurrentCapacity" = 6719
       "AbsoluteCapacity" = 7274
       "PresentDOD" = 5629
       "ExternalConnected" = No
       "ExternalChargeCapable" = No
       "BootVoltage" = 4000
       "ITSimulationCounter" = 1
       "Serial" = "F8Y7478797871YCAU"
       "NominalChargeCapacity" = 10687
       "AppleRawMaxCapacity" = 10310
       "FullyCharged" = No
       "AtWarnLevel" = No
       "ForceFullGGUpdateOnBoot" = 0
       "MaxCapacity" = 100
       "Temperature" = 2310
       "IsCharging" = No
       "Voltage" = 3973
       "OCVTakenFlag" = 0
       "CycleCount" = 45
       "BootBBCapacity" = 6799
       "ITMiscStatus" = 63769
       "GaugeFlagRaw" = 8192
       "Manufacturer" = "F"
       "AppleChargeRateLimitIndex" = 0
       .
       .
  }

"CurrentCapacity" = 68 This is the current battery level
"MaxCapacity" = 100 Interesting, might be useful for future projects 🧐
"Temperature" = 2310 Current tempearature of your battery, we don’t need this, but cool / or hot 😉
"IsCharging = No" – This will be useful later to make our script stateless.
"CycleCount" = 45 – This is how many cycle counts your battery has seen so far

step 3 Put it all together / all files can be downloaded from GitHub [2]

At the end we will have a script that controls the plug:

  • We start charging a 67%
  • We stop charging at 70%
 So, we keep the battery in a perfect voltage (battery level) range that ensures a great lifetime.

There is one little step that is required first:
We need a service that runs on the device to monitor the voltage (I tried this with sleep commands before, but apple power saving is strong and stops every timer)

The magic of apple on devices services:
Place this file in /Library/LaunchDaemons

The apple daemon syntax is a little bit like we know it from cron jobs. But somehow more painful.

This will call our script every 5 minutes. Do not worry, when the device is off (in deep sleep) it will be called less often. The iOS on device scheduler is doing a great job for you. In practice there should be no impact on battery life when no charger is connected by this script.

<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Label</key>
        <string>de.irmo.battery_longevity</string>
        
        <key>ProcessType</key>
        <string>Interactive</string>

        <key>ProgramArguments</key>
        <array>
            <string>/bin/bash</string>
            <string>/usr/libexec/battery_longevity.sh</string>
        </array>
    <key>StartCalendarInterval</key>
    <array>
        <dict>
            <key>Minute</key>
            <integer>0</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>5</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>10</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>15</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>20</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>25</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>30</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>35</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>40</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>45</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>50</integer>
        </dict>
        <dict>
            <key>Minute</key>
            <integer>55</integer>
        </dict>
    </array>
    </dict>
</plist>

/usr/libexec/battery_longevity.sh in section ProgramArguments

This is a bash script that is called every 5 minutes. Do not try to call python scripts directly, this will not work.

#!/bin/bash

/usr/bin/python3 /usr/libexec/hue.py

And finally, the script that controls the charger based on the battery level

#!usr/bin/python

import urllib.request
import ssl
import time
import subprocess
import re

DATA_ON  = b'{"on":true}'
DATA_OFF = b'{"on":false}'

url='https://philips-hue.fritz.box/api/zHOKjohzHiPvlH2NU0uGlChh-GbWL-73iYQdw2er/lights/12/state'

regex_cap = rb'"CurrentCapacity" = (\d+)'
regex_charge = rb'"IsCharging" = (\w+)'

limit_lower = 67 # start charging if below or equal this value
limit_upper = 70 # stop charging if above or equal this value


def isCharging(regex_charge, result):
	# are we charging?
	# Lets look if device is charging / remember every wifi call is expensive in terms of power consumption

	match = re.search(regex_charge, result.stdout)  

	if (match != None):
		charging = match.group(1) == b'Yes'
	else:
		charging = False

	print (charging)

	return charging


while (True):

	# Getting battery level

	result = subprocess.run(['ioreg', '-w0','-p', 'IOPower', '-c', 'AppleARMPMUCharger', '-r'], stdout=subprocess.PIPE)
	match = re.search(regex_cap, result.stdout)  

	if (match != None):
		battery_level = int(match.group(1))
	else:
		battery_level = 100

	print (battery_level)




	if (battery_level <= limit_lower):
		print ("Start charging ...")
		if (isCharging(regex_charge, result) == True):
			print ("No need to send request we are already charging ...")
			pass
		else:
			req = urllib.request.Request(url=url, data=DATA_ON,method='PUT')
			try:
				with urllib.request.urlopen(req, context=ssl._create_unverified_context()) as f:
				    pass
			except:
				pass

	if (battery_level >= limit_upper):
		print ("Stop charging ...")
		if (isCharging(regex_charge, result) == False):
			print ("No need to send request we are not charging ...")
			pass
		else:
			req = urllib.request.Request(url=url, data=DATA_OFF,method='PUT')
			try:
				with urllib.request.urlopen(req, context=ssl._create_unverified_context()) as f:
				    pass
			except:
				pass

	break
	

That is all you need. In the link below you find all files as a direct download with a README where to place the files. Have fun!

If you have not done this before you need one more package from Cydia. Python 😊

Python install screenshot

This is how it should look like at the end

iOS battery stats with recharge script

Here we see 8 mini charges that keep the battery level between 67-70%

Further tips:

Do not let the capacity fall below 25%. Take a power bank with you to be on the safe side. Avoid high temperatures! – no direct sunlight or storage in a car during a hot summer day.
Whenever it is possible avoid fully charging to 100%
Following these steps, it should be possible to maintain the battery, so the overall capacity loss is under 30% within 10 years.

Sources:
[1] Xu, Bolun & Oudalov, Alexandre & Ulbig, Andreas & Andersson, Göran & Kirschen, D.s. (2016). Modeling of Lithium-Ion Battery Degradation for Cell Life Assessment. IEEE Transactions on Smart Grid. 99. 1-1. 10.1109/TSG.2016.2578950.

https://www.researchgate.net/publication/303890624_Modeling_of_Lithium-Ion_Battery_Degradation_for_Cell_Life_Assessment

[2] Gibhub repository (Updated 2020-10-16)
https://github.com/irmo-de/ios_battery_longevity


[3] HUE developer API https://developers.meethue.com/develop/get-started-2/

8 thoughts on “How your iPad battery can (nearly) last forever

  1. Anonymous Reply

    Came here from a reddit post. Did not expect anything special. Learned something interesting about batteries. And this is the first time I see how services work ion iOS.

  2. Jules Reply

    I come here because ioreg -w0 -p IOPower -c in not working for me: ioreg -w0 -p IOPower -c AppleARMPMUCharger -r did the job.
    I just wanted to know about the cycles of my iPhone battery.

    The battery part is interesting by the way!

  3. Anonymous Reply

    Why does apple not allow to set a maximum battery level. They want us to buy new gadget every year :/ !

    • irmo Post authorReply

      Do not forget they provide software updates for a very long time. I think the reason is usability. I am not aware of any tablet that allows changes in battery management.

  4. Raz0r 11 Reply

    I tried something similar years ago by using private APIs. This could work without a jailbreak by self-signing the app. Nowadays apple testflight blocks these kinds of apps.

Leave a Reply

Your email address will not be published. Required fields are marked *