115 lines
3.7 KiB
Python
115 lines
3.7 KiB
Python
"""Qt serial/lifecycle port. Protocol decisions are exclusively in C99."""
|
|
from __future__ import annotations
|
|
|
|
from PySide6.QtCore import QObject, QTimer, QElapsedTimer, Signal
|
|
from PySide6.QtSerialPort import QSerialPort, QSerialPortInfo
|
|
from .native import NativeAnalyzer
|
|
|
|
|
|
class AnalyzerPort(QObject):
|
|
changed = Signal()
|
|
completed = Signal(object)
|
|
error = Signal(str)
|
|
log = Signal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.core = None
|
|
self.active = False
|
|
self._last_done = False
|
|
self.serial = QSerialPort(self)
|
|
self.serial.readyRead.connect(self._read)
|
|
self.serial.errorOccurred.connect(self._serial_error)
|
|
self.timer = QTimer(self)
|
|
self.timer.setInterval(10)
|
|
self.timer.timeout.connect(self._tick)
|
|
self.clock = QElapsedTimer()
|
|
|
|
@staticmethod
|
|
def ports():
|
|
return [(p.portName(), p.description()) for p in QSerialPortInfo.availablePorts()]
|
|
|
|
def open(self, name, demo=False):
|
|
self.close()
|
|
try:
|
|
self.core = NativeAnalyzer()
|
|
self.core.reset(demo)
|
|
except RuntimeError as exc:
|
|
self.error.emit(str(exc))
|
|
return
|
|
if not demo:
|
|
self.serial.setPortName(name)
|
|
self.serial.setBaudRate(921600)
|
|
self.serial.setDataBits(QSerialPort.DataBits.Data8)
|
|
self.serial.setParity(QSerialPort.Parity.NoParity)
|
|
self.serial.setStopBits(QSerialPort.StopBits.OneStop)
|
|
self.serial.setFlowControl(QSerialPort.FlowControl.NoFlowControl)
|
|
if not self.serial.open(QSerialPort.OpenModeFlag.ReadWrite):
|
|
self.error.emit(self.serial.errorString())
|
|
return
|
|
self.active = True
|
|
self._last_done = False
|
|
self.clock.start()
|
|
self.timer.start()
|
|
self.log.emit("ДЕМО: синтетические данные, триггер не моделируется" if demo
|
|
else "%s · 921600 8N1" % name)
|
|
self._pump()
|
|
|
|
def close(self):
|
|
self.active = False
|
|
self.timer.stop()
|
|
self.serial.close()
|
|
self.changed.emit()
|
|
|
|
def start(self, *settings):
|
|
if not self.active or not self.core:
|
|
return
|
|
try:
|
|
self.core.start(*settings)
|
|
except ValueError as exc:
|
|
self.error.emit(str(exc))
|
|
return
|
|
self._last_done = False
|
|
self._pump()
|
|
|
|
def _tick(self):
|
|
if not self.active:
|
|
return
|
|
self.core.tick(self.clock.restart())
|
|
self._pump()
|
|
|
|
def _read(self):
|
|
data = bytes(self.serial.readAll())
|
|
if self.active and data:
|
|
self.log.emit("RX " + data.hex(" ").upper())
|
|
self.core.feed(data)
|
|
self._pump()
|
|
|
|
def _pump(self):
|
|
if not self.active:
|
|
return
|
|
if self.core.state == NativeAnalyzer.ERROR:
|
|
message = self.core.error
|
|
self.close()
|
|
self.error.emit(message)
|
|
return
|
|
packet = self.core.next_request()
|
|
if packet:
|
|
self.log.emit("TX " + packet.hex(" ").upper())
|
|
if self.serial.write(packet) != len(packet):
|
|
self.core.fail_io()
|
|
self._pump()
|
|
return
|
|
self.clock.restart()
|
|
if self.core.state == NativeAnalyzer.DONE and not self._last_done:
|
|
self._last_done = True
|
|
self.completed.emit(self.core.capture())
|
|
self.changed.emit()
|
|
|
|
def _serial_error(self, code):
|
|
if self.active and code != QSerialPort.SerialPortError.NoError:
|
|
message = self.serial.errorString()
|
|
self.core.fail_io()
|
|
self.close()
|
|
self.error.emit(message)
|