InstroAWG
InstroAWG is a hardware abstraction layer (HAL) that provides a unified interface for arbitrary waveform generators. The category class defines the vendor-independent API (set_waveform, set_amplitude, set_offset, set_modulation, …). A vendor-specific driver owns its connection details and translates those calls into vendor commands.
Supported Vendors
- Rigol: DG1022Z (DG1000Z series) via SCPI/VISA (
RigolDG1022Z)
Key Concepts
Driver Composition
AnInstroAWG is built from a concrete driver:
- The RigolDG1022Z owns the connection setup and vendor-specific command mapping.
InstroAWGowns the category-level workflow: waveform programming, publishers, the background daemon.
Lifecycle
The typical InstroAWG workflow:- Construct: instantiate the vendor driver and pass it to
InstroAWG, along with the channel count. open(): establishes the connection to the instrument.- Configure and generate: define a waveform on a channel, set its amplitude and offset, and enable the output, etc.
start(): begins a periodic background daemon that polls output state. (Optional)stop(): ends the background daemon (if started).close(): disconnects from hardware.
Waveform Definitions
InstroAWG supports programming a channel with the following waveforms:Sine, Square, Sawtooth, Triangle, Pulse, Arbitrary, or StaticValue. These are all frozen dataclasses in instro.unstable.awg.
Driver support variesCertain features are only available on particular waveforms. Driver’s own their implementation of features in InstroAWG. Consult your specific driver for exact support.
Creating an InstroAWG Instance
Parameters
name: A name for this AWG instance. Used as a prefix for channel names when publishing.driver: A concreteAWGDriverBaseinstance (e.g.RigolDG1022Z) configured with the connection details for that model.num_channels: Number of output channels on the waveform generator.publishers: Optional list of publishers to attach.**kwargs: Additional keyword arguments become default tags when using a publisher that supports tags (likeNominalCorePublisher).
Choosing a Driver
Choose the concrete driver that matches the AWG model, then pass the instrument connection settings to that driver. For example, useRigolDG1022Z for the Rigol DG1022Z.
To inspect a VISA instrument’s identity before choosing a driver:
Examples
Basic Usage
Important Note about PublishersData is published as a direct result of an instrument method being called.For example, when you call
get_output_state(), this not only queries the instrument for the output state but also causes all attached Publishers to publish the measurement response automatically.Published channels
Every measurement/command call produces a channel keyed under{name}.{descriptor}, where {name} is the constructor argument and {descriptor} is the row below. Substitute {N} with the actual channel number (1, 2, …).
get_waveform(), get_amplitude(), convert_amplitude(), get_modulation_type(), get_burst_type(), and get_gate_polarity() return a plain Python value (Waveform, tuple[float, AmplitudeMeasurementUnit], float, ModulationType, BurstType, GatePolarity) directly rather than a Measurement, and don’t publish. Every other readback listed above publishes a Measurement on the descriptor shown.Method Reference
Custom Driver Development
This section is for developers implementingInstroAWG support for waveform generators that aren’t supported out of the box.
Overview
Driver developers subclassAWGDriverBase and own whatever transport their instrument needs. The caller chooses a concrete driver, and that concrete driver exposes connection parameters that make sense for its protocol:
InstroAWG’s vendor-independent API (set_waveform, set_amplitude, output_enable, …) into vendor-specific commands.
Driver Responsibilities
An AWG driver must:- Expose a protocol-native constructor: accept inputs like
visa_resource,host,port, depending on the instrument. - Own transport setup: create and store the transport internally. Do not require users to pass a
VisaDriveror other transport object. - Own lifecycle: implement
open()andclose()by opening and closing the underlying transport. - Map commands: translate each abstract method into vendor-specific commands.
- Parse responses: convert instrument responses to the expected Python types (
Waveform,float,bool,ModulationType, etc.). - Validate hardware constraints: if the instrument only supports a subset of waveform shapes, modulation types, or carrier/modulator combinations, raise
ValueErrorfor unsupported combinations rather than silently misprogramming the instrument.
AWGDriverBase Interface
All AWG drivers subclassAWGDriverBase. The required methods are declared @abc.abstractmethod:
NotImplementedError if not supported):
set_output_load(channel, load)/get_output_load(channel): Set or read the output load impedance;Nonemeans high-Z.align_phase(): Sync the phase of all channels.set_modulation(channel, mod_type, shape, magnitude): Configure a channel’s modulation. Callmodulation_enable()to activate it.modulation_enable(channel, enable): Enable or disable modulation on channel.get_modulation_type(channel): Read back the active modulation type from the instrument.get_modulation_state(channel): Read back whether modulation is enabled from the instrument.set_burst(channel, burst_type)/get_burst_type(channel): Configure or read back a channel’s burst type (NCYCLE/GATED/INFINITE). Callburst_enable()to activate it.burst_enable(channel, enable)/get_burst_state(channel): Enable/disable burst mode, or read back whether it’s enabled.set_burst_trigger(channel, source): Set the burst trigger source (INTERNAL/EXTERNAL/MANUAL).set_burst_delay(channel, delay_s)/get_burst_delay(channel): Set or read back the burst trigger delay in seconds.set_gate_polarity(channel, gate_polarity)/get_gate_polarity(channel): Set or read back the gate polarity (NORM/INV) for GATED bursts.set_ncycles(channel, n_cycles)/get_burst_ncycles(channel): Set or read back the number of cycles per trigger for NCYCLE bursts.set_burst_period(channel, period)/get_burst_period(channel): Set or read back the internal burst period in seconds.
Talking to the Instrument
Concrete drivers should hide transport details behind private attributes. For VISA-backed drivers, create aVisaDriver internally and use it for all I/O:
self._visa.write(command): Send a SCPI command (no response expected).self._visa.query(command): Send a SCPI query and receive the response string.
VisaDriver owns the resource lock. Concurrent write / query calls against the same driver are serialized automatically. Use with self._visa.lock(): when a sequence of writes and their error check need to execute atomically.
See the VisaDriver guide for the full transport reference, covering configuration, terminators, timeouts, serial settings, and the raw-byte I/O path.
For non-VISA instruments, follow the same shape with the protocol client your driver needs. The important part is that the public constructor describes the instrument connection, while the transport object remains an implementation detail.
Implementation Example: RigolDG1022Z Driver
Here’s an abridged shape of the Rigol DG1022Z driver:Using a Custom Driver
For drivers that aren’t shipped in the library, constructInstroAWG with your own driver instance. The driver should accept connection settings directly and create its transport internally:
Summary
Driver development requires careful mapping of vendor-specific behavior to the unifiedInstroAWG interface. Focus on:
- Subclassing
AWGDriverBase - Designing a constructor around natural connection parameters for the instrument
- Hiding transport construction inside the driver
- Implementing all abstract methods on
AWGDriverBase(and optional overrides where supported) - Using the correct vendor protocol or command syntax
- Converting instrument responses to the expected Python types
- Validating carrier/modulator/waveform compatibility your instrument actually supports, raising
ValueErrorrather than misprogramming the instrument - Querying and reporting errors from the instrument’s error queue where one exists
- Testing with actual hardware to ensure commands work as expected