Skip to content

Instantly share code, notes, and snippets.

@dreness
Last active January 31, 2024 02:41
Show Gist options
  • Save dreness/c83813479c458f4a274d755b967fc973 to your computer and use it in GitHub Desktop.
Save dreness/c83813479c458f4a274d755b967fc973 to your computer and use it in GitHub Desktop.
Display rate of charge / discharge of an Apple laptop battery
#!python
# To use this, you need Python 3 and also the pyobjc module.
# pip install pyobjc
import objc
from Foundation import NSBundle, NSString
import datetime
import time
import sys
"""
ioreg -l -n AppleSmartBattery -r
To learn about battery time remaining calculations:
https://opensource.apple.com/source/PowerManagement/PowerManagement-637.20.2/pmconfigd/BatteryTimeRemaining.c.auto.html
To learn about pmset's rawlog:
https://opensource.apple.com/source/PowerManagement/PowerManagement-637.20.2/pmset/pmset.c.auto.html
hat tip to frogor for the boilerplate :)
... but don't let any of this fool you: pmset -g rawlog is still way more efficient
... HOWEVER, pmset won't show you other interesting info emitted by this script!
Notes:
* "Selected adapter" and "Adapter Watts" may show stale info from the most recently
used adapter if the system is not charging (Apple Silicon only?)
* I don't know what some of the values mean, you'll just have to experiment for
yourself ;)
"""
IOKit_bundle = NSBundle.bundleWithIdentifier_("com.apple.framework.IOKit")
functions = [
("IORegistryEntryFromPath", b"II*"),
("IORegistryEntryCreateCFProperties", b"IIo^@II"),
]
objc.loadBundleFunctions(IOKit_bundle, globals(), functions)
kIOMasterPortDefault = 0
kCFAllocatorDefault = 0
kNilOptions = 0
# IOService:/AppleARMPE/arm-io/AppleT600xIO/smc@90400000/AppleASCWrapV4/iop-smc-nub/RTBuddy(SMC)/SMCEndpoint1/AppleSMCKeysEndpoint/AppleSmartBatteryManager/AppleSmartBattery
# M1 Max, maybe others
# ... it moved, maybe for Sonoma. This is the old one.
def oldM1MaxBatteryPath():
return "/".join([
'IOService:', 'AppleARMPE', 'arm-io', 'AppleT600xIO',
'smc@90400000', 'AppleASCWrapV4', 'iop-smc-nub', 'RTBuddyV2',
'SMCEndpoint1', 'AppleSMC', 'AppleSmartBatteryManager',
'AppleSmartBattery']).encode('utf-8')
# M1 Max, maybe others
def M1MaxBatteryPath():
return "/".join([
'IOService:', 'AppleARMPE', 'arm-io', 'AppleT600xIO',
'smc@90400000', 'AppleASCWrapV4', 'iop-smc-nub', 'RTBuddy(SMC)',
'SMCEndpoint1', 'AppleSMCKeysEndpoint', 'AppleSmartBatteryManager',
'AppleSmartBattery']).encode('utf-8')
# 2013 rMBP and a 2019 16" rMBP
def IntelBatteryPath():
return "/".join([
'IOService:', 'AppleACPIPlatformExpert', 'SMB0',
'AppleECSMBusController', 'AppleSmartBatteryManager',
'AppleSmartBattery']).encode('utf-8')
def M3MaxBatteryPath():
return "/".join([
'IOService:',
'AppleARMPE',
'arm-io@10F00000',
'AppleH15IO',
'smc@A4400000',
'AppleASCWrapV6',
'iop-smc-nub',
'RTBuddy(SMC)',
'SMCEndpoint1',
'AppleSMCKeysEndpoint',
'AppleSmartBatteryManager',
'AppleSmartBattery']).encode('utf-8')
def get_battery(path, entry):
err, details = IORegistryEntryCreateCFProperties(
entry, None, kCFAllocatorDefault, kNilOptions
)
return details, err
def validateIORegistry(paths):
"""
Check for battery info in the provided list of paths, stopping
at the first valid one.
"""
for path in paths:
#print(f"Trying {path}")
entry = IORegistryEntryFromPath(kIOMasterPortDefault, path)
# Is this IORegistry path valid?
res, err = get_battery(path, entry)
if err == 0:
return path, entry
print("Couldn't find a valid IO Registry path to battery info. Sorry!")
sys.exit(1)
path, entry = validateIORegistry([M3MaxBatteryPath(), oldM1MaxBatteryPath(), M1MaxBatteryPath(), IntelBatteryPath()])
print(
"""
Displaying power consumption stats every minute.
Positive charge rate == charging
Negative charge rate == discharging.
"Time Remaining" means time until full when charging, or time until empty when discharging.
"Selected Adapter" is set to 254 when no power adapter is connected.
"""
)
# fmt = "{:<29} {:<7} {:<4} {:<10} {:<18} {:<18} {:<15} {:<2}"
fmt = "{:<17} {:<7} {:<4} {:<10} {:<18} {:<17} {:<15} {:<2}"
print(
fmt.format(
"Time",
"Rate",
"%",
"Capacity",
"Time Remaining",
# "Charging Voltage",
"Selected Adapter",
"Adapter Watts",
"Not Charging Reason",
)
)
while True:
res, err = get_battery(path, entry)
if err != 0:
print(f"Battery polling returned error {err}!")
# Uncomment the below line to see all the IORegistry data at this path
# print(res)
print(
fmt.format(
str(datetime.datetime.utcnow())[5:19],
res.get("Amperage") / 1000.0,
res.get("BatteryData").get("StateOfCharge"),
res.get("CurrentCapacity"),
res.get("TimeRemaining"),
# res["ChargerData"]["ChargingVoltage"],
res.get("BestAdapterIndex") or " ",
res.get("AdapterDetails").get("Watts") or " ",
res.get("ChargerData").get("NotChargingReason") or " ",
)
)
# IOKit provides new data at 60 second intervals
time.sleep(60)
@dreness
Copy link
Author

dreness commented Apr 13, 2020

❯ python -i ~/bin/powerConsumption.py

Displaying power consumption stats every minute.
  Positive charge rate == charging
  Negative charge rate == discharging.

"Time Remaining" means time until full when charging, or time until empty when discharging.
"Selected Adapter" becomes None when no power adapter is connected.

Time                          Rate    %    Capacity   Time Remaining     Selected Adapter  Adapter Watts   Not Charging Reason
2020-04-13 22:50:19.798078    0.798   99   8404       7                  0                 86              0 
2020-04-13 22:51:19.803939    0.758   99   8404       6                  0                 86              0 
2020-04-13 22:52:19.787836    0.692   99   8404       5                  0                 86              0 
2020-04-13 22:53:19.793269    0.303   99   8403       7                  0                 86              0 
2020-04-13 22:54:19.799085    0.618   99   8403       3                  0                 86              0 
2020-04-13 22:55:19.805651    0.602   99   8403       2                  0                 86              0 
2020-04-13 22:56:19.808222    0.568   99   8403       1                  0                 86              0 
2020-04-13 22:57:19.812074    0.541   99   8403       0                  0                 86              0 
2020-04-13 22:58:19.815651    0.513   99   8403       0                  0                 86              0 
2020-04-13 22:59:19.822129    0.484   99   8403       0                  0                 86              0 
2020-04-13 23:00:19.826928    0.458   99   8403       0                  0                 86              0 
2020-04-13 23:01:19.829554    0.435   99   8403       0                  0                 86              0 
2020-04-13 23:02:19.837048    0.337   99   8403       0                  0                 86              23
2020-04-13 23:03:19.842459    0.385   99   8403       0                  0                 86              23
2020-04-13 23:04:19.847887    0.325   99   8403       0                  0                 86              23
2020-04-13 23:05:19.861549    0.162   100  8568       65535              0                 86              4 
2020-04-13 23:06:19.871665    -0.001  100  8577       65535              0                 86              4 
2020-04-13 23:07:19.879030    0.0     100  8577       0                  0                 86              4 
2020-04-13 23:08:19.887081    -0.001  100  8577       0                  0                 86              4 
2020-04-13 23:09:19.894252    0.0     100  8577       0                  0                 86              4 
2020-04-13 23:10:19.899607    0.0     100  8577       0                  0                 86              4 
2020-04-13 23:11:19.883654    -0.04   99   8576       0                  0                 86              4 

@dreness
Copy link
Author

dreness commented Jul 7, 2022

Here's an M1 Max transitioning from battery to AC power.

Time              Rate    %    Capacity   Time Remaining     Selected Adapter  Adapter Watts   Not Charging Reason
07-07 03:37:02    -0.528  19   20         171                254                               128
07-07 03:38:02    8.17    19   20         252                3                 140               
07-07 03:39:02    8.527   21   21         98                 3                 140               
07-07 03:40:02    8.551   23   23         94                 3                 140               
07-07 03:41:02    8.544   25   25         92                 3                 140               
07-07 03:42:02    8.544   26   27         90                 3                 140               

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment