InstroScope
InstroScope is a hardware abstraction layer (HAL) that provides a unified interface for SCPI-based oscilloscopes. The category class defines the vendor-independent API (set_vertical_scale, fetch_waveform, measure, set_trigger_level, …). A vendor-specific driver translates those calls into vendor commands.
Supported Vendors
- Keysight: InfiniiVision 1200 X-Series via SCPI/VISA (
Keysight1200X) - Siglent: SDS1000X-E series via SCPI/VISA (
SiglentSDS1000XE) - Tektronix: 2 Series MSO via SCPI/VISA (
Tektronix2SeriesMSO)
Key Concepts
Driver Composition
AnInstroScope is built from a concrete driver:
Keysight1200Xowns the connection setup and vendor-specific command mapping.InstroScopeowns the category-level workflow: configuration tracking, acquisition control, measurements, waveform fetches, publishers, the background daemon.
Lifecycle
- Construct: instantiate the vendor driver and pass it to
InstroScope. open(): establish the VISA connection.- Configure: set vertical (per channel), horizontal, acquisition, and trigger settings.
- Acquire: arm a single-shot with
single(), or free-run withrun(). - Read: fetch a waveform with
fetch_waveform()or take a built-in measurement withmeasure(). close(): disconnect from hardware.
Acquisition Control vs. the Background Daemon
InstroScope has two independent start/stop concepts:
- Acquisition control drives the instrument:
run(),single(), andstop_acquisition(). These map to the front-panel Run, Single, and Stop keys. Aftersingle(), bothfetch_waveform()andmeasure()block up to theirtimeoutfor the trigger to fire, then read. They raiseTimeoutErrorif it does not. - The background daemon drives polling on the host:
start()andstop()(inherited fromInstrument) periodically call the methods you register and stream their results to publishers.
Configuration Tracking
InstroScope tracks vertical, horizontal, acquisition, and trigger state locally as you issue commands. Call sync_configuration() after open() (or after load_settings()) to bulk-query the instrument and overwrite the tracked state.
sync_configuration() does not bulk-query trigger source, type, level, slope, or mode. Not all scopes expose those through a simple query, so set them explicitly to populate the tracked state.Measurements
measure(measurement_type, channel) reads a built-in measurement: VPP, VMAX, VMIN, VAVG, VRMS, FREQUENCY, PERIOD, or DUTY_CYCLE. It returns NaN when the scope has no valid result (no acquisition yet, channel off). fetch_waveform(channel) returns a Measurement whose timestamps are relative to the trigger point (negative values are pre-trigger), with voltages already scaled through the configured probe attenuation.
Creating an InstroScope Instance
Parameters
name: A name for this scope instance. Used as a prefix for channel names when publishing.driver: A concreteScopeDriverBaseinstance configured with the connection details for that model.num_channels: Number of analog-input channels on the oscilloscope.publishers: Optional list of publishers to attach.**kwargs: Additional keyword arguments become default tags when using a publisher that supports tags (likeNominalCorePublisher). Passlegacy_naming=Truehere to publish pre-v1.0 channel names.
Choosing a Driver
Choose the driver that matches the model, then pass it the connection settings. UseKeysight1200X, SiglentSDS1000XE, or Tektronix2SeriesMSO. Each accepts a VISA resource string or a VisaConfig.
To inspect a VISA instrument’s identity before choosing a driver:
Examples
All measurement methods returnMeasurement objects. This is common amongst all Instrument objects.
Basic Usage
Important Note about PublishersData is published as a direct result of an instrument method being called. Calling
measure() reads the value from the instrument and causes all attached publishers to publish it automatically.Background Daemon for Continuous Monitoring
Register the measurements to poll, free-run the instrument withrun(), then start() the daemon.
Scope Background Daemon
InstroScope registers no default daemon functions. Register the measurements with add_background_daemon_function() before start(), or use define_background_daemon() to replace the registered set. The default polling interval is 1 second, configurable via background_interval.Published channels
Every call produces a channel keyed under{name}.{descriptor}, where {name} is the constructor argument and {descriptor} is the row below. Substitute {N} with the channel number and {type} with the lowercased measurement type (e.g. vpp). Pass legacy_naming=True to the constructor for the pre-v1.0 names.
| Method | Descriptor | Type |
|---|---|---|
set_vertical_scale(channel=N) | ch{N}.vscale.cmd | command |
get_vertical_scale(channel=N) | ch{N}.vscale | telemetry |
set_vertical_offset(channel=N) | ch{N}.voffset.cmd | command |
get_vertical_offset(channel=N) | ch{N}.voffset | telemetry |
set_coupling(channel=N) | ch{N}.coupling.cmd | command |
get_coupling(channel=N) | ch{N}.coupling.cmd | command |
set_probe_attenuation(channel=N) | ch{N}.probe_attenuation.cmd | command |
get_probe_attenuation(channel=N) | ch{N}.probe_attenuation | telemetry |
set_horizontal_scale() | hscale.cmd | command |
get_horizontal_scale() | hscale | telemetry |
get_sample_rate() | sample_rate | telemetry |
set_acquisition_mode() | acquisition_mode.cmd | command |
get_acquisition_mode() | acquisition_mode.cmd | command |
set_average_count() | average_count.cmd | command |
get_average_count() | average_count | telemetry |
run() / stop_acquisition() / single() | acquisition_control.cmd | command |
get_acquisition_state() | acquisition_state.cmd | command |
fetch_waveform(channel=N) | ch{N}.waveform | telemetry |
measure(type, channel=N) | ch{N}.{type} (e.g. ch{N}.vpp) | telemetry |
set_trigger_source() | trigger_source.cmd | command |
set_trigger_type() | trigger_type.cmd | command |
set_trigger_level() | trigger_level.cmd | command |
set_trigger_slope() | trigger_slope.cmd | command |
set_trigger_mode() | trigger_mode.cmd | command |
force_trigger() | trigger_control.cmd | command |
get_trigger_status() | trigger_status.cmd | command |
save_screenshot() | screenshot.cmd | command |
save_settings() | save_settings.cmd | command |
load_settings() | load_settings.cmd | command |
Method Reference
| Method | Purpose |
|---|---|
InstroScope(name, driver, num_channels, publishers=None, **kwargs) | Construct an InstroScope with a vendor driver |
open() | Establish the VISA connection to the scope |
close() | Disconnect from scope and close all publishers |
sync_configuration() | Bulk-query the instrument and overwrite the tracked configuration |
set_vertical_scale(volts_per_div, channel=N) | Set the vertical scale in volts/div |
get_vertical_scale(channel=N) | Read the vertical scale in volts/div |
set_vertical_offset(offset, channel=N) | Set the vertical offset in volts |
get_vertical_offset(channel=N) | Read the vertical offset in volts |
set_coupling(coupling, channel=N) | Set AC/DC input coupling |
get_coupling(channel=N) | Read the input coupling |
set_probe_attenuation(factor, channel=N) | Set the probe attenuation ratio (e.g. 1, 10, 100) |
get_probe_attenuation(channel=N) | Read the probe attenuation ratio |
set_horizontal_scale(seconds_per_div) | Set the timebase in seconds/div (applies to all channels) |
get_horizontal_scale() | Read the timebase in seconds/div |
get_sample_rate() | Read the effective sample rate in samples/second |
set_acquisition_mode(mode) | Set the acquisition mode (NORMAL, AVERAGE, HIGH_RESOLUTION, PEAK_DETECT, ENVELOPE) |
get_acquisition_mode() | Read the acquisition mode |
set_average_count(count) | Set the waveforms-to-average count (used in AVERAGE mode) |
get_average_count() | Read the waveforms-to-average count |
run() | Start continuous (free-running) acquisition |
stop_acquisition() | Stop acquisition, leaving captured data intact |
single() | Arm a single-shot acquisition |
get_acquisition_state() | Read the acquisition run state (RUNNING / STOPPED) |
fetch_waveform(channel=N, timeout=5.0) | Fetch the most recently acquired waveform |
measure(measurement_type, channel=1, timeout=5.0) | Take a built-in measurement |
set_trigger_source(channel) | Set the trigger source channel |
set_trigger_type(trigger_type) | Set the trigger type (EDGE, PULSE) |
set_trigger_level(level) | Set the trigger threshold in volts |
set_trigger_slope(slope) | Set the trigger edge slope (RISING, FALLING, EITHER) |
set_trigger_mode(mode) | Set the trigger sweep mode (AUTO, NORMAL) |
force_trigger() | Force a trigger event immediately |
get_trigger_status() | Read the trigger status (ARMED, READY, TRIGGERED, …) |
save_screenshot(filepath, to_instrument=False) | Capture a screenshot to the host or the scope’s filesystem |
save_settings(name, to_instrument=False) | Save the current scope setup |
load_settings(name, from_instrument=False) | Recall a scope setup (invalidates tracked configuration) |
start() | Begin the background telemetry daemon |
stop() | End the background telemetry daemon |
Custom Driver Development
This section is for developers implementingInstroScope support for oscilloscopes that aren’t supported out of the box.
A scope driver subclasses ScopeDriverBase and owns its transport. Its responsibilities:
- Expose a protocol-native constructor: accept a
visa_resourcestring orVisaConfig. - Own transport setup: create and store the transport internally. Do not require callers to pass a
VisaDriver. - Own lifecycle: implement
open()andclose(). - Map commands: translate each abstract method into vendor commands.
- Parse responses: convert responses to the expected types (
float, enums,WaveformData). - Drain the error queue: implement
check_errors().InstroScopecalls it between setup commands and blocking queries, so a pending syntax error raises instead of hanging a data query.
ScopeDriverBase Interface
All scope drivers subclassScopeDriverBase and implement these abstract methods:
setup_measurement(measurement_type, channel) is an optional override (default no-op). Implement it for instruments that compute measurements during acquisition (e.g. Tektronix), where the measurement slot must exist at trigger time. Raise NotImplementedError for AcquisitionMode values the scope does not support (e.g. the Keysight 1200X has no ENVELOPE mode). See Exceptions for unsupported-feature handling.
For VISA-backed drivers, create a VisaDriver internally and use self._visa.write(command) and self._visa.query(command) for all I/O. VisaDriver owns the resource lock and serializes concurrent calls. See the VisaDriver guide for the full transport reference.
Implementation Example: Keysight 1200 X-Series Driver
Using a Custom Driver
ConstructInstroScope with your own driver instance: