If I had to guess (and this is only a guess), I bet that sometimes ioreg
is giving you the information in a different order than you are expecting.
The key to avoiding that is to never trust that ioreg
will give you the information in the same order, and deciding what to do about that.
Here’s an alternative solution that I have used for this.
This command:
ioreg -r -k "BatteryPercent" \
| egrep '"Product"|"BatteryPercent"' \
gives me this result:
"Product" = "Magic Trackpad 2"
"BatteryPercent" = 92
"Product" = "Magic Keyboard"
"BatteryPercent" = 90
So far so good. Except that the percent is on a different line from the device. If we had something like this:
"Product" = "Magic Trackpad 2" "BatteryPercent" = 92
"Product" = "Magic Keyboard" "BatteryPercent" = 90
That would be better because we’d know that each device and its percent would be together on the same line.
Q: But how do we say “remove some of the line breaks”?
Answer: Don’t know.
Notice the formatting of the output. The product names are "quoted" but the percentages are not.
Q: What if we could say “Only remove a line break if it comes immediately after a quotation mark?” Then it wouldn't remove the ones after "92" and "90" because they don't have a quotation mark.
In perl
you can replace a line break by searching for \n
. So we add a quick perl
line to do that, telling perl
only to replace \n
if it comes immediately after "
and it looks like this:
ioreg -r -k "BatteryPercent" \
| egrep '"Product"|"BatteryPercent"' \
| perl -p -e 's#"\n#" #'
Technically we told perl
to:
- look for a " followed by \n
- replace it with a " followed by a space (blank).
The #
are just there to mark the beginning, middle, and end of the perl
regular expression.
Now the output is:
"Product" = "Magic Trackpad 2" "BatteryPercent" = 92
"Product" = "Magic Keyboard" "BatteryPercent" = 90
Getting close now. We really don’t needs the words "Product" and "BatteryPercent" or the equal signs. But those are easy enough to get rid of.
(Note: someone who knows perl
better than me could probably avoid also needing sed
but I barely know any perl
so… I'm using sed
also. Please accept my apologies if this offends you.)
ioreg -r -k "BatteryPercent" \
| egrep '"Product"|"BatteryPercent"' \
| perl -p -e 's#"\n#" #' \
| sed -e 's#^ *"Product" = ##g' \
-e 's# "BatteryPercent" = # #g'
There are two sed
commands there. The first gets rid of 'Product' the equals sign (and space) that follows it, and the blank spaces in front of it.
The second sed
command gets rid of BatteryPercent
and the equals sign and space, and replace them with a tab
character. There are 2 reasons for using a tab
which we'll discuss in a moment, but just to be clear where the tab
is, it's where you see the letters TAB
here:
-e 's# "BatteryPercent" = #TAB#g'
The first reason for using a tab
is that it gives this nicely formatted output:
"Magic Trackpad 2" 92
"Magic Keyboard" 90
That's very pleasing.
But the second reason for using a tab
will be in a moment when we need to search for a specific character, and tab
is a good one.
Note that we still haven’t necessarily solved the underlying issue.
What happens if next time we run ioreg
we get the keyboard battery info first and the trackpad second?
What if we use a different keyboard someday that doesn’t report its battery percentage at all?
What if we trade in our “Magic Trackpad 2” for a USB mouse?
(OK, I realize that last one is a stretch, but for the sake of discussion.)
Ideally what we would like to do is be able to look at the output of ioreg
and say “Show me the Trackpad battery percent” and “Show me the keyboard percent” and not care which order that they are in.
Turns out, we can do that too.
First we’re going to put the entire output of our commands into a variable named $INFO
:
INFO=$(ioreg -r -k "BatteryPercent" \
| egrep '"Product"|"BatteryPercent"' \
| perl -p -e 's#"\n#" #' \
| sed -e 's#^ *"Product" = ##g' \
-e 's# "BatteryPercent" = # #g')
You'll notice that I just added INFO=$(
to the beginning and )
to the end. All that does is save our nice output from above into a variable called $INFO
.
Now that we have a variable with information we can re-use without having to keep using ioreg
. All that is left is to figure out how to say “Give me the part of $INFO
which has the keyboard info” and “Give me the part of $INFO
which has the trackpad info.”
echo "$INFO" | awk -F' ' '/Keyboard/{print $NF}'
Let me be clear that there is a tab
after the -F
echo "$INFO" | awk -F'TAB' '/Keyboard/{print $NF}'
What that says is: “Give me ALL of the information from the variable $INFO
but then use awk
to limit it just to the line which includes the word Keyboard
and then show me the last ‘word’ on that line.”
{print $NF}
in awk
means “Show me the last ‘word’ i.e. anything that is not whitespace”
Now we do the same thing for the trackpad:
echo "$INFO" | awk -F' ' '/Trackpad/{print $NF}'
Now it doesn’t matter what order the information comes to us, because we are being very specific about what information we want. We can put those two commands into variables, like this:
KEYBOARD_BATTERY_PERCENT=$(echo "$INFO" | awk -F' ' '/Keyboard/{print $NF}')
TRACKPAD_BATTERY_PERCENT=$(echo "$INFO" | awk -F' ' '/Trackpad/{print $NF}')
And now we can use $KEYBOARD_BATTERY_PERCENT
or $TRACKPAD_BATTERY_PERCENT
anywhere we want.
Guess what happens if there is no Trackpad information in the variable $INFO
?
$TRACKPAD_BATTERY_PERCENT
will be empty! You won’t accidentally get the keyboard information. And vice versa.
Ok, but how do I use this with Stream Deck and Keyboard Maestro ?
My solution would be to do it all in a shell script (try to contain your surprise), and have that script call certain Keyboard Maestro macros.
First I would create 4 Keyboard Maestro macros, named something like this:
- “StreamDeck Trackpad Battery Good”
- “StreamDeck Trackpad Battery Low”
- “StreamDeck Keyboard Battery Good”
- “StreamDeck Keyboard Battery Low”
Each of those 4 macros should set the button image that you want in each of those cases.
You might also want two more:
- “StreamDeck Trackpad Battery Unknown”
- “StreamDeck Keyboard Battery Unknown”
for that “just in case” scenario if the script does not get the information that you are expecting.
In a shell script, you can trigger a Keyboard Maestro macro with this syntax:
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Trackpad Battery Good"'
You can also use the UUID of the macro, which is better in that it won’t change, but worse in that it’s harder to know which is which. (If I use a macro by name in a script, I make sure to add a comment to the top of that macro in Keyboard Maestro which says “DO NOT RENAME!”)
Conditionals in shell scripts
Our old friends $KEYBOARD_BATTERY_PERCENT
or $TRACKPAD_BATTERY_PERCENT
will now come in handy because we can use them to check the battery percentage, and run different macros depending on their value. From your original macro I see that you wanted to change the icon if the mouse battery was 10% or less and keyboard battery is 15% or less.
That’s easy enough to script. I’m also going to check to make sure that the variables are not empty.
if [[ "$KEYBOARD_BATTERY_PERCENT" == "" ]]
then
echo "$NAME: 'KEYBOARD_BATTERY_PERCENT' is empty" >>/dev/stderr
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Keyboard Battery Unknown"'
else
# if we get here, we know it is not empty, so now we just need to know if it is
# within the warning range.
if [[ "$KEYBOARD_BATTERY_PERCENT" -le "15" ]]
then
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Keyboard Battery Low"'
else
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Keyboard Battery Good"'
fi
fi
Same thing for the trackpad, except 10% not 15 (note that -le
means “less than or equal”)
if [[ "$TRACKPAD_BATTERY_PERCENT" == "" ]]
then
echo "$NAME: 'TRACKPAD_BATTERY_PERCENT' is empty" >>/dev/stderr
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Trackpad Battery Unknown"'
else
# if we get here, we know it is not empty, so now we just need to know if it is
# within the warning range.
if [[ "$TRACKPAD_BATTERY_PERCENT" -le "10" ]]
then
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Trackpad Battery Low"'
else
osascript -e 'tell application "Keyboard Maestro Engine" to do script "StreamDeck Trackpad Battery Good"'
fi
fi
Whew! Well, that was a trip!
Now, I have to admit that the reason I wrote all of this out is because when I saw this idea, I knew that I wanted to use it for myself, so really you just got to watch me figure out how I’m going to implement this.
I’m also going to put the final script into a gist
in Github so it will be easy to find and easy for others to fork if they wish. I will add that back here once I’m done.
Questions?
I realize that was probably a very different answer than you expected, and possibly different than you wanted, so let me know if you have questions.