Track completed Single acquisitions and persist recent addresses
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
"""Editable recent-address selector with portable, per-interface JSON storage."""
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
try:
|
||||||
|
from PySide6.QtCore import QSignalBlocker
|
||||||
|
from PySide6.QtWidgets import QComboBox, QSizePolicy
|
||||||
|
except ImportError:
|
||||||
|
from PySide2.QtCore import QSignalBlocker
|
||||||
|
from PySide2.QtWidgets import QComboBox, QSizePolicy
|
||||||
|
|
||||||
|
class AddressHistoryBox(QComboBox):
|
||||||
|
def __init__(self, filename, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.filename = Path(filename)
|
||||||
|
self.interface = 'Ethernet'
|
||||||
|
self.history = {}
|
||||||
|
self.setEditable(True)
|
||||||
|
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||||
|
self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
|
||||||
|
try:
|
||||||
|
saved = json.loads(self.filename.read_text(encoding='utf8'))
|
||||||
|
if isinstance(saved, dict):
|
||||||
|
self.history = {key: [x for x in items if isinstance(x, str) and x.strip()][:20]
|
||||||
|
for key, items in saved.items() if isinstance(items, list)}
|
||||||
|
except (OSError, ValueError):
|
||||||
|
pass
|
||||||
|
self.set_interface(self.interface)
|
||||||
|
|
||||||
|
def text(self):
|
||||||
|
return self.currentText()
|
||||||
|
|
||||||
|
def setText(self, text):
|
||||||
|
self.setEditText(text)
|
||||||
|
|
||||||
|
def setPlaceholderText(self, text):
|
||||||
|
self.lineEdit().setPlaceholderText(text)
|
||||||
|
|
||||||
|
def set_interface(self, interface):
|
||||||
|
self.interface = interface
|
||||||
|
with QSignalBlocker(self):
|
||||||
|
self.clear()
|
||||||
|
self.addItems(self.history.get(interface, []))
|
||||||
|
if not self.count():
|
||||||
|
self.setEditText('')
|
||||||
|
|
||||||
|
def remember(self):
|
||||||
|
address = self.currentText().strip()
|
||||||
|
if not address:
|
||||||
|
return
|
||||||
|
previous = self.history.get(self.interface, [])
|
||||||
|
self.history[self.interface] = [address] + [x for x in previous if x.casefold()!=address.casefold()][:19]
|
||||||
|
self.filename.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
temporary = self.filename.with_suffix('.tmp')
|
||||||
|
temporary.write_text(json.dumps(self.history, ensure_ascii=False, indent=2), encoding='utf8')
|
||||||
|
temporary.replace(self.filename)
|
||||||
|
self.set_interface(self.interface)
|
||||||
@@ -97,6 +97,19 @@ class DPO4034:
|
|||||||
return True
|
return True
|
||||||
raise ValueError("Некорректное состояние сбора: " + state)
|
raise ValueError("Некорректное состояние сбора: " + state)
|
||||||
|
|
||||||
|
def acquisition_status(self):
|
||||||
|
if self.instrument is None:
|
||||||
|
raise RuntimeError("Прибор не подключён")
|
||||||
|
fields = self.instrument.query("ACQUIRE:STATE?;:ACQUIRE:STOPAFTER?;:ACQUIRE:NUMACQ?").strip().split(";")
|
||||||
|
if len(fields) != 3:
|
||||||
|
raise ValueError("Некорректное состояние сбора")
|
||||||
|
state, mode, count = (value.strip().upper() for value in fields)
|
||||||
|
if state not in ("0", "1", "OFF", "ON", "STOP", "RUN"):
|
||||||
|
raise ValueError("Некорректное состояние сбора")
|
||||||
|
if mode not in ("RUNSTOP", "RUNST", "SEQUENCE", "SEQ"):
|
||||||
|
raise ValueError("Некорректный режим сбора")
|
||||||
|
return state in ("1", "ON", "RUN"), mode in ("SEQUENCE", "SEQ"), int(count)
|
||||||
|
|
||||||
def settings(self, channel="CH1"):
|
def settings(self, channel="CH1"):
|
||||||
self._channel(channel)
|
self._channel(channel)
|
||||||
if self.instrument is None:
|
if self.instrument is None:
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"""Read completed Single records once, without interfering with acquisition."""
|
||||||
|
class CaptureState:
|
||||||
|
def __init__(self):
|
||||||
|
self.armed = False
|
||||||
|
self.delivered = None
|
||||||
|
|
||||||
|
def arm(self):
|
||||||
|
self.armed = True
|
||||||
|
|
||||||
|
def ready(self, status):
|
||||||
|
running, single, count = status
|
||||||
|
if not single:
|
||||||
|
self.armed = False
|
||||||
|
self.delivered = None
|
||||||
|
return running
|
||||||
|
if running or count == 0:
|
||||||
|
self.armed = True
|
||||||
|
return False
|
||||||
|
return count > 0 and (self.armed or self.delivered != count)
|
||||||
|
|
||||||
|
def consumed(self, status):
|
||||||
|
if status[1]:
|
||||||
|
self.delivered = status[2]
|
||||||
|
self.armed = False
|
||||||
@@ -3,6 +3,7 @@ from dataclasses import dataclass
|
|||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import csv
|
import csv
|
||||||
|
from .tektronix_capture_state import CaptureState
|
||||||
from .tektronix import DPO4034, Waveform
|
from .tektronix import DPO4034, Waveform
|
||||||
from .plot_decimation import pixel_envelope
|
from .plot_decimation import pixel_envelope
|
||||||
|
|
||||||
@@ -36,6 +37,7 @@ class ScopeSession:
|
|||||||
|
|
||||||
def _run(self, factory):
|
def _run(self, factory):
|
||||||
client = factory()
|
client = factory()
|
||||||
|
state = CaptureState()
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
action, args = self.commands.get()
|
action, args = self.commands.get()
|
||||||
@@ -44,11 +46,14 @@ class ScopeSession:
|
|||||||
try:
|
try:
|
||||||
if action == "capture" and len(args) in (4, 5):
|
if action == "capture" and len(args) in (4, 5):
|
||||||
automatic = len(args) == 5 and args[4]
|
automatic = len(args) == 5 and args[4]
|
||||||
if automatic and not client.is_running():
|
status = client.acquisition_status() if automatic else None
|
||||||
|
if automatic and not state.ready(status):
|
||||||
value = None
|
value = None
|
||||||
else:
|
else:
|
||||||
value = client.capture(*args[:3])
|
value = client.capture(*args[:3])
|
||||||
value = CaptureFrame(value, prepare_preview(value, args[3]))
|
value = CaptureFrame(value, prepare_preview(value, args[3]))
|
||||||
|
if automatic:
|
||||||
|
state.consumed(status)
|
||||||
elif action == "export":
|
elif action == "export":
|
||||||
filename, waves = args
|
filename, waves = args
|
||||||
with open(filename, "w", newline="", encoding="utf-8-sig") as stream:
|
with open(filename, "w", newline="", encoding="utf-8-sig") as stream:
|
||||||
@@ -62,6 +67,10 @@ class ScopeSession:
|
|||||||
writer.writerow([stamp * 1000, *(w.values[index] for w in waves)])
|
writer.writerow([stamp * 1000, *(w.values[index] for w in waves)])
|
||||||
value = filename
|
value = filename
|
||||||
else:
|
else:
|
||||||
|
if action == "connect":
|
||||||
|
state = CaptureState()
|
||||||
|
if action == "acquisition" and args[0] == "single":
|
||||||
|
state.arm()
|
||||||
value = getattr(client, action)(*args)
|
value = getattr(client, action)(*args)
|
||||||
self.results.put((action, value, None))
|
self.results.put((action, value, None))
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
|
|||||||
Reference in New Issue
Block a user