CLI reference

benchpod is the command-line tool for driving a pod by hand. It talks to the pod over its TCP/JSON API (port 8080, over Ethernet, Wi-Fi or the USB-C cable) or its USB console: signal generation, ADC capture, logic-analyzer pins, Wi-Fi config, SWD and SPI flash programming, and the pod's own firmware, policies and cloud link. For automated tests, use the pytest library instead; the CLI is for interactive bring-up and one-off operations. Every command and flag is listed in the command reference at the end.

Install

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

The binary isn't Apple-notarized, so the cask strips the quarantine flag on install. It also installs dfu-util, which flash-self needs.

# or the plain binary (the releases/latest/download URL is always the newest release)
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/

On an Intel Mac use benchpod_Darwin_x86_64.tar.gz. If you download the archive with a browser instead, clear the quarantine flag once with xattr -d com.apple.quarantine ./benchpod.

With Homebrew, brew upgrade --cask benchpod picks up later releases. Pre-built binaries for macOS, Linux and Windows (amd64 + arm64) are attached to every GitHub release. flash-self needs dfu-util:

The Homebrew cask installs it. Without the cask: brew install dfu-util.

Flashing a target also needs OpenOCD (see Flashing below).

Setting up a new pod

benchpod setup is the one command for a new pod. It runs five steps and says what it did in each: 1/5 Find the pod (USB and the LAN, as discover does; the global --connection picks one instead), 2/5 Network (waits for an Ethernet lease, or joins Wi-Fi with --ssid), 3/5 Board I/O voltage, 4/5 embeddedci.com (signs in with login and registers the pod) and 5/5 Save the connection. It ends with the next steps: wiring, a first flash, a first test. A step that is already done is skipped, so running it again is safe, and a pod registered to another account is left alone.

# Interactive: asks what it needs (Wi-Fi or Ethernet, the I/O voltage, whether to register)
benchpod setup

# A pod you already know the address of, Wi-Fi from a secret, a 1.8 V board, no prompts
benchpod --connection /dev/cu.usbmodem1103 setup \
  --ssid my-bench-wifi --password-stdin --la-voltage 1.8V --yes < wifi-password.txt

With --yes it never prompts: it registers without asking, and stops naming the flag to pass when it needs an answer (for example --la-voltage). --no-register skips embeddedci.com for a pod you only use over the LAN. The flags are in the reference.

benchpod status then shows the pod at a glance, including whether a newer firmware is out and whether the pod is on your account; --json prints the raw status instead. Over USB it prints the console's own status text.

$ benchpod status
BenchPod at 192.168.1.221: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

Finding a pod

benchpod discover answers two questions at once: can a pod be found, and does it work? It probes every USB port for a bench-pod console and browses the LAN over mDNS, then calls the API of each pod it found on the network, so the report says whether the pod actually answers commands and whether it is registered with the cloud, rather than only that something replied to a broadcast. A pod reachable both ways is reported once, with the two entries matched up.

It also reports whether each pod is already registered to an account, on both transports: the USB half reads it off the pod's own console, so the answer comes even from a pod with no network yet. That is what tells you whether a pod you were handed still needs benchpod login and benchpod register, or arrived already claimed and needs nothing. Firmware too old to report it says nothing rather than guessing.

When this computer is signed in (benchpod login), a registered pod is also checked against that account: discover prints its name when it is yours, and says registered to a different embeddedci.com account when it is not. That is usually a second account. A pod set up for you is on the account your activation email was sent to; sign in with that address (benchpod login --force).

Because it probes USB, it finds a pod that has no network at all, which is the state a pod ships in. When nothing is found, the output lists the ports it probed and the likely causes (charge-only cable, no firmware, mDNS blocked). --save stores the address as the default connection when exactly one pod is found, preferring the network address; --no-usb / --no-network restrict the search, and --wait / --probe-timeout tune how long each half gets.

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 valueTransport
192.168.1.5[:8080]TCP/JSON API over the network: Ethernet, Wi-Fi, or the pod's USB network address
/dev/tty…, COM3USB console, explicit device path
usbthe pod on this computer's USB-C cable, found by itself: the full API over the USB network adapter (firmware 3.9.0 or later), else the USB console
embeddedci:<name>the pod named <name> on embeddedci.com, for the commands that can go through it, such as status, ping and la voltage (see Over embeddedci.com). For deregister it is the same as --device-name <name>.
(omitted)the default saved by benchpod set-connection

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

With --connection usb and firmware 3.9.0 or later, every command works over the pod's USB network adapter, including install-blobs, cloud ca and cloud proxy (the USB link may change the company CA and the proxy; the pod's Ethernet and Wi-Fi may not). Firmware 3.9.0 has no uploads on its USB console, so it needs benchpod 0.6.0 or later. On older firmware the USB console runs status, la voltage, install-blobs and the cloud settings, as before. set-wifi, show-network, clear-wifi, dfu, identity, lan-policy, sig-policy and bootsel use the USB console (physical presence). cloud ca and cloud proxy also work over embeddedci.com (except cloud ca set).

Over embeddedci.com

embeddedci:<name> reaches a registered pod through embeddedci.com from any network, whether it comes from --connection, BENCHPOD_CONNECTION or the saved default. These commands work that way: status (including --json), ping, la voltage, la pullup, la status, la step, generate, cloud ca show / clear, cloud proxy show / set / clear, lan-policy, sig-policy and deregister. It authenticates with BENCHPOD_API_KEY (sent as Authorization: ApiKey), else the benchpod login session; BENCHPOD_API_BASE picks the server.

These refuse over the cloud and name the alternative, so run them on the LAN or over USB: capture, stream, measure, test, flash, spi-flash, register, cloud ca set, discover, set-wifi, show-network, clear-wifi, install-blobs, bootsel, dfu and flash-self. A saved embeddedci: default does not get in the way of the USB-only commands: they find the pod on USB by themselves. A pod that is busy (another holder has its lease) exits with code 4, a pod that is offline on embeddedci.com with 5, and a command that needs an owner or admin role you lack with 3.

The saved connection

benchpod discover --save and benchpod set-connection (which also accepts embeddedci:<name>) store the default connection in ~/.config/benchpod-cli/config.json, a file with "version": 1. It is shared: the Python SDK (BenchPod() with no connection) and the MCP server (connect with no argument) read the same default, after an explicit connection and BENCHPOD_CONNECTION. The pytest plugin uses it only when you pass --benchpod-connection=saved.

benchpod setup and discover --save save the pod's Ethernet or Wi-Fi address when it has one, so the default keeps working from other machines and after the pod moves. A pod that only has its USB network link (firmware 3.9.0 and later) is saved as usb, never as its 10.237.x.y address, which only works while the pod is plugged into this computer.

The wiring profile

Over a network connection, when you are signed in and the pod is on your account, flash and spi-flash take their pins from the wiring profile stored on embeddedci.com (the Wiring tab of the pod's workspace): --swclk, --swdio, --nreset and --target default to swd_swclk, swd_swdio, swd_nreset and swd_target; --sck, --mosi, --miso and --cs default to spi_sclk, spi_mosi, spi_miso and spi_cs. Flags you pass win (--nreset=false overrides a profile that says true). Target power never comes from the profile. If the lookup fails (it gives up after 6 seconds), the CLI prints one warning and uses the defaults.

Commands

# A new pod: the guided setup (find it, network, I/O voltage, register, save the connection)
benchpod setup

# 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

# Save a default connection so later commands can omit --connection:
benchpod set-connection 192.168.1.5
benchpod set-connection usb

# Reachability / state:
benchpod ping
benchpod status           # readable summary; --json for the raw status
# Instruments (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 ...                               # asynchronous ADC capture
benchpod test ...                                 # diagnostic pattern, no FPGA
benchpod la pullup 3 on                           # pull-up on LA3
benchpod la status
benchpod la step --la 6 --steps 200 --delay 500us --dir-la 7 --direction 1

# A pod on this computer's USB-C cable only: keep it online on embeddedci.com
benchpod usb-bridge install

# Wi-Fi (always over the pod's USB console; turns the pod's USB network off):
benchpod set-wifi --ssid my-bench-wifi
benchpod show-network     # the pod's IP (wired lease included), SSID, state, RSSI
benchpod clear-wifi

# Firmware:
benchpod flash ...        # the target's firmware, SWD via OpenOCD's CMSIS-DAP backend
benchpod spi-flash ...    # read/erase/program an SPI NOR flash on four LA pins
benchpod flash-self       # the pod's own firmware over USB DFU (latest release); a blank pod needs BOOT0 + RESET first
benchpod flash-self --enter-dfu   # same, restarting a pod that already runs firmware into DFU for you
benchpod install-blobs    # the gateware and ESP32-C3 images the pod's firmware goes with (USB)
benchpod dfu              # reboot the pod into its DFU bootloader (USB console only)

# Cloud account (optional):
benchpod login [--server-url https://www.embeddedci.com]
benchpod register --connection 192.168.1.5      # or --connection usb for a pod on USB-C
benchpod deregister ...
benchpod logout

# Pod settings (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 set permissive --connection usb
benchpod identity --connection usb                         # show the device identity
benchpod cloud ca set acme-root.pem --connection usb       # company CA for a TLS-inspecting proxy
benchpod cloud proxy set proxy.corp:3128 --user alice --connection usb

Run benchpod <command> -h for the flags of any subcommand, or see the command reference. benchpod completion bash|zsh|fish|powershell prints a shell completion script.

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.

Exit codes

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

CodeMeaning
0Success.
1Any other error.
2Usage: a bad flag, argument or subcommand.
3Refused: the pod refused the command (a locked LAN, a missing owner or admin role, a bad request).
4Busy: the pod or one of its engines is in use (a cloud job's lease held by someone else, another session).
5Unreachable: 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.

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, so a flash is one round-trip per DAP command instead of thousands of per-bit round-trips, so it is fast even over the network. It works over the network and over USB, and needs a recent OpenOCD with the cmsis_dap_tcp backend (post-0.12.0, below). The CLI checks for the backend up front and fails with clear advice if it's missing.

# Homebrew's stable open-ocd is 0.12.0, so build it from HEAD
brew install --HEAD open-ocd

Name the SWD pins with --swclk and --swdio, then pass --target with --file (or your own -c commands). With the target's NRST wired to the pod's reset pin, add --nreset: the target is held in reset while OpenOCD connects, so running firmware cannot turn SWD off first. --target-power 1 powers the target first.

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 (the spi_master capability), the LA voltage set, and a network connection.

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, programs it and has the pod read every chunk back. --hz picks the SCK rate (rounded 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 for the whole run, so its own controller stays off the bus. The pins and the reset are always released at the end, also after an error or Ctrl-C. The pod does not clear block-protect bits.

Updating the pod itself

flash-self writes the pod's own firmware over USB DFU with dfu-util. With no argument it fetches the latest release; the pod must be in its DFU bootloader (BOOT0 high at reset for a blank pod, or --enter-dfu for a running one), and it waits up to --wait for it. It refuses an image that does not fit the pod's flash.

Over the network. --network sends the image over the pod's own API instead: its USB network adapter (firmware 3.9.0 or later, no Wi-Fi set up) or the --connection address over Ethernet or Wi-Fi. The pod stages it in PSRAM, checks its SHA-256 and signature, writes its flash and restarts, and the CLI then installs its blobs the same way. No dfu-util, but the pod must be running firmware, and a network access setting of Instruments only or None refuses it over Ethernet and Wi-Fi. DFU stays the default because it also recovers a pod whose firmware does not start. A pod on embeddedci.com is updated from the web app.

Firmware 3.9.0 and later: update the CLI (brew upgrade --cask benchpod) and the Python packages (pip install --upgrade embeddedci embeddedci-mcp) before you update the pod. 3.9.0 changes how the pod connects over USB, and older tools may not reach it afterward. The update notice on the BenchPod page and the Update firmware window say so too.

benchpod flash-self                         # latest release
benchpod flash-self --enter-dfu             # reboot a running pod into DFU first
benchpod flash-self ./bench_pod_stm32.bin   # a local build
benchpod flash-self --firmware-version v3.5.1
benchpod flash-self --network               # over the USB network adapter (firmware 3.9.0+), no dfu-util
benchpod flash-self --network --connection 192.168.1.42   # over Ethernet or Wi-Fi
benchpod install-blobs --dir stm32h563/build/blobs   # blobs from a local build

Firmware from 3.5.0 on keeps the iCE40 gateware and the ESP32-C3 Wi-Fi image in the pod's W25Q flash. After writing the firmware, flash-self installs the ones the pod lacks from the same release; install-blobs does that step on its own (--release, --dir, --only gw0,gw1,esp,bridge, --force), and set-wifi runs it first when the ESP32-C3 image is missing.

Signatures. Releases can publish a signed manifest, <asset>.sig, next to the firmware and each blob. flash-self and the blob install check it and print one line, such as signature: ok (release-1 key 9b5cc58445e62d88, release 3.6.0) or signature: none (this release is not signed). Firmware that checks signatures itself also gets the manifest and its verdict is printed as pod signature check: .... For now the CLI only reports: a missing or failing signature never stops a flash or an install, and the exit status does not depend on it. What the pod itself accepts is its sig-policy.

Update reports. When you are signed in (benchpod login or BENCHPOD_API_KEY), the CLI tells embeddedci.com about each firmware change it makes: flash-self, an update over the USB network or the LAN, and install-blobs. The report carries the pod's identity key and name, the old and new firmware and gateware versions, how it was installed and the CLI version, so the pod's history on embeddedci.com includes updates that did not go through the web app. It is best effort: a failed report never fails the update. Turn it off with BENCHPOD_NO_FIRMWARE_REPORT=1 or --no-firmware-report. Signed out, nothing is sent.

USB-C, Ethernet and Wi-Fi

A pod connects in one of three ways. At a desk, one USB-C cable to your computer carries power and the full API: from firmware 3.9.0 the pod is a USB network adapter as well as a console, on macOS, Linux and Windows 11 with no driver (Windows 10 is supported but not tested), and two pods on one computer each get their own address (not yet tested with two physical pods). In a lab or rack it sits on Ethernet, powered from USB-C. On v3 pods Wi-Fi is an option, set once with set-wifi; while Wi-Fi is set up the USB network is off.

Over Ethernet or Wi-Fi the pod reaches embeddedci.com on its own. On USB-C alone it has no internet of its own, so the usb-bridge on the computer carries its cloud link. Install it once as a background service and the pod stays online in the web app after the computer or the pod reboots:

# Once per computer: run the bridge as a background service (starts at login)
$ benchpod usb-bridge install

# Is it installed and running, and which pod does it carry?
$ benchpod usb-bridge status

# Remove the service (also clears the pod's cloud proxy)
$ benchpod usb-bridge uninstall

# Or run it in the foreground for this session only (Ctrl-C stops it)
$ benchpod usb-bridge

Pod policies: LAN and signatures

Two settings the pod keeps in its config flash. benchpod lan-policy and benchpod sig-policy show them; ... set <value> changes them. Pick the pod with --device-name / --device-id (through embeddedci.com) or with --connection usb. Over the LAN they can only be shown.

The web app calls lan-policy Network access without sign-in (the pod's Settings tab): open is Full, locked Instruments only and off None. It covers the API on Ethernet and Wi-Fi, which has no login. The USB connection stays on under every value, because using it needs physical access to the pod.

PolicyValueMeaning
lan-policyopenThe LAN port allows everything (the default).
lockedRead and instrument control only; config and firmware need the cloud or USB.
offNo listener and no mDNS on Ethernet and Wi-Fi. The cloud and USB keep working.
sig-policyauditEvery image is accepted; signatures are only reported.
permissiveUnsigned images are accepted, a bad signature is refused.
requiredUnsigned images are refused.
$ benchpod lan-policy --device-name benchpod-baea06
LAN policy of benchpod-baea06: locked (read and instrument control only on the LAN)
$ benchpod sig-policy set required --device-name benchpod-baea06
  • 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; loosening it needs the USB console.
  • With lan-policy off, LAN tools (SCPI/VISA, LAN discover) stop working until it is set back over the cloud or USB.

LAN TLS

Pods with firmware 3.9.3 or later serve the same API on port 8443 with TLS. For an address on the default port, benchpod uses 8443 when the pod has it and pins the pod's certificate the first time (in known_pods.json next to the config, shared with the SDK); older pods stay on plaintext 8080. A different certificate later stops the command with both fingerprints. --tls on|off overrides, --tls-ca verifies a certificate signed by your own CA instead of pinning.

$ benchpod lan-tls --connection 192.168.1.220
LAN TLS of 192.168.1.220:8080 (connected over TLS)
  port        8443
  https-only  off
  certificate CN=benchpod-baea06 (generated, EC P-256), valid to 9999-12-31
  sha256      3f1c...
  pinned      yes, for 192.168.1.220:8443
$ benchpod lan-tls https-only on
$ benchpod lan-tls set-cert --cert pod.pem --key pod.key
$ benchpod lan-tls trust        # after a change made elsewhere
  • https-only on turns plaintext 8080 off on Ethernet and Wi-Fi (the USB cable keeps it). The web app's pod Settings has the same switch, HTTPS-only API.
  • The pod refuses https-only off, regenerate and set-cert over plaintext Ethernet or Wi-Fi: use TLS (the default), --connection usb or --connection embeddedci:<name>.
  • VISA raw sockets have no TLS: with HTTPS-only on, run a local wrapper such as socat with the certificate from benchpod lan-tls show --pem.

Device identity

The pod's identity is an Ed25519 key in its own flash; its name (benchpod-a1b2c3) comes from the public key, and embeddedci.com knows the pod by it. benchpod identity shows it, and says why when the pod has none. benchpod identity wipe erases the key and makes a new one; it works only over the USB console, so it needs someone at the pod.

$ benchpod identity --connection usb
$ benchpod identity wipe --connection usb     # asks first; --yes skips the question

After a wipe the pod is a new pod to embeddedci.com: deregister the old record with benchpod deregister --device-name <old name> (its data is kept and the name is freed), power-cycle the pod, register it again with the old name, and set its wiring profile again.

On a network whose proxy inspects TLS, the pod's cloud link fails because 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 ca and benchpod cloud proxy set both over the USB console; the LAN can show them but not change them.

$ 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 ...
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:
The pod reconnects to the cloud through proxy.corp:3128.
  • cloud ca set takes a PEM file (at most 16 KB), prints each certificate's subject and SHA-256, refuses anything that is not a certificate, and uploads only the certificates. They are trusted in addition to the built-in roots. cloud ca clear removes them.
  • cloud proxy set <host:port> with --user takes the password from a prompt, --password-stdin or --password. The pod never reports it back. 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. cloud proxy clear removes the proxy.
  • The pod reconnects to the cloud after every change.

Cloud auth

Direct firmware commands are unauthenticated and send no tokens; they assume a trusted local network.benchpod login (device-login flow) and benchpod register authenticate with embeddedci-server for cloud features such as driving a pod by name or registering a new pod. See Security & trusted repos for the model.

  • benchpod register --connection <pod-ip>: claim a pod into your org and wait until it is connected. It is named after the pod (benchpod-a1b2c3); names are unique within an org, so pick another with --device-name if one is taken. The pod is provisioned to connect to api.embeddedci.com. Running it again on the same pod is always safe: it keeps the pod's record, name and history.
  • benchpod login: authenticate the CLI for cloud operations. It prints the account it signed in to (Logged in as you@example.com), so a pod set up for you can be checked against the address your activation email was sent to. benchpod logout deletes the cached tokens; it does not deregister anything.
  • benchpod deregister --connection <pod-ip>: detach a pod from your org. Its record is disabled, not deleted: its captures, waveforms and wiring are kept, and registering the same pod again restores them. Registering it for another account starts that account with a fresh record. A pod you cannot reach can be deregistered with --device-name or from the BenchPod page. Deleting or disabling an account deregisters its pods automatically; pods in a shared org pass to another member.

Command reference

Every command and flag of the current CLI release. "Network" means the pod's TCP/JSON API (--connection <address>), "USB" its USB console.

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 and token.json for the embeddedci.com session.

FlagDefaultPurpose
--connection(saved set-connection target)Network address, pod name (benchpod-a1b2c3, found on the LAN over mDNS, else on USB), device path, usb, or embeddedci:<name> to go through embeddedci.com; see Connection above.
--config-file~/.config/benchpod-cli/config.jsonThe CLI's config file, where set-connection saves the default.
--no-firmware-reportoffDo not report firmware and blob updates to embeddedci.com (also BENCHPOD_NO_FIRMWARE_REPORT=1); see Update reports above.
--output-filename(stdout)Write command output to this file instead of stdout.
--timeout0 (per-command default)Overall command deadline; 0 uses each command's own default.
--tlsautoTLS to the pod's LAN API: auto (port 8443 when the pod has it, pinned on first use), on (TLS only) or off (plaintext 8080). Also BENCHPOD_TLS.
--tls-ca(none)PEM file of CA certificates: verify the pod's certificate and name against them instead of pinning. Also BENCHPOD_TLS_CA.

Connection and discovery

benchpod setup

The guided first-time setup, in five steps: find the pod (USB and the LAN; the global --connection picks one instead), put it on the network (Ethernet, or Wi-Fi with --ssid), set the board's I/O voltage, sign in to embeddedci.com and register the pod, and save the connection: the pod's Ethernet or Wi-Fi address when it has one, else usb for a pod reached only over its USB network link (its 10.237.x.y address only works while the pod is plugged into this computer). A --connection given to setup is saved as it is. It ends with the next steps. Safe to run again: a step that is already done is skipped.

FlagDefaultPurpose
--la-voltage(keep the current one, or ask)The board's I/O voltage: 1.8V or 3.3V.
--ssid(none)Join this Wi-Fi network when the pod has no network address (2.4 GHz only). It prompts for the password.
--password-stdinoffRead the Wi-Fi password from the first line of stdin (with --ssid).
--no-registeroffSkip signing in and registering with embeddedci.com (address the pod by IP instead).
--yesoffNever prompt: register without asking, and fail naming the flag to pass when an answer is needed (for scripts and CI).

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 its Ethernet or Wi-Fi address, or as usb:benchpod-a1b2c3 when the pod is only on USB. The Python SDK and the MCP server read the same default. 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. Signed in (benchpod login), it also says whether a registered pod is on your account or on a different one.

FlagDefaultPurpose
--saveoffSave 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 network link, else its USB console device.
--no-usboffSkip probing USB ports.
--no-networkoffSkip mDNS browsing of the LAN.
--wait3sHow long to browse the LAN for mDNS replies.
--probe-timeout2sHow long each USB port gets to answer status.

benchpod ping

Connectivity check over the network. No flags.

benchpod status

A readable summary of the pod: firmware (and whether a newer release exists), gateware, board, I/O voltage, target power, network, and cloud state (including whether it is on your account). Over USB it prints the console's status text.

FlagDefaultPurpose
--jsonoffPrint the pod's raw status JSON instead of the summary (the output status printed before the summary existed).

Instruments

benchpod generate

Start DAC waveform output (network).

FlagDefaultPurpose
--waveform(required)sine, square or sawtooth.
--freq1kHzOutput frequency with its unit, e.g. 2kHz; a bare number is Hz.
--pathDAC output for the volts: 3v3, 5v or 12v (firmware 3.9.1 and later).
--amplitude-vPeak in volts on --path, calibrated and played at 16 bits (firmware 3.9.1 and later; the CLI refuses on older firmware).
--offset-vCenter in volts on --path, with --amplitude-v.
--amplitude-maPeak in mA on the 4-20 mA output (J9), 16-bit (firmware 3.9.1 and later).
--offset-maCenter in mA on the 4-20 mA output, with --amplitude-ma.
--amplitude127Half-scale amplitude in 8-bit levels, 0-127: 256 steps over the output, uncalibrated. Not with the volts or mA flags.
--offset128DC offset in 8-bit levels, 0-255.
--duration0How long to play, e.g. 2s or 500ms; 0 runs until the next command.
--duration-ms0The same in ms (older form of --duration).
--sample-rateautoFPGA sample-clock rate, e.g. 10MHz; leave it out to auto-pick.
--sample-rate-mhzautoThe same in MHz (older form of --sample-rate).

benchpod capture

Blocking ADC snapshot (network). Prints the samples.

FlagDefaultPurpose
--samples256Number of ADC samples, 1-4096.
--outputjsonOutput format: json, csv or ndjson.
--sample-ratemaxADC sample-clock rate, e.g. 10MHz; leave it out for the maximum.
--sample-rate-mhzmaxThe same in MHz (older form of --sample-rate).

benchpod stream

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

FlagDefaultPurpose
--samples256Number of ADC samples, 1-4096.
--outputjsonOutput format: json, csv or ndjson.
--sample-ratemaxADC sample-clock rate, e.g. 10MHz; leave it out for the maximum.
--sample-rate-mhzmaxThe same in MHz (older form of --sample-rate).

benchpod measure

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

FlagDefaultPurpose
--waveform(required)sine, square or sawtooth.
--freq1kHzOutput frequency with its unit, e.g. 2kHz; a bare number is Hz.
--amplitude127Half-scale amplitude code, 0-127.
--offset128DC offset code, 0-255.
--samples256Number of ADC samples, 1-4096.
--outputjsonOutput format: json, csv or ndjson.
--sample-rateautoSample-clock rate, e.g. 10MHz; leave it out to auto-pick.
--sample-rate-mhzautoThe same in MHz (older form of --sample-rate).

benchpod test

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

FlagDefaultPurpose
--patternsinesine, counter, ramp or const (const when only --value is set).
--value255Constant byte value, 0-255, for the const pattern.
--samples256Number of samples, 1-4096.
--outputjsonOutput format: json, csv or ndjson.

benchpod adc-range [show|normal|low|test]

Show the ADC input range, switch it, or self-test it (firmware 3.9.5+; the low range needs analog module v3r4+). normal is ±32.8 V at about 1 mV per count, low ±5.46 V at about 167 µV per count with a 1.82 MΩ input: use 1x leads, not 10x probes. It applies to ext, cal1 and current_in and stays until changed, the analog path goes off or to cal2, or the pod restarts. test drives the 5 V output into the cal1 loopback at 1 V and 3.5 V, reads both ranges and exits non-zero when they disagree: disconnect anything on the 5 V output first. Over the network and embeddedci.com.

benchpod la

Logic-analyzer pin control: the bank voltage, pull-ups and step pulses. Pins are 1-14 or la1-la14. Everything but la voltage needs the network.

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 and over USB. No flags.

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.

FlagDefaultPurpose
--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.
--direction0Level for --dir-la: 0 or 1.

Flashing

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) above. Pass --target (with --file or --build) or at least one -c/--openocd-arg. --build, --repo and --ref flash a build uploaded to embeddedci.com instead of a --file: over --connection embeddedci:<pod> embeddedci.com runs OpenOCD (nothing to install here, and it always verifies and resets); on the network or USB the file is downloaded and flashed by the local OpenOCD. Both need benchpod login or BENCHPOD_API_KEY.

FlagDefaultPurpose
--swclk(wiring profile swd_swclk)LA pin for SWCLK, 1-14 (1 or la1).
--swdio(wiring profile swd_swdio)LA pin for SWDIO, 1-14.
--target(wiring profile swd_target)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(wiring 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 a profile that says true.
--no-connect-under-resetoffDo not hold the target in reset while connecting.
--no-verifyoffDo not verify after programming.
--no-resetoffDo not reset the target after programming.
--keep-reset-initoffKeep the target config's reset-init clock boost (cleared by default because it glitches the link).
--target-power(none)Turn on a target power rail first: 1 (Internal 5 V, eFuse 1) or 2 (External, eFuse 2).
--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.

benchpod spi-flash

Read, erase and program a 25-series SPI NOR flash on four LA pins (network). See SPI flash above. These flags apply to every subcommand.

FlagDefaultPurpose
--sck(wiring profile spi_sclk)LA pin for SCK, 1-14.
--mosi(wiring profile spi_mosi)LA pin for MOSI, 1-14.
--miso(wiring profile spi_miso)LA pin for MISO, 1-14.
--cs(wiring profile spi_cs)LA pin for CS, 1-14.
--hz1000000SCK rate in Hz; the pod uses the nearest rate at or below it (190 kHz to 6 MHz).
--mode0SPI mode, 0 or 3.
--nresetoffHold 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).

FlagDefaultPurpose
--addr0Flash 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.

FlagDefaultPurpose
--addr0Flash address to write at, e.g. 0x10000.
--no-eraseoffDo not erase first (the range must already be erased).
--no-verifyoffDo not read each chunk back.

benchpod spi-flash erase

Erase a range or the whole part.

FlagDefaultPurpose
--addr0First address to erase.
--len(none)Bytes to erase, e.g. 65536 or 1M.
--chipoffErase 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 Updating the pod itself above. With --network it goes over the pod's network API instead (its USB network adapter or the --connection address): no dfu-util, but the pod must be running firmware. It prints a signature: line for the release's .sig (report only, see Signatures above) 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.

FlagDefaultPurpose
--enter-dfuoffFirst reboot a running pod into DFU over its USB console.
--wait1m (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.
--address0x08000000Flash base address (STM32 main flash).
--dfu-util(PATH)Path to the dfu-util binary.
--no-leaveoffStay in DFU after flashing instead of starting the firmware.
--skip-blobsoffDo not install the gateware and ESP32-C3 images afterwards.
--blobs-dir(next to the firmware)Take the blobs from this directory.
--networkoffUpdate over the pod's network API instead of USB DFU: its USB network adapter (firmware 3.9.0+) or the --connection address. The DFU-only flags do not apply.

benchpod install-blobs

Install the gateware and ESP32-C3 images the pod's firmware goes with (USB). 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.

FlagDefaultPurpose
--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, bridge (the Wi-Fi bridge firmware, on firmware with Wi-Fi bridge support).
--forceoffInstall 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.

FlagDefaultPurpose
--yesoffSkip the confirmation prompt.

benchpod bootsel

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

FlagDefaultPurpose
--yesoffSkip the confirmation prompt.

Network and USB

benchpod set-wifi

Save Wi-Fi credentials and join (USB). Installs the ESP32-C3 image first when the pod lacks it. While Wi-Fi is set up the pod's USB network is off (its USB console keeps working); clear-wifi turns it back on.

FlagDefaultPurpose
--ssid(required)Wi-Fi SSID.
--password(prompt)Wi-Fi password (visible in shell history; prefer the prompt or --password-stdin).
--password-stdinoffRead the password from the first line of stdin.
--skip-blobsoffDo 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). With a Wi-Fi bridge, Via names it and State is the bridge's (joining, connected, auth-failed, no-ap). No flags.

benchpod scan-wifi

List the Wi-Fi networks the pod sees, strongest first, to pick one for set-wifi (USB). The pod scans with its built-in Wi-Fi (firmware 3.9.4 or later) and stays joined if it is; on older firmware the command says so and how to join anyway. No flags.

benchpod clear-wifi

Erase the stored Wi-Fi credentials (USB). Wi-Fi goes down at once, no reboot needed, and the pod's USB network 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

Run the USB bridge in the foreground until Ctrl-C: it carries the cloud link of the pod on this computer's USB-C cable (firmware 3.9.0 or later) through this computer's internet access, so a pod with no Ethernet or Wi-Fi is online on embeddedci.com. Use usb-bridge install to keep it running.

FlagDefaultPurpose
--port3128TCP port of the proxy on this computer's USB address.
--watchoffWait for the pod and survive unplugging it (for running in the background).
--keep-proxyoffLeave the pod's cloud proxy set when the bridge stops.

benchpod usb-bridge install

Install the USB bridge as a background service that starts at login, so a pod on USB-C alone stays online in the web app after this computer or the pod reboots.

FlagDefaultPurpose
--port3128TCP port of the proxy on this computer's USB address.

benchpod usb-bridge status

Whether the bridge service is installed and running, and which pod it carries.

benchpod usb-bridge uninstall

Stop and remove the bridge service and clear the pod's cloud proxy. A pod on USB-C alone goes offline on embeddedci.com; over the cable it keeps working.

benchpod usb-network [show|on|off]

Show, or turn on or off, the pod's USB network adapter (firmware 3.9.0 or later). Changing it only works on the device itself, over USB: off makes the pod reconnect as a console only, on (over the console) brings the adapter back. Ethernet, Wi-Fi and embeddedci.com can only show it.

benchpod usb-reattach

Make the pod reconnect on USB, so this computer sets up its USB network adapter again (for example after a Mac ignored it while the screen was locked).

Cloud account

benchpod login

Authenticate with embeddedci.com (device-login flow) and print the account it signed in to (Logged in as you@example.com). A no-op when a usable session exists.

FlagDefaultPurpose
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken cache.
--no-browseroffPrint the approval URL instead of opening a browser.
--forceoffAuthenticate again even when a session exists.

benchpod logout

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

FlagDefaultPurpose
--token-file~/.config/benchpod-cli/token.jsonToken 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.

FlagDefaultPurpose
--device-name(the pod's own name)Pod name, URL-safe and unique in the organization, e.g. bench-01.
--wait30sHow long to wait for the pod to connect; 0 does not wait.
--pod-hostapi.embeddedci.comHost the pod connects to (api.embeddedci.com for embeddedci.com, else the --server-url host).
--insecure-skip-verifyoffProvision the pod to skip TLS certificate checks (bring-up only).
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken 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>.

FlagDefaultPurpose
--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-configoffLeave the pod's cloud configuration in place.
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken cache.

benchpod vision

AI Vision boards (BenchPod AI Vision), registered on embeddedci.com the way register does a pod. <board> is the board's LAN address (host, host:port or URL). Signs in with BENCHPOD_API_KEY when set and --token-file is not given, else with the benchpod login session.

benchpod vision register <board>

Register the board in your active organization and store the pairing on it, after which it connects to embeddedci.com on its own; waits until it does. Running it again is harmless. The board then shows in the BenchPod workspace, and tests reach it as embeddedci:<name>.

FlagDefaultPurpose
--name(the board's host name)The board's name on embeddedci.com, URL-safe.
--board-tokenAIVISION_TOKENThe board's API token, when it has one.
--pair-serverapi.embeddedci.comHost the board connects to (api.embeddedci.com for embeddedci.com, else the --server-url host).
--wait30sHow long to wait for the board to connect; 0 does not wait.
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken cache.

benchpod vision list

The organization's AI Vision boards: name, online or offline, the embeddedci:<name> connection and the id.

FlagDefaultPurpose
--jsonoffPrint the boards as JSON.
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken cache.

benchpod vision deregister <name or id>

Take the board out of the organization: it is disconnected and its logins are refused until it is registered again.

FlagDefaultPurpose
--unpair(none)The board's LAN address: also make it forget its pairing.
--board-tokenAIVISION_TOKENThe board's API token, when it has one.
--server-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken cache.

benchpod vision unpair <board>

Make the board forget its embeddedci.com pairing, on its LAN; it stays registered until benchpod vision deregister.

FlagDefaultPurpose
--board-tokenAIVISION_TOKENThe board's API token, when it has one.

Pod settings

benchpod lan-policy

Show the pod's LAN policy (Network access without sign-in in the web app, where open is Full, locked Instruments only and off None: the API on Ethernet and Wi-Fi; USB is never affected): open, locked or off. See Pod policies above. 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.

FlagDefaultPurpose
--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-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken 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 lan-tls

Show the pod's LAN TLS certificate (port 8443, firmware 3.9.3+), its SHA-256, the HTTPS-only setting and whether this computer has it pinned. Over the network, USB or embeddedci.com. See LAN TLS above.

FlagDefaultPurpose
--pemoffPrint the certificate the pod presents on 8443, PEM, instead.

benchpod lan-tls show

Same as benchpod lan-tls.

FlagDefaultPurpose
--pemoffPrint the certificate the pod presents on 8443, PEM (for curl --cacert, socat cafile=).

benchpod lan-tls https-only <on|off>

on: the pod refuses plaintext 8080 on Ethernet and Wi-Fi and answers only on 8443 there; its USB link keeps 8080. off needs a TLS, USB or embeddedci.com connection. The web app's pod Settings has the same switch (HTTPS-only API).

benchpod lan-tls regenerate

A new key and self-signed certificate on the pod; this computer's pin follows. Needs a TLS, USB or embeddedci.com connection.

benchpod lan-tls set-cert

Install your own certificate and key: checked here first (they must match, the key not encrypted, at most 7.6 KB together), uploaded over the network (TLS or the USB network adapter), then the pin is updated.

FlagDefaultPurpose
--cert(none)PEM certificate file, optionally followed by its chain. Required.
--key(none)PEM private key file (EC P-256/P-384 or RSA), not encrypted. Required.

benchpod lan-tls trust

Pin the certificate the pod presents now, after a change made elsewhere. Prints its SHA-256: compare it with the pod's console lan-tls or the web app's pod Settings.

FlagDefaultPurpose
--sha256(none)Only trust a certificate with this SHA-256.

benchpod lan-tls forget

Drop this computer's pin for the pod; the next TLS connection pins what it presents.

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.

FlagDefaultPurpose
--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-urlhttps://www.embeddedci.comembeddedci-server base URL.
--token-file~/.config/benchpod-cli/token.jsonToken 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 above. 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.

FlagDefaultPurpose
--yesoffDo not ask for confirmation.

benchpod cloud

How the pod reaches embeddedci.com: a company CA and an HTTP proxy. See Cloud link behind a company network above. Changes need USB (or the cloud link); 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. 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.

FlagDefaultPurpose
--user(none)Proxy user name (Basic authentication).
--password(prompt)Proxy password (visible in shell history; prefer the prompt or --password-stdin).
--password-stdinoffRead 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.