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.

Saturday, February 6, 2021

Installing Python 3.9.1 and psycopg2 on Big Sur 11.1 - Apple M1

 I recently purchased my first Mac.  It is a MacBook Pro 13" M1, great little machine with lots of power.  The work I do is Python, ansible, and other miscellaneous development projects connecting to databases, lately PostgreSQL. I do use a laptop for photography work as well.  The first steps were getting the development environment up and running.  Microsoft VS Code went in smoothly and synced everything once connected to my Microsoft account.

Next was installing Python.  This was interesting as I wanted at least Python 3.8+ and for the M1 it seemed that 3.9.1 was the first real supported release.  The M1 with Big Sur comes with Python 2.7 and you do not want to replace it.

I found two ways to install Python 3.9, one using pyenv and one using brew. I did find that you do not want to mix the two.  I removed both and tired just pyenv by itself and got lots of errors with matplotlib, pandas, and psycopg2.  Most of the solutions I found did not seem to help and maybe I did not do the right things.  So I removed the pyenv install and went the brew install of Python 3.9.1 and here are the steps I used to get them the modules I wanted installed and working.


First installing Python 3.9.1

First thing I noticed with this installation is that everything ends up in /opt/homebrew and you will need to add directories to your path.

In my case homebrew was installed in /opt/homebrew by default. I followed the instructions on the Brew Homepage. Update your .zshrc to update the PATH on terminal startup.

export PATH="/opt/homebrew/bin:$PATH" 
Next install openssl and Python 3.9

$ brew install openssl
$ brew install python@3.9

I had number of modules that had issues getting installed, particularly dependencies for matplotlib and the next steps got numpy, pandas, and matplotlib installed without issues.

$ brew install openblas

$ OPENBLAS="$(brew --prefix openblas)" MACOSX_DEPLOYMENT_TARGET=11.1 python3 -m pip install cython --no-use-pep517

$ OPENBLAS="$(brew --prefix openblas)" MACOSX_DEPLOYMENT_TARGET=11.1 python3 -m pip install numpy --no-use-pep517

$ OPENBLAS="$(brew --prefix openblas)" MACOSX_DEPLOYMENT_TARGET=11.1 python3 -m pip install pandas --no-use-pep517

$ OPENBLAS="$(brew --prefix openblas)" MACOSX_DEPLOYMENT_TARGET=11.1 python3 -m pip install pybind11 --no-use-pep517

$ OPENBLAS="$(brew --prefix openblas)" MACOSX_DEPLOYMENT_TARGET=11.1 python3 -m pip install scipy --no-use-pep517

$ brew install libjpeg zlib

$ python3 -m pip install pillow

$ python3 -m pip install matplotlib

Next I installed PostgreSQL 12.5
   
$ brew install postgresql@12
export PATH="/opt/homebrew/opt/postgresql@12/bin:$PATH" 


Validate pip3 is being used from /opt/homebrew/bin/pip3
   
$ which pip3
Next set the following flags to install psycopg2-binary
   
export LDFLAGS="-L/opt/homebrew/opt/openssl/lib"
export CPPFLAGS="-L/opt/homebrew/opt/openssl/include"

Install psycopg2-binary using pip3
   
$ pip3 install psycopg2-binary

I found that I needed to ensure the python script has at the top a call to use python3
   
#!/usr/bin/env python3

Monday, February 22, 2016

Ham Radio: Digital Mode JT-65 and JT-9

Amateur Radio is another one of my hobbies.  In January I found this new Digital Mode called JT-65, so far I have used it on 10 meters through 40 meters and listened on 6 meters and 80 meters.  What is really neat about this mode is less power is more.  10 watts on 20 meters I worked Reunion Island from Longview, Washington and based on the signal report I could have probably used even less.


I won't go into what JT-65 is, but since you can read tons about it here.  WSJT stands for "Weak Signal Communication by K1JT."  The mode is used for EME (Earth-Moon-Earth) communications, meteor scatter and HF/LF communications.  
The JT-65 QSO is very short but very long.  You can only send 13 characters, uppercase A-Z, 0-9, slash(/), hyphen(-) and the space character.  Each side of the QSO takes 1 minute.  Yes! 1 minute so a standard QSO, from QSO to sign-off is 6 minutes.  Typically you will see something like:

CQ N7DQ CN86             <-- basic call for a QSO with grid square
N7DQ KG7EYC CN86    <-- Please talk to me
KG7EYC N7DQ -01        <-- Signal report for KG7EYC from N7DQ
N7DQ KG7EYC R-01      <-- Acknowledge signal report and send a signal report
KG7EYC N7DQ 73          <-- Thank you for the QSO
N7DQ KG7EYC 73          <-- Thank you for the QSO 

Remember this is one minute for each phase of the QSO, well actually, 47 seconds sending the transmission and 13 seconds of listening, deciding what to do next. Look at the list of stations, decide which one you want to talk with, and click on the call and ensure you have transmit enabled.

You can hear JT-65 on multiple HF and even VHF frequencies.  The sound reminds me of music from a child's music box.  This link provides a list of those frequencies and recommendations on Good Operating Procedures.  

Now your are thinking how do I try this mode out.  First I am assuming you already are doing some form of digital communication from your computer through your radio such as RTTY or PSK31.  If not, you will want to get a link between your computer and radio using something like a SignaLink USB or checkout and see if your radio can connect directly to your computer via a cable and do digital modes.  You should need a simple cable that you should be able to purchase from your radio manufacture or the place you bought the radio.

One other connection you might want to setup is Rig Control, this way you can use your computer to control your radio.  Again, the cable should be available via your radio manufacture or your radio vendor.  Search on Google for your radio and rig control or CAT, for example,  "Yaesu FT-897 CAT" or "Kenwood TS-850 Rig Control" and you should find something to point you in the right direction.

Now for the process to set things up. Your computer clock must be accurate to within a second or two.  From my experience with Windows 7, 8, and 10, Windows only updates the clock about once a day and for some laptops the time drift can be big.  First download Dimension4.  It is a basic install, accept the defaults, once installed start it up and configure it to start on Windows startup.  I have found that if you put your computer to sleep, having this running will help with time drift issues.

Next, download WSJT-x (1.6). During the install it will ask some basic questions.  There is a question about using hamspots.net, I recommend you take the default. While you run WSJT-x, it will report what stations you hear and the signal report you would give them.  You can later change this to pskreporter.info if you want and if you want to see a map of connection  go here. What is cool about this is you will see how far your signal can go around the world.








Image from hamspots.net spots:



Image from pskreporter.info spots on a map:


Finally, download JT Alert  - make sure you get the supporting applications as well. Install in the order downloaded.  JT Alert provides an interface to some logs and helps you track needed contacts for state, country or grid.

Basic quick start:
Start WSJT-x, go to File->Settings. Fill out the first tab with your information.  




Next if you have a rig control cable, go to the rig tab and configure it for your radio, 



next go to the audio tab and setup the connection to your sound card interface. 



There are some other options that are beyond this simple article and you will some good setup directions out there.  Read some of the docs for WSJT-x on setting things up, basically about 15 minutes, maybe 30 minutes or if you don't skim an hour.  This mode and setup is easy, follow the quick guides and get on the air.

Now make sure you started Dimension4, ensure it syncs your computer clock and is set to startup on booting the computer. Start WSJT-x followed by JT Alert. Set your radio to the recommended frequencies in WSJT-x and listen, let the program decode.

Focus on JT-65 not JT-9 at this time. Make sure you set your radio output power to no more than 10 watts, yes that is right 10 watts. More than that you will be splattering the band with noise and causing interference for other stations.  If you are getting lots of -01 reports, you might consider decreasing your power.  There is a site that will help you do a signal report and power comparison called dBCalc: Signal Power & dB Comparison.  One other reason to keep the power low is that this mode is full key down during the entire communication.  If you are using more than 50 watts or 100 watts, you could have some issues with your finals life span.

What I find interesting and cool is how well, WSJT-x pulls signals from the noise, here is an image of a station calling from Japan and for me is right in some noise either caused by propagation conditions, antenna issues, computer noise, radio noise or interference.  You can even have signals that are crossed over each other see both QSOs. Though if two transmitting signals are exactly on each other you may not decode either and the music is not good.

Go out to Youtube and do a search for WJST-x and find a couple of videos about operating in this mode -- probably should have told you this first but we are amateur radio operators and we try stuff before reading everything.

There are several other programs that do JT-65/JT-9 but I have found that I like the way WSJT-x works by keeping things simple.  I have downloaded and used the programs and they have some pretty neat features as well.  Here is a list of the other programs.  JT Alert will interface to each of them as well, so you may need to reinstall JT Alert to configure it to see these programs.

JT65-HF-Comfort by DL3VCO

JT65-HF HB9HQX Edition

JT65-HF by W6CQZ looks like it is no longer available and the project is abandoned.

Hope you give this mode a try and you should see me out on the air at some point.

73 de Mike - N7DQ