Skip to content

Instantly share code, notes, and snippets.

@savetheclocktower
Last active February 5, 2025 19:53

Revisions

  1. savetheclocktower revised this gist Jul 18, 2019. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    **NOTE:** This Gist was an early write-up of [this blog post](https://andrewdupont.net/2017/06/26/nostalgia-tron-part-7-led-control/), part of what became [an eleven-part series on my arcade cabinet](https://andrewdupont.net/series/nostalgia-tron/?order=asc). I'd suggest you read that post instead of this, but some of the comments on this Gist contain updates and field reports that you might find useful.

    # RetroPie, LED control, and you

    I wanted [LEDBlinky](http://www.ledblinky.net/ledblinky.htm)-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.
  2. savetheclocktower renamed this gist Sep 21, 2016. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. savetheclocktower revised this gist Sep 21, 2016. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion led-end
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    #!/usr/bin/env ruby

    # For now, let's just turn all LEDs back on.
    exec(%Q[pacdrive -a -q])
    exec(%Q[/home/pi/bin/pacdrive -a -q])
    2 changes: 1 addition & 1 deletion led-start
    Original file line number Diff line number Diff line change
    @@ -94,7 +94,7 @@ class LEDState
    #
    # Uses `exec`, so once this method is called, we implicitly exit.
    def apply!
    exec("pacdrive", "-q", "-s", to_s)
    exec("/home/pi/bin/pacdrive", "-q", "-s", to_s)
    end

    protected
  4. savetheclocktower revised this gist Sep 20, 2016. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion led-start
    Original file line number Diff line number Diff line change
    @@ -85,7 +85,8 @@ class LEDState
    end
    end

    str.join('').reverse
    binary = str.join('').reverse
    "0x%04x" % binary.to_i(2)
    end

    # Calls the external `pacdrive` app to set the LEDs to the represented
  5. savetheclocktower revised this gist Sep 19, 2016. 2 changed files with 7 additions and 2 deletions.
    1 change: 0 additions & 1 deletion .ledrc
    Original file line number Diff line number Diff line change
    @@ -1 +0,0 @@
    +13, +14, +15, +16
    8 changes: 7 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -88,7 +88,13 @@ If you want to do it exactly how I did:
    3. Paste the contents of the `runcommand-onstart.sh` from this gist.
    4. Repeat steps 3 and 4 for `runcommand-onend.sh`.

    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_. That happens in a file called `/home/pi/.ledrc`. I've included mine in this gist for illustration. You can also read the source of `led-start` to learn more.
    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_. That happens in a file called `/home/pi/.ledrc`. Here's what mine looks like:

    ```
    +13, +14, +15, +16
    ```

    That means "always light up both start buttons, the coin buttons, and the small function buttons." You can read the source of `led-start` to learn more.

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

  6. savetheclocktower revised this gist Sep 19, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions .ledrc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    +13, +14, +15, +16
  7. savetheclocktower revised this gist Sep 19, 2016. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -48,6 +48,7 @@ sudo make install
    Now we're ready to install the `pacdrive` utility.

    ```bash
    cd ~/src
    wget "http://www.zumbrovalley.net/pacdrive/dnld/pacdrive_0_2.tar.gz"
    tar xvzf pacdrive_0_2.tar.gz
    cd pacdrive_0_2
  8. savetheclocktower revised this gist Sep 19, 2016. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -8,7 +8,11 @@ I've got a simple control panel with six buttons per player. All I wanted was th
    * When I launch The Simpsons, only the first two buttons for each player should light up.
    * When I launch Pac-Man, none of the buttons should light up.

    You get the idea. Here's how I did it.
    You get the idea.

    [Here's a demonstration](http://gfycat.com/RingedImpressionableButterfly) in the form of a shaky video.

    Here's how I did it.

    ## Hardware

  9. savetheclocktower revised this gist Sep 18, 2016. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion led-end
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    #!/usr/bin/env ruby

    # For now, let's just turn all LEDs back on.
    exec(%Q[pacdrive -a])
    exec(%Q[pacdrive -a -q])
    2 changes: 1 addition & 1 deletion led-start
    Original file line number Diff line number Diff line change
    @@ -93,7 +93,7 @@ class LEDState
    #
    # Uses `exec`, so once this method is called, we implicitly exit.
    def apply!
    exec("pacdrive", "-s", to_s)
    exec("pacdrive", "-q", "-s", to_s)
    end

    protected
  10. savetheclocktower revised this gist Sep 18, 2016. 1 changed file with 1 addition and 2 deletions.
    3 changes: 1 addition & 2 deletions led-start
    Original file line number Diff line number Diff line change
    @@ -93,8 +93,7 @@ class LEDState
    #
    # Uses `exec`, so once this method is called, we implicitly exit.
    def apply!
    cmd = %Q[pacdrive -b #{to_s}]
    exec("pacdrive", "-b", to_s)
    exec("pacdrive", "-s", to_s)
    end

    protected
  11. savetheclocktower revised this gist Sep 18, 2016. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -83,7 +83,7 @@ If you want to do it exactly how I did:
    3. Paste the contents of the `runcommand-onstart.sh` from this gist.
    4. Repeat steps 3 and 4 for `runcommand-onend.sh`.

    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_.
    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_. That happens in a file called `/home/pi/.ledrc`. I've included mine in this gist for illustration. You can also read the source of `led-start` to learn more.

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

  12. savetheclocktower revised this gist Sep 18, 2016. No changes.
  13. savetheclocktower revised this gist Sep 18, 2016. 1 changed file with 6 additions and 4 deletions.
    10 changes: 6 additions & 4 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -12,13 +12,13 @@ You get the idea. Here's how I did it.

    ## Hardware

    I got a PACDrive from Ultimarc. It's got 16 terminals for LEDs. 1–6 control Player 1's buttons; 7–12 control Player 2's buttons. The remaining four terminals got devoted to the start buttons (13 and 14), the coin buttons (both on 15), and four small buttons I use for functions (all on 16).
    I got a [Pac-Drive](https://www.ultimarc.com/pacdrive.html) from Ultimarc. It's got 16 terminals for LEDs. 1–6 control Player 1's buttons; 7–12 control Player 2's buttons. The remaining four terminals got devoted to the start buttons (13 and 14), the coin buttons (both on 15), and four small buttons I use for functions (all on 16).

    After all that's wired up, all you need is a single USB cable running to your Pi — or, even better, to a powered USB hub that is itself connected to the Pi. The LEDs get powered from USB.

    ## Software

    Most of the software for the PACDrive is meant for a Windows environment. After some digging, I found [this third-party utility for Linux](http://www.zumbrovalley.net/readpost.php?artid=33) that allows for controlling a PACDrive from the command-line. It was exactly what I needed, though it was eight years old and took some wrangling to get installed on a Pi.
    Most of the software for the Pac-Drive is meant for a Windows environment. After some digging, I found [this third-party utility for Linux](http://www.zumbrovalley.net/readpost.php?artid=33) that allows for controlling a Pac-Drive from the command-line. It was exactly what I needed, though it was eight years old and took some wrangling to get installed on a Pi.

    From a stock RPi with Raspbian Jessie, you'll need at least these packages:

    @@ -58,13 +58,13 @@ So now you can control the LEDs from the command line, but that doesn't help muc

    ### How will it know which LEDs to light up for which game?

    One option here would be to use something called `controls.dat`, which is a project for cataloguing the controls for most MAME games. (4-way joystick? 8-way? How many buttons? How many players? Hotseat, or did each player have their own controls? And so on.)
    One option here would be to use something called `controls.dat`, which is a project for cataloguing the controls for most MAME games. (4-way joystick? 8-way? How many buttons? How many players? Hotseat, or does each player have their own controls? And so on.)

    The problem with this approach is that (a) the `controls.dat` project is dormant; (b) there are lots of gaps in its database.

    If you're anything like me, you don't need a comprehensive solution. I don't care how many buttons some random mah-jongg game uses because I'll never play it. I've got about 100 arcade games set up and I don't mind specifying their configs manually.

    So here's what I've got in mind:
    So here's what I came up with:

    1. Create a `/home/pi/leds` directory.
    2. Inside that directory, create another directory for each system you want to control. (I've got `arcade` and `daphne`; you might have others.)
    @@ -87,6 +87,8 @@ These scripts reference two other scripts called `led-start` and `led-end`, whic

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

    What if a game doesn't have a config? Well, if you followed these instructions to the letter, none of the buttons for either player will light up. That's fine with me, because I don't have any games without configs. You might want it to do something different in that situation.

    ## Problems?

    If this doesn't work, it's probably because I left something out. Leave a comment and we'll figure it out together.
  14. savetheclocktower revised this gist Sep 18, 2016. 1 changed file with 19 additions and 0 deletions.
    19 changes: 19 additions & 0 deletions runcommand-onend
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,19 @@
    #!/usr/bin/bash

    # ARGUMENTS, IN ORDER:
    # 1. System (e.g., "arcade")
    # 2. Emulator (e.g. "lr-fba-next")
    # 3. Full path to game (e.g., /home/pi/RetroPie/roms/arcade/wjammers.zip)

    if [ -z "$3" ]; then
    exit 0
    fi

    system=$1
    emulator=$2

    # Gets the basename of the game (e.g., "wjammers")
    game=$(basename $3)
    game=${game%.*}

    /home/pi/bin/led-end "$system" "$game" >/dev/null
  15. savetheclocktower revised this gist Sep 18, 2016. 2 changed files with 17 additions and 90 deletions.
    4 changes: 2 additions & 2 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -1,14 +1,14 @@
    # RetroPie, LED control, and you

    I wanted LEDBlinky-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.
    I wanted [LEDBlinky](http://www.ledblinky.net/ledblinky.htm)-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.

    I've got a simple control panel with six buttons per player. All I wanted was this:

    * When I launch Street Fighter 2, all twelve buttons should light up.
    * When I launch The Simpsons, only the first two buttons for each player should light up.
    * When I launch Pac-Man, none of the buttons should light up.

    Here's how I did it.
    You get the idea. Here's how I did it.

    ## Hardware

    103 changes: 15 additions & 88 deletions runcommand-onstart.sh
    Original file line number Diff line number Diff line change
    @@ -1,93 +1,20 @@
    # RetroPie, LED control, and you
    #!/usr/bin/bash

    I wanted LEDBlinky-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.
    # ARGUMENTS, IN ORDER:
    # 1. System (e.g., "arcade")
    # 2. Emulator (e.g. "lr-fba-next")
    # 3. Full path to game (e.g., /home/pi/RetroPie/roms/arcade/wjammers.zip)

    I've got a simple control panel with six buttons per player. All I wanted was this:
    if [ -z "$3" ]; then
    exit 0
    fi

    * When I launch Street Fighter 2, all twelve buttons should light up.
    * When I launch The Simpsons, only the first two buttons for each player should light up.
    * When I launch Pac-Man, none of the buttons should light up.
    system=$1
    emulator=$2

    Here's how I did it.
    ## Hardware
    I got a PACDrive from Ultimarc. It's got 16 terminals for LEDs. 1–6 control Player 1's buttons; 7–12 control Player 2's buttons. The remaining four terminals got devoted to the start buttons (13 and 14), the coin buttons (both on 15), and four small buttons I use for functions (all on 16).

    After all that's wired up, all you need is a single USB cable running to your Pi — or, even better, to a powered USB hub that is itself connected to the Pi. The LEDs get powered from USB.
    ## Software
    Most of the software for the PACDrive is meant for a Windows environment. After some digging, I found [this third-party utility for Linux](http://www.zumbrovalley.net/readpost.php?artid=33) that allows for controlling a PACDrive from the command-line. It was exactly what I needed, though it was eight years old and took some wrangling to get installed on a Pi.
    From a stock RPi with Raspbian Jessie, you'll need at least these packages:

    ```bash
    sudo apt-get install libusb-dev build-essential
    ```

    You'll also need `libhid`, which is hard to find because it's deprecated. There's no package for it in APT, the source code is behind a login for some reason, and then you've got to make [a change to the source](http://matthewcmcmillan.blogspot.com/2013/03/compiling-libhid-for-raspbian-linux-on.html) to get it to compile on the Pi. I solved all three problems by applying that fix and then putting up the fixed source on my server.

    From your home directory:

    ```bash
    mkdir src && cd src
    wget "http://andrewdupont.net/pi/libhid-0.2.16.tar.gz"
    tar xvzf libhid-0.2.16.tar.gz
    cd libhid-0.2.16
    ./configure
    make
    sudo make install
    ```

    Now we're ready to install the `pacdrive` utility.
    ```bash
    wget "http://www.zumbrovalley.net/pacdrive/dnld/pacdrive_0_2.tar.gz"
    tar xvzf pacdrive_0_2.tar.gz
    cd pacdrive_0_2
    make
    ```
    If you do `make install` it'll put the `pacdrive` binary in `/usr/bin`, though you can edit the `Makefile` beforehand to specify a different directory. I did neither; I just manually copied the `pacdrive` binary to `/home/pi/bin`. (If you do this, make sure you put `/home/pi/bin` in your `PATH`.)

    ## Hooking into RetroPie

    So now you can control the LEDs from the command line, but that doesn't help much.
    ### How will it know which LEDs to light up for which game?
    One option here would be to use something called `controls.dat`, which is a project for cataloguing the controls for most MAME games. (4-way joystick? 8-way? How many buttons? How many players? Hotseat, or did each player have their own controls? And so on.)
    The problem with this approach is that (a) the `controls.dat` project is dormant; (b) there are lots of gaps in its database.
    If you're anything like me, you don't need a comprehensive solution. I don't care how many buttons some random mah-jongg game uses because I'll never play it. I've got about 100 arcade games set up and I don't mind specifying their configs manually.
    So here's what I've got in mind:
    1. Create a `/home/pi/leds` directory.
    2. Inside that directory, create another directory for each system you want to control. (I've got `arcade` and `daphne`; you might have others.)
    3. Inside each system's directory, define a text file whose name matches the name of the game. For instance: `simpsons2p` is the ROM name for the two-player version of The Simpsons, so I'd create a file called `simpsons2p.cfg` with these contents: `1, 2, 7, 8`. In other words: light up buttons 1, 2, 7, and 8, and turn off all the other LEDs.

    This way it's simple to define new configs and simple for RetroPie to know where to look for configs knowing only the system and the ROM name.
    ### How will it light up the LEDs on game launch?
    All emulator launches go through something called `runcommand.sh`. There's a new feature in RetroPie 4.x: the ability to define scripts called `runcommand-onstart.sh` and `runcommand-onend.sh`. These scripts will run before a game starts and after a game ends, respectively, and they receive some metadata, including the name of the system and the name of the game.

    If you want to do it exactly how I did:

    1. `cd /opt/retropie/configs/all`
    2. `nano runcommand-start.sh` (shouldn't need `sudo`, but I might be wrong)
    3. Paste the contents of the `runcommand-onstart.sh` from this gist.
    4. Repeat steps 3 and 4 for `runcommand-onend.sh`.
    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_. That happens in a file called `/home/pi/.ledrc`. I've included mine in this gist for illustration. You can also read the source of `led-start` to learn more.

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

    ## Problems?

    If this doesn't work, it's probably because I left something out. Leave a comment and we'll figure it out together.
    # Gets the basename of the game (e.g., "wjammers")
    game=$(basename $3)
    game=${game%.*}

    # Turn on the relevant LEDs for this game.
    /home/pi/bin/led-start "$system" "$game" >/dev/null
  16. savetheclocktower created this gist Sep 18, 2016.
    93 changes: 93 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    # RetroPie, LED control, and you

    I wanted LEDBlinky-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.

    I've got a simple control panel with six buttons per player. All I wanted was this:

    * When I launch Street Fighter 2, all twelve buttons should light up.
    * When I launch The Simpsons, only the first two buttons for each player should light up.
    * When I launch Pac-Man, none of the buttons should light up.

    Here's how I did it.

    ## Hardware

    I got a PACDrive from Ultimarc. It's got 16 terminals for LEDs. 1–6 control Player 1's buttons; 7–12 control Player 2's buttons. The remaining four terminals got devoted to the start buttons (13 and 14), the coin buttons (both on 15), and four small buttons I use for functions (all on 16).

    After all that's wired up, all you need is a single USB cable running to your Pi — or, even better, to a powered USB hub that is itself connected to the Pi. The LEDs get powered from USB.

    ## Software

    Most of the software for the PACDrive is meant for a Windows environment. After some digging, I found [this third-party utility for Linux](http://www.zumbrovalley.net/readpost.php?artid=33) that allows for controlling a PACDrive from the command-line. It was exactly what I needed, though it was eight years old and took some wrangling to get installed on a Pi.

    From a stock RPi with Raspbian Jessie, you'll need at least these packages:

    ```bash
    sudo apt-get install libusb-dev build-essential
    ```

    You'll also need `libhid`, which is hard to find because it's deprecated. There's no package for it in APT, the source code is behind a login for some reason, and then you've got to make [a change to the source](http://matthewcmcmillan.blogspot.com/2013/03/compiling-libhid-for-raspbian-linux-on.html) to get it to compile on the Pi. I solved all three problems by applying that fix and then putting up the fixed source on my server.

    From your home directory:

    ```bash
    mkdir src && cd src

    wget "http://andrewdupont.net/pi/libhid-0.2.16.tar.gz"
    tar xvzf libhid-0.2.16.tar.gz
    cd libhid-0.2.16
    ./configure
    make
    sudo make install
    ```

    Now we're ready to install the `pacdrive` utility.

    ```bash
    wget "http://www.zumbrovalley.net/pacdrive/dnld/pacdrive_0_2.tar.gz"
    tar xvzf pacdrive_0_2.tar.gz
    cd pacdrive_0_2
    make
    ```

    If you do `make install` it'll put the `pacdrive` binary in `/usr/bin`, though you can edit the `Makefile` beforehand to specify a different directory. I did neither; I just manually copied the `pacdrive` binary to `/home/pi/bin`. (If you do this, make sure you put `/home/pi/bin` in your `PATH`.)

    ## Hooking into RetroPie

    So now you can control the LEDs from the command line, but that doesn't help much.

    ### How will it know which LEDs to light up for which game?

    One option here would be to use something called `controls.dat`, which is a project for cataloguing the controls for most MAME games. (4-way joystick? 8-way? How many buttons? How many players? Hotseat, or did each player have their own controls? And so on.)

    The problem with this approach is that (a) the `controls.dat` project is dormant; (b) there are lots of gaps in its database.

    If you're anything like me, you don't need a comprehensive solution. I don't care how many buttons some random mah-jongg game uses because I'll never play it. I've got about 100 arcade games set up and I don't mind specifying their configs manually.

    So here's what I've got in mind:

    1. Create a `/home/pi/leds` directory.
    2. Inside that directory, create another directory for each system you want to control. (I've got `arcade` and `daphne`; you might have others.)
    3. Inside each system's directory, define a text file whose name matches the name of the game. For instance: `simpsons2p` is the ROM name for the two-player version of The Simpsons, so I'd create a file called `simpsons2p.cfg` with these contents: `1, 2, 7, 8`. In other words: light up buttons 1, 2, 7, and 8, and turn off all the other LEDs.

    This way it's simple to define new configs and simple for RetroPie to know where to look for configs knowing only the system and the ROM name.

    ### How will it light up the LEDs on game launch?

    All emulator launches go through something called `runcommand.sh`. There's a new feature in RetroPie 4.x: the ability to define scripts called `runcommand-onstart.sh` and `runcommand-onend.sh`. These scripts will run before a game starts and after a game ends, respectively, and they receive some metadata, including the name of the system and the name of the game.

    If you want to do it exactly how I did:

    1. `cd /opt/retropie/configs/all`
    2. `nano runcommand-start.sh` (shouldn't need `sudo`, but I might be wrong)
    3. Paste the contents of the `runcommand-onstart.sh` from this gist.
    4. Repeat steps 3 and 4 for `runcommand-onend.sh`.

    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_.

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

    ## Problems?

    If this doesn't work, it's probably because I left something out. Leave a comment and we'll figure it out together.

    4 changes: 4 additions & 0 deletions led-end
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    #!/usr/bin/env ruby

    # For now, let's just turn all LEDs back on.
    exec(%Q[pacdrive -a])
    162 changes: 162 additions & 0 deletions led-start
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,162 @@
    #!/usr/bin/env ruby

    require 'optparse'
    require 'pathname'
    require 'pp'

    $options = {
    debug: false,
    debug_path: '/home/pi/led_log.txt'
    }

    # Takes a string like "1,2,3,7,8,9" and determines which LEDs should be lit
    # up. Shells out to the `pacdrive` binary to do it.

    $usage = OptionParser.new do |opts|
    opts.banner = "Usage: led-start [OPTIONS] system game"
    opts.separator ""

    opts.on('-d', '--debug', "Writes debug output to the specified file (defaults to ~/led_log.txt).") do |path|
    $options[:debug] = true
    $options[:debug_path] = path unless (path.nil? || path.empty?)
    end

    opts.on_tail('-h', '--help', "Displays this help message.")
    end

    $usage.parse!

    # The path to the file that sets defaults for LED state. If no config is
    # found for a certain game, this file ends up determining all states. If a
    # game config omits a certain button, the default config can specify that
    # that button should nonetheless be on. (This is useful for, e.g., coin
    # buttons, aux buttons, and such. These should almost always be on, but it's
    # dumb to have to specify them in every game config file.)
    DEFAULT_CONFIG_PATH = Pathname.new('/home/pi/.ledrc')

    # The base directory where all configs are stored. Each system should be its
    # own folder; each game should be a text file in that folder whose name
    # matches the game's name and has a "cfg" extension.
    BASE_CONFIG_PATH = Pathname.new('/home/pi/leds')

    class LEDState

    def initialize(path)
    @contents = path.is_a?(String) ? path : File.read(path).chomp
    @button_map = {}
    debug "config file contents: #{@contents}"
    buttons = @contents.split(/,\s*/)
    (1..16).each do |digit|
    state = { value: 0, force: false }
    if buttons.include?(digit.to_s)
    state = { value: 1, force: false }
    elsif buttons.include?("+#{digit}")
    # "+x" syntax means we should force this button to be on.
    state = { value: 1, force: true }
    elsif buttons.include?("-#{digit}")
    # "-x" syntax means we should force this button to be off.
    state = { value: 0, force: true }
    end
    debug "State for button #{digit}:"
    debug state
    @button_map[digit] = state
    end
    self
    end

    def with_default(path)
    debug "Combining with default config"
    default = LEDState.new(path)
    merge!(default)
    self
    end

    def map
    @button_map
    end

    # Converts the state to the format expected by the `pacdrive` utility.
    def to_s
    str = (1..16).map do |digit|
    state = '0'
    data = @button_map[digit]
    if data && data.has_key?(:value)
    state = data[:value] == 1 ? '1' : '0'
    end
    end

    str.join('').reverse
    end

    # Calls the external `pacdrive` app to set the LEDs to the represented
    # state.
    #
    # Uses `exec`, so once this method is called, we implicitly exit.
    def apply!
    cmd = %Q[pacdrive -b #{to_s}]
    exec("pacdrive", "-b", to_s)
    end

    protected

    # For combining this state with another. The argument to this function is
    # assumed to be a default, which means it will take precedence over this
    # state when conflicts happen.
    def merge!(default)
    output = {}
    default_map = default.map
    @button_map.keys.each do |digit|
    own = @button_map[digit]
    dflt = default_map[digit]

    result = nil

    if !dflt[:force]
    # Our version always wins.
    result = own[:value]
    else
    # Default wants to force a value...
    if !own[:force]
    # ...and we don't, so use the default.
    result = dflt[:value]
    else
    # ...and so do we, so we win.
    result = own[:value]
    end
    end
    output[digit] = { value: result }
    end # each

    # Replace the button map with the merged version.
    @button_map = output
    end # merge
    end # LEDState

    def debug(str)
    return unless $options[:debug]
    `echo "#{str}" >> #{$options[:debug_path]}`
    end

    if ARGV.size == 1
    # We've been given a string like "1,2,3,7,8,9" (probably for debugging).
    # Ignore all configs and just create and apply an LED state.
    debug "Given input: #{ARGV[0]}"
    LEDState.new(ARGV[0]).apply!
    elsif ARGV.size == 2
    # We've been given a system and a ROM name. Look up its config file and
    # turn it into a binary string.
    system, game = ARGV
    debug "Setting LEDs for game #{game} on system #{system}..."
    game_config_path = BASE_CONFIG_PATH.join(system, "#{game}.cfg")

    if game_config_path.exist?
    state = LEDState.new(game_config_path).with_default(DEFAULT_CONFIG_PATH)
    else
    # No specific LEDs for this game, so just apply the default.
    state = LEDState.new(DEFAULT_CONFIG_PATH)
    end

    state.apply!
    else
    puts $usage
    end
    93 changes: 93 additions & 0 deletions runcommand-onstart.sh
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,93 @@
    # RetroPie, LED control, and you

    I wanted LEDBlinky-style functionality out of my RetroPie cabinet. But I didn't need RGB control or magical frontend integration or anything like that. I had buttons with simple single-color LEDs.
    I've got a simple control panel with six buttons per player. All I wanted was this:

    * When I launch Street Fighter 2, all twelve buttons should light up.
    * When I launch The Simpsons, only the first two buttons for each player should light up.
    * When I launch Pac-Man, none of the buttons should light up.

    Here's how I did it.
    ## Hardware
    I got a PACDrive from Ultimarc. It's got 16 terminals for LEDs. 1–6 control Player 1's buttons; 7–12 control Player 2's buttons. The remaining four terminals got devoted to the start buttons (13 and 14), the coin buttons (both on 15), and four small buttons I use for functions (all on 16).

    After all that's wired up, all you need is a single USB cable running to your Pi — or, even better, to a powered USB hub that is itself connected to the Pi. The LEDs get powered from USB.
    ## Software
    Most of the software for the PACDrive is meant for a Windows environment. After some digging, I found [this third-party utility for Linux](http://www.zumbrovalley.net/readpost.php?artid=33) that allows for controlling a PACDrive from the command-line. It was exactly what I needed, though it was eight years old and took some wrangling to get installed on a Pi.
    From a stock RPi with Raspbian Jessie, you'll need at least these packages:

    ```bash
    sudo apt-get install libusb-dev build-essential
    ```

    You'll also need `libhid`, which is hard to find because it's deprecated. There's no package for it in APT, the source code is behind a login for some reason, and then you've got to make [a change to the source](http://matthewcmcmillan.blogspot.com/2013/03/compiling-libhid-for-raspbian-linux-on.html) to get it to compile on the Pi. I solved all three problems by applying that fix and then putting up the fixed source on my server.

    From your home directory:

    ```bash
    mkdir src && cd src
    wget "http://andrewdupont.net/pi/libhid-0.2.16.tar.gz"
    tar xvzf libhid-0.2.16.tar.gz
    cd libhid-0.2.16
    ./configure
    make
    sudo make install
    ```

    Now we're ready to install the `pacdrive` utility.
    ```bash
    wget "http://www.zumbrovalley.net/pacdrive/dnld/pacdrive_0_2.tar.gz"
    tar xvzf pacdrive_0_2.tar.gz
    cd pacdrive_0_2
    make
    ```
    If you do `make install` it'll put the `pacdrive` binary in `/usr/bin`, though you can edit the `Makefile` beforehand to specify a different directory. I did neither; I just manually copied the `pacdrive` binary to `/home/pi/bin`. (If you do this, make sure you put `/home/pi/bin` in your `PATH`.)

    ## Hooking into RetroPie

    So now you can control the LEDs from the command line, but that doesn't help much.
    ### How will it know which LEDs to light up for which game?
    One option here would be to use something called `controls.dat`, which is a project for cataloguing the controls for most MAME games. (4-way joystick? 8-way? How many buttons? How many players? Hotseat, or did each player have their own controls? And so on.)
    The problem with this approach is that (a) the `controls.dat` project is dormant; (b) there are lots of gaps in its database.
    If you're anything like me, you don't need a comprehensive solution. I don't care how many buttons some random mah-jongg game uses because I'll never play it. I've got about 100 arcade games set up and I don't mind specifying their configs manually.
    So here's what I've got in mind:
    1. Create a `/home/pi/leds` directory.
    2. Inside that directory, create another directory for each system you want to control. (I've got `arcade` and `daphne`; you might have others.)
    3. Inside each system's directory, define a text file whose name matches the name of the game. For instance: `simpsons2p` is the ROM name for the two-player version of The Simpsons, so I'd create a file called `simpsons2p.cfg` with these contents: `1, 2, 7, 8`. In other words: light up buttons 1, 2, 7, and 8, and turn off all the other LEDs.

    This way it's simple to define new configs and simple for RetroPie to know where to look for configs knowing only the system and the ROM name.
    ### How will it light up the LEDs on game launch?
    All emulator launches go through something called `runcommand.sh`. There's a new feature in RetroPie 4.x: the ability to define scripts called `runcommand-onstart.sh` and `runcommand-onend.sh`. These scripts will run before a game starts and after a game ends, respectively, and they receive some metadata, including the name of the system and the name of the game.

    If you want to do it exactly how I did:

    1. `cd /opt/retropie/configs/all`
    2. `nano runcommand-start.sh` (shouldn't need `sudo`, but I might be wrong)
    3. Paste the contents of the `runcommand-onstart.sh` from this gist.
    4. Repeat steps 3 and 4 for `runcommand-onend.sh`.
    These scripts reference two other scripts called `led-start` and `led-end`, which you should put in `/home/pi/bin`. This is completely overengineered, but it lets me define some buttons as "always on" without having to include them in _every single config file_. That happens in a file called `/home/pi/.ledrc`. I've included mine in this gist for illustration. You can also read the source of `led-start` to learn more.

    If you want to use these scripts, you'll need Ruby, which isn't installed by default on Raspbian Jessie. `sudo apt-get install ruby2.1` should take care of it.

    ## Problems?

    If this doesn't work, it's probably because I left something out. Leave a comment and we'll figure it out together.