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 😊
This is how it should look like at the end
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.
[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/
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.
Github link is broken
Thank you Ted. Fixed!
To complicated. Put the app in the app store and share a **** link!
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!
Why does apple not allow to set a maximum battery level. They want us to buy new gadget every year :/ !
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.
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.