From a2b1ccf17845e55caef7f69a5e68f49a55b6a166 Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Fri, 13 Mar 2026 11:47:00 +0100 Subject: 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. --- src/Xpl2Protocol.cpp | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/Xpl2Protocol.cpp (limited to 'src/Xpl2Protocol.cpp') 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 + +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 +splitFields (const QByteArray &data) +{ + QList 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 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 -- cgit v1.2.3