Chapter 9 — Login State Machine

The login state machine walks each node through the OpenLCB initialization sequence: sending Initialization Complete, announcing all producer and consumer events, and finally transitioning to the RUNSTATE_RUN state where normal protocol processing begins.

Source files: src/openlcb/openlcb_login_statemachine.c, src/openlcb/openlcb_login_statemachine.h

9.1 State Progression

Each node tracks its current state in openlcb_node->state.run_state. The login state machine only processes nodes whose run_state is less than RUNSTATE_RUN. Nodes already in RUNSTATE_RUN are skipped.

stateDiagram-v2 [*] --> INIT: Node allocated INIT --> GENERATE_SEED: CAN layer GENERATE_SEED --> SEND_CID: CAN layer SEND_CID --> WAIT_200MS: CAN layer WAIT_200MS --> SEND_RID: CAN layer SEND_RID --> SEND_AMD: CAN layer SEND_AMD --> LOAD_INIT_COMPLETE: CAN layer sets state LOAD_INIT_COMPLETE --> LOAD_PRODUCER_EVENTS: Init Complete sent LOAD_PRODUCER_EVENTS --> LOAD_CONSUMER_EVENTS: All producers announced LOAD_CONSUMER_EVENTS --> LOGIN_COMPLETE: All consumers announced LOGIN_COMPLETE --> RUN: on_login_complete callback note right of RUN: Normal protocol processing note left of INIT: States before LOAD_INIT_COMPLETE\nare handled by the CAN layer
StateHandlerDescription
RUNSTATE_INIT through RUNSTATE_SEND_AMD (CAN login state machine) Transport-specific initialization handled by the CAN layer (alias reservation, CID/RID/AMD).
RUNSTATE_LOAD_INITIALIZATION_COMPLETE load_initialization_complete() Builds and sends the Initialization Complete message with this node's full Node ID.
RUNSTATE_LOAD_PRODUCER_EVENTS load_producer_events() Iterates through the node's producer event list, sending Producer Identified for each. Uses the enumerate flag for multi-message sequencing.
RUNSTATE_LOAD_CONSUMER_EVENTS load_consumer_events() Same as above but for consumer events. Sends Consumer Identified for each event in the list.
RUNSTATE_LOGIN_COMPLETE on_login_complete() Optional callback for application-specific post-login work. If it returns false, the state machine retries next cycle. On success (or if NULL), transitions to RUNSTATE_RUN.
RUNSTATE_RUN (main dispatcher) Normal protocol operation. The login state machine skips this node from now on.

9.2 Priority Dispatch

Like the main dispatcher, OpenLcbLoginMainStatemachine_run() uses a priority chain where the first handler that does work causes an immediate return:

flowchart TD A["OpenLcbLoginMainStatemachine_run()"] --> B{"handle_outgoing\n_openlcb_message()"} B -- "true" --> Z["return"] B -- "false" --> C{"handle_try\n_reenumerate()"} C -- "true" --> Z C -- "false" --> D{"handle_try_enumerate\n_first_node()"} D -- "true" --> Z D -- "false" --> E{"handle_try_enumerate\n_next_node()"} E -- "true / false" --> Z style A fill:#4a90d9,color:#fff style Z fill:#888,color:#fff
No FIFO pop: Unlike the main dispatcher, the login state machine does not pop from the incoming FIFO. It generates outgoing messages by walking through node states rather than responding to incoming messages.

9.3 Event Announcement

During the RUNSTATE_LOAD_PRODUCER_EVENTS and RUNSTATE_LOAD_CONSUMER_EVENTS phases, the login state machine announces all events a node produces or consumes. This is accomplished through the enumerate flag:

  1. The handler checks if the node has events at the current index.
  2. If yes, it builds a Producer/Consumer Identified message with the event's status (Unknown, Set, or Clear), sets outgoing_msg_info.valid = true and outgoing_msg_info.enumerate = true, and increments the event index.
  3. The dispatcher sends the message, then re-enters the handler (priority 2).
  4. When all events have been announced, the handler clears the enumerate flag and advances the node's run_state to the next phase.

9.4 Multi-Node Login

The login state machine uses the same round-robin enumeration pattern as the main dispatcher. Each call to _run() processes one node at a time. If a node needs to send multiple messages (event announcements), it uses the enumerate flag to hold the enumeration position until all messages are sent before advancing to the next node.

The node enumerator key OPENLCB_LOGIN_STATMACHINE_NODE_ENUMERATOR_INDEX is separate from the main dispatcher's key, so the two state machines maintain independent iteration positions.

9.5 The on_login_complete Callback

The optional on_login_complete callback gives the application a chance to perform post-login initialization (e.g., starting timers, querying the network). The callback signature:

bool (*on_login_complete)(openlcb_node_t *openlcb_node);

9.6 Login Context Structure

The login state machine maintains its own context, openlcb_login_statemachine_info_t, separate from the main dispatcher context. Its outgoing message buffer uses BASIC-sized payload (sufficient for Init Complete and event identification messages, which carry at most 8 bytes of payload).

typedef struct {
    openlcb_msg_info_t   outgoing_msg_info;   // worker buffer + valid/enumerate flags
    openlcb_node_t      *openlcb_node;        // node currently being enumerated
} openlcb_login_statemachine_info_t;