104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
"""Qt transport/lifecycle port for the C SETCAN stream receiver.
|
|
|
|
CAN ingress accepts canonical RX events from an existing bus connection.
|
|
UART uses the same SETCAN frames inside the shared AA55/CRC16 transport.
|
|
Reception and rendering clocks are separate; no packet-rate repainting.
|
|
"""
|
|
from __future__ import annotations
|
|
from PySide6.QtCore import QObject, QTimer, QElapsedTimer, Signal
|
|
from PySide6.QtSerialPort import QSerialPort, QSerialPortInfo
|
|
from .stream import NativeStream
|
|
|
|
|
|
class StreamPort(QObject):
|
|
updated = Signal(object, object)
|
|
changed = Signal(bool)
|
|
error = Signal(str)
|
|
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.core = None
|
|
self.active = False
|
|
self.mode = "demo"
|
|
self.paused = False
|
|
self.serial = QSerialPort(self)
|
|
self.serial.readyRead.connect(self._read)
|
|
self.serial.errorOccurred.connect(self._error)
|
|
self.timer = QTimer(self)
|
|
self.timer.setInterval(50)
|
|
self.timer.timeout.connect(self._tick)
|
|
self._previous = None
|
|
self._last_data = QElapsedTimer()
|
|
self._last_received = None
|
|
|
|
@staticmethod
|
|
def ports():
|
|
return [(p.portName(), p.description()) for p in QSerialPortInfo.availablePorts()]
|
|
|
|
def open(self, mode, port_name="", device_id=None):
|
|
self.close()
|
|
if mode not in ("demo", "can", "uart", "both"):
|
|
self.error.emit("Неизвестный транспорт")
|
|
return
|
|
try:
|
|
self.core = NativeStream(device_id)
|
|
except (RuntimeError, ValueError) as exc:
|
|
self.error.emit(str(exc))
|
|
return
|
|
self.mode = mode
|
|
if mode in ("uart", "both"):
|
|
self.serial.setPortName(port_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.paused = False
|
|
self._previous = None
|
|
self._last_received = None
|
|
self._last_data.start()
|
|
self.timer.start()
|
|
self.changed.emit(True)
|
|
|
|
def close(self):
|
|
self.active = False
|
|
self.timer.stop()
|
|
self.serial.close()
|
|
self.changed.emit(False)
|
|
|
|
def receive_event(self, event):
|
|
if (self.active and self.mode in ("can", "both") and event.get("kind") == "can"
|
|
and event.get("direction") == "RX"):
|
|
self.core.feed_can(event["identifier"], event["data"],
|
|
event.get("extended", False), event.get("remote", False))
|
|
|
|
def _read(self):
|
|
data = bytes(self.serial.readAll())
|
|
if self.active and self.mode in ("uart", "both"):
|
|
self.core.feed_uart(data)
|
|
|
|
def _tick(self):
|
|
if not self.active:
|
|
return
|
|
if self.mode == "demo":
|
|
self.core.demo_step()
|
|
stats = self.core.stats
|
|
signature = (stats["received"], stats["session"])
|
|
if signature != self._last_received:
|
|
self._last_received = signature
|
|
self._last_data.restart()
|
|
stats["stale"] = self._last_data.elapsed() > max(2000, stats["period_ns"]*3/1e6)
|
|
if not self.paused and stats != self._previous:
|
|
self._previous = stats
|
|
self.updated.emit(self.core.snapshot(self.mode == "demo"), stats)
|
|
|
|
def _error(self, code):
|
|
if self.active and code != QSerialPort.SerialPortError.NoError:
|
|
message = self.serial.errorString()
|
|
self.close()
|
|
self.error.emit(message)
|