Files
templates/python/set_devices/tektronix.py

128 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Получение осциллограмм Tektronix DPO4034 через SCPI/VISA. Параметры масштаба из прибора
# переводят сырые отсчёты в физические значения; выбор VISA-ресурса отделён от графического
# интерфейса.
"""DPO4034 SCPI acquisition over VISA (GPIB or Ethernet), without Qt.
See docs/tektronix-dpo4034.md for wiring, commands and scaling.
"""
from dataclasses import dataclass
import math
def resource_name(interface, address, board=0):
address = str(address).strip()
if interface == "GPIB":
if not address.isdigit() or not 0 <= int(address) <= 30:
raise ValueError("Адрес GPIB должен быть в диапазоне 0…30")
if not 0 <= int(board) <= 99:
raise ValueError("Неверный номер контроллера GPIB")
return f"GPIB{int(board)}::{int(address)}::INSTR"
if interface != "Ethernet":
raise ValueError("Неизвестный интерфейс")
if not address or any(c.isspace() for c in address) or any(c in address for c in ";\r\n"):
raise ValueError("Укажите IP/имя прибора или TCPIP VISA resource")
if "::" in address:
if not address.upper().startswith("TCPIP") or not address.upper().endswith("::INSTR"):
raise ValueError("Ожидается TCPIP…::INSTR (VXI-11)")
return address
return f"TCPIP0::{address}::inst0::INSTR"
@dataclass(frozen=True)
class Waveform:
channel: str
seconds: tuple
values: tuple
unit: str
class DPO4034:
"""Single-thread-owned session. Inject a ResourceManager for offline tests."""
def __init__(self, manager_factory=None):
self._factory = manager_factory
self.manager = None
self.instrument = None
def connect(self, resource, timeout_ms=5000):
self.close()
factory = self._factory
if factory is None:
try:
import pyvisa
except ImportError as exc:
raise RuntimeError("Установите PyVISA и NI-VISA или TekVISA") from exc
factory = pyvisa.ResourceManager
try:
self.manager = factory()
self.instrument = self.manager.open_resource(resource, open_timeout=timeout_ms)
self.instrument.timeout = timeout_ms
self.instrument.write_termination = "\n"
self.instrument.read_termination = "\n"
identity = self.instrument.query("*IDN?").strip()
fields = identity.upper().split(",")
if len(fields) < 2 or "TEKTRONIX" not in fields[0] or fields[1].strip() != "DPO4034":
raise ValueError(f"Ожидался Tektronix DPO4034, получено: {identity}")
self.instrument.write("HEADER OFF")
return identity
except Exception:
self.close()
raise
def close(self):
instrument, manager = self.instrument, self.manager
self.instrument = self.manager = None
try:
if instrument is not None:
instrument.close()
finally:
if manager is not None:
manager.close()
def capture(self, channels, points=100000):
channels = tuple(channels)
if not channels or len(set(channels)) != len(channels) or any(c not in ("CH1", "CH2", "CH3", "CH4") for c in channels):
raise ValueError("Выберите каналы CH1…CH4")
if not 1 <= points <= 1000000:
raise ValueError("Число точек должно быть 1…1000000")
scope = self.instrument
if scope is None:
raise RuntimeError("Прибор не подключён")
# Freeze the record so all selected channels belong to the same capture.
running = int(scope.query("ACQUIRE:STATE?").strip())
result = []
try:
scope.write("ACQUIRE:STATE STOP")
scope.query("*OPC?")
for channel in channels:
if not int(scope.query(f"SELECT:{channel}?").strip()):
raise ValueError(f"Включите {channel} на осциллографе")
scope.write(f"DATA:SOURCE {channel}")
scope.write("DATA:ENCDG RIBINARY")
scope.write("DATA:WIDTH 2")
scope.write("DATA:START 1")
scope.write(f"DATA:STOP {points}")
if scope.query("WFMOUTPRE:PT_FMT?").strip().upper() != "Y":
raise ValueError("ENV/Peak Detect не поддерживается; выберите Sample или Average")
params = [float(scope.query(f"WFMOUTPRE:{key}?").strip()) for key in
("XINCR", "XZERO", "PT_OFF", "YMULT", "YOFF", "YZERO")]
dx, x0, offset, mult, yoff, y0 = params
if not all(math.isfinite(v) for v in params) or dx <= 0:
raise ValueError("Некорректная преамбула осциллограммы")
unit = scope.query("WFMOUTPRE:YUNIT?").strip().strip('"')
count = int(scope.query("WFMOUTPRE:NR_PT?").strip())
if not 1 <= count <= points:
raise ValueError("Некорректная длина осциллограммы")
raw = scope.query_binary_values("CURVE?", datatype="h", is_big_endian=True,
container=list)
if len(raw) != count:
raise ValueError(f"Неполная осциллограмма: {len(raw)} из {count}")
result.append(Waveform(channel,
tuple(x0 + (i - offset) * dx for i in range(count)),
tuple(y0 + (v - yoff) * mult for v in raw), unit))
finally:
if running:
scope.write("ACQUIRE:STATE RUN")
return result