The Green Shed

GPIO Button Handling on RaspberryPi in Ruby

I’ve working on adding some physical controls to my kitchen status display. The setup is very simple, and for a first-run all I wanted to be able to do was detect a button press, and run a script somewhere on the system.

I couldn’t find any existing code that does this (please point some out if you know of any), though there’s plenty of code out there for button handling in general.

Also, most of the code out there for doing GPIO work is either in C or Python, and I was hoping for a Ruby solution, or at least a solution that would be easy to wrap in Ruby.

In the end I learned about how to interface with the GPIO system on RPi via the filesystem.

From there, I found this code from Aaron Patterson using epoll to monitor for changes in the level on the monitored pin. That seemed like it would work, but the epoll gem seems to be unmaintained at this point. Instead, I went with the Sleepy Penguin gem, which provides an interface to the same underlying Linux filesystem events.

Lastly, I wanted my pin pulled high by default, and there’s no way to do that via Sysfs, so I ended up using the gpio utility to set the pin state and pull up resistor.

From there, I was able to run this little script, which will output the current state of the pin when it changes.

# install gpio command line tool
# gpio -g mode 16 up
require 'sleepy_penguin'

def watch pin, on:

  puts "Setup Export"
  File.binwrite "/sys/class/gpio/export", pin.to_s


  puts "Setup Edge"

  retries = 0
  begin

    File.binwrite "/sys/class/gpio/gpio#{pin}/edge", on
  rescue
    raise if retries > 3
    sleep 0.1
    retries += 1
    retry
  end

  puts "Read Value"

  fd = File.open "/sys/class/gpio/gpio#{pin}/value", 'r'
  yield fd.read.chomp

  puts "Setup epoll"

  epoll = SleepyPenguin::Epoll.new
  epoll.add fd, SleepyPenguin::Epoll::PRI

  puts "Start loop wait"
  loop do
    fd.seek 0, IO::SEEK_SET
    epoll.wait do
      # yield fd.read.chomp
      value = fd.read.chomp
      p value
    end
  end
ensure

  File.binwrite "/sys/class/gpio/unexport", pin.to_s
end

pin = 16

watch pin, on: 'both' do |value|
  p value
end

Still to come: debouncing, better handling of a change event, and hopefully wrapping it all up into something much easier to work with.