aboutsummaryrefslogtreecommitdiffstats
path: root/src/Xpl2Protocol.cpp
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-13 11:47:00 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-13 11:47:00 +0100
commita2b1ccf17845e55caef7f69a5e68f49a55b6a166 (patch)
treea7e8c414dbadeeb9bcac29478cf3fbf7e99a4a05 /src/Xpl2Protocol.cpp
parent34faf3cdea798c1948229ec1bb53c828e2b40bb7 (diff)
downloadQtXpl2-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.
Diffstat (limited to 'src/Xpl2Protocol.cpp')
-rw-r--r--src/Xpl2Protocol.cpp128
1 files changed, 128 insertions, 0 deletions
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