Files
templates/python/altera_logic/stream_port.py

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)