This repository is a fork of kassane/zig-esp-idf-sample by kassane. It keeps the upstream Apache-2.0 / MIT-0 license model and adds new example firmware, including an embedded AI agent PoC inspired by nullclaw/nullclaw.
Credits: The Zig/ESP-IDF integration layer, build system, and reusable Zig wrappers are the work of kassane and contributors. The NullClaw PoC example (
nullclaw-poc.zig) applies the NullClaw agent architecture to ESP32-S3 hardware.
Experimental. The project is a sample integration layer for building Zig code inside an ESP-IDF application, not a drop-in replacement for the ESP-IDF C/C++ toolchain.
- A Zig entry point in main/app.zig
- Reusable Zig wrappers in imports/
- Example firmware modules in main/examples/
- ESP-IDF component wiring in main/CMakeLists.txt
- C/C++ shims used by the ESP-IDF component in main/
The default build uses main/app.zig. Example selection is handled in main/CMakeLists.txt through Kconfig switches, and the selected Zig file is passed to build.zig with -Dexample=....
Current example files include:
smartled-rgb.zigwifi-station.zigdsp-math.ziggpio-blink.zighttp-server.zigble-gatt-server.zigi2c-scan.ziguart-echo.zigmatter-light.zignullclaw-poc.zig
Detailed PoC demonstrating an embedded AI agent with a working protocol engine and tool-calling loop on ESP32-S3. Inspired by nullclaw/nullclaw — the smallest fully autonomous AI assistant infrastructure.
-
Full documentation: docs/nullclaw-poc.md
-
Purpose: a small proof-of-concept that shows a REPL-driven agent running on the ESP32 which can:
- Send prompts to an LLM provider over HTTPS and parse responses.
- Execute declared "tool" functions returned by the LLM, including
gpio_set,file_create,uart_tx,spi_transfer,i2c_scan,i2c_write, andi2c_read. - Validate GPIO usage so invalid pins such as input-only lines or reserved pins are rejected before the tool runs.
- Echo content responses back to the UART console.
-
Location: main/examples/nullclaw-poc.zig
-
Architecture / components (mirrors the in-repo layout):
- Channel: UART REPL handled via console wrappers.
- Provider: OpenAI-compatible HTTP LLM calls (
callLLMin the example). - Tools:
gpio_set,file_create,uart_tx,spi_transfer,i2c_scan,i2c_write, andi2c_read. - Agent loop: a ReAct-like loop that parses JSON responses, executes tool calls, and injects tool results back into the conversation for up to 3 rounds.
- Protocol engine: bit-bang UART, SPI, and I2C helpers exposed through C shims and dispatched from Zig.
-
How it works (high level):
- The REPL reads a user line from UART.
- The agent sends a JSON request to the configured LLM endpoint (model, system prompt and a
toolsschema are included). - If the model's response contains
tool_calls, the example executes each tool and appends the result to the prompt, repeating up to 3 rounds; otherwise it prints thecontentstring to UART. i2c_scanis the preferred first step beforei2c_writeori2c_read, because it discovers responding device addresses instead of guessing.
-
Build & run:
- Configure options with
idf.py menuconfig(see configuration items below). - Build the example with:
- Configure options with
idf.py -DCONFIG_ZIG_EXAMPLE_NULLCLAW_POC=y build
idf.py -DCONFIG_ZIG_EXAMPLE_NULLCLAW_POC=y flash-
Configuration (Kconfig items referenced in the source):
CONFIG_NULLCLAW_LLM_API_URL— LLM HTTP endpoint URLCONFIG_NULLCLAW_LLM_API_KEY— API key / bearer tokenCONFIG_NULLCLAW_LLM_MODEL— model identifier used in the request JSON- Standard WiFi config:
CONFIG_ESP_WIFI_SSID,CONFIG_ESP_WIFI_PASSWORD
-
Important implementation notes & limitations:
- The example uses fixed-size static buffers (16KiB response buffer, 4KiB request buffer) to avoid dynamic allocations on the stack.
file_createstores content into NVS (key truncated to 15 chars) — there is no POSIX filesystem example here; the project uses NVS as a tiny key/value "file" backend.- GPIO validation rejects invalid or input-only pins before protocol tools run, which prevents the LLM from trying values like
313or243as if they were pins. - Console I/O relies on C shim functions in
main/such asinit_console_uart()andconsole_getchar(). - TLS certificate validation requires the clock to be synchronized (
sync_time()is called at startup). - The LLM response parsing is minimal — the PoC expects a JSON structure with
choices[0].messageand supportstool_callsarrays as produced by the example provider format.
If you want, I can also add a short troubleshooting subsection (common errors, how to inspect NVS entries, and example LLM prompts that trigger tool calls).
- Zig 0.16.0 or a compatible build
- ESP-IDF 5.0 or 6.0
- A target supported by the current Zig/ESP-IDF combination
The current build logic in build.zig is aimed at these ESP32 families:
- ESP32
- ESP32-S2
- ESP32-S3
- ESP32-C2
- ESP32-C3
- ESP32-C5
- ESP32-C6
- ESP32-C61
- ESP32-H2
- ESP32-H21
- ESP32-H4
- ESP32-P4
RISC-V targets can work with upstream Zig using generic CPU fallbacks. Xtensa targets still require an Espressif-compatible Zig toolchain.
- The allocator examples in the code are intentionally small and use ESP-IDF heap wrappers, not custom filesystem abstractions.
- The older README mentioned file systems more broadly than this repository currently demonstrates directly, so that wording has been removed here.
This fork keeps the upstream dual-license setup:
Please preserve the original license notices when redistributing or reusing this code.
- kassane/zig-esp-idf-sample — the original Zig/ESP-IDF integration project by kassane
- nullclaw/nullclaw — the NullClaw agent architecture and design patterns
- Espressif ESP-IDF — the official ESP32 development framework