A real product rarely has one target. There's the STM32 main board and the nRF radio module; this hardware revision and the last one you still ship; the variant with the extra sensor. Testing all of them on hardware usually means a single debug probe and a lot of cable-swapping — plug in board A, flash, test, unplug, plug in board B. That's fine at a desk. It's hopeless in CI, where there's nobody to move the cable.
The BenchPod is designed to make that problem go away in two complementary ways.
One pod handles any SWD target
A single pod isn't tied to one chip. Flashing goes through a standard OpenOCD target config, so covering a different MCU is a one-line change:
bp.flash(file="app.elf", target="target/stm32f4x.cfg", ...) # STM32F4
bp.flash(file="app.elf", target="target/stm32h7x.cfg", ...) # STM32H7
bp.flash(file="app.elf", target="target/nrf52.cfg", ...) # nRF52Same pod hardware, same wiring pattern, a heterogeneous set of targets. So your fleet can be identical pods even when your boards aren't.
A pod per board, wired once
The bigger idea is that BenchPods are built to be inexpensive — cheap enough that you don't share one across boards, you give every target its own. Wire each DUT to its own pod once, give it a stable name, and leave it connected. Nothing ever gets disconnected; CI just talks to whichever pod it needs.
To keep per-board coverage even cheaper, a smaller mini variant — the same flashing, power control, UART, and logic-analyzer features without the analog front-end — is on the roadmap, for the many targets that don't need a 16-bit ADC and DAC hanging off them.
The result is a standing rack of always-connected boards instead of one bench you keep re-cabling.
Naming and routing
Each pod connects out to embeddedci.com and gets a stable, URL-safe name on the BenchPod page (benchpod-stm32f4, benchpod-nrf52, …). A test selects one by name, and the same test runs against any pod by changing one string:
pytest --benchpod-connection=embeddedci:benchpod-stm32f4Per repository, you choose which devices that repo's GitHub Actions are allowed to drive — Any device, or a specific list — so a fleet can be shared across teams without anyone being able to grab a board they shouldn't.
Fan out in CI
Once every board is named and always connected, a test matrix is just a GitHub Actions matrix — one job per device, all running in parallel, each flashing and testing its own target:
permissions:
id-token: write
contents: read
jobs:
hil:
runs-on: ubuntu-latest
strategy:
matrix:
include:
- device: benchpod-stm32f4
target: target/stm32f4x.cfg
- device: benchpod-nrf52
target: target/nrf52.cfg
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with: { python-version: "3.12" }
- run: pip install pytest "embeddedci[cloud]"
- run: |
pytest hil/ -v \
--benchpod-connection=embeddedci:${{ matrix.device }} \
--benchpod-firmware=build/${{ matrix.device }}.elfEvery board in your matrix gets flashed and tested on every push, in parallel, with no human at the bench and nothing to reconnect. Add a new target? Wire up another pod, name it, and add a line to the matrix.
For the auth model behind embeddedci:<device-name> (GitHub OIDC, no stored secrets) see Run HIL in GitHub Actions; for the test API, the pytest framework docs.