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:
| Module | Purpose | Standard |
|---|---|---|
can | 29-bit CAN identifiers, CanFrame, CID/RID/AMD/AME/AMR control frames | CAN Frame Transfer |
gridconnect | The :X19490ABCN; ASCII encoding used by USB/TCP adapters, streaming Decoder | (de facto) |
alias | 12-bit alias candidate generator | CAN Frame Transfer |
message | High-level Messages, their (multi-)frame encoding and reassembly | Message Network, CAN Frame Transfer |
node | A virtual Node: alias reservation, login, standard replies, event identification | Message Network, Event Transport, SNIP |
traction | Traction Control payloads, Speed, train search | Traction Control, Train Search |
event, node_id, mti, float16 | Basic types | Event 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:
- Getting started: add the crate, log a node in, send and receive your first event.
- Architecture and the sans-IO model: how the layers fit together and how to drive a node from any I/O stack.
- Writing a throttle: search → assign → speed → heartbeat → release.
- Consuming and producing events: a dispatch panel.
- Running and embedding the hub:
rolcb-hubflags, JMRI, usinghub::Hub::portfrom your own server. no_stdand WASM: using the crate in a browser or on a microcontroller.- Protocol reference: MTIs, CAN header layout, frame types, PIP bits, traction command bytes, error codes.
§Feature flags
| Feature | Default | Enables | Dependencies |
|---|---|---|---|
std | yes | Links std. Without it the crate is no_std + alloc; everything except the hub works the same (ParseError implements core::error::Error either way). | — |
serde | no | Serialize/Deserialize for NodeId and EventId (as dotted-hex strings) and EventState | serde (no_std compatible) |
hub | no | The async hub module (library only) | std, tokio |
serial | no | hub::uplink_serial for USB GridConnect adapters | hub, tokio-serial |
log | no | Hub connection, disconnection and lag messages through the log crate. Without it the library never prints anything. | log |
cli | no | The rolcb-hub binary (multi-threaded tokio runtime, Ctrl-C handling, logs to stderr) | hub, log, clap |
§Examples and tools
examples/gc_dump.rsdecodes a GridConnect capture (a JMRI monitor log,rolcb-hub -voutput, orcat /dev/ttyACM0) into readable messages:cargo run --example gc_dump -- capture.txt.rolcb-hub(src/bin/rolcb-hub.rs, featurecli) is a standalone GridConnect hub:cargo install rolcb --features cli,serial. See the hub chapter.tests/throttle_session.rsdrives 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.
- hub
hub - 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§
- Parse
Error - Error returned when parsing textual or binary protocol data fails.