/** * @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); connect (&m_jcStatusTimer, &QTimer::timeout, this, &MockServer::sendJcStatusMsg); connect (&m_phStatusTimer, &QTimer::timeout, this, &MockServer::sendPhStatusMsg); } 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); /* 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 (client, 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 (client, cmd, params); /* CF_ custom-shape commands */ else if (cmd == "CF_JC_SET_PURGE_SETTINGS") handleCfJcSetPurgeSettings (client, params); else if (cmd == "CF_JC_SET_JETTING_PARAMS") handleCfJcSetJettingParams (client, params); else if (cmd == "CF_PH_SET_JETTING_PARAMS") handleCfPhJettingParams (client, cmd, params); else if (cmd == "CF_PH_GET_JETTING_PARAMS") handleCfPhJettingParams (client, cmd, params); else if (cmd == "CF_JC_SETTER") handleCfJcSetter (client, params); else if (cmd == "CF_PH_SETTER") handleCfPhSetter (client, params); else if (cmd == "CF_JC_GETTER") handleCfJcGetter (client, params); else if (cmd == "CF_PH_GETTER") handleCfPhGetter (client, params); 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 ()); /* Parse level and interval from params: "level,interval" */ QList 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 (client, cmd + ",1," + params + ",1\n"); } void MockServer::handleCnStatusMessagingStop (QTcpSocket *client, const QByteArray &cmd) { qDebug ("%s RX %s", qPrintable (logTag (client->localPort ())), cmd.constData ()); if (cmd == "CN_JC_STATUS_MESSAGING_STOP") m_jcStatusTimer.stop (); else m_phStatusTimer.stop (); sendReply (client, cmd + ",1,1\n"); } void MockServer::handleCfJcSetPurgeSettings (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_JC_SET_PURGE_SETTINGS,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_JC_SET_PURGE_SETTINGS,1," + params + ",1\n"); } void MockServer::handleCfJcSetJettingParams (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_JC_SET_JETTING_PARAMS,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_JC_SET_JETTING_PARAMS,1," + params + ",1\n"); } void MockServer::handleCfPhJettingParams (QTcpSocket *client, const QByteArray &cmd, const QByteArray ¶ms) { qDebug ("%s RX %s,%s", qPrintable (logTag (client->localPort ())), cmd.constData (), params.constData ()); if (cmd == "CF_PH_GET_JETTING_PARAMS") sendReply (client, cmd + ",1," + params + ",0,0,0,0,0,1\n"); else sendReply (client, cmd + ",1," + params + ",1\n"); } void MockServer::handleCfJcSetter (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_JC_SETTER,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_JC_SETTER,1," + params + ",1\n"); } void MockServer::handleCfPhSetter (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_PH_SETTER,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_PH_SETTER,1," + params + ",1\n"); } void MockServer::handleCfJcGetter (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_JC_GETTER,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_JC_GETTER,1," + params + ",\"0\",1\n"); } void MockServer::handleCfPhGetter (QTcpSocket *client, const QByteArray ¶ms) { qDebug ("%s RX CF_PH_GETTER,%s", qPrintable (logTag (client->localPort ())), params.constData ()); sendReply (client, "CF_PH_GETTER,1," + params + ",\"0\",1\n"); } void MockServer::sendJcStatusMsg () { 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'; for (auto *client : m_clients) if (client->state () == QAbstractSocket::ConnectedState && client->localPort () == m_status.number) sendReply (client, base); } void MockServer::sendPhStatusMsg () { 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'; for (auto *client : m_clients) if (client->state () == QAbstractSocket::ConnectedState && client->localPort () == m_status.number) sendReply (client, base); }