Chapter 7 — Utilities
Three utility modules provide essential helper functions used throughout the library: openlcb_utilities.h (message manipulation), openlcb_float16.h (speed encoding), and openlcb_gridconnect.h (CAN-ASCII conversion).
7.1 Payload Insert Functions
All insert functions write data in big-endian byte order (network order, as required by the OpenLCB specification) and update payload_count automatically.
| Function | Writes | Offset |
|---|---|---|
OpenLcbUtilities_copy_event_id_to_openlcb_payload(msg, event_id) |
8-byte event ID | Always at offset 0 |
OpenLcbUtilities_copy_node_id_to_openlcb_payload(msg, node_id, offset) |
6-byte node ID | Caller-specified |
OpenLcbUtilities_copy_byte_to_openlcb_payload(msg, byte, offset) |
1 byte | Caller-specified |
OpenLcbUtilities_copy_word_to_openlcb_payload(msg, word, offset) |
2-byte word (big-endian) | Caller-specified |
OpenLcbUtilities_copy_dword_to_openlcb_payload(msg, dword, offset) |
4-byte doubleword (big-endian) | Caller-specified |
OpenLcbUtilities_copy_string_to_openlcb_payload(msg, str, offset) |
Null-terminated string | Caller-specified |
OpenLcbUtilities_copy_byte_array_to_openlcb_payload(msg, array, offset, count) |
Byte array | Caller-specified |
Example: Building a Verified Node ID Reply
// Allocate a BASIC buffer and populate it
openlcb_msg_t *reply = OpenLcbBufferStore_allocate_buffer(BASIC);
if (reply) {
OpenLcbUtilities_load_openlcb_message(reply,
node->alias, node->id, // source
0, 0, // dest (global)
MTI_VERIFIED_NODE_ID);
OpenLcbUtilities_copy_node_id_to_openlcb_payload(reply, node->id, 0);
// reply->payload_count is now 6
send_openlcb_msg(reply); // Ownership transfers
}
7.2 Payload Extract Functions
Extract functions read data in big-endian order. They do not modify payload_count.
| Function | Reads | Returns |
|---|---|---|
OpenLcbUtilities_extract_event_id_from_openlcb_payload(msg) |
8 bytes at offset 0 | event_id_t |
OpenLcbUtilities_extract_node_id_from_openlcb_payload(msg, offset) |
6 bytes at offset | node_id_t |
OpenLcbUtilities_extract_byte_from_openlcb_payload(msg, offset) |
1 byte at offset | uint8_t |
OpenLcbUtilities_extract_word_from_openlcb_payload(msg, offset) |
2 bytes at offset | uint16_t |
OpenLcbUtilities_extract_dword_from_openlcb_payload(msg, offset) |
4 bytes at offset | uint32_t |
7.3 Message Classification Helpers
| Function | Purpose |
|---|---|
OpenLcbUtilities_is_addressed_openlcb_message(msg) |
Returns true if the MTI has the destination-address-present bit set (MASK_DEST_ADDRESS_PRESENT) |
OpenLcbUtilities_is_addressed_message_for_node(node, msg) |
Returns true if the message destination matches this node's alias or Node ID |
OpenLcbUtilities_count_nulls_in_openlcb_payload(msg) |
Counts null bytes (0x00) in the payload. Used for SNIP validation (SNIP has multiple null-terminated strings). |
OpenLcbUtilities_set_multi_frame_flag(target, flag) |
Sets the multi-frame control bits (upper nibble) in the first data byte, preserving the lower nibble |
7.4 Event Range Utilities
Event ranges allow a node to declare that it produces or consumes a contiguous block of event IDs with a single message, rather than listing each event individually.
event_id_t OpenLcbUtilities_generate_event_range_id(event_id_t base, event_range_count_enum count);
bool OpenLcbUtilities_is_event_id_in_consumer_ranges(openlcb_node_t *node, event_id_t event_id);
bool OpenLcbUtilities_is_event_id_in_producer_ranges(openlcb_node_t *node, event_id_t event_id);
generate_event_range_id(): Creates a masked event ID encoding the base address and range size for a Range Identified message.is_event_id_in_consumer_ranges()/is_event_id_in_producer_ranges(): Tests whether a given event ID falls within any of the node's declared event ranges.
7.5 Broadcast Time Event Encoding/Decoding
The Broadcast Time protocol encodes time, date, year, and rate information into 64-bit event IDs. The utilities module provides both encoding and decoding functions.
Decoding (Event ID to Values)
| Function | Extracts |
|---|---|
OpenLcbUtilities_is_broadcast_time_event(event_id) | Returns true if event ID is in the broadcast time space |
OpenLcbUtilities_extract_clock_id_from_time_event(event_id) | 48-bit clock identifier (upper 6 bytes) |
OpenLcbUtilities_get_broadcast_time_event_type(event_id) | Event type enum (time, date, year, rate, query, etc.) |
OpenLcbUtilities_extract_time_from_event_id(event_id, &hour, &minute) | Hour (0-23) and minute (0-59) |
OpenLcbUtilities_extract_date_from_event_id(event_id, &month, &day) | Month (1-12) and day (1-31) |
OpenLcbUtilities_extract_year_from_event_id(event_id, &year) | Year (0-4095) |
OpenLcbUtilities_extract_rate_from_event_id(event_id, &rate) | 12-bit signed fixed-point rate (10.2 format) |
Encoding (Values to Event ID)
| Function | Creates |
|---|---|
OpenLcbUtilities_create_time_event_id(clock_id, hour, minute, is_set) | Report/Set Time event ID |
OpenLcbUtilities_create_date_event_id(clock_id, month, day, is_set) | Report/Set Date event ID |
OpenLcbUtilities_create_year_event_id(clock_id, year, is_set) | Report/Set Year event ID |
OpenLcbUtilities_create_rate_event_id(clock_id, rate, is_set) | Report/Set Rate event ID |
OpenLcbUtilities_create_command_event_id(clock_id, command) | Query, Start, Stop, or Date Rollover event ID |
The is_set parameter distinguishes between Report events (sent by a clock producer to announce current state) and Set events (sent by a consumer to change the clock).
7.6 Train Search Event Encoding/Decoding
Train search uses event IDs in the 0x090099FF________ space to encode a DCC address and control flags:
bool OpenLcbUtilities_is_train_search_event(event_id_t event_id);
void OpenLcbUtilities_extract_train_search_digits(event_id_t event_id, uint8_t *digits);
uint8_t OpenLcbUtilities_extract_train_search_flags(event_id_t event_id);
uint16_t OpenLcbUtilities_train_search_digits_to_address(const uint8_t *digits);
event_id_t OpenLcbUtilities_create_train_search_event_id(uint16_t address, uint8_t flags);
The event ID encodes 6 BCD-like nibbles for the address (with 0xF meaning "unused digit") and a flags byte in the lowest byte containing:
| Flag | Value | Meaning |
|---|---|---|
TRAIN_SEARCH_FLAG_ALLOCATE | 0x80 | Allocate new train node if no match |
TRAIN_SEARCH_FLAG_EXACT | 0x40 | Exact match only |
TRAIN_SEARCH_FLAG_ADDRESS_ONLY | 0x20 | Match address, not name |
TRAIN_SEARCH_FLAG_DCC | 0x08 | DCC protocol |
TRAIN_SEARCH_FLAG_LONG_ADDR | 0x04 | Force long (14-bit) DCC address |
TRAIN_SEARCH_SPEED_STEP_MASK | 0x03 | Speed step mode (0=default, 1=14, 2=28, 3=128) |
7.7 Float16: IEEE 754 Half-Precision
The OpenLCB Train Protocol encodes speed as IEEE 754 binary16 (half-precision floating point). The sign bit doubles as the direction indicator: 0 = forward, 1 = reverse. The openlcb_float16.h module provides integer-only bit manipulation for embedded targets that lack hardware float16 support.
Constants
| Constant | Value | Meaning |
|---|---|---|
FLOAT16_POSITIVE_ZERO | 0x0000 | Forward, stopped |
FLOAT16_NEGATIVE_ZERO | 0x8000 | Reverse, stopped |
FLOAT16_NAN | 0x7E00 | Speed not available |
FLOAT16_SIGN_MASK | 0x8000 | Direction bit (bit 15) |
FLOAT16_EXPONENT_MASK | 0x7C00 | 5-bit exponent (bits 14-10) |
FLOAT16_MANTISSA_MASK | 0x03FF | 10-bit mantissa (bits 9-0) |
Conversion Functions
uint16_t OpenLcbFloat16_from_float(float value); // float32 to float16 bits
float OpenLcbFloat16_to_float(uint16_t half); // float16 bits to float32
uint16_t OpenLcbFloat16_negate(uint16_t half); // Flip direction bit
bool OpenLcbFloat16_is_nan(uint16_t half); // Test for NaN
bool OpenLcbFloat16_is_zero(uint16_t half); // Test for +/- zero
Speed + Direction Helpers
uint16_t OpenLcbFloat16_speed_with_direction(float speed, bool reverse);
float OpenLcbFloat16_get_speed(uint16_t half); // Magnitude only (abs)
bool OpenLcbFloat16_get_direction(uint16_t half); // true = reverse
Example: Setting Train Speed
// Set train to 50.0 mph forward
uint16_t f16 = OpenLcbFloat16_speed_with_direction(50.0f, false);
// f16 is now the float16 bit pattern for 50.0 forward
// Extract speed and direction
float speed = OpenLcbFloat16_get_speed(f16); // 50.0
bool reverse = OpenLcbFloat16_get_direction(f16); // false
// Emergency stop: set to +0 (forward, stopped)
uint16_t stop = FLOAT16_POSITIVE_ZERO; // 0x0000
7.8 GridConnect: ASCII CAN Format
GridConnect is an ASCII encoding of CAN frames used for serial and TCP/IP communication. The format is:
:X<8-hex-ID>N<hex-data>;
Examples:
:X19170AAAN050101011800; (Verified Node ID from alias 0xAAA)
:X195B4AAAN0101000000010000; (PCER event from alias 0xAAA)
The maximum string length is 29 bytes (MAX_GRID_CONNECT_LEN): 2 prefix (:X) + 8 ID + 1 (N) + 16 data (8 bytes x 2 hex chars) + 1 (;) + 1 NUL.
Streaming Parser
bool OpenLcbGridConnect_copy_out_gridconnect_when_done(uint8_t next_byte,
gridconnect_buffer_t *buffer);
Feeds one byte at a time from the incoming serial/TCP stream. Uses static internal state to track parsing progress through three states:
| State | Value | Waiting For |
|---|---|---|
GRIDCONNECT_STATE_SYNC_START | 0 | :X or :x prefix |
GRIDCONNECT_STATE_SYNC_FIND_HEADER | 2 | 8 hexadecimal identifier characters |
GRIDCONNECT_STATE_SYNC_FIND_DATA | 4 | Data bytes until ; terminator |
Returns true when a complete, valid message is ready in the buffer. Malformed input automatically resets the parser to the sync state.
The streaming parser uses static state and is NOT thread-safe. It must only be called from a single context (e.g., the UART receive handler or the main loop, but not both).
Conversion Functions
void OpenLcbGridConnect_to_can_msg(gridconnect_buffer_t *buffer, can_msg_t *can_msg);
void OpenLcbGridConnect_from_can_msg(gridconnect_buffer_t *buffer, can_msg_t *can_msg);
to_can_msg(): Converts a validated GridConnect string (from the parser) to acan_msg_t. No additional format validation is performed.from_can_msg(): Converts acan_msg_tto a null-terminated GridConnect string. Output uses uppercase hex with zero-padded 8-character identifier.
7.9 Configuration Memory Buffer Operations
A parallel set of insert/extract functions operates on configuration_memory_buffer_t (64-byte) buffers used by the datagram protocol for configuration memory read/write operations:
node_id_t OpenLcbUtilities_extract_node_id_from_config_mem_buffer(buffer, index);
uint16_t OpenLcbUtilities_extract_word_from_config_mem_buffer(buffer, index);
void OpenLcbUtilities_copy_node_id_to_config_mem_buffer(buffer, node_id, index);
void OpenLcbUtilities_copy_event_id_to_config_mem_buffer(buffer, event_id, index);
event_id_t OpenLcbUtilities_copy_config_mem_buffer_to_event_id(buffer, index);
These are used by the configuration memory protocol handler when reading and writing node configuration data through address spaces.
7.10 Emergency Event Detection
bool OpenLcbUtilities_is_emergency_event(event_id_t event_id);
Returns true if the event ID matches one of the four well-known emergency events: Emergency Off, Clear Emergency Off, Emergency Stop, or Clear Emergency Stop. Used by the train protocol to handle global emergency events.