Skip to main content

Module node

Module node 

Source
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:

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:

IncomingReply
Verify Node ID Global (no ID, or our ID) / Addressed to usVerified Node ID
AME (empty, or our Node ID)AMD
Protocol Support InquiryProtocol Support Reply with Node::set_protocols flags
SNIP RequestSNIP Reply from NodeInfo
Identify Events Global / Addressed to usProducer/Consumer Identified for every registered event
Identify Consumer / Producer for a registered eventConsumer / Producer Identified with its state
Datagram to usDatagram 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 usOptional 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.
NodeInfo
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::send holds while the node is not yet permitted. Further messages are dropped (Node::try_send reports it).