A Recipe for mRuby Raspberry Pi? Just Add h2o!

Share this article

A Recipe for mRuby Raspberry Pi? Just Add h2o!

IoTWeek_Gray

It’s IoT Week at SitePoint! All week we’re publishing articles focused on the intersection of the internet and the physical world, so keep checking the IoT tag for the latest updates.

When the excellent folks at SitePoint told me about IoT Week and said I needed to generate a couple of Ruby-related IoT posts, I took it as a sign. A couple of months ago, I bought a Raspberry Pi 3 and it’s been collecting digital dust ever since. It sits there on a table next to my nerd chair, mocking me.

“Hey Glenn, remember when you bought me? I am still here…can you at least use me as a coaster or something?”

Every day I’ve had to deal with my nerd shame, facing that unused beacon of my own inaction.

Well, no more! IoT Week has offered me a hand up, a way to vanquish my shame and finally use my Raspberry Pi. No longer will I have to consider turning in my Nerd Card and seeking employment as a mega-store greeter.

Truth be told, one of my main motivators behind purchasing the Raspberry Pi was to do “stuff” with my kids. A couple of my children (I have so many) have shown a penchant for programming and other geekery, so I figured the RPi was a great platform to water those seeds. However, when it comes to the Raspberry Pi, I am merely an amateur gardener. As such, I am using IoT Week and this post as a chance to get more familiar with the Raspberry Pi.

On that note, a disclaimer may be in order. I am new to the RPi and embedded systems in general. Mucking about on these platforms often involve compiling C libraries and other things about which I have long forgotten everything I once knew. So, this post and the things in it are done in a “just get it working” manner. And, for what it’s worth, I had some struggles.

OK, that’s enough about me and my shortcomings. Let’s get nerdy.

Goals

One of my longer term goals for the RPi is to make one of those visual doorbells that you can buy for $129. These devices allow you to use an app to see who is at your door, including the ability to talk to them, etc. Now, before you get excited and think this post is going to show you how to make one of these, don’t. I will only cover a couple of aspects of what I think will make up the final product.

The first thing I’ll tackle is the ability to transmit pictures from the RPi and see them in a browser. The doorbell will need to do that, obviously, so it’s a good place to start.

Goal #2 is to serve up a simple API from the RPi. My doorbell will need to capture and serve data, and do other API/microservice/shiny toy things, so this will prove foundational to my long-term effort.

Equipment

Briefly, let’s go through the equipment:

equipment

To start my RPi crusades, I purchased the following:

  • Raspberry Pi 3
  • 5V Power Supply
  • Raspberry Pi Camera Module
  • Saucy Red Raspberry Pi Case
  • 64 GB microSD Card. I think this needs to be a class 10 or higher, so be sure to get the right stuff. You can buy microSD cards already installed with NOOBS, which I’ll talk about in the next section.

If you need to purchase these items, I’ve had great luck with MCM Electronics and Pololu.com.

Then, from my ever growing nerd stash, I supplied:

  • A USB Keyboard
  • A USB Mouse
  • A HDMI-enabled Monitor

Software

Seeing as how this is a SitePoint Ruby channel post, I will attempt to use some Ruby tools to accomplish my goals. I started research for this post by looking for Ruby and embedded systems and the like. This almost immediately led me to mRuby, which I had seen in a few posts in my tenure as editor. Truth be told, I wasn’t really sure what mRuby is or how to use it. In fact, at one point I thought it was just a stripped down Ruby interpreter with a small footprint. It took writing this post to teach me that it’s embedded in other programs, etc. Meaning, mRuby is built into another program to provide Ruby handling to that program. The stuff we do today in this post should drive that point home.

Operating System

For the operating system, I chose Raspbian, as it is the “default” RPi OS. I mentioned NOOBS above, which you can acquire preinstalled on a microSD card. NOOBS comes with several OS options and you choose one with you boot up the RPi. If you have gone that route, choose Raspbian and you’re done.

Another option is to install NOOBS (or other OS) on a blank microSD card, which I tried to do. For four hours. I could not get the RPI to boot off of that card, so I punted and installed Raspbian directly on the card with these instructions. That worked great, and I could boot up the RPi:

desktop

Raspbian ships with Ruby 2.1.5, which is good enough for our needs today. Also, most RPi instructions recommend you run raspi-config and expand the file system to use your entire card. I also used raspi-config to select the right locale and keyboard. If you are seeing odd behavior when you type, like typing $ shows £ (or vice-versa, depending on what you want), then you probably need to change your locale and keyboard.

As I mentioned, I want to create an API and capture pics from the camera, exposing both to the browser. This requires a web server of some sort. Combining my web server need with my desire to use mRuby led me to the perfect piece of software: h2o web server. h2o claims to be a faster web server that uses less CPU and takes full advantage of all the HTTP/2 fun. Also, and I am not ashamed to admit that I squealed a little bit when I read this: h2o uses mRuby, providing the ability to write handlers with Ruby! It’s like the authors knew that I would eventually write this post! Yay!

h2o Setup

Thankfully, others have gone through the steps to setup h2o on the Raspberry Pi. I referred to two such posts in my research:

Both of those articles are quick and informative reads, so I’d recommend you go through at least one of them before we move forward. I’ll quickly run through the setup steps. You need to make sure all the right packages are installed on the RPi:

$ sudo apt-get update
$ sudo apt-get install -y build-essential cmake bison git

That’ll install the requested items. We should be good to install h2o now:

$ git clone https://github.com/h2o/h2o.git
$ cd h2o
$ cmake -DCMAKE_C_FLAGS_RELEASE= \
  -DCMAKE_CXX_FLAGS_RELEASE= \
  -DCMAKE_INSTALL_PREFIX=./dist \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_FIND_FRAMEWORK=LAST \
  -DCMAKE_VERBOSE_MAKEFILE=ON \
  -Wno-dev
$ make
$ make install

The make step takes a while, just so you know.

Configuring h2o is a simple matter of creating or YAML and firing up the h2o binary. Here is an example of the h2o configuration file, called h2o.conf and placed in the h2o directory:

listen: 8080
hosts:
  "localhost:8080":
    paths:
      /api:
        file.dir: .
        mruby.handler-file: json_api.rb

This file will tell h2o to listen on port 8080 and expose a /api endpoint that is backed my a Ruby handler in a filed named json_api.rb. A Ruby handler, in this case, is just a Rack application, as shown in this file stolen from Bounga’s post:

class JsonApi
  def initialize
    @storage = {foo: "bar"}
  end

  def call(env)
    body = JSON.generate({foo: @storage[:foo]})

    [
      200,
        {'Content-Type'   => 'application/json',
         'Content-Length' => body.size},
       [body]
     ]
  end
end

JsonApi.new

With the configuration file and handler in place, starting the h2o server is as easy as:

dist/bin/h2o --conf h2o.conf

and visiting the endpoint, which, in this case, is http://localhost:8080/api

curl http://localhost:8080/api/
{"foo":"bar"}

Congratulations! You are now an official user of mRuby.

Goal #1: Capture Pictures

In order to capture pictures, I am going to use an OS command I found when I was testing the installation of the Camera Module called raspistill. Incidentally, you should install your camera module on the RPi. Now, to capture an image from the camera, type the following:

raspistill -o image.png

You should see a window pop up on the screen and eventually go away (this is the “preview” window), indicating the image is captured. Open the file and you’ll see something like:

capture

Note: Your picture will, no doubt, be of a more attractive subject.

So, we have a system command we can use to capture an image from the camera. Now, we simply need to invoke it from a Ruby handler:

class PicApi
  def call(env)
    file = capture

    [
      200,
        {'Content-Type'   => 'image/jpeg',
         'Content-Length' => File.size(file)},
       file
     ]
  end

  private

  def capture
    fn = Time.now.to_i.to_s
    `raspistill -n -o /tmp/#{fn}.jpg`
    File.open("/tmp/#{fn}.jpg")
  end
end

PicApi.new

This is very straightforward. I capture the file using raspistill (if you didn’t know, backticks fire off a system command in Ruby), just like we did above. The difference is the location and a parameter. I write the file (name based on the current time) to the /tmp directory so it’ll eventually get clobbered, and I use the -n parameter to skip the preview screen. Once we have the file, opening it with File.open returns a File object, which responds to each per the Rack specification. I was pretty psyched when I found how easy this was. All of this really helped me place mRuby in its rightful spot in my brain and made me feel like a pretty solid nerd.

With the handler ready, it needs to be added to the h2o configuration file (h2o.conf):

listen: 8080
hosts:
  "localhost:8080":
    paths:
      /:
        file.dir: .
      /api:
        file.dir: .
        mruby.handler-file: json_api.rb
      /pic:
        file.dir: .
        mruby.handler-file: pic_api.rb
    access-log: /dev/null

Now, from within the h2o directory:

dist/bin/h2o --conf h2o.conf

If you open up http://localhost:8080/pic, a captured image is your reward!

Goal #2: An API

The two h2o posts I reference above both have examples of how to create a basic API handler for h2o. As such, I need to do something different to add to the knowledge instead of simply recycle it. I am going to walk through adding PostgreSQL support to the embedded mRuby installation and demonstrate how to connect to PostgreSQL. In my opinion, the real benefit here is another example of adding a capability to mRuby and h2o, along with mentioning where I stumbled along the way.

First, the easy part: Because Ruby is such an incredible community, someone has already created mRuby bindings for PostgreSQL. We simply need to add it to mRuby. Luca’s post does the same thing for Hiredis, and the steps are:

Clone the Repo

Clone the relevant git repository into the deps/ folder in the h2o directory. In this case, I cloned this one.

Compile h2o

Compile h2o using the same cmake command from above. However, I got stuck here for hours. This is where my C noobage nearly killed all my mojo. When you build h2o, one of the artifacts created is the mruby/ directory with a compiled version of mRuby. On subsequent builds, if this directory exists, mRuby is not rebuilt, so any new deps won’t be compiled into the binary. I did not realize that, much to my shame. So, if you have an mRuby/ directory in your h2o directory (Note: I am NOT talking about deps/mruby), delete it, then run:

cmake -DCMAKE_C_FLAGS_RELEASE= \
    -DCMAKE_CXX_FLAGS_RELEASE= \
    -DCMAKE_INSTALL_PREFIX=./dist \
    -DCMAKE_BUILD_TYPE=Release \
    -DCMAKE_FIND_FRAMEWORK=LAST \
    -DCMAKE_VERBOSE_MAKEFILE=ON \
    -Wno-dev

Create the Ruby Handler

Create your new handler. I created a file called pg_api.rb that looks like:

class PgApi
  def initialize
    @conn = PG::Connection.new(host: '192.168.1.52',
              port: 5432,
              dbname: 'mydb', 
              user:'myuser', password:'mypassword')
  end

  def call(env)
    body = get_data
    [
      200,
        {'Content-Type'   => 'application/json',
         'Content-Length' => body.size },
      body 
    ]
  end

  private

  def get_data
    json = []
    @conn.exec("select * from accounts") do |result|
      json << {
       email: result["email"],
       created_at: result["created_at"]
      }
    end
    json
  end
end

PgApi.new

Using the mruby-pg class PG::Connection, I created a connection to a PostgreSQL instance running elsewhere on my network. If you don’t have access to such a thing, I recommend using Docker to put it on a box on your network. It’s very easy.

Configure h2o to Use the Handler

Add the new handler to the h2o configuration:

listen: 8080
hosts:
  "localhost:8080":
    paths:
      /:
        file.dir: .
      /api:
        file.dir: .
        mruby.handler-file: json_api.rb
      /pic:
        file.dir: .
        mruby.handler-file: pic_api.rb
      /pg:
        file.dir: .
        mruby.handler-file: pg_api.rb
    access-log: /dev/null

This is old hat now. Just add a /pg endpoint that points to our Ruby handler and restart h2o.

dist/bin/h2o --conf h2o.conf

If you visit http://localhost:8080/pg/, you will see the result of your SQL select statement in JSON form. mmmmmmmRuby…that’s good RPi.

As you can see, I could create any JSON API I desire, backed by PostgreSQL. For practice, you should try this with another database, like MongoDB or MySQL.

Conclusion

Well, I reached my two goals. I can capture images from the camera and expose them to the browser, and I can publish an API backed by a PostgreSQL database . Maybe not the most ambitious goals in the world, but they are foundational for me and my ultimate aims. One of my next steps is capturing live video…I hear uv4l is good for that…

I hope you got something out of this post and are now inspired to hack around with the Raspberry Pi and mRuby. If you want my advice, just add h2o…

Glenn GoodrichGlenn Goodrich
View Author

Glenn works for Skookum Digital Works by day and manages the SitePoint Ruby channel at night. He likes to pretend he has a secret identity, but can't come up with a good superhero name. He's settling for "Roob", for now.

GlennGiotmrubyRaspberry Pi
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week