Battery status over i.MX and LPC

I wanted to get information about the battery state on the command line. I found these UART instructions that should do this, but they don’t seem to work for me. Is there something I might be missing? Or is there a better way to get the battery status, since that advice in June?

I have the October firmware updates for the keyboard and LPC installed in case that affects this feature.

To be more clear, here’s what happens:

  1. As root, I run echo "xUAR1" >/dev/hidraw0.
  2. I get 60–70 new lines in the terminal, as if I had hit the enter key many times? I’m not sure why that happens or if it’s relevant.
  3. The OLED turns on and says “Waking up LPC” and then “No response from LPC”.
  4. I run screen /dev/ttymxc2 57600. It clears the display, but no text ever appears, even after waiting several minutes.

If I start screen first (from a fresh boot) and then issue the xUAR1 command, then I get this on the screen output:

0
0
0
0
0
error: syntax

Because you work with rawhid from the same rawhid device - you generate keydown event (when entering echo command) and the echo command starts execution of the report event which takes time to return from (waking up lpc) - so keyboard driver does not receive keyup and hence is generating repeated keypress event - which generates those empty lines on the terminal. You may do sleep 1; echo … which will complete enter input event and then do an echo in a second. Or you can do direct hidapi call. But that just explains empty lines, the resullt is the same.
Not sure why error is generated, maybe desync between kbd and lpc uarts while it is waking up. But the command is ultimately applied, and if you request a report (echo -en "\0RPRT" > /dev/hidraw0) you should get back on UART2 something like

35 35 35 33 33 35 33 33 mA 0000mV27680 100% P1

Upd: or you can simply do Circle+B to get battery status, the only difference is that RPRT will only output to UART2 while CircleB to both UART2 and OLED.

P.S. and thanks for reminding about this “instructions” post, I knew I saw it earlier but couldn’t find it :slight_smile:

Thank you for the tips. I was busy during the last week and I’m only now getting a chance to try this out.

Adding sleep 1 ahead of the echo command did prevent the repeated newlines. Thanks for that advice. It’s an interesting interaction that I didn’t understand before.

I was also able to use the “\0RPRT” command to get the battery line like you said. I understand the need for the -e flag to escape the initial \0, but it didn’t seem to matter whether I used the -n flag you suggested. Is there a side effect if it sends the trailing new-line?

To rewrite for clarity, I think that these are the complete, correct instructions to get battery status on the command line:

  1. As root, run screen /dev/ttymxc2 57600 to open a connection to the LPC.
  2. As root, in a separate terminal, run sleep 1; echo "xUAR1" > /dev/hidraw0 to tell the LPC to write its output to /dev/ttymxc2.
  3. As root, run echo "xRPRT" > /dev/hidraw0 to tell the LPC to issue a battery status report. Look at the screen session to see the results. Repeat this step as often as needed.
  4. When finished, as root, run sleep 1; echo "xUAR0" > /dev/hidraw0 to tell the LPC to stop writing output to /dev/ttymxc2.

Now that I can confirm this is working, I’m hoping that I can find a way to write a shell script to output the battery status directly, without having to keep a screen session open. I don’t know enough about this stuff to know if that’s possible, but I’ll do some reading and find out as much as I can.

Thanks again for your help.

No it does not really matter (currently) whether you pass x or “\0” at the beginning and nothing or “\n” in the end of the command, current firmware ignores both ReportType and trailing bytes (doesn’t mean it will always do though). it just makes the message exactly as if it is sent by HIDAPI interface. and -n by removing the trailing newline char is making message smaller by 1 byte :slight_smile: so in short in point 3 you can also use echo “xRPRT” >…

Ah, thanks for explaining that. I updated my instructions above to use “xRPRT” for simplicity and consistency across all the commands.

I started looking for the source code that is handling these commands so I could try to get a better understanding of what’s happening. Do you know offhand where I should look? I figured it would be under reform2-lpc-fw, but haven’t found what I’m looking for yet.

Look starting from here for commands.
For scripting you may find stty useful:

$ sudo stty -F /dev/ttymxc2 57600
$ sudo awk '/ mA /{print"Bat1: "$1}' /dev/ttymxc2 &
$ sleep 1; echo xRPRT | sudo tee /dev/hidraw0

Thanks again for your help. Starting from your suggestion, I was able to make a bash script to display the current battery info on the command line.

I don’t really plan to use this seriously. It takes a while to run because it enables and disables the output to ttymxc2 every time it runs. It’s easier just to press circle-B and look at the OLED display. But it was an educational exercise, and I’m glad i was able to get the results I wanted.

Here’s my final script, in case anyone is interested. This has some options to either print the raw output or to print it with nicer labels and formatting. Please understand that I’ve done almost zero bash scripting, so there is probably bad code in here! (For example, I assume that the user has permission to write to /dev/hidraw0, and don’t do anything to handle the situation if they don’t.)

#!/bin/bash

# In order to run this script, a user needs to have permission to write to /dev/hidraw0. One way
# to do this is to create a udev rule to set the device's group to a group that the user belongs
# to. For example, this command will create a udev rule that sets the group on all hidraw devices
# to 'dialout' and sets their permissions to 660.
#
# $ sudo echo SUBSYSTEM=="hidraw", GROUP="dialout", MODE="0660" > /etc/udev/rules.d/50-hidraw.rules
#
# After creating the rule, restart the machine to reload the device with the new properties.
#
# Make sure to add the user to the group you used, if they aren't already a member of that group.
# See /etc/group for current group membership and the adduser command for assigning a user to a group.


# https://source.mnt.re/reform/reform/-/blob/master/reform2-keyboard-fw/Keyboard.c#L299
# lpc format: 32 32 32 32 32 32 32 32 mA 0256mV26143 ???% P1
#             |  |  |  |  |  |  |  |  | |      |     |    |
#             0  3  6  9  12 15 18 21 24|      |     |    |
#                                       26     33    39   44
#                                       |
#                                       `- can be a minus


help()
{
	echo "Display battery information from the MNT Reform LPC."
	echo "Usage: batstat [ OPTIONS ]"
	echo "Options:"
	echo "	-r	Show the raw output from the LPC. Any other options will be ignored."
	echo "	-i	Show all the info, nicely formatted. This is the default. Equivalent to -cavpo"
	echo "	-c	Show the voltage of each individual cell."
	echo "	-a	Show the amps."
	echo "	-v	Show the total voltage of all the cells."
	echo "	-p	Show the remaining power as a percentage."
	echo "	-o	Show the power state as 'On' or 'Off'."
	exit
}

get_bat_info()
{
	# What does stty do here? Does it persist after this script finishes?
	stty -F /dev/ttymxc2 57600

	# Tell the LPC to send its output to ttymxc2.
	sleep 1; echo "xUAR1" > /dev/hidraw0

	# Wait a couple seconds for the xUAR1 command to be applied, so we don't see its output.
	sleep 2

	# Request the battery status.
	sleep 1; echo "xRPRT" > /dev/hidraw0

	# Echo the LPC output line. Probably this process will start before the LPC issues its output.
	head -n 1 /dev/ttymxc2

	# Tell the LPC to stop sending its output to ttymxc2.
	sleep 1; echo "xUAR0" > /dev/hidraw0

	# Wait a couple seconds. For some reason, we don't get keyboard control back right away, even
	# though the command prompt appears. So just wait until we probably have control back.
	sleep 2
}

# Initialize default options
SHOW_RAW=0
SHOW_CELLS=0
SHOW_AMPS=0
SHOW_VOLTS=0
SHOW_PERCENT=0
SHOW_POWER=0

# Set command line options
if [ $# -eq 0 ]; then
	SHOW_CELLS=1
	SHOW_AMPS=1
	SHOW_VOLTS=1
	SHOW_PERCENT=1
	SHOW_POWER=1
else
	while getopts :cavpoir FLAG; do
		case $FLAG in
			c) # cell voltage
				SHOW_CELLS=1
				;;
			a) # amps
				SHOW_AMPS=1
				;;
			v) # total voltage
				SHOW_VOLTS=1
				;;
			p) # battery percentage
				SHOW_PERCENT=1
				;;
			o) # on/off state
				SHOW_POWER=1
				;;
			i) # all info
				SHOW_CELLS=1
				SHOW_AMPS=1
				SHOW_VOLTS=1
				SHOW_PERCENT=1
				SHOW_POWER=1
				;;
			r) # raw output
				SHOW_RAW=1
				;;
			\?) # unknown option
				help
				;;
		esac
	done
fi

result=$(get_bat_info)

if [ $SHOW_RAW -eq 1 ]; then
	echo $result
	exit
fi

if [ $SHOW_CELLS -eq 1 ]; then
	echo "Cell Voltage:"
	IDX=0
	while [ $IDX -lt 23 ]; do
		echo "  Cell $(($IDX / 3)):	${result:$IDX:1}.${result:$(($IDX + 1)):1}V"
		IDX=$(($IDX + 3))
	done
fi

if [ $SHOW_AMPS -eq 1 ]; then
	# Use // / parameter expansion to remove any spaces.
	AMPS="${result:26:2}.${result:28:3}mA"
	echo "Amps:		${AMPS// /}"
fi

if [ $SHOW_VOLTS -eq 1 ]; then
	echo "Total Volts:	${result:33:2}.${result:35:3}V"
fi

if [ $SHOW_PERCENT -eq 1 ]; then
	# Use // / parameter expansion to remove any spaces.
	PERCENT="${result:39:4}"
	echo "Percent:	${PERCENT// /}"
fi

if [ $SHOW_POWER -eq 1 ]; then
	if [ ${result:45:1} -eq 1 ]; then
		echo "Power:		On"
	else
		# Interesting, if true.
		echo "Power:		Off"
	fi
fi

4 Likes

Love it. :smiley:

Thanks for sharing this. I’ve been wanting to hack together a way to show an alert when I hit 25% while discharging and 75% charge while charging. (Lithium batteries like to be kept around half-full, I’ve heard, so I’m trying to extend my battery life by staying in this range as much as possible.) This example will be very useful to me!

1 Like

Edit: There’s a newer version of this script in a post below that changes the approach from one-shot polling to long-running polling and makes the first two fields of the alert spec optional.

Alright, I’ve put together a script for checking the battery charge status and then executing commands based on whether it has discharged or charged to or past certain levels. Currently running this in my crontab every five minutes. :smiley:

#!/bin/sh

set -e

usage="Usage: $0 [OPTIONS]

Options:
  -h        Display this message and exit with error code 0.

  -c [file]    Cache the last battery reading in [file] and use the file to ensure
               an alert gets displayed even if a target charge level is passed
	       between runs of this script. Without this flag, an alert will be
	       displayed only if the battery is at a target charge level at the
	       moment this script run.

	       [file] defaults to ~/.local/var/$(basename "$0")/cache
	       If the file does not exist, it and its parent directories will be
	       created.

  -a <spec>    An alert specification. This flag may be set multiple times. Alert
               specifications are in this format:
               \"<charging|discharging>:<percent charge>:<command>\"
	    
	       For example:
	       $0 \\
	         -a 'discharging:25:notify-send \"Discharged to 25%!\"' \\
	         -a 'charging:75:notify-send \"Charged to 75%!\"'
"

[ "$1" ] || { >&2 echo "$usage"; exit 1;  }

cache_file=""
alerts=""

while [ "$1" ]; do
	case "$1" in
		-h)
			echo "$usage"
			exit 0
			;;
		-c)
			if [ ! "$2" ] || [ "$2" != "${2#-}" ]; then
				cache_file="$HOME/.local/var/$(basename "$0")/cache"
			else
				cache_file="$2"
				shift
			fi
			;;
		-a)
			[ "$2" ] || { >&2 echo "Missing alert specification!"; exit 1; }

			if ! printf '%s' "$2" \
				| grep -q '^\(dis\)\?charging:[0-9]\{1,3\}:.\+$' 
			then
				>&2 echo "Invalid alert specification:"
				>&2 printf "%s\n" "$2"
				exit 1
			fi

			if [ "$(printf '%s' "$2" | cut -d: -f2)" -gt 100 ]; then
				>&2 echo "Target charge percentage >100 in specification:"
				>&2 printf "%s\n" "$2"
				exit 1
			fi

			# Accumulate all alert specifications, keeping track of how many
			# bytes long each is.
			alerts="${alerts}$(printf '%s' "$2" | wc -c):${2}"
			shift
			;;
		*)
			>&2 echo "Unrecognized option: $1"
			exit 1
			;;
	esac

	shift
done

# Function below written by salsaalibi and copied from
# https://community.mnt.re/t/battery-status-over-i-mx-and-lpc/668/7
get_bat_info()
{
        # What does stty do here? Does it persist after this script finishes?
        stty -F /dev/ttymxc2 57600

        # Tell the LPC to send its output to ttymxc2.
        sleep 1; echo "xUAR1" > /dev/hidraw0

        # Wait a couple seconds for the xUAR1 command to be applied, so we don't see its output.
        sleep 2

        # Request the battery status.
        sleep 1; echo "xRPRT" > /dev/hidraw0

        # Echo the LPC output line. Probably this process will start before the LPC issues its output.
        head -n 1 /dev/ttymxc2

        # Tell the LPC to stop sending its output to ttymxc2.
        sleep 1; echo "xUAR0" > /dev/hidraw0

        # Wait a couple seconds. For some reason, we don't get keyboard control back right away, even
        # though the command prompt appears. So just wait until we probably have control back.
        sleep 2
}

if [ ! -e "$(dirname "$cache_file")" ]; then
	mkdir -p "$(dirname "$cache_file")"
fi

bat_info="$(get_bat_info)"
percent="$(printf '%s' "$bat_info" | tail -c+40 | head -c3 | tr -d ' ')"
amps="$(printf '%s' "$bat_info" | tail -c+27 | head -c2 | tr -d ' ')"

if [ "$cache_file" ] && [ -e "$cache_file" ]; then
	cached_percent="$(cat "$cache_file")"
fi

if [ "${amps}" != "${amps#-}" ]; then
	state="charging"
else
	state="discharging"
fi

while [ "$alerts" ]; do
	strlen="${alerts%%:*}"
	alerts="${alerts#*:}"
	alert="$(printf '%s' "$alerts" | head -c"$strlen")"
	alerts="$(printf '%s' "$alerts" | tail -c+"$(expr 1 + "$strlen")")"

	[ "${alert}" != "${alert#$state}" ] || continue

	target="$(printf '%s' "$alert" | cut -d: -f2 | tr -d ' ')"

	if [ "$cached_percent" ]; then
		if [ "$state" = "charging" ] && \
			{ [ "$cached_percent" -ge "$target" ] || [ "$percent" -lt "$target" ]; }
		then
			continue
		fi

		if [ "$state" = "discharging" ] && \
			{ [ "$cached_percent" -le "$target" ] || [ "$percent" -gt "$target" ]; }
		then
			continue
		fi

	elif [ "$percent" != "$target" ]; then
		continue;
	fi

	# Execute the alert command
	echo "$(printf '%s' "$alert" | cut -d: -f3)" | sh
done

[ ! "$cache_file" ] || printf '%s' "$percent" > "$cache_file"

Edit: Running every minute now. The batteries charge fast enough that only checking every five minutes resulted in significantly overshooting the “charge” target.

4 Likes

It kind of bothers me to be writing to a file that may be in nonvolatile memory every second minute, so now I’m adapting my script to instead be long-running. But now I’m wondering if there are any downsides to just leaving the LPC’s output redirected to /dev/ttymxc2. @minute?

Apart from the fact you cannot use the uart interface for anything else - no. LPC is checking the batteries in the loop anyway. So what you do is:

  • Extra wake for LPC
  • Extra data for HID
  • Extra data for UART

this extra is nothing comparing to one start of application or one open/scroll/refresh of the web page in the browser.

1 Like

Edit: Made the script a little more efficient about how it parses out the fields from the battery info string and added support for $amps and $volts in the alert command specs.

Edit 2: I’ve actually reduced the polling to 60 seconds just now as I have begun seeing some weird responses from the LPC after running the service for a while. Not sure what’s going on there, but my first guess here is that I’m over-taxing whatever circuitry is involved with my polling every two seconds. Will report back if this problem continues to occur.

Edit 3: Still seeing the strange responses sometimes. They look like this:

j0328mV25750  59%

All the information I want is there, so I could work around it in my script, but I’d rather get at the root cause.

Okay, I’ve got an approach for tracking my battery charge status that I’m pretty happy with for now.

I’m running this script as a systemd service. It’s an evolution of the script above. It only sends the “redirect output” command once when it starts and then the “un-redirect output” command once when it’s killed. It polls the battery state periodically and is meant to be long-running. It also allows omitting either or both of the first two “alert” fields, which lets you specify a command to run when a given percentage is hit or every time the script polls the battery, regardless of whether you’re discharging or charging. You can also reference the battery state and charge percentage, as well as the original line returned by the LPC, in the command string.

Here it is. I’ve got it installed at /usr/local/sbin/reform-battery-charging-alert-service.sh:

#!/bin/sh

set -e

usage="Usage: $0 [OPTIONS]

Options:
  -h           Display this message and exit with error code 0.

  -p <seconds> How long to wait between polls. Defaults to 60 seconds.

  -a <spec>    An alert specification. This flag may be set multiple times. Alert
               specifications are in this format:
               \"[charging|discharging]:[percent charge]:<command>\"

	       The following variables can be referenced in the <command> string:
	       - \$state: Either \"charging\" or \"discharging\".
	       - \$amps: Battery charge as a percentage.
	       - \$volts: Battery voltage, unformatted.
	       - \$percent: Battery charge as a percentage.
	       - \$bat_info: The raw battery status string returned by the LPC.
	    
	       Example:
	       $0 \\
	         -a 'discharging:25:notify-send \"Discharged to 25%!\"' \\
	         -a 'charging:75:notify-send \"Charged to 75%!\"' \\
	         -a 'charging::notify-send \"Polled the battery while charging!\"' \\
	         -a ':50:notify-send \"Battery charged or discharged to 50%!\"' \\
	         -a '::notify-send \"Polled! Battery at \$percent% and \$state.\"'
"

[ "$1" ] || { >&2 echo "$usage"; exit 1;  }

poll_pause=60
alerts=""

expect_value() {
	if [ ! "$2" ]; then
		>&2 echo "Missing value for flag '$1'!"
		exit 1

	elif [ "$2" != "${2#-}" ]; then
		>&2 echo "Invalid value for flag '$1': '$2'"
		exit 1
	fi
}

while [ "$1" ]; do
	case "$1" in
		-h)
			echo "$usage"
			exit 0
			;;
			
		-p)
			expect_value "$1" "$2"
			poll_pause="$2"
			shift
			;;

		-a)
			expect_value "$1" "$2"

			if ! printf '%s' "$2" \
				| grep -q '^\(\(dis\)\?charging\)\?:\([0-9]\{1,3\}\)\?:.\+$' 
			then
				>&2 echo "Invalid alert specification:"
				>&2 printf "%s\n" "$2"
				exit 1
			fi

			target="$(printf '%s' "$2" | cut -d: -f2)"
			if [ "$target" ] && [ "$target" -gt 100 ]; then
				>&2 echo "Target charge percentage >100 in specification:"
				>&2 printf "%s\n" "$2"
				exit 1
			fi

			# Accumulate all alert specifications, keeping track of how many
			# bytes long each is.
			alerts="${alerts}$(printf '%s' "$2" | wc -c):${2}"
			shift
			;;

		*)
			>&2 echo "Unrecognized option: $1"
			exit 1
			;;
	esac

	shift
done

# Set the baud rate on ttymxc2 to 57600
stty -F /dev/ttymxc2 57600

# Redirect the LPC's serial output to /dev/ttymxc2
echo "xUAR1" > /dev/hidraw0

# Stop redirecting the output after the script ends for whatever reason.
trap 'sleep 1; echo "xUAR0" > /dev/hidraw0' EXIT HUP INT QUIT TERM STOP PWR

# Wait a couple seconds for the redirect command to apply and its output to clear.
sleep 2

# Loops until a kill signal is received.
while true; do
	bat_info=""

	while [ ! "$bat_info" ]; do
		# Request battery status.
		echo "xRPRT" > /dev/hidraw0

		# Read the LPC's response.
		export bat_info="$(head -n 1 /dev/ttymxc2)"

		if [ ! "$bat_info" ]; then
			>&2 echo "Error trying to retrieve battery information. Pausing before retrying..."
			sleep "$poll_pause"
		fi
	done

	# Parse the LPC's battery info line.
	# This line will split each field into our argument array.
	eval "set -- $(echo "$bat_info" | sed 's/m\(V\|A\)/ /g')"
	export amps="$(echo "$9" | sed 's/\(-\?.\)\(...\)/\1.\2/')"
	export volts="$(echo "${10}" | sed 's/\(..\)\(...\)/\1.\2/')"
	export percent="${11%\%}"

	if [ "${amps}" != "${amps#-}" ]; then
		state="charging"
	else
		state="discharging"
	fi
	export state

	alerts_parsing="$alerts"

	while [ "$alerts_parsing" ]; do
		strlen="${alerts_parsing%%:*}"
		alerts_parsing="${alerts_parsing#*:}"
		alert="$(printf '%s' "$alerts_parsing" | dd bs=1 count=$strlen 2> /dev/null)"
		alerts_parsing="$(printf '%s' "$alerts_parsing" | dd bs=1 skip=$strlen 2> /dev/null)"

		if [ "${alert}" = "${alert#:}" ] && [ "${alert}" = "${alert#$state}" ]; then
			continue
		fi

		target="$(printf '%s' "$alert" | cut -d: -f2 | tr -d ' ')"

		if [ "$target" ]; then
			if [ ! "$cached_percent" ] && [ "$percent" != "$target" ]; then
				continue;
			fi

			if [ "$state" = "charging" ] && \
				{ [ "$cached_percent" -ge "$target" ] || [ "$percent" -lt "$target" ]; }
			then
				continue
			fi

			if [ "$state" = "discharging" ] && \
				{ [ "$cached_percent" -le "$target" ] || [ "$percent" -gt "$target" ]; }
			then
				continue
			fi
		fi

		# Execute the alert command
		echo "$(printf '%s' "$alert" | cut -d: -f3-)" | sh
	done

	cached_percent="$percent"
	sleep "$poll_pause"
done

As the name suggests, I’ve got it installed as a systemd service now. Here’s /etc/systemd/system/reform-battery-alert.service:

[Unit]
Description=Reform Battery Alerts

[Service]
Type=simple
ExecStartPre=touch /dev/shm/battery && chmod 644 /dev/shm/battery
ExecStart=/usr/local/sbin/reform-battery-charging-alert-service.sh \
	-p 60 \
	-a 'discharging:25:/usr/local/sbin/reform-oled-text.sh -c "" "   Battery at 25%!" "   Time to charge."' \
        -a 'charging:75:/usr/local/sbin/reform-oled-text.sh -c "" "   Battery at 75%!" "   Time to unplug."' \
        -a '::echo "$percent\t$state" > /dev/shm/battery'
ExecStopPost=rm /dev/shm/battery

[Install]
WantedBy=multi-user.target

Might reduce the polling frequency (set to 2 seconds in that service file), might not. It’s not an especially heavy script. Polling frequency is currently to once every 60 seconds in that service file.

I’m writing the charge percentage and charging state to /dev/shm/battery in that file to allow multiple scripts to check my battery’s charge state and percentage. Thought about using a named pipe, but didn’t want to deal with any confusion later on if the script’s output gained a consumer.

Currently the sole consumer is a script I’m running from yambar to keep tabs on my charge percentage and charging state, ~/intramuros/scripts/battery-yambar.sh":

#!/bin/sh

percent="$(cut -f1 /dev/shm/battery)%"
state="$(cut -f2 /dev/shm/battery)"

if [ "$state" = "charging" ]; then
	percent="⚡ $percent"
fi
echo "battery|string|$percent"
echo

Which is referenced in ~/.config/yambar/config.yml like so:

...
  right:
...
    - script:
        path: "/home/lykso/intramuros/scripts/battery-yambar.sh"
        args: []
        content: {string: {text: "{battery}"}}
        poll-interval: 2
...

As you might have noticed, I’m also using @salsaalibi’s script for writing text to my OLED screen when I reach 75% discharged and 75% charged. It was originally shared here. I’m including it here as well for completeness:

#!/bin/bash

# In order to run this script, a user needs to have permission to write to /dev/hidraw0. One way
# to do this is to create a udev rule to set the device's group to a group that the user belongs
# to. For example, this command will create a udev rule that sets the group on all hidraw devices
# to 'dialout' and sets their permissions to 660.
#
# $ sudo echo SUBSYSTEM=="hidraw", GROUP="dialout", MODE="0660" > /etc/udev/rules.d/50-hidraw.rules
#
# After creating the rule, restart the machine to reload the device with the new properties.
#
# Make sure to add the user to the group you used, if they aren't already a member of that group.
# See /etc/group for current group membership and the adduser command for assigning a user to a group.


help()
{
	echo "Write some text to the OLED display on MNT Reform."
	echo "Usage: oledmsg [OPTIONS] [TEXT]"
	echo
	echo "The OLED display is 4 lines tall. Each line is 21 characters long."
	echo "If TEXT is omitted, oledmsg will clear the OLED display."
	echo
	echo "Options:"
	echo "	-h	Print this help."
	echo "	-j	Print all text arguments without line breaks. This is the default."
	echo "	-l	Start a new line between each text argument. Long lines will wrap."
	echo "	-c	Start a new line between each text argument. Long lines will be clipped."
	exit
}

JOIN_MODE=0
BREAK_MODE=1
CLIP_MODE=2
MODE=$JOIN_MODE

while getopts :cjl FLAG; do
	case $FLAG in
		c) # line break between args and clip long lines
			MODE=$CLIP_MODE
			;;
		l) # line break between args and wrap long lines
			MODE=$BREAK_MODE
			;;
		\?) # unknown option
			help
			;;
	esac
done

# Shift argument pointer to the end of option arguments (start of text arguments).
shift $((OPTIND-1))

# Build a string according to the chosen mode.
if [ $MODE -eq $BREAK_MODE ]; then
	for ARG in "$@"; do
		for (( i=0; i < ${#ARG}; i+=21 )); do
			MSG+=$(printf "%-21s" "${ARG:(i):21}")
		done
	done
	MSG=$(printf "%-84s" "${MSG}")
elif [ $MODE -eq $CLIP_MODE ]; then
	for ARG in "$@"; do
		MSG+=$(printf "%-21s" "${ARG:0:21}")
	done
	MSG=$(printf "%-84s" "${MSG}")
elif [ $MODE -eq $JOIN_MODE ]; then
	ARGJOIN="$@"
	MSG=$(printf "%-84s" "${ARGJOIN}")
else
	help
fi

# Write the formatted message to the OLED display.
echo "xOLED${MSG:0:84}" > /dev/hidraw0
2 Likes

Thanks for sharing this! I especially appreciate the systemd service file. I had been wondering about mapping the LPC data to a file in /dev, and your solution looks very simple and convenient.

1 Like

The most “correct” solution probably would be to just write a driver to poll the battery and populate, e.g., /sys/class/power_supply/BAT0, but I’ve never written a Linux driver before and it frankly seems like it’d be overkill for my use case. :smiley:

TBH, I’m looking at what I’ve got now and thinking maybe I should trim that first script waaay down and move to a model where interested scripts just poll /dev/shm/battery for the raw LPC line. Seems like it’d be simpler. But I’ve probably spent enough time on this for today.

Edit: It’s quite nice trimmed-down and split up, but I think I like the approach I posted better after all.

Correct, but driver should communicate with lpc directly, via spi/i2c/1wire/etc.
AFAIR it was supposed to comm via I2C SPI but there was some issue so Lukas disabled it till better times.
I could write a simple driver but need to know the status of the electronics.

2 Likes

Thank you for your work! I used the script and service, and I’m showing the battery percentage in waybar.

One thing that I had to do, was change the initial sleep call from 2 to 5 seconds, otherwise the script would exit with an I/O Error.

1 Like

You many want to double check your hidraw number. I have a solokey in a USB port and it grabbed hidraw0 (meaning I had to use hidraw1).