Article summary
When porting some Ruby code over to JRuby for a prototype I’ve been working on recently, I ran into a small hiccup that I should have seen coming but wasn’t originally very worried about: my app talks over a serial port via ruby-serialport. The serialport gem is built with native code, as Bundler was kind enough to remind me; I couldn’t use its convenient interface in my new JRuby app.
So I hit Google, thinking surely — by now — someone has published a nice JRuby serial library. This is apparently not the case; while I did find an older (abandoned?) project called dgn110-jruby, there was a lot of code I knew I wouldn’t need, and the tests looked incomplete, so I kept looking.
Happily, Ian Dees wrote a neat PragPub article about testing Arduino code using JRuby and included example code for a nice little adapter to bridge Ruby code to the RXTX library. While Ian wasn’t attempting to make a fully-compliant replacement for serialport’s interface, it sure looked good enough for my needs, and easy to read and modify. I copied his code, tweaked the read/write methods to better suit my project, and I was back in business without any major modifications to the original prototype code.
Source Code
Here’s the code for what I called JrSerialPort:
require 'java'
require_relative 'RXTXcomm.jar'
import('gnu.io.CommPortIdentifier')
import('gnu.io.SerialPort') { 'GnuSerialPort' }
class JrSerialPort
NONE = GnuSerialPort::PARITY_NONE
def initialize name, baud, data, stop, parity
port_id = CommPortIdentifier.get_port_identifier name
data = GnuSerialPort.const_get "DATABITS_#{data}"
stop = GnuSerialPort.const_get "STOPBITS_#{stop}"
@port = port_id.open 'JRuby', 500
@port.set_serial_port_params baud, data, stop, parity
@in = @port.input_stream
@in_io = @in.to_io
@out = @port.output_stream
end
def close
@port.close
end
def write(s)
@out.write s.to_java_bytes
end
def read
@in_io.read(@in.available) || ''
end
end
To test it out, the following code starts a simple serial terminal, relaying each line you type to the connected serial port and printing every byte received from the port to stdout on arrival:
require_relative 'jr_serial_port'
port_name = ARGV.shift || '/dev/ttyUSB0'
serial_port = JrSerialPort.new port_name, 19200, 8, 1, JrSerialPort::NONE
Thread.new do
while true do
sleep 0.01
print serial_port.read
end
end
while true
print "Prompt> "
serial_port.write gets.strip
end
Preparation
These examples assume that your system has serial ports available and configured or that you’ve got a USB-serial cable with drivers installed (Linux systems usually have many USB-serial cable drivers preinstalled), and that you know how to address your port(s), e.g., /dev/ttyUSB0.
You’ll need to add two binary dependencies to your project: the cross-platform RXTXcomm.jar
and the native binary for your target platform, such as librxtxSerial.so
(for Linux) or rxtxSerial.dll (for Windows), etc. You’ll also need to let Java know where to find that native library, so update your JRuby command or your JRUBY_OPTS
environment variable.
As an example, I set up my project to be run assuming the current working directory is the root of my project, so I:
- Copied
RXTXcomm.jar
intolib
. - Copied
librxtxSerial.so
intolib
. - Setup my shell environment:
export JRUBY_OPTS="-J-Djava.library.path=lib"
.
This isn’t a gem yet, as it’s only being used in prototype code, has no tests, and I don’t have a ton of experience wrangling serial communication parameters in different and challenging environments. But I am planning to release this little guy as a convenient gem to quickly connect to a serial interface and get things up and talking. For now, feel free to clone the example code and use it as you please.