EmbeddedCI BenchPod

io.github.embeddedci-comPublished by GitHub account embeddedci-comv2.13.9Updated Nov 7, 2026

Drive a BenchPod hardware-in-the-loop bench: power, flash, UART, I2C, CAN, analog and logic.

Overview

AI-generated overview

Drives an EmbeddedCI BenchPod hardware-in-the-loop bench: power, flash, UART, I2C, CAN, SPI, GPIO, analog and logic capture on a real target board.

What you can do
  • Connect to a pod over LAN, USB or the embeddedci.com cloud, and read status, wiring profile and pin ownership
  • Power the target rail on/off, reset it, and measure or profile supply current, voltage and energy
  • Flash firmware over SWD, program and read SPI NOR flash, and run raw SPI transactions
  • Capture UART output, power-cycle and capture boot logs, and open an interactive console session
  • Emulate I2C sensors and a GPS receiver, drive GPIO, pull resistors and step/dir pulses
  • Capture and decode logic and ADC signals, generate or replay DAC waveforms, run control loops, and send/receive CAN frames
When to use it
Use it when an assistant should operate a real embedded target on a BenchPod bench: flashing and booting firmware, checking boot logs, measuring power, exercising buses, or emulating sensors and peripherals during bring-up and test.
Requirements
Python 3.10+ with uv (uvx) or pip, running locally. A BenchPod reachable over the network, USB (firmware 3.9.0+) or the embeddedci.com cloud. BENCHPOD_CONNECTION and BENCHPOD_LA_VOLTAGE are optional; cloud access needs BENCHPOD_API_KEY or a benchpod login session. Flashing a local file needs OpenOCD with the cmsis_dap_tcp backend on the server host.
Before you install
It drives real hardware: power, reset, flash and SPI programming change the target, and GPIO or pull-resistor calls can conflict with other tools. A cloud pod is shared, so connect takes an exclusive lease. BENCHPOD_API_KEY grants cloud pod and waveform-library access; the HTTP transport refuses a non-loopback bind without EMBEDDEDCI_MCP_TOKEN. Wiring profiles and captures can be stored on embeddedci.com.

Installation

In SourceWeft

  1. Open EmbeddedCI BenchPod in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

embeddedci-mcp

An MCP server that lets an AI agent drive an EmbeddedCI BenchPod — a hardware-in-the-loop tester wired to a real board. Ask Claude (or any MCP client) to flash a build, power-cycle the target, watch its UART, pretend to be the sensor it expects, probe its I2C/CAN traffic or drive and measure analog signals, and the pod does it.

It is a thin layer over the embeddedci SDK: every tool maps to SDK calls, so anything an agent discovers interactively can become a pytest test.

Requirements

  • Python 3.10+ and uv (for uvx), or pip.
  • A BenchPod reachable over the network, the embeddedci.com cloud, or USB. With firmware 3.9.0 and later, connection usb reaches the full API over the pod's USB network adapter (unless Wi-Fi is configured on the pod, which turns USB networking off); status then reports kind usb_net. A serial device path, or usb on older firmware, is the USB console: status, LA voltage and power only, since the STM32 pod's console has no JSON mode. With several pods plugged into one computer, usb errors and lists them by name; pick one with usb:<name> (for example usb:benchpod-baea06).
  • For flash: OpenOCD with the cmsis_dap_tcp backend (newer than 0.12.0 — e.g. brew install --HEAD open-ocd, or the xPack build) on the machine running the server. The firmware file is read from that machine too. Flashing a build uploaded to embeddedci.com (build/repo/ref) over a cloud connection needs neither: embeddedci.com runs OpenOCD.

Set up your client

Claude Code

bash
claude mcp add benchpod \  -e BENCHPOD_CONNECTION=192.168.1.213 -e BENCHPOD_LA_VOLTAGE=3.3 \  -- uvx embeddedci-mcp

BENCHPOD_LA_VOLTAGE is the board's I/O voltage, configured once here — use 1.8 for a 1V8 board. Without it the agent is told to call set_la_voltage before touching the LA bank.

Both variables are optional. connect picks the pod, first match wins:

  1. its connection argument;
  2. the server default, --connection;
  3. BENCHPOD_CONNECTION;
  4. the default connection benchpod-cli saved with benchpod set-connection <address|device|usb> (read-only, from ~/.config/benchpod-cli/config.json, or under $XDG_CONFIG_HOME);
  5. mDNS discovery of the one pod on the LAN.

So the CLI, the SDK and the MCP server share one saved connection. With none of the first four, connect looks for a BenchPod on the LAN over mDNS (like discover) and uses it when it finds exactly one. When it finds none or several, the error lists the pods it heard (name and address) and what to pass instead: host[:port], usb, or embeddedci:<device> for a cloud pod (cloud_list_devices lists those). So claude mcp add benchpod -- uvx embeddedci-mcp is enough for a single pod on your network: just ask the agent to connect.

TLS. A network address uses TLS on port 8443 when the pod has it (firmware 3.9.3+) and pins the pod's certificate on first use, in the same known_pods.json as benchpod-cli; older pods stay on plaintext 8080. BENCHPOD_TLS=on|off and BENCHPOD_TLS_CA work as in the SDK. A changed certificate stops connect with both fingerprints: after a deliberate change, the user runs benchpod lan-tls trust. The lan_tls tool shows the certificate and switches HTTPS-only.

Claude Desktop / Cursor

claude_desktop_config.json (Claude Desktop) or .cursor/mcp.json (Cursor):

json
{  "mcpServers": {    "benchpod": {      "command": "uvx",      "args": ["embeddedci-mcp"],      "env": {        "BENCHPOD_CONNECTION": "192.168.1.213",        "BENCHPOD_LA_VOLTAGE": "3.3"      }    }  }}

Codex

The Codex CLI keeps its MCP servers in ~/.codex/config.toml. Add it with the CLI:

bash
codex mcp add benchpod \  --env BENCHPOD_CONNECTION=192.168.1.213 --env BENCHPOD_LA_VOLTAGE=3.3 \  -- uvx embeddedci-mcp

…or write the entry yourself (codex mcp list shows what is configured):

toml
# ~/.codex/config.toml[mcp_servers.benchpod]command = "uvx"args = ["embeddedci-mcp"]env = { BENCHPOD_CONNECTION = "192.168.1.213", BENCHPOD_LA_VOLTAGE = "3.3" }

A pod in the cloud

Use the device name and an API key — the cloud transport is included:

json
"env": {  "BENCHPOD_CONNECTION": "embeddedci:my-bench",  "BENCHPOD_API_KEY": "eci_…",  "BENCHPOD_LA_VOLTAGE": "3.3"}

Or skip the API key: after benchpod login, the server reuses that session (~/.config/benchpod-cli/token.json, refreshed and written back when it has expired) for cloud_list_devices, connect("embeddedci:<name>") and, on a LAN or USB connection, the pod's stored wiring profile. BENCHPOD_API_KEY wins when set, and inside GitHub Actions the SDK uses OIDC instead.

cloud_list_devices lists the pods on your account and which are online, without connecting to one; pass a result's connection to connect. The session token behind a cloud connection is renewed before it expires, so a long session keeps working.

A cloud pod is shared, so connect takes an exclusive lease (waiting up to --lease-wait seconds if another run holds it). The lease is released by disconnect, or after --idle-timeout seconds without a tool call — the next call reconnects transparently, so an idle chat never blocks CI on that pod.

Options

FlagEnvironmentDefault
--connectionBENCHPOD_CONNECTIONthe CLI's saved connection, else mDNShost[:port], serial device, usb, usb:<name>, discover, saved, or embeddedci:<device>; unset = benchpod set-connection's saved one, else find the one pod on the LAN
--la-voltageBENCHPOD_LA_VOLTAGE—LA I/O voltage (1.8 or 3.3) applied on connect
—BENCHPOD_API_KEY—cloud pods and the waveform library (without it, cloud tools use the benchpod login session)
—BENCHPOD_API_BASEhttps://www.embeddedci.comanother embeddedci server
—BENCHPOD_WIRING—wiring profile file (.json/.toml) that wins over the stored profile
—BENCHPOD_WIRING_SOURCEautolocal never asks embeddedci.com for the pod's stored wiring profile
--lease-wait—30cloud: seconds to wait for a busy pod
--idle-timeout—600cloud: release the lease after this many idle seconds (0 = never)
--timeout—30per-command device timeout
--transport—stdiostdio or http
--host / --port—127.0.0.1 / 8000HTTP bind address
--auth-tokenEMBEDDEDCI_MCP_TOKEN—require Authorization: Bearer <token> (mandatory off loopback)
--allowed-host——Host header(s) to accept on a network bind (DNS-rebinding protection)
--allow-unauthenticated—offserve a network address without a token (isolated networks only)

Serving a bench over HTTP

Run the server on the machine next to the pod, and point clients at it:

bash
export EMBEDDEDCI_MCP_TOKEN=$(openssl rand -hex 32)embeddedci-mcp --transport http --host 0.0.0.0 --connection 192.168.1.213 --la-voltage 3.3
bash
claude mcp add --transport http benchpod http://bench-host:8000/mcp \  --header "Authorization: Bearer $EMBEDDEDCI_MCP_TOKEN"

The server drives real hardware, so it refuses a non-loopback bind without a token. It holds one pod connection shared by all HTTP clients, and serialises their tool calls.

Tools

Every tool, grouped as in the server. Parameters in bold are required; the others are optional and show their default when they have one (no default means "from the wiring profile", "keep the current setting" or "not used"). Units are volts, amps, seconds and hertz. LA channel arguments take a number (1-14) or a name from the wiring profile. Each tool's input schema carries the full description, ranges and enums, so an agent sees more than this table.

Connection

ToolPurposeParameters
connectOpen a connection (closing any previous one) and report status, capabilities and where the wiring profile came from. Without a connection or a configured default (--connection, BENCHPOD_CONNECTION, the CLI's saved connection), finds the one pod on the LAN over mDNS.connection, la_voltage, lease_wait=30
disconnectClose UART/CAN sessions and the connection, releasing a cloud lease.none
statusFirmware, capabilities, LA voltage, open sessions and warnings (works when not connected).none
set_la_voltageSelect the LA I/O voltage to match the DUT (1.8 V needs a rev3 pod).voltage

Cloud (embeddedci.com)

ToolPurposeParameters
cloud_list_devicesThe pods on your embeddedci.com organization and whether each is online, without connecting.none

Wiring profile

ToolPurposeParameters
wiringWhich DUT signal is on which LA channel, plus the power rail, UART baud and SWD target.none
set_wiringReplace the wiring profile for this connection, or store it on embeddedci.com.profile, save=false

Power

ToolPurposeParameters
power_onSwitch the target's power rail on (optionally scheduled pod-side).efuse, delay
power_offSwitch the target's power rail off.efuse, delay
power_statusBoth target-power rails, internal 5 V (efuse 1) and external (efuse 2): on/off, tripped, bus voltage and current, and the pod's own draw.none
reset_targetDrive the DUT's reset line from the pod's reset pin.action=pulse, pulse=0.1

Power profiles

ToolPurposeParameters
measure_powerSample supply current and voltage for a window: average, min, peak, energy and charge.duration, efuse, rate_hz=500, points=0
power_profile_startStart sampling the rail in the background while other tools run.efuse, rate_hz=500, max_duration=60
power_profile_stopStop the running profile and return its statistics (and a trace).points=0

Flash

ToolPurposeParameters
flashProgram the DUT over SWD through the pod's CMSIS-DAP probe: a local file (OpenOCD runs next to the server), or a build uploaded to embeddedci.com (build id or latest, repo, ref; embeddedci.com runs OpenOCD on a cloud connection, LAN/USB downloads it).swclk, swdio, target, file, nreset, load_address, target_power, verify=true, reset=true, connect_under_reset, extra_configs, extra_args, timeout=300, connect_attempts=5, build, repo, ref, artifact, server_side

SPI flash and SPI devices

ToolPurposeParameters
spi_flash_infoRead the JEDEC ID and size of the SPI NOR flash on the SPI pins.sck, mosi, miso, cs, hz=1000000, mode=0
spi_flash_programErase, write and verify an image in the SPI NOR flash.file, addr=0, erase=true, verify=true, hold_reset=false, sck, mosi, miso, cs, hz=6000000, mode=0
spi_flash_readRead flash bytes to a file, or up to 4096 bytes as hex.addr, length, file, sck, mosi, miso, cs, hz=6000000, mode=0
spi_transferOne full-duplex SPI transaction with any device on the SPI pins.tx_hex, sck, mosi, miso, cs, hz=1000000, mode=0

UART

ToolPurposeParameters
capture_uartRecord the DUT's UART for a window, or until a regex matches.duration, rx, tx, baud, until_regex
power_cycle_and_capturePower-cycle the target and capture UART across the boot.rx, tx, efuse, delay=1, duration=4, baud, until_regex, off_settle=0.3
uart_openStart buffering the DUT's UART in the background.rx, tx, baud
uart_writeSend text to the DUT through the open UART session.text, line_ending=lf
uart_readReturn output received since the last read, optionally waiting for a match.until_regex, timeout=2
uart_closeStop the background UART session.none

Emulated I2C sensor

ToolPurposeParameters
enable_i2c_sensorMake the pod act as an I2C sensor for the DUT to read (engage pull-ups first): sensor is bmp280, bme280, sht4x or mpu6050.sda, scl, address, temperature_c, pressure_pa, sensor="bmp280", values (readings by key, e.g. {"humidity_pct": 55})
set_i2c_sensorChange what the emulated sensor reports; the reply lists every reading.temperature_c, pressure_pa, values
i2c_sensor_typesThe models the pod emulates, with their addresses and readings (key, unit, range, default).none
disable_i2c_sensorDisarm the emulated sensor.none
i2c_sensor_statusSensor state and bus activity counters: did the DUT talk to it?none
i2c_sensor_regsRead the emulated sensor's register image.start=0, length=256
i2c_sensor_captureCapture and decode the sensor's I2C bus into a transaction trace.samples=4096, sample_rate_hz=500000, address, register

Emulated GPS receiver

NMEA sentences (u-blox style: RMC VTG GGA GSA GSV GLL) on one LA channel for the DUT's UART, from the pod's second UART, so uart_open keeps the console. Needs gateware v48 (gps capability).

ToolPurposeParameters
enable_gpsStart the receiver on tx, the channel wired to the DUT's RX. The position moves along course_deg at speed_kmh between fixes.tx, baud=9600, rate_hz=1, sentences, latitude_deg, longitude_deg, altitude_m, speed_kmh, course_deg, satellites, hdop, fix, utc (default now)
set_gpsChange the fix (any subset); fix=0 drops the lock.latitude_deg, longitude_deg, altitude_m, speed_kmh, course_deg, satellites, hdop, fix, utc
disable_gpsStop the receiver and release its pin.none
gps_statusThe session (tx, baud, rate, epochs, overruns) and the fix it prints.none

Pull resistors

ToolPurposeParameters
set_pullEngage or release the bias resistors: LA1-LA6 pull up, LA7/LA8 pull down.las, enabled
pull_statusState, direction and value of the bias resistor on LA1-LA8.none
lan_tlsThe pod's LAN API over TLS (firmware 3.9.3+): port 8443, the HTTPS-only setting and the certificate's sha256 (what clients pin). Turning HTTPS-only off and regenerating need a TLS, USB or cloud connection.https_only, regenerate
usb_networkRead the pod's USB network setting (firmware 3.9.0+), or turn the adapter off over the USB link. Only the device changes it; turning it back on is benchpod usb-network on on the USB console.set=off

GPIO on the LA pins

ToolPurposeParameters
la_pinsWhat owns each of the 14 LA channels, with GPIO mode, level and bias resistor.none
gpio_modeClaim LA channels as GPIO (input, output or open drain).la, mode=output, level
gpio_writeSet the level of GPIO output and open-drain channels.la, level
gpio_readThe live level of LA channels (all 14 when la is omitted).la
gpio_waitWait until an LA channel reads a level (reached: false on timeout).la, level=1, timeout=5
gpio_pulseFPGA-timed pulses on an LA channel: width 4 us to 65.535 ms, count up to 65535.la, width, count=1
gpio_releaseReturn channels to high-Z, watched by captures.la

Analog

ToolPurposeParameters
analog_pathSet every analog mux and relay for a named path in one step; range sets the ADC input range too.path, range
dac_outputRoute a DAC output and drive a calibrated DC voltage on it.path, volts
current_outHold a current on the 4-20 mA output (J9), in amps, or read its range.current
adc_readOne calibrated reading: front SMA, DAC loopbacks or the 4-20 mA input (J8); reports the ADC range, its full scale and overrange.source=ext, range
adc_rangeRead or switch the ADC input range: normal ±32.8 V or low ±5.46 V at 6x the resolution (analog module v3r4+, firmware 3.9.5+; 1x leads, the low range's input is 1.82 MΩ), or self-test it through the cal1 loopback.range, test=false
calibrationThe stored calibration: the ADC offset (measured on the 4-20 mA input) and the fit it uses, plus the DMM lines and gains (firmware 3.9.4+).none
calibrateMeasure and store the ADC offset on the 4-20 mA input (J8 open), or clear the calibration (all of it, or one part).source=current_in, clear=false, clear_part
calibrate_dacStore this pod's line for a DAC output from two DMM readings of set points driven with dac_output (firmware 3.9.4+).path, code1, volts1, code2, volts2
calibrate_adc_gainMeasure an ADC source's gain through a loopback against a DMM-calibrated output (firmware 3.9.4+); range=low the ADC low range's gain through cal1 (firmware 3.9.5+).source, range=normal

Capture and decode

ToolPurposeParameters
capture_adcCapture the ADC: calibrated statistics, dominant frequency and a min/max envelope; clipped_samples counts samples past the range's full scale. oversample converts at 400 kS/s and averages down to sample_rate_hz for lower noise, about 5 s at most.samples=4096, sample_rate_hz, source, points=200, trigger_la, trigger_edge=rising, trigger_timeout=10, range, oversample
capture_laCapture all 14 LA channels: levels, edge count, first edge and frequency per channel. Up to 48 MS/s on gateware v50+.samples=4096, sample_rate_hz, stop_dac_after, trigger_la, trigger_edge=rising, trigger_timeout=10, trigger_pre_samples=0
capture_correlatedADC and LA from one hardware trigger, on one timebase.adc_samples=4096, adc_sample_rate_hz, la_samples=4096, la_sample_rate_hz, stop_dac_after, points=200, trigger_la, trigger_edge=rising, trigger_timeout=10, source
decode_laDecode I2C, UART or SPI from the last LA capture (or a new one).protocol, sda, scl, rx, baud, sclk, mosi, miso, cs, mode=0, capture=last, samples=16384, sample_rate_hz=1000000, max_items=200
la_timingEdge timestamps, pulse widths, frequency, duty cycle and channel-to-channel delay from the last capture.la, edge=rising, to_la, to_edge=rising, after=0, max_edges=100

DAC

ToolPurposeParameters
generateDrive a sine, square or sawtooth on a DAC output (volts, or amps on current_out).waveform, freq_hz, amplitude, offset, dac_path=3v3, duration, sample_rate_hz, on_capture=false, route=true
dac_stopStop any DAC output: generator, replay or control loop.none
replayLoop your own samples, or the last ADC capture, out of the DAC.volts, from_last_capture=false, dac_path=3v3, mapping=faithful, sample_rate_hz, fault, on_capture=false, route=true, switch_image=true
list_waveformsThe organization's cloud waveform library.none
replay_waveformLoop a cloud-library waveform out of the DAC.waveform_id, dac_path, mapping=faithful, sample_rate_hz, window_start=0, window_len=0, target_samples=0, fault, on_capture=false, switch_image=true
save_capture_as_recordingSave the last ADC capture to the cloud waveform library.name, full_scale_v

Control loop

ToolPurposeParameters
control_loopRun a tabulated transfer function in the FPGA (for example a solar panel I-V curve).curve, voc_code, sharpness=4, points=256, k=8192, vmin=0, vmax=65535, tick_div=64, source, input_code=0, step=0, input_map, switch_image=true
loop_inputRe-target the running loop's input without re-arming.input_code, source, step
loop_probeThe running loop's live operating point.none
fpga_imageSwitch the FPGA to the loop or deep_replay gateware image (~2-3 s).image

CAN

ToolPurposeParameters
can_openBring up the pod's CAN interface (normal, loopback or listen-only).bitrate=500000, mode=normal, term=false
can_writeQueue one classic CAN frame.can_id, data, ext=false, rtr=false
can_readRead received frames, or wait for one id.timeout=0, can_id, max_frames=32
can_respondAnswer a CAN id from the pod firmware (ECU simulation), or clear all rules.match_id, reply_id, reply_data, match_ext=false, reply_ext=false, clear=false
can_statusLink state: mode, bitrate, error counters, bus-off.none
can_closeClear responder rules and stop CAN.none

Other

ToolPurposeParameters
la_stepStep pulses for step/dir motor drivers: delay is half the step period (4 us to 65.535 ms), steps up to 65535.la, steps, delay, dir_la, direction=0
commandRaw firmware JSON command, for anything no tool covers.request

AI Vision board

For a bench with an AI Vision board (ai-vision-2): a camera on the DUT's screen that reads text, finds buttons and presses them through its actuator. Its connection is separate from the pod's, so one session drives both. Without vision_connect, the first vision_* call connects to AIVISION_CONNECTION (token in AIVISION_TOKEN). Boxes and taps are in screen pixels once the screen is calibrated.

A board paired with embeddedci.com is reached from anywhere as embeddedci:<name>: vision_cloud_boards lists the organization's boards, and vision_connect takes the connection it returns. Over embeddedci.com the server's sign-in applies (BENCHPOD_API_KEY, an API key with the "Control AI Vision" scope, else the benchpod login session) and lease_as takes the server's device lease, the one the web app and other runs see.

ToolPurposeParameters
vision_cloud_boardsThe AI Vision boards on embeddedci.com, online or not, with the connection to pass to vision_connect.none
vision_connectOpen the AI Vision connection (a LAN address, discover or embeddedci:<name>; closing a previous one) and report its status.connection, token, lease_as
vision_disconnectClose the connection and release its lease.none
vision_statusVersion, calibration, camera lock, actuators, lease, capabilities, with warnings.none
vision_lookThe screen as an image (an image content block), or what the camera sees; a region zooms in at full sensor resolution.space=screen, overlay, region
vision_ocrAll text on the screen or in a region, in reading order, with boxes.region, zoom
vision_findElements matching text, a kind (button, icon, text) or a template, best first.text, label, template, match, region
vision_wait_forWait until text appears (or with gone, disappears); met: false on timeout.text, timeout_s=10, gone=false, match, region
vision_compareSimilarity of the screen to a stored reference screen.reference, mask
vision_screen_healthBlack, dim, frozen, flickering or stuck pixels over a window.window_ms=1000, expect_motion=false
vision_calibrateMap camera pixels to screen pixels: four corners and the screen size, or method auto/markers.corners, screen_size, method
vision_add_referenceStore the current screen (or a region, as a template) as a reference.name, kind=screen, region, overwrite=false
vision_pressFind an element, tap its center, optionally wait for verify_text. Fails with the visible text when it is not there.text, verify_text, verify_timeout_s=5, match, actuator
vision_tapTap a point in screen pixels (hold_ms for a long press).x, y, hold_ms=0, actuator

Resources: benchpod://wiring (the connected bench's own wiring profile, then LA channels, bias resistors and analog paths) and benchpod://help (the server instructions).

How it behaves

  • Instructions. The server sends usage instructions at initialisation — session start, typical flows, units, error contract — so the agent knows to connect and set_la_voltage before anything else.

  • Typed, structured results. Every tool has an input schema with enums and ranges (paths, sources, LA channels 1-14, internal 5 V (efuse 1) or external (efuse 2), …) and an output schema; results come back as structured content. Units are volts, seconds and hertz.

  • Errors. A tool that cannot do what was asked fails with an MCP tool error whose message names the cause, e.g. FirmwareError: la voltage not set or NotConnectedError: …. A completed operation with a negative outcome is a normal result: flash returns ok: false with its logs, a UART capture matched: false.

  • Refusals. When the pod or the server refuses a command, the error names the kind and ends with a one-line hint on what to do: PodLockedError (the pod's LAN policy keeps the command for the cloud or USB), PodLeasedError (a cloud job holds the pod; the message names who and for how long), PodBusyError (a capture or upload is running), PermissionDeniedError (the credential lacks the right, for example an API key without the benchpod:admin scope) and TransportTimeout (the pod did not answer). A dropped link is a ConnectionClosedError. UnsupportedFeatureError means the pod cannot do it at all: its firmware or gateware lacks the feature, or it is the digital-only board without the analog front end; the hint says which. status also warns when a cloud job holds the pod or the LAN policy is locked. Refusals whose fix the firmware spells as a raw protocol command name the tool instead: FirmwareError: uart_proxy_start: la voltage not set; call set_la_voltage first with the DUT's I/O voltage (1.8 or 3.3) and PinConflictError: uart_proxy_start: pin conflict: LA4 is in use by gpio; release it with gpio_release(la=[4]). The other pin owners point to uart_close, disable_i2c_sensor and disable_gps; a pull conflict to set_pull(las=[7], enabled=false).

  • Capabilities. connect and status report what the pod can do as flags, so an agent can check before it calls a tool: for example la_pins, capture_trigger, power_profile, nrst_pin, can, calibrate, cal_dmm, current_out, pod_current and analog (false on the digital-only board), plus the pod's policy and cloud settings (dac_limits, flash_kb, ota_sig, sig_policy, lan_policy, tunnel_max_tier, lease_state, cloud_ca, cloud_proxy).

  • Wiring profile. wiring is the bench's map of DUT signal → LA channel, plus the target-power rail, the UART baud and the SWD target. Omitted channel / baud / rail / SWD arguments come from it, and channel arguments also accept its names (trigger_la: "READY", rx: "uart_rx"), so an agent that read wiring once can call flash(), uart_open() or power_on() with no pin numbers at all. set_wiring replaces it for the connection, or stores it on embeddedci.com with save: true.

    It is the same profile the web UI's Wiring tab edits. The SDK resolves it, first match wins: BENCHPOD_WIRING (a file); the profile stored on embeddedci.com for this pod (a cloud pod by name; a LAN or USB pod by the device it reports it is registered as, when BENCHPOD_API_KEY or the benchpod login session is available and that device is on the same account); the defaults. wiring and connect/status (wiring_source) report where it came from: server, file, dict (from set_wiring) or defaults. When the lookup fails (not signed in, pod not registered, server unreachable, another account) the defaults apply and nothing fails; over the LAN or USB it gives up after about 3 seconds. The STM32 pod's USB text console cannot report its registration, so over USB the defaults apply. BENCHPOD_WIRING_SOURCE=local skips the lookup.

  • Agent-sized captures. capture_adc returns calibrated statistics, the dominant frequency and a min/max envelope; capture_la returns per-channel levels, edges and frequencies. The last captures stay in the session, so decode_la, replay(from_last_capture=true) and save_capture_as_recording don't capture again.

  • Triggered captures. capture_adc, capture_la and capture_correlated take trigger_la (+ trigger_edge rising/falling/high/low and trigger_timeout) so sampling starts on an event — t = 0 is the trigger — and the summary echoes it as trigger: "LA9 rising". A condition that never happens fails with TriggerTimeout: ….

  • Pins and GPIO. Each LA channel has one owner at a time: none, GPIO, a UART proxy, SWD, the emulated sensor or a step train. la_pins shows the table, gpio_mode / gpio_write / gpio_read / gpio_wait / gpio_pulse drive and watch channels, gpio_release frees them. A second claim fails with PinConflictError: pin conflict: LA5 is in use by uart_rx; … naming the owner and how to free it — so the agent releases GPIO before opening a UART session on that channel. Captures observe all 14 channels whatever owns them.

  • Power profiles. measure_power(duration) reports average, minimum and peak current, voltage, energy and charge. Every sample is timestamped and the integrals run over those timestamps, so energy is integrated rather than estimated. rate_hz (100-500, default 500) tracks the request to ~200 Hz and then flattens near 365 Hz — the result reports the rate actually delivered in rate_hz and the sensor's configured rate in adc_rate_hz. Optional downsampled trace (points). power_profile_start / power_profile_stop bracket other tool calls; the running profile lives on the session (status reports it) and is dropped on disconnect.

  • Sessions. uart_open buffers the DUT's console in the background (open it before power_on, then uart_read / uart_write); can_open keeps a CAN bus open across calls.

  • Gateware images. The pod's FPGA runs either the loop image (control_loop) or the deep_replay image (replays longer than 2048 samples). control_loop, replay and replay_waveform switch automatically (~3 s) and report it as switched_image; switch_image: false fails instead. A switch resets the FPGA — a running DAC output, UART session or I2C sensor emulation stops — and the server instructions tell the agent to start those after it. fpga_image switches explicitly.

  • Non-blocking. Tools run on worker threads under one device lock: a 5-minute flash doesn't freeze the server, sends progress notifications, and concurrent calls can't interleave commands.

  • Annotations. Read-only tools (status, power_status, adc_read, …) are marked so clients can auto-approve them; tools that power, flash or drive voltages are marked destructive.

Example

Connect to the bench, flash build/app.elf to the STM32F4 (SWCLK on LA11, SWDIO on LA12, reset wired), then power-cycle it and tell me whether it reaches APP_OK on the UART (DUT TX on LA5, RX on LA4).

connect()                                   # BENCHPOD_CONNECTION + BENCHPOD_LA_VOLTAGEflash(swclk=11, swdio=12, nreset=true, target="target/stm32f4x.cfg", file="build/app.elf", target_power=1)power_cycle_and_capture(rx=5, tx=4, delay=1.0, duration=5.0, until_regex="APP_OK")

With a wiring profile stored for the bench, the same run needs no pin numbers:

connect()wiring()                                    # SWCLK on LA11, DUT TX on LA5, rail internal 5 V (efuse 1), …flash(file="build/app.elf")power_cycle_and_capture(delay=1.0, duration=5.0, until_regex="APP_OK")measure_power(duration=2.0, points=100)     # what the firmware draws once it is up

Stability

2.x freezes the tool names, input schemas and annotations (tests/tools_surface.json; CI fails on any unreviewed change): tools and optional parameters may be added, nothing is renamed or removed within a major version. See CHANGELOG.md for migrating from 0.1.

Publishing to the MCP Registry

server.json describes this package for the MCP Registry. The embeddedci-mcp-v* release workflow publishes it after the PyPI upload (which is what makes uvx embeddedci-mcp work), logged in with GitHub OIDC. Bump both version fields in server.json together with pyproject.toml: the release fails when they differ, and so does CI.

To publish by hand instead:

bash
mcp-publisher login githubmcp-publisher publish

The registry verifies PyPI ownership through the mcp-name comment at the top of this README.

Development

bash
pip install -e "../embeddedci[dev]" -e ".[dev]"pytest

Source: packages/embeddedci-mcp/README.md at commit 3649f29

Tools

93

connect

Connect to a BenchPod

Open a BenchPod connection (closing any previous one and its sessions) and report status. Over the cloud the device is shared: this takes an exclusive lease, released by `disconnect` or after the server's idle timeout (the next tool call then reconnects).

Parametersconnectionla_voltagelease_wait

disconnect

Disconnect

Close UART/CAN sessions and the connection, releasing a cloud lease. Safe when not connected.

Takes no parameters.

status

Pod status

Connection, firmware, capabilities, selected LA voltage and open sessions — with warnings for anything that will block the next steps (e.g. no LA voltage). Works when not connected.

Takes no parameters.

set_la_voltage

Set LA I/O voltage

Select the LA I/O-bank voltage to match the DUT's I/O (1.8 V needs a rev3 pod). Required before flash, UART, LA capture, pull resistors or I2C-sensor emulation.

Parametersvoltagerequired

wiring

Wiring profile

Which DUT signal is on which LA channel — call this first, before any channel argument. Returns the bench's effective profile: a 14-row pin table (what is wired to each channel and its bias resistor), the named signals, the target-power rail, the UART baud and the SWD target, plus warnings about risky wiring. Every tool whose channel, baud, rail or SWD arguments are omitted takes them from this profile, and channel arguments accept these names.

Takes no parameters.

set_wiring

Set the wiring profile

Replace the wiring profile this connection uses (and optionally store it on embeddedci.com). The profile is validated first: two roles or signals on one LA channel, an out-of-range pin or an unknown key are rejected with a message naming every problem.

Parameterssaveprofilerequired

power_on

Power target on

Switch the target's power rail on. The result says which rail was used.

Parametersdelayefuse

power_off

Power target off

Switch the target's power rail off. The result says which rail was used.

Parametersdelayefuse

measure_power

Profile the target's power

Measure the DUT's supply current and voltage for a window: average, minimum and peak current, voltage, energy and charge (gap-free sampling, so energy is integrated, not estimated). Use this for a self-contained window (a boot, a sleep interval). To profile across other tool calls, bracket them with power_profile_start / power_profile_stop.

Parametersefusepointsrate_hzdurationrequired

power_profile_start

Start a power profile

Start sampling the target-power rail in the background, then run the steps you want to profile and call power_profile_stop. Replaces any profile already running.

Parametersefuserate_hzmax_duration

power_profile_stop

Stop a power profile

Stop the running power profile and return its statistics (and, with points, a trace).

Parameterspoints

power_status

Power status

Both target-power rails, internal 5 V (efuse 1) and external (efuse 2): on/off and tripped state, bus voltage and current draw.

Takes no parameters.

reset_target

Reset target

Drive the DUT's reset line from the pod's reset pin (rev3 pods, DUT header J1 pin 22).

Parameterspulseaction

spi_flash_info

Read the SPI flash's ID

Read the JEDEC ID of the SPI NOR flash on the SPI pins (gateware v45+, capability spi_master). present=false means the ID read all 00 or all FF: nothing is answering (wiring, power, or /WP and /HOLD not tied high). The pins are released again afterwards.

Parameterscshzsckmisomodemosi

spi_flash_program

Program an SPI flash

Erase, write and verify an image into the SPI NOR flash wired to the SPI pins. 25-series parts, first 16 MB. Throughput is roughly 30 KB/s over the LAN (erase is extra) and slower over the cloud. Fails with the pod's message when nothing answers, the part is write-protected, or a chunk does not verify.

Parameterscshzsckaddrfilerequiredmisomodemosieraseverifyhold_reset

spi_flash_read

Read an SPI flash range

Read bytes back from the SPI NOR flash, to a file or (up to 4096 bytes) as hex.

Parameterscshzsckaddrrequiredfilemisomodemosilengthrequired

spi_transfer

SPI transfer

One full-duplex SPI transaction (CS asserted for all of tx) with any SPI device on the SPI pins. Returns what came back on MISO. The SPI master is armed for the call and released after it.

Parameterscshzsckmisomodemositx_hexrequired

flash

Flash firmware over SWD

Program the DUT over SWD through the pod's CMSIS-DAP probe. Give either `file` (a path on this server's host) or an uploaded build (`build`, or `repo`/`ref` for the newest build of a branch). OpenOCD runs on this server's host, except for an uploaded build on a cloud connection, which embeddedci.com flashes itself. The SWD pins, the reset flag and the target config come from the wiring profile when omitted. A failed flash is a normal result (ok=false): read target_unreachable (unpowered, mis-wired or held in reset), stalled, and the log tails to decide what to change; `artifact` says which build was flashed.

Parametersreffilerepobuildresetswclkswdionresettargetverifytimeoutartifact+7 more

capture_uart

Capture UART output

Record the DUT's UART output for a fixed window (or until until_regex matches). The channels and baud come from the wiring profile when omitted.

Parametersrxtxbauddurationrequireduntil_regex

power_cycle_and_capture

Power-cycle and capture boot log

Power the target off, then capture UART while it powers back on — the boot banner lands in the window. The channels, baud and power rail come from the wiring profile when omitted.

Parametersrxtxbauddelayefusedurationoff_settleuntil_regex

uart_open

Open a UART session

Start buffering the DUT's UART in the background (replacing any open session). Open it BEFORE an action whose output matters (power_on, reset_target), then uart_read; use uart_write to type into the DUT's console. The channels and baud come from the wiring profile when omitted; the result says which were used. A channel used as GPIO must be released first (gpio_release).

Parametersrxtxbaud

uart_write

Write to the DUT console

Send text to the DUT's RX through the open UART session.

Parameterstextrequiredline_ending

uart_read

Read the DUT console

Return the UART output received since the previous uart_read, optionally waiting for a match.

Parameterstimeoutuntil_regex

uart_close

Close the UART session

Stop the background UART session (safe when none is open).

Takes no parameters.

enable_i2c_sensor

Emulate an I2C sensor

Make the pod act as an I2C sensor on sda/scl for the DUT to read. Engage pull-ups on both lines first (set_pull). The channels come from the wiring profile when omitted. Readings start at the model's defaults; seed them with temperature_c/pressure_pa or values.

Parameterssclsdasensorvaluesaddresspressure_patemperature_c

set_i2c_sensor

Set emulated sensor values

Change what the emulated sensor reports (at least one value); the reply lists every reading.

Parametersvaluespressure_patemperature_c

i2c_sensor_types

Emulated sensor models

The sensor models the pod can emulate, with their addresses and settable readings (key, unit, range, default).

Takes no parameters.

disable_i2c_sensor

Stop the emulated sensor

Disarm the emulated sensor (safe when none is active).

Takes no parameters.

i2c_sensor_status

Emulated sensor status

Emulated sensor state plus bus activity counters — did the DUT talk to it at all?

Takes no parameters.

i2c_sensor_regs

Emulated sensor registers

Read the emulated sensor's register image.

Parametersstartlength

i2c_sensor_capture

Capture the sensor's I2C bus

Capture and decode the emulated sensor's I2C bus into a transaction trace (S/Sr/P, addr, data, ACK).

Parametersaddresssamplesregistersample_rate_hz

enable_gps

Emulate a GPS receiver

Make the pod play a GPS module: NMEA sentences (u-blox style, 9600 baud, 1 Hz by default) on tx for the DUT's UART. It runs on a second UART, so uart_open keeps working for the DUT's console. The position moves along course_deg at speed_kmh between fixes. Needs gateware v48 ("gps" in status caps).

Parameterstxrequiredfixutcbaudhdoprate_hzsentencesspeed_kmhaltitude_mcourse_degsatelliteslatitude_deg+1 more

set_gps

Set the emulated GPS fix

Change the fix the emulated GPS prints (any subset); the next sentences carry it.

Parametersfixutchdopspeed_kmhaltitude_mcourse_degsatelliteslatitude_deglongitude_deg

disable_gps

Stop the emulated GPS

Stop the GPS receiver and release its pin (safe when none runs).

Takes no parameters.

gps_status

Emulated GPS status

The GPS receiver's session (tx, baud, rate, epochs, overruns) and the fix it prints.

Takes no parameters.

set_pull

Set pull resistors

Engage or release the fixed bias resistors: LA1-LA6 pull UP (I2C lines), LA7/LA8 pull DOWN. The resistors are 3V3-referenced, so the pod refuses to engage them with the LA bank at 1.8 V.

Parameterslasrequiredenabledrequired

usb_network

USB network setting

Read the pod's USB network setting (firmware 3.9.0+), or turn the adapter off. Only the device changes it: Ethernet, Wi-Fi and the cloud may only read it.

Parametersset

lan_tls

LAN TLS

The pod's LAN API over TLS (firmware 3.9.3+): port 8443, HTTPS-only, and the certificate clients pin (sha256). Connections to a network address use TLS by default when the pod has it. Installing your own certificate: `benchpod lan-tls set-cert` or the SDK's set_lan_tls_cert.

Parametershttps_onlyregenerate

pull_status

Pull resistor status

State, direction and value of the bias resistor on LA1-LA8.

Takes no parameters.

la_pins

LA pin functions

What owns each of the 14 LA channels — none (free), gpio, uart_rx/uart_tx, swd_clk/swd_dio, i2c_sda/i2c_scl or step — plus each channel's GPIO mode, commanded level and bias resistor. Read this when a tool is refused with a pin conflict: it names the owner to stop. Live pin levels come along when the gateware can read them. Captures observe every channel whatever owns it.

Takes no parameters.

gpio_mode

Use LA channels as GPIO

Claim LA channels as GPIO so the pod can drive or read them. A channel stays GPIO — across disconnects — until gpio_release, and while it is GPIO nothing else can use it: release it before uart_open, flash or enable_i2c_sensor on that channel. A channel already owned by another function is refused with a PinConflictError naming the owner and the tool that frees it, and an engaged bias resistor that would fight the mode with a PullConflictError.

Parameterslarequiredmodelevel

gpio_write

Drive GPIO channels

Set the level of GPIO output / open-drain channels (all of them in one pod command). The channels must already be in an output or open_drain mode (gpio_mode).

Parameterslarequiredlevelrequired

gpio_read

Read pin levels

The live level (0/1) of LA channels — omit `la` for all 14. Works whatever owns a channel; a GPIO input is the usual way to watch a DUT output.

Parametersla

gpio_wait

Wait for a pin level

Wait until an LA channel reads `level` (reached=false when the timeout passes first). Polled from this host, so it resolves to a few milliseconds plus the round trip — for precise timing use a triggered capture (capture_la trigger_la) and la_timing instead.

Parameterslarequiredleveltimeout

gpio_pulse

Pulse a GPIO channel

Emit FPGA-timed pulses on an LA channel — a trigger for the DUT, or a step/dir motor train. The channel must be free or a GPIO output at level 0 (it returns to its GPIO level afterwards). The FPGA runs the train by itself, so this returns as soon as it starts.

Parameterslarequiredcountwidthrequired

gpio_release

Release GPIO channels

Stop using channels as GPIO — they go back to high-Z, watched by captures. Omit `la` to release every GPIO channel (channels owned by other functions are left alone). Do this before starting a UART session, flashing or emulating a sensor on those channels.

Parametersla

analog_path

Apply an analog path

Set every analog mux and relay for a named path in one step. dac_3v3/dac_5v/dac_12v route the DAC to an output; adc_ext connects the ADC to the front SMA; cal1/cal2 loop the 5 V/12 V DAC output back into the ADC; current_in reads the 4-20 mA input (J8); current_out switches the DAC voltage outputs off for the 4-20 mA output (J9); off parks everything. range also sets the ADC input range (see adc_range); off and cal2 put it back to normal.

Parameterspathrequiredrange

adc_range

Read or switch the ADC input range

Read or switch the ADC input range (analog module v3r4 and later, firmware 3.9.5+). normal is the ÷12 divider: ±32.8 V, about 1 mV per count. low is ÷2: ±5.46 V, about 167 µV per count, 6x the resolution for signals up to ±5 V. The low range's input is 1.82 MΩ: tell the user to use 1x leads, not a 10x scope probe. It applies to ext, cal1 and current_in (not cal2), and stays until changed, analog_path off or cal2, or a pod restart. capture_adc and adc_read scale with the range in use. A board without the low range refuses low. Use low for small signals; switch back to normal when a reading or capture reports overrange.

Parameterstestrange

dac_output

Set a DC output voltage

Route a DAC output (3v3, 5v, 12v = ±12 V, or off) and drive a calibrated DC voltage on it.

Parameterspathrequiredvolts

current_out

Set the 4-20 mA output current

Hold a current on the 4-20 mA output (terminal J9), in amps, or read the range it can do. The output is loop powered: it needs an external floating supply on J9 (pin 1 plus to the supply plus, pin 2 through the receiver to the supply minus; 8 V plus 20 mA times the loop resistance, 30 V at most). Nothing in the loop may touch pod ground, also not through a target that shares a ground with the pod. Ask the user how J9 is wired before driving it. Never wire J9 straight into J8. The pod cannot see the loop: the call succeeds with the supply off. It cannot go below its live zero (a little above 0.004 A) or above about 0.0201 A: there is no 0 mA or 21 mA level. 0.004 gives the lowest current; anything else outside the range fails with the pod's message. The DAC is shared: this switches the 3v3/5v/12v outputs off, and dac_output, generate and replay also move the loop current. dac_stop leaves the loop where it was: set 0.004 to go back to 4 mA.

Parameterscurrent

adc_read

Read a voltage

One calibrated voltage: ext = front SMA (true volts), cal1/cal2 = DAC loopbacks, current_in = 4-20 mA input (J8). current_in also returns current, the loop current in amps. range low reads small signals (up to ±5.46 V) at 6x the resolution on analog module v3r4+; overrange says the input clipped. Refused while the input is still moving (e.g. a DAC output left running).

Parametersrangesource

calibration

Read the pod's calibration

The calibration the pod has stored: the ADC offset (measured on current_in, terminal J8) and the fit it uses, and on firmware 3.9.4+ the DMM-guided parts (dac: a line per DAC output, adc_gain_ppm: a gain per ADC source). calibrated=false means the pod has no ADC offset: analog readings can be off by up to about 50 mV.

Takes no parameters.

calibrate

Calibrate the pod

Calibrate the pod's ADC offset on its 4-20 mA input (current_in, terminal J8) and store it on the pod. Nothing may be connected to J8: ask the user to disconnect it and confirm before calling. The pod reads its open input (0 V) and keeps that offset in flash. Every later adc_read on current_in has it taken out, and on firmware 3.9.3+ every other ADC source and capture too (they share the front end). Once per pod is enough: it survives reboots and firmware updates. The pod refuses, and keeps what it had, when something is driving J8 (more than 150 mV; 50 mV before 3.9.3). clear=true removes the stored calibration (all of it, or clear_part only).

Parametersclearsourceclear_part

calibrate_dac

Store a DAC output's DMM calibration

Store this pod's own line for a DAC output from two DMM readings (firmware 3.9.4+, cap cal_dmm). The flow, with the user holding a bench DMM: ask them to disconnect the DUT from the output and put the DMM (DC volts) on that output's SMA. Drive each set point with dac_output(path, volts) and keep its code16 (3v3: 0.5 and 3.0 V, 5v: 1.0 and 4.0 V, 12v: -5 and +5 V), ask the user what the DMM reads, then call this with both points and switch the output off (dac_output path off). From then on dac_output, generate and the DAC output limits use this line. The pod refuses, and keeps what it had, when the codes are too close, a reading is far from what the output should give (DMM on the wrong SMA) or the gain is more than 2 % off.

Parameterspathrequiredcode1requiredcode2requiredvolts1requiredvolts2required

calibrate_adc_gain

Measure an ADC gain through a loopback

Measure and store an ADC source's gain against a DMM-calibrated DAC output (firmware 3.9.4+). Needs the ADC offset (calibrate) and the driving output's DMM line (calibrate_dac on 5v for cal1, 12v for cal2 and ext). For ext, ask the user to connect the SMA cable first. The pod drives two set points itself and switches the outputs off afterwards; measured says what it saw. Nothing to read on a DMM.

Parametersrangesourcerequired

capture_adc

Capture an ADC waveform

Capture the ADC and summarise it: calibrated stats, dominant frequency and a min/max envelope. source current_in captures the 4-20 mA input (J8): the stats are then the loop current in amps (unit "A"), and replay(from_last_capture=true, dac_path="current_out") plays it back as a current. Above 32768 samples the capture streams from PSRAM (multi-second captures work). With trigger_la the capture waits for that edge or level, so t = 0 is the trigger moment. The capture is kept for replay and save_capture_as_recording. range low captures small signals (up to ±5.46 V) at 6x the resolution (see adc_range); clipped_samples > 0 means it clipped. oversample suits measuring small or near-0 V signals at low rates; the result's oversample says how many conversions each sample averages, and it fails if they do not fit in the pod's memory.

Parametersrangepointssourcesamplesoversampletrigger_latrigger_edgesample_rate_hztrigger_timeout

capture_la

Capture logic channels

Capture all 14 LA channels and summarise each: levels, edge count, first edge, estimated frequency. With trigger_la the capture starts on that edge or level instead of immediately, so a short event can be caught at a high sample rate. The capture is kept for decode_la and la_timing. sample_rate_hz goes up to 12 MS/s, or 48 MS/s on gateware v50+ (48, 24 or 16 MS/s); above 12 MS/s a signal changing over ~6 million times per second for long stops early (truncated); retry at 12 MS/s.

Parameterssamplestrigger_latrigger_edgesample_rate_hzstop_dac_aftertrigger_timeouttrigger_pre_samples

capture_correlated

Capture ADC and logic together

ADC and LA captured from one hardware trigger, so their timebases align. Both are kept. With trigger_la both streams start on that edge or level. There is one ADC: the analog half is the SMA voltage or, with source current_in, the loop current on J8 (unit "A"), never both.

Parameterspointssourcela_samplestrigger_laadc_samplestrigger_edgestop_dac_aftertrigger_timeoutla_sample_rate_hzadc_sample_rate_hz

decode_la

Decode a logic capture

Decode I2C (sda, scl), UART (rx, baud) or SPI (sclk + mosi/miso/cs, mode) from LA data. Sample at least ~10x the bit rate. Decoding the last capture again with other channels is free.

Parameterscsrxsclsdabaudmisomodemosisclkcapturesamplesprotocolrequired+2 more

la_timing

Measure timing on a logic capture

Timing of one channel of the last capture_la / capture_correlated: edge timestamps, pulse widths, frequency and duty cycle; with to_la, the delay from its first edge to the next edge on to_la (e.g. a trigger pin to a "result ready" pin). Resolution is one sample; no new capture is taken.

Parameterslarequirededgeafterto_lato_edgemax_edges

generate

Generate a waveform

Drive a sine, square or sawtooth on a DAC output. On firmware 3.9.1+ the pod takes the volts, calibrates them for dac_path and plays the waveform at the DAC's full 16 bits (bits=16 in the result); both peaks must then be on the path. Older firmware builds it from 8-bit levels (bits=8): 256 steps over the path. dac_path current_out plays it as a current on the 4-20 mA output (J9), with amplitude and offset in amps and steps of about 0.25 uA (63 uA on older firmware). See current_out for the wiring it needs. The DAC voltage outputs are switched off for it. dac_stop then leaves the loop where the waveform stopped: call current_out(0.004) to go back to 4 mA.

Parametersrouteoffsetfreq_hzrequireddac_pathdurationwaveformrequiredamplituderequiredon_capturesample_rate_hz

dac_stop

Stop the DAC

Stop any DAC output: generator, replay or control loop. The 4-20 mA output follows the DAC, so after a waveform on current_out the loop stays where it stopped: call current_out(0.004) to return it to 4 mA.

Takes no parameters.

replay

Replay a waveform

Loop a waveform out of the DAC until dac_stop — agent-provided volts or the last ADC capture. More than 2048 samples need the deep_replay gateware image, switched to automatically. dac_path current_out replays on the 4-20 mA output (J9) in amps. A last capture of current_in replays as the same current; a capture of a voltage source there needs mapping fit. See current_out for the wiring. After dac_stop, call current_out(0.004) to go back to 4 mA.

Parametersfaultroutevoltsmappingdac_pathon_captureswitch_imagesample_rate_hzfrom_last_capture

list_waveforms

List library waveforms

The organisation's cloud waveform library (needs a cloud connection or BENCHPOD_API_KEY).

Takes no parameters.

cloud_list_devices

List cloud BenchPods

The BenchPods registered to your embeddedci.com organisation, and whether each is online. Drive one with connect(connection) using the returned `connection` ('embeddedci:<name>'). Needs no pod connection. Authenticates with BENCHPOD_API_KEY, else the session saved by `benchpod login` (~/.config/benchpod-cli/token.json), refreshing it when it has expired.

Takes no parameters.

replay_waveform

Replay a library waveform

Loop a cloud-library waveform out of the DAC until dac_stop (needs server access). A recording too long for a shallow replay switches the pod to the deep_replay gateware image (unless target_samples asks for a downsampled replay). dac_path current_out replays on the 4-20 mA output (J9). A recording with unit mA replays there as the same current; a recording in V there, or a mA recording on a voltage output, needs mapping fit. After dac_stop, call current_out(0.004) to go back to 4 mA.

Parametersfaultmappingdac_pathon_capturewindow_lenwaveform_idrequiredswitch_imagewindow_startsample_rate_hztarget_samples

save_capture_as_recording

Save capture to the library

Save the last capture_adc to the cloud waveform library as a replayable recording. A capture of current_in (the 4-20 mA input) is stored as a current, unit mA; full_scale_v is then in mA too.

Parametersnamerequiredfull_scale_v

control_loop

Arm the DAC control loop

Run a tabulated transfer function in the FPGA: each tick out = curve[input], damped and clamped. Needs the loop gateware image, switched to automatically. Poll with loop_probe, move the input with loop_input, stop with dac_stop.

Parameterskstepvmaxvmincurvepointssourcetick_divvoc_codeinput_mapsharpnessinput_code+1 more

loop_input

Move the loop input

Re-target the running loop's input without re-arming (hold a point, meter, move on).

Parametersstepsourceinput_code

loop_probe

Probe the control loop

The running loop's live operating point; assert against loop_input, not i, in open-loop runs.

Takes no parameters.

fpga_image

Switch gateware image

Reprogram the pod's FPGA with another stored gateware image (~2-3 s). Rarely needed: control_loop, replay and replay_waveform switch automatically. The switch resets the FPGA (any DAC output, UART session or I2C sensor emulation stops).

Parametersimagerequired

can_open

Open CAN

Bring up the pod's CAN interface (replacing any open CAN session).

Parametersmodetermbitrate

can_write

Send a CAN frame

Queue one classic CAN frame.

Parametersextrtrdatacan_idrequired

can_read

Read CAN frames

Read received CAN frames (id, data, timestamps in pod milliseconds).

Parameterscan_idtimeoutmax_frames

can_respond

CAN auto-responder

Make the pod firmware answer a CAN id instantly (ECU simulation), or clear all rules.

Parametersclearmatch_idreply_idmatch_extreply_extreply_data

can_status

CAN status

CAN link state: mode, bitrate, error counters, bus-off.

Takes no parameters.

can_close

Close CAN

Clear responder rules and stop CAN (safe when not open).

Takes no parameters.

la_step

Step pulse train

Emit step pulses on an LA channel (step/dir motor drivers); with dir_la, set direction first. The step rate is 1 / (2 x delay). The FPGA runs the train by itself; this returns as soon as it starts. More than 65535 steps: send several trains (wait for the pod between them).

Parameterslarequireddelayrequiredstepsrequireddir_ladirection

command

Raw firmware command

Escape hatch for firmware commands no tool covers. Prefer the dedicated tools.

Parametersrequestrequired

vision_connect

Connect to an AI Vision board

Open the AI Vision connection (closing a previous one) and report the board's status.

Parameterstokenlease_asconnection

vision_cloud_boards

List AI Vision boards on embeddedci.com

The AI Vision boards registered to your embeddedci.com organisation, and whether each is online. Connect to one with vision_connect(connection) using its `connection` ('embeddedci:<name>'). Authenticates with BENCHPOD_API_KEY, else the `benchpod login` session.

Takes no parameters.

vision_disconnect

Disconnect from the AI Vision board

Close the AI Vision connection and release its lease. Safe when not connected.

Takes no parameters.

vision_status

AI Vision board status

Version, calibration, camera lock, actuators, lease and capabilities, with warnings for what will block the next steps.

Takes no parameters.

vision_look

Look at the screen

The screen as an image, so you can read it yourself. With a region: that part at full sensor resolution (small text, icons).

Parametersspaceregionoverlay

vision_ocr

Read text on the screen

All text on the screen (or a region) in reading order, with boxes.

Parameterszoomregion

vision_find

Find on the screen

Elements matching text, a kind or a template, best first. Text inside a button returns the button (its center is where a press taps).

Parameterstextlabelmatchregiontemplate

vision_wait_for

Wait for the screen

Wait until text appears (or, with gone, disappears). Returns met=false on timeout.

Parametersgonetextrequiredmatchregiontimeout_s

vision_compare

Compare with a reference screen

How close the screen is to a stored reference screen.

Parametersmaskreferencerequired

vision_screen_health

Screen health

Black, dim, frozen, flickering or stuck pixels over a window of frames.

Parameterswindow_msexpect_motion

vision_calibrate

Calibrate the screen

Map camera pixels to screen pixels; every later box and tap is in screen pixels.

Parametersmethodcornersscreen_size

vision_add_reference

Store a reference screen

Store the current screen (or a region of it, e.g. an icon as a template) as a reference.

Parameterskindnamerequiredregionoverwrite

vision_press

Press on the screen

Find the element, tap its center with the actuator, and optionally verify the result. Fails with the visible text when the element is not on the screen.

Parameterstextrequiredmatchactuatorverify_textverify_timeout_s

vision_tap

Tap a point

Tap a point in screen pixels (hold_ms > 0 for a long press).

Parametersxrequiredyrequiredhold_msactuator

Version history

22
  1. v2.13.9LatestNov 7, 2026
  2. v2.13.8Nov 4, 2026
  3. v2.13.7Nov 3, 2026
  4. v2.13.5Oct 31, 2026
  5. v2.13.4Oct 30, 2026
  6. v2.13.3Oct 20, 2026
  7. v2.13.2Oct 19, 2026
  8. v2.13.1Oct 16, 2026
  9. v2.13.0Oct 15, 2026
  10. v2.12.0Oct 13, 2026
  11. v2.11.0Oct 12, 2026
  12. v2.10.0Oct 9, 2026
  13. v2.9.0Oct 9, 2026
  14. v2.8.0Oct 9, 2026
  15. v2.7.0Oct 8, 2026
  16. v2.6.0Oct 7, 2026
  17. v2.5.0Oct 5, 2026
  18. v2.3.0Sep 29, 2026
  19. v2.2.0Sep 29, 2026
  20. v2.1.1Sep 23, 2026
  21. v2.1.0Sep 22, 2026
  22. v2.0.0Sep 21, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts