aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--demo/Main.qml175
-rw-r--r--mock-server/EchoServer.cpp61
-rw-r--r--mock-server/EchoServer.h13
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/Xpl2Client.cpp221
-rw-r--r--src/Xpl2Client.h64
-rw-r--r--src/Xpl2Protocol.cpp128
-rw-r--r--src/Xpl2Protocol.h27
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 &params)
+{
+ 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 &params)
{
- 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 &params = {});
+ 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 &params);
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 &params)
+{
+ 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 &params = {});
+
+ParsedMessage parseMessage (const QByteArray &raw);
+
+} // namespace Xpl2Protocol