Programming a Raspberry Pi To Read a Gamepad
Use a USB controller to control your Pi-based projects!
Join the DZone community and get the full member experience.
Join For FreeLast week I wrote up how to prepare the UCTRONICS robot I've been playing with for hacking.
This week, I'm taking a break to update an old tutorial from five years ago: how to use a gamepad to control a Raspberry Pi. This post has been around a few years, and I just found out that a bunch of images broke, so it's time to update with a new Pi and easier-to-follow instructions.
Here are the robot and gamepad I'm using. I won't get the robot moving this week, but I'll lay the foundation for next week.
You can get the robot here. (Affiliate link, as are a few others.)
You can get the gamepad here. It's a Logitech F710, but these instructions should work with any USB gamepad. You might see different scan codes, so follow my procedures and adjust for the codes you see.
Prerequisites
You'll need a Raspberry Pi and a gamepad.
You'll also need to be able to open a terminal on the Pi. One way is to follow my tutorial last week and install Jupyter, which will give you a terminal and an easy way to test the code I'll show you here.
If you're already running another program watching for controllers, kill it before trying this tutorial.
Reading from Device Files
To use the gamepad, we need to read information from it. Linux, like most UNIX-like operating systems, makes this easy via device files. When the gamepad (or its receiver in the case of a wireless device) is connected, Linux creates a special file that produces characters when you operate the gamepad.
The /dev/input directory
First, let's take a look at the /dev/input directory without the gamepad (or the USB transceiver) connected.
Open a terminal on your Raspberry Pi. I'm going open a shell to mine. You can either ssh to the Pi or connect a keyboard and screen and use the GUI. Jupyter terminal seems to have problems reading devices files.
Now, list the content of /dev/input.
You should see a file named mice. (I don't see any others since I don't have any input devices plugged in yet. You may see a few more if you have a keyboard and mouse attached.)
Now, connect the gamepad or its USB receiver and wait a few seconds.
You'll see a couple of new files and two new folders if this is the first device you're connecting to.
The event0 and js0 devices correspond to my gamepad. The by-id and by-path directories provide an alternative method for addressing devices, as well as additional information about them.
Since these devices are files, you can use command-line utilities to check them out. Let’s see what’s “in” them.
Opening them in an editor won't work well, but you can use good old cat to have some fun.
Cat event0 (or eventN if you had one before you connected the new gamepad) and press a button on the gamepad:
The image above is for a single button press. That’s a lot of characters! And most of them seem garbled, but the gamepad device file is just like any other file you can read.
Reading the Gamepad
We’re going to use the evdev package to interpret the input from the gamepad. I’ll explain what this package does as we go. The package has not been installed on any of the Pi distributions I have worked with, so let’s start by installing it with pip:
sudo pip install evdev
Pip will do its thing, and after a bit (it can take a while on a slower Pi), the package is installed.
Now let’s open the gamepad and print some information about it. For these exercises, I'm using Jupyterhub, but a command-line session with Python or running these lines from a script will work too.
The first line imports InputDevice from evdev.
The second line creates an InputDevice by passing the path to the gamepad device file.
Finally, we print this new device object to standard output and see some interesting information about it. You'll need to either click stop on Jupyter or press CTRL-C to end the read loop.
While the mechanics of reading from the gamepad device are as simple as any other file, this quick 3 line program demonstrates how evdev makes it even simpler. The package has a tremendous amount of code built-in for managing devices like joysticks and gamepads.
Let’s use evdev to read some buttons. Start with the same import statements and create an InputDevice as we did above, but now, replace printing the device with the two lines below. Then press a button on your gamepad.
Instead of the garbled characters we saw earlier, we see why there were so many characters earlier. The gamepad sends a lot of info with each button press.
Before we discuss the events, let’s talk about this line:
xxxxxxxxxx
for event in gamepad.read_loop()
This illustrates another feature of the evdev package. The package provides a simple for loop that reads the device and creates events for us. Without this feature, our code would look something like this:
xxxxxxxxxx
from evdev import InputDevice
from select import select
gamepad = InputDevice('/dev/input/event0')
while True:
r,w,x = select([gamepad], [], [])
for event in gamepad.read():
print(event)
But the output wouldn't look nearly as clean.
Evdev eliminates the outer while loop, as well as the call to select. In terms of lines of code, the difference is negligible, but the read_loop() version is easier to read.
In an application that needs to monitor more than one input device, such as a mouse and a keyboard, read_loop() won’t work without setting up more than one thread and adding a lot of complexity. Select is excellent for reading and writing from multiple devices.
Now, let’s identify what’s being pressed on the gamepad:
Now for every event, we examine the type field and check if it is a button.
If it is a button, categorize* gives us a more specific type of event.
Note that we see both the button down up events. Let's finish up by capturing that key down events for the A-B-X-Y buttons on the gamepad.
I'm going to use the four buttons on the right side of the gamepad to control my robot in next week's post. So, I pressed each one and noted what event is generated.
xxxxxxxxxx
Button 'A' - key event at 1607808074.513679, 305 (['BTN_B', 'BTN_EAST']), down
Button 'X' - key event at 1607808091.133587, 304 (['BTN_A', 'BTN_GAMEPAD', 'BTN_SOUTH']), down
Button 'Y' - key event at 1607808172.285273, 307 (['BTN_NORTH', 'BTN_X']), down
Button 'B' - key event at 1607808188.589244, 306 (BTN_C), down
We see the button's scancode, keycodes, and then up or down. One of the buttons has three keycodes. Another has only one. None of them match the labels on the controller!
So, ignoring the keycodes and using the scancodes is our best bet for figuring out which button is being pressed.
Your gamepad may register different codes, so adjust your code accordingly.
xxxxxxxxxx
from evdev import InputDevice, categorize, ecodes, KeyEvent
gamepad = InputDevice('/dev/input/event0')
for event in gamepad.read_loop():
if event.type == ecodes.EV_KEY:
keyevent = categorize(event)
if keyevent.keystate == KeyEvent.key_down:
if keyevent.scancode == 305:
print('Back')
elif keyevent.scancode == 304:
print ('Left')
elif keyevent.scancode == 307:
print ('Forward')
elif keyevent.scancode == 306:
print ('Right')
This code is available as a gist.
Run this and press a few buttons. It works!
You could easily add code to check for up or down events and add duration to your gamepad controller!
That’s it for this week. Next week I'll get back to hacking the UCTRONICs car.
Published at DZone with permission of Eric Goebelbecker, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments