Expand description
A sans-IO virtual OpenLCB node on a CAN segment.
The node handles alias reservation and defence, the login sequence, the mandatory replies (Verify Node ID, AME, Protocol Support, SNIP) and event identification. Applications drive it with three calls:
Node::receivefor every frame read from the bus,Node::pollperiodically (or atNode::next_deadline),Node::take_outgoingto collect frames to transmit.
Time is passed in as milliseconds from any monotonic clock.
§Lifecycle
start(now) poll(now >= now+200ms)
Inhibited ─────────► Reserving ─────────────────────────► Permitted
▲ (4× CID sent) │ (RID, AMD, Init Complete, │
│ │ Identified for every event, │
│ │ then queued messages) │
│ alias collision: │ │
│ restart with next ◄─┘ │
│ candidate alias │
│ ◄──────────────────────────────────┘
│ someone else *used* our alias, or
│ start() called again: AMR, then start
│ over (no 2nd Init Complete)
└──────────── reset() (link lost) ─────────────────────────┘While reserving, any frame from our candidate alias means someone else has it, so the node picks the next candidate and sends new CIDs. Once permitted, a CID for our alias is answered with RID (we keep it), while any other frame using our alias forces us to release it with AMR and reserve a new one, as the CAN Frame Transfer Standard requires.
§Automatic behaviour
Once permitted, Node::receive answers these by itself:
| Incoming | Reply |
|---|---|
| Verify Node ID Global (no ID, or our ID) / Addressed to us | Verified Node ID |
| AME (empty, or our Node ID) | AMD |
| Protocol Support Inquiry | Protocol Support Reply with Node::set_protocols flags |
| SNIP Request | SNIP Reply from NodeInfo |
| Identify Events Global / Addressed to us | Producer/Consumer Identified for every registered event |
| Identify Consumer / Producer for a registered event | Consumer / Producer Identified with its state |
| Datagram to us | Datagram Rejected (0x1043, NOT_IMPLEMENTED_UNKNOWN_MTI) |
Traction Command to us, unless protocol::TRACTION_CONTROL is set or it is a Controller Changing Notify (20 04) | Optional Interaction Rejected (0x1043) |
| Any other unknown addressed message to us | Optional Interaction Rejected (0x1043) |
Replies (Traction Control Reply, Datagram Received OK / Rejected,
Optional Interaction Rejected, SNIP reply, …) are never rejected.
Everything (including messages that were auto-answered) is still
returned to the application as a Received.
The node also produces EventId::DUPLICATE_NODE_ID (as a PCER, once)
the first time it sees another node announce its Node ID; see
Node::duplicate_node_id_detected.
§Example: login driven by a fake clock
use rolcb::can::CanHeader;
use rolcb::node::ALIAS_RESERVE_MS;
use rolcb::{EventId, EventState, Message, Node, NodeId, NodeInfo};
let mut node = Node::new(
NodeId::new(0x0501_0101_2299),
NodeInfo { manufacturer: "Example".into(), model: "Panel".into(), ..Default::default() },
);
node.add_consumer(EventId(0x0501_0101_2200_0010), EventState::Unknown);
// Sending before login is fine; the message is held until we have an alias.
node.send(Message::Pcer(EventId(0x0501_0101_2299_0001)));
let mut now = 1_000;
node.start(now);
let cids = node.take_outgoing();
assert_eq!(cids.len(), 4);
assert!(cids.iter().all(|f| matches!(f.header(), CanHeader::Cid { .. })));
assert_eq!(node.next_deadline(), Some(now + ALIAS_RESERVE_MS));
// Nothing happens until 200 ms have passed.
now += 199;
node.poll(now);
assert!(node.take_outgoing().is_empty());
assert!(!node.is_permitted());
now += 1;
node.poll(now);
let out = node.take_outgoing();
let alias = node.alias().unwrap();
assert_eq!(out[0].header(), CanHeader::Rid { alias });
assert_eq!(out[1].header(), CanHeader::Amd { alias });
// …followed by Initialization Complete, Consumer Identified and the queued PCER.
assert_eq!(out.len(), 5);
assert_eq!(node.next_deadline(), None);In a real application, call Node::poll from a timer (every 10-50 ms
is plenty) or sleep until Node::next_deadline, and drain
Node::take_outgoing (or Node::pop_outgoing, one frame at a time)
after every start, poll, receive and send. The
throttle guide shows a complete loop.
Structs§
- Node
- A virtual OpenLCB node.
- Node
Info - Simple Node Information, reported to configuration tools such as JMRI.
Constants§
- ALIAS_
RESERVE_ MS - How long to wait after the CID frames before claiming the alias.
- MAX_
PENDING - How many messages
Node::sendholds while the node is not yet permitted. Further messages are dropped (Node::try_sendreports it).