diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-03-13 11:47:00 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-03-13 11:47:00 +0100 |
| commit | a2b1ccf17845e55caef7f69a5e68f49a55b6a166 (patch) | |
| tree | a7e8c414dbadeeb9bcac29478cf3fbf7e99a4a05 | |
| parent | 34faf3cdea798c1948229ec1bb53c828e2b40bb7 (diff) | |
| download | QtXpl2-a2b1ccf17845e55caef7f69a5e68f49a55b6a166.tar.gz QtXpl2-a2b1ccf17845e55caef7f69a5e68f49a55b6a166.zip | |
XPL2 protocol foundation: wire framing, typed API, KA_PING, GS_JC_VERSION
Add Xpl2Protocol namespace with buildMessage/parseMessage for wire
serialization. Replace raw send/receive API on Xpl2Client with typed
protocol methods and internal dispatch. Auto-reply to KA_PING on all
sockets (qDebug only). Add GS_JC_VERSION as first typed command with
controllerId, firmwareVersion, hardwareVersion, printheadCount properties.
Upgrade mock server from echo to line-based command dispatch with 1s
KA_PING timer and canned GS_JC_VERSION response. Unknown commands
produce qWarning instead of echo.
Overhaul demo: remove raw send UI and port config, add wireDebug
toggle and Get JC Version button.
| -rw-r--r-- | demo/Main.qml | 175 | ||||
| -rw-r--r-- | mock-server/EchoServer.cpp | 61 | ||||
| -rw-r--r-- | mock-server/EchoServer.h | 13 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/Xpl2Client.cpp | 221 | ||||
| -rw-r--r-- | src/Xpl2Client.h | 64 | ||||
| -rw-r--r-- | src/Xpl2Protocol.cpp | 128 | ||||
| -rw-r--r-- | src/Xpl2Protocol.h | 27 |
8 files changed, 453 insertions, 238 deletions
diff --git a/demo/Main.qml b/demo/Main.qml index d234eae..edf317c 100644 --- a/demo/Main.qml +++ b/demo/Main.qml @@ -14,13 +14,6 @@ ApplicationWindow { width: 800 Connections { - function onCommandSocketResponseReceived(response: string) { - debugConsole.appendLog("Command: " + response); - responseModel.append({ - text: "[Command] " + response - }); - } - function onConnectedChanged() { debugConsole.appendLog(Xpl2Client.connected ? "Connected" : "Disconnected"); @@ -30,22 +23,21 @@ ApplicationWindow { debugConsole.appendLog("ERROR: " + error); } - function onImagingSocketResponseReceived(response: string) { - debugConsole.appendLog("Imaging: " + response); - responseModel.append({ - text: "[Imaging] " + response - }); + function onJcVersionReceived() { + debugConsole.appendLog( + "JC Version: controller=%1 fw=%2 hw=%3 phCount=%4".arg( + Xpl2Client.controllerId).arg( + Xpl2Client.firmwareVersion).arg( + Xpl2Client.hardwareVersion).arg( + Xpl2Client.printheadCount)); } function onStatusMessage(message: string) { debugConsole.appendLog(message); } - function onStatusSocketResponseReceived(response: string) { - debugConsole.appendLog("Status: " + response); - responseModel.append({ - text: "[Status] " + response - }); + function onWireDebugMessage(message: string) { + debugConsole.appendLog("WIRE: " + message); } target: Xpl2Client @@ -66,9 +58,8 @@ ApplicationWindow { Layout.fillWidth: true title: "Connection" - GridLayout { + RowLayout { anchors.fill: parent - columns: 2 Label { text: "Host:" @@ -83,66 +74,7 @@ ApplicationWindow { onEditingFinished: Xpl2Client.host = text } - Label { - text: "Command port:" - } - - TextField { - id: commandPortField - - Layout.fillWidth: true - text: Xpl2Client.commandPort - - validator: IntValidator { - bottom: 1 - top: 65535 - } - - onEditingFinished: Xpl2Client.commandPort = parseInt( - text) - } - - Label { - text: "Imaging port:" - } - - TextField { - id: imagingPortField - - Layout.fillWidth: true - text: Xpl2Client.imagingPort - - validator: IntValidator { - bottom: 1 - top: 65535 - } - - onEditingFinished: Xpl2Client.imagingPort = parseInt( - text) - } - - Label { - text: "Status port:" - } - - TextField { - id: statusPortField - - Layout.fillWidth: true - text: Xpl2Client.statusPort - - validator: IntValidator { - bottom: 1 - top: 65535 - } - - onEditingFinished: Xpl2Client.statusPort = parseInt( - text) - } - Button { - Layout.alignment: Qt.AlignRight - Layout.columnSpan: 2 text: Xpl2Client.connected ? "Disconnect" : "Connect" onClicked: { @@ -152,81 +84,48 @@ ApplicationWindow { Xpl2Client.connectToServer(); } } + + CheckBox { + checked: Xpl2Client.wireDebug + text: "Wire debug" + + onToggled: Xpl2Client.wireDebug = checked + } } } - // --- Send Message --- + // --- Protocol Commands --- GroupBox { Layout.fillWidth: true enabled: Xpl2Client.connected - title: "Send" + title: "Protocol Commands" - RowLayout { + ColumnLayout { anchors.fill: parent - ComboBox { - id: portCombo - - model: ["Command", "Imaging", "Status"] - } - - TextField { - id: cmdField - - Layout.fillWidth: true - placeholderText: "Enter message…" - - onAccepted: sendBtn.clicked() - } - - Button { - id: sendBtn + RowLayout { + Button { + text: "Get JC Version" - text: "Send" + onClicked: Xpl2Client.getJcVersion() + } - onClicked: { - if (cmdField.text.length === 0) - return; - if (portCombo.currentIndex === 0) - Xpl2Client.sendToCommandSocket(cmdField.text); - else if (portCombo.currentIndex === 1) - Xpl2Client.sendToImagingSocket(cmdField.text); - else - Xpl2Client.sendToStatusSocket(cmdField.text); - cmdField.text = ""; + Label { + text: Xpl2Client.controllerId > 0 + ? "Controller: %1 | FW: %2 | HW: %3 | PHs: %4".arg( + Xpl2Client.controllerId).arg( + Xpl2Client.firmwareVersion).arg( + Xpl2Client.hardwareVersion).arg( + Xpl2Client.printheadCount) : + "No version data" } } } } - // --- Responses --- - GroupBox { + // --- Spacer --- + Item { Layout.fillHeight: true - Layout.fillWidth: true - title: "Responses" - - ListView { - id: responseView - - anchors.fill: parent - clip: true - - delegate: Text { - id: responseDelegate - - required property string text - - font.family: "monospace" - font.pixelSize: 13 - text: responseDelegate.text - width: responseView.width - wrapMode: Text.Wrap - } - model: ListModel { - id: responseModel - - } - } } } @@ -234,14 +133,14 @@ ApplicationWindow { Rectangle { id: debugConsole - function appendLog(msg) { + function appendLog(msg: string): void { let ts = new Date().toLocaleTimeString(Qt.locale(), "HH:mm:ss"); debugLog.text += "[" + ts + "] " + msg + "\n"; debugLog.cursorPosition = debugLog.text.length; } Layout.fillWidth: true - Layout.preferredHeight: 120 + Layout.preferredHeight: 160 border.color: "#444" color: "#1e1e1e" radius: 4 diff --git a/mock-server/EchoServer.cpp b/mock-server/EchoServer.cpp index 43fc6f9..8d86df1 100644 --- a/mock-server/EchoServer.cpp +++ b/mock-server/EchoServer.cpp @@ -1,6 +1,6 @@ /** * @file EchoServer.cpp - * @brief Simple TCP echo server for a single port. + * @brief Mock XPL2 server for a single port. */ #include "EchoServer.h" @@ -12,11 +12,16 @@ EchoServer::EchoServer (quint16 port, const char *name, QObject *parent) connect (this, &QTcpServer::newConnection, this, &EchoServer::onNewConnection); + connect (&m_pingTimer, &QTimer::timeout, this, &EchoServer::sendKaPing); + if (!listen (QHostAddress::Any, port)) qCritical ("Failed to listen on %s port %d: %s", m_name, m_port, qPrintable (errorString ())); else - qInfo ("Listening on %s port %d", m_name, m_port); + { + qInfo ("Listening on %s port %d", m_name, m_port); + m_pingTimer.start (1000); + } } void @@ -25,20 +30,23 @@ EchoServer::onNewConnection () while (auto *sock = nextPendingConnection ()) { qInfo ("[%s:%d] client connected", m_name, m_port); + m_clients.append (sock); connect (sock, &QTcpSocket::readyRead, this, - &EchoServer::onClientReadyRead); + &EchoServer::onClientMessageReady); connect (sock, &QTcpSocket::disconnected, this, &EchoServer::onClientDisconnected); } } void -EchoServer::onClientReadyRead () +EchoServer::onClientMessageReady () { auto *sock = qobject_cast<QTcpSocket *> (sender ()); - QByteArray data = sock->readAll (); - qInfo ("[%s:%d] echo %lld bytes", m_name, m_port, data.size ()); - sock->write (data); + while (sock->canReadLine ()) + { + QByteArray line = sock->readLine (); + handleCommand (sock, line); + } } void @@ -46,5 +54,44 @@ EchoServer::onClientDisconnected () { auto *sock = qobject_cast<QTcpSocket *> (sender ()); qInfo ("[%s:%d] client disconnected", m_name, m_port); + m_clients.removeOne (sock); sock->deleteLater (); } + +void +EchoServer::sendKaPing () +{ + for (auto *client : m_clients) + { + if (client->state () == QAbstractSocket::ConnectedState) + client->write ("KA_PING\n"); + } +} + +void +EchoServer::handleCommand (QTcpSocket *client, const QByteArray &line) +{ + QByteArray trimmed = line.trimmed (); + if (trimmed.isEmpty ()) + return; + + /* Split on first comma to get command token. */ + int comma = trimmed.indexOf (','); + QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed; + + if (cmd == "KA_PING") + { + qDebug ("[%s:%d] KA_PING ACK received", m_name, m_port); + return; + } + + if (cmd == "GS_JC_VERSION") + { + qInfo ("[%s:%d] -> GS_JC_VERSION reply", m_name, m_port); + client->write ("GS_JC_VERSION,1,\"1.05\",\"2.00\",15\n"); + return; + } + + qWarning ("[%s:%d] Unknown command: %s", m_name, m_port, + trimmed.constData ()); +} diff --git a/mock-server/EchoServer.h b/mock-server/EchoServer.h index 2e51b1e..c0a41c9 100644 --- a/mock-server/EchoServer.h +++ b/mock-server/EchoServer.h @@ -1,10 +1,14 @@ /** * @file EchoServer.h - * @brief Simple TCP echo server for a single port. + * @brief Mock XPL2 server for a single port. */ #pragma once +#include <QList> #include <QTcpServer> +#include <QTimer> + +class QTcpSocket; class EchoServer : public QTcpServer { @@ -15,10 +19,15 @@ public: private slots: void onNewConnection (); - void onClientReadyRead (); + void onClientMessageReady (); void onClientDisconnected (); + void sendKaPing (); private: + void handleCommand (QTcpSocket *client, const QByteArray &line); + const char *m_name; quint16 m_port; + QList<QTcpSocket *> m_clients; + QTimer m_pingTimer; }; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3339013..4d57f8a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,8 @@ qt_add_qml_module( VERSION 1.0 SOURCES + Xpl2Protocol.h + Xpl2Protocol.cpp Xpl2Client.h Xpl2Client.cpp OUTPUT_DIRECTORY diff --git a/src/Xpl2Client.cpp b/src/Xpl2Client.cpp index b29ac30..f7b280f 100644 --- a/src/Xpl2Client.cpp +++ b/src/Xpl2Client.cpp @@ -4,6 +4,8 @@ */ #include "Xpl2Client.h" +#include <QDebug> + Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) { connect (&m_commandSocket, &QTcpSocket::connected, this, @@ -11,7 +13,7 @@ Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) connect (&m_commandSocket, &QTcpSocket::disconnected, this, &Xpl2Client::onCommandSocketDisconnected); connect (&m_commandSocket, &QTcpSocket::readyRead, this, - &Xpl2Client::onCommandSocketReadyRead); + &Xpl2Client::onCommandSocketMessageReady); connect (&m_commandSocket, &QAbstractSocket::errorOccurred, this, &Xpl2Client::onCommandSocketError); @@ -20,7 +22,7 @@ Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) connect (&m_imagingSocket, &QTcpSocket::disconnected, this, &Xpl2Client::onImagingSocketDisconnected); connect (&m_imagingSocket, &QTcpSocket::readyRead, this, - &Xpl2Client::onImagingSocketReadyRead); + &Xpl2Client::onImagingSocketMessageReady); connect (&m_imagingSocket, &QAbstractSocket::errorOccurred, this, &Xpl2Client::onImagingSocketError); @@ -29,11 +31,15 @@ Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) connect (&m_statusSocket, &QTcpSocket::disconnected, this, &Xpl2Client::onStatusSocketDisconnected); connect (&m_statusSocket, &QTcpSocket::readyRead, this, - &Xpl2Client::onStatusSocketReadyRead); + &Xpl2Client::onStatusSocketMessageReady); connect (&m_statusSocket, &QAbstractSocket::errorOccurred, this, &Xpl2Client::onStatusSocketError); } +/* ------------------------------------------------------------------ */ +/* Properties */ +/* ------------------------------------------------------------------ */ + QString Xpl2Client::host () const { @@ -49,57 +55,55 @@ Xpl2Client::setHost (const QString &host) emit hostChanged (); } -quint16 -Xpl2Client::commandPort () const +bool +Xpl2Client::isConnected () const { - return m_commandPort; + return m_connected; } -void -Xpl2Client::setCommandPort (quint16 port) +bool +Xpl2Client::wireDebug () const { - if (m_commandPort == port) - return; - m_commandPort = port; - emit commandPortChanged (); + return m_wireDebug; } -quint16 -Xpl2Client::imagingPort () const +void +Xpl2Client::setWireDebug (bool enabled) { - return m_imagingPort; + if (m_wireDebug == enabled) + return; + m_wireDebug = enabled; + emit wireDebugChanged (); } -void -Xpl2Client::setImagingPort (quint16 port) +int +Xpl2Client::controllerId () const { - if (m_imagingPort == port) - return; - m_imagingPort = port; - emit imagingPortChanged (); + return m_controllerId; } -quint16 -Xpl2Client::statusPort () const +QString +Xpl2Client::firmwareVersion () const { - return m_statusPort; + return m_firmwareVersion; } -void -Xpl2Client::setStatusPort (quint16 port) +QString +Xpl2Client::hardwareVersion () const { - if (m_statusPort == port) - return; - m_statusPort = port; - emit statusPortChanged (); + return m_hardwareVersion; } -bool -Xpl2Client::isConnected () const +int +Xpl2Client::printheadCount () const { - return m_connected; + return m_printheadCount; } +/* ------------------------------------------------------------------ */ +/* Connection */ +/* ------------------------------------------------------------------ */ + void Xpl2Client::connectToServer () { @@ -108,11 +112,7 @@ Xpl2Client::connectToServer () emit statusMessage (QStringLiteral ("Already connected")); return; } - emit statusMessage (QStringLiteral ("Connecting to %1 (ports %2/%3/%4)…") - .arg (m_host) - .arg (m_commandPort) - .arg (m_imagingPort) - .arg (m_statusPort)); + emit statusMessage (QStringLiteral ("Connecting to %1…").arg (m_host)); m_commandSocket.connectToHost (m_host, m_commandPort); m_imagingSocket.connectToHost (m_host, m_imagingPort); m_statusSocket.connectToHost (m_host, m_statusPort); @@ -127,38 +127,96 @@ Xpl2Client::disconnectFromServer () } void -Xpl2Client::sendToCommandSocket (const QString &message) +Xpl2Client::getJcVersion () +{ + sendCommand (m_commandSocket, "GS_JC_VERSION"); +} + +/* ------------------------------------------------------------------ */ +/* Send / dispatch */ +/* ------------------------------------------------------------------ */ + +void +Xpl2Client::sendCommand (QTcpSocket &socket, const QByteArray &command, + const QVariantList ¶ms) +{ + if (socket.state () != QAbstractSocket::ConnectedState) + { + emit errorOccurred (QStringLiteral ("Socket not connected for %1") + .arg (QString::fromUtf8 (command))); + return; + } + QByteArray data = Xpl2Protocol::buildMessage (command, params); + socket.write (data); + + if (m_wireDebug) + emit wireDebugMessage (QStringLiteral ("TX [%1:%2] %3") + .arg (socket.peerAddress ().toString ()) + .arg (socket.peerPort ()) + .arg (QString::fromUtf8 (data).trimmed ())); +} + +void +Xpl2Client::dispatchCommandMessage (const Xpl2Protocol::ParsedMessage &msg) +{ + if (msg.command == "KA_PING") + { + handleKaPing (m_commandSocket); + return; + } + if (msg.command == "GS_JC_VERSION") + { + handleGsJcVersion (msg.params); + return; + } +} + +void +Xpl2Client::dispatchImagingMessage (const Xpl2Protocol::ParsedMessage &msg) { - sendToSocket (m_commandSocket, QStringLiteral ("Command"), message); + if (msg.command == "KA_PING") + { + handleKaPing (m_imagingSocket); + return; + } } void -Xpl2Client::sendToImagingSocket (const QString &message) +Xpl2Client::dispatchStatusMessage (const Xpl2Protocol::ParsedMessage &msg) { - sendToSocket (m_imagingSocket, QStringLiteral ("Imaging"), message); + if (msg.command == "KA_PING") + { + handleKaPing (m_statusSocket); + return; + } } void -Xpl2Client::sendToStatusSocket (const QString &message) +Xpl2Client::handleKaPing (QTcpSocket &socket) { - sendToSocket (m_statusSocket, QStringLiteral ("Status"), message); + qDebug () << "KA_PING received, replying"; + sendCommand (socket, "KA_PING", { 1 }); } void -Xpl2Client::sendToSocket (QTcpSocket &socket, const QString &label, - const QString &message) +Xpl2Client::handleGsJcVersion (const QVariantList ¶ms) { - if (socket.state () != QAbstractSocket::ConnectedState) + if (params.size () < 4) { - emit errorOccurred ( - QStringLiteral ("%1 socket not connected").arg (label)); + qWarning () << "GS_JC_VERSION: expected 4 params, got" << params.size (); return; } - QByteArray data = message.toUtf8 () + '\n'; - socket.write (data); - emit statusMessage (QStringLiteral ("Sent [%1]: %2").arg (label, message)); + m_controllerId = params[0].toInt (); + m_firmwareVersion = params[1].toString (); + m_hardwareVersion = params[2].toString (); + m_printheadCount = params[3].toInt (); + emit jcVersionReceived (); } +/* ------------------------------------------------------------------ */ +/* Internal */ +/* ------------------------------------------------------------------ */ + void Xpl2Client::updateConnectedState () { @@ -172,6 +230,10 @@ Xpl2Client::updateConnectedState () emit connectedChanged (); } +/* ------------------------------------------------------------------ */ +/* Socket slots — command */ +/* ------------------------------------------------------------------ */ + void Xpl2Client::onCommandSocketConnected () { @@ -187,13 +249,21 @@ Xpl2Client::onCommandSocketDisconnected () } void -Xpl2Client::onCommandSocketReadyRead () +Xpl2Client::onCommandSocketMessageReady () { while (m_commandSocket.canReadLine ()) { - QString line - = QString::fromUtf8 (m_commandSocket.readLine ()).trimmed (); - emit commandSocketResponseReceived (line); + QByteArray raw = m_commandSocket.readLine (); + if (m_wireDebug) + emit wireDebugMessage ( + QStringLiteral ("RX [%1:%2] %3") + .arg (m_commandSocket.peerAddress ().toString ()) + .arg (m_commandSocket.peerPort ()) + .arg (QString::fromUtf8 (raw).trimmed ())); + + auto msg = Xpl2Protocol::parseMessage (raw); + if (msg.valid) + dispatchCommandMessage (msg); } } @@ -205,6 +275,10 @@ Xpl2Client::onCommandSocketError (QAbstractSocket::SocketError error) QStringLiteral ("Command: %1").arg (m_commandSocket.errorString ())); } +/* ------------------------------------------------------------------ */ +/* Socket slots — imaging */ +/* ------------------------------------------------------------------ */ + void Xpl2Client::onImagingSocketConnected () { @@ -220,13 +294,21 @@ Xpl2Client::onImagingSocketDisconnected () } void -Xpl2Client::onImagingSocketReadyRead () +Xpl2Client::onImagingSocketMessageReady () { while (m_imagingSocket.canReadLine ()) { - QString line - = QString::fromUtf8 (m_imagingSocket.readLine ()).trimmed (); - emit imagingSocketResponseReceived (line); + QByteArray raw = m_imagingSocket.readLine (); + if (m_wireDebug) + emit wireDebugMessage ( + QStringLiteral ("RX [%1:%2] %3") + .arg (m_imagingSocket.peerAddress ().toString ()) + .arg (m_imagingSocket.peerPort ()) + .arg (QString::fromUtf8 (raw).trimmed ())); + + auto msg = Xpl2Protocol::parseMessage (raw); + if (msg.valid) + dispatchImagingMessage (msg); } } @@ -238,6 +320,10 @@ Xpl2Client::onImagingSocketError (QAbstractSocket::SocketError error) QStringLiteral ("Imaging: %1").arg (m_imagingSocket.errorString ())); } +/* ------------------------------------------------------------------ */ +/* Socket slots — status */ +/* ------------------------------------------------------------------ */ + void Xpl2Client::onStatusSocketConnected () { @@ -253,12 +339,21 @@ Xpl2Client::onStatusSocketDisconnected () } void -Xpl2Client::onStatusSocketReadyRead () +Xpl2Client::onStatusSocketMessageReady () { while (m_statusSocket.canReadLine ()) { - QString line = QString::fromUtf8 (m_statusSocket.readLine ()).trimmed (); - emit statusSocketResponseReceived (line); + QByteArray raw = m_statusSocket.readLine (); + if (m_wireDebug) + emit wireDebugMessage ( + QStringLiteral ("RX [%1:%2] %3") + .arg (m_statusSocket.peerAddress ().toString ()) + .arg (m_statusSocket.peerPort ()) + .arg (QString::fromUtf8 (raw).trimmed ())); + + auto msg = Xpl2Protocol::parseMessage (raw); + if (msg.valid) + dispatchStatusMessage (msg); } } diff --git a/src/Xpl2Client.h b/src/Xpl2Client.h index 7ce70ae..9212771 100644 --- a/src/Xpl2Client.h +++ b/src/Xpl2Client.h @@ -4,6 +4,8 @@ */ #pragma once +#include "Xpl2Protocol.h" + #include <QObject> #include <QQmlEngine> #include <QTcpSocket> @@ -15,13 +17,15 @@ class Xpl2Client : public QObject QML_SINGLETON Q_PROPERTY (QString host READ host WRITE setHost NOTIFY hostChanged) - Q_PROPERTY (quint16 commandPort READ commandPort WRITE setCommandPort NOTIFY - commandPortChanged) - Q_PROPERTY (quint16 imagingPort READ imagingPort WRITE setImagingPort NOTIFY - imagingPortChanged) - Q_PROPERTY (quint16 statusPort READ statusPort WRITE setStatusPort NOTIFY - statusPortChanged) Q_PROPERTY (bool connected READ isConnected NOTIFY connectedChanged) + Q_PROPERTY ( + bool wireDebug READ wireDebug WRITE setWireDebug NOTIFY wireDebugChanged) + Q_PROPERTY (int controllerId READ controllerId NOTIFY jcVersionReceived) + Q_PROPERTY ( + QString firmwareVersion READ firmwareVersion NOTIFY jcVersionReceived) + Q_PROPERTY ( + QString hardwareVersion READ hardwareVersion NOTIFY jcVersionReceived) + Q_PROPERTY (int printheadCount READ printheadCount NOTIFY jcVersionReceived) public: explicit Xpl2Client (QObject *parent = nullptr); @@ -29,52 +33,51 @@ public: QString host () const; void setHost (const QString &host); - quint16 commandPort () const; - void setCommandPort (quint16 port); - - quint16 imagingPort () const; - void setImagingPort (quint16 port); + bool isConnected () const; - quint16 statusPort () const; - void setStatusPort (quint16 port); + bool wireDebug () const; + void setWireDebug (bool enabled); - bool isConnected () const; + int controllerId () const; + QString firmwareVersion () const; + QString hardwareVersion () const; + int printheadCount () const; Q_INVOKABLE void connectToServer (); Q_INVOKABLE void disconnectFromServer (); - Q_INVOKABLE void sendToCommandSocket (const QString &message); - Q_INVOKABLE void sendToImagingSocket (const QString &message); - Q_INVOKABLE void sendToStatusSocket (const QString &message); + Q_INVOKABLE void getJcVersion (); signals: void hostChanged (); - void commandPortChanged (); - void imagingPortChanged (); - void statusPortChanged (); void connectedChanged (); - void commandSocketResponseReceived (const QString &response); - void imagingSocketResponseReceived (const QString &response); - void statusSocketResponseReceived (const QString &response); + void wireDebugChanged (); void errorOccurred (const QString &error); void statusMessage (const QString &message); + void wireDebugMessage (const QString &message); + void jcVersionReceived (); private slots: void onCommandSocketConnected (); void onCommandSocketDisconnected (); - void onCommandSocketReadyRead (); + void onCommandSocketMessageReady (); void onCommandSocketError (QAbstractSocket::SocketError error); void onImagingSocketConnected (); void onImagingSocketDisconnected (); - void onImagingSocketReadyRead (); + void onImagingSocketMessageReady (); void onImagingSocketError (QAbstractSocket::SocketError error); void onStatusSocketConnected (); void onStatusSocketDisconnected (); - void onStatusSocketReadyRead (); + void onStatusSocketMessageReady (); void onStatusSocketError (QAbstractSocket::SocketError error); private: - void sendToSocket (QTcpSocket &socket, const QString &label, - const QString &message); + void sendCommand (QTcpSocket &socket, const QByteArray &command, + const QVariantList ¶ms = {}); + void dispatchCommandMessage (const Xpl2Protocol::ParsedMessage &msg); + void dispatchImagingMessage (const Xpl2Protocol::ParsedMessage &msg); + void dispatchStatusMessage (const Xpl2Protocol::ParsedMessage &msg); + void handleKaPing (QTcpSocket &socket); + void handleGsJcVersion (const QVariantList ¶ms); void updateConnectedState (); QTcpSocket m_commandSocket; @@ -85,4 +88,9 @@ private: quint16 m_imagingPort = 9111; quint16 m_statusPort = 9112; bool m_connected = false; + bool m_wireDebug = false; + int m_controllerId = 0; + QString m_firmwareVersion; + QString m_hardwareVersion; + int m_printheadCount = 0; }; diff --git a/src/Xpl2Protocol.cpp b/src/Xpl2Protocol.cpp new file mode 100644 index 0000000..9ee7f4f --- /dev/null +++ b/src/Xpl2Protocol.cpp @@ -0,0 +1,128 @@ +/** + * @file Xpl2Protocol.cpp + * @brief Wire framing for the XPL2 printhead protocol. + */ +#include "Xpl2Protocol.h" + +#include <QMetaType> + +namespace Xpl2Protocol +{ + +QByteArray +buildMessage (const QByteArray &command, const QVariantList ¶ms) +{ + QByteArray msg = command; + + for (const QVariant &p : params) + { + msg += ','; + + switch (p.typeId ()) + { + case QMetaType::Bool: + msg += p.toBool () ? '1' : '0'; + break; + case QMetaType::Int: + case QMetaType::LongLong: + msg += QByteArray::number (p.toLongLong ()); + break; + case QMetaType::UInt: + case QMetaType::ULongLong: + msg += QByteArray::number (p.toULongLong ()); + break; + case QMetaType::Float: + case QMetaType::Double: + msg += QByteArray::number (p.toDouble (), 'g', 10); + break; + default: + msg += '"'; + msg += p.toString ().toUtf8 (); + msg += '"'; + break; + } + } + + msg += Terminator; + return msg; +} + +static QList<QByteArray> +splitFields (const QByteArray &data) +{ + QList<QByteArray> fields; + QByteArray current; + bool inQuotes = false; + + for (int i = 0; i < data.size (); ++i) + { + char c = data.at (i); + + if (c == '"') + { + inQuotes = !inQuotes; + current += c; + } + else if (c == ',' && !inQuotes) + { + fields.append (current); + current.clear (); + } + else + { + current += c; + } + } + + if (!current.isEmpty ()) + fields.append (current); + + return fields; +} + +static QVariant +parseField (const QByteArray &field) +{ + if (field.size () >= 2 && field.front () == '"' && field.back () == '"') + return QString::fromUtf8 (field.mid (1, field.size () - 2)); + + bool ok = false; + + int intVal = field.toInt (&ok); + if (ok) + return intVal; + + double dblVal = field.toDouble (&ok); + if (ok) + return dblVal; + + return QString::fromUtf8 (field); +} + +ParsedMessage +parseMessage (const QByteArray &raw) +{ + QByteArray data = raw; + + /* Strip terminator. */ + while (!data.isEmpty () && data.back () == Terminator) + data.chop (1); + + if (data.isEmpty ()) + return {}; + + QList<QByteArray> fields = splitFields (data); + if (fields.isEmpty ()) + return {}; + + ParsedMessage msg; + msg.command = fields.takeFirst (); + msg.valid = true; + + for (const QByteArray &f : fields) + msg.params.append (parseField (f)); + + return msg; +} + +} // namespace Xpl2Protocol diff --git a/src/Xpl2Protocol.h b/src/Xpl2Protocol.h new file mode 100644 index 0000000..6d4a348 --- /dev/null +++ b/src/Xpl2Protocol.h @@ -0,0 +1,27 @@ +/** + * @file Xpl2Protocol.h + * @brief Wire framing for the XPL2 printhead protocol. + */ +#pragma once + +#include <QByteArray> +#include <QVariantList> + +namespace Xpl2Protocol +{ + +constexpr char Terminator = '\n'; + +struct ParsedMessage +{ + QByteArray command; + QVariantList params; + bool valid = false; +}; + +QByteArray buildMessage (const QByteArray &command, + const QVariantList ¶ms = {}); + +ParsedMessage parseMessage (const QByteArray &raw); + +} // namespace Xpl2Protocol |
