TofuPilot Framework

Last updated on May 21, 2026

The TofuPilot Framework is a Python test framework purpose-built for hardware. A single procedure.yaml declares phases, measurements, plugs, operator UI, and execution policy, so you can ship a new procedure in minutes instead of stitching together a custom runner. Source on GitHub at tofupilot/framework, MIT licensed.

You write the YAML, push to Git, and the CLI handles bootstrapping, validation, and execution end-to-end. It is the only framework that exposes the full feature set, since the OpenHTF, Pytest, and Robot connectors each cover a subset.

Getting started

To get started with the TofuPilot Framework:

  • Clone our starter template to your favorite Git provider and deploy it on TofuPilot.
  • If you have a procedure directory, push it to your Git provider and import it from the New Procedure wizard.

Procedure structure

A procedure is a directory with procedure.yaml at its root. The CLI reads the file, bootstraps Python through uv, and runs every phase declared in the YAML.

procedure.yaml
procedure.yaml
name: Battery Functional Test
version: 1.0.0
description: Validates battery voltage, capacity, and charge cycles
id: 1cf7e59a-bf0a-11f0-b044-639a4e9293c8

unit:
  serial_number:
    pattern: "^BAT[0-9]{6}$"
  part_number:
    default_value: "BAT-001"

plugs:
  - name: power_supply
    python: plugs.psu:PowerSupply

setup:
  - name: Power On
    python: phases.power_on

main:
  - name: Measure Voltage
    python: phases.measure_voltage

teardown:
  - name: Power Off
    python: phases.power_off

Run the procedure from its directory:

Terminal
tofupilot run

Fields

The top-level keys map directly to procedure resources on the dashboard.

FieldPurpose
nameDisplay name. 1-100 characters. Required.
versionFree-form version string. 1-50 characters. Required.
descriptionOptional documentation, up to 50,000 characters.
idDashboard procedure UUID. Links uploaded runs to the procedure record.
unitUnit identification rules and defaults.
plugsLong-lived resources for phases.
executionWorker count, slots, strategy, on-fail policy. See Execution.
setupPhases that run before main. Aborts if any setup phase fails.
mainBulk of the test. Runs after setup completes.
teardownAlways runs, even after failure. Releases resources.

Phases

A phase is a Python function referenced from procedure.yaml. The CLI imports the module, calls the function, and records the outcome.

phases/measure_voltage.py
def measure_voltage(test, power_supply):
    voltage = power_supply.read_voltage()
    test.measurements.voltage = voltage

See Phases for the full reference.

Measurements

A measurement is a typed value declared on a phase and assigned at runtime. Limits, units, and outcome are evaluated by the CLI and uploaded to the dashboard.

procedure.yaml
main:
  - name: Measure Voltage
    python: phases.measure_voltage
    measurements:
      - name: voltage
        unit: V
        limits:
          min: 4.8
          max: 5.2

See Measurements for the full reference.

Plugs

A plug is a long-lived resource shared across phases. The CLI instantiates it once per slot and tears it down when the procedure ends.

plugs/psu.py
class PowerSupply:
    def __enter__(self):
        return self

    def __exit__(self, *args):
        pass

    def read_voltage(self) -> float:
        return 5.03
procedure.yaml
plugs:
  - name: power_supply
    python: plugs.psu:PowerSupply

Operator UI

The Operator UI block declares interactive prompts that stream to the kiosk and the dashboard. The phase pauses until the operator responds.

procedure.yaml
main:
  - name: Confirm Cable
    operator_ui:
      type: prompt
      message: "Plug in the USB cable"

See Operator UI for the full reference.

Execution

The execution block controls worker count, parallel slots, scheduling strategy, and failure handling.

procedure.yaml
execution:
  strategy: phase_first
  workers: 16
  on_first_failure: stop
  slots:
    - name: USB0
    - name: USB1

See Execution for the full reference.

Monorepos

When a repo hosts multiple procedures that share phases and plugs, the Monorepos layout keeps shared code in one place.

Offline upload

When the bench loses connectivity, the CLI queues runs locally and uploads them when the network comes back. Each queued run keeps its original timestamp, so analytics stay accurate even when uploads land hours later. See tofupilot queue.

How is this guide?

On this page