Ruby on Nickel-Silver Rails
It’s been hot here in Melbourne so rather than actually leaving the house to do anything I’ve been sitting in front of a fan mucking about with my computer. Yesterday afternoon I created a Ruby implementation of a LocoNetOverTCP server. I’ve decided to call it Nickel-Silver because that’s the common name of the alloy most model railroad track is made of and some trifling project has already used the name “Ruby on Rails”. Here’s a quick list of Nickel-Silver’s features:
- Implements all of LocoNetOverTCP protocol version 1
- Multithreaded (inherits from GServer)
- Easily extendable interface for LocoNet hardware devices
- Only about 100 lines of code (according to egrep -c -v "^[ ]*$|^[ ]*#")
- Base implementation uses only the standard library
- Included “driver” for the LocoBuffer-USB hardware uses a single extra package, ruby-serialport
- It’s written in Ruby
Hardware setup
Currently the only “driver” provided connects to a LocoNet network via a LocoBuffer-USB. So a complete hardware setup looks something like the following diagram.

LocoNetOverTCP clients connect to the server running on the computer which acts as a proxy to the LocoNet. This removes the need for each computer to have its own LocoNet hardware interface. It also removes the need for the clients to be physically located near layout.
Server setup
Assuming we’re going to use the included LocoBuffer-USB “driver” we need to do the following to start a server.
- Install ruby-serialport if you haven’t already.
- Install the nickel-silver-server gem
- Open a new ruby script in your favourite editor or start IRB.
- Determine the serial port your LocoBuffer-USB is connected to.
- Create an instance of the LocoBufferUSB “driver” class.
- Create an instance of the server with the “driver” as a parameter.
- Run the script
To install the server’s gem open a terminal and run:
gem install nickel-silver-server
That should get everything that’s required with the exception of ruby-serialport which you must install manually.
My LocoBuffer-USB is connected to my computer via the virtual serial port /dev/cu.usbserial-FTQ1P4JC so a complete script for my server would look like the following.
#!/usr/bin/ruby require 'rubygems' require 'nickel-silver-server' # connect to a LocoBufferUSB on the virtual serial port /dev/cu.usbserial-FTQ1P4JC' interface = NickelSilver::Server::Interface::LocoBufferUSB.new( '/dev/cu.usbserial-FTQ1P4JC' ) # create a server using the default port (i.e. 5626, 'loco' spelt on a phone keypad) # using our freshly connected LocoBuffer-USB server = NickelSilver::Server::LocoNetServer.new( interface ) # start the server server.start # wait for the server to stop before exiting server.join
Documentation
Currently this post is the only documentation not contained within the gem. You can view the gem’s rdoc documentation at http://nickel-silver.rubyforge.org/nickel-silver-server/rdoc/
Sending trains down the track
Until boredom strikes again there isn’t a Ruby throttle program that can interface with Nickel-Silver. Instead we can use the excellent Java program JMRI to talk to our trains.
JMRI already knows about LocoNetOverTCP servers right out of the box. Starting JMRI and looking at the preferences we can wrangle things to look like the following.

Now we’re ready to go. Selecting Tools > Throttles > New Throttle gives us a throttle window where we can enter a locomotive address and make it move.
Implementing a new “driver”
The only LocoNet computer interface I have access to is a LocoBuffer-USB. I’d highly recommend one to anyone who is interested but acknowledge that there are many people with other hardware.
As long as you can send and receive LocoNet packets via your hardware in Ruby (or in any language that may be used for Ruby extensions) you can write a “driver”.
The only things a “driver” needs to be able to do are:
- Store incoming packets as FixNums representing bytes in a buffer array
- Send outgoing bytes (represented as FixNums in a buffer array) to LocoNet
- Use a Mutex to lock access to the buffers when in use (remember Nickel-Silver is multithreaded)
- Provide a method that causes your interface to start collecting packets
The interface is simple. Only the following public methods are needed:
- Accessors for input_buffer, output_buffer and io_mutex
- run() which starts buffering
How you do this is up to you but you can take a look at LocoBufferUSB.rb to get an idea of how it might be done. A stub driver might look like the following…
class SomeLocoNetInterface attr_accessor :input_buffer, :output_buffer, :io_mutex def initialize # these may be modified at any time by the server @input_buffer = [] @output_buffer = [] # only make changes when locked using @io_mutex @io_mutex = Mutex.new end def run loop do # get incoming bytes if byte_waiting? @io_mutex.synchronize do # byte getting code here @input_buffer << get_byte end end # send outgoing bytes until @output_buffer.empty? do @io_mutex.synchronize do # send a byte send_byte( @output_buffer.shift ) end end end end end
Remember that either buffer may be empty or already containing data and that you must lock the mutex for the minimum amount of time possible.
Isn’t there already a LocoNetOverTCP server project?
Yes there is! It’s called LbServer and is hosted over at SourceForge.
If you’re looking for a mature LocoNetOverTCP server that’s probably the place you should start. Unlike my implementation there are more people than just me using it and they probably have more bugs fixed than I’m likely to notice any time soon.