Chapter 5 — Node Management
The node module (openlcb_node.h/.c) manages a fixed-size pool of openlcb_node_t structures. It provides allocation, enumeration, and lookup functions used by every other module in the library.
5.1 Static Node Pool
The pool is sized by USER_DEFINED_NODE_BUFFER_DEPTH:
typedef struct {
openlcb_node_t node[USER_DEFINED_NODE_BUFFER_DEPTH];
uint16_t count; // Number of allocated nodes (never decreases)
} openlcb_nodes_t;
Nodes are allocated once and never deallocated. The count field only increases. This simplifies memory management and avoids dangling pointer issues -- once you have a pointer to a node, it remains valid for the lifetime of the application.
5.2 Node Allocation
openlcb_node_t *OpenLcbNode_allocate(uint64_t node_id, const node_parameters_t *node_parameters);
Finds the first free slot, initializes it with:
- The provided 48-bit
node_id - A pointer to the const
node_parameters_t(stored, not copied -- it must remain valid) - Auto-generated consumer and producer event IDs based on the Node ID
- State flags:
allocated = 1,run_state = RUNSTATE_INIT - The node's index in the array
Returns NULL if the pool is full.
Event Auto-Generation
The consumer_count_autocreate and producer_count_autocreate fields in node_parameters_t tell the allocator how many event IDs to automatically generate. Events are derived from the Node ID, creating unique event IDs for each node without requiring the application to manually compute them.
5.3 Multi-Key Node Enumerator
The library frequently needs to iterate over all allocated nodes -- for example, the main state machine processes each node on every tick, and the login state machine runs independently for each node. To support multiple independent, concurrent iterations, the node module provides a keyed enumerator system.
openlcb_node_t *OpenLcbNode_get_first(uint8_t key);
openlcb_node_t *OpenLcbNode_get_next(uint8_t key);
bool OpenLcbNode_is_last(uint8_t key);
Each enumerator key (0 to MAX_NODE_ENUM_KEY_VALUES - 1) maintains its own position index. The library supports 8 concurrent enumeration keys.
Usage Pattern
// Iterate all nodes using key 0
openlcb_node_t *node = OpenLcbNode_get_first(0);
while (node) {
// Process this node...
process_node(node);
node = OpenLcbNode_get_next(0);
}
// Meanwhile, another module can independently iterate using key 1
openlcb_node_t *other = OpenLcbNode_get_first(1);
while (other) {
// Different processing...
other = OpenLcbNode_get_next(1);
}
Keys are not dynamically allocated -- they are convention-based. Each module that needs to enumerate nodes uses a different key constant. If two modules accidentally use the same key simultaneously, their iteration positions will interfere with each other.
5.4 Node Lookup
openlcb_node_t *OpenLcbNode_find_by_alias(uint16_t alias);
openlcb_node_t *OpenLcbNode_find_by_node_id(uint64_t node_id);
Linear scans of the node pool. For typical pool sizes (1-4 nodes), this is fast enough. Both return NULL if no match is found.
5.5 The openlcb_node_t Structure Walkthrough
| Field | Type | Description |
|---|---|---|
state | openlcb_node_state_t | Bitfield with run_state, allocated, permitted, initialized, etc. |
id | uint64_t | 48-bit Node ID (permanent, globally unique) |
alias | uint16_t | 12-bit CAN alias (temporary, assigned during login) |
seed | uint64_t | Current seed for the LFSR alias generation algorithm |
consumers | event_id_consumer_list_t | List of consumed events with statuses, plus range list and enumerator |
producers | event_id_producer_list_t | List of produced events with statuses, plus range list and enumerator |
parameters | const node_parameters_t * | Pointer to const configuration (SNIP, CDI, address spaces, PSI bits) |
timerticks | uint16_t | 100ms timer tick counter (incremented by main loop) |
owner_node | uint64_t | Node ID that has locked/reserved this node (0 = unlocked) |
last_received_datagram | openlcb_msg_t * | Saved incoming datagram for reply processing |
index | uint8_t | This node's index in the node array |
train_state | train_state_t * | Pointer to train state (NULL for non-train nodes) |
5.6 The train_state Pointer
For non-train nodes, train_state is NULL. For train nodes, it points to a train_state_t structure allocated from a separate static pool by OpenLcbApplicationTrain_setup(). The train state holds:
- Speed values:
set_speed,commanded_speed,actual_speed(all float16) - Emergency stop flags:
estop_active,global_estop_active,global_eoff_active - Controller assignment:
controller_node_id,reserved_node_count - Heartbeat management:
heartbeat_timeout_s,heartbeat_counter_100ms - Consist listener list:
listeners[],listener_count - Function values:
functions[]array indexed by function number - DCC addressing:
dcc_address,is_long_address,speed_steps - Back-pointer:
owner_nodepointing back to the owningopenlcb_node_t
5.7 Other Node Module Functions
void OpenLcbNode_initialize(const interface_openlcb_node_t *interface);
void OpenLcbNode_reset_state(void);
void OpenLcbNode_100ms_timer_tick(uint8_t current_tick);
initialize(): Clears all node structures and stores the optional callback interface.reset_state(): Resets all allocated nodes toRUNSTATE_INIT, clearingpermittedandinitializedflags. Does not deallocate nodes. Used when the transport layer needs to re-login (e.g., after a bus reset).100ms_timer_tick(): Increments node timer counters and fires the application callback (if registered) at most once per unique tick value.