Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/nullclaw/nullclaw/llms.txt

Use this file to discover all available pages before exploring further.

NullClaw provides native hardware peripheral support for embedded boards, enabling AI-powered control of GPIO pins, sensors, actuators, and microcontroller flashing.

Overview

NullClaw’s hardware architecture:

Arduino

Serial JSON protocol over USB

Raspberry Pi

Direct GPIO access via sysfs

STM32/Nucleo

Flash & debug via probe-rs

Supported Hardware

Arduino Boards

  • Serial: /dev/ttyACM0 or /dev/ttyUSB0
  • Baud: 9600
  • Protocol: JSON over serial

Raspberry Pi

  • All models with GPIO header (Zero, 3, 4, 5)
  • Direct GPIO via /sys/class/gpio
  • I2C, SPI support planned
  • Native Linux, no external tools

STM32/Nucleo Boards

  • STM32F4, STM32L4, STM32H7 series
  • Nucleo-F401RE, Nucleo-F411RE
  • Flash via probe-rs CLI
  • Debug via SWD/JTAG

Hardware Tools

NullClaw exposes hardware capabilities as tools:
ToolDescriptionBoards
hardware_infoList connected devicesAll
hardware_memoryRead/write device memorySTM32, Arduino
i2cI2C bus communicationRPi, Arduino
spiSPI bus communicationRPi, Arduino
gpio_readRead GPIO pin stateRPi, Arduino
gpio_writeSet GPIO pin stateRPi, Arduino
flash_firmwareFlash firmware to MCUSTM32, Arduino

Arduino Setup

Flash Arduino Sketch

Upload the serial JSON protocol sketch:
arduino_bridge.ino
void setup() {
  Serial.begin(9600);
  pinMode(13, OUTPUT);
}

void loop() {
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');
    
    // Parse JSON: {"cmd":"gpio_write","pin":13,"value":1}
    if (cmd.indexOf("gpio_write") > 0) {
      int pin = cmd.substring(cmd.indexOf("pin":) + 5, cmd.indexOf(",")).toInt();
      int value = cmd.substring(cmd.indexOf("value":) + 7, cmd.indexOf("}")).toInt();
      
      digitalWrite(pin, value);
      Serial.println("{\"status\":\"ok\"}");
    }
  }
}
Full protocol implementation available in ~/.nullclaw/workspace/examples/arduino_bridge.ino

Configure NullClaw

config.json
{
  "peripherals": {
    "arduino": {
      "enabled": true,
      "port": "/dev/ttyACM0",
      "baud": 9600,
      "timeout_ms": 1000
    }
  },
  "tools": {
    "hardware_info": { "enabled": true },
    "i2c": { "enabled": true },
    "gpio_read": { "enabled": true },
    "gpio_write": { "enabled": true }
  }
}

Find Serial Port

# Linux
ls /dev/ttyACM* /dev/ttyUSB*

# macOS
ls /dev/tty.usbmodem* /dev/cu.usbmodem*

# With dmesg (Linux)
dmesg | grep tty

Test Connection

# Check hardware
nullclaw hardware scan

# Get device info
nullclaw hardware info

# Test GPIO
nullclaw agent -m "Set Arduino pin 13 to HIGH"

Raspberry Pi Setup

Enable GPIO Access

# Add user to gpio group
sudo usermod -aG gpio $USER

# Reload groups
newgrp gpio

# Verify permissions
ls -l /sys/class/gpio/export

Configure NullClaw

config.json
{
  "peripherals": {
    "raspberry_pi": {
      "enabled": true,
      "gpio_base": "/sys/class/gpio"
    }
  },
  "tools": {
    "gpio_read": { "enabled": true },
    "gpio_write": { "enabled": true }
  }
}

GPIO Pin Mapping

Raspberry Pi GPIO pins (BCM numbering):
PhysicalBCMName
3GPIO 2SDA
5GPIO 3SCL
7GPIO 4GPCLK0
11GPIO 17GPIO_GEN0
13GPIO 27GPIO_GEN2
15GPIO 22GPIO_GEN3
Use BCM numbers in commands:
nullclaw agent -m "Set GPIO 17 to HIGH"

Example Usage

nullclaw agent -m "Read the state of GPIO pin 17"

STM32/Nucleo Setup

Install probe-rs

# Install probe-rs CLI
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-installer.sh | sh

# Verify installation
probe-rs --version

Configure NullClaw

config.json
{
  "peripherals": {
    "stm32": {
      "enabled": true,
      "probe": "auto",
      "chip": "STM32F401RETx"
    }
  },
  "tools": {
    "hardware_memory": { "enabled": true },
    "flash_firmware": { "enabled": true }
  }
}

Flash Firmware

# Build firmware
zig build firmware

# Flash to board
nullclaw hardware flash --board nucleo-f401re --file zig-out/bin/firmware.bin

# Or via agent
nullclaw agent -m "Flash firmware.bin to the STM32 board"

Read/Write Memory

# Read memory address
nullclaw agent -m "Read memory at address 0x08000000"

# Write byte
nullclaw agent -m "Write value 0xFF to address 0x20000000"
Direct memory access can brick devices. Only use on development boards with recovery options.

Hardware Commands

Scan Devices

# List all connected hardware
nullclaw hardware scan

# Output:
# Found 3 devices:
# - Arduino Uno (/dev/ttyACM0)
# - STM32F401RE (probe: 0483:374b)
# - Raspberry Pi GPIO (native)

Device Info

# Get capabilities
nullclaw hardware info arduino

# Output:
# Board: Arduino Uno
# Type: arduino_uno
# GPIO Pins: D2-D13, A0-A5
# Flash: 32 KB
# Serial: Yes
# I2C: Yes
# SPI: Yes

Flash Firmware

# Arduino
nullclaw hardware flash --board arduino --port /dev/ttyACM0 --file sketch.hex

# STM32
nullclaw hardware flash --board stm32 --chip STM32F401RETx --file firmware.bin

Monitor Serial

# Watch serial output
nullclaw hardware monitor /dev/ttyACM0

# With baud rate
nullclaw hardware monitor /dev/ttyACM0 --baud 115200

I2C Communication

Read sensors via I2C bus:

Configure I2C

config.json
{
  "tools": {
    "i2c": {
      "enabled": true,
      "bus": "/dev/i2c-1",
      "allowed_addresses": ["0x48", "0x68", "0x76"]
    }
  }
}

Read I2C Device

# Scan I2C bus
nullclaw agent -m "Scan I2C bus 1 for devices"

# Read from device
nullclaw agent -m "Read temperature from I2C device at 0x48"

# Write to device
nullclaw agent -m "Write 0x01 to I2C device 0x68 register 0x6B"

Common I2C Sensors

DeviceAddressType
BMP2800x76Temperature/Pressure
MPU60500x68Accelerometer/Gyro
ADS11150x48ADC
SSD13060x3COLED Display

SPI Communication

Communicate with SPI devices:

Configure SPI

config.json
{
  "tools": {
    "spi": {
      "enabled": true,
      "bus": "/dev/spidev0.0",
      "max_speed_hz": 1000000,
      "mode": 0
    }
  }
}

SPI Transfer

# Transfer data
nullclaw agent -m "Send [0x9F, 0x00, 0x00] via SPI and read response"

Security

Hardware access is restricted:

Serial Port Allowlist

Only specific paths allowed:
const allowed_serial_prefixes = [_][]const u8{
    "/dev/ttyACM",
    "/dev/ttyUSB",
    "/dev/tty.usbmodem",
    "/dev/cu.usbmodem",
    "/dev/tty.usbserial",
    "/dev/cu.usbserial",
};
Prevents access to system serial devices.

I2C Address Restrictions

config.json
{
  "tools": {
    "i2c": {
      "allowed_addresses": ["0x48", "0x68"]  // Specific devices only
    }
  }
}
Empty list denies all I2C access.

GPIO Pin Limits

config.json
{
  "peripherals": {
    "raspberry_pi": {
      "allowed_pins": [17, 22, 27]  // Specific pins only
    }
  }
}

Memory Access Guards

STM32 memory access:
  • Read-only by default
  • Write requires explicit approval
  • Flash regions protected
  • System memory blocked

Troubleshooting

Device Not Found

# Check USB connections
lsusb

# Check serial ports
ls -l /dev/ttyACM* /dev/ttyUSB*

# Check permissions
groups  # Should include 'dialout' or 'uucp'

# Add user to group
sudo usermod -aG dialout $USER

Permission Denied

# GPIO access
sudo chmod 666 /sys/class/gpio/export
sudo usermod -aG gpio $USER

# Serial access
sudo usermod -aG dialout $USER

# I2C access
sudo usermod -aG i2c $USER

Arduino Not Responding

Check serial protocol:
# Test with screen
screen /dev/ttyACM0 9600

# Send test command
{"cmd":"ping"}
# Should receive: {"status":"ok"}

STM32 Flash Failed

# Check probe connection
probe-rs list

# Verify chip
probe-rs info --chip STM32F401RETx

# Try recovery mode
probe-rs erase --chip STM32F401RETx

GPIO Pin Not Working

# Check if exported
ls /sys/class/gpio/gpio17

# Export manually
echo 17 > /sys/class/gpio/export

# Set direction
echo out > /sys/class/gpio/gpio17/direction

# Test
echo 1 > /sys/class/gpio/gpio17/value

Advanced Usage

Custom Peripheral

Implement custom hardware interface:
custom_board.zig
const Peripheral = @import("peripherals.zig").Peripheral;

pub const CustomBoard = struct {
    name_str: []const u8 = "custom_board",
    
    pub fn peripheral(self: *CustomBoard) Peripheral {
        return .{
            .ptr = @ptrCast(self),
            .vtable = &vtable,
        };
    }
    
    const vtable = Peripheral.VTable{
        .name = name,
        .board_type = boardType,
        .health_check = healthCheck,
        .init_peripheral = initPeripheral,
        .read = read,
        .write = write,
        .flash = flash,
        .capabilities = capabilities,
    };
    
    fn name(ptr: *anyopaque) []const u8 {
        const self: *CustomBoard = @ptrCast(@alignCast(ptr));
        return self.name_str;
    }
    
    // Implement other methods...
};

Multi-Board Setup

config.json
{
  "peripherals": {
    "arduino_1": {
      "enabled": true,
      "port": "/dev/ttyACM0",
      "alias": "sensor_board"
    },
    "arduino_2": {
      "enabled": true,
      "port": "/dev/ttyACM1",
      "alias": "actuator_board"
    },
    "stm32": {
      "enabled": true,
      "chip": "STM32F401RETx",
      "alias": "control_board"
    }
  }
}
Reference by alias:
nullclaw agent -m "Read sensor from sensor_board"
nullclaw agent -m "Activate relay on actuator_board"

Next Steps

Sandboxing

Secure hardware access with sandboxing

AI Providers

Configure AI models for hardware control