/** * @file MockServer.cpp * @brief Mock XPL2 server — listens on all three protocol ports. */ #include "MockServer.h" #include bool MockServer::s_wireDebug = false; void MockServer::enableWireDebug () { s_wireDebug = true; } MockServer::MockServer (QObject *parent) : QObject (parent) { setupPort (m_command, "Command", 9110); setupPort (m_imaging, "Imaging", 9111); setupPort (m_status, "Status", 9112); connect (&m_pingTimer, &QTimer::timeout, this, &MockServer::sendKaPing); m_pingTimer.start (1000); } void MockServer::setupPort (Port &port, const char *name, quint16 number) { port.name = name; port.number = number; connect (&port.server, &QTcpServer::newConnection, this, &MockServer::onNewConnection); if (!port.server.listen (QHostAddress::Any, number)) qCritical ("%s Failed to listen: %s", qPrintable (logTag (number)), qPrintable (port.server.errorString ())); else qInfo ("%s Listening", qPrintable (logTag (number))); } QString MockServer::logTag (quint16 localPort) const { const char *name = "Unknown"; if (localPort == m_command.number) name = m_command.name; else if (localPort == m_imaging.number) name = m_imaging.name; else if (localPort == m_status.number) name = m_status.name; /* Fixed-width tag: "[Command:9110]" = 14 chars, left-padded to 15. */ return QStringLiteral ("[%1:%2]").arg (name).arg (localPort).leftJustified ( 15); } void MockServer::onNewConnection () { auto *server = qobject_cast (sender ()); while (auto *sock = server->nextPendingConnection ()) { quint16 lp = sock->localPort (); qInfo ("%s Client connected", qPrintable (logTag (lp))); m_clients.append (sock); connect (sock, &QTcpSocket::readyRead, this, &MockServer::onClientMessageReady); connect (sock, &QTcpSocket::disconnected, this, &MockServer::onClientDisconnected); } } void MockServer::onClientMessageReady () { auto *sock = qobject_cast (sender ()); while (sock->canReadLine ()) { QByteArray line = sock->readLine (); handleCommand (sock, line); } } void MockServer::onClientDisconnected () { auto *sock = qobject_cast (sender ()); quint16 lp = sock->localPort (); qInfo ("%s Client disconnected", qPrintable (logTag (lp))); m_clients.removeOne (sock); sock->deleteLater (); } void MockServer::sendKaPing () { for (auto *client : m_clients) { if (client->state () == QAbstractSocket::ConnectedState) sendReply (client, "KA_PING\n"); } } void MockServer::handleCommand (QTcpSocket *client, 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 (client, params); else if (cmd == "GS_JC_VERSION") handleGsJcVersion (client); else if (cmd == "GS_PH_VERSION") handleGsPhVersion (client, 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 (client, 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 (client, cmd, params); /* CN_ custom-shape commands */ else if (cmd == "CN_PH_CALIBRATION_DATA") handleCnPhCalibrationData (client, params); else if (cmd == "CN_PH_CALIBRATION_RAW_DATA") handleCnPhCalibrationRawData (client, params); else if (cmd == "CN_PH_CALIBRATED_BASE_FREQUENCY") handleCnPhCalibratedBaseFrequency (client, params); /* CN_ status messaging (arrives on status port) */ else if (cmd == "CN_JC_STATUS_MESSAGING_START" || cmd == "CN_PH_STATUS_MESSAGING_START") handleCnStatusMessagingStart (client, cmd, params); else if (cmd == "CN_JC_STATUS_MESSAGING_STOP" || cmd == "CN_PH_STATUS_MESSAGING_STOP") handleCnStatusMessagingStop (client, cmd); else qWarning ("%s Unknown command: %s", qPrintable (logTag (client->localPort ())), cmd.constData ()); } void MockServer::sendReply (QTcpSocket *client, const QByteArray &data) { client->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 (client->localPort ())), cmd.constData (), wire.constData ()); } void MockServer::handleKaPing (QTcpSocket *client, const QByteArray ¶ms) { QByteArray wire; if (s_wireDebug) wire = " << KA_PING," + params; qDebug ("%s RX KA_PING ACK%s", qPrintable (logTag (client->localPort ())), wire.constData ()); } void MockServer::handleGsJcVersion (QTcpSocket *client) { qDebug ("%s RX GS_JC_VERSION", qPrintable (logTag (client->localPort ()))); sendReply (client, "GS_JC_VERSION,1,\"1.05\",\"2.00\",15\n"); } void MockServer::handleGsPhVersion (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX GS_PH_VERSION,%s", qPrintable (logTag (client->localPort ())), params.constData ()); /* Echo back canned version info for whatever printhead ID was requested. */ sendReply (client, QByteArray ("GS_PH_VERSION,1,") + params + ",\"3.10\",\"1.00\",\"Standard\"," "\"2.05\",\"1.02\",\"0.9.1\"\n"); } void MockServer::handleCnJcSuccess (QTcpSocket *client, const QByteArray &cmd) { qDebug ("%s RX %s", qPrintable (logTag (client->localPort ())), cmd.constData ()); sendReply (client, cmd + ",1,1\n"); } void MockServer::handleCnPhSuccess (QTcpSocket *client, const QByteArray &cmd, const QByteArray ¶ms) { qDebug ("%s RX %s,%s", qPrintable (logTag (client->localPort ())), 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 (client, cmd + ",1," + phId + ",1\n"); } void MockServer::handleCnPhCalibrationData (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CN_PH_CALIBRATION_DATA,%s", qPrintable (logTag (client->localPort ())), 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 (client, reply); } void MockServer::handleCnPhCalibrationRawData (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CN_PH_CALIBRATION_RAW_DATA,%s", qPrintable (logTag (client->localPort ())), params.constData ()); QByteArray reply = "CN_PH_CALIBRATION_RAW_DATA,1," + params; for (int i = 0; i < 48; ++i) reply += ",-1"; reply += '\n'; sendReply (client, reply); } void MockServer::handleCnPhCalibratedBaseFrequency (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CN_PH_CALIBRATED_BASE_FREQUENCY,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CN_PH_CALIBRATED_BASE_FREQUENCY,1," + params + ",10.0,10.0\n"); } void MockServer::handleCnStatusMessagingStart (QTcpSocket *client, const QByteArray &cmd, const QByteArray ¶ms) { qDebug ("%s RX %s,%s", qPrintable (logTag (client->localPort ())), cmd.constData (), params.constData ()); /* Echo back: controllerId, level, interval, success. */ sendReply (client, cmd + ",1," + params + ",1\n"); } void MockServer::handleCnStatusMessagingStop (QTcpSocket *client, const QByteArray &cmd) { qDebug ("%s RX %s", qPrintable (logTag (client->localPort ())), cmd.constData ()); sendReply (client, cmd + ",1,1\n"); }