VirtualScope#

class VirtualScope(
num_channels: int = 4,
signals: Mapping[int, SineWave | SquareWave] | None = None,
)[source]#

Bases: ScopeDriverBase

Oscilloscope simulated in-process: no transport, server, or hardware.

Every channel sees a signal from the start: a 1 kHz, 1 V peak SineWave on odd channels (channel 1, 3, …) and a 1 kHz, 1 V peak SquareWave on even channels (2, 4, …). A capture is 1000 points across 10 horizontal divisions centered on the trigger, where the trigger source crosses the trigger level on the configured slope. A sine whose peaks miss the level aligns to its nearest peak; a square triggers on its edges at any level. Gaussian noise of 0.01 division is added, reduced by the square root of the average count in AVERAGE mode, and AC coupling removes the signal’s offset. Like an ADC, a capture clips at the screen edges: the vertical offset is the voltage at screen center, and the screen spans 4 divisions of the vertical scale above and below it.

While running, every fetch and measurement reads a fresh capture. stop, single, and digitize capture every channel at once, as a real acquisition does, and later fetches and measurements read that capture until the scope runs again, whatever settings or signals change meanwhile. Voltage measurements come from the same acquisition, clipped the same way, but sampled at 100 points per cycle over at most 10 cycles, so a slow timebase reads the signal’s true levels, as a real scope’s full-resolution record does, even when the 1000 fetched points alias. Frequency, period, and duty cycle come from the signal the capture was taken from. Every setting reads back as set.

It simulates driving a scope from a script or notebook, so it has no screen or storage: save_screenshot, save_settings, and load_settings raise NotImplementedError.

Create a virtual scope.

Parameters:
  • num_channels (int)

    Number of analog-input channels, numbered from 1. Pass the same count to InstroScope; a channel above it raises ValueError.

  • signals (Mapping[int, SineWave | SquareWave] | None)

    The signal on each listed channel. Channels left out see the default: a sine on odd channels, a square on even ones. Change a channel’s signal at any time with set_signal.

Raises:

ValueError – num_channels is less than 1, or signals names a channel that does not exist.

Example:

from instro.scope import InstroScope, ScopeMeasurementType
from instro.scope.drivers import VirtualScope
from instro.scope.drivers.virtual import SineWave

driver = VirtualScope(num_channels=4, signals={3: SineWave(frequency=5e3, amplitude=0.5)})
with InstroScope(name="scope", driver=driver, num_channels=4) as scope:
    scope.single()
    print(scope.measure(ScopeMeasurementType.VRMS, channel=1).latest)  # about 0.71: sine
    print(scope.measure(ScopeMeasurementType.VRMS, channel=2).latest)  # about 1.0: square
    print(scope.measure(ScopeMeasurementType.FREQUENCY, channel=3).latest)  # 5000.0

Methods

check_errors

No error queue; invalid settings, including NaN or infinite values, raise ValueError when set.

close

Nothing to disconnect.

digitize

Take one capture and stop; never times out, since the simulated signal always triggers.

fetch_waveform

A copy of the capture, so editing the returned samples leaves later reads unchanged.

force_trigger

Nothing to force: the simulated signal always triggers.

get_acquisition_mode

Read back the current acquisition mode.

get_acquisition_state

Read back the acquisition run state (RUNNING / STOPPED).

get_average_count

Read back the waveforms-to-average count.

get_coupling

Read back input coupling on channel.

get_horizontal_scale

Read back the timebase (seconds/div).

get_probe_attenuation

Read back channel's probe attenuation ratio.

get_sample_rate

1000 points across the 10-division window.

get_signal

Return the signal channel sees.

get_trigger_status

Read back the trigger status (ARMED / READY / TRIGGERED / …).

get_vertical_offset

Read back channel's vertical offset (volts).

get_vertical_scale

Read back channel's vertical scale (V/div).

load_settings

Not supported: the virtual scope has no setup storage.

measure

Voltages from the capture; NaN for frequency, period, and duty cycle on a signal with no edges.

open

Nothing to connect to.

run

Free-run: every fetch and measurement reads a fresh capture.

save_screenshot

Not supported: the virtual scope has no screen to capture.

save_settings

Not supported: the virtual scope has no setup storage.

set_acquisition_mode

Accept any mode; only AVERAGE changes the capture, by reducing its noise.

set_average_count

Set the number of waveforms to average.

set_coupling

Set AC/DC input coupling on channel.

set_horizontal_scale

Set the timebase to seconds_per_div.

set_probe_attenuation

Accepted and read back; waveforms are already referred to the probe tip.

set_signal

Set the signal channel sees; a stopped scope keeps showing its last capture.

set_trigger_level

Set the trigger threshold to level (volts).

set_trigger_mode

Accept either mode; the simulated signal always triggers.

set_trigger_slope

EITHER aligns on the rising edge.

set_trigger_source

Set the trigger source to analog channel.

set_trigger_type

Accept any type; captures align as an edge trigger.

set_vertical_offset

Set the voltage at screen center; captures clip 4 divisions above and below it.

set_vertical_scale

Set channel's vertical scale to volts_per_div (V/div).

setup_measurement

Ensure a measurement slot exists for measurement_type/channel before the scope triggers.

single

Capture every channel at once and stop; the simulated signal always triggers.

stop

Freeze every channel's capture for later fetches and measurements; a stopped scope keeps its own.

set_signal(
signal: SineWave | SquareWave,
channel: int,
) → None[source]#

Set the signal channel sees; a stopped scope keeps showing its last capture.

Parameters:
Raises:

ValueError – channel does not exist.

get_signal(
channel: int,
) → SineWave | SquareWave[source]#

Return the signal channel sees.

open() → None[source]#

Nothing to connect to.

close() → None[source]#

Nothing to disconnect.

check_errors() → None[source]#

No error queue; invalid settings, including NaN or infinite values, raise ValueError when set.

set_vertical_scale(
volts_per_div: float,
channel: int,
) → None[source]#

Set channel’s vertical scale to volts_per_div (V/div).

Drivers may snap to the nearest hardware-supported step. Callers that need the actual applied value should get_vertical_scale() afterward.

get_vertical_scale(channel: int) → float[source]#

Read back channel’s vertical scale (V/div).

set_vertical_offset(offset: float, channel: int) → None[source]#

Set the voltage at screen center; captures clip 4 divisions above and below it.

get_vertical_offset(channel: int) → float[source]#

Read back channel’s vertical offset (volts).

set_coupling(
coupling: Coupling,
channel: int,
) → None[source]#

Set AC/DC input coupling on channel.

get_coupling(channel: int) → Coupling[source]#

Read back input coupling on channel.

set_probe_attenuation(factor: float, channel: int) → None[source]#

Accepted and read back; waveforms are already referred to the probe tip.

get_probe_attenuation(channel: int) → float[source]#

Read back channel’s probe attenuation ratio.

set_horizontal_scale(seconds_per_div: float) → None[source]#

Set the timebase to seconds_per_div. Applies globally to all channels.

get_horizontal_scale() → float[source]#

Read back the timebase (seconds/div).

get_sample_rate() → float[source]#

1000 points across the 10-division window.

set_acquisition_mode(
mode: AcquisitionMode,
) → None[source]#

Accept any mode; only AVERAGE changes the capture, by reducing its noise.

get_acquisition_mode() → AcquisitionMode[source]#

Read back the current acquisition mode.

set_average_count(count: int) → None[source]#

Set the number of waveforms to average. Only takes effect in AcquisitionMode.AVERAGE.

get_average_count() → int[source]#

Read back the waveforms-to-average count.

run() → None[source]#

Free-run: every fetch and measurement reads a fresh capture.

stop() → None[source]#

Freeze every channel’s capture for later fetches and measurements; a stopped scope keeps its own.

single() → None[source]#

Capture every channel at once and stop; the simulated signal always triggers.

digitize(timeout: float) → None[source]#

Take one capture and stop; never times out, since the simulated signal always triggers.

Right after single, keeps that shot instead of taking another, to work around an InstroScope bug (#716).

get_acquisition_state() → AcquisitionState[source]#

Read back the acquisition run state (RUNNING / STOPPED).

fetch_waveform(
channel: int,
) → WaveformData[source]#

A copy of the capture, so editing the returned samples leaves later reads unchanged.

measure(
measurement_type: ScopeMeasurementType,
channel: int,
) → float[source]#

Voltages from the capture; NaN for frequency, period, and duty cycle on a signal with no edges.

set_trigger_source(channel: int) → None[source]#

Set the trigger source to analog channel.

Drivers typically cache this value because the trigger-level SCPI on some scopes requires the source channel in the same command.

set_trigger_type(
trigger_type: TriggerType,
) → None[source]#

Accept any type; captures align as an edge trigger.

set_trigger_level(level: float) → None[source]#

Set the trigger threshold to level (volts). Applies to the configured trigger source.

set_trigger_slope(
slope: TriggerSlope,
) → None[source]#

EITHER aligns on the rising edge.

set_trigger_mode(
mode: TriggerMode,
) → None[source]#

Accept either mode; the simulated signal always triggers.

force_trigger() → None[source]#

Nothing to force: the simulated signal always triggers.

get_trigger_status() → TriggerStatus[source]#

Read back the trigger status (ARMED / READY / TRIGGERED / …).

save_screenshot(
filepath: str,
to_instrument: bool = False,
) → bytes[source]#

Not supported: the virtual scope has no screen to capture.

save_settings(
name: str,
to_instrument: bool = False,
) → bytes[source]#

Not supported: the virtual scope has no setup storage.

load_settings(
name: str,
from_instrument: bool = False,
) → None[source]#

Not supported: the virtual scope has no setup storage.

setup_measurement(
measurement_type: ScopeMeasurementType,
channel: int,
) → None#

Ensure a measurement slot exists for measurement_type/channel before the scope triggers.

Required for instruments (e.g. Tektronix) that compute measurements during acquisition — the slot must be present at trigger time or the first measure() returns stale/invalid data. Default is a no-op for instruments (e.g. Keysight 1200X) that compute on demand.