TestHub · Hardware-in-the-loop CI
Every pull request, tested on real boards.
A self-hosted test farm in your own lab. CI pipelines and developers run firmware tests on real hardware and emulators behind your firewall, and nothing in the lab has to be reachable from outside.
- No open ports, no VPN
- Your existing tests, unchanged
- Results in the pull request
$ thub run --type hw \ --label board:nucleo-f401re \ --command './ci/test.sh' --wait Job A-00123 → lab-hw-01 QUEUED → RUNNING flash ok · uart captured · pytest PASSED tests=42 failed=0 → comment posted on PR #128
The problem
Your boards sit in a closed lab. Your CI can’t reach them.
Manual pre-release testing
Flash, watch the log, repeat. Every release waits for someone at the bench.
Bugs that only real hardware shows
Timing, peripherals and power issues pass in simulation and ship to the field.
Benches nobody can share
Engineers collide on the same board, and remote developers can’t reach it at all.
How it works
One command. Same in CI and on a laptop.
The same thub run works in a pipeline step and on a developer’s machine, so any CI failure can be reproduced exactly.
AGENT · thub
CI pipelines and developers
A CLI and the only way to create jobs. GitHub Actions, GitLab, Bitbucket, Jenkins or a laptop.
COORDINATOR
Queue, scheduler, dashboard
The single source of truth and the only thing with a public HTTPS port. Logs stream live.
Your lab · no inbound traffic
CLIENT · HW
Flashes and tests a real board
CLIENT · SW
Runs an emulator in Docker
- 01 Submit
thub run sends the job: type, labels, command, inputs.
- 02 Schedule
It goes to an idle Client with matching type, labels and group. Never assigned twice.
- 03 Prepare
The Client downloads the firmware and prepares the board or emulator.
- 04 Run
Your command runs. Output and UART logs stream live to CI and the dashboard.
- 05 Verdict
Exit code 0 passes the pipeline, anything else fails it. JUnit counts come back.
Use cases
One lab, every way your team tests
Hardware-in-the-loop CI
Every push builds firmware, then a lab Client flashes a real board through ST-Link, captures its UART and runs the tests.
Emulator test farms
QEMU, Renode or your simulator on Linux machines, in Docker if you like. Scale out by adding VMs.
Remote access for developers
Reproduce a CI failure from home on the exact bench where it happened.
Shared benches without collisions
Reserve a board for 30 minutes to 8 hours or a custom time. Only your jobs run on it, and it’s released when your time is up.
Dedicated pools
Groups and labels route jobs to the right hardware, such as a nightly pool apart from the PR pool.
Any command-line test suite
pytest, ctest, Robot Framework, a Dockerized runner or a flash-and-smoke script.
Remote bench
The lab bench, inside VS Code
Reserve a board from your editor, then flash, debug, read its serial ports and watch its camera as if it sat on your desk. The lab still allows nothing inbound, and everything ends with the reservation.
- RESERVE30 minutes, 2, 4 or 8 hours, or a custom time. One HIL and one SIL reservation at once.
- FLASHOne click or a build task, then verify the sections.
- DEBUGF5 and the standard debug keys, with Cortex-Debug over SWD and OpenOCD.
- UARTA VS Code terminal with timestamps, a log file or a plain TCP socket.
- RTTSEGGER RTT logs in a terminal.
- CAMERAIn VS Code, mpv, ffplay, VLC or the browser. Private to your reservation.
- AUDITWho reserved which board, for how long, and how it ended.
lab-hw-01 · HIL reserved for you until 16:00 UART dut1-uart localhost:40001 GDB ST-Link #1 localhost:3333 RTT channel 0 localhost:19021 CAMERA bench camera localhost:8890 WEB client:8080 localhost:48080 ⚠ 10 minutes left · Extend +1 h · Release
Features
Everything you need, in the VS Code extension
TestHub works where embedded developers already are: the editor. Every feature below runs in VS Code and talks to your own Coordinator.
Lab and reservations
- Reserve a board from your editor, for 30 minutes, 2, 4, 8 hours or a custom time.
- Hold one hardware (HIL) and one simulation (SIL) reservation at the same time.
- Warnings 10 minutes before a reservation ends, with one-click extend (+1 hour) or release.
- Only your jobs run on a board you hold; the board is released when your time is up.
Flash and debug from VS Code
- Flash your firmware (ELF) to a remote board with one click or as a build task.
- Flash after build: a successful build loads the board automatically, with an option to skip an unchanged image.
- Verify after flashing (compare sections), then reset and run, stay halted, or start debugging.
- Debug a remote board with F5 and the standard debug keys, using Cortex-Debug.
- Pick which ST-Link (SWD probe) to flash or debug from its record in the tree.
- Works over SWD through OpenOCD on the board’s Client; choose the OpenOCD target per board or per workspace.
- Reattach the debugger if the link drops.
Serial and logs
- Open a board’s UART as a VS Code terminal, with timestamps.
- Log everything a serial terminal receives to a file.
- Reach any UART from other tools as a plain TCP socket (for example pyserial).
- RTT logs: read SEGGER RTT output from the board in a terminal, with saved search settings per board.
Camera
- Watch a board’s camera inside VS Code, in an external player (mpv, ffplay, VLC) or in the browser.
- Each camera stream is private to your reservation and stops when the reservation ends.
Ports and services
- One Ports panel lists every local port of your connected boards: UARTs, GDB, RTT, camera and forwarded services.
- Forward a service running on a board’s Client (for example a web UI) and open it inside VS Code.
- Forwarded services are saved per board and restored on reconnect.
Setup and security
- Sign in to your Coordinator with an access key, stored in VS Code’s secret storage.
- The Agent CLI installs and updates itself in VS Code; no separate install is needed.
- Works over Remote-SSH, WSL and dev containers: the tools run on the same machine as the extension.
STM32G491
Demo firmware included
An STM32G491 demo project: periodic UART tick message, LED control over UART (ON, OFF, BLINK), RTT logs and an independent watchdog that resets the board if it stops responding.
Limits, stated plainly
- USB power control (power cycle, on, off) works only during a job of yours on the board. Reservation-wide power control isn’t available yet.
- Forwarded services must listen on 127.0.0.1 on the Client host.
Hardware
Boards, flight controllers and simulators
Every target follows one pattern: name its USB port, flash with its own tool, check the result. Ready-made scripts and Client configs are in the examples repository.
| Target | Connected by | Flashed with | Checked over |
|---|---|---|---|
| STM32 Nucleo-64 | ST-LINK/V2-1 | st-flash | virtual COM port |
| nRF52840 DK | J-Link OB | nrfutil | VCOM |
| ESP32-DevKitC | CP2102N UART | esptool | UART |
| Arduino Uno R3 | ATmega16U2 | avrdude | USB serial |
| Betaflight FC | STM32 USB / DFU | dfu-util | Betaflight CLI |
| ArduPilot FC | USB | uploader.py | MAVLink |
| Renode | SW Client, Docker | — | emulated UART |
| QEMU | SW Client | — | semihosting, exit code |
Plus USB power control through a smart USB hub during your jobs, several boards per Client, and scheduled nightly reboots.
CI/CD & code review
Results where reviewers already look
The pipeline step fails exactly when the tests do. Canceling the pipeline cancels the job. Failed tests land in the pull or merge request.
JUnit from pytest, ctest, Robot Framework, Maven and Gradle.
Pull request #128 · comment
42 tests · 0 failed · 0 skipped
- uses: andrianyablonskyy/thub-action@v1
with:
type: hw
key: ${{ secrets.THUB_CI_TOKEN }}
Illustrative comment. The GitHub action also writes a Job Summary and a commit status.
Security
Built to pass your security review
- Outbound only. Lab machines dial out over HTTPS. The firewall allows nothing inbound.
- On-premises. Firmware, logs and job state stay in your network.
- Least privilege. Jobs run as a service user, never as root. CI tokens never reach the lab.
- SSO. GitHub and Google sign-in, limited to your organisations or domains.
- Audit log. Every change and reservation, with who made it.
Deployment
Small to run, quick to start
One Coordinator, a single Node.js process with SQLite, serves tens of Clients and hundreds of jobs a day. A quick-start script brings up a Coordinator, a demo Client and a first job in minutes.
Start with a pilot
30 days in your lab, credited to the licence
We set TestHub up for you: up to 4 boards, one pipeline and hands-on support. The paid pilot is credited to the annual licence.
Bench review
Show us your bench.
In 30 minutes we map your boards and pipeline to a TestHub setup and a pilot plan. Free.