Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

benchpod-cli

benchpod is the EmbeddedCI BenchPod command-line tool. It talks to a pod over its network TCP/JSON API (port 8080), over its USB console (CDC-ACM), or through embeddedci.com (--connection embeddedci:<name>), and drives the pod's hardware: signal generation, ADC capture, logic-analyzer pins, Wi-Fi configuration, firmware flashing over SWD, SPI NOR flash programming, and the pod's own firmware, policies and cloud link. Every command and flag is listed in the command reference.

New pod? benchpod setup walks the getting-started steps for you: it finds the pod, gets it on the network, sets the board I/O voltage, registers it with embeddedci.com and saves the connection, skipping whatever is already done.

Installation

Pre-built binaries for macOS, Linux and Windows (amd64 + arm64) are attached to every GitHub release.

Homebrew (macOS / Linux)

brew install --cask embeddedci-com/tap/benchpod

This taps embeddedci-com/homebrew-tap and installs the benchpod binary; brew upgrade --cask benchpod later picks up new releases. The macOS binary is not Apple-notarized, so the cask's post-install hook strips the com.apple.quarantine attribute that Homebrew sets — otherwise macOS Gatekeeper would refuse to run the unsigned binary (even from the terminal). This deliberately bypasses Gatekeeper for our own binary; if you'd rather not, use a notarized build (not currently produced) or build from source.

Direct download

Grab the archive for your OS/arch and put the binary on your PATH. The releases/latest/download/… URLs always point at the newest release. For example, macOS arm64:

curl -fsSL -o benchpod.tar.gz \
  https://github.com/embeddedci-com/benchpod-cli/releases/latest/download/benchpod_Darwin_arm64.tar.gz
tar -xzf benchpod.tar.gz benchpod
sudo mv benchpod /usr/local/bin/
benchpod --version

Swap Darwin_arm64 for Darwin_x86_64, Linux_x86_64, Linux_arm64, etc. On Windows download benchpod_Windows_x86_64.zip and extract benchpod.exe. releases/latest/download/checksums.txt verifies the download.

macOS, direct download only: if you fetch the archive with a browser (not curl/Homebrew) it gets the quarantine flag and double-clicking is blocked. Run it from the terminal, or clear the flag once: xattr -d com.apple.quarantine /usr/local/bin/benchpod.

From source

You need Go (see go.mod for the version), then make build. On macOS the build requires cgo (CGO_ENABLED=1, the default for a native build) because USB serial-port enumeration uses the IOKit framework; Linux and Windows are pure Go.

Linux: keep ModemManager off the pod

On Linux, ModemManager probes new USB serial ports as if they were modems, which garbles the pod's USB console for its first seconds. The release archives include packaging/linux/70-benchpod.rules; install it once:

sudo cp packaging/linux/70-benchpod.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules && sudo udevadm trigger

It sets ENV{ID_MM_DEVICE_IGNORE}="1" for the pod's USB IDs: 0483:5740 (ST's stock CDC ID, every STM32 firmware including 3.9.0; it also matches other ST CDC devices) and 1209:0001 (pre-release 3.9.0 builds), and TAG+="uaccess", which lets the user signed in at the desktop open the console without joining the dialout group. Over SSH, or without the rule, add yourself to dialout (sudo usermod -aG dialout $USER, then log in again). On firmware 3.9.0 and later the network commands need neither: over --connection usb the CLI finds the pod from this computer's end of its USB network link.

OpenOCD (for flash)

The flash command shells out to OpenOCD, which must be installed separately and on your PATH. It needs a build with the cmsis_dap_tcp backend (post-0.12.0) — on macOS that currently means brew install --HEAD open-ocd. See Flashing: CMSIS-DAP below.

dfu-util (for flash-self)

flash-self reflashes a pod's own firmware over USB DFU (STM32) and shells out to dfu-util. The Homebrew cask declares it as a dependency, so brew install --cask embeddedci-com/tap/benchpod pulls it in automatically; otherwise brew install dfu-util (or your distro's package). See Flashing the pod itself below.

Firmware from 3.5.0 on keeps the iCE40 gateware and the ESP32-C3 Wi-Fi image in the pod's W25Q flash rather than in its image. After writing the firmware, flash-self waits for the pod's USB console and installs the ones it lacks from the same release (or from blobs/ next to a local .bin); install-blobs does that step on its own, and set-wifi runs it first when the ESP32-C3 image is missing. flash-self also refuses an image that does not fit the pod's flash (a 1 MB STM32H563 cannot take firmware built for the 2 MB part).

Releases can publish a signed manifest, <asset>.sig, next to bench_pod_stm32.bin and each blob-*.bin. flash-self and the blob install check it against the release keys (plus your developer key, if ~/.config/benchpod/fw-signing-dev.key exists) and print one line, for example signature: ok (release-1 key 9b5cc58445e62d88, release 3.6.0) or signature: none (this release is not signed). A local file's signature is read from <file>.sig. Firmware that checks signatures itself also gets the manifest with each USB upload, and its verdict is printed as pod signature check: .... For now this only reports: a missing or failing signature never stops a flash or an install.

Connection

The global --connection flag is the single place that says where and how to reach the pod; the transport is inferred from its value:

--connection value Transport
192.168.1.5[:8080] The pod's network TCP/JSON API (Ethernet or Wi-Fi).
benchpod-a1b2c3, a1b2c3 The pod with that name (as on its label and in discover): its address found on the LAN over mDNS, else usb:benchpod-a1b2c3. See below.
/dev/tty..., COM3 USB, explicit console device path.
usb USB, the console auto-detected by probing the ports.
usb:<name> USB, the pod with that name (usb:benchpod-a1b2c3) when several are plugged in; plain usb then lists them (not yet tested with two physical pods).
embeddedci:<name> Through embeddedci.com to the pod with that name on your account (see below).
(omitted) the default saved by benchpod discover --save, benchpod setup or benchpod set-connection.

Pod names. A value shaped like a pod's name, benchpod-a1b2c3 or just a1b2c3 (optionally with .local), is looked up the way discover finds pods: an mDNS browse of the LAN for up to 3 seconds, which ends as soon as the pod answers. The CLI does this itself because the system resolver it is built with does not resolve .local names. A pod that does not answer on the LAN is looked for on USB, as usb:benchpod-a1b2c3. Any other hostname (lab-pod.example.com) goes to the system resolver as before.

The global flags are also settable via a BENCHPOD_* environment variable (BENCHPOD_CONNECTION=usb, BENCHPOD_TIMEOUT=2m, BENCHPOD_CONFIG_FILE, BENCHPOD_OUTPUT_FILENAME), with precedence flag > env > default. Command flags are flags only.

The firmware itself is unauthenticated; benchpod login is independent of the device path and authenticates with embeddedci-server (device-login flow) for cloud features. Direct firmware commands on the network or USB do not send tokens.

USB networking (firmware 3.9.0+). The pod's USB device is also a network adapter that carries the full network API on a fixed address (10.237.x.y, its own point-to-point link: the computer keeps its default route and DNS). With a USB connection (usb or a device path), a command that needs the network uses that adapter, so the whole CLI works over one USB cable; the console is still used for the commands it runs (below) and as the fallback. USB networking is off while Wi-Fi is configured on the pod (set-wifi turns it off, clear-wifi back on). If a command says the adapter is not up, unlock the screen (macOS ignores a new adapter while it is locked) and run benchpod usb-reattach. benchpod usb-bridge carries the pod's cloud link over the same cable.

The USB network adapter works with the in-box drivers of macOS, Linux and Windows 11 (tested). Windows 10 (1903 or newer) is supported but not tested: if the adapter shows up without a driver there, open Device Manager, update its driver, and pick "UsbNcm Host Device" from the list of network adapters. The pod keeps ST's USB ID 0483:5740: on a Windows computer with ST's Virtual COM Port driver installed, that driver can claim the whole pod, which then shows up as one COM port without the network adapter. Uninstall it for the pod (Device Manager, Ports, "STMicroelectronics Virtual COM Port", Uninstall device, tick "Attempt to remove the driver", then replug the pod); the steps are in the firmware's USB serial interface docs. WSL2 and Docker on Windows are not tested: Windows routes the pod's 10.237.x.y address itself, so a WSL2 distribution or a container on the host network should reach it with --connection <that address>, but USB auto-detect and mDNS may not work from inside them.

Compatibility with older firmware and older CLIs. The CLI decides per pod, from what the pod reports (the usb line of its console status), never from its own version:

Pod firmware before 3.9.0 Pod firmware 3.9.0 and later
This CLI (0.6.0+) Works as before: the USB console for status, la voltage, install-blobs, flash-self, set-wifi, the policies, cloud ca/proxy and identity; the network commands need the pod's network address, as before, and say so. The serial auto-detect still finds 0483:5740 and 2E8A consoles. Over usb, every command uses the pod's USB network adapter when it is up (including flash, which the STM32 pod's console never ran), and the console for the commands that stay there. When USB networking is off (Wi-Fi configured) or not up, the network commands use the pod's Ethernet or Wi-Fi address from its console status if this computer reaches it, say so, and are subject to the pod's external LAN policy there (benchpod clear-wifi turns USB networking back on).
An older CLI (0.5.x) Unchanged. The kept console commands keep their names and replies (status, dfu, identity, identity wipe, lan-policy, sig-policy, set-wifi, show-network, clear-wifi, cloud ca show/clear, cloud proxy), so those still work, and flash-self still flashes over DFU. What 3.9.0 moved to the network API (la voltage over USB, and every upload: install-blobs, the blob step of flash-self and set-wifi, cloud ca set) prints the pod's "moved to the network API ... update the benchpod CLI to 0.6.0 or later" line: update the CLI with the firmware. Over a network address everything works.

Which command works over which connection:

Connection Commands
Network address Every pod command except the USB-only ones below.
USB With firmware 3.9.0+ and no Wi-Fi configured, every network command over the pod's USB network adapter, including status, la voltage, flash (SWD), install-blobs, cloud ca and cloud proxy; without it the pod's Ethernet or Wi-Fi address, and the console where it still has the command. Firmware before 3.9.0: the console, as before. lan-policy, sig-policy and identity always use the console (physical presence).
embeddedci:<name> status, ping, la voltage, la pullup, la status, la step, generate, lan-policy, sig-policy, cloud ca show, cloud ca clear, cloud proxy (show, set, clear), deregister.
USB only set-wifi, show-network, clear-wifi, identity, install-blobs, bootsel, dfu, usb-bridge, usb-reattach; flash-self uses USB DFU. These always use the USB console: a saved address or embeddedci: default falls through to USB auto-detection, a device path still selects the port, and an explicit --connection embeddedci:<name> is refused.

A command that cannot use the connection it is given says so and names what to use instead.

Through embeddedci.com

embeddedci:<name> is the form the Python SDK and the MCP server take. The CLI looks the name up in your account's pod list and sends each command to POST /api/benchpod/devices/{id}/command, which relays it over the pod's own cloud link and returns the pod's reply: the same single-reply JSON commands the network API takes, so the output is the same. It needs:

  • A sign-in: BENCHPOD_API_KEY (an eci_... key, sent as Authorization: ApiKey) when set, else the benchpod login session.
  • The pod online on embeddedci.com, and no other session holding its lease (the web app, a CI job or the SDK): a held lease exits with code 4 (busy), an offline pod with code 5 (unreachable).
  • For a change to persisted settings (cloud proxy set, cloud ca clear, the policy sets): an organization owner or admin (API keys: the benchpod:admin scope).

BENCHPOD_API_BASE points the CLI at another embeddedci-server (default https://www.embeddedci.com), as it does for the SDK.

Commands that stream data or need a direct link to the pod do not go through embeddedci.com: capture, stream, measure, test, flash --file, spi-flash, cloud ca set, register, the USB-only commands and discover. Over a cloud connection they refuse with a message pointing to the web app, the Python SDK or pytest plugin (which take embeddedci:<name> for those too), or a network/USB connection. flash --build (a build uploaded to embeddedci.com) does work over a cloud connection: embeddedci.com runs OpenOCD. The CLI has no UART command; use the web app's Terminal or the SDK.

One wiring profile

The pod's wiring profile on embeddedci.com (the web app's Wiring tab; the SDK and MCP read the same one) says which LA channel carries which signal. flash and spi-flash take the pins you leave out from it:

Command Flag Profile field
flash --swclk swd_swclk
flash --swdio swd_swdio
flash --nreset swd_nreset
flash --target swd_target
spi-flash --sck, --mosi, --miso, --cs spi_sclk, spi_mosi, spi_miso, spi_cs

That happens when the connection is the pod's network address (or, for flash --build, embeddedci:<pod>), this machine is signed in (benchpod login or BENCHPOD_API_KEY) and the pod is on that account (its cloud_status device id is in the account's pod list, as status and discover check). Flags always win. The log says which values came from the profile ([benchpod] flash: SWCLK LA11, SWDIO LA12 from the pod's wiring profile on embeddedci.com; ...) or that the flags were used. Without a profile (over USB, not signed in, another account) the command behaves as before: the pin flags are required. If the lookup fails (it gives up after a few seconds), the CLI prints one warning line and goes on with the flags.

Shared config

config.json in ~/.config/benchpod-cli/ (or $XDG_CONFIG_HOME/benchpod-cli/) holds the saved connection. It is shared: the Python SDK and the MCP server read its connection key as their default pod when they are given none, so benchpod set-connection (or discover --save, setup) picks the pod for all three. Only the CLI writes it.

{
  "version": 1,
  "connection": "embeddedci:benchpod-a1b2c3",
  "last_serial": "/dev/cu.usbmodem1101"
}
Key Meaning
version Format version, 1. Written on every save; a file without it is version 1.
connection The default --connection value: an address, a device path, usb or embeddedci:<name>.
last_serial The USB device most recently auto-detected as a pod, probed first next time (CLI only).
bench_pod_addr Legacy: an address saved by old CLI versions, read as connection when that is empty.

Readers ignore keys they do not know. token.json next to it holds the benchpod login session, which the MCP server also uses.

CLI usage

# First time: guided setup (find, network, I/O voltage, register, save):
benchpod setup
benchpod setup --la-voltage 3.3V --yes      # non-interactive

# Save a default connection so later commands can omit --connection:
benchpod set-connection 192.168.1.5
benchpod set-connection benchpod-a1b2c3     # by name: saves its LAN address, or usb:benchpod-a1b2c3
benchpod set-connection usb

# Find every pod on this machine and this LAN, and check each one works:
benchpod discover
benchpod discover --save     # save it as the default when exactly one is found

# Reachability / state:
benchpod ping
benchpod status            # readable summary
benchpod status --json     # the pod's raw status reply

# Hardware (network):
benchpod la voltage 3.3V                          # the DUT's I/O voltage; LA operations need it (network or USB)
benchpod generate --waveform sine --freq 1kHz --duration 2s   # DAC waveform
benchpod measure --waveform sine --samples 1024   # DAC + ADC loopback
benchpod capture --samples 1024 --output csv      # ADC snapshot
benchpod stream ...
benchpod test ...
benchpod la pullup 3 on                           # LA pin pull-up
benchpod la status
benchpod la step --la 6 --steps 200 --delay 500us --dir-la 7 --direction 1

# Network (always over the USB console):
benchpod set-wifi
benchpod show-network     # the pod's IP (wired lease included), SSID, state, RSSI
benchpod clear-wifi

# One USB cable (firmware 3.9.0+, no Wi-Fi configured):
benchpod capture --connection usb ...   # network commands use the pod's USB network adapter
benchpod usb-bridge                     # the pod's cloud link through this computer (Ctrl-C stops it)
benchpod usb-reattach                   # re-enumerate on USB (macOS: after unlocking the screen)

# Firmware:
benchpod flash ...        # flash a TARGET/DUT wired to the pod (SWD via OpenOCD CMSIS-DAP; --build: an uploaded build)
benchpod spi-flash ...    # read/erase/program an SPI NOR flash on four LA pins (network)
benchpod bootsel          # reboot a (retired) RP2350 pod into its UF2 bootloader (USB console only)
benchpod dfu              # reboot an STM32 pod into its USB DFU bootloader (USB console only)
benchpod flash-self                  # fetch latest firmware + flash the POD over USB DFU (STM32)
benchpod flash-self --enter-dfu      # …rebooting a running pod into DFU first
benchpod flash-self ./fw.bin         # …flashing a specific local build instead
benchpod install-blobs               # gateware + ESP32-C3 images the pod's firmware goes with (USB)
benchpod install-blobs --dir stm32h563/build/blobs   # …from a local firmware build

# Cloud auth (optional):
benchpod login [--server-url https://www.embeddedci.com]
benchpod register --connection 192.168.1.5
benchpod deregister ...
benchpod logout

# Pod policies (cloud with --device-name, or USB):
benchpod lan-policy --device-name benchpod-baea06          # show
benchpod lan-policy set locked --device-name benchpod-baea06
benchpod sig-policy --connection usb                       # show
benchpod sig-policy set permissive --connection usb

# Device identity (USB console only):
benchpod identity --connection usb                         # show
benchpod identity wipe --connection usb                    # erase the key, make a new one

# Cloud link behind a company network (USB or LAN):
benchpod cloud ca set acme-root.pem --connection usb
benchpod cloud ca show
benchpod cloud proxy set proxy.corp:3128 --user alice   # prompts for the password
benchpod cloud proxy clear

Flags that take a physical quantity accept it with its unit: --freq 2kHz, --sample-rate 10MHz, --duration 2s, --delay 50us, and benchpod la voltage 3.3V. The older flags with the unit in their name (--sample-rate-mhz, --duration-ms, --delay-us) keep working; pass one or the other, not both.

deregister is the inverse of register: it detaches the pod from the logged-in account and clears the pod's cloud configuration so it stops connecting. The server keeps everything recorded for that pod (captures, waveforms, wiring) and only marks the device disabled — registering the same pod again for the same account brings the device and its data back. Registering it for a different account gives that account a fresh device and leaves the previous owner's history untouched, which is how a pod changes hands:

benchpod deregister --connection <pod-ip>       # identifies the pod by its own key
benchpod deregister --device-name <name>        # …for a pod you can no longer reach

Running register again on the same pod is always safe: it refreshes the pod's cloud settings and keeps its device, name and history, even after a deregister. A pod registered in an organization you are not in cannot be claimed until someone there deregisters it, on the BenchPod page or with benchpod deregister --device-name <name>. The pod does not need to be online for that. Deleting or disabling an account deregisters its pods automatically.

register names the pod after its mDNS name (benchpod-a1b2c3) unless you pass --device-name. Names identify pods, so each is unique within an organization: if another pod already has it, pick another name. It then waits up to --wait (default 30s) for the pod to connect, and if it does not, prints the pod's last connection error and exits non-zero.

Pod policies: LAN and signatures

Two settings the pod keeps in its config flash:

lan-policy What the LAN TCP port allows
open everything (the default)
locked read and instrument control only; config and firmware need cloud or USB
off nothing: no LAN listener, no mDNS. The cloud and USB keep working
sig-policy How firmware and blob signatures are treated
audit every image is accepted; signatures are only reported
permissive unsigned images are accepted, a bad signature is refused
required unsigned images are refused

benchpod lan-policy and benchpod sig-policy show the policy; ... set <value> changes it. Pick the pod with --device-name / --device-id (through embeddedci.com, like deregister) or with --connection usb. Over the LAN the commands can only show the policy: the pod refuses changes from the LAN.

$ benchpod lan-policy --device-name benchpod-baea06
LAN policy of benchpod-baea06: locked (read and instrument control only on the LAN)
  • Over the cloud a change needs an organization owner or admin (an API key needs the benchpod:admin scope).
  • The cloud can only make sig-policy stricter. Only the USB console can loosen it (benchpod sig-policy set permissive --connection usb); the CLI says so when the pod refuses.
  • With lan-policy off, LAN tools stop working until it is set back: SCPI/VISA, hwe2e TestHW and LAN discover. Set it back over the cloud or USB.
  • Firmware without these commands gets "this firmware has no LAN policy; update the pod's firmware".

Device identity: show and wipe

The pod's identity is an Ed25519 key in its own flash sector; its name benchpod-a1b2c3 is the first three bytes of the public key, and embeddedci.com knows the pod by that key. The firmware makes the key once, on a blank sector, and never writes over a record it does not recognize (an older or newer layout, or damage): such a pod stays offline and says why, rather than lose the key it is registered with.

$ benchpod identity --connection usb
Identity of the pod on /dev/cu.usbmodem1101: none: unknown identity record (magic 0xc0ffee02 v2), not replaced
The pod stays offline without an identity. Recover it with: benchpod identity wipe --connection usb

benchpod identity wipe erases the key and makes a new one. It works only over the pod's USB console (--connection usb or a serial device path): the pod refuses it over the LAN and the cloud, so it needs someone at the pod. The CLI shows the current identity and asks before it wipes (--yes skips the question for scripts); the pod also checks a confirmation token (the current short id, or unknown), so a stray command cannot wipe a key.

After a wipe the pod is a new pod to embeddedci.com, and its cloud login is refused until it is registered again:

  1. benchpod deregister --device-name <old name>: the old record keeps its captures, waveforms and wiring, and the name is freed. Works while the pod is offline; an organization owner or admin can also do it on the BenchPod page.
  2. Power-cycle the pod, so it advertises its new name on the LAN.
  3. benchpod register --connection <pod address> --device-name <old name>.
  4. Set the wiring profile again on the BenchPod page.

No server-side login reset is needed: the host-bound login state (ws_auth.v2) belongs to the old record.

Cloud link: company CA and HTTP proxy

On a network whose proxy inspects TLS, the pod's cloud link fails: the proxy re-signs embeddedci.com with a company root the pod does not trust. Some networks also only reach the internet through an HTTP proxy. benchpod cloud sets both over the USB console (--connection usb) or through embeddedci.com (--connection embeddedci:<name>: cloud proxy show, set and clear, cloud ca show and clear; the CA upload needs USB, and a change needs an organization owner or admin). The LAN (--connection <address>) can show them; current firmware refuses changes from the LAN:

$ benchpod cloud ca set acme-root.pem --connection usb
acme-root.pem: 1 certificate
  CN=Acme Root CA,O=Acme Corp
    SHA-256 3b1f...c2d9
Uploading 652 bytes to the pod on /dev/cu.usbmodem1101 ...
Company CA of the pod on /dev/cu.usbmodem1101 is now 1 certificate
  CN=Acme Root CA,O=Acme Corp
    SHA-256 3b1f...c2d9
The pod reconnects to the cloud, trusting this CA in addition to its built-in roots.

$ benchpod cloud proxy set proxy.corp:3128 --user alice --connection usb
Proxy password:
HTTP proxy of the pod on /dev/cu.usbmodem1101 is now proxy.corp:3128 (with a user and password)
The pod reconnects to the cloud through proxy.corp:3128.
  • cloud ca set takes a PEM file with one or more certificates (at most 16 KB as PEM). It checks them first, prints each subject and SHA-256, warns about a certificate that is not marked as a CA, refuses anything that is not a certificate (a private key, for example), and uploads only the certificate blocks. The CA is used in addition to the built-in roots; host name and chain checks stay on. cloud ca clear removes it.

  • cloud proxy set <host:port> with --user takes the password from --password, --password-stdin or a prompt. The pod never reports the password back and the CLI never prints it. cloud proxy clear removes the proxy. With a LAN address as the target, the CLI first warns that the LAN API is plain TCP and the password would cross the network unencrypted.

  • show (the default) reports what the pod holds. The pod reconnects to the cloud after every change.

  • Current firmware refuses set and clear from the LAN, whatever its LAN policy: only USB or the cloud link may change the company CA or the proxy. The CLI then prints the pod's reason and a hint to rerun with --connection usb:

    $ benchpod cloud proxy clear --connection 192.168.1.50
    [benchpod] cloud proxy clear: the pod refused: cloud_proxy: change it from the cloud or over USB (the pod only takes this change over USB or from the cloud, never from Ethernet or Wi-Fi; run it with --connection usb)
    

    Older firmware with its LAN policy locked refuses the same way, with its own message.

  • Firmware without them gets "this firmware has no company CA support; update the pod's firmware" (or the same for the HTTP proxy).

Run benchpod <command> -h for the flags of any subcommand.

Flashing: CMSIS-DAP

flash always drives host-side OpenOCD — the pod holds no flash intelligence and OpenOCD's exit code is the verdict. The pod runs a CMSIS-DAP processor locally and OpenOCD's cmsis-dap TCP backend ships whole DAP transfers (DAP_Transfer/DAP_TransferBlock), so a flash is one round-trip per DAP command instead of thousands of per-bit round-trips — fast even over the network. Works on TCP (dap_start) and USB (the console dap-start command). It needs a recent OpenOCD with the cmsis_dap_tcp backend (post-0.12.0; e.g. brew install --HEAD open-ocd); the CLI checks for the backend up front and fails with clear advice if it is missing.

The legacy per-bit remote_bitbang path has been retired — the pod no longer speaks it.

SPI flash

spi-flash reads, erases and programs a 25-series SPI NOR flash (W25Q, MX25, GD25, IS25, ...) wired to four LA pins, through the pod's SPI master. It needs gateware v45 or newer (spi_master in the status caps; the CLI checks first) and the LA voltage set. Network (TCP) connection only for now. Pins you leave out come from the pod's wiring profile on embeddedci.com when you are signed in (see One wiring profile).

PINS="--sck 3 --mosi 4 --miso 5 --cs 6"
benchpod spi-flash id $PINS                          # JEDEC ID, size, status register
benchpod spi-flash write fw.bin $PINS --nreset       # erase, program, verify
benchpod spi-flash write app.bin $PINS --addr 0x100000 --hz 4000000
benchpod spi-flash read dump.bin $PINS               # whole part (or --addr/--len, e.g. --len 1M)
benchpod spi-flash erase $PINS --addr 0 --len 64K    # or --chip

write erases the range the file covers (whole 4 KB sectors, 1 MB per command), programs it in 768-byte chunks, and has the pod read every chunk back (--no-erase, --no-verify skip those steps). --hz picks the SCK rate (the pod rounds down, 190 kHz to 6 MHz) and --mode is 0 or 3. 3-byte addresses reach the first 16 MB.

With --nreset the DUT is held in reset (its NRST wired to the pod's reset pin) for the whole run, so its own controller stays off the SPI bus. At the end the CLI always releases the SPI pins and then the reset, also after an error or Ctrl-C. The pod does not clear block-protect bits; a part that ships protected fails the write with "write enable did not stick".

Flashing the pod itself (STM32, USB DFU)

flash programs a target wired to the pod. To flash the pod's own firmware there are two paths, mirroring how the two pod MCUs boot:

  • RP2350 pod — drag-and-drop UF2. benchpod bootsel reboots a running pod into the UF2 drive; for a blank board hold the physical BOOT0 button. Drop firmware.uf2 onto the RPI-RP2 drive (or use picotool).

  • STM32 pod — USB DFU via dfu-util, no SWD probe needed. A first-time user just runs benchpod flash-self with no arguments: the latest prebuilt firmware is fetched automatically (no toolchain, no manual download), the pod is detected in DFU mode — with on-screen guidance and a wait if it isn't yet — and dfu-util writes it.

    benchpod flash-self
    

    The device must be in its ROM DFU bootloader; flash-self waits (up to --wait, default 60s) and tells you how to get there:

    • first flash / blank board: hold BOOT0 high at reset (hardware only — a board with no firmware can't be driven over USB yet), then run the command.
    • re-flash a running pod: add --enter-dfu to reboot it into DFU for you (equivalent to benchpod dfu first):
      benchpod flash-self --enter-dfu
      

    Overrides: a local path (benchpod flash-self ./bench_pod_stm32.bin), an explicit --firmware-url, or a pinned --firmware-version v0.0.5.

  • Over the network instead of DFU (--network): the pod receives the image over its own network API, stages it in PSRAM, checks its SHA-256 and writes it to flash itself, then restarts. No dfu-util and no bootloader. Over the USB cable this uses the pod's USB network adapter (firmware 3.9.0 and later, no Wi-Fi configured); with --connection <address> it goes over Ethernet or Wi-Fi (an external LAN policy of locked or off refuses it there). Afterwards the gateware and ESP32-C3 images are installed the same way.

    benchpod flash-self --network                          # over the USB cable
    benchpod flash-self --network --connection 192.168.1.42 # over the LAN
    

    DFU stays the default: it needs nothing from the running firmware, so it also brings back a pod whose firmware does not start. If power is lost while the pod writes its flash (a few seconds), only DFU recovers it. A pod on embeddedci.com is updated from the web app instead.

The prebuilt firmware is fetched from the public embeddedci-com/benchpod-firmware releases (the firmware source repo is private); a published .sha256 is verified when present. Under the hood flash-self runs dfu-util -a 0 --dfuse-address 0x08000000:leave -D <firmware.bin>; :leave starts the new firmware once the write verifies. The firmware side is the dfu console command, which jumps to the STM32 system-memory bootloader (the same flow the firmware make flash-dfu target uses).

Command reference

Every command and flag. "Network" means the pod's TCP/JSON API (`--connection

`), "USB" its USB console (`--connection usb` or a device path). Run `benchpod -h` for the same list in the terminal. `benchpod completion bash|zsh|fish|powershell` prints a shell completion script, and `benchpod --version` the CLI version.

benchpod (global flags)

These apply to every command. The CLI keeps its files in ~/.config/benchpod-cli/ (or $XDG_CONFIG_HOME/benchpod-cli/): config.json for the saved connection (shared with the SDK and MCP, see Shared config) and token.json for the embeddedci.com session.

Flag Default Purpose
--connection (saved set-connection target) Network address, pod name (benchpod-a1b2c3), device path, usb, or embeddedci:<name> (through embeddedci.com); see Connection.
--config-file ~/.config/benchpod-cli/config.json The CLI's config file, where set-connection saves the default.
--output-filename (stdout) Write command output to this file instead of stdout.
--timeout 0 (per-command default) Overall command deadline; 0 uses each command's own default.
--no-firmware-report false Do not report firmware and blob updates to embeddedci.com (also BENCHPOD_NO_FIRMWARE_REPORT=1); see Firmware-update reports.

Firmware-update reports

When you are signed in to embeddedci.com (benchpod login or BENCHPOD_API_KEY), flash-self and install-blobs tell embeddedci.com which pod they updated, so the server keeps each pod's firmware history next to what the pod reports when it connects. A report holds the pod's identity public key and name, the firmware and gateware versions before and after, the blobs installed, how they were installed (dfu, usb_network, usb_console, lan), the CLI version and the time. Nothing else from your computer is sent.

The report never slows down or fails an update: it is sent with a short timeout, nothing is sent when you are not signed in, and a report that could not be sent (offline) is kept in ~/.config/benchpod-cli/firmware-reports.jsonl for one more try on a later run. Turn it off with --no-firmware-report or BENCHPOD_NO_FIRMWARE_REPORT=1.

Exit codes

Scripts can tell why a command failed from its exit code. The error is printed on stderr with a [benchpod] prefix.

Code Meaning
0 Success.
1 Any other error.
2 Usage: a bad flag, argument or subcommand.
3 Refused: the pod refused the command (a locked LAN, a missing role, a bad request).
4 Busy: the pod or one of its engines is in use (a cloud job's lease, another session).
5 Unreachable: nothing answered on the pod's address, no pod was found on USB, or the pod is offline on embeddedci.com.

A refusal is printed word for word as the pod sent it. For a locked: (LAN policy), busy: (cloud lease) or forbidden: (missing role) refusal the CLI adds what to do next. So it does for la voltage not set (run benchpod la voltage 3.3V or 1.8V, or set it on the web app's Wiring tab) and for a pin conflict: LA4 is in use by ..., where the raw JSON command the firmware suggests is replaced by where to free the pin (the web app, the SDK or the MCP session that holds it).

benchpod setup

Guided first-time setup. It walks the getting-started steps in order and skips each one that is already done:

  1. Find the pod over USB and on the LAN (like discover). With --connection it sets up the pod at that address or USB device instead. It stops when it finds none or more than one.
  2. Network: a pod with no address gets one. Choose Ethernet (setup waits up to two minutes for its DHCP lease) or Wi-Fi (SSID and password, like set-wifi). A pod this computer reaches over its USB network link (firmware 3.9.0+) already works from here, so setup goes on and says how to add Ethernet or Wi-Fi for other machines and CI.
  3. Board I/O voltage: 1.8 V or 3.3 V, the DUT's logic level (like la voltage). One already set is kept unless --la-voltage is given. The pod forgets it on a restart, so when this machine is signed in and the pod is registered to that account, setup also saves it (la_mv) to the pod's wiring profile on embeddedci.com, keeping every other field. The server applies it on every connect and the web app's setup checklist ticks "I/O voltage". A voltage already on the pod but missing from the profile is saved too. If the save fails, setup warns and goes on.
  4. embeddedci.com: an unregistered pod is registered to your account, signing in first (login) when there is no session, and its new wiring profile gets the I/O voltage. A pod registered to your account is named; one registered to another account is explained (sign in with the address your activation email was sent to, benchpod login --force) and left alone.
  5. Save the connection (like discover --save): the pod's Ethernet or Wi-Fi address becomes the default when it has one, so the default keeps working from other machines and after the pod moves to another desk or lab. A pod that only has its USB network link (firmware 3.9.0+, no Ethernet or Wi-Fi) is saved as usb, not as its 10.237.x.y address, which only works while it is plugged into this computer. A --connection given to setup is saved as it is. The pytest line in the next steps uses the saved connection.

It ends with the next steps: SWD wiring (SWCLK on LA11, SWDIO on LA12, GND), a benchpod flash line, the pytest line and the getting-started guide.

$ benchpod setup --la-voltage 3.3V

1/5  Find the pod
     Looking over USB and on the LAN (mDNS)...
     Found a BenchPod: USB /dev/cu.usbmodem1101, network 192.168.1.221:8080, firmware v3.8.0.

2/5  Network
     Done: the pod is on the network at 192.168.1.221:8080.

3/5  Board I/O voltage
     Set to 3.3 V on the pod and saved to its wiring profile on embeddedci.com.

4/5  embeddedci.com
     Done: registered to your account (you@example.com) as benchpod-baea06.

5/5  Save the connection
Default connection set to network/TCP 192.168.1.221:8080 (saved to ~/.config/benchpod-cli/config.json).

When the pod runs firmware older than the latest release, step 1 also prints the update command (benchpod flash-self --enter-dfu, then replug USB-C); setup does not flash on its own.

Flag Default Purpose
--la-voltage (ask) Board I/O voltage to set: 1.8V or 3.3V. Without it, a voltage already set is kept and an unset one is asked for.
--ssid (ask) Join this Wi-Fi network when the pod has no network address (instead of asking Ethernet or Wi-Fi).
--password-stdin off Read the Wi-Fi password from the first line of stdin (with --ssid). Without it the password is prompted for.
--no-register off Do not register the pod with embeddedci.com (it works over the LAN without an account).
--yes off Never prompt: register without asking, and fail with a message naming the flag to pass when an answer is needed.

benchpod set-connection <address|name|device|usb|embeddedci:name>

Save the default connection (a network address, a device path, usb or embeddedci:<name>) in the config file, so later commands can leave out --connection. A pod name (benchpod-a1b2c3 or a1b2c3) is looked up on the LAN over mDNS and saved as the pod's Ethernet or Wi-Fi address, so later commands do not look it up again; a pod heard only on USB (or on its USB network link) is saved as usb:benchpod-a1b2c3. The Python SDK and the MCP server read the same value (see Shared config). No flags.

benchpod discover

Find every pod on USB and on the LAN (mDNS), check each one answers its API, and report whether it is registered. When this machine is signed in (benchpod login), a registered pod is also checked against that account: discover names it when it is yours, and says so when it is registered to a different account (usually a second account; a pod set up for you is on the account your activation email was sent to). A pod advertises itself on each of its interfaces; when this computer hears it on the pod's USB network link (firmware 3.9.0+), discover shows and uses that address and lists the LAN one next to it (LAN address 192.168.1.220:8080). --save stores the LAN address when the pod has one, and usb when it was only heard on its USB link: a 10.237.x.y address only works while the pod is plugged into this computer, so it is never saved; usb finds the pod again on any port.

Flag Default Purpose
--save off Save the pod as the default connection when exactly one is found: its Ethernet or Wi-Fi address first, usb for a pod only on its USB link, else its USB console device.
--no-usb off Skip probing USB ports.
--no-network off Skip mDNS browsing of the LAN.
--wait 3s How long to browse the LAN for mDNS replies.
--probe-timeout 2s How long each USB port gets to answer status.

benchpod ping

Connectivity check over the network or through embeddedci.com. No flags.

benchpod status

A short summary of the pod over the network or through embeddedci.com (--connection embeddedci:<name>): firmware version (and whether a newer release exists, when GitHub answers within a few seconds), gateware, board, the board I/O (LA) voltage or "not set", target power (the internal 5 V and external eFuses), the power draw (each rail that is on and, on boards with the pod's own current monitor, the pod's draw and the total it takes from USB), the network address and link, the cloud registration (and, when this machine is signed in, whether the pod is on your account), and a cloud job's lease when one holds the pod. With firmware 3.9.0+ a USB line shows the pod's USB network address and warns when the USB port powering the pod offers only default USB power (500 mA), which may not carry the pod plus a DUT on the internal 5 V rail. Over USB it asks over the pod's USB network adapter when that is up, else it prints the console's status text.

BenchPod at 192.168.1.220:8080
  Firmware      3.8.0 (latest)
  Gateware      48
  Board         stm32h563 v3
  I/O voltage   3.3 V
  Target power  off
  Network       192.168.1.220 (ready), Wi-Fi connected
  Cloud         registered, connected, on your account as benchpod-baea06
Flag Default Purpose
--json off Print the pod's raw status JSON instead of the summary (network and embeddedci.com).

benchpod generate

Start DAC waveform output (network or embeddedci.com).

Give the waveform in volts with --path, --amplitude-v and --offset-v (or in mA on the 4-20 mA output with --amplitude-ma and --offset-ma): firmware 3.9.1 and later calibrates it and plays it at the DAC's full 16 bits, about 50 µV a step on the 3.3 V output. The CLI checks the pod has it first and refuses on older firmware. --amplitude and --offset are the older 8-bit levels: 256 steps over the output, no calibration.

benchpod generate --waveform sine --freq 1kHz --path 3v3 --amplitude-v 1.48 --offset-v 1.65
Flag Default Purpose
--waveform (required) sine, square or sawtooth.
--freq 1kHz Output frequency with its unit, e.g. 2kHz; a bare number is Hz.
--path DAC output for the volts: 3v3, 5v or 12v (firmware 3.9.1+).
--amplitude-v Peak in volts on --path, 16-bit and calibrated (firmware 3.9.1+).
--offset-v Center in volts on --path, with --amplitude-v.
--amplitude-ma Peak in mA on the 4-20 mA output (J9), 16-bit (firmware 3.9.1+).
--offset-ma Center in mA on the 4-20 mA output, with --amplitude-ma.
--amplitude 127 Half-scale amplitude in 8-bit levels, 0-127.
--offset 128 DC offset in 8-bit levels, 0-255.
--duration 0 How long to play, e.g. 2s or 500ms; 0 runs until the next command.
--duration-ms 0 The same in ms (older form of --duration).
--sample-rate auto FPGA sample-clock rate, e.g. 10MHz; leave it out to auto-pick.
--sample-rate-mhz auto The same in MHz (older form of --sample-rate).

benchpod capture

Blocking ADC snapshot (network). Prints the samples.

Flag Default Purpose
--samples 256 Number of ADC samples, 1-4096.
--output json Output format: json, csv or ndjson.
--sample-rate max ADC sample rate, e.g. 100kHz; leave it out for the maximum (400 kSPS).
--sample-rate-mhz max The same in MHz (older form of --sample-rate).

benchpod stream

Asynchronous ADC capture (network). Same flags and output as capture.

Flag Default Purpose
--samples 256 Number of ADC samples, 1-4096.
--output json Output format: json, csv or ndjson.
--sample-rate max ADC sample rate, e.g. 100kHz; leave it out for the maximum (400 kSPS).
--sample-rate-mhz max The same in MHz (older form of --sample-rate).

benchpod measure

DAC plus ADC loopback capture (network): play a waveform and capture the ADC.

Flag Default Purpose
--waveform (required) sine, square or sawtooth.
--freq 1kHz Output frequency with its unit, e.g. 2kHz; a bare number is Hz.
--amplitude 127 Half-scale amplitude code, 0-127.
--offset 128 DC offset code, 0-255.
--samples 256 Number of ADC samples, 1-4096.
--output json Output format: json, csv or ndjson.
--sample-rate auto Sample-clock rate, e.g. 10MHz; leave it out to auto-pick.
--sample-rate-mhz auto The same in MHz (older form of --sample-rate).

benchpod test

A diagnostic sample pattern made by the pod's MCU, without the FPGA (network).

Flag Default Purpose
--pattern sine sine, counter, ramp or const (const when only --value is set).
--value 255 Constant byte value, 0-255, for the const pattern.
--samples 256 Number of samples, 1-4096.
--output json Output format: json, csv or ndjson.

benchpod la

Logic-analyzer pin control: the bank voltage, pull-ups and step pulses. Pins are 1-14 or la1-la14. All of them work over the network and through embeddedci.com; la voltage also works over USB.

benchpod la voltage [VOLTAGE]

Show or set the LA bank's I/O voltage, which must match the DUT's: 1.8V or 3.3V (also 1800mV, 3.3 or 3300). Leave it out to show the setting. The pod refuses LA capture, UART, SWD flash, pull-ups and I2C-sensor emulation until it is set; 1.8 V needs a v3 pod, and the pod refuses a change while an LA pin is in use. Works over the network, over USB and through embeddedci.com. No flags.

The pod forgets the voltage on a restart. When this machine is signed in (benchpod login or BENCHPOD_API_KEY) and the pod is registered to that account, setting it over the network or an embeddedci: connection also saves it to the pod's wiring profile on embeddedci.com (only la_mv changes), which the server applies on every connect. A failed save is a warning; the pod setting still stands. Over USB it is set on the pod only.

benchpod la pullup PIN STATE

Switch an LA pin's pull-up: PIN is 1-8 (or la1), STATE is on or off. No flags.

benchpod la status [PIN]

Report the pull-up state of one pin, or the bitmask of all of them. No flags.

benchpod la step

Pulse an LA pin N times, for step/dir stepper drivers. The FPGA times the train.

Flag Default Purpose
--la (required) LA pin to pulse, e.g. 6 or la6.
--steps (required) Number of pulses, positive.
--delay (required) High and low half-period, e.g. 50us or 1ms (this or --delay-us).
--delay-us (required) The same in microseconds (older form of --delay).
--dir-la (none) Direction pin, driven before stepping.
--direction 0 Level for --dir-la: 0 or 1.

benchpod flash

Flash an SWD target wired to the pod, through host-side OpenOCD and the pod's CMSIS-DAP backend (network or USB). See Flashing: CMSIS-DAP. Over the network and embeddedci.com, --swclk, --swdio, --nreset and --target default to the pod's wiring profile (see One wiring profile).

--build, --repo and --ref flash a build uploaded to embeddedci.com (the upload-artifact GitHub action or the pytest build_report fixture) instead of a --file. Over embeddedci.com (--connection embeddedci:<pod>) embeddedci.com runs OpenOCD, so nothing needs installing here; on the network or USB the file is downloaded and flashed by the local OpenOCD. Both need benchpod login or BENCHPOD_API_KEY.

benchpod flash --connection embeddedci:bench-1 --repo acme/firmware --ref main
benchpod flash --build 3f9c2a1e-... --target-power 1

Without --artifact the build's .elf is flashed, else its .hex, else its .bin. An omitted --target falls back to the target the build recorded. A flash run by embeddedci.com always verifies and resets: --no-verify, --no-reset, --keep-reset-init, --openocd, -c and --openocd-arg only apply to a local run.

Flag Default Purpose
--swclk (profile swd_swclk, else required) LA pin for SWCLK, 1-14 (1 or la1).
--swdio (profile swd_swdio, else required) LA pin for SWDIO, 1-14.
--target (profile swd_target, else none) OpenOCD target config, passed as -f, e.g. target/stm32f1x.cfg.
--file (none) Firmware image to flash, used with --target.
--build (none) Flash a build uploaded to embeddedci.com instead of --file: its build id (the upload's build_id), or latest for the newest one matching --repo/--ref.
--repo (none) With --build: the GitHub repository of the uploaded build, owner/name.
--ref (none) With --build: its branch or tag, e.g. main.
--artifact (.elf, .hex, then .bin) With --build: the file to flash when the build holds several.
--server-side (on over embeddedci.com) With --build: embeddedci.com runs OpenOCD. --server-side=false downloads the file and flashes it here (the default on the network and USB).
--load-address (none) Load address for a raw .bin image.
--nreset (profile swd_nreset, else off) The target's NRST is wired to the pod's reset pin (DUT header J1 pin 22); turns on connect-under-reset. --nreset=false overrides the profile.
--no-connect-under-reset off Do not hold the target in reset while connecting.
--no-verify off Do not verify after programming.
--no-reset off Do not reset the target after programming.
--keep-reset-init off Keep the target config's reset-init clock boost (cleared by default because it glitches the link).
--target-power (none) Turn on a target power eFuse first: 1 (internal 5 V) or 2 (external).
--openocd (PATH) Path to the openocd binary.
--command, -c (none) Extra OpenOCD command, repeatable, appended last.
--openocd-arg (none) Extra raw OpenOCD argument, repeatable, appended last.

Pass --target (with --file or --build) or at least one -c/--openocd-arg.

benchpod spi-flash

Read, erase and program a 25-series SPI NOR flash on four LA pins (network only). See SPI flash. These flags apply to every subcommand; the pins you leave out come from the pod's wiring profile (see One wiring profile).

Flag Default Purpose
--sck (profile spi_sclk, else required) LA pin for SCK, 1-14.
--mosi (profile spi_mosi, else required) LA pin for MOSI, 1-14.
--miso (profile spi_miso, else required) LA pin for MISO, 1-14.
--cs (profile spi_cs, else required) LA pin for CS, 1-14.
--hz 1000000 SCK rate in Hz; the pod uses the nearest rate at or below it (190 kHz to 6 MHz).
--mode 0 SPI mode, 0 or 3.
--nreset off Hold the DUT in reset through the pod's reset pin for the whole run.

benchpod spi-flash id

Read the JEDEC ID, size and status register. No flags of its own.

benchpod spi-flash read OUT

Read flash contents into a file (- for stdout).

Flag Default Purpose
--addr 0 Flash address to read from.
--len (to the end) Bytes to read, e.g. 4096 or 1M.

benchpod spi-flash write FILE

Erase, program and verify a raw image.

Flag Default Purpose
--addr 0 Flash address to write at, e.g. 0x10000.
--no-erase off Do not erase first (the range must already be erased).
--no-verify off Do not read each chunk back.

benchpod spi-flash erase

Erase a range or the whole part.

Flag Default Purpose
--addr 0 First address to erase.
--len (none) Bytes to erase, e.g. 65536 or 1M.
--chip off Erase the whole part instead of a range.

benchpod flash-self [firmware.bin | URL]

Flash the pod's own firmware over USB DFU (STM32) with dfu-util, then install its blobs. With no argument it fetches the latest release. See Flashing the pod itself.

Flag Default Purpose
--enter-dfu off First reboot a running pod into DFU over its USB console.
--wait 1m (60s) How long to wait for the pod to show up in DFU mode.
--firmware-version (latest) Fetch this release tag instead of the latest.
--firmware-url (latest) Download the firmware from this URL instead.
--address 0x08000000 Flash base address (STM32 main flash).
--dfu-util (PATH) Path to the dfu-util binary.
--no-leave off Stay in DFU after flashing instead of starting the firmware.
--skip-blobs off Do not install the gateware and ESP32-C3 images afterwards.
--blobs-dir (next to the firmware) Take the blobs from this directory.
--network off Update over the pod's network API instead of USB DFU: its USB network adapter (firmware 3.9.0+) or the --connection address. Needs running firmware; the DFU-only flags do not apply.

It prints a signature: line for the release's .sig (report only, see dfu-util) and exits non-zero when the firmware does not fit the pod's flash, the download's checksum does not match, or dfu-util fails.

benchpod install-blobs

Install the gateware and ESP32-C3 images the pod's firmware goes with (USB: over the pod's USB network adapter when it is up, firmware 3.9.0+, else its console). By default they come from the release that matches the firmware the pod runs. Each blob's signature is reported; for now a missing or failing one never stops it.

Flag Default Purpose
--release (the pod's) Take the blobs from this firmware release tag.
--dir (none) Take the blobs from this directory (holding blobs-manifest.json), e.g. stm32h563/build/blobs.
--only (all) Comma-separated slots to install: gw0, gw1, esp.
--force off Install even the blobs the pod already holds.

benchpod dfu

Reboot an STM32 pod into its USB DFU bootloader (USB). Then flash it with flash-self or dfu-util.

Flag Default Purpose
--yes off Skip the confirmation prompt.

benchpod bootsel

Reboot a (retired) RP2350 pod into its UF2 bootloader (USB).

Flag Default Purpose
--yes off Skip the confirmation prompt.

benchpod set-wifi

Save Wi-Fi credentials and join (USB). With firmware 3.9.0+ this turns the pod's USB networking off (Wi-Fi and USB networking are one or the other; clear-wifi turns it back on). Installs the ESP32-C3 image first when the pod lacks it; if the pod's image slots cannot be read, it stops and says so (--skip-blobs sets Wi-Fi without the check).

Flag Default Purpose
--ssid (required) Wi-Fi SSID.
--password (prompt) Wi-Fi password (visible in shell history; prefer the prompt or --password-stdin).
--password-stdin off Read the password from the first line of stdin.
--skip-blobs off Do not install a missing ESP32-C3 image first.

benchpod show-network

The pod's IP (the wired lease first) plus the stored Wi-Fi SSID, state and RSSI (USB). No flags.

benchpod clear-wifi

Erase the stored Wi-Fi credentials (USB). Wi-Fi goes down at once, no reboot needed, and USB networking (firmware 3.9.0+) comes back on. Firmware after 3.7.0 also erases the copy older firmware left on the ESP32-C3 Wi-Fi chip, which takes a few seconds more. No flags.

benchpod usb-bridge

Carry the pod's cloud link over USB through this computer, until Ctrl-C (USB). The pod's USB network adapter has no router, so the pod cannot reach embeddedci.com over it by itself. The bridge runs an HTTP CONNECT proxy on this computer's end of the USB link and sets it as the pod's cloud proxy over that link (never the USB console: the web app's setup wizard, terminals and other commands keep it); the pod then connects to embeddedci.com through this computer's internet connection, with no Ethernet or Wi-Fi. When it stops it clears the pod's proxy again. The proxy serves only the pod, and only tunnels to port 443. It refuses to replace a proxy the pod already has (benchpod cloud proxy clear --connection usb removes it). Needs firmware 3.9.0 or later and no Wi-Fi configured on the pod.

On Windows the USB link is an unidentified network, which the firewall treats as Public: allow benchpod through Windows Defender Firewall when it asks, or add an inbound rule for TCP port 3128 from 10.237.0.0/16, or the pod's connections to the bridge are blocked.

Flag Default Purpose
--port 3128 TCP port of the proxy on this computer's USB address.
--keep-proxy off Leave the pod's cloud proxy set when the bridge stops.
--watch off Wait for the pod and pick it up again after it is unplugged or restarted (how the background service runs it). It watches this computer's network interfaces for the pod's USB network link and reads, sets and clears the pod's proxy over that link; it never opens the USB console. With Wi-Fi configured on the pod there is no USB link, so it does nothing.

benchpod usb-bridge install

Run benchpod usb-bridge --watch in the background for this user, at every login: a launchd agent on macOS (~/Library/LaunchAgents/com.embeddedci.benchpod-usb-bridge.plist, log in ~/Library/Logs/benchpod-usb-bridge.log), a systemd user unit on Linux (~/.config/systemd/user/benchpod-usb-bridge.service; loginctl enable-linger keeps it running while you are logged out), or a scheduled task at logon on Windows (BenchPod usb-bridge). A pod with only USB then keeps its cloud link, and so the web app, after this computer restarts. --connection is passed on, so --connection usb:<name> installs it for one of several pods. On Windows, also let the pod in through Windows Defender Firewall (see usb-bridge).

Flag Default Purpose
--port 3128 TCP port of the proxy on this computer's USB address.

benchpod usb-bridge uninstall

Stop and remove the background service, then clear the pod's cloud proxy when it still points at this computer (the pod must be plugged in for that). No flags.

benchpod usb-bridge status

Show whether the background service is installed and running. No flags.

benchpod usb-reattach

Make the pod drop off the USB bus for half a second and connect again (USB). The computer then sets up the console and the USB network adapter anew, as after replugging the cable. macOS ignores a new network adapter while the screen is locked: run this after unlocking when the pod was plugged in or rebooted meanwhile. Needs firmware 3.9.0 or later. No flags.

benchpod login

Authenticate with embeddedci.com (device-login flow) and print the account's email address: Logged in as you@example.com. A no-op when a usable session exists (Already logged in as you@example.com). Against a server or a token file that predates the email, the user id is shown instead.

Flag Default Purpose
--server-url https://www.embeddedci.com embeddedci-server base URL.
--token-file ~/.config/benchpod-cli/token.json Token cache.
--no-browser off Print the approval URL instead of opening a browser.
--force off Authenticate again even when a session exists.

benchpod logout

Delete the cached tokens. Local only: no pod is deregistered.

Flag Default Purpose
--token-file ~/.config/benchpod-cli/token.json Token cache.

benchpod register

Register the pod reached with --connection to your account and provision it to connect to embeddedci.com. Needs benchpod login first. Exits non-zero, with the pod's last connection error, when the pod does not connect within --wait.

Flag Default Purpose
--device-name (the pod's own name) The pod's name on embeddedci.com, URL-safe and unique in the organization, e.g. bench-01.
--wait 30s How long to wait for the pod to connect; 0 does not wait.
--pod-host api.embeddedci.com Host the pod connects to (api.embeddedci.com for embeddedci.com, else the --server-url host).
--insecure-skip-verify off Provision the pod to skip TLS certificate checks (bring-up only).
--server-url https://www.embeddedci.com embeddedci-server base URL.
--token-file ~/.config/benchpod-cli/token.json Token cache.

benchpod deregister

Detach the pod from your account (its data is kept) and clear its cloud configuration. By default the pod reached with --connection is identified by its key; --connection embeddedci:<name> is the same as --device-name <name>. It signs in with BENCHPOD_API_KEY when that is set and --token-file is not given, else with the benchpod login session; the policy commands do the same.

Flag Default Purpose
--device-name (none) Deregister the pod with this name instead (it may be offline).
--device-id (none) Deregister the pod with this id instead.
--keep-pod-config off Leave the pod's cloud configuration in place.
--server-url https://www.embeddedci.com embeddedci-server base URL.
--token-file ~/.config/benchpod-cli/token.json Token cache.

benchpod lan-policy

Show the pod's LAN policy: open, locked or off. See Pod policies. Over the cloud with --device-name/--device-id or --connection embeddedci:<name>, or over USB; the LAN can only show it. These flags apply to show and set too.

Flag Default Purpose
--device-name (none) Go through embeddedci.com to the registered pod with this name.
--device-id (none) Go through embeddedci.com to the registered pod with this id.
--server-url https://www.embeddedci.com embeddedci-server base URL.
--token-file ~/.config/benchpod-cli/token.json Token cache.

benchpod lan-policy show

Same as benchpod lan-policy.

benchpod lan-policy set <open|locked|off>

Set the LAN policy (cloud with an owner or admin role, or USB).

benchpod usb-network [show|on|off]

Show the pod's USB network setting, or turn its USB network adapter on or off (firmware 3.9.0 and later). With off the pod re-enumerates as its USB console only, so the computer sees no network adapter; on brings the adapter back. The setting is stored and is separate from lan-policy, which covers Ethernet and Wi-Fi.

Only the device changes it: off over --connection usb (the USB link itself, or the console), on over the USB console (the link does not exist while it is off). Over Ethernet, Wi-Fi or --connection embeddedci:<name> it can only be read. benchpod status shows the adapter as "network off (turned off on the pod)".

benchpod sig-policy

Show the pod's signature policy: audit, permissive or required. Same transports as lan-policy; the cloud can only make it stricter. These flags apply to show and set too.

Flag Default Purpose
--device-name (none) Go through embeddedci.com to the registered pod with this name.
--device-id (none) Go through embeddedci.com to the registered pod with this id.
--server-url https://www.embeddedci.com embeddedci-server base URL.
--token-file ~/.config/benchpod-cli/token.json Token cache.

benchpod sig-policy show

Same as benchpod sig-policy.

benchpod sig-policy set <audit|permissive|required>

Set the signature policy. Loosening it needs the USB console.

benchpod identity

Show the pod's device identity (USB). See Device identity. No flags.

benchpod identity show

Same as benchpod identity.

benchpod identity wipe

Erase the pod's device key and make a new one (USB only). The pod then needs to be registered again.

Flag Default Purpose
--yes off Do not ask for confirmation.

benchpod cloud

How the pod reaches embeddedci.com: a company CA and an HTTP proxy. See Cloud link. Changes need USB or --connection embeddedci:<name> (an organization owner or admin; ca set needs USB); the LAN can show them.

benchpod cloud ca

Show the company CA certificates the pod holds (same as cloud ca show).

benchpod cloud ca show

Show the company CA certificates the pod holds. No flags.

benchpod cloud ca set <file.pem>

Check a PEM file (at most 16 KB) and store its CA certificates on the pod. Over USB only: through the pod's USB network adapter on firmware 3.9.0+ (which may change the CA, physical presence; the pod's Ethernet and Wi-Fi refuse it, and embeddedci.com does not carry the upload), or the USB console on older firmware. No flags.

benchpod cloud ca clear

Remove the company CA; the pod trusts only its built-in roots again. No flags.

benchpod cloud proxy

Show the pod's HTTP proxy (same as cloud proxy show).

benchpod cloud proxy show

Show the pod's HTTP proxy. The password is never reported. No flags.

benchpod cloud proxy set <host:port>

Set the pod's HTTP proxy.

Flag Default Purpose
--user (none) Proxy user name (Basic authentication).
--password (prompt) Proxy password (visible in shell history; prefer the prompt or --password-stdin).
--password-stdin off Read the password from the first line of stdin.

benchpod cloud proxy clear

Remove the HTTP proxy; the pod connects to the cloud directly. No flags.

Development

Makefile targets

Target What it does
make build Build bin/benchpod for the host.
make run Build and run the binary (pass args via ARGS="...").
make test go test ./....
make vet go vet ./....
make fmt go fmt ./....
make tidy go mod tidy.
make clean Remove bin/.

Releasing

Releases are cut by GoReleaser (.goreleaser.yaml) from the release workflow, triggered by pushing a v* tag:

git tag v0.1.0 && git push origin v0.1.0

The tag is the version (GoReleaser sets main.version from it). Before anything is published, the workflow checks that the tag is vX.Y.Z on a commit in main and runs the full ci workflow (vet, tests, snapshot build).

The job runs on a macOS runner (the macOS binaries need cgo/IOKit; Linux and Windows cross-compile from there with cgo off) and produces:

  • the GitHub Release in this repo, with all six archives + checksums.txt — created with the workflow's default GITHUB_TOKEN;
  • the updated benchpod cask in embeddedci-com/homebrew-tap. That tap is a separate repo, so the default token can't push to it; the cask step uses a TAP_GITHUB_TOKEN secret (a PAT with write access to the tap repo).

Every push/PR also runs a GoReleaser --snapshot build (ci workflow) so a cross-platform break is caught before tagging.

Layout

Path Purpose
cmd/benchpod-cli/ CLI entry point and all subcommands (Cobra + Viper).
internal/tcpclient/ TCP/JSON API client (commands, sessions, identity, SWD).
internal/serialconsole/ USB CDC-ACM serial console client and port auto-detection.
internal/openocd/ OpenOCD wrapper used by the flash command.
internal/spiflash/ SPI NOR flash driver over the pod's spi_* JSON commands (spi-flash).
internal/benchpodconfig/ Local config (the saved set-connection target).
internal/serverapi/ HTTP client for embeddedci-server (device-login, refresh).
internal/authstore/ Token cache (~/.config/benchpod-cli/token.json).

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages