Ruby & Serial Ports - A Debugging Adventure
So as part of my ongoing "playing around" with model railway control I treated myself to a 16 zone RailCom receiver from DCC₄PC, in part because their documentation is so clear about how to talk to their devices using software you've written yourself. Enter a shelf kit with 7 pieces of setrack individually wired, a DCC command station (DCC++EX) and some DCC₄PC parts.
The PC runs JMRI to tell the command station what instruction to give which loco. The command station converts this into a DCC packet which is placed onto the tracks. After each packet the command station sends a long inter-packet preamble. The output from the command station doesn't however go straight to the track, it first goes through a RailCom cutout module, this shrinks the preamble to the minimum allowed by the DCC specifications and disconnects the track from the command station, allowing the DCC decoders in the locomotives to send some data back (using RailCom). After passing through the cutout the DCC for the tracks lastly goes through the RailCom receiver fromDCC₄PC, this board receives the RailCom data ready for the PC to ask for it.
So far so simple right? We've got the hardware setup (and we can make several locos move) and we know the protocol for talking to the RailCom receiver. Or so I thought, I've used a serial port in Ruby before so grab a gem, toggle the control lines as needed and send some bytes then read some bytes, workout what they mean and puts some text about what's plugged in. Especially as the protocol provides some instructions to easily find what's on the bus. Well it turns out no!
So first let's get the DCC₄PC provided software (and a serial port debugging program) and check that the hardware works and that the DCC₄PC OmniBus specification is accurate, the hardware works and the serial port was being used exactly as I understood the spec to require. Then add a pile of debugging puts statements to my Ruby code to check I'm giving the data to the serial port that I expect and what I'm getting back, I'm sending (or at least telling the gem to send) exactly the same as the DCC₄PC software but getting nothing back. At that point I've reached a "go for a walk" level of stumped, have a look at the OmniBus specification (linked previously) and see if you can guess what the problem turned out to be.
So what realisations did I have on my walk?
- I need to buy a rubber duck (https://rubberduckdebugging.com/)!
- The gem probably uses a C extension / library to talk to the serial port, the enumerate instruction is 0 (a null byte), how's my carefully constructed array of integers getting passed along?
And that's when I went down the rabbit hole of looking at what happens in the gem, my carefully constructed array of integers gets converted to a null terminated string, great! Guess I'm going to have to learn FFI and dust of my C skills.
A few blog posts later ("Ruby FFI Module Tutorial (Example: Play MP3 with VLC)" and "Linux Serial Ports Using C/C++") I was certain I could do this and had was successfully enumerating what was on the omnibus from ruby.