Skip to content

Scripting

deckerjulian edited this page Oct 6, 2026 · 1 revision

Scripting

openSciLab works without its window, too: the command line captures, converts, decodes and runs flows, and the Python API does the same from your own scripts. Neither loads the user interface, so both work over SSH and in CI. This page is the practical overview; the reference is docs/cli.md.

The commands

The commands come with the Python package (see Installation, running from source): openscilab starts the application, and openscilab <command> runs a command instead – the same as openscilab-cli <command>. python -m openscilab <command> works as well.

Command Does
devices list the connected devices with their identifiers
info [DEVICE] describe a device (channels, rates, memory, what it simulates)
capture capture and save the samples, optionally decode them right away
convert IN OUT convert a capture file (.lac, .lac.gz, .sr → those, .csv, .vcd)
decode FILE run protocol decoders on a capture file
decoders list the protocol decoders
plugins list the plugins and the kinds of devices they add
run FLOW run a flow
sim [PROFILE] list the simulator profiles, or describe one

openscilab-cli --help and openscilab-cli <command> --help list every option. --decoders-dir DIR (before the command) adds a folder of decoders. Every error ends with exit code 1 and one line on stderr; OPENSCILAB_TRACEBACK=1 shows where it came from.

Devices

Every device has an identifier. Without --device the first device found is used.

Identifier Device
pico:/dev/cu.usbmodem1, pico:COM5 Pico board with the openSciLab Pico firmware over USB
pico-net:192.168.1.5:4045 Pico W board over WiFi
pico-multi:/dev/ttyACM0,/dev/ttyACM1 multi device set of 2 to 5 boards
dslogic, dslogic:1:4 the first DSLogic, or the one at USB bus 1, address 4
arduino:/dev/cu.usbserial-1410 Arduino board with the openSciLab Arduino firmware
rigol:192.168.1.20 Rigol DHO900 oscilloscope (through its bridge app when it runs)
sim:free, sim:uno, sim:pico, ... a simulator (openscilab sim lists them)
arduino-sim:uno, rigol-sim:bridge simulators that speak the real device's protocol
<kind>:... a device of a plugin (openscilab-cli plugins)
openscilab-cli devices
openscilab-cli info sim:free

Capturing

# 8 channels at 10 MHz for 5 ms, 1000 samples before a rising edge on channel 0
openscilab-cli capture --channels 0-7 --rate 10M --duration 5ms --pre 1000 \
    --trigger edge:0:rising -o capture.sr

# Start at once, stream 2 seconds from a DSLogic
openscilab-cli capture -d dslogic -c 0-3 -r 20M -t 2s --mode stream --trigger immediate -o long.lac.gz

# No hardware: the UART of the simulator on D9, decoded right away
openscilab-cli capture -d sim:free -c 9 -r 4M -t 2ms --pre 100 --trigger edge:9:falling \
    -o uart.sr --decode uart:rx=9,baudrate=115200 --annotations uart.csv
Option Meaning
--device, -d device identifier
--channels, -c channels counted from 0: 0-7,9 (default 0-7); --names gives them names
--rate, -r 10M, 100k, 1e6, 24MHz (default: the highest)
--samples, -n / --duration, -t total samples (including --pre) / a time: 5ms, 250us, 1s, 2 min (5m is 5 milliseconds)
--pre samples before the trigger
--trigger edge:<ch>:rising|falling, pattern:0=1,1=0, fast:0=1,1=0, immediate
--mode buffer (into the device's memory) or stream (over USB while capturing)
--software-trigger with --mode stream: look for the trigger in the stream, on any channel, also on devices whose streams have no trigger
--threshold input threshold in volts (DSLogic)
--timeout seconds to wait for the trigger
--output, -o .lac, .lac.gz, .sr, .csv (--time adds a time column) or .vcd
--decode, --annotations decoders to run right after the capture, and where their output goes

Settings the device cannot capture are rejected before the capture starts.

Converting and decoding

openscilab-cli convert capture.lac capture.sr            # open it in PulseView
openscilab-cli convert --time capture.sr capture.csv
openscilab-cli decode capture.sr --decoder uart:rx=0,baudrate=115200 -o uart.csv
openscilab-cli decode capture.lac -D i2c:scl=SCL,sda=SDA -D eeprom24xx -o eeprom.json

After <decoder>: come its channels (capture channel number or name) and options. A decoder that works on the output of another one (eeprom24xx on i2c) is stacked on the one before it. The CSV has the columns Start time (s), End time (s), Start sample, End sample, Decoder, Row and Value (times relative to the trigger); the JSON file has the same records and the errors of the decoders. More in Protocol decoders and Files.

Running flows

openscilab run flows/test.flow.yaml                      # real time, the devices of the flow
openscilab run examples/flows/counter.flow.yaml --sim --fast
openscilab run flows/curve.flow.yaml --device dho=sim:dho924s --report out.html
openscilab run flows/logger.flow.yaml --duration 1h
openscilab run flows/counter.py --fast                   # a flow written in Python
Option Meaning
--sim every device node of the flow is a simulator (sim: devices are anyway)
--fast virtual time: as fast as possible, the same result every time (simulators)
--device NAME=ADDRESS another address for the device node NAME (repeatable)
--duration 10s end the flow after this (virtual or real) time
--seed N seed of the random numbers (noise of simulators)
--timeout S give up after this many seconds of real time
--data-dir DIR where files are written (default: data/ of the project, or the flow's folder)
--report FILE.html a report of the run: the parts of the report.* nodes, the checks, the run itself
-v, -q the log of the nodes while running / no summary

The exit code is 0 when the flow finished and every check passed (report.check, control.compare), 1 otherwise: a flow is a test on a test bench (Panels and reports). A flow inside a project folder (one with project.yaml) uses the project's devices and own nodes, and writes into its data/ folder. Remote devices run in real time only and have no --sim replacement; give them a remote-sim: address with --device instead.

The Python API

from openscilab import api

print(api.devices())

with api.open("sim:free") as device:          # or api.open() for the first device found
    capture = device.capture(
        channels=[0, 1, 2, 3],
        rate="10M",                           # or 10_000_000
        duration="1ms",                       # or samples=10_000
        pre_trigger=100,
        trigger=api.Edge(0, rising=True),     # api.Pattern({0: 1, 1: 0}), api.Immediate(), api.Sequence([...])
        mode="buffer",
        timeout=10,
    )

capture.save("capture.sr")                    # .lac, .lac.gz, .sr, .csv, .vcd
bit0 = capture.samples(0)                     # numpy array of 0/1, or samples("CLK")
print(capture.frequency, capture.sample_count, capture.trigger_sample)

capture = api.load("capture.lac")
for item in capture.decode("i2c", channels={"scl": 0, "sda": 1}):
    print(f"{item.start_time:.6f} {item.row}: {item.value}")
  • capture() waits until the samples are there. It raises ValueError for settings the device cannot capture, api.CaptureFailed when the device reports an error and TimeoutError after timeout seconds without a trigger.
  • mode="stream", software_trigger=True looks for the trigger in the stream: any trigger on any captured channel, also an api.Sequence of patterns, edges, pulse widths and gaps.
  • api.instrument("sim:uno") gives an instrument with all its facets: uno.gpio.write("D7", 1), uno.monitor.sample(["D3"]).
  • device.inject("disconnect") (also "delay", "overflow", "restart") injects a fault into a simulator, to test how a script copes.
  • api.open(..., process=True) reads the device in a process of its own, as the application does.

Flows from Python

Build a flow in code and run it – the same model as the YAML file and the editor:

from openscilab.lab import flow, nodes as n

with flow("Counter") as f:
    sim = f.device("sim", "sim:free")
    cap = n.device.capture(sim, channels=["D0", "D8"], rate="4 MHz", samples=20000, trigger=n.edge("D8"))
    freq = n.measure.frequency(cap.D8)
    freq >> n.report.check(name="clock", low=0.9e6, high=1.1e6)
    cap.capture >> n.data.file(path="counter.lac")

result = f.run(fast=True)
print(result.state, result.time, result.ok)
print(result.value("frequency", "out"))
print(f.to_yaml())                            # the same flow as YAML
  • n.<group>.<name>(...) adds a node; a device (or uno.pin("D9")) given to it is wired to its device input, a port given to it is wired to its first free input; other arguments set parameters. Nodes are named after their type (capture, frequency) unless you pass id=.
  • a.out >> b.in wires two ports, a >> b the first output to the first free input.
  • f.run(fast=True, sim=True) runs it; f.save("counter.flow.yaml") writes the YAML file.
  • An existing flow file runs with api.run_flow("flows/test.flow.yaml", sim=True, fast=True).

Such a script also opens in the application as a graph, and openscilab run script.py runs it. For node types of your own, see Nodes.

Inside the application

The console at the bottom of the window (View → Console, Ctrl+J) has a Python tab: a Python console with shell (the window), openscilab, documents() and active() (the active document), with completion of attribute names. Handy to look at the data of a document or try a line of the API without leaving the application.

See also

Clone this wiki locally