LabWired Whitepaper
Tutorial

Run your PlatformIO unit tests without hardware — or anything installed

Running pio test on real hardware means a board on the desk, a USB cable, and a free hand to press reset on push. With LabWired, none of that is necessary.

This guide wires your PlatformIO pio test suite into the LabWired simulator, a deterministic digital twin of your MCU. Your tests don't change and PlatformIO still parses the usual Unity output; the only thing missing is the board. If you take the GitHub Actions path below, you won't install anything locally either.

It plugs into PlatformIO the same way Renode and QEMU do: through the built-in test_testing_command hook. PlatformIO doesn't need to know anything about LabWired; it runs a command and reads the Unity results from its output.

Start in GitHub Actions (nothing to install)

Fork w1ne/labwired-core, drop this into .github/workflows/firmware-tests.yml, and push:

name: Firmware tests (LabWired simulator)
on: [push, pull_request]
jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install PlatformIO
        run: pipx install platformio
      - uses: dtolnay/rust-toolchain@stable
      - name: Build the LabWired CLI and put it on PATH
        run: |
          cargo build --release -p labwired-cli
          echo "$PWD/target/release" >> "$GITHUB_PATH"
      - name: Run unit tests in the simulator
        working-directory: examples/platformio/nrf52840-unity
        run: pio test -e nrf52840_dk

GitHub's runner installs PlatformIO, builds the firmware, runs it in the simulator, and reports per-test results, all without touching your machine:

test/test_smoke/test_main.c:50: test_addition         [PASSED]
test/test_smoke/test_main.c:51: test_uart_is_enabled  [PASSED]
test/test_smoke/test_main.c:52: test_string_length    [PASSED]
================== 3 test cases: 3 succeeded in 00:00:01.4 ==================

If you'd rather poke at it from a terminal, open the repo in a GitHub Codespace and run cd examples/platformio/nrf52840-unity && pio test. That still keeps everything off your machine, since the Codespace runs in your browser.

How it works

flowchart LR
  A["pio test<br/>builds firmware.elf"] -- ELF --> B["LabWired simulator<br/>boots the ELF · deterministic"]
  B -- "UART → stdout" --> C["PlatformIO<br/>parses Unity PASS/FAIL"]
  C --> D(("✓"))
  classDef ok fill:#27c93f,stroke:#1a1a1a,stroke-width:2px,color:#0d2b12;
  class D ok;
      
The board's role is played by a process; the rest of the pipeline is unchanged.

Both sides are plain headless processes connected by a pipe. There is nothing to flash, no probe to enumerate, and no board state to drift between runs — which is where most hardware-in-the-loop flakiness comes from.

The whole integration

It's a few lines of platformio.ini:

[platformio]                 ; so the ${platformio.*} vars resolve

[env:nrf52840_dk]
platform = nordicnrf52
board = nrf52840_dk

test_testing_command =
    labwired test --script labwired.test.yaml
    --firmware ${platformio.build_dir}/${this.__env__}/firmware.elf

pio test builds firmware.elf, then runs this command and parses its output. The only firmware-side requirement is that Unity's output reaches the UART that LabWired mirrors to stdout. With the Arduino, Zephyr, or mbed frameworks, that is your normal test serial already.

What you can test today. The simulator is solid for ARM Cortex-M firmware — bare-metal and minimal-HAL on validated boards (STM32, nRF52, RP2040). Full framework stacks are landing board by board: a complete Arduino-ESP32 + FreeRTOS image now boots to a rendered e-paper panel. Check the supported boards before pointing the loop at a large framework project. The example above is bare-metal nRF52840.

What you get out of it

The simulator executes your firmware on a full Cortex-M core model with peripherals validated against real silicon, so the instructions that run in CI are the ones that would run on the board.

See the runnable example →