Skip to content

Instantly share code, notes, and snippets.

@savetheclocktower
Created June 24, 2017 22:24
Show Gist options
  • Save savetheclocktower/8c8b3b3a9aabf5dd7f8b5a78156989c9 to your computer and use it in GitHub Desktop.
Save savetheclocktower/8c8b3b3a9aabf5dd7f8b5a78156989c9 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# For now, let's just turn all LEDs back on.
exec(%Q[/home/pi/bin/pacdrive -a -q])
#!/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
binary = str.join('').reverse
"0x%04x" % binary.to_i(2)
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!
exec("/home/pi/bin/pacdrive", "-q", "-s", 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
#!/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
#!/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%.*}
# Turn on the relevant LEDs for this game.
/home/pi/bin/led-start "$system" "$game" >/dev/null
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment