Skip to main content

Crate rolcb

Crate rolcb 

Source
Expand description

§rolcb — Rustified OpenLCB

A sans-IO implementation of the OpenLCB / LCC protocol stack. The crate never touches sockets, serial ports or clocks: you feed it CAN frames and the current time, and it hands back frames to transmit and decoded messages. That makes it usable from async runtimes, WASM in the browser, and embedded targets alike (no_std + alloc with default-features = false).

OpenLCB (marketed by the NMRA as LCC, Layout Command Control) is a peer-to-peer control bus for model railroads. Nodes (block detectors, signal drivers, turnout controllers, throttles, command stations, dispatch panels) talk over CAN or TCP using a small set of standardised messages. The most important ones are events (64-bit IDs meaning “something happened”, see event) and traction control (driving trains, see traction).

§Layers

Bottom to top:

ModulePurposeStandard
can29-bit CAN identifiers, CanFrame, CID/RID/AMD/AME/AMR control framesCAN Frame Transfer
gridconnectThe :X19490ABCN; ASCII encoding used by USB/TCP adapters, streaming Decoder(de facto)
alias12-bit alias candidate generatorCAN Frame Transfer
messageHigh-level Messages, their (multi-)frame encoding and reassemblyMessage Network, CAN Frame Transfer
nodeA virtual Node: alias reservation, login, standard replies, event identificationMessage Network, Event Transport, SNIP
tractionTraction Control payloads, Speed, train searchTraction Control, Train Search
event, node_id, mti, float16Basic typesEvent Identifiers, Unique Identifiers
hub (feature hub)Async GridConnect hub over TCP and serial—

The rolcb-hub binary (feature cli) packages the hub as a standalone program.

§Guide

Longer, narrative documentation lives in the guide module:

  1. Getting started: add the crate, log a node in, send and receive your first event.
  2. Architecture and the sans-IO model: how the layers fit together and how to drive a node from any I/O stack.
  3. Writing a throttle: search → assign → speed → heartbeat → release.
  4. Consuming and producing events: a dispatch panel.
  5. Running and embedding the hub: rolcb-hub flags, JMRI, using hub::Hub::port from your own server.
  6. no_std and WASM: using the crate in a browser or on a microcontroller.
  7. Protocol reference: MTIs, CAN header layout, frame types, PIP bits, traction command bytes, error codes.

§Feature flags

FeatureDefaultEnablesDependencies
stdyesLinks std. Without it the crate is no_std + alloc; everything except the hub works the same (ParseError implements core::error::Error either way).—
serdenoSerialize/Deserialize for NodeId and EventId (as dotted-hex strings) and EventStateserde (no_std compatible)
hubnoThe async hub module (library only)std, tokio
serialnohub::uplink_serial for USB GridConnect adaptershub, tokio-serial
lognoHub connection, disconnection and lag messages through the log crate. Without it the library never prints anything.log
clinoThe rolcb-hub binary (multi-threaded tokio runtime, Ctrl-C handling, logs to stderr)hub, log, clap

§Examples and tools

  • examples/gc_dump.rs decodes a GridConnect capture (a JMRI monitor log, rolcb-hub -v output, or cat /dev/ttyACM0) into readable messages: cargo run --example gc_dump -- capture.txt.
  • rolcb-hub (src/bin/rolcb-hub.rs, feature cli) is a standalone GridConnect hub: cargo install rolcb --features cli,serial. See the hub chapter.
  • tests/throttle_session.rs drives a throttle against a scripted train end to end, and the throttle chapter does the same as a doctest.

§Quick example

Two nodes on an in-memory “bus”: a block detector produces an event and a panel consumes it. Frames go through GridConnect text on the way, exactly as they would over a USB adapter or a TCP hub.

use rolcb::{gridconnect, EventId, EventState, Message, Node, NodeId, NodeInfo};

/// Deliver everything `from` wants to send to `to`; return what `to` decoded.
fn deliver(from: &mut Node, to: &mut Node, now_ms: u64) -> Vec<Message> {
    let mut got = Vec::new();
    for frame in from.take_outgoing() {
        let line = gridconnect::format(&frame);          // ":X195B4…N…;"
        let frame = gridconnect::parse(&line).unwrap();
        if let Some(rx) = to.receive(&frame, now_ms) {
            got.push(rx.msg);
        }
    }
    got
}

let occupied: EventId = "05.01.01.01.22.00.00.10".parse()?;

let mut detector = Node::new("05.01.01.01.22.00".parse()?, NodeInfo::default());
detector.add_producer(occupied, EventState::Invalid);
let mut panel = Node::new(NodeId::new(0x0501_0101_2299), NodeInfo::default());
panel.add_consumer(occupied, EventState::Unknown);

// Log both nodes in: CID frames now, RID/AMD/Init Complete 200 ms later.
detector.start(0);
panel.start(0);
deliver(&mut detector, &mut panel, 0);
deliver(&mut panel, &mut detector, 0);
detector.poll(200);
panel.poll(200);
deliver(&mut detector, &mut panel, 200);
deliver(&mut panel, &mut detector, 200);
assert!(detector.is_permitted() && panel.is_permitted());

// The block becomes occupied.
detector.set_producer_state(occupied, EventState::Valid);
detector.send(Message::Pcer(occupied));
let got = deliver(&mut detector, &mut panel, 300);
assert_eq!(got, vec![Message::Pcer(occupied)]);

// The panel learned the detector's Node ID from its AMD frame.
assert_eq!(panel.alias_of(detector.node_id()), detector.alias());

With real I/O the shape is the same: write take_outgoing() frames to your port, pass every frame you read to Node::receive, and call Node::poll from a timer. See the architecture chapter for tokio and browser loops.

Re-exports§

pub use can::CanFrame;
pub use can::CanHeader;
pub use event::EventId;
pub use event::EventState;
pub use message::EncodeError;
pub use message::Message;
pub use message::Received;
pub use mti::Mti;
pub use node::Node;
pub use node::NodeInfo;
pub use node_id::NodeId;

Modules§

alias
12-bit alias generation, using the pseudo-random sequence suggested by the CAN Frame Transfer Standard (seeded from the Node ID).
can
CAN frame layer: 29-bit extended identifiers as laid out by the OpenLCB CAN Frame Transfer Standard.
event
64-bit OpenLCB Event IDs and event states.
float16
IEEE 754 half-precision floats, as used for Traction speeds.
gridconnect
GridConnect ASCII framing: :X19490ABCN0102030405060708;
guide
Narrative documentation: tutorials, design notes and protocol tables.
hubhub
An async GridConnect hub (feature hub).
message
High-level OpenLCB messages and their encoding onto CAN frames.
mti
Message Type Indicators (16-bit, as defined in the Message Network Standard).
node
A sans-IO virtual OpenLCB node on a CAN segment.
node_id
48-bit OpenLCB Node IDs.
traction
Traction Control protocol payloads (MTI 0x05EB command / 0x01E9 reply) and the Train Search protocol.

Enums§

ParseError
Error returned when parsing textual or binary protocol data fails.