Nostalgia-Tron, Part 5: A proper power button

Posted in Articles, Raspberry Pi, Tutorials

Now to the fun part.

The rest of this series will cover the customizations I’ve made to my arcade cabinet. They’re not completely new ideas, but they’re things that I myself wish I’d been able to find guides for before I dove into the deep end.

Let’s start with something simple.

I wanted an easy way to power the system on or off without going through a menu. Turning on a Pi is easy enough: just plug it into power. But turning it off is harder than just removing the power. Like any other computer, you’re tempting fate if you don’t let it shut down gracefully first.

Various solutions have emerged for this problem. The one that stood out to me was Mausberry’s shutdown switch.

How it works

For years laptops and desktops have been able to turn themselves on and off via software. When you turn off your PC, you don’t need to hit a switch at the end the way you did back in the Windows 95 days. The computer goes into a mode virtually identical to its “unplugged” state, except that it draws a tiny amount of power so that it can turn itself back on when a button is pressed — or, optionally, when it receives certain input from LAN, USB, or elsewhere.

In those computers, something called ACPI makes all this possible. The Pi, being a $40 computer, does not have ACPI. The way to shut down your Pi safely is to run sudo poweroff (or shutdown now, or one of half a dozen other commands that are functionally equivalent). Once the shutdown process is done, you can cut the power to the Pi.

In other words, you can easily make the Pi stop asking for power — just initiate shutdown. But the Pi doesn’t have an “on” button, digital or analog. The way to turn it on is to start supplying power again.

Most Pi kits come with your standard wall wart that turns 120V AC power into 5V DC power supplied via a USB micro connector. In this setup, the way to turn the Pi off is by unplugging it after shutdown, and the way to turn it on is by plugging it back in. Technically, you don’t have to unplug it after shutdown, but once you want to turn it back on again you’ll have to unplug and replug.

The Mausberry switch helps in a few ways:

  1. Rather than make you run sudo poweroff or its equivalent, it lets you execute that shutdown command via a button press.
  2. Because the switch plugs into the Pi’s USB-micro power port and acts as an intermediary, it knows to stop supplying power at the end of the shutdown cycle.
  3. When the Pi is off, a press of the attached button will tell the switch to start giving the Pi power again.

The result: safe power cycling without having to pull anything out of the wall. Your Pi can be as power‐savvy as your PC.

Setting it up

Mausberry sells several varieties of their switch. I chose the “use your own switch” circuit because the rest all use traditional rocker switches, and I wanted to use a momentary button. (Luckily, the switch supports either kind of input.) If I used a rocker switch, that switch would be the single source of truth. But a button doesn’t have any state; it would give me the flexibility to power off the system in other ways if I wanted.

I had a few extra tiny buttons of the sort I put into my control panel for auxiliary functions. They’re perfect for this project because they’re small and low‐profile and because the button doesn’t jut out from the housing at all, so it’s very hard to press by accident.

Mausberry’s site has pretty good instructions for how to wire up the button. I didn’t wire up the button’s internal LED because I didn’t need it, but the option is there if you feel differently.

The other important part is connecting the switch to two GPIO signal pins. Mausberry’s instructions recommend GPIO 23 and 24, but pick any two you like. Just remember which two later on when you’re setting up the daemon.

Writing a shutdown script

There’s one software task we’ve got to take care of: writing a script that listens on the GPIO pins to know when it should initiate shutdown.

Mausberry distributes a bash script for this, but I decided I’d rather write my own in Python. Their version simply checks the state of the pins every second, and while I didn’t think that would be a huge drain on resources, it seemed like a blunt way of getting the job done.

Raspbian Jessie comes with Python 2.7 and 3.4, and both versions have a built‐in library for communicating with the Pi’s GPIO pins. (You shouldn’t need to install any new packages via apt, but let me know if I’m wrong.) The RPi.GPIO library gives us more sophisticated ways of interacting with the GPIO pins.

Much like writing an event listener in JavaScript involves less work than writing a poller, we can replace the bash script’s polling behavior with a hardware interrupt.

The code

#!/usr/bin/env python3

import os
import signal
import sys

from RPi import GPIO

# The lead marked OUT.
PIN_OUT = 23
# The lead marked IN.
PIN_IN  = 24

def on_exit(signum=None, stack=None):
  GPIO.cleanup()
  sys.exit(1)
  
signal.signal(signal.SIGINT, on_exit)

GPIO.setmode(GPIO.BCM)

# The lead labeled OUT is setup as IN — it's output from the power switch,
# but it's input to us, and vice-versa.
GPIO.setup(PIN_OUT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(PIN_IN,  GPIO.OUT)

GPIO.output(PIN_IN, GPIO.HIGH)

# The script will wait here until the shutdown event gets flagged by the
# callback thread. That way we don't waste CPU by polling at an interval.
print("Waiting...")
GPIO.wait_for_edge(PIN_OUT, GPIO.BOTH)

# If we get this far, the switch has been set to OFF. Power down the system
# gracefully.
#
# We don't need to reset the event or loop or anything because, well, the
# system is about to be shut down.

# Use this stub file to keep track of the last shutdown.
os.system("touch '/home/pi/last_poweroff'")

print("Powering off!")
os.system("sudo poweroff")

How does the script work?

This is 20 lines of code if you ignore comments — and you should always ignore my code comments. It’s a good warmup for the more complicated code we’ll be writing in future installments. So let’s look at it line‐by‐line.

PIN_OUT = 23
PIN_IN  = 24

The Mausberry switch labels the pins “out” and “in,” but its output is treated as input from the Pi’s perspective, and vice‐versa. So PIN_OUT is the Pi’s input pin, and PIN_IN is the Pi’s output pin. My preference would’ve been to name them in a less confusing way, but I couldn’t think of a better one, and at least this way you’ll be able to remember how to hook them up.

def on_exit(signum, stack):
  GPIO.cleanup()
  sys.exit(1)

signal.signal(signal.SIGINT, on_exit)

Here we’re defining a handler that will run if we exit in an exceptional way — e.g., if our process is killed, or if the user presses Ctrl + C. In those cases we should call GPIO.cleanup and exit with a non‐zero exit code. (The cleanup method will reset the pins we touched — and only the pins we touched. If we don’t call it, those pins will stay in the state we left them in even after our script exits. Rude.)

GPIO.setmode(GPIO.BCM)

There are two common ways of numbering the GPIO pins, and we want to opt into “BCM” numbering. If you care about what this means, check pinout.xyz (which is worth bookmarking if you haven’t already).

GPIO.setup(PIN_OUT, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(PIN_IN,  GPIO.OUT)

GPIO.output(PIN_IN, GPIO.HIGH)

Because the pins on the Pi are “general‐purpose” — i.e., any pin can serve as either an input or an output — we have to define in software which pins are serving which functions. We set up PIN_OUT (output from the switch, input into the Pi) as an input pin pulled to high — which, to oversimplify, means that we want it to be “on” by default. We set up PIN_IN (output from the Pi, input into the switch) as an output pin, then tell it to output high voltage.

Why two pins?

  • PIN_OUT is used by the switch to talk to the Pi. When the logic level changes on that pin, it’s telling the Pi to initiate shutdown. Writing this script is our way of fulfilling that contract.
  • PIN_IN is used by the Pi to talk to the switch. Writing it to high is our way of telling the switch that the Pi is on. After we shut down and the Pi deactivates the GPIO pins, that pin’s level will naturally fall to low, which tells the switch that shutdown is complete and it can cut off power to the Pi.
print("Waiting...")
GPIO.wait_for_edge(PIN_OUT, GPIO.BOTH)

Once we’ve configured an input pin, we can get its value at any time by calling GPIO.input(pin_number). But that will tell us what the value of the pin is at that instant, so to use this to detect a change in state we’d have to poll. No good.

Instead, one way to use hardware interrupts is the GPIO.wait_for_edge function. Here we’re saying, “pause the script and do not proceed until PIN_OUT changes from high to low.”

os.system("touch '/home/pi/last_poweroff")

The call to wait_for_edge makes our lives easy. It means that once the script gets past that line, we are certain that we’re about to shut down the computer.

The next line is something I threw in while debugging but decided to keep. The touch command will update a file’s last‐modified time to the current time, and will create the file if it doesn’t exist. Putting it here lets you distinguish between a graceful shutdown and an abrupt shutdown; if the timestamp on /home/pi/last_poweroff doesn’t match up to the last reboot, then that reboot didn’t happen cleanly.

print("Powering off!")
os.system("sudo poweroff")

Finally, we tell the user we’re about to power off (when we daemonize this script, this output will go into the system log) and then execute the sudo poweroff command. The OS takes it from there.

How do I run it?

RetroPie puts a lot of the user’s custom data — ROMs and such — under the pi user’s home folder. That’s as good a place to put this as any.

Create /home/pi/bin if it doesn’t already exist. Also check that it’s in your PATH:

echo $PATH
# don't see `/home/pi/bin` or `~/bin`? run...
echo "export PATH=$PATH:~/bin" > ~/.bashrc
# ...and restart your shell

Save the Python script as /home/pi/bin/monitor-power-switch, then make it executable with chmod +x ~/bin/monitor-power-switch.

Now you can test it out by running monitor-power-switch. Run the script and then press the button. You should see the Powering off! message. Mere seconds later your Pi should be off.

How do I run it on startup?

I remember about a year ago when I googled “raspberry pi add startup script” and had a pleasant experience wandering through the search results. I had to write a bash script to manage its life cycle and place it into an init.d folder somewhere. A bit awkward, but simple enough.

Six months later, googling the same thing, I felt like Troy Barnes coming back into the apartment. Everyone was angry at something called systemd, and though I didn’t yet have an opinion on whether that anger was justified, I just knew that it made it harder to find answers.

Here’s what happened: in between Wheezy (the last release) and Jessie (the current release), Raspbian followed the lead of its parent project Debian and switched from init to systemd. This was a controversial decision because many people feel that systemd is overengineered and bloated.

I tend not to resolve that something sucks until it has caused me personal pain, so I mention this only to give you some backstory. You can investigate the controversy yourself if you’re inclined.

But it means that Raspbian Jessie has a different process for creating a startup script than what you may be used to.

Create a .service file

You’ll need to make a .service file — a plain text file that looks roughly like this:

[Unit]
Description=Power switch monitor

[Service]
User=pi
Group=pi
ExecStart=/home/pi/bin/monitor-power-switch

[Install]
WantedBy=multi-user.target

The brevity of this file is refreshing to me, at least: all I need to do is describe the service, tell it what to run, which user/group to run it as, and which phase of the system’s life‐cycle should trigger it.

Save this as ~/monitor-power-switch.service, then do something like this:

chmod +x ~/monitor-power-switch.service
sudo mv ~/monitor-power-switch.service /etc/systemd/system
sudo systemctl enable monitor-power-switch
sudo systemctl start monitor-power-switch

The systemctl utility is what you use to tell systemd to run things. The start command means “start this service now”; the enable command means “start this service every time I boot.” There are corresponding commands stop and disable, plus restart.

And you’re done

I’m pretending that you live in the fantasy world that technical tutorials share with cooking shows, DIY references, and the like — the place where nobody makes a mistake, least of all the instructor, and everything works perfectly the first time. Assuming you do live in this world, you should be done.

For sanity’s sake, it’s a good idea to take your Pi out of your cabinet while you’re testing this stuff. Just wire a loose button to the GPIO until the button is working the way you want. At that point you can figure out where the button will go in your cabinet.

Power Button
Mounted (superglued) to the back corner.

Using a Forstner bit, I drilled a hole on the back of my cabinet in the rear left corner. The button fit into this hole, but the MDF was just barely too thick to allow me to screw on the securing ring from the inside. The way I’d placed the hole didn’t give me many options for workarounds, so I ended up just affixing the button with superglue. God help me if I actually need to remove it one day. Remember, folks: I’m not a role model. I’m a cautionary tale.

What now?

At this point I was finally able to turn my Pi on and off safely with a button press. I’d improved my position, surely, but it occurred to me that my monitor and marquee light still needed to be controlled the old‐fashioned way.

Had I really saved myself any work if I still had to unplug the cabinet from the wall to turn everything off? Wouldn’t it be awesome if they just knew to synchronize their power state with the state of the Pi?

Thus, having encountered a problem, I solved it by making more work for myself. And more work for you, because you’ll have to read a future installment to learn how I made it happen.

Leave a comment

(Huh?)
What's allowed? Markdown syntax, with these caveats…
GitHub-Flavored Markdown

A couple aspects of GFM are supported:

  • Three backticks (```) above and below a section of code mark a code block. Begin a JavaScript block with ```js, Ruby with ```ruby, Python with ```python, or HTML with ```html.
  • Underscores in the middle of words (cool_method_name) will not be interpreted as emphasis.
HTML tags/attributes allowed

Whether you're writing HTML or Markdown, the resulting markup will be sanitized to remove anything not on this list.

<a href="" title="" class=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class=""> <del datetime=""> <em> <i> <li> <ol> <pre> <q cite=""> <s> <strike> <strong> <ul>

Now what? Subscribe to this entry's comment feed to follow the discussion.