/** * @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 (sender ()); qInfo ("%s Connected to client", qPrintable (logTag (socket))); } void MockServer::onSocketDisconnected () { auto *socket = qobject_cast (sender ()); qInfo ("%s Disconnected from client", qPrintable (logTag (socket))); } void MockServer::onSocketMessageReady () { auto *socket = qobject_cast (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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), 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 (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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { qDebug ("%s RX %s,%s", qPrintable (logTag (&socket)), cmd.constData (), params.constData ()); } void MockServer::handleImagingMaskEnd (QTcpSocket &socket, const QByteArray &cmd, const QByteArray ¶ms) { 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"); }