Sunday, January 23, 2022

MacOS: Pulling Battery Data from IOReg to CSV File using Python and LaunchAgents

 I wanted to track battery data over time and have not found a reasonable approach using the current apps to store the information in a database. I decided the first step was to figure out how to pull the information and store in a CSV file. Next steps include either pushing to PostgresSQL and to Google Sheets.

First I found the ioreg command which enabled me to pull the data. I could have used a shell script but preferred to use Python 3 since it would give me more options in the future.

I used the command 

                            ioreg -rd1 -c AppleSmartBattery

Which enabled me to pull just the battery information I was interested in. I was able to use python to pull specific items and push to a csv file. I'm not sure I got everything I wanted but it is a start. Once the program worked I created a plist file to run it every 60 minutes via LaunchAgent. The cool part of LaunchAgents is the ability to run after coming out of sleep mode.

I did install pyinstaller to create an "executable" which I placed in the bin directory and it writes to a data directory. Below find the program and plist I used. 


local.batteryinfo.plist

<?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>com.username.personal.batteryinfo</string>

<key>Program</key>

<string>/Users/username/bin/battery-info</string>

<key>RunAtLoad</key>

<true/>

<key>StandardErrorPath</key>

<string>/dev/null</string>

<key>StandardOutPath</key>

<string>/Users/username/data/batteryinfo.out</string>

<key>StartInterval</key>

<integer>3600</integer>

<key>WorkingDirectory</key>

<string>/Users/username/data</string>

</dict>

</plist>


battery-info.py

!/opt/homebrew/bin/python3

import subprocess
from csv import DictWriter
from datetime import datetime

# datetime object containing current date and time
now = datetime.now()
nowDate = now.strftime("%Y%m%d")
nowTime = now.strftime("%H%M%S")
fileName = "/Users/username/data/batteryinfo.csv"

batteryRawInfo = {}
batteryInfo = {}
excludeList = ['+-o', '{', '}', "\r"]
headersCSV = [
'Date',
'Time',
'DeviceName',
'AtCriticalLevel',
'ExternalConnected',
'AppleRawCurrentCapacity',
'Serial',
'NominalChargeCapacity',
'FullyCharged',
'DesignCycleCount9C',
'MaxCapacity',
'DesignCapacity',
'IsCharging',
'CycleCount',
'AppleRawMaxCapacity',
'TimeRemaining',
'Temperature',
'VirtualTemperature',
'AtCriticalLevel',
]

def exist_and_not_empty(filepath):
try:
import pathlib as p
path = p.Path(filepath)
if '~' in filepath:
path = path.expanduser()
if not path.exists() and path.stat().st_size > 0:
return False
return True
except FileNotFoundError:
return False


def writeCSVFile(fileName, headers, batDict):
with open(fileName, 'a', newline='') as f_object:
# Pass the CSV file object to the Dictwriter() function
# Result - a DictWriter object
dictwriter_object = DictWriter(f_object, fieldnames=headers)
# Pass the data in the dictionary as an argument into the writerow() function
dictwriter_object.writerow(batDict)
# Close the file object
f_object.close()


def main():

with subprocess.Popen(['ioreg', '-rd1', '-c', 'AppleSmartBattery'], stdout=subprocess.PIPE, text=True) as proc:
# print(proc.__dict__)
# with proc.stdout.read() as stdoutString:,
# print(stdoutString)
batteryLines = proc.stdout.readlines()

for line in batteryLines:
if [ele for ele in excludeList if (ele in line)]:
continue

# print("Line: [{}]".format(line.strip().replace(' ','').replace('"','')))
if "=" in line:
(k, v) = line.strip().replace(' ', '').replace('"', '').split("=")
batteryRawInfo[k] = v

# print("Dictionary ........................... ")
# print(batteryRawInfo)

for key, value in batteryRawInfo.items():
# print(f'Key: {key}, Value: {value}')
if key in headersCSV:
batteryInfo[key] = value

batteryInfo["Date"] = nowDate
batteryInfo["Time"] = nowTime

# print(batteryInfo)
writeCSVFile(fileName, headersCSV, batteryInfo)

if __name__ == '__main__':
main()


I did have some issues loading the launch agent and finally download an application called LaunchControl. Helped with a permissions issue and fixed my issue.

Enjoy.