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:

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.

flowchart TD START["get_first(key=0)"] --> N0["Node 0"] N0 -->|"get_next(key=0)"| N1["Node 1"] N1 -->|"get_next(key=0)"| N2["Node 2"] N2 -->|"get_next(key=0)"| END["NULL (end)"] START2["get_first(key=1)"] --> N0b["Node 0"] N0b -->|"get_next(key=1)"| N1b["Node 1"] N1b -->|"is_last(key=1)?"| CHECK{"true"} style START fill:#c8e6c9,stroke:#2e7d32 style START2 fill:#bbdefb,stroke:#1565c0 style END fill:#ffcdd2,stroke:#c62828 style CHECK fill:#fff9c4,stroke:#f57f17

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);
}
Key Allocation

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

FieldTypeDescription
stateopenlcb_node_state_tBitfield with run_state, allocated, permitted, initialized, etc.
iduint64_t48-bit Node ID (permanent, globally unique)
aliasuint16_t12-bit CAN alias (temporary, assigned during login)
seeduint64_tCurrent seed for the LFSR alias generation algorithm
consumersevent_id_consumer_list_tList of consumed events with statuses, plus range list and enumerator
producersevent_id_producer_list_tList of produced events with statuses, plus range list and enumerator
parametersconst node_parameters_t *Pointer to const configuration (SNIP, CDI, address spaces, PSI bits)
timerticksuint16_t100ms timer tick counter (incremented by main loop)
owner_nodeuint64_tNode ID that has locked/reserved this node (0 = unlocked)
last_received_datagramopenlcb_msg_t *Saved incoming datagram for reply processing
indexuint8_tThis node's index in the node array
train_statetrain_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:

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);
← Previous: Ch 4b — CAN Buffer System Next: Ch 6 — Thread Safety →