Tenderlove Making

Evented GPIO on Raspberry PI with Ruby

I need to know when my cats are pooping so over the weekend I hooked up a motion sensor to my Raspberry PI. This is the code I used to get an interrupt when the motion sensor turns on or off:

require 'epoll'

def watch pin, on:
  # Export the pin we want to watch
  File.binwrite "/sys/class/gpio/export", pin.to_s

  # It takes time for the pin support files to appear, so retry a few times
  retries = 0
  begin
    # `on` should be "none", "rising", "falling", or "both"
    File.binwrite "/sys/class/gpio/gpio#{pin}/edge", on
  rescue
    raise if retries > 3
    sleep 0.1
    retries += 1
    retry
  end

  # Read the initial pin value and yield it to the block
  fd = File.open "/sys/class/gpio/gpio#{pin}/value", 'r'
  yield fd.read.chomp

  epoll = Epoll.create
  epoll.add fd, Epoll::PRI

  loop do
    fd.seek 0, IO::SEEK_SET
    epoll.wait # put the program to sleep until the status changes
    yield fd.read.chomp
  end
ensure
  # Unexport the pin when we're done
  File.binwrite "/sys/class/gpio/unexport", pin.to_s
end

pin = 5

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

Whenever an event happens on the GPIO pin, the block will be executed. I want the block to be executed when the sensor detects movement and when it detects no movement (if you imagine that as a wave, I want to know about the rising and falling edges), so I passed “both” to the watch function.

I am very new to developing on Raspberry PI, and I’m not sure what people normally use for Ruby + GPIO on Raspberry PI. I looked at the rpi_gpio gem. It gives access to read values of GPIO, but doesn’t give you any events. In other words, you can use it to read the current value of a pin, but it won’t let you know when the value of a pin has changed. It looks like there is code to support this, but it’s not fully hooked up yet. I noticed that the C code is just using Epoll, so I tried using the epoll gem, and it works.

The rpi_gpio gem is cool because it allows your program to read from a pin without echoing to “exports” and reading from a file. The gem just mmaps a special device, and then reads from memory. Unfortunately, it doesn’t seem like there is a way to generate “on change” events with that system. That means we have to write to the “export” file and run poll on the “value” file. As you can see from the example above, waiting for events (the thing we want to do) accounts for only a few lines of code where managing export / value files accounts for most of the function.

I am new to Raspberry PI development, so maybe there is an easier way, but I haven’t found it. At least this works so I can know when my cats are pooping.

The End.

« go back