API Reference
This document provides the complete CRUMBS C API reference for users implementing controllers and peripherals. All public functions, types, constants, and helper utilities are documented here.
Overview
The CRUMBS API is organized into:
- Core API: Message encoding/decoding, context management, callbacks
- Handler Dispatch: Per-command function registration for peripherals
- Message Helpers: Type-safe payload builders and readers
- Platform HAL: Arduino and Linux adapter functions
- Discovery: Bus scanning for CRUMBS-compatible devices
Key Types and Constants
Message Structure
typedef struct {
uint8_t address; // Device address (not serialized, for context only)
uint8_t type_id; // Device/module type identifier
uint8_t opcode; // Command/query opcode
uint8_t data_len; // Payload length (0ā27)
uint8_t data[27]; // Payload buffer
uint8_t crc8; // CRC-8 checksum over serialized frame
} crumbs_message_t;
Serialized frame format (4ā31 bytes):
[type_id:1][opcode:1][data_len:1][data:0ā27][crc8:1]
CRC is computed over: type_id + opcode + data_len + data[0..data_len-1]
Context Structure
typedef struct crumbs_context_t {
crumbs_role_t role; // CONTROLLER or PERIPHERAL
uint8_t address; // Device I²C address (peripherals only)
// CRC statistics
uint32_t crc_error_count; // Cumulative CRC failures
int last_crc_ok; // 1 if last decode succeeded, 0 otherwise
// Callbacks
crumbs_message_cb_t on_message; // General message callback
crumbs_request_cb_t on_request; // Peripheral request callback (for reads)
void *user_data; // Opaque user pointer
// Handler dispatch table (if enabled)
// ... internal fields ...
} crumbs_context_t;
Constants
#define CRUMBS_MAX_PAYLOAD 27 // Maximum payload bytes
#define CRUMBS_MESSAGE_MAX_SIZE 31 // Maximum serialized frame size
#define CRUMBS_VERSION 1003 // Library version (1003 = v0.10.3, formula: major*10000 + minor*100 + patch)
Callback Signatures
// Message callback (invoked on all received messages)
typedef void (*crumbs_message_cb_t)(
crumbs_context_t *ctx,
const crumbs_message_t *msg);
// Request callback (invoked when peripheral receives I²C read request)
typedef void (*crumbs_request_cb_t)(
crumbs_context_t *ctx,
crumbs_message_t *reply);
// Command handler (per-opcode dispatch)
typedef void (*crumbs_handler_fn)(
crumbs_context_t *ctx,
uint8_t opcode,
const uint8_t *data,
uint8_t data_len,
void *user_data);
Core API
Context Initialization
void crumbs_init(crumbs_context_t *ctx, crumbs_role_t role, uint8_t address);
Initialize a CRUMBS context. Must be called before any other operations.
Parameters:
ctxā Context to initializeroleāCRUMBS_ROLE_CONTROLLERorCRUMBS_ROLE_PERIPHERALaddressā Device I²C address (only used for peripherals; controllers can use 0x00)
Callback Registration
void crumbs_set_callbacks(crumbs_context_t *ctx,
crumbs_message_cb_t on_message,
crumbs_request_cb_t on_request,
void *user_data);
Install callbacks for message handling.
Parameters:
on_messageā Invoked when a message is received (both roles). May be NULL.on_requestā Invoked when peripheral receives an I²C read request. May be NULL.user_dataā Opaque pointer passed to callbacks.
Callback Execution Order:
- Message decoded and CRC validated
on_messagecallback invoked (if registered)- Handler dispatch (if registered for this opcode)
Encoding and Decoding
size_t crumbs_encode_message(const crumbs_message_t *msg,
uint8_t *buffer,
size_t buffer_len);
Encode a message into a wire-format frame.
Returns:
- Encoded frame length (4 + data_len) on success
0on failure (buffer too small or data_len > 27)
int crumbs_decode_message(const uint8_t *buffer,
size_t buffer_len,
crumbs_message_t *msg,
crumbs_context_t *ctx);
Decode a wire-format frame into a message structure.
Parameters:
ctxā Optional context for CRC statistics (may be NULL)
Returns:
0ā Success-1ā Invalid frame (too short, bad data_len, truncated)-2ā CRC mismatch
If ctx is non-NULL, CRC statistics are updated.
Controller Operations
int crumbs_controller_send(const crumbs_context_t *ctx,
uint8_t target_addr,
const crumbs_message_t *msg,
crumbs_i2c_write_fn write_fn,
void *write_ctx);
Send a message from controller to a peripheral device.
Returns:
0ā Success-1ā Invalid arguments (NULL ctx/msg/write_fn)-2ā Context not in controller role-3ā Encode failed (data_len > 27 or buffer issue)>0ā I2C write error (platform-specific, from write_fn)
Common issues:
- Return
-3: Checkmsg->data_len⤠27 - Return
>0: I²C bus error - check wiring, pull-ups, address - No response from peripheral: Add 10ms delay before reading
Example:
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x10); // type=LED, opcode=query
int rc = crumbs_controller_send(&ctx, 0x08, &msg,
crumbs_arduino_wire_write, NULL);
if (rc != 0) {
Serial.print("Send failed: ");
Serial.println(rc); // Debug error code
}
Peripheral Operations
int crumbs_peripheral_handle_receive(crumbs_context_t *ctx,
const uint8_t *buffer,
size_t len);
Process incoming data on a peripheral device. Decodes the message, validates CRC, and invokes callbacks/handlers.
Returns: 0=success, -1=invalid/decode fail, -2=CRC error (check wiring, use crumbs_get_crc_error_count())
Called from Wire onReceive() on Arduino.
int crumbs_peripheral_build_reply(crumbs_context_t *ctx,
uint8_t *out_buf,
size_t out_buf_len,
size_t *out_len);
Build a reply frame for an I²C read request. Invokes the on_request callback to populate the reply.
Returns:
0ā Success (out_len set to frame size, or 0 if no reply)-1ā Invalid arguments or not peripheral role-2ā Encode failed
Typically called from Wire onRequest() callback on Arduino.
CRC Statistics
uint32_t crumbs_get_crc_error_count(const crumbs_context_t *ctx);
int crumbs_last_crc_ok(const crumbs_context_t *ctx);
void crumbs_reset_crc_stats(crumbs_context_t *ctx);
Access CRC validation statistics. Use these for diagnostics or to detect noisy I²C bus conditions.
ABI Compatibility Check
size_t crumbs_context_size(void);
Returns the size of crumbs_context_t as compiled into the library. Use this to detect mismatches when CRUMBS_MAX_HANDLERS differs between library and application.
When to use: Precompiled libraries (PlatformIO lib_deps)
Example:
void setup() {
if (sizeof(crumbs_context_t) != crumbs_context_size()) {
Serial.println("ERROR: CRUMBS_MAX_HANDLERS mismatch!");
while(1); // Halt
}
}
Fix: Set CRUMBS_MAX_HANDLERS via build_flags in platformio.ini.
Handler Dispatch
The handler dispatch system provides per-opcode function registration for structured command processing.
Handler Registration
int crumbs_register_handler(crumbs_context_t *ctx,
uint8_t opcode,
crumbs_handler_fn fn,
void *user_data);
Register a handler function for a specific opcode.
Returns:
0ā Success-1ā NULL context or handler table full
Example:
crumbs_register_handler(&ctx, 0x01, handle_led_set, &led_state);
crumbs_register_handler(&ctx, 0x10, handle_led_query, NULL);
int crumbs_unregister_handler(crumbs_context_t *ctx, uint8_t opcode);
Remove a handler for a specific opcode. Always returns 0.
Alternatively, call crumbs_register_handler() with fn = NULL.
Handler Function Signature
void my_handler(crumbs_context_t *ctx,
uint8_t opcode,
const uint8_t *data,
uint8_t data_len,
void *user_data) {
// Process command...
}
Parameters:
ctxā The CRUMBS contextopcodeā The command that triggered this handlerdataā Payload bytes (may be NULL if data_len == 0)data_lenā Payload length (0ā27)user_dataā Opaque pointer registered with this handler
Handler Dispatch Flow
When a message arrives at a peripheral:
- Message decoded and CRC validated
on_messagecallback invoked (if registered)- Handler dispatch searches for matching opcode
- Handler invoked (if found)
Both mechanisms can coexist ā use on_message for logging, handlers for command logic.
Memory Configuration
Handler dispatch uses CRUMBS_MAX_HANDLERS slots (default: 16).
Adjust handler table size:
# platformio.ini
build_flags = -DCRUMBS_MAX_HANDLERS=8
Disable handler dispatch:
build_flags = -DCRUMBS_MAX_HANDLERS=0
Important: Always set via
build_flags. Defining in sketch before#includedoes not work on Arduino/PlatformIO due to separate library compilation.
Memory usage:
| Max Handlers | AVR (2-byte ptr) | 32-bit (4-byte ptr) |
|---|---|---|
| 16 | ~68 bytes | ~132 bytes |
| 8 | ~36 bytes | ~68 bytes |
| 4 | ~21 bytes | ~37 bytes |
| 0 | 0 bytes | 0 bytes |
Message Helpers
The crumbs_message_helpers.h header provides zero-overhead inline helpers for building and reading message payloads. These eliminate manual byte manipulation.
Include:
#include "crumbs_message_helpers.h" // Linux/CMake
#include <crumbs_message_helpers.h> // Arduino
Message Builder
Initialization
void crumbs_msg_init(crumbs_message_t *msg, uint8_t type_id, uint8_t opcode);
Initialize a message with type and opcode. Sets data_len = 0 and zeros all fields.
Adding Values
All add functions return 0 on success, -1 if the value would exceed the 27-byte payload limit.
int crumbs_msg_add_u8(crumbs_message_t *msg, uint8_t value);
int crumbs_msg_add_u16(crumbs_message_t *msg, uint16_t value); // little-endian
int crumbs_msg_add_u32(crumbs_message_t *msg, uint32_t value); // little-endian
int crumbs_msg_add_i8(crumbs_message_t *msg, int8_t value);
int crumbs_msg_add_i16(crumbs_message_t *msg, int16_t value); // little-endian
int crumbs_msg_add_i32(crumbs_message_t *msg, int32_t value); // little-endian
int crumbs_msg_add_float(crumbs_message_t *msg, float value); // native byte order
int crumbs_msg_add_bytes(crumbs_message_t *msg, const void *data, uint8_t len);
Encoding notes:
- Multi-byte integers use little-endian (LSB first)
- Floats use native byte order (memcpy)
Example:
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x02, 0x02); // type=Servo, opcode=SetBoth
crumbs_msg_add_u16(&msg, 1500); // Servo 1
crumbs_msg_add_u16(&msg, 2000); // Servo 2
Message Reader
All read functions return 0 on success, -1 if reading would exceed buffer bounds.
int crumbs_msg_read_u8(const uint8_t *data, uint8_t len, uint8_t offset, uint8_t *out);
int crumbs_msg_read_u16(const uint8_t *data, uint8_t len, uint8_t offset, uint16_t *out);
int crumbs_msg_read_u32(const uint8_t *data, uint8_t len, uint8_t offset, uint32_t *out);
int crumbs_msg_read_i8(const uint8_t *data, uint8_t len, uint8_t offset, int8_t *out);
int crumbs_msg_read_i16(const uint8_t *data, uint8_t len, uint8_t offset, int16_t *out);
int crumbs_msg_read_i32(const uint8_t *data, uint8_t len, uint8_t offset, int32_t *out);
int crumbs_msg_read_float(const uint8_t *data, uint8_t len, uint8_t offset, float *out);
int crumbs_msg_read_bytes(const uint8_t *data, uint8_t len, uint8_t offset, void *out, uint8_t count);
Example (peripheral handler):
void handle_servo(crumbs_context_t *ctx, uint8_t cmd,
const uint8_t *data, uint8_t len, void *user) {
uint8_t channel;
uint16_t pulse_us;
if (crumbs_msg_read_u8(data, len, 0, &channel) < 0) return;
if (crumbs_msg_read_u16(data, len, 1, &pulse_us) < 0) return;
set_servo(channel, pulse_us);
}
Command Header Pattern
For reusable command definitions, create a shared header file. See examples/handlers_usage/mock_ops.h for a complete example.
Pattern:
// my_device_commands.h
#ifndef MY_DEVICE_COMMANDS_H
#define MY_DEVICE_COMMANDS_H
#include "crumbs.h"
#include "crumbs_message_helpers.h"
#define MY_TYPE_ID 0x10
#define MY_CMD_ACTION_A 0x01
#define MY_CMD_ACTION_B 0x02
static inline int my_send_action_a(
crumbs_context_t *ctx, uint8_t addr,
uint16_t param1, uint8_t param2,
crumbs_i2c_write_fn write_fn, void *write_ctx) {
crumbs_message_t msg;
crumbs_msg_init(&msg, MY_TYPE_ID, MY_CMD_ACTION_A);
crumbs_msg_add_u16(&msg, param1);
crumbs_msg_add_u8(&msg, param2);
return crumbs_controller_send(ctx, addr, &msg, write_fn, write_ctx);
}
#endif
Benefits:
- Type-safe: Function parameters enforce correct types
- Self-documenting: Command names and parameters are explicit
- Reusable: Same header works on controller and peripheral
- Composable: Include multiple device headers in one controller
Platform HAL: Arduino
Initialization
void crumbs_arduino_init_controller(crumbs_context_t *ctx);
void crumbs_arduino_init_peripheral(crumbs_context_t *ctx, uint8_t address);
Initialize context and register Wire callbacks. Uses default Wire instance.
Example (controller):
crumbs_context_t ctx;
crumbs_arduino_init_controller(&ctx);
Example (peripheral):
#define I2C_ADDRESS 0x08
crumbs_context_t ctx;
crumbs_arduino_init_peripheral(&ctx, I2C_ADDRESS);
crumbs_set_callbacks(&ctx, NULL, on_request, NULL);
I²C Write Function
int crumbs_arduino_wire_write(void *user_ctx, uint8_t addr,
const uint8_t *data, size_t len);
Wire-based write function for crumbs_controller_send().
Returns:
0ā Success>0ā Wire error code (fromendTransmission())
Example:
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x01);
crumbs_controller_send(&ctx, 0x08, &msg, crumbs_arduino_wire_write, NULL);
I²C Read Function
int crumbs_arduino_read(void *user_ctx, uint8_t addr,
uint8_t *buffer, size_t len, uint32_t timeout_us);
Wire-based read function for scanner and diagnostics.
Returns:
- Number of bytes read (0ā31)
- Negative values on error
Bus Scanner
int crumbs_arduino_scan(TwoWire *wire, uint8_t start_addr, uint8_t end_addr,
int strict, uint8_t *found, size_t max_found);
Scan I²C bus for CRUMBS-compatible devices.
Parameters:
strictā Non-zero: requires valid CRUMBS frame. Zero: ACK-based detection.foundā Output array for discovered addressesmax_foundā Size of found array
Returns: Number of devices found
Example:
uint8_t devices[10];
int count = crumbs_arduino_scan(&Wire, 0x08, 0x77, 1, devices, 10);
Platform HAL: Linux
Note: Linux HAL supports controller mode only. Peripheral mode (I²C target) is not yet implemented.
Initialization
int crumbs_linux_init_controller(crumbs_context_t *ctx,
crumbs_linux_i2c_t *i2c,
const char *device_path,
uint32_t timeout_us);
Initialize controller context and open I²C bus device.
Parameters:
device_pathā e.g.,"/dev/i2c-1"timeout_usā Read timeout in microseconds
Returns:
0ā Success-1ā Invalid arguments-2ā Failed to open device
Example:
crumbs_context_t ctx;
crumbs_linux_i2c_t bus;
int rc = crumbs_linux_init_controller(&ctx, &bus, "/dev/i2c-1", 10000);
Cleanup
void crumbs_linux_close(crumbs_linux_i2c_t *i2c);
Close I²C bus file descriptor.
I²C Write Function
int crumbs_linux_i2c_write(void *user_ctx, uint8_t target_addr,
const uint8_t *data, size_t len);
Linux I²C write function for crumbs_controller_send().
Returns:
0ā Success-1ā Invalid arguments or bus not open-2ā Failed to select slave address-3ā Write I/O error-4ā Incomplete write
I²C Read Function
int crumbs_linux_read_message(crumbs_linux_i2c_t *i2c, uint8_t target_addr,
crumbs_context_t *ctx, crumbs_message_t *out_msg);
Read and decode a CRUMBS message from a peripheral.
Returns:
0ā Success-1ā Invalid arguments or bus not open-2ā Failed to select slave address-3ā Read I/O error-4ā No bytes read-1/-2ā Decode/CRC error (from decode)
Discovery and Scanning
Core Scanner
int crumbs_controller_scan_for_crumbs(
const crumbs_context_t *ctx,
uint8_t start_addr,
uint8_t end_addr,
int strict,
crumbs_i2c_write_fn write_fn,
crumbs_i2c_read_fn read_fn,
void *io_ctx,
uint8_t *found,
size_t max_found,
uint32_t timeout_us);
Platform-independent bus scanner for CRUMBS-compatible devices.
Parameters:
start_addr/end_addrā Inclusive probe range (0x08ā0x77 typical)strictā Non-zero: request data-phase read (strong detection). Zero: ACK probe only.write_fn/read_fnā Platform primitivesfoundā Output array for discovered addressesmax_foundā Size of found arraytimeout_usā Read timeout per device
Returns: Number of devices found, or negative on error
Example (Arduino):
uint8_t devices[10];
int count = crumbs_controller_scan_for_crumbs(
&ctx, 0x08, 0x77, 1,
crumbs_arduino_wire_write, crumbs_arduino_read, NULL,
devices, 10, 10000);
Return Values and Error Codes
All CRUMBS functions use consistent conventions:
- Success:
0(or positive value where documented) - Failure: Negative values indicate specific error conditions
Core Functions
| Function | Success | Error |
|---|---|---|
crumbs_encode_message() |
>0 (frame length) |
0 (buffer too small) |
crumbs_decode_message() |
0 |
-1 (frame error), -2 (CRC mismatch) |
crumbs_controller_send() |
0 |
-1 (args), -2 (role), -3 (encode), >0 (I2C error) |
crumbs_peripheral_handle_receive() |
0 |
-1 (args/decode), -2 (CRC) |
crumbs_peripheral_build_reply() |
0 |
-1 (args/role), -2 (encode) |
crumbs_register_handler() |
0 |
-1 (NULL ctx or table full) |
crumbs_unregister_handler() |
0 |
Never fails |
Arduino HAL
| Function | Success | Error |
|---|---|---|
crumbs_arduino_wire_write() |
0 |
>0 (Wire error code) |
Linux HAL
| Function | Success | Error |
|---|---|---|
crumbs_linux_init_controller() |
0 |
-1 (args), -2 (open failed) |
crumbs_linux_i2c_write() |
0 |
-1 (args), -2 (select), -3 (I/O), -4 (incomplete) |
crumbs_linux_read_message() |
0 |
-1 (args), -2 (select), -3 (I/O), -4 (no data), decode errors |
Complete Examples
Peripheral with Handler Dispatch
#include <crumbs.h>
#include <crumbs_arduino.h>
#include <crumbs_message_helpers.h>
#define I2C_ADDRESS 0x08
static crumbs_context_t ctx;
static uint8_t led_state = 0;
void handle_set_all(crumbs_context_t *c, uint8_t cmd,
const uint8_t *data, uint8_t len, void *user) {
uint8_t bitmask;
if (crumbs_msg_read_u8(data, len, 0, &bitmask) == 0) {
led_state = bitmask;
}
}
void handle_set_one(crumbs_context_t *c, uint8_t cmd,
const uint8_t *data, uint8_t len, void *user) {
uint8_t index, state;
if (crumbs_msg_read_u8(data, len, 0, &index) == 0 &&
crumbs_msg_read_u8(data, len, 1, &state) == 0) {
if (state) led_state |= (1 << index);
else led_state &= ~(1 << index);
}
}
void on_request(crumbs_context_t *ctx, crumbs_message_t *reply) {
if (ctx->requested_opcode == 0x10) { // Query state
crumbs_msg_init(reply, 0x01, 0x10);
crumbs_msg_add_u8(reply, led_state);
}
}
void setup() {
crumbs_arduino_init_peripheral(&ctx, I2C_ADDRESS);
crumbs_register_handler(&ctx, 0x01, handle_set_all, NULL);
crumbs_register_handler(&ctx, 0x02, handle_set_one, NULL);
crumbs_set_callbacks(&ctx, NULL, on_request, NULL);
}
void loop() {}
Controller with Message Helpers
#include <crumbs.h>
#include <crumbs_arduino.h>
#include <crumbs_message_helpers.h>
static crumbs_context_t ctx;
void setup() {
Serial.begin(9600);
crumbs_arduino_init_controller(&ctx);
}
void loop() {
// Send LED command
crumbs_message_t msg;
crumbs_msg_init(&msg, 0x01, 0x02); // type=LED, opcode=set_one
crumbs_msg_add_u8(&msg, 0); // LED index 0
crumbs_msg_add_u8(&msg, 1); // State ON
int rc = crumbs_controller_send(&ctx, 0x08, &msg,
crumbs_arduino_wire_write, NULL);
if (rc != 0) {
Serial.print("Send failed: ");
Serial.println(rc);
}
delay(1000);
}
See Also
- Protocol Specification ā Wire format, versioning, CRC-8
- Platform Setup ā Installation and configuration
- Examples ā Complete working examples
- Source headers:
src/crumbs.h,src/crumbs_message_helpers.h,src/crumbs_arduino.h,src/crumbs_linux.h