"""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)