How to Configure Station Variables with TofuPilot
Last updated on October 21, 2026
Learn how to run one procedure on several benches when each bench has its own DMM address and each environment talks to its own MES. The addresses become station Config environment variables, the MES URL a Config per environment and the MES token a Secret per environment, all set in TofuPilot instead of the repository.
| Key | Type | Read as | Set per |
|---|---|---|---|
DMM_RESOURCE | Config | {config.DMM_RESOURCE} | station |
MES_URL | Config | {config.MES_URL} | environment |
MES_TOKEN | Secret | {secrets.MES_TOKEN} | environment |
Prerequisites
- A procedure linked to TofuPilot and deployed with git deployments.
- Two stations running it, here
line-1-bench-1andline-1-bench-2. - The Admin or Owner role to set station variables and Secrets, and the CLI logged in as yourself (
tofupilot login).
Step 1: Declare the keys
Declare what the procedure reads, then use the keys in with:.
name: Board Functional Test
version: 1.4.0
config:
DMM_RESOURCE:
required: true
description: VISA resource of this bench's DMM
MES_URL:
required: true
secrets:
MES_TOKEN:
required: true
plugs:
- name: DMM
key: dmm
python: plugs.dmm:Dmm
with:
resource: "{config.DMM_RESOURCE}"
main:
- name: Measure 3V3
python: phases.rails.measure_3v3
- name: Report to MES
python: phases.mes.report
with:
url: "{config.MES_URL}"
token: "{secrets.MES_TOKEN}"The plug receives the resource as a constructor argument, and the phase receives the URL and the token as function arguments:
import pyvisa
class Dmm:
# resource comes from {config.DMM_RESOURCE}, set per station
def __init__(self, resource):
self.inst = pyvisa.ResourceManager().open_resource(resource)
def read_voltage(self):
return float(self.inst.query("MEAS:VOLT:DC?"))
def close(self):
self.inst.close()import requests
def report(unit, run, url, token):
# url and token come from with:, resolved for this run's environment
response = requests.post(
f"{url}/results",
headers={"Authorization": f"Bearer {token}"},
json={"serial": unit.serial_number, "station": run.station.name},
timeout=10,
)
response.raise_for_status()Step 2: Run it locally with .env.local
A source run reads the shell, then .env.local, then .env next to procedure.yaml, never the values stored in TofuPilot. Write the bench on your desk and the MES sandbox:
DMM_RESOURCE="TCPIP0::192.168.1.50::INSTR"
MES_URL="https://mes-sandbox.example.com"
MES_TOKEN="sandbox-token-0001"echo ".env*" >> .gitignore
tofupilot run .Once development values are stored in TofuPilot (Step 3), tofupilot env pull writes this file for you.
Step 3: Set the values
Set the DMM address on each station as a Config. A station variable counts as production and applies to every procedure the station runs:
tofupilot env add DMM_RESOURCE --station line-1-bench-1 --no-sensitive --value "TCPIP0::10.0.1.21::INSTR"
tofupilot env add DMM_RESOURCE --station line-1-bench-2 --no-sensitive --value "TCPIP0::10.0.1.22::INSTR"Set the MES URL per environment on the procedure, from the linked directory:
tofupilot env add MES_URL production --no-sensitive --value https://mes.example.com
tofupilot env add MES_URL preview,development --no-sensitive --value https://mes-sandbox.example.comSet the token per environment. Secret is the default type, and the CLI asks for each value at a hidden prompt:
tofupilot env add MES_TOKEN production --note "Rotate in the MES admin console"
tofupilot env add MES_TOKEN preview,developmentThe same values can be set in the dashboard: the station page's Environment Variables section, and the procedure's Environment Variables tab.
Step 4: Check what each station receives
tofupilot env resolve production --station line-1-bench-2Key Value Wins Shadows
DMM_RESOURCE TCPIP0::10.0.1.22::INSTR station -
MES_URL https://mes.example.com procedure -A Secret shows a digest, never its value. The narrowest level wins: station, then procedure, then Shared.
Step 5: Deploy and run
Merge to the production branch. The stations pull the build, fetch their values and keep them in an encrypted cache, so a run also starts when the server is unreachable. Saving a value later notifies the stations; the next run uses it, and the DMM plug reconnects with the new address.
A station missing a required value does not start the run. The operator sees Station not configured with the missing keys, the procedure's Environment Variables tab flags them, and tofupilot service status on the station lists them:
Board Functional Test: not configured: config.DMM_RESOURCE missingEach run then records the values it used in its Environment Variables section: the DMM address and MES URL with their level and version, and MES_TOKEN by version only.
A preview build pushed to a line station receives the preview MES token, and no Secret set on the station itself. Production credentials only reach production builds. See the trust model for who can read a station's secrets.
Step 6: Rotate the token
tofupilot env update MES_TOKEN productionOr use Rotate in the row menu of the procedure's Environment Variables tab. The value is replaced and its version increases. Runs from then on record the new version, so you can tell which runs used which token. Only a value of 8 characters or more is masked in logs, so use long tokens.
How is this guide?