#pragma once #include #include #include #include #include #include #include #include #include // SerialWorker lives in its own QThread and owns the actual data source, // which is either a QSerialPort (real/virtual serial device) or a // QTcpSocket (for firmware/emulators that send their debug UART over a // TCP connection, e.g. FS-UAE's serial-over-socat setup). Both are // QIODevice subclasses, so the byte-parsing pipeline below (line // buffering, ANSI stripping, tag detection, logging) is shared as-is // between the two transports; only opening/closing/reconnect handling // differs. // // It emits newLine() for every complete line received, // tagDetected() when a line contains a recognised tag like [WDG], // and clearScreen() when an ANSI clear-screen sequence is received. // Auto-reconnect: if the connection drops unexpectedly, the worker // retries every reconnectIntervalMs until success or closePort(). class SerialWorker : public QObject { Q_OBJECT public: explicit SerialWorker(QObject *parent = nullptr); ~SerialWorker(); void setAutoReconnect(bool enabled) { m_autoReconnect = enabled; } void setReconnectInterval(int ms) { m_reconnectIntervalMs = ms; } public slots: void openPort(const QString &portName, qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::Parity parity, QSerialPort::StopBits stopBits, QSerialPort::FlowControl flowControl); void openNetwork(const QString &host, quint16 port); void closePort(); void setLogFile(const QString &path); void stopLogging(); signals: void newLine(const QString &line); void tagDetected(const QString &tag, const QString &value); void clearScreen(); void portOpened(); void portClosed(); void reconnecting(int attempt); void errorOccurred(const QString &message); private slots: void onReadyRead(); void onPortError(QSerialPort::SerialPortError err); void onSocketConnected(); void onSocketDisconnected(); void onSocketError(QAbstractSocket::SocketError err); void tryReconnect(); void flushScanTail(); private: enum class ConnectionMode { Serial, Network }; void processRawData(const QByteArray &data); void appendToLineBuffer(const QByteArray &toProcess); void processLine(const QString &line); void scheduleReconnect(); ConnectionMode m_mode = ConnectionMode::Serial; QSerialPort *m_port = nullptr; QTcpSocket *m_socket = nullptr; // Points at whichever of m_port/m_socket is the currently active data // source; onReadyRead() reads from this without caring which it is. QIODevice *m_device = nullptr; QFile *m_logFile = nullptr; QTextStream *m_logStream = nullptr; QString m_buffer; QByteArray m_scanTail; // carries partial ANSI sequences across reads // If no further bytes arrive shortly after some data was held back in // m_scanTail (to safely detect a possibly-split ANSI sequence), this // timer fires and flushes that tail through the normal line-processing // path anyway. Without this, the last line(s) of a burst can sit stuck // in m_scanTail indefinitely if the sender goes quiet (e.g. a device // that streams a final line and then stops) and only surface once new // bytes eventually arrive (e.g. after a reboot). QTimer *m_idleFlushTimer = nullptr; static constexpr int kIdleFlushMs = 50; QTimer *m_reconnectTimer = nullptr; bool m_autoReconnect = true; bool m_userDisconnected = false; int m_reconnectIntervalMs = 2000; int m_reconnectAttempt = 0; // Serial-specific connection parameters QString m_portName; qint32 m_baudRate = 115200; QSerialPort::DataBits m_dataBits = QSerialPort::Data8; QSerialPort::Parity m_parity = QSerialPort::NoParity; QSerialPort::StopBits m_stopBits = QSerialPort::OneStop; QSerialPort::FlowControl m_flowControl = QSerialPort::NoFlowControl; // Network-specific connection parameters QString m_networkHost; quint16 m_networkPort = 0; };