GitHub Action

Build firmware and run it on real hardware with onmcu/onmcu-action.

onmcu/onmcu-action installs the OnMCU CLI and runs firmware on real hardware. The step fails if the hardware job fails, is cancelled, or times out.

Quick start

.github/workflows/hardware.yml
name: Hardware

on: [push]

permissions:
  contents: read

jobs:
  run:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Build firmware
        run: cargo build --release

      - name: Run on real hardware
        uses: onmcu/onmcu-action@v1
        with:
          board: NUCLEO-H743ZI
          file: target/thumbv7em-none-eabihf/release/my-firmware
          api-key: ${{ secrets.ONMCU_API_KEY }}

Create ONMCU_API_KEY as a repository or organization secret before running the workflow.

Inputs

InputRequiredDefaultDescription
boardyesBoard identifier, for example NUCLEO-H743ZI.
fileyesFirmware path, resolved relative to working-directory.
api-keyyesOnMCU API key. Always pass a GitHub secret.
servernohttps://ctrl1.onmcu.comOnMCU controller URL.
versionnolatestCLI release, such as 0.4.1, or latest. An optional leading v is accepted.
chunk-sizeno5Upload chunk size in MiB.
retriesno3Retries for each failed upload chunk.
timeoutno600Maximum hardware job runtime in seconds.
working-directoryno.Directory where the onmcu run command executes.
argsnoemptyExtra raw options appended to onmcu run.

Pin the CLI version

onmcu/onmcu-action@v1 pins the action's major version. The version input selects the CLI release. Set both if a CLI update must not change a run.

Build Rust and capture the ELF

Use Cargo's JSON output to find the linked executable without guessing its path:

- name: Provision Rust toolchain and targets
  run: rustup show

- uses: Swatinem/rust-cache@v2

- name: Build
  id: build
  run: |
    set -euo pipefail
    shopt -s inherit_errexit
    elf=$(cargo build --release --message-format=json \
      | jq -r 'select(.reason=="compiler-artifact" and .executable!=null) | .executable' \
      | tail -n1)
    echo "elf=$elf" >> "$GITHUB_OUTPUT"

- name: Run on real hardware
  uses: onmcu/onmcu-action@v1
  with:
    board: NUCLEO-H743ZI
    file: ${{ steps.build.outputs.elf }}
    api-key: ${{ secrets.ONMCU_API_KEY }}

inherit_errexit makes the step fail if the build inside the command substitution fails.

Serial logging and other CLI options

Use args for optional onmcu run flags:

- uses: onmcu/onmcu-action@v1
  with:
    board: NUCLEO-F401RE
    file: build/serial-test.elf
    api-key: ${{ secrets.ONMCU_API_KEY }}
    args: --logging serial --baud 115200

The action splits args on spaces. Use it for CLI options, not for untrusted input.

Working directory

When the firmware crate lives below the repository root, set the same directory for the build and run steps:

- name: Build
  working-directory: firmware/nucleo-h743zi
  run: cargo build --release

- uses: onmcu/onmcu-action@v1
  with:
    working-directory: firmware/nucleo-h743zi
    board: NUCLEO-H743ZI
    file: target/thumbv7em-none-eabihf/release/my-firmware
    api-key: ${{ secrets.ONMCU_API_KEY }}

What the action does

  1. Reuses a compatible onmcu already on PATH, or downloads the requested CLI release.
  2. Writes an ephemeral onmcu-ci.toml under RUNNER_TEMP with the server, upload, retry, and timeout inputs.
  3. Exposes the API key as ONMCU_API_KEY only to the run step.
  4. Invokes onmcu run --api-key-from-env with the selected board and firmware.

The CLI's exit code becomes the action step's exit code.

Minimize hardware runs on pull requests

For a repository with one directory per board, use a paths filter and a matrix to run only the firmware that changed. See the c-examples workflow: a board change runs that board, while a shared-library change runs every board.

On this page