Handler Dispatch Guide

This guide covers the CRUMBS handler dispatch system β€” an alternative to switch statements for processing incoming commands on peripheral devices.

Overview

Instead of writing large switch statements in your on_message callback, you can register per-command handler functions. CRUMBS will automatically dispatch to the correct handler when a message arrives.

Benefits:

  • Cleaner code organization (one function per command)
  • Easy to add/remove commands without modifying a central switch
  • Supports per-handler user data
  • Memory-efficient on AVR when tuned with CRUMBS_MAX_HANDLERS

Handler Function Signature

All handlers use this signature:

typedef void (*crumbs_handler_fn)(
    crumbs_context_t *ctx,      // The CRUMBS context
    uint8_t command_type,       // The command that triggered this handler
    const uint8_t *data,        // Payload bytes (may be NULL if len==0)
    uint8_t data_len,           // Payload length (0–27)
    void *user_data             // Opaque pointer registered with handler
);

Registering Handlers

Use crumbs_register_handler() to associate a command with a function:

int crumbs_register_handler(crumbs_context_t *ctx,
                            uint8_t command_type,
                            crumbs_handler_fn fn,
                            void *user_data);

Returns: 0 on success, -1 if context is NULL or handler table is full.

Example:

crumbs_register_handler(&ctx, 0x01, handle_led_set, NULL);
crumbs_register_handler(&ctx, 0x02, handle_led_blink, NULL);
crumbs_register_handler(&ctx, 0x10, handle_led_query, &led_state);

Unregistering Handlers

Remove a handler with:

int crumbs_unregister_handler(crumbs_context_t *ctx, uint8_t command_type);

Or call crumbs_register_handler() with fn = NULL.

Complete Peripheral Example

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

#define I2C_ADDRESS 0x08

static crumbs_context_t ctx;
static uint8_t led_state = 0;

// Handler: Set all LEDs via bitmask
void handle_set_all(crumbs_context_t *c, uint8_t cmd,
                    const uint8_t *data, uint8_t len, void *user) {
    (void)c; (void)cmd; (void)user;

    uint8_t bitmask;
    size_t offset = 0;
    if (crumbs_msg_read_u8(data, len, &offset, &bitmask) == 0) {
        led_state = bitmask;
        // Apply to hardware...
    }
}

// Handler: Set single LED
void handle_set_one(crumbs_context_t *c, uint8_t cmd,
                    const uint8_t *data, uint8_t len, void *user) {
    (void)c; (void)cmd; (void)user;

    size_t offset = 0;
    uint8_t index, state;
    if (crumbs_msg_read_u8(data, len, &offset, &index) == 0 &&
        crumbs_msg_read_u8(data, len, &offset, &state) == 0) {
        if (state) led_state |= (1 << index);
        else led_state &= ~(1 << index);
        // Apply to hardware...
    }
}

// Handler: Respond to state query
void on_request(crumbs_context_t *ctx, crumbs_message_t *reply) {
    reply->type_id = 0x01;
    reply->command_type = 0x10;  // GET_STATE response
    crumbs_msg_init(reply);
    crumbs_msg_add_u8(reply, led_state);
}

void setup() {
    crumbs_arduino_init_peripheral(&ctx, I2C_ADDRESS);

    // Register handlers (no on_message callback needed)
    crumbs_register_handler(&ctx, 0x01, handle_set_all, NULL);
    crumbs_register_handler(&ctx, 0x02, handle_set_one, NULL);

    // Register request callback for I2C reads
    crumbs_set_callbacks(&ctx, NULL, on_request, NULL);
}

void loop() {
    // Wire callbacks handle everything
}

Using Message Helpers

The crumbs_msg.h helpers provide type-safe payload parsing:

// Reading values (peripheral handler)
size_t offset = 0;
uint8_t channel;
uint16_t value;

if (crumbs_msg_read_u8(data, len, &offset, &channel) < 0) return;
if (crumbs_msg_read_u16(data, len, &offset, &value) < 0) return;
// offset now points past the read bytes

Available read functions:

Function Type Size
crumbs_msg_read_u8() uint8_t 1 byte
crumbs_msg_read_u16() uint16_t 2 bytes
crumbs_msg_read_u32() uint32_t 4 bytes
crumbs_msg_read_i8() int8_t 1 byte
crumbs_msg_read_i16() int16_t 2 bytes
crumbs_msg_read_i32() int32_t 4 bytes
crumbs_msg_read_float() float 4 bytes
crumbs_msg_read_bytes() raw n bytes

All functions return 0 on success, -1 on bounds overflow.

Command Header Pattern

For reusable command definitions, create a shared header:

// led_commands.h β€” shared by controller and peripheral

#define LED_TYPE_ID       0x01

#define LED_CMD_SET_ALL   0x01  // Payload: [bitmask:u8]
#define LED_CMD_SET_ONE   0x02  // Payload: [index:u8, state:u8]
#define LED_CMD_BLINK     0x03  // Payload: [count:u8, delay_10ms:u8]
#define LED_CMD_GET_STATE 0x10  // Payload: none (reply has state)

See examples/common/led_commands.h and examples/common/servo_commands.h for complete examples with controller-side sender functions.

Memory Optimization

The handler table uses CRUMBS_MAX_HANDLERS slots (default: 16). On AVR, each slot uses ~4 bytes.

For memory-constrained devices:

# platformio.ini
build_flags = -DCRUMBS_MAX_HANDLERS=8

Important: Define CRUMBS_MAX_HANDLERS in build flags, not in your sketch. Arduino precompiles the library separately, so sketch-level defines won’t affect it.

To disable handler dispatch entirely (use callbacks only):

build_flags = -DCRUMBS_MAX_HANDLERS=0

Dispatch Order

When a message arrives:

  1. on_message callback is invoked first (if registered)
  2. Handler dispatch searches for a matching command_type
  3. If found, the handler is invoked with payload data

Both mechanisms can coexist β€” use on_message for logging/validation, handlers for command processing.

Error Handling

Handlers should validate payload length before reading:

void handle_servo(crumbs_context_t *c, uint8_t cmd,
                  const uint8_t *data, uint8_t len, void *user) {
    size_t offset = 0;
    uint8_t channel, angle;

    // These return -1 if there aren't enough bytes
    if (crumbs_msg_read_u8(data, len, &offset, &channel) < 0) {
        // Log error, return early
        return;
    }
    if (crumbs_msg_read_u8(data, len, &offset, &angle) < 0) {
        return;
    }

    // Process valid data
    set_servo(channel, angle);
}

See Also