No description
  • Python 98.1%
  • C 1.3%
  • CMake 0.4%
  • Shell 0.2%
Find a file
NicoJoerger 58e5412adf
Some checks failed
checks / test (3.12) (push) Failing after 3s
checks / test (3.13) (push) Failing after 2s
checks / test (3.14) (push) Failing after 2s
feat: add hardware workbench and optional MCP support
Add device leases, interactive consoles, managed debugging,
flash readback, erase, reset and optional power recovery.

Add CLI, board configuration, documentation and automated tests.
Preserve existing HTTP endpoints.

Validation: 79 tests passed; 4 Linux-specific tests skipped.
Physical hardware acceptance remains pending.
2026-10-18 07:32:22 +02:00
.github/workflows feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
docs feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
firmware feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
scripts feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
service feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
tests feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
.gitignore feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
chipbarn.example.toml feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
chipbarn.service feat: add Chipbarn 0.2 artifacts, deployment history and crash decoding 2026-10-01 00:20:16 +02:00
LICENSE Initial import: chipbarn — HTTP flash/monitor service for Pico and ESP32 boards 2026-07-02 21:16:03 +02:00
pyproject.toml feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
README.md feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
requirements-dev.txt feat: add hardware workbench and optional MCP support 2026-10-18 07:32:22 +02:00
requirements.txt feat: add Chipbarn 0.2 artifacts, deployment history and crash decoding 2026-10-01 00:20:16 +02:00
test_api.py Initial import: chipbarn — HTTP flash/monitor service for Pico and ESP32 boards 2026-07-02 21:16:03 +02:00

chipbarn

Chipbarn — RPi4 + Pico (RP2040 / RP2350) + ESP32 (any chip)

Local hardware workbench on port 8080 for Pico and ESP32 development.

0.3 adds an optional MCP server, board leases, interactive byte consoles, managed Pico/ESP32 debugging, reset/erase/readback, configured power recovery, and a CLI. Build on your own machine; upload artifacts and control boards over REST or MCP. Start with the workbench guide and example board configuration.

pip install -e '.[cli,debug,mcp]'
CHIPBARN_MCP=1 chipbarn-serve

The new workbench has hardware-free automated tests. Its physical debugging, console and power paths still require acceptance on your configured hardware. The remaining examples below document the compatible legacy HTTP interface.

Version 0.2 introduced stored firmware, deployment history and ESP crash decoding. Flash, reset, monitoring and decoding were verified on one RP2350 and one ESP32-S3. Pico GDB remains an optional preview, disabled by default and awaiting hardware acceptance. See the release review for verified results and limits.

Picos are wired to fixed RPi4 GPIO pins (see service/config.py) — any Pico SDK target works (original Pico, Pico W, Pico 2, Pico 2 W). ESP32 boards of any variant (S3, S2, C3, C6, H2, P4, original ESP32) are auto-discovered — plug in as many as you like; each is addressed by its USB serial number and the chip is auto-detected at flash time. GET /devices lists everything currently available.

Base URL: http://127.0.0.1:8080 on the Pi. The service binds to loopback by default; use an SSH tunnel or configure a bearer token before binding it to a network interface.


Service

Install dependencies

Use Python 3.12 or newer (release tests used Python 3.13). The dependency lower bounds in requirements.txt have not all been tested individually.

cd ~/chipbarn
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt

ESP-IDF users may omit esptool if Chipbarn is run with an environment where the ESP-IDF esptool executable is on PATH.

Start (foreground)

scripts/run-chipbarn.sh

The launcher uses the project venv, one worker and 127.0.0.1:8080. Reach it remotely through a tunnel:

ssh -N -L 8080:127.0.0.1:8080 <user>@<chipbarn-host>

To bind to a LAN address, create a mode-600 environment file readable only by the service user (for example ~/chipbarn.env):

CHIPBARN_API_TOKEN=<long-random-secret>
CHIPBARN_HOST=0.0.0.0

The systemd service reads this file. For foreground use, export the values before running the launcher; it does not load ~/chipbarn.env itself:

export CHIPBARN_API_TOKEN='<long-random-secret>'
export CHIPBARN_HOST='<your-LAN-IP>'
scripts/run-chipbarn.sh

The launcher requires a token for non-loopback binding. Send it with Authorization: Bearer <long-random-secret>; this protects the API, documentation and monitor streams. Never put the token in a URL.

systemd (auto-start on boot)

Install dependencies in the repository .venv first. Copy the template to a local file and replace every REPLACE_ME with your account name. Set Group to that account's group and adjust WorkingDirectory, ExecStart and EnvironmentFile to absolute paths for your installation. System services resolve %h to /root, regardless of User=, so the template uses explicit paths.

cp chipbarn.service /tmp/chipbarn.service
# Edit /tmp/chipbarn.service: account, group and all three paths.
systemd-analyze verify /tmp/chipbarn.service
sudo install -m 644 /tmp/chipbarn.service /etc/systemd/system/chipbarn.service
sudo systemctl daemon-reload
sudo systemctl enable --now chipbarn
sudo journalctl -u chipbarn -f

Stop an existing foreground/background Chipbarn instance before starting systemd; run one instance with one worker per Pi. The unit defaults to 127.0.0.1:8080. Configure tool paths in the environment file before starting it, as described below.

Configuration

The service resolves tools from PATH and auto-detects each ESP chip at flash time. Pico GPIO SWD requires a Raspberry Pi OpenOCD build with bcm2835gpio and the appropriate chip target. ESP decoding requires the matching toolchain addr2line. Systemd may have a different PATH from your login shell; use absolute tool paths in its environment file. Unsupported or missing tools are reported through /status. These environment variables are available:

Variable Default Purpose
OPENOCD openocd on PATH OpenOCD binary (Pico SWD flash + monitor)
ESPTOOL Project venv executable, then PATH esptool binary (ESP32 flash); IDF users already have it
MAX_FIRMWARE_BYTES 268435456 (256 MiB) Max accepted firmware upload size; overruns return HTTP 413
OPENOCD_FLASH_TIMEOUT 120 Wall-clock cap (seconds) on one Pico flash operation
ESPTOOL_TIMEOUT 180 Wall-clock cap (seconds) on one esptool operation
OPENOCD_INIT_TIMEOUT 15 Wall-clock cap (seconds) for OpenOCD initialization
CHIPBARN_HOST 127.0.0.1 Launcher bind address; external binding requires a token
CHIPBARN_PORT 8080 Launcher HTTP port
CHIPBARN_DATA_DIR ~/chipbarn-data Stored artifacts and SQLite deployment history
CHIPBARN_API_TOKEN unset Optional bearer token; required by the launcher for network binding
ADDR2LINE_ESP32S3 toolchain on PATH Optional absolute path to ESP32-S3 addr2line
CHIPBARN_ENABLE_PICO_DEBUG 0 Enable the preview Pico GDB API after local configuration

Example ~/chipbarn.env (replace the tool paths with your installed binaries):

CHIPBARN_HOST=127.0.0.1
CHIPBARN_PORT=8080
OPENOCD=/absolute/path/to/rpi-openocd
ADDR2LINE_ESP32S3=/absolute/path/to/xtensa-esp32s3-elf-addr2line

Set file permissions with chmod 600 ~/chipbarn.env. The environment file uses literal absolute paths; ~ and shell variables are not expanded by systemd. For foreground operation, export these settings in your shell instead. Stored artifacts and SQLite data default to ~/chipbarn-data, outside this checkout. If you select a data directory inside the repository, add it to your local .git/info/exclude before staging files. Local builds and uploads/ are ignored; do not commit runtime logs or generated firmware/tool binaries.

Pico SWD pin mapping, per-Pico chip selection (rp2040 vs rp2350), and the supported USB-serial bridge list live in service/config.py.

Interactive API docs

http://<your-host>:8080/docs


Discover devices

curl http://<your-host>:8080/devices

Returns { picos: [{id, chip, swdio, swclk}, ...], esp32: [{id, port}, ...] }. Call this first to learn the <id> of each connected ESP32. The Pico chip field reports rp2040 or rp2350 so callers know which SDK to build against. ESP32 chip detection happens lazily at flash time, so it is not listed here. Pico entries are configured SWD connections; their physical presence is not checked by this endpoint. ESP entries identify supported USB serial adapters.


Flash Firmware

Pico — upload .elf

Works for any Pico SDK target — original Pico / Pico W (RP2040) or Pico 2 / Pico 2 W (RP2350). The chip is selected per-board in service/config.py.

curl -X POST http://<your-host>:8080/flash/pico1 -F "firmware=@build/blink.elf"
curl -X POST http://<your-host>:8080/flash/pico2 -F "firmware=@build/blink.elf"

Returns JSON with OpenOCD output when done. Once the request body is fully read, closing the HTTP connection cancels the operation and stops its tool processes. An interrupted write may leave incomplete firmware; the association is marked unknown until a subsequent flash succeeds. This also applies to ESP flash routes and saved deployments.

ESP32 — upload build tarball (any chip)

After idf.py build, from the project root:

tar -czf fw.tar.gz -C build \
    flasher_args.json \
    <project>.bin \
    bootloader/bootloader.bin \
    partition_table/partition-table.bin

The chip family is read from flasher_args.json (IDF writes it during build), so the same flow works for ESP32, S2, S3, C3, C6, H2, P4 without any parameters.

Flash a specific board by its USB serial (<id> from /devices):

curl -X POST http://<your-host>:8080/flash/esp32/5970047764 -F "firmware=@fw.tar.gz"

Or the legacy no-id endpoint (targets the first detected ESP32):

curl -X POST http://<your-host>:8080/flash/esp32 -F "firmware=@fw.tar.gz"

A merged single binary (.bin) works too — the chip is auto-detected via esptool chip_id:

curl -X POST http://<your-host>:8080/flash/esp32/5970047764 -F "firmware=@merged.bin"

To skip auto-detection (e.g. for a stripped binary that doesn't enter the bootloader cleanly), pass ?chip=:

curl -X POST "http://<your-host>:8080/flash/esp32/5970047764?chip=esp32c3" -F "firmware=@merged.bin"

Successful responses include "chip": "<family>" so you can confirm what was flashed.

If the named ESP32 isn't plugged in the service returns 503 with the list of currently connected boards — plug it in and retry.


Monitor (SSE — use -N to disable curl buffering)

curl -N http://<your-host>:8080/monitor/pico1   # Pico #1 semihosting via SWD
curl -N http://<your-host>:8080/monitor/pico2   # Pico #2 semihosting via SWD
curl -N http://<your-host>:8080/monitor/esp32/5970047764   # specific ESP32 by USB serial
curl -N http://<your-host>:8080/monitor/esp32             # legacy: first detected ESP32

Optional timeout: curl -N "http://<your-host>:8080/monitor/pico1?timeout=60"
Default timeout: 300 s. Ctrl-C to stop early.

ESP address decoding is opt-in with ?decode=true. Raw serial lines remain data events; decoded frames use symbol events. When no artifact ID is provided, Chipbarn uses the last successful stored deployment if one exists.


Store firmware and deploy by artifact

Upload a Pico ELF or ESP build package to store it with a SHA-256 hash. ESP packages can include the matching application ELF for crash decoding. Create the package from a completed ESP-IDF build with:

scripts/package-esp-idf.py firmware/esp32s3_hello/build /tmp/esp32s3.tar.gz
curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  -F target=esp32s3 -F firmware=@/tmp/esp32s3.tar.gz \
  http://127.0.0.1:8080/artifacts
curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  -F target=rp2350 -F firmware=@firmware/pico_blink/build/blink.elf \
  http://127.0.0.1:8080/artifacts

Use the artifact ID from the upload response to deploy:

curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"device_id":"5970047764","artifact_id":"<artifact-id>"}' \
  http://127.0.0.1:8080/deployments
curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  'http://127.0.0.1:8080/deployments?limit=50'

Pico IDs are pico1 and pico2; ESP IDs are USB serial numbers. Legacy /flash/... uploads remain supported and appear in history, but their firmware association is marked unknown because Chipbarn did not retain their ELF. “Last successful” means the last successful deployment through Chipbarn; it cannot detect later flashes performed by other tools.

Decode ESP crash addresses

Send panic/backtrace text with the artifact built from that firmware:

curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"artifact_id":"<artifact-id>","log":"Backtrace: 0x400d51d7:0x3ffb1234"}' \
  http://127.0.0.1:8080/diagnostics/decode

Chipbarn uses the target's addr2line tool and the stored application ELF to report functions and source locations. ESP-IDF packages made with the helper include that ELF. For a merged BIN, upload its matching ELF in the optional symbols multipart field. Configure a toolchain path such as ADDR2LINE_ESP32S3 if addr2line is not on PATH. Results depend on using the matching ELF; decoding does not prove which image is currently running.

Pico GDB session

The preview API is disabled by default. Enable it with CHIPBARN_ENABLE_PICO_DEBUG=1 after configuring a suitable OpenOCD and completing the local debug acceptance checks. Starting a session resets and halts the Pico unless reset_halt is false:

curl -H "Authorization: Bearer $CHIPBARN_API_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"ttl_seconds":900,"reset_halt":true}' \
  -X POST http://127.0.0.1:8080/debug/pico/pico1/sessions

The response lists the actual gdb_ports and corresponding gdb_targets reported by OpenOCD. The number of ports depends on its target configuration. Forward the desired returned port over SSH and load the ELF from the same build:

ssh -N -L 13335:127.0.0.1:3335 <user>@<chipbarn-host>
arm-none-eabi-gdb <matching-build>/blink.elf
(gdb) target extended-remote 127.0.0.1:13335

GDB handles breakpoints, stepping and variable inspection. Stop with DELETE /debug/sessions/<session-id>; the session also stops at its TTL or service shutdown. Stop debugging before flashing or monitoring that Pico. Stopping the session leaves the target in its current run/halt state. Repeated DELETE requests for one of the last 256 closed sessions are idempotent.


Status

GET /status reports cached tool versions, feature availability and storage usage without probing chips. POST /status/refresh refreshes those checks. Pico entries in /devices are configured wiring, not a presence check.

curl http://<your-host>:8080/status

Hardware

Supported boards

Family Chips Connection
Pico SDK RP2040 (Pico, Pico W), RP2350 (Pico 2, Pico 2 W) SWD via RPi4 GPIO
ESP-IDF ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6, ESP32-H2, ESP32-P4 USB-UART (auto-discovered)

ESP8266 is not supported — modern ESP-IDF dropped it, and its ROM bootloader reset sequence differs.

Pico SWD Wiring

3-pad connector, bottom edge, USB facing up: GND / SD / SC (left to right). Same pad layout on the original Pico, Pico W, Pico 2, and Pico 2 W.

Signal Pico pad Pico #1 RPi4 GPIO Pico #2 RPi4 GPIO
SWDIO SD (middle) GPIO 24 — pin 18 GPIO 22 — pin 15
SWDCLK SC (right) GPIO 25 — pin 22 GPIO 23 — pin 16
GND GND (left) any GND any GND

3.3 V both sides, no level shifter. No UART wires needed — console goes over SWD via semihosting.

ESP32

Discovery is automatic — the service scans /sys/class/tty/ for known USB-to-UART bridges (CH340/CH343, CP210x, FTDI, and Espressif's native USB-CDC on VID 0x303a — see ESP_USB_BRIDGES in service/config.py) and addresses each board by its USB serial number. No udev rule is required; plug boards into any USB port. User must be in the dialout group (no sudo).

To find the serial number of a board without the service:

udevadm info -q property -n /dev/ttyACM0 | grep ID_USB_SERIAL_SHORT

Adding a new Pico

One line in service/config.py — the chip drives the OpenOCD target config:

PicoDevice("pico3", chip="rp2040", swdio=18, swclk=17, gdb_port=3335,
           tcl_port=6668, telnet_port=4446, debug_gdb_port=3351),

Restart the service. /flash/pico3 and /monitor/pico3 are now available.

Adding a new ESP-style USB-UART bridge

One line in service/config.py — use pid=None to match every product from a vendor:

ESP_USB_BRIDGES: list[tuple[str, str | None]] = [
    ...,
    ("abcd", None),  # imaginary vendor; any PID
]

Firmware Examples

Source lives in firmware/:

Directory Target Notes
firmware/pico_blink/ RP2350 (Pico 2 / Pico 2 W) Blinks LED at 250 ms, prints blink #N via semihosting
firmware/esp32s3_hello/ ESP32-S3 Logs chip info on boot, blinks GPIO2 at 1 Hz, prints blink #N

The same flash flow works for any Pico SDK or ESP-IDF target — only the build differs. For a different chip, change set-target (ESP-IDF) or the SDK / link flags (Pico SDK) and rebuild; the chipbarn side needs no configuration change.

Pico SDK — build notes

  • RP2350 (Pico 2 W): LED is wired through the CYW43 WiFi chip, not a direct GPIO. PICO_DEFAULT_LED_PIN is undefined. Link pico_cyw43_arch_none; drive LED via cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1).
  • RP2040 (original Pico): LED is on a direct GPIO, PICO_DEFAULT_LED_PIN is defined; no CYW43 link needed.
  • RP2040 (Pico W): same CYW43 LED path as Pico 2 W.
  • Enable semihosting in CMakeLists.txt: pico_enable_stdio_semihosting(blink 1) / pico_enable_stdio_uart(blink 0).
  • Build with a clean PATH — ESP-IDF prepends xtensa cross-assemblers that break pioasm/picotool host builds. Strip the environment: env -i HOME=$HOME PATH=/usr/local/bin:/usr/bin:/bin cmake ...

ESP-IDF — build notes

  • ESP-IDF v6.0.x (install via the official ESP-IDF installer).
  • Activate: source $IDF_PATH/export.sh (or your IDF activation script).
  • First build: idf.py set-target <chip> where <chip> is esp32, esp32s2, esp32s3, esp32c3, esp32c6, esp32h2, or esp32p4.
  • Build: idf.py build
  • The service flashes via the tarball described above — no need to flash from the RPi4 directly.

Tests

Run the complete hardware-free suite from the repository root:

.venv/bin/python -m unittest discover -s tests -v
.venv/bin/python -m pip check
git diff --check

The suite uses fake devices/tools and temporary data, including real loopback HTTP disconnects, cancellation, GDB TTL and SIGTERM shutdown checks. It does not flash connected boards. test_api.py is a separate, optional hardware script that can overwrite firmware; inspect its device settings before running it. Its extra dependency is installed with .venv/bin/pip install -r requirements-dev.txt.

Historical development plans are preserved in docs/; the release review records the current acceptance scope.