- Python 98.1%
- C 1.3%
- CMake 0.4%
- Shell 0.2%
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. |
||
|---|---|---|
| .github/workflows | ||
| docs | ||
| firmware | ||
| scripts | ||
| service | ||
| tests | ||
| .gitignore | ||
| chipbarn.example.toml | ||
| chipbarn.service | ||
| LICENSE | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| test_api.py | ||
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_PINis undefined. Linkpico_cyw43_arch_none; drive LED viacyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1). - RP2040 (original Pico): LED is on a direct GPIO,
PICO_DEFAULT_LED_PINis 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/picotoolhost 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>isesp32,esp32s2,esp32s3,esp32c3,esp32c6,esp32h2, oresp32p4. - 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.