Config Files#
Constructing instruments from JSON config files instead of Python setup code
Every config-driven category (InstroPSU, InstroDMM, InstroELoad, InstroAWG, InstroScope, …) can be constructed directly from a JSON config file, a plain dict, or an already-built config object, instead of driver=... Python setup code:
psu = InstroPSU(config="bench_psu.json")
psu = InstroPSU(config={"device": {"name": "bench_psu"}, "driver": {...}, ...})
psu = InstroPSU(config=my_psu_config)
config is mutually exclusive with driver/num_channels. A passed-in config object is copied up front, so later edits to the object you passed don’t reach the instrument.
Category-specific state blocks#
Beyond the shared shape above, some categories accept an optional block that sets initial instrument state through the same public setters a manual call would use, so it publishes the same .cmd channels.
PSU#
InstroPSU’s config has no initial-state block — only device, driver, timing, and publishers. See the PSU guide for a full example.
DMM#
An optional measurement block:
function(required within the block): one ofDC_VOLTAGE,AC_VOLTAGE,DC_CURRENT,AC_CURRENT,TWO_WIRE_RESISTANCE,FOUR_WIRE_RESISTANCE.digits: resolution in digits. Omit to keep the instrument default. Driver support varies: the Agilent 34401A accepts it, while the Keysight 34461A rejects it in favor ofaperture_nplc.aperture_nplc/aperture_seconds: integration time, in power-line cycles or seconds. At most one of the two.range: a number for a manual range in the function’s units, or"auto"for auto-range. Omit to keep the instrument default.
Because background polling cannot start without a measurement function, a config that declares timing must also declare measurement.
ELoad#
An optional load block:
mode(required within the block): one ofCC,CV,CP,CR. Required because level and range cannot be set before a mode.level: operating level in the mode’s units (CC: A, CV: V, CP: W, CR: Ω). Omit to keep the instrument default.range: operating range in the mode’s units. Driver support forrangeonCP/CRmodes varies by vendor.slew_rate:{"direction": "RISE" | "FALL" | "BOTH", "rate": <A/µs>}, mapping toset_slewrate.
Note
The config never enables the input. The load block pre-arms the setpoint; enabling the input stays an explicit runtime call (eload.output_enable(True)). The polled measurements (get_voltage, get_current) work regardless of mode, so timing is valid without a load block for passive monitoring.
AWG#
A required, non-empty channels dict, keyed by channel number as a string. Each entry is applied once per session on open() (a close() + open() cycle reapplies it):
waveform(required): one ofsine,square,sawtooth,triangle,pulse,arbitrary, orstatic_value, discriminated by ashapefield.arbitrary’ssamplesaccepts an inline array of at least 2 floats normalized to[-1.0, 1.0], or a string path to a CSV file (read fresh on everyopen(); a relative path resolves against the process’s working directory, not the config file).amplitude(optional):{"value": <float>, "unit": "VPP" | "VP" | "VRMS" | "DBM"};unitdefaults toVPP.offset(optional): DC offset in volts.modulation/burst/sweep(optional): at most one may carry"enable": trueper channel. Seeset_modulation,set_burst*,set_sweep*for the field semantics.
output_enable cannot be set from the config; call awg.output_enable(channel, True) explicitly after open().
Scope#
Optional channels, acquisition, and trigger blocks, applied through the public setters when open() runs, in dependency order (coupling/attenuation before scale, trigger before acquisition, average_count before mode). If acquisition.start_acquisition_on_open is set, run() fires before the acquisition block applies, since some scopes only accept acquisition-mode changes while running.
channels: per-channel initial state keyed by 1-based channel number. Acceptsvertical_scale,vertical_offset,coupling("AC"/"DC"),probe_attenuation, and ameasurementslist to poll from the background daemon.acquisition:mode,average_count(requiresmode: "AVERAGE"),horizontal_scale,start_acquisition_on_open(defaultfalse).trigger:source(required within the block),type,level,slope,mode.
After the blocks apply, open() calls sync_configuration() and logs one warning per field the instrument reports differently from the config (scopes snap requested values to the nearest supported step). Trigger fields are not checked, since sync_configuration() does not read them back. autostart (host-side daemon) and acquisition.start_acquisition_on_open (instrument acquiring) are independent knobs.
See the Scope guide for a full example.