ESP-IDF firmware for ESP32-C6. It runs a tiny patchbay on-device and publishes sensor data to NATS. The DSL/runtime matches the pure Zig op kernel vendored from lexvicacom/monoblok. Intro blog post.
- Connects to Wi-Fi, then NATS over TCP or TLS.
- Supports no auth, user/pass, or NATS
.credsauth. - Publishes heap, RSSI, uptime, and temperature-derived subjects from
patchbay.edn. - Set Wi-Fi and upstream NATS details with
make menuconfig; ESP-IDF writes them to localsdkconfig. - Run
make build,make flash, thenmake monitorto try it on hardware.
tools/gen.py compiles patchbay.edn into main/zig/rules.zig. The generated Zig is straight-line rule code with static state slots, which is much friendlier to a microcontroller than walking an s-expression tree at runtime.
The reusable ops live in main/zig/kernel.zig. That file is vendored from monoblok and should stay byte-identical; use make sync-kernel or make sync-kernel-remote when it changes upstream.
A driver is just a function named from patchbay.edn:
(pump "tinyblok.temp" :from tinyblok_read_temp_c :type f32 :hz 1)Codegen declares the function for Zig and adds it to a C pump table. main/c/drivers.c arms one esp_timer per pump, posts onto esp_event, then calls back into Zig. Use C for IDF-heavy sources, Zig for dependency-free ones.
main/zig/tx_ring.zig sits between rule eval and the NATS socket. publish! queues a subject/payload record; main/zig/main.zig drains it through main/c/nats.c. If Wi-Fi or the broker stalls, the ring drops the oldest samples rather than blocking rule evaluation.
main/c/stub.c, main/c/nats.c, main/c/drivers.c, and main/c/sources.c own the ESP-IDF surface: Wi-Fi, NVS, sockets, TLS, timers, events, and sensors. Zig owns the portable patchbay path: generated rules, op kernels, and the publish queue.
That split keeps IDF macros out of @cImport and keeps the Zig side small enough to host-test with make test.
Use the top-level Makefile:
make gen
make test
make build
make flash
make monitor
make menuconfigChecked-in defaults belong in sdkconfig.defaults; local choices live in sdkconfig. Real secrets do not: use secrets/nats.creds.example as the local .creds template.