A guide to power off the Pinephone on low battery

Update 2022-09-24:

Auto-poweroff before the battery is no longer recoverable by the phone has been implemented in several distros recently. Check with your distro to see if you need this solution.


I created a lowpoweroff script for the Pinephone and Pinephone Pro to help prevent over-discharge by forcing a power-off below a certain capacity.

It loops through all power sources the kernel is aware of and enforces a minimum capacity rule that powers off the system if a battery is below a certain threshold (default: 5%) and not charging.

Install the lowpoweroff script

# Create /usr/local/sbin if it doesn't exist
# and set privileges if it's been newly created
sudo mkdir /usr/local/sbin && sudo chmod 755 /usr/local/sbin

# Install lowpoweroff
sudo touch /usr/local/sbin/lowpoweroff
sudo chmod 755 /usr/local/sbin/lowpoweroff
sudo vi /usr/local/sbin/lowpoweroff

# Paste the following:
#!/usr/bin/env bash
# License: BSD 0-Clause, Ulfnic
Err(){
	printf '%s\n' "$2" 1>&2
	[ $1 -gt 0 ] && exit $1
}

while [ "$1" ]; do
	case $1 in
		--test|-t)
			Test=1 ;;
		--min-capacity|-m)
			MinCapacity=$2
			shift ;;
		*)
			Err 1 "Unrecognized option: $1" ;;
	esac
	shift
done

# Apply default to minimum battery capacity and validate
: ${MinCapacity:=5}
Re='^[0-9][0-9]?0?$'
[[ $MinCapacity =~ $Re ]] || Err 1 "Unrecognized minimum battery capacity: $MinCapacity"

BatteryPathCount=0
for BatteryPath in /sys/class/power_supply/*; do
	[[ $Test == 1 ]] && printf '%s\n%s\n' "=== $((BatteryPathCount++)) ===" "Battery: $BatteryPath"

	if [[ ! -f "$BatteryPath/type" ]]; then
		[[ $Test == 1 ]] && printf '%s\n' "Skipping, battery type file not found at: $BatteryPath/type"
		continue
	fi
	BatteryType=$(cat "$BatteryPath/type")
	[[ $? > 0 ]] && continue
	[[ $Test == 1 ]] && printf '%s\n' "Type: $BatteryType"

	if [[ $BatteryType == 'Battery' ]]; then

		# Fetch and act on battery status
		if [[ ! -f "$BatteryPath/status" ]]; then
			[[ $Test == 1 ]] && printf '%s\n' "Skipping, battery status file not found at: $BatteryPath/status"
			continue
		fi
		BatteryStatus=$(cat "$BatteryPath/status")
		[[ $? > 0 ]] && continue
		[[ $Test == 1 ]] && printf '%s\n' "Status: $BatteryStatus"

		if [[ $BatteryStatus == 'Discharging' ]] || [[ $Test == 1 ]]; then

			# Obtain battery capacity and poweroff if it's below minimum threshold
			if [[ ! -f "$BatteryPath/capacity" ]]; then
				[[ $Test == 1 ]] && printf '%s\n' "Skipping, battery status file not found at: $BatteryPath/capacity"
				continue
			fi
			BatteryCapacity=$(cat "$BatteryPath/capacity")
			[[ $? > 0 ]] && continue
			[[ $Test == 1 ]] && printf '%s\n%s\n' "Capacity: $BatteryCapacity" "Minimum Capacity: $MinCapacity"

			# Validate battery capacity format
			Re='^[0-9][0-9]?0?$'
			if [[ ! $BatteryCapacity =~ $Re ]]; then
				[[ $Test == 1 ]] && printf '%s\n' "Skipping, battery capacity format is incompatible: $BatteryCapacity"
				continue
			fi

			if [[ $BatteryCapacity < $MinCapacity ]]; then
				if [[ $Test == 1 ]]; then printf '%s\n' "Battery below minimum capacity, this would trigger poweroff"
				elif [ -f '/usr/sbin/poweroff' ]; then /usr/sbin/poweroff
				elif [ -f '/sbin/poweroff' ]; then /sbin/poweroff
				else Err 1 'poweroff not found'
				fi
			fi
		fi
	fi
done

Test lowpoweroff script

Normally lowpoweroff is quiet unless there’s an error. Adding --test performs a dry-run and displays a readout of every power supply value it’s reading. If the script is working and USB is plugged out, this test should say “Battery below minimum capacity, this would trigger poweroff”.

# Plug in USB power
sudo /usr/local/sbin/lowpoweroff --test --min-capacity 100
# Unplug USB power
sudo /usr/local/sbin/lowpoweroff --test --min-capacity 100

Make it a daemon

lowpoweroff needs something to run it once every minute. Choose from Option 1 or 2 below this post for the method most suitable to your operating system.

If you’d like to set it up differently, here’s some example commands you might want to use:

# Auto-poweroff at the default 5% capacity
/usr/local/sbin/lowpoweroff

# Auto-poweroff at 3% capacity
/usr/local/sbin/lowpoweroff --min-capacity 3
2 Likes

Option 1. Setting up lowpoweroff with systemd

Create the timer that’ll run lowpoweroff 3 minutes after boot and every minute thereafter:

sudo vi /etc/systemd/system/lowpoweroff.timer
# Paste the following:
[Unit]
Description=Power off if battery capacity is too low

[Timer]
OnBootSec=3min
OnUnitActiveSec=1min

[Install]
WantedBy=timers.target

Create the service to be run by the timer:

sudo vi /etc/systemd/system/lowpoweroff.service
# Paste the following:
[Unit]
Description=Power off if battery capacity is too low

[Service]
Type=oneshot
User=root
Group=root
ExecStart=/usr/local/sbin/lowpoweroff

Initialize the service:

# Recognize the new unit files you made
sudo systemctl daemon-reload

# Start the service and check it's status
sudo systemctl start lowpoweroff.timer
sudo systemctl status lowpoweroff.timer

# If it's running correctly, enable it so it starts on boot.
sudo systemctl enable lowpoweroff.timer
1 Like

Option 2. Setting up lowpoweroff with cron

Verify crond is running. If not follow the appropriate steps for your init system and make sure it’s enable to run at boot.

# === FOR: Systemd === 
sudo systemctl status crond
# If not runing:
sudo systemctl start crond # Start service
sudo systemctl status crond # Confirm working correctly
sudo systemctl enable crond # Start crond at boot

# === FOR: OpenRC (PostmarketOS) ===
sudo rc-service crond status
# If not runing:
sudo rc-service crond start # Start crond
sudo rc-service crond status # Confirm working correctly
sudo rc-update add crond # Start crond at boot

#  === FOR: runit ===
sudo sv status crond

Add the script to the root crontab set to run every minute:

sudo crontab -e
# Paste the following in a new line and save
*	*	*	*	*	/usr/local/sbin/lowpoweroff
1 Like