I have a number of Raspberry Pi installed in my home for specific tasks that have no monitor or keyboard connected. To shut these down, I always need to ssh into them (fortunately they are all network connected) but this is not very practical. In this post I describe how to add a power-off button to a Raspberry Pi to shutdown a headless Raspberry Pi cleanly.
The Raspberry Pi is a great small computer that often runs a full Linux distribution or Linux-based appliance and hence requires to be shutdown properly to not damage the filesystem on the SDHC card containing its software. To shutdown a headless Raspberry Pi (i.e. without keyboard/monitor), one either needs to connect a keyboard to hit
DEL or log in over the network to shut it down. As this is not very practical I have been looking for a solution to simply push a button to shutdown a headless Raspberry Pi for a while.
Now I am definitely the first solving this problem, see also this MagPi article, this or this Hackster guide or this elaborate guide for the RPi4, but somehow none of these suited my needs (either too simple or too complex) and/or had dependencies on external libraries. Besides they were all quick solutions (which is fine as they were designed as such) and not really flexible and documented. Although my solution is the same hard-ware wise, I have implemented the generic script
gpio_trigger.py (available on my GitLab instance).
My specific use case was a Raspberry Pi2 running Calin Crisan’s excellent motionEyeOS, a great solution to build your own camera surveillance system. As the Pi2 running it also had a camera itself, it was located in an impractical place in my house to plug in a keyboard (on top of a cupboard in my kitchen), I really needed something else to power it off when needed.
The power-off button
To create the power-off button, all that is needed are basic soldering gear, a simple NC (normally not connected) switch and short dupont cable with 2 female connectors:
As we need to connect the switch to the Raspberry Pi GPIO connector, I simply cut this cable in the middle, stripped the two just-cut ends and soldered them to the two contacts of the switch. Next I connected the sides with the headers to pins 39 and 40 of the Raspberry Pi GPIO as depicted below (yes different cable as I made several of these):
As you can see the power-button fits nicely in the case I had (the hole was already there). GPIO pins #39 and #40 are the two pins close to the USB port.
Testing the button
Before closing the case, it is good to test the setup. I did this with the script I created by running:
sudo ./gpio_trigger.py echo button pressed
This runs the script in interactive mode and waits until the button is pressed and then executes the shell command
echo button pressed (which just prints “button pressed”). The script must be run as root (or with
sudo as I do in this example) to access the GPIO.
Once this works, the next step is to test the script as intended, to power-off your Raspberry Pi. For this run the command:
sudo ./gpio_trigger.py -D poweroff
This will start the script in the background (option
-D) waiting for the button to be pressed. The shell prompt will immediately return and you can even log out. Below movie shows what happens when the button is pressed.
Once the button is pressed, the green I/O LED flashes a few times randomly and then 10 times to indicate that the Pi has powered down.
When this works, you can implement this solution permanently on your Raspberry Pi. Please note though that by using
poweroff, the Pi will end up in a mode that requires to disconnect the power and connect it again to restart the Pi (or to add a reset button to start it again, which I don’t need).
Implementing on motionEyeOS
To implement this as a permanent solution,
gpio_trigger.py must be started during the boot-up process. For MotionEyeOS this means copying it to
/data/gpio_trigger.py and adding to
/data/gpio_trigger.py --daemon --hold 5000 poweroff
As you can see I am using long command options as they are more clear later and also added
--hold 5000, which requires the power-off button to be kept pressed for 5 seconds so that an accidental press would not shut it down.
Since with my Pi’s camera case is closed I can barely see the internal LEDs so I added visible feedback using the Pi’s network adapter’s LEDs. Normally I have these switched off so that they don’t annoy anyone but during the power-off sequence I wanted them to switch on briefly. The contents of my
/data/etc/userinit.sh for this are::
# disable the ethernet LEDs lan951x-led-ctl --fdx=0 --lnk=0 --spd=0 # enable ethernet LEDs and power off when button is pressed 5s /data/gpio_trigger.py --daemon --hold 5000 -- lan951x-led-ctl --fdx=1 --lnk=1 --spd=1\; poweroff
Now when I press the button after 5 seconds the ethernet lights will go on and then when the power-off is complete will go out again. Below a picture of my motionEyeOS camera with power-off button on the right side.
Implementing using systemd
As I also have a Raspberry Pi running a normal linux distribution, I also implemented this for Raspbian using systemd (other RPi distributions should be similar). From my GitLab instance:
- copy the file
rpi_poweroff_button.serviceand copy it to
- copy the file
/usr/local/sbin/gpio_trigger.pyand make it executable.
Next run the following two commands to enable and activate
sudo systemctl enable rpi_poweroff_button sudo service rpi_poweroff_button start
This will activate
gpio_trigger.py running as a service to be started as all other services have started, waiting for the a button connected to GPIO pins 39 and 40 to be pressed for 5 seconds before starting the
poweroff command. These settings can be changed easily in the
To disable this service run the command:
sudo service rpi_poweroff_button stop
To remove it, run:
sudo systemctl disable rpi_poweroff_button
Implementing without systemd
For systems without systems, the solution to to start
gpio_trigger.py during the boot-up process is often adding it to /etc/rc.local. From my GitLab instance, install
/usr/local/sbin/ and make it executable. Next add the following to
if [ -x /usr/local/sbin/gpio_trigger.py ]; then /usr/local/sbin/gpio_trigger.py --daemon --hold 5000 poweroff echo enabled the shutdown button else echo unable to enable the shutdown button fi
Again using long command options as they are more clear later and added
--hold 5000 again to require the power-off button to be pressed for at least 5 seconds before starting the shutdown.