Skip to content
robin-cachy edited this page Jun 10, 2026 · 5 revisions

CLI

All scripts run directly with Python and use the active workbench by default. Pass --workbench <name> to any script to override.


core/nachoVisa.py — Instrument scanner

Scans USB and LAN for VISA instruments, identifies them, and saves a workbench file.

python core/nachoVisa.py                        # scan USB + LAN, prompt for name
python core/nachoVisa.py --usb-only             # skip LAN scan
python core/nachoVisa.py --host 192.168.1.50    # probe a specific IP
python core/nachoVisa.py --subnet 192.168.1.0/24
python core/nachoVisa.py --save my_lab          # save without prompting
python core/nachoVisa.py --set-active my_lab    # switch active workbench
python core/nachoVisa.py --fix-udev             # write udev rules (Linux)
python core/nachoVisa.py --debug                # verbose output

--fix-udev creates /etc/udev/rules.d/70-usbtmc.rules so PyVISA-py can access USBTMC instruments without root. Run once, then re-plug USB instruments.


core/setWorkbench.py — Apply instrument state

Drives all instruments in the active workbench to a specified state defined in a config JSON file.

GUI equivalent: The Save state / Load state / Reset all bar in the Workbench tab covers the same workflow interactively. Use setWorkbench.py for headless / scripted operation or when the GUI is not running. See Workbench and Instruments#bench-state--save-load-reset.

python core/setWorkbench.py                     # apply workbench_config.json
python core/setWorkbench.py --set foo.json      # apply a specific config file
python core/setWorkbench.py --reset-bench       # safe defaults on all instruments

--reset-bench zeroes outputs and disables all channels without needing a config file. The state files saved by the GUI (workbench_states/*.json) use a compatible format and can be passed to --set.

Config format:

{
  "name": "Lab Ready",
  "hosts": ["192.168.1.100"],
  "instruments": {
    "edu36311a": {
      "outputs": [
        { "channel": 1, "voltage": 5.0, "current_limit": 0.5, "enabled": false }
      ]
    },
    "edu33211a": {
      "channels": [
        { "channel": 1, "function": "SIN", "frequency": 1000,
          "amplitude": 1.0, "amplitude_unit": "VPP", "offset": 0.0, "enabled": false }
      ]
    },
    "scope": { "reset": true }
  }
}

"hosts" lists IP addresses of Ethernet instruments (LAN instruments are not auto-discovered by the @py backend).


scripts/ — Standalone scripts

These run independently without the GUI.

Script What it does
screenshot.py Capture a screenshot from the active workbench scope and save as PNG
acAnalysis.py AC frequency sweep: step a generator through frequencies from a CSV, record Vpp on scope CH1/CH2
dcSweep.py Step one or both PSU channels across a voltage range; log V/I readings at each point
psuInterrupt.py Drive a V1 → interrupt (off or V2) → V3 cycle; sweep interrupt duration and/or voltage across multiple runs
waveformAnalysis.py Live waveform analysis: autoscale, measure freq/Vpp/risetime, save screenshot and CSV

scripts/cgb-US21x-equipment/ contains instrument-specific examples for the Keysight EDU lab kit (EDU33211A AWG, EDU34450A DMM, EDU36311A PSU) and the Korad KA3005P.


Writing your own scripts

The pattern for every script is: load the workbench → open instruments by role → classify each instrument → dispatch SCPI commands through get_command.

import pyvisa
from workbench import load_workbench, open_by_role
from eewBackbone import classify, get_command

# Load the active workbench (workbenches/active.json).
# Pass a name to use a different one: load_workbench("portable_rig")
wb = load_workbench()
rm = pyvisa.ResourceManager("@py")

# open_by_role finds the instrument whose "role" field matches in the workbench JSON.
# Scripts stay portable — no hardcoded USB resource strings or IP addresses.
psu = open_by_role(rm, wb, "psu")
dmm = open_by_role(rm, wb, "dmm")

# classify() matches the *IDN? response to a family in eewBackbone.json
# and returns the full resolved command set for that specific instrument.
psu_fam = classify(psu.query("*IDN?"))
dmm_fam = classify(dmm.query("*IDN?"))


def dispatch(instr, family, operation, **kwargs):
    """Run one operation; return the query response, or None for write-only ops."""
    result = None
    for action, scpi in get_command(family, operation, **kwargs):
        if action == "write":
            instr.write(scpi)
        elif action == "query":
            result = instr.query(scpi)
    return result


# Example: step PSU channel 1 from 1 V to 5 V, log DMM voltage at each point
dispatch(psu, psu_fam, "set_current_limit", ch=1, value="0.5")  # 500 mA limit
dispatch(psu, psu_fam, "output_on", ch=1)

for v in range(1, 6):
    dispatch(psu, psu_fam, "set_voltage", ch=1, value=str(v))
    reading = dispatch(dmm, dmm_fam, "measure_vdc")  # returns the query response string
    print(f"V_set={v} V   V_meas={float(reading):.4f} V")

dispatch(psu, psu_fam, "output_off", ch=1)

Because all SCPI commands go through eewBackbone.json, this script runs unchanged on any PSU and DMM family that defines set_voltage, set_current_limit, and measure_vdc — swapping instruments only requires updating the workbench file.

Clone this wiki locally