aboutsummaryrefslogtreecommitdiffstats
path: root/mock-jetting-controller/MockServer.cpp
diff options
context:
space:
mode:
authorThomas Vanbesien <tvanbesi@proton.me>2026-03-23 16:48:32 +0100
committerThomas Vanbesien <tvanbesi@proton.me>2026-03-23 16:48:32 +0100
commit8bcf948b76c9564cb38d3611228ccaf73890a548 (patch)
tree5d7bc5aec767ff7911c067d1bc137f6905919c91 /mock-jetting-controller/MockServer.cpp
parent61debe99a269bf7e87f6ba2f8d2a376e619fcf12 (diff)
downloadQtXpl2-8bcf948b76c9564cb38d3611228ccaf73890a548.tar.gz
QtXpl2-8bcf948b76c9564cb38d3611228ccaf73890a548.zip
Rename demo/ → jetting-interface/, mock-server/ → mock-jetting-controller/
Executables: QtXpl2Demo → JettingInterfaceDemo, Xpl2MockServer → MockJettingController. Dev log prefixes: "Demo" → "JI", "MockServer" → "MockJC". Window title → "Jetting Interface".
Diffstat (limited to 'mock-jetting-controller/MockServer.cpp')
-rw-r--r--mock-jetting-controller/MockServer.cpp541
1 files changed, 541 insertions, 0 deletions
diff --git a/mock-jetting-controller/MockServer.cpp b/mock-jetting-controller/MockServer.cpp
new file mode 100644
index 0000000..08eb116
--- /dev/null
+++ b/mock-jetting-controller/MockServer.cpp
@@ -0,0 +1,541 @@
+/**
+ * @file MockServer.cpp
+ * @brief Mock XPL2 server — connects out to the client on three protocol
+ * ports.
+ */
+#include "MockServer.h"
+
+bool MockServer::s_wireDebug = false;
+
+void
+MockServer::enableWireDebug ()
+{
+ s_wireDebug = true;
+}
+
+MockServer::MockServer (const QString &host, QObject *parent)
+ : QObject (parent), m_host (host)
+{
+ setupPort (m_command, "Command", 9110);
+ setupPort (m_imaging, "Imaging", 9111);
+ setupPort (m_status, "Status", 9112);
+
+ connect (&m_tickTimer, &QTimer::timeout, this, &MockServer::tick);
+ m_tickTimer.start (1000);
+
+ connect (&m_jcStatusTimer, &QTimer::timeout, this,
+ &MockServer::sendJcStatusMsg);
+ connect (&m_phStatusTimer, &QTimer::timeout, this,
+ &MockServer::sendPhStatusMsg);
+
+ connectAll ();
+}
+
+void
+MockServer::setupPort (Port &port, const char *name, quint16 number)
+{
+ port.name = name;
+ port.number = number;
+
+ connect (&port.socket, &QTcpSocket::connected, this,
+ &MockServer::onSocketConnected);
+ connect (&port.socket, &QTcpSocket::disconnected, this,
+ &MockServer::onSocketDisconnected);
+ connect (&port.socket, &QTcpSocket::readyRead, this,
+ &MockServer::onSocketMessageReady);
+ connect (&port.socket, &QAbstractSocket::errorOccurred, this,
+ [this, &port] (QAbstractSocket::SocketError)
+ {
+ qDebug ("%s Connection error: %s",
+ qPrintable (logTag (&port.socket)),
+ qPrintable (port.socket.errorString ()));
+ });
+}
+
+void
+MockServer::connectAll ()
+{
+ for (auto *port : { &m_command, &m_imaging, &m_status })
+ if (port->socket.state () == QAbstractSocket::UnconnectedState)
+ port->socket.connectToHost (m_host, port->number);
+}
+
+QString
+MockServer::logTag (const QTcpSocket *socket) const
+{
+ const char *name = "Unknown";
+ quint16 number = 0;
+ if (socket == &m_command.socket)
+ {
+ name = m_command.name;
+ number = m_command.number;
+ }
+ else if (socket == &m_imaging.socket)
+ {
+ name = m_imaging.name;
+ number = m_imaging.number;
+ }
+ else if (socket == &m_status.socket)
+ {
+ name = m_status.name;
+ number = m_status.number;
+ }
+
+ /* Fixed-width tag: "[Command:9110]" = 14 chars, left-padded to 15. */
+ return QStringLiteral ("[%1:%2]").arg (name).arg (number).leftJustified (15);
+}
+
+void
+MockServer::onSocketConnected ()
+{
+ auto *socket = qobject_cast<QTcpSocket *> (sender ());
+ qInfo ("%s Connected to client", qPrintable (logTag (socket)));
+}
+
+void
+MockServer::onSocketDisconnected ()
+{
+ auto *socket = qobject_cast<QTcpSocket *> (sender ());
+ qInfo ("%s Disconnected from client", qPrintable (logTag (socket)));
+}
+
+void
+MockServer::onSocketMessageReady ()
+{
+ auto *socket = qobject_cast<QTcpSocket *> (sender ());
+ Port *port = nullptr;
+ if (socket == &m_command.socket)
+ port = &m_command;
+ else if (socket == &m_imaging.socket)
+ port = &m_imaging;
+ else
+ port = &m_status;
+
+ while (socket->canReadLine ())
+ {
+ QByteArray line = socket->readLine ();
+ handleCommand (*port, line);
+ }
+}
+
+void
+MockServer::tick ()
+{
+ /* Send KA_PING on connected sockets, retry connection on disconnected. */
+ for (auto *port : { &m_command, &m_imaging, &m_status })
+ {
+ if (port->socket.state () == QAbstractSocket::ConnectedState)
+ sendReply (port->socket, "KA_PING\n");
+ else if (port->socket.state () == QAbstractSocket::UnconnectedState)
+ port->socket.connectToHost (m_host, port->number);
+ }
+}
+
+void
+MockServer::handleCommand (Port &port, const QByteArray &line)
+{
+ QByteArray trimmed = line.trimmed ();
+ if (trimmed.isEmpty ())
+ return;
+
+ /* Split on first comma to get command token and remaining params. */
+ int comma = trimmed.indexOf (',');
+ QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed;
+ QByteArray params = (comma >= 0) ? trimmed.mid (comma + 1) : QByteArray ();
+
+ if (cmd == "KA_PING")
+ handleKaPing (port, params);
+ else if (cmd == "GS_JC_VERSION")
+ handleGsJcVersion (port.socket);
+ else if (cmd == "GS_PH_VERSION")
+ handleGsPhVersion (port.socket, params);
+ /* CN_ JC success shape (no params needed) */
+ else if (cmd == "CN_JETTING_ALL_ON" || cmd == "CN_JETTING_ON"
+ || cmd == "CN_JETTING_OFF" || cmd == "CN_JC_ID_LED_ON"
+ || cmd == "CN_JC_ID_LED_OFF" || cmd == "CN_JC_CALIBRATION"
+ || cmd == "CN_JC_RESET_FAULT_CODES")
+ handleCnJcSuccess (port.socket, cmd);
+ /* CN_ PH success shape (params carry printheadId) */
+ else if (cmd == "CN_PH_JETTING_ON" || cmd == "CN_PH_JETTING_OFF"
+ || cmd == "CN_PH_ID_LED_ON" || cmd == "CN_PH_ID_LED_OFF"
+ || cmd == "CN_PH_CALIBRATION" || cmd == "CN_PH_RESET_FAULT_CODES"
+ || cmd == "CN_PH_NOZZLES_DISABLED")
+ handleCnPhSuccess (port.socket, cmd, params);
+ /* CN_ custom-shape commands */
+ else if (cmd == "CN_PH_CALIBRATION_DATA")
+ handleCnPhCalibrationData (port.socket, params);
+ else if (cmd == "CN_PH_CALIBRATION_RAW_DATA")
+ handleCnPhCalibrationRawData (port.socket, params);
+ else if (cmd == "CN_PH_CALIBRATED_BASE_FREQUENCY")
+ handleCnPhCalibratedBaseFrequency (port.socket, params);
+ /* CN_ status messaging (arrives on status port) */
+ else if (cmd == "CN_JC_STATUS_MESSAGING_START"
+ || cmd == "CN_PH_STATUS_MESSAGING_START")
+ handleCnStatusMessagingStart (port.socket, cmd, params);
+ else if (cmd == "CN_JC_STATUS_MESSAGING_STOP"
+ || cmd == "CN_PH_STATUS_MESSAGING_STOP")
+ handleCnStatusMessagingStop (port.socket, cmd);
+ /* CF_ JC success shape */
+ else if (cmd == "CF_PH_DEASSIGN_ID" || cmd == "CF_JC_SAVE_CALIBRATION"
+ || cmd == "CF_JC_RESET_CALIBRATION"
+ || cmd == "CF_JC_SWITCH_OFF_PURGE"
+ || cmd == "CF_JC_RESET_SETTINGS_ALL_PRINTHEADS"
+ || cmd == "CF_JC_REBOOT_ALL_PRINTHEADS"
+ || cmd == "CF_JC_RESET_CONTROLLER_SOFTWARE"
+ || cmd == "CF_JC_RESTART" || cmd == "CF_JC_SHUTDOWN"
+ || cmd == "CF_JC_SAVE_ALL_PRINTHEAD_SETTINGS")
+ handleCnJcSuccess (port.socket, cmd);
+ /* CF_ PH success shape */
+ else if (cmd == "CF_PH_SET_ID" || cmd == "CF_PH_SAVE_CALIBRATION"
+ || cmd == "CF_PH_RESET_CALIBRATION"
+ || cmd == "CF_PH_RESET_ALL_SETTINGS" || cmd == "CF_PH_REBOOT"
+ || cmd == "CF_PH_SAVE_SETTINGS")
+ handleCnPhSuccess (port.socket, cmd, params);
+ /* CF_ custom-shape commands */
+ else if (cmd == "CF_JC_SET_PURGE_SETTINGS")
+ handleCfJcSetPurgeSettings (port.socket, params);
+ else if (cmd == "CF_JC_SET_JETTING_PARAMS")
+ handleCfJcSetJettingParams (port.socket, params);
+ else if (cmd == "CF_PH_SET_JETTING_PARAMS")
+ handleCfPhJettingParams (port.socket, cmd, params);
+ else if (cmd == "CF_PH_GET_JETTING_PARAMS")
+ handleCfPhJettingParams (port.socket, cmd, params);
+ else if (cmd == "CF_JC_SETTER")
+ handleCfJcSetter (port.socket, params);
+ else if (cmd == "CF_PH_SETTER")
+ handleCfPhSetter (port.socket, params);
+ else if (cmd == "CF_JC_GETTER")
+ handleCfJcGetter (port.socket, params);
+ else if (cmd == "CF_PH_GETTER")
+ handleCfPhGetter (port.socket, params);
+ /* Imaging commands (port 2) */
+ else if (cmd == "m2")
+ handleImagingStart (port.socket, params);
+ else if (cmd == "m4")
+ handleImagingStop (port.socket);
+ else if (cmd == "m0" || cmd == "m5")
+ handleImagingMaskStart (port.socket, cmd, params);
+ else if (cmd == "m1" || cmd == "m6")
+ handleImagingMaskEnd (port.socket, cmd, params);
+ else if (cmd == "m3")
+ handleImageCount (port.socket);
+ else
+ qWarning ("%s Unknown command: %s", qPrintable (logTag (&port.socket)),
+ cmd.constData ());
+}
+
+void
+MockServer::sendReply (QTcpSocket &socket, const QByteArray &data)
+{
+ socket.write (data);
+ QByteArray trimmed = data.trimmed ();
+ int comma = trimmed.indexOf (',');
+ QByteArray cmd = (comma >= 0) ? trimmed.left (comma) : trimmed;
+ QByteArray wire;
+ if (s_wireDebug)
+ wire = " >> " + trimmed;
+ qDebug ("%s TX %s%s", qPrintable (logTag (&socket)), cmd.constData (),
+ wire.constData ());
+}
+
+void
+MockServer::handleKaPing (Port &port, const QByteArray &params)
+{
+ QByteArray wire;
+ if (s_wireDebug)
+ wire = " << KA_PING," + params;
+ qDebug ("%s RX KA_PING ACK%s", qPrintable (logTag (&port.socket)),
+ wire.constData ());
+}
+
+void
+MockServer::handleGsJcVersion (QTcpSocket &socket)
+{
+ qDebug ("%s RX GS_JC_VERSION", qPrintable (logTag (&socket)));
+ sendReply (socket, "GS_JC_VERSION,1,\"1.05\",\"2.00\",15\n");
+}
+
+void
+MockServer::handleGsPhVersion (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX GS_PH_VERSION,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ /* Echo back canned version info for whatever printhead ID was requested. */
+ sendReply (socket, QByteArray ("GS_PH_VERSION,1,") + params
+ + ",\"3.10\",\"1.00\",\"Standard\","
+ "\"2.05\",\"1.02\",\"0.9.1\"\n");
+}
+
+void
+MockServer::handleCnJcSuccess (QTcpSocket &socket, const QByteArray &cmd)
+{
+ qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ());
+ sendReply (socket, cmd + ",1,1\n");
+}
+
+void
+MockServer::handleCnPhSuccess (QTcpSocket &socket, const QByteArray &cmd,
+ const QByteArray &params)
+{
+ qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (),
+ params.constData ());
+ /* Extract printhead ID (first field before any comma). */
+ int comma = params.indexOf (',');
+ QByteArray phId = (comma >= 0) ? params.left (comma) : params;
+ sendReply (socket, cmd + ",1," + phId + ",1\n");
+}
+
+void
+MockServer::handleCnPhCalibrationData (QTcpSocket &socket,
+ const QByteArray &params)
+{
+ qDebug ("%s RX CN_PH_CALIBRATION_DATA,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ /* Reply: controllerId, phId, then 48 floats (-1 = uncalibrated). */
+ QByteArray reply = "CN_PH_CALIBRATION_DATA,1," + params;
+ for (int i = 0; i < 48; ++i)
+ reply += ",-1";
+ reply += '\n';
+ sendReply (socket, reply);
+}
+
+void
+MockServer::handleCnPhCalibrationRawData (QTcpSocket &socket,
+ const QByteArray &params)
+{
+ qDebug ("%s RX CN_PH_CALIBRATION_RAW_DATA,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ QByteArray reply = "CN_PH_CALIBRATION_RAW_DATA,1," + params;
+ for (int i = 0; i < 48; ++i)
+ reply += ",-1";
+ reply += '\n';
+ sendReply (socket, reply);
+}
+
+void
+MockServer::handleCnPhCalibratedBaseFrequency (QTcpSocket &socket,
+ const QByteArray &params)
+{
+ qDebug ("%s RX CN_PH_CALIBRATED_BASE_FREQUENCY,%s",
+ qPrintable (logTag (&socket)), params.constData ());
+ sendReply (socket,
+ "CN_PH_CALIBRATED_BASE_FREQUENCY,1," + params + ",10.0,10.0\n");
+}
+
+void
+MockServer::handleCnStatusMessagingStart (QTcpSocket &socket,
+ const QByteArray &cmd,
+ const QByteArray &params)
+{
+ qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (),
+ params.constData ());
+
+ /* Parse level and interval from params: "level,interval" */
+ QList<QByteArray> parts = params.split (',');
+ int level = parts.size () > 0 ? parts[0].toInt () : 1;
+ int interval = parts.size () > 1 ? parts[1].toInt () : 1000;
+
+ if (cmd == "CN_JC_STATUS_MESSAGING_START")
+ {
+ m_jcStatusLevel = level;
+ m_jcStatusTimer.start (interval);
+ }
+ else
+ {
+ m_phStatusLevel = level;
+ m_phStatusTimer.start (interval);
+ }
+
+ sendReply (socket, cmd + ",1," + params + ",1\n");
+}
+
+void
+MockServer::handleCnStatusMessagingStop (QTcpSocket &socket,
+ const QByteArray &cmd)
+{
+ qDebug ("%s RX %s", qPrintable (logTag (&socket)), cmd.constData ());
+
+ if (cmd == "CN_JC_STATUS_MESSAGING_STOP")
+ m_jcStatusTimer.stop ();
+ else
+ m_phStatusTimer.stop ();
+
+ sendReply (socket, cmd + ",1,1\n");
+}
+
+void
+MockServer::handleCfJcSetPurgeSettings (QTcpSocket &socket,
+ const QByteArray &params)
+{
+ qDebug ("%s RX CF_JC_SET_PURGE_SETTINGS,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_JC_SET_PURGE_SETTINGS,1," + params + ",1\n");
+}
+
+void
+MockServer::handleCfJcSetJettingParams (QTcpSocket &socket,
+ const QByteArray &params)
+{
+ qDebug ("%s RX CF_JC_SET_JETTING_PARAMS,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_JC_SET_JETTING_PARAMS,1," + params + ",1\n");
+}
+
+void
+MockServer::handleCfPhJettingParams (QTcpSocket &socket, const QByteArray &cmd,
+ const QByteArray &params)
+{
+ qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (),
+ params.constData ());
+ if (cmd == "CF_PH_GET_JETTING_PARAMS")
+ sendReply (socket, cmd + ",1," + params + ",0,0,0,0,0,1\n");
+ else
+ sendReply (socket, cmd + ",1," + params + ",1\n");
+}
+
+void
+MockServer::handleCfJcSetter (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX CF_JC_SETTER,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_JC_SETTER,1," + params + ",1\n");
+}
+
+void
+MockServer::handleCfPhSetter (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX CF_PH_SETTER,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_PH_SETTER,1," + params + ",1\n");
+}
+
+void
+MockServer::handleCfJcGetter (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX CF_JC_GETTER,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_JC_GETTER,1," + params + ",\"0\",1\n");
+}
+
+void
+MockServer::handleCfPhGetter (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX CF_PH_GETTER,%s", qPrintable (logTag (&socket)),
+ params.constData ());
+ sendReply (socket, "CF_PH_GETTER,1," + params + ",\"0\",1\n");
+}
+
+void
+MockServer::sendJcStatusMsg ()
+{
+ if (m_status.socket.state () != QAbstractSocket::ConnectedState)
+ return;
+
+ static int tick = 0;
+ ++tick;
+ bool odd = (tick % 2) != 0;
+
+ /* Alternate between two value sets so the demo visually updates.
+ Level 1: cid, level, cpu, rail5v, canBus8v, temp, humidity, busCurrent,
+ onTimeSeconds */
+ QByteArray level = QByteArray::number (m_jcStatusLevel);
+ QByteArray base = "EV_STATUS_MSG_JC,1," + level;
+ if (odd)
+ base += ",42.5,4.98,7.95,35.2,55.0,0.12,3600";
+ else
+ base += ",58.3,5.02,8.05,37.8,48.5,0.15,3660";
+
+ if (m_jcStatusLevel >= 2)
+ {
+ if (odd)
+ base += ",\"192.168.1.100\",23.5,1,1,1,3,\"1.05\",\"2.00\""
+ ",0,0,0,0,0,0";
+ else
+ base += ",\"192.168.1.100\",24.1,1,1,1,3,\"1.05\",\"2.00\""
+ ",1,0,1,0,1,0";
+ }
+ base += '\n';
+
+ sendReply (m_status.socket, base);
+}
+
+void
+MockServer::sendPhStatusMsg ()
+{
+ if (m_status.socket.state () != QAbstractSocket::ConnectedState)
+ return;
+
+ static int tick = 0;
+ ++tick;
+ bool odd = (tick % 2) != 0;
+
+ /* Alternate between two value sets.
+ Level 1: cid, level, phId, temp, humidity, mcuTemp, pdsV, mdsV, sysV,
+ eFuseI, nozzleI, vdd, 13×trip bools, dutyCycle, pwmFreq, drive,
+ nozzleDriveFreq, nozzleDriveDutyCycle, onTimeSeconds */
+ QByteArray level = QByteArray::number (m_phStatusLevel);
+ QByteArray base = "EV_STATUS_MSG_PH,1," + level + ",1";
+ if (odd)
+ base += ",38.5,45.0,40.2,36.0,24.0,3.3,0.05,0.02,3.3"
+ ",0,0,0,0,0,0,0,0,0,0,0,0,0"
+ ",50.0,10000.0,3,8000.0,50.0,1800";
+ else
+ base += ",41.2,42.0,43.1,35.5,23.8,3.28,0.06,0.03,3.31"
+ ",0,0,0,0,0,0,0,0,0,0,0,0,0"
+ ",55.0,10500.0,4,8200.0,52.0,1860";
+
+ if (m_phStatusLevel >= 2)
+ {
+ if (odd)
+ base += ",0,\"MCU001\",\"FLASH001\",12345"
+ ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\""
+ ",100.0,45.0,20.0,36.0,30.0,18.0,2.0,\"1.00\""
+ ",0,0,0,0,0,0,0,0,0,0,0,0,0,0";
+ else
+ base += ",0,\"MCU001\",\"FLASH001\",12345"
+ ",\"1.00\",\"3.10\",\"Standard\",\"1.00\",\"2.05\",\"0.9.1\""
+ ",100.0,45.0,20.0,35.5,30.0,18.0,2.0,\"1.00\""
+ ",1,-1,2,0,0,0,0,0,0,0,0,0,0,0";
+ }
+ base += '\n';
+
+ sendReply (m_status.socket, base);
+}
+
+void
+MockServer::handleImagingStart (QTcpSocket &socket, const QByteArray &params)
+{
+ qDebug ("%s RX m2,%s", qPrintable (logTag (&socket)), params.constData ());
+ sendReply (socket, "n,\"A\"\n");
+}
+
+void
+MockServer::handleImagingStop (QTcpSocket &socket)
+{
+ qDebug ("%s RX m4", qPrintable (logTag (&socket)));
+ sendReply (socket, "m4,1\n");
+}
+
+void
+MockServer::handleImagingMaskStart (QTcpSocket &socket, const QByteArray &cmd,
+ const QByteArray &params)
+{
+ qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (),
+ params.constData ());
+}
+
+void
+MockServer::handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd,
+ const QByteArray &params)
+{
+ qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (),
+ params.constData ());
+ sendReply (socket, "n,\"A\"\n");
+}
+
+void
+MockServer::handleImageCount (QTcpSocket &socket)
+{
+ qDebug ("%s RX m3", qPrintable (logTag (&socket)));
+ sendReply (socket, "n,\"0\"\n");
+}