Examples

Controller Example

Send commands via serial interface and request data from peripherals. The examples in examples/arduino/ and examples/platformio/ show both controller and peripheral sketches using the C core API (see crumbs_arduino.h).

Usage

  1. Upload to Arduino
  2. Open Serial Monitor (115200 baud)
  3. Send: address,type_id,command_type,byte0,byte1,... (comma-separated bytes)
  4. Request: request=address

Example Commands

8,1,1,0,0,128,63,0,0,0,0     // Send to address 8 (first 4 bytes = 1.0f in little-endian)
request=8                    // Request data from address 8

Peripheral Example

Respond to controller commands and data requests. The C core uses callbacks with signatures on_message(ctx, const crumbs_message_t*) and on_request(ctx, crumbs_message_t*).

Key Code Patterns (C API β€” Arduino HAL)

// Message callback from the core
void on_message(crumbs_context_t *ctx, const crumbs_message_t *m) {
    switch (m->command_type) {
        case 0: // Data request (handled in on_request instead)
            break;
        case 1: // Set parameters
            // read bytes from m->data[0..m->data_len-1] and apply
            // example: decode a float from bytes
            if (m->data_len >= sizeof(float)) {
                float val;
                memcpy(&val, m->data, sizeof(float));
            }
            break;
    }
}

// Build reply for onRequest β€” the framework will encode and Wire.write the result
void on_request(crumbs_context_t *ctx, crumbs_message_t *reply) {
    reply->type_id = 1;
    reply->command_type = 0;
    float val = 42.0f;
    reply->data_len = sizeof(float);
    memcpy(reply->data, &val, sizeof(float));
}

void setup() {
    crumbs_arduino_init_peripheral(&ctx, 0x08);
    crumbs_set_callbacks(&ctx, on_message, on_request, NULL);
}

Handler-Based Peripheral (Alternative Pattern)

Instead of using a switch statement inside on_message, you can register individual handler functions for each command type. This pattern is cleaner when you have many commands.

Arduino Peripheral with Handlers

#include <crumbs.h>
#include <crumbs_arduino.h>

#define CMD_LED_ON  0x01
#define CMD_LED_OFF 0x02
#define CMD_BLINK   0x03

static crumbs_context_t ctx;

void handleLedOn(crumbs_context_t *ctx, uint8_t cmd,
                 const uint8_t *data, uint8_t len, void *user) {
    digitalWrite(LED_BUILTIN, HIGH);
}

void handleLedOff(crumbs_context_t *ctx, uint8_t cmd,
                  const uint8_t *data, uint8_t len, void *user) {
    digitalWrite(LED_BUILTIN, LOW);
}

void handleBlink(crumbs_context_t *ctx, uint8_t cmd,
                 const uint8_t *data, uint8_t len, void *user) {
    if (len < 2) return;
    uint8_t count = data[0];
    uint8_t delayMs = data[1] * 10;
    for (uint8_t i = 0; i < count; i++) {
        digitalWrite(LED_BUILTIN, HIGH);
        delay(delayMs);
        digitalWrite(LED_BUILTIN, LOW);
        delay(delayMs);
    }
}

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);
    crumbs_arduino_init_peripheral(&ctx, 0x08);

    // Register per-command handlers
    crumbs_register_handler(&ctx, CMD_LED_ON,  handleLedOn,  NULL);
    crumbs_register_handler(&ctx, CMD_LED_OFF, handleLedOff, NULL);
    crumbs_register_handler(&ctx, CMD_BLINK,   handleBlink,  NULL);
}

void loop() { }

Linux Controller for Handler Peripheral

// Send LED ON command
crumbs_message_t msg = {0};
msg.type_id = 1;
msg.command_type = 0x01;  // CMD_LED_ON
msg.data_len = 0;
crumbs_controller_send(&ctx, 0x08, &msg, crumbs_linux_i2c_write, &lw);

// Send BLINK command: 5 blinks, 200ms delay
msg.command_type = 0x03;  // CMD_BLINK
msg.data_len = 2;
msg.data[0] = 5;   // count
msg.data[1] = 20;  // delay = 20 * 10ms = 200ms
crumbs_controller_send(&ctx, 0x08, &msg, crumbs_linux_i2c_write, &lw);

See examples/arduino/handler_peripheral_led/ and examples/linux/multi_handler_controller/ for complete working examples.

Message Helpers Pattern

The crumbs_msg.h header provides type-safe payload building and reading. This pattern is cleaner than manual byte manipulation:

Controller with Message Helpers

#include <crumbs_msg.h>

// Define command header (see examples/common/ for full pattern)
#define SERVO_TYPE_ID    0x02
#define SERVO_CMD_ANGLE  0x01

crumbs_message_t msg;
crumbs_msg_init(&msg);
msg.type_id = SERVO_TYPE_ID;
msg.command_type = SERVO_CMD_ANGLE;
crumbs_msg_add_u8(&msg, servo_index);    // Which servo
crumbs_msg_add_u16(&msg, 1500);          // Pulse width in ΞΌs

crumbs_controller_send(&ctx, 0x10, &msg, write_fn, write_ctx);

Peripheral Handler with Message Readers

void handle_servo_angle(crumbs_context_t *ctx, uint8_t cmd,
                        const uint8_t *data, uint8_t len, void *user) {
    size_t off = 0;
    uint8_t index;
    uint16_t pulse;

    if (crumbs_msg_read_u8(data, len, &off, &index) < 0) return;
    if (crumbs_msg_read_u16(data, len, &off, &pulse) < 0) return;

    servo_set_pulse(index, pulse);
}

void setup() {
    crumbs_arduino_init_peripheral(&ctx, 0x10);
    crumbs_register_handler(&ctx, SERVO_CMD_ANGLE, handle_servo_angle, NULL);
}

See the handler examples for complete working code:

  • examples/arduino/handler_peripheral_led/ β€” LED strip control with RGB values
  • examples/arduino/handler_peripheral_servo/ β€” Servo control with pulse widths
  • examples/linux/multi_handler_controller/ β€” Linux controller using multiple device command headers

See Message Helpers for complete API documentation.

Common Patterns

Device Proxy Pattern (Controller-Side)

When sending many commands to the same device, you can bundle the context, address, and I/O function into a struct to reduce repetition:

// Bundle device parameters (user-side pattern, not library API)
typedef struct {
    crumbs_context_t *ctx;
    uint8_t addr;
    crumbs_i2c_write_fn write_fn;
    void *io;
} led_device_t;

// Initialize once
led_device_t led = { &ctx, 0x08, crumbs_arduino_wire_write, NULL };

// Shorter sender wrapper
static inline int led_set_all(led_device_t *dev, uint8_t bitmask) {
    crumbs_message_t msg;
    crumbs_msg_init(&msg);
    msg.type_id = LED_TYPE_ID;
    msg.command_type = LED_CMD_SET_ALL;
    crumbs_msg_add_u8(&msg, bitmask);
    return crumbs_controller_send(dev->ctx, dev->addr, &msg, dev->write_fn, dev->io);
}

// Clean usage
led_set_all(&led, 0x0F);

This is an optional convenience pattern. The library’s explicit parameter passing remains the primitive for maximum flexibility.

Multiple Devices

uint8_t addresses[] = {0x08, 0x09, 0x0A};
for (int i = 0; i < 3; i++) {
    crumbs_controller_send(&ctx, addresses[i], &msg, crumbs_arduino_wire_write, NULL);
    delay(10);
}

Data Requests

On Arduino, use Wire.requestFrom() and the core API helper to decode; e.g. read bytes and call crumbs_decode_message().

On Linux, the HAL helper crumbs_linux_read_message() wraps the low-level reads and decoding for you.

CRC Validation

Use crumbs_decode_message(buffer, bytesRead, &out, ctx) which returns 0 on success, -1 if too small and -2 on CRC mismatch.

I2C Scanning

for (uint8_t addr = 8; addr < 120; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
        Serial.print("Found device at 0x");
        Serial.println(addr, HEX);
    }
}

For protocol-aware discovery (find devices that actually speak CRUMBS) use the core helper crumbs_controller_scan_for_crumbs() which performs a read-and-decode probe. See the Getting Started guide for short Arduino and Linux examples.

There is a minimal native C example that demonstrates using CRUMBS as a compiled library and linking a small program against the crumbs static target. The example is located at examples/linux/simple_native_controller.

Build the example in-tree (recommended for local development):

cmake -S examples/linux/simple_native_controller -B examples/linux/simple_native_controller/build -DCRUMBS_BUILD_IN_TREE=ON
cmake --build examples/linux/simple_native_controller/build --config Release
./examples/linux/simple_native_controller/build/crumbs_native_example

If you have installed CRUMBS (and supplied CMake config files to your install prefix) you can instead build the example against an installed package by turning off CRUMBS_BUILD_IN_TREE and setting CMAKE_PREFIX_PATH accordingly.

When CRUMBS is installed and its CMake package files are available, out-of-tree builds can use find_package:

find_package(crumbs CONFIG REQUIRED)
add_executable(myprog main.c)
target_link_libraries(myprog PRIVATE crumbs::crumbs)

PlatformIO examples (Arduino Nano)

There are several PlatformIO examples that target the Arduino Nano:

  • examples/platformio/simple_controller/ β€” controller sketch that reads serial CSV commands and sends them to a peripheral.
  • examples/platformio/simple_peripheral/ β€” peripheral sketch (address 0x08) that prints received commands and returns sample replies.
  • examples/platformio/handler_peripheral_led/ β€” LED peripheral using handler dispatch pattern.
  • examples/platformio/handler_peripheral_servo/ β€” servo peripheral using handler dispatch pattern.

Build with PlatformIO CLI inside the example directories:

cd examples/platformio/simple_controller
pio run -e nanoatmega328new

cd ../simple_peripheral
pio run -e nanoatmega328new