Fork me on GitHub

Project Notes

#334 Serial Communications with Ruby

Options for serial communications in Ruby as they stand in 2025.

Notes

NB: these notes have now been updated to reflect the renewed maintenance of the original serialport gem as of 2025-07-03.

In the past I’d been accustomed to using the old serialport gem for serial communications.

By early 2025 it had fallen into a bit of disrepair: failing to keep up with ruby changes, macOS compatibility, and looking for a new maintainer.

This prompted me to take a few alternatives for a test drive (the rubyserial and uart gems), and a serialport fork by larskanis that got the old gem working again.

Very happily, the serialport gem is back under active maintenance. Yay!

TLDR: as of late 2025, my general recommendation:

  • prefer the uart gem for simple uses
  • use serialport >= 1.4.0 for more complex cases, especially if serial port signal control is required

The serialport gem - older and still widely used

The 1.3.2 version of the serialport gem would not install on my macOS 15.5 on Apple Silicon with Ruby 3.3.3. The native extensions cannot compile - see for example gem_make.out.txt.

These issues were addressed in serialport >= 1.4.0. This is the version I am now using, as specified in the Gemfile.

Key aspects of the serialport gem:

  • inherits from the IO class, and implements the corresponding input/output stream handling methods
    • read/write in binary or character mode
    • read/write text by character, line, or multiple lines
  • provides access to all serial port signals:
    • read: DCD, DSR, RI
    • read/write: DTR, RTS

The rubyserial is a more portable library, it does not require any native compilation thanks to using using Ruby-FFI.

Key aspects of the rubyserial gem:

  • very simple interface
  • all read/write calls are blocking
  • does not directly support timeouts
  • no access or control over serial port signals

Because of these limitations, in most cases it is probably important to have predictable termination indicators in the incoming data, and probably run the blocking call in a separate thread (with timeout).

The uart gem - even cleaner

Aaron Patterson developed a pure Ruby alternative: uart.

Key aspects of the uart gem:

  • No C code
  • No FFI code
  • 5 second timeout by default

Testing

I have an Arduino running a simple SerialControl sketch, attached to my mac and showing up as a device as follows:

$ ls -1 /dev/*serial*
/dev/cu.usbserial-2430
/dev/cu.wchusbserial2430
/dev/tty.usbserial-2430
/dev/tty.wchusbserial2430

Testing the serialport gem

Taking a very basic approach to send a command and read the response:

require 'serialport'
...
socket = SerialPort.new(port_name, 'baud' => baud_rate, 'data_bits' => 8, 'stop_bits' => 1, 'parity' => SerialPort::NONE)
socket.timeout = 1
socket.write "#{command}\r\n"
begin
  data = socket.gets rescue nil
  puts data if data
end while data

See serialport_example/example.rb:

$ cd serialport_example
$ bundle exec example.rb /dev/cu.wchusbserial2430 s32
# SerialPort demonstration
# client initialised for : /dev/cu.wchusbserial2430
#     connection options : {"baud"=>115200, "data_bits"=>8, "stop_bits"=>1, "parity"=>0}
#                signals : {"rts"=>1, "dtr"=>1, "cts"=>1, "dsr"=>1, "dcd"=>1, "ri"=>1}
Random String with length=32:
kPJnkGR7Q0B0MwbPNRB7BJI6dqpRz1gl
$

Testing the rubyserial gem

Taking a very basic approach to send a command and read the response:

require 'rubyserial'
...
socket = Serial.new(port_name, baud_rate, 8, :none, 1)
socket.write "#{command}\r\n"
begin
  data = socket.gets
  puts data unless data.chomp.empty?
end until data.chomp.empty?
socket.close

See rubyserial_example/example.rb:

$ cd rubyserial_example
$ bundle exec example.rb /dev/cu.wchusbserial2430 i
# RubySerial demonstration
# client initialised for : /dev/cu.wchusbserial2430
Random Integer:
45
$

Testing the uart gem

Taking a very basic approach to send a command and read the response:

require 'uart'
...
UART.open port_name, baud_rate, '8N1' do |socket|
  socket.write "#{command}\r\n"
  begin
    data = socket.gets
    puts data unless data.chomp.empty?
  end until data.chomp.empty?
end

See uart_example/example.rb:

$ cd uart_example
$ bundle exec example.rb /dev/cu.wchusbserial2430 s28
# UART demonstration
# client initialised for : /dev/cu.wchusbserial2430
Random String with length=28:
sDClAyQ67Rr71R83pEVYSS5RyWOL
$ bundle exec example.rb /dev/cu.wchusbserial2430 t
# UART demonstration
# client initialised for : /dev/cu.wchusbserial2430
LED on pin 13: turning LED ON
$

Credits and References

About LCK#334 Ruby

This page is a web-friendly rendering of my project notes shared in the LittleCodingKata GitHub repository.

Project Source on GitHub Return to the LittleCodingKata Catalog
About LittleCodingKata

LittleCodingKata is my collection of programming exercises, research and code toys broadly spanning things that relate to programming and software development (languages, frameworks and tools).

These range from the trivial to the complex and serious. Many are inspired by existing work and I'll note credits and references where applicable. The focus is quite scattered, as I variously work on things new and important in the moment, or go back to revisit things from the past.

This is primarily a personal collection for my own edification and learning, but anyone who stumbles by is welcome to borrow, steal or reference the work here. And if you spot errors or issues I'd really appreciate some feedback - create an issue, send me an email or even send a pull-request.

Follow the Blog follow projects and notes as they are published in your favourite feed reader