Oᴘᴇʀᴏʀ Pᴇʀꜰᴏʀᴍꜱ Exᴘᴇʀɪᴍᴇɴᴛᴀʟ Rᴜɴꜱ Oɴ Rᴇꜱᴏᴜʀᴄᴇꜱ
Operor loads instrument adapters and recipes from YAML, precompiles command templates and expressions, and executes scheduled tasks with optional preview, dry-run, CSV output, and TCP streaming.
- Instruments with VISA interface
- A VISA implementation to talk to instruments at runtime, such as NI-VISA or Keysight IO Libraries1
- For Linux or macOS users, Run
# download and install operor via a install script curl -fsSL https://raw.githubusercontent.com/uwni/operor/main/install.sh | sh # or use wget instead via wget -qO- https://raw.githubusercontent.com/uwni/operor/main/install.sh | sh
Note
macOS users may get a Gatekeeper warning because the binary is not signed/notarized. If that happens, allow it manually in System Settings -> Privacy & Security.
- For Microsoft Windows users, Run
powershell -ExecutionPolicy Bypass -NoProfile -Command "iwr https://raw.githubusercontent.com/uwni/operor/main/install.ps1 | iex"To run an existing Operor binary, only the VISA runtime is required. You can download it from National Instrument.
If your VISA library is not installed in a standard location, pass it explicitly with --visa-lib <path>.
Preview the bundled example recipe without opening instruments:
operor run -d test-data/adapters test-data/recipes/r1_set.yaml --previewOpen an interactive REPL and discover instruments:
operor replOr connect directly to a known resource:
operor repl -r USB0::...::INSTRExecute a recipe:
operor run -d test-data/adapters test-data/recipes/r1_set_voltage.yamloperor run -d <adapter_dir> <recipe> [--preview] [--dry-run] [--visa-lib <path>]
operor repl [-r <resource>] [--visa-lib <path>]
Command behavior:
runloads adapters, precompiles the recipe, and executes scheduled tasks.run --previewvalidates the recipe, adapters, commands, variables, and pipeline configuration without instrument I/O.run --dry-runrenders commands and logs them instead of writing to the instrument.replopens a stateful interactive session. Use-rto connect on startup, or discover instruments withlistandopeninside the REPL.
Warning
Prevent your device from sleeping during experiments while running a recipe. Unexpected sleep can interrupt a running workflow; for example, on macOS you can use caffeinate to keep the system awake.
Operor has two configuration layers:
- Adapters define instrument metadata and command templates in YAML.
- Recipes define instruments, variables, task schedules, expressions, and sinks in YAML.
Adapter documents are YAML files with optional metadata, an optional instrument section for VISA session defaults, and a commands object.
metadata:
version: "1.2.3"
description: Bench power supply
instrument:
timeout_ms: 2500
write_termination: "\n"
read_termination: "\n"
query_delay_ms: 25
chunk_size: 4096
manufacturer: Keysight Technologies
models:
- E36312A
firmware: "1.10"
commands:
set_voltage:
write: "VOLT {voltage},(@{channels})"
measure_voltage:
write: "MEAS:VOLT?"
read: floatNotes:
- Command template placeholders use
{name}syntax. - Placeholder names must be valid identifiers.
- A command with
read = "raw" | "float" | "int" | "string"reads and parses a response after the write. write_terminationis appended automatically to every command sent through that adapter.read_termination,timeout_ms,query_delay_ms, andchunk_sizetune the VISA session used for instruments that reference the adapter.
Recipe documents are YAML files with these top-level sections:
instruments: named instrument instances with an adapter file and VISA resource stringvars: initial variable values used by expressions andassignslotstasks: periodic work definitionspipeline: optional CSV and TCP sink configurationstop_when: optional stop condition expression
Example:
instruments:
psu:
adapter: psu.yaml
resource: "USB0::1::INSTR"
vars:
target_voltage: 5
measured_voltage: 0
delta: 0
pipeline:
mode: realtime
record: [measured_voltage, delta]
file_path: samples.csv
tasks:
- while: true
steps:
- call: psu.set_voltage
args:
voltage: "${target_voltage}"
channels: [1, 2]
- call: psu.measure_voltage
assign: measured_voltage
- compute: "${measured_voltage} - ${target_voltage}"
assign: delta
if: "$ITER > 0"
stop_when: "$ELAPSED_MS >= 2000 || $ITER >= 20"Recipe notes:
- Tasks can be sequential (just
steps), conditional (ifguard), or looping (whilecondition). ifis optional on bothcallandcomputesteps — the step is skipped when the expression is falsy.- Use
sleep_ms: 100as a step to pause between iterations. - Step arguments can be scalars or lists.
- A string argument written exactly as
${name}is treated as a runtime variable reference. assignstores a response or compute result into a declared variable slot.- Variables referenced by
${name}orassignmust be declared invars.
An iteration is one complete execution of a task's step list. Every time a task finishes running all of its steps from top to bottom, that counts as one iteration. The global iteration counter $ITER starts at 0 and increments by 1 after each iteration, regardless of which task produced it.
Iterations drive two key behaviors:
- Pipeline output — at the end of each iteration, all
assignvalues recorded during that run are collected into a single frame and pushed to the pipeline (CSV file, TCP stream). One iteration = one row in the output. - Stop conditions —
stop_whenis evaluated between iterations.$ITERreflects the number of completed iterations so far.
For a sequential task (no while or if), the steps run exactly once — that is one iteration. For a loop task (while: true), each pass through the step list is one iteration. For a conditional task (if: ...), the steps run at most once, producing zero or one iteration depending on the guard.
Set expected_iterations at the recipe top level to tell the runtime how many iterations to expect. This enables progress reporting (e.g. progress bars).
expected_iterations: 100Expressions are used by compute and if.
Supported syntax:
| Item | Meaning | Use | Example |
|---|---|---|---|
${name} |
Variable reference. Reads a value from vars. |
Use recipe variables in if and compute. |
${target_voltage} |
$ITER |
Global iteration counter, starting at 0. |
Stop conditions, warm-up logic, periodic logic. | $ITER >= 20 |
$TASK_IDX |
Zero-based task index in the tasks list. |
Task-specific branching in shared expressions. | $TASK_IDX == 1 |
$ELAPSED_MS |
Elapsed runtime in milliseconds since execution start. | Time-based stop/guard conditions. | $ELAPSED_MS >= 2000 |
| Item | Meaning | Use | Example |
|---|---|---|---|
+ |
Addition. | Numeric sum. | ${a} + ${b} |
- |
Subtraction. | Numeric difference. | ${measured} - ${target} |
* |
Multiplication. | Numeric product/scaling. | ${voltage} * 0.1 |
/ |
Division. | Numeric ratio/average. | ${sum} / 2 |
> |
Greater-than comparison. | True when left side is greater than right side. | ${temp} > 40 |
< |
Less-than comparison. | True when left side is less than right side. | ${delta} < 0.1 |
>= |
Greater-than-or-equal comparison. | Inclusive upper/lower bound checks. | $ITER >= 10 |
<= |
Less-than-or-equal comparison. | Inclusive threshold checks. | $ELAPSED_MS <= 5000 |
== |
Equality comparison. | Exact numeric equality checks. | $TASK_IDX == 0 |
!= |
Inequality comparison. | Not-equal checks. | ${state} != 0 |
&& |
Logical AND. | True only if both sides are true. | $ITER > 0 && ${delta} < 0.1 |
|| |
Logical OR. | True if either side is true. | `$ELAPSED_MS > 2000 |
! |
Logical NOT. | Invert a condition. | !(${ok} == 1) |
( ... ) |
Parentheses/grouping. | Control evaluation order. | (${a} + ${b}) / 2 |
| Item | Meaning | Use | Example |
|---|---|---|---|
min(x, y) |
Smaller of two values. | Clamp to an upper limit. | min(${v}, 10) |
max(x, y) |
Larger of two values. | Clamp to a lower limit. | max(${v}, 0) |
All expression math is evaluated as f64. A non-zero result is treated as true.
Important distinction:
- Adapter command templates use
{name}placeholders. - Recipe expressions use
${name}variable references.
--preview stops after recipe precompilation and prints a summary of:
- Instruments and resource addresses
- Stop conditions
- Pipeline configuration
- Task intervals and step kinds
--dry-run performs full scheduling and expression evaluation, but instrument calls are logged instead of written:
[dry-run] psu.yaml -> VOLT 5,(@1,2)
This is useful for validating template rendering and variable flow before touching hardware.
Execution uses a dedicated sampler thread plus an SPSC ring buffer so slow sinks do not block instrument sampling.
- The sampler thread performs instrument I/O and produces task-level result frames.
- A worker thread drains the ring buffer and performs slower output work.
- High buffer usage emits warnings.
- Buffer overflow stops scheduling new work and is reported in the final summary.
Pipeline fields:
pipeline:
mode: realtime
buffer_size: 8192
warn_usage_percent: 90
file_path: samples.csv
network_host: 127.0.0.1
network_port: 9000
record: allNotes:
modecurrently selects default behavior such as conservative versus throughput-oriented buffer sizing.buffer_sizeis normalized internally to a power of two.warn_usage_percentmust be between 1 and 100.file_pathwrites CSV rows using recordedassignvalues.network_hostandnetwork_portstream the same task-level frames as JSON over TCP.recordcan beallor an explicit list ofassignvariable names.- Relative sink paths are resolved from the recipe file directory.
The REPL is a stateful interactive session with two modes: disconnected and connected.
In disconnected mode:
list List available VISA instruments.
open [<resource>] Connect by address, or scan and pick interactively.
help Show available commands.
quit Leave the REPL.
In connected mode, instrument I/O commands become available:
write <command> Send a command to the instrument.
read Read a response from the instrument.
query <command> Send a command and then read the response.
list List available VISA instruments.
close Disconnect from the current instrument.
help Show available commands.
quit Leave the REPL.
Examples:
# Start disconnected, discover and connect interactively
operor repl
# Connect to a known resource on startup
operor repl -r USB0::0x0957::0x1798::MY12345678::INSTRBundled examples live in:
test-data/adapters/test-data/recipes/
Useful starting points:
test-data/adapters/psu.yamltest-data/adapters/dmm.yamltest-data/recipes/r1_set.yamltest-data/recipes/r1_set_voltage.yamltest-data/recipes/r2_stop_when.yaml
Footnotes
-
--previewdoes not open VISA sessions, so it can be used without a VISA library installed. ↩