collage of Pragpub magazine covers
Take a step back in history with the archives of PragPub magazine. The Pragmatic Programmers hope you’ll find that learning about the past can help you make better decisions for the future.

FROM THE ARCHIVES OF PRAGPUB MAGAZINE APRIL 2011

Testing Arduino Code: An Everyday JRuby Adventure

By Erin Dees

The Pragmatic Programmers
11 min readAug 15, 2022

--

Erin brings the testing power of the Ruby-based Cucumber testing library to the Arduino.

https://pragprog.com/newsletter/
https://pragprog.com/newsletter/

This article is the latest in my series on everyday JRuby. But it’s also an exploration in bringing serious software development tools to the Arduino.

People are using JRuby for amazing things worldwide. They’re also using it at smaller scales to make their day-to-day work a little easier. This series is a journey through everyday situations where JRuby comes in handy.

In the previous installment, we implemented the Six Degrees of Kevin Bacon game using a graph database. We then packaged it up for easy deployment to machines that don’t have Ruby installed.

Today, we’ll use the Ruby-based Cucumber testing library to do a quick smoke test of an Arduino-based project. The technique will work in several different Ruby implementations; using JRuby here will give us an excuse to talk about calling into a Java library from Ruby.

The Hardware

The hardware we’ll be testing is the Mood Hat, a little demo I threw together last summer as an excuse to play with microcontrollers. It features the Lilypad a washable Arduino-based circuit board that can be sewn to clothing.

The Mood Hat is a simple toy. It’s a baseball cap with five LEDs representing five different moods from furious to ecstatic. At any given time, one LED is lit. You press one of two buttons to advance to the next happier or angrier state.

You can use nearly any cheap consumer LEDs and pushbuttons for this; I bought a handful of tiny ones from Sparkfun that are designed to go with the Lilypad.

Construction is simple. First, we’ll wire up the five LEDs to pins 6 through 10 on the Lilypad. For each pin, connect a 1K resistor to the pin, then connect the positive terminal of an LED to the other side of the resistor. Wire the negative terminal of the LED to the ground pin (marked as on the board).

Now, we’ll hook up the pushbuttons to pins 2 and 3. Connect each button between its corresponding input pin and ground. When the wearer presses a button, the voltage read at the pin will drop to near zero. But you’ll need something to keep the voltage high when the button isn’t being pressed. Connect a small resistor, something on the order of 2.2K, between each output pin and the supply pin (marked as + on the board).

That’s it for the hardware — on to the tests!

The Tests

We’re going to write a series of tests using the Cucumber test framework. Each test will drive the hardware by sending commands to it over the board’s built-in serial interface.

The Protocol

We’ll define a simple set of single-character commands we can send from our test script to the Mood Hat over the serial port:

Those are all the commands we need to drive the hardware during testing. We’ll need to implement them in both the tests and in the firmware. Let’s start with the tests.

The Feature

Here’s what one piece of a Cucumber test might look like:

Given I am happy
When I increase my happiness
Then I should be ecstatic

This wording is going to get really old if we have to repeat it for every combination of starting state and button push. Let’s take advantage of Cucumber’s ability to define several steps in a single ASCII art table. Here’s the full Cucumber test, which should go in features/mood_hat.feature in your project directory:

Feature: Mood hat
In order to get my feelings across
As a passionate person
I want to display my mood on my hat
Scenario Outline: Changing mood
Given I am <feeling_now>
When I <change> my happiness
Then I should be <feeling_next>
Examples:
| feeling_now | change | feeling_next |
| furious | increase | unhappy |
| furious | decrease | furious |
| unhappy | increase | neutral |
| unhappy | decrease | furious |
| neutral | increase | happy |
| neutral | decrease | unhappy |
| happy | increase | ecstatic |
| happy | decrease | neutral |
| ecstatic | increase | ecstatic |
| ecstatic | decrease | happy |

Go ahead and install Cucumber, then run what you’ve got so far:

$ jruby -S gem install cucumber rspec-expectations
$ jruby -S cucumber features

You should see a copy of the test, plus a list of missing step definitions:


10 scenarios (10 undefined
30 steps (30 undefined)
0m1.360s
You can implement step definitions for undefined steps with these snippets:
Given /^I am furious$/ do
pending # express the regexp above with the code you wish you had
end
When /^I increase my happiness$/ do
pending # express the regexp above with the code you wish you had
end
Then /^I should be unhappy$/ do
pending # express the regexp above with the code you wish you had
end

For each line in the feature description, Cucumber expects to find a chunk of code implementing that particular test step. Since we haven’t defined any steps yet, Cucumber has supplied a template for us. Let’s create some step definitions based on the template.

Step Definitions

Cut and paste the first three empty step definitions into a new file called features/step_definitions/mood_hat_steps.rb. Adjust them to contain the following code:

Given /^I am (.+)$/ do |mood| 
@port.putc int_for(mood).to_s
end
When /^I ([^ ]+) my happiness$/ do |change|
char = (change == ‘increase’) ? ‘+’ : ‘-’
@port.putc char
end
Then /^I should be (.+)$/ do |mood|
@port.putc ‘?’
@port.getc.to_i.should == int_for(mood)
end

Since each test step matches one of these three regular expressions, these definitions are all we need. The @port variable is an instance of the SerialPort class (more on that in a moment). We need to create this variable before the first test runs; features/support/env.rb is the place to put this kind of initialization code:

$: << File.dirname( FILE ) + ‘/../../lib’
require ‘serialport’
port = SerialPort.new ENV[‘MOOD_HAT’], 9600, 8, 1, SerialPort::NONE
port.read_timeout = 1000
port.putc(‘?’) until (port.getc =~ /\d/ rescue nil)
at_exit { port.close }

That code opens the serial port and keeps trying to contact the Mood Hat until it receives a reply. How to we make the connection visible to our test code? Cucumber provides the World method for this purpose. The following code (also in env.rb) will define a MoodWorld class to make the port available to Cucumber:

class MoodWorld
def initialize(port)
@port = port
end
# … more methods here …
end
World { MoodWorld.new(port) }

The MoodWorld class is also a good place for helper methods, like the int_for method that converts the names of emotions into numbers we can send over the port:

Emotions = %w(furious unhappy neutral happy ecstatic)
def int_for(emotion)
Emotions.index emotion
end

Now, on to the SerialPort class.

Supporting Code

In regular Ruby, we’d install a gem called serialport to provide the SerialPort class. That gem has C code in it, though. While some C-based gems are compatible with JRuby, serialport isn’t one of them.

The good news is that we only need a tiny subset of serialport’s functionality. We can easily write a JRuby-specific file that provides this subset. If you’re following along with regular Ruby, you can just do a gem install serialport and skip to the next section.

Java in Your Serial

To access the serial port from the JVM, we’re going to use the JavaComm API. Although this is an official Java API, it doesn’t actually ship with the JVM. Fortunately, there’s an open source implementation called RXTX.

To use RXTX in this project, download the latest prerelease version from the download page. Extract the zip file. You’ll need two files from this archive: a platform-specific library (for example, rxtxSerial.dll on Windows) and the cross-platform file RXTXComm.jar. Put the platform-specific file in your project directory. Create a lib subdirectory and put the jar file into it.

Serial With Class

Now we need to define the SerialPort class. Create a new file called lib/serialport.rb, and place the following code at the top of it:

require ‘java’
require ‘RXTXcomm.jar’
java_import(‘gnu.io.CommPortIdentifier’) java_import(‘gnu.io.SerialPort’) { ‘JSerialPort’ }

This brings the RXTX classes we’re using into the Ruby namespace, so we can use them just like any Ruby class. We don’t want a name collision between the SerialPort Java class defined in RXTX and the one we’ll be writing in Ruby. By providing the name JSerialPort in the block passed to java_import, we prevent a clash.

Now, sketch the outline of the new class:

class SerialPort 
attr_accessor :read_timeout
NONE = JSerialPort::PARITY_NONE
# … methods go here …
end

First, we’ve defined the read_timeout= method, one of the three SerialPort methods our test script uses. Next, we’ve added a constant that needs to be visible from outside the class. Now, let’s add a couple of methods to open and close the serial port:

def initialize name, baud, data, stop, parity
port_id = CommPortIdentifier.get_port_identifier name
data = JSerialPort.const_get “DATABITS_#{data}”
stop = JSerialPort.const_get “STOPBITS_#{stop}”
@port = port_id.open ‘JRuby’, 500
@port.set_serial_port_params baud, data, stop, parity
@in = @port.input_stream
@out = @port.output_stream
end
def
close
@port.close
end

There’s not much going on here. We just do a little translation from the Ruby API to the Java one, then set up the serial port. Now, add the putc method, which sends a single character:

def putc(char)
@out.write char[0, 1].to_java_bytes
end

This method is simple; we just take the first character of the string passed to us, convert it to a Java byte array, and tell the underlying output stream to send it. getc is the other half of the equation:

def getc
if @read_timeout
deadline = Time.now + @read_timeout / 1000.0
sleep 0.1 until @in.available > 0 || Time.now > deadline
end
@in.to_io.read(@in.available)[-1, 1] || ‘’
end

If this port has a read timeout defined, we want to wait for that amount of time until data becomes available. Otherwise, we proceed straight to the read (which will block indefinitely).

To do the actual read, we could allocate a fixed-size Java byte array, pass it to the underlying input stream, then copy it into a Ruby string. That’d be a lot of boilerplate, though. Instead, we use a method provided by JRuby called to_io. This presents the Java stream as a Ruby IO object, which just uses plain Ruby strings. We read all the bytes available, then return the last one.

That’s all the serial support we need to get this test code running. We can finally turn our attention to the firmware.

The Firmware

You can write the Mood Hat’s firmware using the official Arduino IDE, or you can use the techniques Maik Schmidt describes in this issue of PragPub. Before you start, you’ll need to install the open source Bounce library for reading button states accurately.

First, create a few definitions you’ll use elsewhere in the code:

#include <Bounce.h>
#define NUM_MOODS 5
#define NEUTRAL ((NUM_MOODS — 1) / 2)
#define UP ‘+’
#define DOWN ‘-’
#define QUERY ‘?’
#define NONE -1
int ledPins[NUM_MOODS] = {6, 7, 8, 9, 10};
int heartbeatPin = 13;
int upPin = 2;
int downPin = 3;
Bounce upButton (upPin, 100);
Bounce downButton(downPin, 100);
int mood = NEUTRAL;

When the firmware loads, we need to open up serial communications and designate our input and output pins. This code goes into a setup function, which will get called automatically after the hardware is done powering up:

void setup() {
for (int i = 0; i < NUM_MOODS; ++i) {
pinMode(ledPins[i], OUTPUT);
}
pinMode(upPin, INPUT);
pinMode(downPin, INPUT);
Serial.begin(9600);
setMood(NEUTRAL);
}

Typical Arduino practice is to write a short, fast loop function. The development kit will generate code that calls our loop repeatedly.

This project doesn’t need to do much work on each pass. We’ll just check the physical buttons and serial port for incoming commands, and then change the mood accordingly:

void loop() {
upButton.update();
downButton.update();
int button = (upButton.fallingEdge() ? UP :
(downButton.fallingEdge() ? DOWN : NONE));
int serial = (Serial.available() > 0 ? Serial.read() : NONE);
int event = (button != NONE ? button : serial);
switch (event) {
case UP:
setMood(mood + 1);
break;
case DOWN:
setMood(mood — 1);
break;
case QUERY:
Serial.print(‘0’ + mood, BYTE);
break;
default:
setMood(event — ‘0’);
break;
}
digitalWrite(heartbeatPin, HIGH);
delay(50);
digitalWrite(heartbeatPin, LOW);
delay(50);
}

Let’s walk through this step by step. First, we check both of our input pins for a signal falling to near zero — such a condition would indicate a button press. We also check the serial port for incoming commands. If we see both buttons and serial characters, buttons take precedence.

Next, we change the current state based on what the incoming request was. Finally, we flash the on-board LED to give an indication that the firmware is still running.

The implementation of setMood just needs to do a little range checking and activate the appropriate LED:

void setMood(int newMood) 
if (newMood < 0 || newMood >= NUM_MOODS) {
return;
}
digitalWrite(ledPins[mood], LOW);
mood = newMood;
digitalWrite(ledPins[mood], HIGH);
}

With this code loaded on the device, try rerunning your tests. You’ll need to set the MOOD_HAT environment variable to your Lilypad’s serial port, which might be COM3 on Windows or /dev/tty-usbserial-… on a Mac:

$ export MOOD_HAT=/dev/tty-usbserial-…
$ jruby -S cucumber features

Now, bask in the green glow of a stream of passing tests on your monitor.

Wrapping Up

What is it that we’ve done here? Well, I wrote this test suite for fun, just to see what kind of hoops one would have to go through to test an embedded device from Ruby. So, mission accomplished.

But despite my best intentions to keep this test suite purely theoretical, it ended up having practical value. Before all the LEDs were wired up, I wrote a temporary user interface that made just one LED brighter or dimmer to indicate the mood. Later, I could freely change the user interface and retest the underlying logic.

The tests were also a good place to explore different edge cases for the interface. For example: should the moods wrap around at the extremes, so that furious is one step happier than ecstatic? Definitely not, and this became clear as the different examples took shape.

Finally, there’s the more obvious benefit of using Cucumber to drive the design: the project now has a basic smoke test that can quickly be run against the firmware. As you write your own variant Mood Hat that’s way better than mine, you’ll be able to refactor fearlessly. When you do, please share in the comments what you’ve built.

The source code for this project is available here. You can see a video of the tests in action here. Happy hacking!

Special thanks to Maik Schmidt for reviewing this article.

About the Author

Erin Dees grew up in sunny Dallas, Texas, but eventually fled to the cooler climes of Portland, Oregon, and then on to Ontario. She got hooked on programming as a child trying out a Timex Sinclair 1000 at school, and has been creating software ever since. She’s hacked on everything from tiny embedded systems that need an oscilloscope to debug, to scaled-out web services that handle hundreds of thousands of requests every minute.

When Erin is “away from keyboard,” you can find her race-walking across Ontario wearing brilliantly hued and possibly aerodynamic garb. She also likes to play tabletop board games, so bring your meeples and meet her at the table!

Erin tweets as @undees.

Cover from PragPub Magazine, April 2011
Cover from PragPub Magazine, April 2011

--

--

PragPub
The Pragmatic Programmers

The Pragmatic Programmers bring you archives from PragPub, a magazine on web and mobile development (by editor Michael Swaine, of Dr. Dobb’s Journal fame).