diff options
Diffstat (limited to 'src')
| -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 |
5 files changed, 351 insertions, 91 deletions
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 |
