Skip to main content

SCPI Instrument Control

PyPI version Downloads Status Python Version Platform

CI codecov Docs License: MIT

Drive your whole bench from Python. Oscilloscopes, function generators, power supplies, and DAQ units — one API, over LAN, USB, GPIB, or serial.

Point it at an IP address and it identifies the instrument, picks the right SCPI dialect, and gets out of your way. Or point it at nothing at all: the built-in mock synthesizes real waveforms so you can write and test the entire acquisition pipeline with an empty bench.

Terminal recording of four example scripts — basic_usage, math_channels, measurement_badges_example and screen_capture_example — running one after another against the built-in mock scope with no instrument attached

Try it with no instrument on your desk

pip install SCPI-Instrument-Control
from scpi_control.connection import MockConnection
from scpi_control.oscilloscope import Oscilloscope
from scpi_control.signal_synth import SignalSpec

# A virtual SDS1104X-E probing a 3.3 V, 1 kHz logic clock.
scope = Oscilloscope("mock", connection=MockConnection(
    "mock",
    channel_states={1: True},
    signals={1: SignalSpec(kind="square", frequency=1000.0, amplitude=1.65, offset=1.65)},
    sample_rate=20e6,
    timebase=500e-6,
))
scope.connect()

wf = scope.get_waveform(channel=1)
print(f"{len(wf.time)} samples")
print(f"Vpp  {wf.voltage.max() - wf.voltage.min():.3f} V")
print(f"Freq {scope.measurement.measure_frequency(1):.2f} Hz")
14000 samples
Vpp  3.280 V
Freq 1000.00 Hz

Those are the same 3.28 V rails as the real SDS824X HD calibration square wave further down this page — captured with nothing plugged in. The mock is not a stub replaying a canned buffer: every sample is computed from the scope's current timebase, volts/division, offset, and trigger state, so the capture window follows the timebase, the code grid quantizes the way an 8-bit digitizer does, and free-running acquisitions drift in phase like a real one. Your CI can exercise the full stack with nothing plugged in.

Captures become reports

Annotate a plot with callouts, reference lines and shaded windows, and both the PDF and Markdown generators render them — here the code, then the plots it produced:

Code adding four plot annotations — a vertical line, a label, a horizontal reference line and a shaded span — to a captured waveform, followed by the two plots that code produced: the full trace and a region zoom inheriting the same annotations

# wf: a WaveformData just captured from the scope. Coordinates below are in
# DOMAIN units -- seconds here, hertz on an FFT plot -- never display units.
wf.annotations = [
    PlotAnnotation(kind=KIND_VLINE, text="trigger", x=1e-3),
    PlotAnnotation(kind=KIND_LABEL, text="overshoot", x=1.03e-3, y=3.75),
    PlotAnnotation(kind=KIND_HLINE, text="3.3 V nominal", y=3.3),
    PlotAnnotation(kind=KIND_SPAN, text="settling", x=1e-3, x_end=2.4e-3),
]
wf.caption = "Figure 1: C1 step response, 10x probe"

# A region zoom carries no annotations of its own -- it inherits the parent's,
# clipped to its own window.
wf.add_region(0.9e-3, 2.2e-3, label="Settling")

MarkdownReportGenerator().generate(report, out / "report.md")

# FFT plots, overlays, styling and sidecars: examples/report_annotations_advanced.py

wf and report come from the capture and report setup around it — examples/report_annotations_advanced.py is the whole thing, runnable with no instrument attached.

What you get

One API, many vendors Siglent, Tektronix, and LeCroy scopes auto-detected from *IDN?, plus AWGs, PSUs, and DAQ units. Unknown models fall back to a conservative per-vendor profile instead of being rejected.
Capture with provenance Every saved waveform records the instrument, settings, and timestamp that produced it. Read any of it back with load_waveform() or the scpi-extract CLI.
Analysis built in FFT, SNR, THD, jitter, rise/fall, and 25+ statistics — no separate toolchain.
A real desktop GUI PyQt6 + PyQtGraph live view with draggable measurement markers.
A browser lab gateway Share an instrument with the bench next door over HTTP/WebSocket.
Publication-ready reports Automated PDF/Markdown test reports, optionally analyzed by a local LLM.

Honest about limits

This project would rather raise than invent a number. Setting a trigger type a scope's dialect cannot express raises FeatureNotSupportedError instead of silently leaving the instrument as it was; a PSU without a documented protection subsystem refuses ovp_level rather than sending a command the firmware discards. Tektronix and LeCroy command tables were verified line-by-line against the vendor programming manuals and exercised against a dialect-aware mock, but have not yet been run against physical Tektronix or LeCroy hardware — the Siglent path has.

Install

pip install SCPI-Instrument-Control          # core: capture + analysis
pip install "SCPI-Instrument-Control[all]"   # everything below
Extra Adds
[gui] PyQt6 desktop application, live view, visual measurements
[web] scpi-web browser lab gateway (FastAPI)
[report-generator] PDF/Markdown test reports, local-LLM analysis
[usb] USB-TMC, GPIB, and serial instruments via PyVISA
[hdf5] .h5 waveform export
[fun] Vector graphics / XY-mode drawing

Requires Python 3.9+. The core install needs only NumPy, SciPy, and Matplotlib.

Then point it at real hardware

Live screen capture pulled over LAN from a Siglent SDS824X HD oscilloscope
Not a mockup — a live screen grab pulled over LAN from a Siglent SDS824X HD with scope.screen_capture.get_screenshot_pil().

from scpi_control import Oscilloscope

scope = Oscilloscope("192.168.1.100")   # dialect auto-detected from *IDN?
scope.connect()

scope.channel1.enable()
scope.channel1.voltage_scale = 1.0      # V/div
scope.channel1.coupling = "DC"

scope.trigger.set_edge_trigger(source="C1", slope="POS")
scope.trigger.level = 1.0

wf = scope.get_waveform(channel=1)
scope.waveform.save_waveform(wf, "capture.npz")   # provenance travels with it

scope.disconnect()

Ask the instrument what it can actually do before you ask it to do something:

caps = scope.capabilities
print(caps.trigger_types, caps.channel_couplings)

Batch sweeps, continuous logging, and trigger-driven capture live in scpi_control.automation — see the automation guide.

The desktop GUI

pip install "SCPI-Instrument-Control[gui]"
siglent-gui

The PyQt6 desktop application showing two live channels — a 1 kHz square wave and a noisy sine
Two channels live. This one is the mock again — the desktop app runs hardware-free too.

FFT tab showing the odd-harmonic comb of a square wave with the first three peaks marked
FFT of that square wave — the odd-harmonic comb, peaks picked out at 1, 3, and 5 kHz.

A live view that holds 5–20 fps on four channels, measurement markers you drag directly onto the trace, cursors, FFT, math channels, reference-waveform overlays, and a raw SCPI terminal for when you need to talk to the instrument yourself. Dedicated tabs connect to a power supply or a DAQ / data logger alongside the scope.

Software protocol decoding covers I2C, SPI, and UART.

→ Full GUI guide

The browser lab gateway

pip install "SCPI-Instrument-Control[web]"
scpi-web                    # prints its URL on every start
scpi-web invite alice       # a 10-minute link + code for a colleague

Run one gateway on the bench machine and reach your instruments from any browser on the LAN — live traces, screenshots, measurements, server-side FFT and filters, and ~1 Hz measurement logging with CSV export. GET /api/discover sweeps the subnet for instruments when DHCP moves them.

Every request needs a token, each session has an owner (owner writes, everyone else watches), and outbound connection targets are validated. The admin panel binds loopback only. It does not terminate TLS — keep it on a trusted network or behind a reverse proxy.

→ Gateway guide · Security model

Automated test reports

pip install "SCPI-Instrument-Control[report-generator]"

Turn captures into PDF or Markdown reports with plots, signal classification, 25+ statistics, pass/fail criteria, and a SHA-256 manifest of the raw data. Comparison and batch modes handle before/after runs and multi-DUT yield tables. Analysis text can be generated by a local LLM through Ollama — on your machine, no cloud provider, no API key.

See example_reports/ for generated samples, including one with LLM analysis.

→ Report generator guide

Supported instruments

Class Models
Siglent scopes (hardware-tested) SDS800X HD (SDS804X/824X HD) · SDS1000X-E (1102X-E/1104X-E/1202X-E/1204X-E) · SDS2000X Plus (2104X+/2204X+/2354X+) · SDS5000X (5034X/5054X/5104X)
Tektronix scopes (manual-verified) TBS1000C (TBS1102C) · 2 Series MSO (MSO24) · 4 Series (MSO44/46) · 5 Series (MSO54/56/58/58LP) · 6 Series (MSO64)
LeCroy scopes (manual-verified) WaveSurfer 3000z (3024z) · WaveRunner 8000 (8104)
Function generators Siglent SDG1000X (1032X/1025/1020) · SDG2000X (2122X/2082X/2042X)
Power supplies Siglent SPD3303X / SPD3303X-E · SPD1305X / SPD1168X
DAQ / data loggers Keysight 34970A / 34972A · DAQ970A / DAQ973A · generic SCPI-99

Other SCPI instruments generally work: model-specific behavior comes from the ModelCapability registry, and unrecognized models fall back to a conservative profile for their vendor with a logged warning.

→ SCPI dialects and per-vendor gaps

Command-line tools

Command Does
siglent-gui Launch the desktop application
scpi-web Serve the browser lab gateway
scpi-extract Inspect or export a saved waveform file
siglent-report-generator Launch the standalone report generator

Documentation

Upgrading? import siglent became import scpi_control in v1.0.0 and the compatibility shim was removed in v2.0.0; the API is otherwise identical. Pin SCPI-Instrument-Control<2.0 if you cannot migrate yet. The v4.x → 5.0 gateway now requires a token — see the security guide.

Contributing

Issues and pull requests are welcome — see the Contributing Guide.

git clone https://github.com/little-did-I-know/SCPI-Instrument-Control.git
cd SCPI-Instrument-Control
make dev-setup
make check

License

MIT — see LICENSE.

Metadata

Release files for SCPI-Instrument-Control 7.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for SCPI-Instrument-Control 7.2.0
File Size Uploaded
scpi_instrument_control-7.2.0.tar.gz 11.4 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for SCPI-Instrument-Control 7.2.0
File Interpreter ABI Platform
scpi_instrument_control-7.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 12.2 MB

Release files / scpi_instrument_control-7.2.0.tar.gz

Download URL scpi_instrument_control-7.2.0.tar.gz
Size 11.4 MB
Tags Source
SHA-256 checksum
How to use checksums
c91c4fd3f65ec2279f6863977ebe054731b40756f7c55746a5a8bc81a10a56b2
BLAKE2b-256 checksum
How to use checksums
8023fa51aef9f1dd68148b1ce7d0a4a2e0efc79f0951ca9d42a17bb16a2c6513
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release files / scpi_instrument_control-7.2.0-py3-none-any.whl

Download URL scpi_instrument_control-7.2.0-py3-none-any.whl
Size 794.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
515b4bd8cb5a14eb98305c0781cb1e7093f4727237ce0a27c4242e61e7265a69
BLAKE2b-256 checksum
How to use checksums
bf412eb5b1f79feb89f025a4a651424b19cc463911fbe38bf6dd8ee48740b100
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 28, 2026.

Transparency log

Release history Release notifications | RSS feed

7.3.0

2 release files

This release

7.2.0 This release

2 release files

7.1.0

2 release files

7.0.1

2 release files

7.0.0

2 release files

6.0.0

2 release files

5.8.0

2 release files

5.7.1

2 release files

5.7.0

2 release files

5.6.0

2 release files

5.5.0

2 release files

5.4.0

2 release files

5.3.0

2 release files

5.2.0

2 release files

5.1.0

2 release files

5.0.0

2 release files

4.1.1

2 release files

4.1.0

2 release files

4.0.0

2 release files

3.3.1

2 release files

3.3.0

2 release files

3.2.0

2 release files

3.1.0

2 release files

3.0.0

2 release files

2.0.0

2 release files

1.1.0

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page