Future proofing iokit registry relevance

Hi All,

I’m writing some relevance to get the battery health from our laptops. For Windows, I’m grabbing from wmi and for Macs, from the iokit registry, like so:

if (exists (setting "_BESClient_ComputerType" of client) whose (value of it is "Laptop")) then (if (windows of operating system) then ((integer value of selects ("FullChargedCapacity from BatteryFullChargedCapacity") of wmi "root/wmi")/(integer value of selects ("DesignedCapacity from BatteryStaticData") of wmi "root/wmi") as floating point * ("100.0" as floating point)) else (if (mac of operating system) then (if (concatenation of unique values of brand strings of processors as lowercase contains "apple") then ((integer of values of entries whose (key of it = "AppleRawMaxCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleSMC" of node "SMCEndpoint1" of node "RTBuddyV2" of node "iop-smc-nub" of node "AppleASCWrapV4" of node "smc" of node "AppleT810xIO" of node "arm-io" of node "AppleARMPE" of service plane of iokit registry)/(integer of values of entries whose (key of it = "DesignCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleSMC" of node "SMCEndpoint1" of node "RTBuddyV2" of node "iop-smc-nub" of node "AppleASCWrapV4" of node "smc" of node "AppleT810xIO" of node "arm-io" of node "AppleARMPE" of service plane of iokit registry) as floating point * ("100.0" as floating point)) else (((integer of value of entry whose(key of it = "MaxCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleECSMBusController" of node "SMB0" of node "AppleACPIPlatformExpert" of service plane of iokit registry)/(integer of value of entry whose(key of it = "DesignCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleECSMBusController" of node "SMB0" of node "AppleACPIPlatformExpert" of service plane of iokit registry) as floating point * ("100.0" as floating point)))) else error "Not supported")) else error "Not a laptop!"

For Intel Macs, the relevant bit from above is:

((integer of value of entry whose(key of it = "MaxCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleECSMBusController" of node "SMB0" of node "AppleACPIPlatformExpert" of service plane of iokit registry)/(integer of value of entry whose(key of it = "DesignCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleECSMBusController" of node "SMB0" of node "AppleACPIPlatformExpert" of service plane of iokit registry) as floating point * ("100.0" as floating point))

And then for (what I thought would be) Apple Silicon I was using:

((integer of values of entries whose (key of it = "AppleRawMaxCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleSMC" of node "SMCEndpoint1" of node "RTBuddyV2" of node "iop-smc-nub" of node "AppleASCWrapV4" of node "smc" of node "AppleT810xIO" of node "arm-io" of node "AppleARMPE" of service plane of iokit registry)/(integer of values of entries whose (key of it = "DesignCapacity") of dictionary of node "AppleSmartBattery" of node "AppleSmartBatteryManager" of node "AppleSMC" of node "SMCEndpoint1" of node "RTBuddyV2" of node "iop-smc-nub" of node "AppleASCWrapV4" of node "smc" of node "AppleT810xIO" of node "arm-io" of node "AppleARMPE" of service plane of iokit registry) as floating point * ("100.0" as floating point))

Unfortunately, it seems the relevance I wrote is only good for M1 chips:
image

Our M2s, M1 Pros, and other non-M1 Apple chips all report back “Singular expression refers to nonexistent object”. I don’t have any of these devices in front of me to test, but I’m assuming the place where this data is stored has once again changed.

Instead of adding another If…Then…Else clause for each different M-series processor, I’m hoping to adjust the relevance to do that for me. Something like being able to find the node “AppleRawMaxCapacity” without needing to provide the entire path. Is this possible?

Thanks!

I’ve found a “solution”, if that solution is just not relying on the iokit registry.

Instead, I deployed a launchdaemon that will query the device once a day for its battery health (using ioreg | grep), store the result, and just have the analysis read that one line file.

My actionscript:

delete __createfile
delete /Library/LaunchDaemon/local.BatterySniffer.plist
createfile until EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>local.BatterySniffer</string>
<key>Program</key>
<string>/Library/Scripts/BatteryHealthReporter.sh</string>
<key>RunAtLoad</key>
<true/>
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>12</integer>
<key>Minute</key>
<integer>00</integer>
</dict>
</dict>
</plist>
EOF
copy "__createfile" "local.BatterySniffer.plist"
wait chmod +x local.BatterySniffer.plist
wait chown root:wheel local.BatterySniffer.plist

delete __createfile
delete /Library/Scripts/BatteryHealthReporter.sh
createfile until EOF
#!/bin/bash
DesignCap=$(ioreg -l -w0 | grep '"DesignCapacity" = ' | sed 's/[^0-9]*//g')
CurrentCap=$(ioreg -l -w0 | grep '"AppleRawMaxCapacity" = ' | sed 's/[^0-9]*//g')
Health=$(echo "result = ($CurrentCap/$DesignCap)*100;scale=2; result/1"|bc -l)
printf "$Health" > /var/tmp/BatteryHealthReport.txt
EOF
copy "__createfile" "BatteryHealthReporter.sh"
wait chmod +x BatteryHealthReporter.sh
wait chown root:wheel BatteryHealthReporter.sh

delete __createfile
delete Kleenex.sh
createfile until EOF
#!/bin/bash
mv local.BatterySniffer.plist /Library/LaunchDaemons/local.BatterySniffer.plist
mv BatteryHealthReporter.sh /Library/Scripts/BatteryHealthReporter.sh

launchctl load /Library/LaunchDaemons/local.BatterySniffer.plist
launchctl start /Library/LaunchDaemons/local.BatterySniffer.plist
EOF
copy "__createfile" "Kleenex.sh"
wait chmod +x Kleenex.sh
wait /bin/sh Kleenex.sh

Then, the analysis is as simple as

lines of file "/var/tmp/BatteryHealthReport.txt"

I’m not thrilled with it, since I’d much rather be able to dynamically and directly query the device, but this seems to be the only solution that works for Macs regardless of processor.

If anyone has any ideas how to make the iokit registry more universally friendly — I’m all ears.