We're hiring!

We're actively seeking developers and designers for our Detroit & Ann Arbor locations.

JrSerialPort: Connect to a Serial Port in JRuby via RXTX

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:

  1. Copied RXTXcomm.jar into lib.
  2. Copied librxtxSerial.so into lib.
  3. 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.
 

This entry was posted in jRuby and tagged , , . Bookmark the permalink. Both comments and trackbacks are currently closed.