/** * @file Xpl2Client.cpp * @brief TCP client for the Alchemie XPL2 printhead protocol. */ #include "Xpl2Client.h" #include bool Xpl2Client::s_wireDebug = false; Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) { for (auto *socket : { &m_commandSocket, &m_imagingSocket, &m_statusSocket }) { connect (socket, &QTcpSocket::connected, this, &Xpl2Client::onSocketConnected); connect (socket, &QTcpSocket::disconnected, this, &Xpl2Client::onSocketDisconnected); connect (socket, &QTcpSocket::readyRead, this, &Xpl2Client::onSocketMessageReady); connect (socket, &QAbstractSocket::errorOccurred, this, &Xpl2Client::onSocketError); } } /* ------------------------------------------------------------------ */ /* Properties */ /* ------------------------------------------------------------------ */ QString Xpl2Client::host () const { return m_host; } void Xpl2Client::setHost (const QString &host) { if (m_host == host) return; m_host = host; emit hostChanged (); } bool Xpl2Client::isConnected () const { return m_connected; } int Xpl2Client::controllerId () const { return m_controllerId; } QString Xpl2Client::firmwareVersion () const { return m_firmwareVersion; } QString Xpl2Client::hardwareVersion () const { return m_hardwareVersion; } int Xpl2Client::printheadCount () const { return m_printheadCount; } void Xpl2Client::enableWireDebug () { s_wireDebug = true; } /* ------------------------------------------------------------------ */ /* Connection */ /* ------------------------------------------------------------------ */ void Xpl2Client::connectToServer () { if (m_connected) { emit statusMessage (QStringLiteral ("Already connected")); return; } emit statusMessage (QStringLiteral ("Connecting to %1…").arg (m_host)); m_commandSocket.connectToHost (m_host, m_commandPort); m_imagingSocket.connectToHost (m_host, m_imagingPort); m_statusSocket.connectToHost (m_host, m_statusPort); } void Xpl2Client::disconnectFromServer () { m_commandSocket.disconnectFromHost (); m_imagingSocket.disconnectFromHost (); m_statusSocket.disconnectFromHost (); } void Xpl2Client::getJcVersion () { sendCommand (m_commandSocket, "GS_JC_VERSION"); } void Xpl2Client::getPhVersion (int printheadId) { sendCommand (m_commandSocket, "GS_PH_VERSION", { printheadId }); } /* ------------------------------------------------------------------ */ /* CN_ Control commands */ /* ------------------------------------------------------------------ */ void Xpl2Client::jettingAllOn () { sendCommand (m_commandSocket, "CN_JETTING_ALL_ON"); } void Xpl2Client::jettingOn (const QString &jettingMask) { sendCommand (m_commandSocket, "CN_JETTING_ON", { jettingMask }); } void Xpl2Client::jettingOff () { sendCommand (m_commandSocket, "CN_JETTING_OFF"); } void Xpl2Client::phJettingOn (int printheadId, const QString &jettingMask) { sendCommand (m_commandSocket, "CN_PH_JETTING_ON", { printheadId, jettingMask }); } void Xpl2Client::phJettingOff (int printheadId) { sendCommand (m_commandSocket, "CN_PH_JETTING_OFF", { printheadId }); } void Xpl2Client::jcIdLedOn () { sendCommand (m_commandSocket, "CN_JC_ID_LED_ON"); } void Xpl2Client::jcIdLedOff () { sendCommand (m_commandSocket, "CN_JC_ID_LED_OFF"); } void Xpl2Client::phIdLedOn (int printheadId) { sendCommand (m_commandSocket, "CN_PH_ID_LED_ON", { printheadId }); } void Xpl2Client::phIdLedOff (int printheadId) { sendCommand (m_commandSocket, "CN_PH_ID_LED_OFF", { printheadId }); } void Xpl2Client::jcCalibration () { sendCommand (m_commandSocket, "CN_JC_CALIBRATION"); } void Xpl2Client::phCalibration (int printheadId) { sendCommand (m_commandSocket, "CN_PH_CALIBRATION", { printheadId }); } void Xpl2Client::phCalibrationData (int printheadId) { sendCommand (m_commandSocket, "CN_PH_CALIBRATION_DATA", { printheadId }); } void Xpl2Client::phCalibrationRawData (int printheadId) { sendCommand (m_commandSocket, "CN_PH_CALIBRATION_RAW_DATA", { printheadId }); } void Xpl2Client::phCalibratedBaseFrequency (int printheadId) { sendCommand (m_commandSocket, "CN_PH_CALIBRATED_BASE_FREQUENCY", { printheadId }); } void Xpl2Client::jcStatusMessagingStart (int statusLevel, int sendIntervalMs) { sendCommand (m_statusSocket, "CN_JC_STATUS_MESSAGING_START", { statusLevel, sendIntervalMs }); } void Xpl2Client::jcStatusMessagingStop () { sendCommand (m_statusSocket, "CN_JC_STATUS_MESSAGING_STOP"); } void Xpl2Client::phStatusMessagingStart (int statusLevel, int sendIntervalMs) { sendCommand (m_statusSocket, "CN_PH_STATUS_MESSAGING_START", { statusLevel, sendIntervalMs }); } void Xpl2Client::phStatusMessagingStop () { sendCommand (m_statusSocket, "CN_PH_STATUS_MESSAGING_STOP"); } void Xpl2Client::jcResetFaultCodes () { sendCommand (m_commandSocket, "CN_JC_RESET_FAULT_CODES"); } void Xpl2Client::phResetFaultCodes (int printheadId) { sendCommand (m_commandSocket, "CN_PH_RESET_FAULT_CODES", { printheadId }); } void Xpl2Client::phNozzlesDisabled (int printheadId, const QString &mask) { sendCommand (m_commandSocket, "CN_PH_NOZZLES_DISABLED", { printheadId, mask }); } /* ------------------------------------------------------------------ */ /* Send / dispatch */ /* ------------------------------------------------------------------ */ void Xpl2Client::sendCommand (QTcpSocket &socket, const QByteArray &command, const QVariantList ¶ms) { if (socket.state () != QAbstractSocket::ConnectedState) { emit errorOccurred (QStringLiteral ("Not connected for %1") .arg (QString::fromUtf8 (command))); return; } QByteArray data = Xpl2Protocol::buildMessage (command, params); socket.write (data); QByteArray wire; if (s_wireDebug) wire = " >> " + data.trimmed (); qDebug ("%s TX %s%s", qPrintable (logTag (&socket)), command.constData (), wire.constData ()); } void Xpl2Client::dispatchCommandMessage (const Xpl2Protocol::ParsedMessage &msg) { if (msg.command == "KA_PING") handleKaPing (m_commandSocket); else if (msg.command == "GS_JC_VERSION") handleGsJcVersion (msg.params); else if (msg.command == "GS_PH_VERSION") handleGsPhVersion (msg.params); /* CN_ commands — JC success shape (controllerId, success) */ else if (msg.command == "CN_JETTING_ALL_ON" || msg.command == "CN_JETTING_ON" || msg.command == "CN_JETTING_OFF" || msg.command == "CN_JC_ID_LED_ON" || msg.command == "CN_JC_ID_LED_OFF" || msg.command == "CN_JC_CALIBRATION" || msg.command == "CN_JC_RESET_FAULT_CODES") handleJcSuccessResponse (msg.command, msg.params); /* CN_ commands — PH success shape (controllerId, phId, success) */ else if (msg.command == "CN_PH_JETTING_ON" || msg.command == "CN_PH_JETTING_OFF" || msg.command == "CN_PH_ID_LED_ON" || msg.command == "CN_PH_ID_LED_OFF" || msg.command == "CN_PH_CALIBRATION" || msg.command == "CN_PH_RESET_FAULT_CODES" || msg.command == "CN_PH_NOZZLES_DISABLED") handlePhSuccessResponse (msg.command, msg.params); /* CN_ commands — custom shapes */ else if (msg.command == "CN_PH_CALIBRATION_DATA") handleCnPhCalibrationData (msg.params); else if (msg.command == "CN_PH_CALIBRATION_RAW_DATA") handleCnPhCalibrationRawData (msg.params); else if (msg.command == "CN_PH_CALIBRATED_BASE_FREQUENCY") handleCnPhCalibratedBaseFrequency (msg.params); else qWarning ("%s Unknown command: %s", qPrintable (logTag (&m_commandSocket)), msg.command.constData ()); } void Xpl2Client::dispatchImagingMessage (const Xpl2Protocol::ParsedMessage &msg) { if (msg.command == "KA_PING") handleKaPing (m_imagingSocket); else qWarning ("%s Unknown command: %s", qPrintable (logTag (&m_imagingSocket)), msg.command.constData ()); } void Xpl2Client::dispatchStatusMessage (const Xpl2Protocol::ParsedMessage &msg) { if (msg.command == "KA_PING") handleKaPing (m_statusSocket); else if (msg.command == "CN_JC_STATUS_MESSAGING_START") handleCnJcStatusMessagingStart (msg.params); else if (msg.command == "CN_JC_STATUS_MESSAGING_STOP") handleCnJcStatusMessagingStop (msg.params); else if (msg.command == "CN_PH_STATUS_MESSAGING_START") handleCnPhStatusMessagingStart (msg.params); else if (msg.command == "CN_PH_STATUS_MESSAGING_STOP") handleCnPhStatusMessagingStop (msg.params); else qWarning ("%s Unknown command: %s", qPrintable (logTag (&m_statusSocket)), msg.command.constData ()); } void Xpl2Client::handleKaPing (QTcpSocket &socket) { sendCommand (socket, "KA_PING", { 1 }); } void Xpl2Client::handleGsJcVersion (const QVariantList ¶ms) { if (params.size () < 4) { qWarning () << "GS_JC_VERSION: expected 4 params, got" << params.size (); return; } m_controllerId = params[0].toInt (); m_firmwareVersion = params[1].toString (); m_hardwareVersion = params[2].toString (); m_printheadCount = params[3].toInt (); qDebug ("%s controller=%d fw=%s hw=%s phCount=%d", qPrintable (logTag (&m_commandSocket)), m_controllerId, qPrintable (m_firmwareVersion), qPrintable (m_hardwareVersion), m_printheadCount); emit statusMessage ( QStringLiteral ("RX JC Version: controller=%1 fw=%2 hw=%3 phCount=%4") .arg (m_controllerId) .arg (m_firmwareVersion) .arg (m_hardwareVersion) .arg (m_printheadCount)); emit jcVersionReceived (); } void Xpl2Client::handleGsPhVersion (const QVariantList ¶ms) { if (params.size () < 8) { qWarning () << "GS_PH_VERSION: expected 8 params, got" << params.size (); return; } int cid = params[0].toInt (); int phId = params[1].toInt (); QString mcuFw = params[2].toString (); QString mcuHw = params[3].toString (); QString mcuFwVar = params[4].toString (); QString fpgaFw = params[5].toString (); QString fpgaHw = params[6].toString (); QString bootVer = params[7].toString (); qDebug ("%s PH[%d] mcuFw=%s mcuHw=%s mcuFwVar=%s fpgaFw=%s fpgaHw=%s " "boot=%s", qPrintable (logTag (&m_commandSocket)), phId, qPrintable (mcuFw), qPrintable (mcuHw), qPrintable (mcuFwVar), qPrintable (fpgaFw), qPrintable (fpgaHw), qPrintable (bootVer)); emit statusMessage ( QStringLiteral ( "RX PH[%1] Version: mcuFw=%2 mcuHw=%3 fpgaFw=%4 fpgaHw=%5 boot=%6") .arg (phId) .arg (mcuFw) .arg (mcuHw) .arg (fpgaFw) .arg (fpgaHw) .arg (bootVer)); emit phVersionReceived (cid, phId, mcuFw, mcuHw, mcuFwVar, fpgaFw, fpgaHw, bootVer); } void Xpl2Client::handleJcSuccessResponse (const QByteArray &command, const QVariantList ¶ms) { if (params.size () < 2) { qWarning () << command << ": expected 2 params, got" << params.size (); return; } int cid = params[0].toInt (); bool ok = params[1].toInt () == 1; qDebug ("%s %s controller=%d success=%d", qPrintable (logTag (&m_commandSocket)), command.constData (), cid, ok); emit statusMessage (QStringLiteral ("RX %1: controller=%2 success=%3") .arg (QString::fromUtf8 (command)) .arg (cid) .arg (ok)); if (command == "CN_JETTING_ALL_ON") emit jettingAllOnResult (cid, ok); else if (command == "CN_JETTING_ON") emit jettingOnResult (cid, ok); else if (command == "CN_JETTING_OFF") emit jettingOffResult (cid, ok); else if (command == "CN_JC_ID_LED_ON") emit jcIdLedOnResult (cid, ok); else if (command == "CN_JC_ID_LED_OFF") emit jcIdLedOffResult (cid, ok); else if (command == "CN_JC_CALIBRATION") emit jcCalibrationResult (cid, ok); else if (command == "CN_JC_RESET_FAULT_CODES") emit jcResetFaultCodesResult (cid, ok); } void Xpl2Client::handlePhSuccessResponse (const QByteArray &command, const QVariantList ¶ms) { if (params.size () < 3) { qWarning () << command << ": expected 3 params, got" << params.size (); return; } int cid = params[0].toInt (); int phId = params[1].toInt (); bool ok = params[2].toInt () == 1; qDebug ("%s %s controller=%d ph=%d success=%d", qPrintable (logTag (&m_commandSocket)), command.constData (), cid, phId, ok); emit statusMessage (QStringLiteral ("RX %1: controller=%2 ph=%3 success=%4") .arg (QString::fromUtf8 (command)) .arg (cid) .arg (phId) .arg (ok)); if (command == "CN_PH_JETTING_ON") emit phJettingOnResult (cid, phId, ok); else if (command == "CN_PH_JETTING_OFF") emit phJettingOffResult (cid, phId, ok); else if (command == "CN_PH_ID_LED_ON") emit phIdLedOnResult (cid, phId, ok); else if (command == "CN_PH_ID_LED_OFF") emit phIdLedOffResult (cid, phId, ok); else if (command == "CN_PH_CALIBRATION") emit phCalibrationResult (cid, phId, ok); else if (command == "CN_PH_RESET_FAULT_CODES") emit phResetFaultCodesResult (cid, phId, ok); else if (command == "CN_PH_NOZZLES_DISABLED") emit phNozzlesDisabledResult (cid, phId, ok); } void Xpl2Client::handleCnPhCalibrationData (const QVariantList ¶ms) { if (params.size () < 50) { qWarning () << "CN_PH_CALIBRATION_DATA: expected 50 params, got" << params.size (); return; } int cid = params[0].toInt (); int phId = params[1].toInt (); QVariantList frequencies = params.mid (2, 48); qDebug ("%s CN_PH_CALIBRATION_DATA controller=%d ph=%d (%lld freqs)", qPrintable (logTag (&m_commandSocket)), cid, phId, static_cast (frequencies.size ())); emit statusMessage ( QStringLiteral ("RX CN_PH_CALIBRATION_DATA: controller=%1 ph=%2") .arg (cid) .arg (phId)); emit phCalibrationDataReceived (cid, phId, frequencies); } void Xpl2Client::handleCnPhCalibrationRawData (const QVariantList ¶ms) { if (params.size () < 50) { qWarning () << "CN_PH_CALIBRATION_RAW_DATA: expected 50 params, got" << params.size (); return; } int cid = params[0].toInt (); int phId = params[1].toInt (); QVariantList frequencies = params.mid (2, 48); qDebug ("%s CN_PH_CALIBRATION_RAW_DATA controller=%d ph=%d (%lld freqs)", qPrintable (logTag (&m_commandSocket)), cid, phId, static_cast (frequencies.size ())); emit statusMessage ( QStringLiteral ("RX CN_PH_CALIBRATION_RAW_DATA: controller=%1 ph=%2") .arg (cid) .arg (phId)); emit phCalibrationRawDataReceived (cid, phId, frequencies); } void Xpl2Client::handleCnPhCalibratedBaseFrequency (const QVariantList ¶ms) { if (params.size () < 4) { qWarning () << "CN_PH_CALIBRATED_BASE_FREQUENCY: expected 4 params, got" << params.size (); return; } int cid = params[0].toInt (); int phId = params[1].toInt (); double baseFreq = params[2].toDouble (); double activeBaseFreq = params[3].toDouble (); qDebug ("%s CN_PH_CALIBRATED_BASE_FREQUENCY controller=%d ph=%d " "base=%.2f active=%.2f", qPrintable (logTag (&m_commandSocket)), cid, phId, baseFreq, activeBaseFreq); emit statusMessage ( QStringLiteral ("RX CN_PH_CALIBRATED_BASE_FREQUENCY: controller=%1 " "ph=%2 base=%3 active=%4") .arg (cid) .arg (phId) .arg (baseFreq) .arg (activeBaseFreq)); emit phCalibratedBaseFrequencyReceived (cid, phId, baseFreq, activeBaseFreq); } void Xpl2Client::handleCnJcStatusMessagingStart (const QVariantList ¶ms) { if (params.size () < 4) { qWarning () << "CN_JC_STATUS_MESSAGING_START: expected 4 params, got" << params.size (); return; } int cid = params[0].toInt (); int level = params[1].toInt (); int interval = params[2].toInt (); bool ok = params[3].toInt () == 1; qDebug ("%s CN_JC_STATUS_MESSAGING_START controller=%d level=%d " "interval=%d success=%d", qPrintable (logTag (&m_statusSocket)), cid, level, interval, ok); emit statusMessage ( QStringLiteral ("RX CN_JC_STATUS_MESSAGING_START: controller=%1 " "level=%2 interval=%3 success=%4") .arg (cid) .arg (level) .arg (interval) .arg (ok)); emit jcStatusMessagingStartResult (cid, level, interval, ok); } void Xpl2Client::handleCnJcStatusMessagingStop (const QVariantList ¶ms) { if (params.size () < 2) { qWarning () << "CN_JC_STATUS_MESSAGING_STOP: expected 2 params, got" << params.size (); return; } int cid = params[0].toInt (); bool ok = params[1].toInt () == 1; qDebug ("%s CN_JC_STATUS_MESSAGING_STOP controller=%d success=%d", qPrintable (logTag (&m_statusSocket)), cid, ok); emit statusMessage ( QStringLiteral ("RX CN_JC_STATUS_MESSAGING_STOP: controller=%1 " "success=%2") .arg (cid) .arg (ok)); emit jcStatusMessagingStopResult (cid, ok); } void Xpl2Client::handleCnPhStatusMessagingStart (const QVariantList ¶ms) { if (params.size () < 4) { qWarning () << "CN_PH_STATUS_MESSAGING_START: expected 4 params, got" << params.size (); return; } int cid = params[0].toInt (); int level = params[1].toInt (); int interval = params[2].toInt (); bool ok = params[3].toInt () == 1; qDebug ("%s CN_PH_STATUS_MESSAGING_START controller=%d level=%d " "interval=%d success=%d", qPrintable (logTag (&m_statusSocket)), cid, level, interval, ok); emit statusMessage ( QStringLiteral ("RX CN_PH_STATUS_MESSAGING_START: controller=%1 " "level=%2 interval=%3 success=%4") .arg (cid) .arg (level) .arg (interval) .arg (ok)); emit phStatusMessagingStartResult (cid, level, interval, ok); } void Xpl2Client::handleCnPhStatusMessagingStop (const QVariantList ¶ms) { if (params.size () < 2) { qWarning () << "CN_PH_STATUS_MESSAGING_STOP: expected 2 params, got" << params.size (); return; } int cid = params[0].toInt (); bool ok = params[1].toInt () == 1; qDebug ("%s CN_PH_STATUS_MESSAGING_STOP controller=%d success=%d", qPrintable (logTag (&m_statusSocket)), cid, ok); emit statusMessage ( QStringLiteral ("RX CN_PH_STATUS_MESSAGING_STOP: controller=%1 " "success=%2") .arg (cid) .arg (ok)); emit phStatusMessagingStopResult (cid, ok); } /* ------------------------------------------------------------------ */ /* Internal */ /* ------------------------------------------------------------------ */ void Xpl2Client::updateConnectedState () { bool allConnected = m_commandSocket.state () == QAbstractSocket::ConnectedState && m_imagingSocket.state () == QAbstractSocket::ConnectedState && m_statusSocket.state () == QAbstractSocket::ConnectedState; if (m_connected == allConnected) return; m_connected = allConnected; emit connectedChanged (); } QString Xpl2Client::logTag (const QTcpSocket *socket) const { const char *name = "Unknown"; quint16 port = 0; if (socket == &m_commandSocket) { name = "Command"; port = m_commandPort; } else if (socket == &m_imagingSocket) { name = "Imaging"; port = m_imagingPort; } else if (socket == &m_statusSocket) { name = "Status"; port = m_statusPort; } return QStringLiteral ("[%1:%2]").arg (name).arg (port).leftJustified (15); } /* ------------------------------------------------------------------ */ /* Socket slots */ /* ------------------------------------------------------------------ */ void Xpl2Client::onSocketConnected () { auto *socket = qobject_cast (sender ()); qInfo ("%s Connected", qPrintable (logTag (socket))); updateConnectedState (); } void Xpl2Client::onSocketDisconnected () { auto *socket = qobject_cast (sender ()); if (!socket) { /* Identify by elimination — which socket just left ConnectedState? */ for (auto *s : { &m_commandSocket, &m_imagingSocket, &m_statusSocket }) if (s->state () != QAbstractSocket::ConnectedState) qInfo ("%s Disconnected", qPrintable (logTag (s))); } else qInfo ("%s Disconnected", qPrintable (logTag (socket))); updateConnectedState (); } void Xpl2Client::onSocketMessageReady () { auto *socket = qobject_cast (sender ()); while (socket->canReadLine ()) { QByteArray raw = socket->readLine (); auto msg = Xpl2Protocol::parseMessage (raw); if (msg.valid) { QByteArray wire; if (s_wireDebug) wire = " << " + raw.trimmed (); qDebug ("%s RX %s%s", qPrintable (logTag (socket)), msg.command.constData (), wire.constData ()); if (socket == &m_commandSocket) dispatchCommandMessage (msg); else if (socket == &m_imagingSocket) dispatchImagingMessage (msg); else dispatchStatusMessage (msg); } } } void Xpl2Client::onSocketError (QAbstractSocket::SocketError error) { Q_UNUSED (error) auto *socket = qobject_cast (sender ()); emit errorOccurred (QStringLiteral ("%1 %2") .arg (logTag (socket)) .arg (socket->errorString ())); }