Nostalgia-Tron, Part 9: A utility script for testing controls

Posted in Articles, Raspberry Pi, Tutorials

For the moment I’m out of hardware topics, so let’s look at some stuff that might be more widely applicable to folks who don’t make my exact hardware choices.

In a minute I’ll show you a script I wrote for testing input. But first…

Quitting EmulationStation

I usually don’t have a keyboard hooked up to the arcade Pi. I have one wireless keyboard/trackpad and it roams from Pi to Pi based on my needs. The more I can do from a remote session the better.

You might remember that RetroPie uses a frontend called EmulationStation. For reasons that will soon become clear, I wanted a quick way to close EmulationStation gracefully from a remote session. This is what the quit-emulationstation script does.

#!/bin/bash

print_help_and_exit() {
  echo "Usage: quit-emulationstation [options]"
  echo ""
  echo "Exits EmulationStation gracefully (if it is running)."
  exit 0
}

while true ; do
  case "$1" in
    -h) print_help_and_exit ;;
    *)  break ;;
  esac
  shift
done

pid=`ps -ef | awk '/emulationstation\/emulationstation$/ {print $2}'`

if [ -z $pid ]; then
  echo "EmulationStation doesn't appear to be running."
  exit 1
else
  printf "Quitting EmulationStation..."
  kill $pid && echo -e "done.\n"
  exit 0
fi

This is almost comically verbose for a script that does a really simple thing. That’s on me. My assumption with these scripts, as with most of the code I write, is that I will not remember how to use them six months later.

Like the others, this lives in /home/pi/bin and is just a few keystrokes away.

Testing controls

When I was wiring up the control panel, I took advantage of the fact that the I‐Pac turns button presses into simulated keyboard presses. At any point I could plug the half‐finished panel into the nearest computer via USB and make sure the button I just wired up was actually doing what I thought. Typically I’d open an empty document in TextEdit or something just so I could have a sandbox for observing key presses.

Things got harder after I put the control panel in place. Say a button seems to stop working in the middle of gameplay — what then? I could quit the game and launch a text editor, but to do that I’d have to dig out the wireless keyboard and start a direct terminal session. This is at odds with my preferred debugging strategy: SSHing into the Pi from my laptop. It’s easier to type on my laptop than on a small wireless keyboard, plus the second screen comes in handy.

But if I SSH into the Pi and open a text editor, input from the I‐Pac won’t be reflected in that document. If local input affected remote sessions, Linux wouldn’t truly be multi‐user.

So I did some research to see how I could detect input from the I‐Pac’s pseudo‐keyboard from a remote SSH session. I eventually found a Ruby gem called libdevinput that would let me listen to raw input from a specific device.

Since I wrote this script, libdevinput has been deprecated in favor of device_input, so in preparation for this article I ended up tweaking this script for the latter library’s slightly different API.

#!/usr/bin/env ruby

require 'pathname'

begin
  require 'highline'
  require 'device_input'
rescue LoadError => e
  puts "This program requires Highline and device_input:"
  puts "  $ gem install highline device_input"
  exit 1
end

KEY_MAP = {
  "Player 1 Button 1 (Red)"    => 29,  # LeftControl
  "Player 1 Button 2 (Yellow)" => 56,  # LeftAlt
  "Player 1 Button 3 (Green)"  => 57,  # Space
  "Player 1 Button 4 (Blue)"   => 42,  # LeftShift
  "Player 1 Button 5 (White)"  => 44,  # Z
  "Player 1 Button 6 (White)"  => 45,  # X

  "Player 1 Joystick UP"    => 103, # Up
  "Player 1 Joystick DOWN"  => 108, # Down
  "Player 1 Joystick LEFT"  => 105, # Left
  "Player 1 Joystick RIGHT" => 106, # Right

  "Player 2 Button 1 (Red)"    => 30,  # A
  "Player 2 Button 2 (Yellow)" => 31,  # S
  "Player 2 Button 3 (Green)"  => 16,  # Q
  "Player 2 Button 4 (Blue)"   => 17,  # W
  "Player 2 Button 5 (White)"  => 37,  # K
  "Player 2 Button 6 (White)"  => 23,  # I

  "Player 2 Joystick UP"    => 19,  # R
  "Player 2 Joystick DOWN"  => 33,  # F
  "Player 2 Joystick LEFT"  => 32,  # D
  "Player 2 Joystick RIGHT" => 34,  # G

  "Player 1 Coin"  => 6,   # 5
  "Player 2 Coin"  => 7,   # 6
  "Player 1 Start" => 2,   # 1
  "Player 2 Start" => 3,   # 2

  "Pause"  => 25,  # P
  "Enter"  => 28,  # Enter
  "Tab"    => 15,  # Tab
  "Escape" => 1,   # Escape
}

$cli = HighLine.new

Signal.trap('INT') do
  puts ""
  exit 1
end

def choose_device
  base_dir = Pathname.new('/dev/input/by-id')
  # Will get all non-hidden files.
  list = Pathname::glob( base_dir.join('*') )
  table = {}
  list.each do |p|
    table[p.basename.to_s] = p
  end
  names = table.keys
  puts "Select the device you want to test:"
  choice = $cli.choose do |menu|
    menu.prompt = "Choice: "
    menu.choices(*names)
  end

  table[choice]
end

$device = choose_device

def keypress?(event)
  return false if !event
  return false if event.data.type != 1
  return false if event.data.value != 1
  true
end

def description_from_code(event)
  KEY_MAP.key(event.data.code)
end

puts "Listening for presses..."

File.open($device, 'r') do |dev|
  DeviceInput.read_loop(dev) do |event|
    if keypress?(event)
      desc = description_from_code(event)
      puts desc unless desc.nil?
    end
  end
end
Notes
  • The script requires the aforementioned device_input gem. It also requires highline to generate the list of devices to choose from.
  • You’ll have noticed the KEY_MAP hash. That’s the part you’d need to customize. It maps my human‐readable button names to the keys they should be mapped to, as represented by key codes.

    If you’re expecting the key codes to be the same as the ones you’d get from a key press in a web browser, I’ve got some awful news for you. To figure out the right codes for the keys you want to test, run sudo showkey --keycodes, then press some keys on the keyboard that’s hooked up to the system — not the keyboard belonging to the computer running your SSH session. With each key press it’ll log that key’s integer value.

  • This script works wonderfully for I‐Pac users, since an I‐Pac presents itself as a keyboard to the system. Some other USB encoders for arcade controls present themselves as joypads or mice rather than keyboards, but since they’re all HID devices, I’d guess that this approach would work without much alteration. You’d just have to figure out the codes produced by the buttons and joystick directions.

  • When you run this script, you’ll choose a device from the options listed (mine is usb-Ultimarc_IPAC_2_Ultimarc_IPAC_2_9-if01-event-kbd), at which point the script will go into a loop waiting for keypresses. Any keypress that maps to a button will get that button’s name logged; press Player 1’s first button and you should see Player 1 Button 1 (Red) (or whatever you named it).

  • My original version of this script cycled through all the defined keys and had you press each one in turn. Later I decided it was much simpler to have the script wait in a loop and tell you when you pressed a key it recognized.

Next time

There are more scripts to cover, but since they all revolve around editing game metadata, I’m saving them for their own post.

Comments

  1. Pingback

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.