/** * @file Xpl2Client.cpp * @brief TCP client for the Alchemie XPL2 printhead protocol. */ #include "Xpl2Client.h" #include bool Xpl2Client::s_wireDebug = false; /* ------------------------------------------------------------------ */ /* Response dispatch table */ /* ------------------------------------------------------------------ */ // clang-format off const QHash Xpl2Client::s_responseTable = { /* CN_ JC success shape: controllerId, successFlag */ { "CN_JETTING_ALL_ON", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jettingAllOnResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JETTING_ON", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jettingOnResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JETTING_OFF", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jettingOffResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JC_ID_LED_ON", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcIdLedOnResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JC_ID_LED_OFF", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcIdLedOffResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JC_CALIBRATION", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcCalibrationResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JC_RESET_FAULT_CODES", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcResetFaultCodesResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_JC_STATUS_MESSAGING_STOP", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcStatusMessagingStopResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CN_PH_STATUS_MESSAGING_STOP", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->phStatusMessagingStopResult (p[0].toInt (), p[1].toInt () == 1); } } }, /* CN_ PH success shape: controllerId, printheadId, successFlag */ { "CN_PH_JETTING_ON", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phJettingOnResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_JETTING_OFF", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phJettingOffResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_ID_LED_ON", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phIdLedOnResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_ID_LED_OFF", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phIdLedOffResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_CALIBRATION", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phCalibrationResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_RESET_FAULT_CODES", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phResetFaultCodesResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CN_PH_NOZZLES_DISABLED", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phNozzlesDisabledResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, /* CN_ calibration data: controllerId, phId, 48 floats */ { "CN_PH_CALIBRATION_DATA", { ResponseShape::CalData, 50, [](auto *s, const auto &p) { emit s->phCalibrationDataReceived (p[0].toInt (), p[1].toInt (), p.mid (2, 48)); } } }, { "CN_PH_CALIBRATION_RAW_DATA", { ResponseShape::CalData, 50, [](auto *s, const auto &p) { emit s->phCalibrationRawDataReceived (p[0].toInt (), p[1].toInt (), p.mid (2, 48)); } } }, /* CN_ base frequency: controllerId, phId, baseFreq, activeBaseFreq */ { "CN_PH_CALIBRATED_BASE_FREQUENCY", { ResponseShape::BaseFreq, 4, [](auto *s, const auto &p) { emit s->phCalibratedBaseFrequencyReceived (p[0].toInt (), p[1].toInt (), p[2].toDouble (), p[3].toDouble ()); } } }, /* CN_ status messaging start: controllerId, level, interval, successFlag */ { "CN_JC_STATUS_MESSAGING_START", { ResponseShape::StatusStart, 4, [](auto *s, const auto &p) { emit s->jcStatusMessagingStartResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt () == 1); } } }, { "CN_PH_STATUS_MESSAGING_START", { ResponseShape::StatusStart, 4, [](auto *s, const auto &p) { emit s->phStatusMessagingStartResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt () == 1); } } }, /* CF_ JC success shape: controllerId, successFlag */ { "CF_PH_DEASSIGN_ID", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->phDeassignIdResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_SAVE_CALIBRATION", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcSaveCalibrationResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_RESET_CALIBRATION", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcResetCalibrationResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_SWITCH_OFF_PURGE", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcSwitchOffPurgeResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_RESET_SETTINGS_ALL_PRINTHEADS", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcResetSettingsAllPrintheadsResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_REBOOT_ALL_PRINTHEADS", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcRebootAllPrintheadsResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_RESET_CONTROLLER_SOFTWARE", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcResetControllerSoftwareResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_RESTART", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcRestartResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_SHUTDOWN", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcShutdownResult (p[0].toInt (), p[1].toInt () == 1); } } }, { "CF_JC_SAVE_ALL_PRINTHEAD_SETTINGS", { ResponseShape::JcSuccess, 2, [](auto *s, const auto &p) { emit s->jcSaveAllPrintheadSettingsResult (p[0].toInt (), p[1].toInt () == 1); } } }, /* CF_ PH success shape: controllerId, phId, successFlag */ { "CF_PH_SET_ID", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phSetIdResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CF_PH_SAVE_CALIBRATION", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phSaveCalibrationResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CF_PH_RESET_CALIBRATION", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phResetCalibrationResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CF_PH_RESET_ALL_SETTINGS", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phResetAllSettingsResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CF_PH_REBOOT", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phRebootResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "CF_PH_SAVE_SETTINGS", { ResponseShape::PhSuccess, 3, [](auto *s, const auto &p) { emit s->phSaveSettingsResult (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, /* CF_ purge settings: controllerId, purgeInterval, purgeTime, successFlag */ { "CF_JC_SET_PURGE_SETTINGS", { ResponseShape::PurgeSettings, 4, [](auto *s, const auto &p) { emit s->jcSetPurgeSettingsResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt () == 1); } } }, /* CF_ JC jetting params: cid, dutyCycle, pwmFreq, drive, nozzleDriveFreq, nozzleDriveDutyCycle, successFlag */ { "CF_JC_SET_JETTING_PARAMS", { ResponseShape::JcJettingParams, 7, [](auto *s, const auto &p) { emit s->jcSetJettingParamsResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt (), p[4].toInt (), p[5].toInt (), static_cast (p[6].toInt ())); } } }, /* CF_ PH jetting params: cid, phId, dutyCycle, pwmFreq, drive, nozzleDriveFreq, nozzleDriveDutyCycle, successFlag */ { "CF_PH_SET_JETTING_PARAMS", { ResponseShape::PhJettingParams, 8, [](auto *s, const auto &p) { emit s->phSetJettingParamsResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt (), p[4].toInt (), p[5].toInt (), p[6].toInt (), static_cast (p[7].toInt ())); } } }, { "CF_PH_GET_JETTING_PARAMS", { ResponseShape::PhJettingParams, 8, [](auto *s, const auto &p) { emit s->phGetJettingParamsResult (p[0].toInt (), p[1].toInt (), p[2].toInt (), p[3].toInt (), p[4].toInt (), p[5].toInt (), p[6].toInt (), static_cast (p[7].toInt ())); } } }, /* CF_ JC setter: cid, saveNewValue, setterId, newValue, successFlag */ { "CF_JC_SETTER", { ResponseShape::JcSetter, 5, [](auto *s, const auto &p) { emit s->jcSetterResult (p[0].toInt (), p[1].toInt () != 0, p[2].toInt (), p[3].toString (), static_cast (p[4].toInt ())); } } }, /* CF_ PH setter: cid, phId, saveNewValue, setterId, newValue, successFlag */ { "CF_PH_SETTER", { ResponseShape::PhSetter, 6, [](auto *s, const auto &p) { emit s->phSetterResult (p[0].toInt (), p[1].toInt (), p[2].toInt () != 0, p[3].toInt (), p[4].toString (), static_cast (p[5].toInt ())); } } }, /* CF_ JC getter: cid, savedValue, getterId, currentValue, successFlag */ { "CF_JC_GETTER", { ResponseShape::JcGetter, 5, [](auto *s, const auto &p) { emit s->jcGetterResult (p[0].toInt (), p[1].toInt () != 0, p[2].toInt (), p[3].toString (), p[4].toInt () == 1); } } }, /* CF_ PH getter: cid, phId, savedValue, getterId, currentValue, successFlag */ { "CF_PH_GETTER", { ResponseShape::PhGetter, 6, [](auto *s, const auto &p) { emit s->phGetterResult (p[0].toInt (), p[1].toInt (), p[2].toInt () != 0, p[3].toInt (), p[4].toString (), p[5].toInt () == 1); } } }, /* EV_ events */ { "EV_PH_CONNECTION_CHANGED", { ResponseShape::PhConnectionChanged, 3, [](auto *s, const auto &p) { emit s->phConnectionChanged (p[0].toInt (), p[1].toInt (), p[2].toInt () == 1); } } }, { "EV_JC_ERROR_CODE", { ResponseShape::ErrorCode, 3, [](auto *s, const auto &p) { QStringList ep; for (int i = 3; i < p.size (); ++i) ep << p[i].toString (); emit s->jcErrorCode (p[0].toInt (), p[1].toInt (), ep); } } }, { "EV_IMG_ERROR_CODE", { ResponseShape::ErrorCode, 3, [](auto *s, const auto &p) { QStringList ep; for (int i = 3; i < p.size (); ++i) ep << p[i].toString (); emit s->imgErrorCode (p[0].toInt (), p[1].toInt (), ep); } } }, { "EV_STATUS_ERROR_CODE", { ResponseShape::ErrorCode, 3, [](auto *s, const auto &p) { QStringList ep; for (int i = 3; i < p.size (); ++i) ep << p[i].toString (); emit s->statusErrorCode (p[0].toInt (), p[1].toInt (), ep); } } }, { "EV_PH_ERROR_CODE", { ResponseShape::PhErrorCode, 4, [](auto *s, const auto &p) { QStringList ep; for (int i = 4; i < p.size (); ++i) ep << p[i].toString (); emit s->phErrorCode (p[0].toInt (), p[1].toInt (), p[2].toInt (), ep); } } }, }; // clang-format on Xpl2Client::Xpl2Client (QObject *parent) : QObject (parent) { setupSocket (m_commandSocket); setupSocket (m_imagingSocket); setupSocket (m_statusSocket); connect (&m_retryTimer, &QTimer::timeout, this, &Xpl2Client::retryConnection); } /* ------------------------------------------------------------------ */ /* Properties */ /* ------------------------------------------------------------------ */ QString Xpl2Client::host () const { return m_host; } void Xpl2Client::setHost (const QString &host) { if (m_host == host) return; m_host = host; emit hostChanged (); } int Xpl2Client::commandPort () const { return m_commandPort; } void Xpl2Client::setCommandPort (int port) { quint16 p = static_cast (port); if (m_commandPort == p) return; m_commandPort = p; emit commandPortChanged (); } 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::setupSocket (QTcpSocket &socket) { 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); } void Xpl2Client::connectToProxy () { if (m_connected) { emit statusMessage (QStringLiteral ("Already connected")); return; } quint16 imagingPort = m_commandPort + 1; quint16 statusPort = m_commandPort + 2; for (auto *s : { &m_commandSocket, &m_imagingSocket, &m_statusSocket }) if (s->state () == QAbstractSocket::UnconnectedState) { if (s == &m_commandSocket) s->connectToHost (m_host, m_commandPort); else if (s == &m_imagingSocket) s->connectToHost (m_host, imagingPort); else s->connectToHost (m_host, statusPort); } if (!m_retryTimer.isActive ()) m_retryTimer.start (2000); emit statusMessage (QStringLiteral ("Connecting to %1:%2/%3/%4…") .arg (m_host) .arg (m_commandPort) .arg (imagingPort) .arg (statusPort)); } void Xpl2Client::disconnectFromProxy () { m_retryTimer.stop (); for (auto *s : { &m_commandSocket, &m_imagingSocket, &m_statusSocket }) if (s->state () != QAbstractSocket::UnconnectedState) s->disconnectFromHost (); updateConnectedState (); } 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 }); } /* ------------------------------------------------------------------ */ /* CF_ Configuration commands */ /* ------------------------------------------------------------------ */ void Xpl2Client::phSetId (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_SET_ID", { printheadId }); } void Xpl2Client::phDeassignId () { sendCommand (&m_commandSocket, "CF_PH_DEASSIGN_ID"); } void Xpl2Client::jcSetJettingParams (int dutyCycle, int pwmFreq, int drive, int nozzleDriveFreq, int nozzleDriveDutyCycle) { sendCommand ( &m_commandSocket, "CF_JC_SET_JETTING_PARAMS", { dutyCycle, pwmFreq, drive, nozzleDriveFreq, nozzleDriveDutyCycle }); } void Xpl2Client::phSetJettingParams (int printheadId, int dutyCycle, int pwmFreq, int drive, int nozzleDriveFreq, int nozzleDriveDutyCycle) { sendCommand (&m_commandSocket, "CF_PH_SET_JETTING_PARAMS", { printheadId, dutyCycle, pwmFreq, drive, nozzleDriveFreq, nozzleDriveDutyCycle }); } void Xpl2Client::phGetJettingParams (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_GET_JETTING_PARAMS", { printheadId }); } void Xpl2Client::jcSaveCalibration () { sendCommand (&m_commandSocket, "CF_JC_SAVE_CALIBRATION"); } void Xpl2Client::phSaveCalibration (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_SAVE_CALIBRATION", { printheadId }); } void Xpl2Client::jcResetCalibration () { sendCommand (&m_commandSocket, "CF_JC_RESET_CALIBRATION"); } void Xpl2Client::phResetCalibration (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_RESET_CALIBRATION", { printheadId }); } void Xpl2Client::jcSetPurgeSettings (int purgeIntervalMs, int purgeTimeMs) { sendCommand (&m_commandSocket, "CF_JC_SET_PURGE_SETTINGS", { purgeIntervalMs, purgeTimeMs }); } void Xpl2Client::jcSwitchOffPurge () { sendCommand (&m_commandSocket, "CF_JC_SWITCH_OFF_PURGE"); } void Xpl2Client::jcSetter (bool saveNewValue, int setterId, const QString &newValue) { sendCommand (&m_commandSocket, "CF_JC_SETTER", { saveNewValue, setterId, newValue }); } void Xpl2Client::phSetter (int printheadId, bool saveNewValue, int setterId, const QString &newValue) { sendCommand (&m_commandSocket, "CF_PH_SETTER", { printheadId, saveNewValue, setterId, newValue }); } void Xpl2Client::jcGetter (bool getSavedValue, int getterId) { sendCommand (&m_commandSocket, "CF_JC_GETTER", { getSavedValue, getterId }); } void Xpl2Client::phGetter (int printheadId, bool getSavedValue, int getterId) { sendCommand (&m_commandSocket, "CF_PH_GETTER", { printheadId, getSavedValue, getterId }); } void Xpl2Client::jcResetSettingsAllPrintheads () { sendCommand (&m_commandSocket, "CF_JC_RESET_SETTINGS_ALL_PRINTHEADS"); } void Xpl2Client::phResetAllSettings (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_RESET_ALL_SETTINGS", { printheadId }); } void Xpl2Client::jcRebootAllPrintheads () { sendCommand (&m_commandSocket, "CF_JC_REBOOT_ALL_PRINTHEADS"); } void Xpl2Client::phReboot (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_REBOOT", { printheadId }); } void Xpl2Client::jcResetControllerSoftware () { sendCommand (&m_commandSocket, "CF_JC_RESET_CONTROLLER_SOFTWARE"); } void Xpl2Client::jcRestart () { sendCommand (&m_commandSocket, "CF_JC_RESTART"); } void Xpl2Client::jcShutdown () { sendCommand (&m_commandSocket, "CF_JC_SHUTDOWN"); } void Xpl2Client::jcSaveAllPrintheadSettings () { sendCommand (&m_commandSocket, "CF_JC_SAVE_ALL_PRINTHEAD_SETTINGS"); } void Xpl2Client::phSaveSettings (int printheadId) { sendCommand (&m_commandSocket, "CF_PH_SAVE_SETTINGS", { printheadId }); } /* ------------------------------------------------------------------ */ /* Imaging (m/n) commands */ /* ------------------------------------------------------------------ */ void Xpl2Client::imagingStart (double speed) { sendCommand (&m_imagingSocket, "m2", { speed }); } void Xpl2Client::imagingStop () { sendCommand (&m_imagingSocket, "m4"); } void Xpl2Client::imageMaskStart (const QString &mask) { sendCommand (&m_imagingSocket, "m0", { mask }); } void Xpl2Client::imageMaskEnd (const QString &tail) { sendCommand (&m_imagingSocket, "m1", { tail }); } void Xpl2Client::dutyCycleMaskStart (const QString &mask) { sendCommand (&m_imagingSocket, "m5", { mask }); } void Xpl2Client::dutyCycleMaskEnd (const QString &tail) { sendCommand (&m_imagingSocket, "m6", { tail }); } void Xpl2Client::imageCount () { sendCommand (&m_imagingSocket, "m3"); } /* ------------------------------------------------------------------ */ /* Send / dispatch */ /* ------------------------------------------------------------------ */ void Xpl2Client::sendCommand (QTcpSocket *socket, const QByteArray &command, const QVariantList ¶ms) { if (!socket || 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 == "EV_SHUTTING_DOWN") emit shuttingDown (); else if (msg.command == "GS_JC_VERSION") handleGsJcVersion (msg.params); else if (msg.command == "GS_PH_VERSION") handleGsPhVersion (msg.params); else if (!dispatchResponse (msg.command, msg.params, &m_commandSocket)) 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 if (msg.command == "EV_SHUTTING_DOWN") emit shuttingDown (); else if (msg.command == "n") { int lines = 0; if (!msg.params.isEmpty ()) lines = msg.params[0].toString ().toInt (nullptr, 16); qDebug ("%s n imageLines=%d", qPrintable (logTag (&m_imagingSocket)), lines); emit statusMessage (QStringLiteral ("RX n: imageLines=%1").arg (lines)); emit imagingReply (lines); } else if (msg.command == "m4") { bool ok = !msg.params.isEmpty () && msg.params[0].toInt () == 1; qDebug ("%s m4 success=%d", qPrintable (logTag (&m_imagingSocket)), ok); emit statusMessage (QStringLiteral ("RX m4: success=%1").arg (ok)); emit imagingStopResult (ok); } else if (!dispatchResponse (msg.command, msg.params, &m_imagingSocket)) 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 == "EV_SHUTTING_DOWN") emit shuttingDown (); else if (msg.command == "EV_STATUS_MSG_JC") handleEvStatusMsgJc (msg.params); else if (msg.command == "EV_STATUS_MSG_PH") handleEvStatusMsgPh (msg.params); else if (!dispatchResponse (msg.command, msg.params, &m_statusSocket)) 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); } bool Xpl2Client::dispatchResponse (const QByteArray &command, const QVariantList ¶ms, const QTcpSocket *socket) { auto it = s_responseTable.find (command); if (it == s_responseTable.end ()) return false; const ResponseEntry &entry = *it; if (params.size () < entry.minParams) { qWarning ("%s %s: expected %d params, got %lld", qPrintable (logTag (socket)), command.constData (), entry.minParams, static_cast (params.size ())); return true; } int cid = params[0].toInt (); QString cmd = QString::fromUtf8 (command); QString logStr; switch (entry.shape) { case ResponseShape::JcSuccess: logStr = QStringLiteral ("RX %1: controller=%2 success=%3") .arg (cmd) .arg (cid) .arg (params[1].toInt () == 1); break; case ResponseShape::PhSuccess: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 success=%4") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toInt () == 1); break; case ResponseShape::CalData: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3") .arg (cmd) .arg (cid) .arg (params[1].toInt ()); break; case ResponseShape::BaseFreq: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 base=%4 active=%5") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toDouble ()) .arg (params[3].toDouble ()); break; case ResponseShape::StatusStart: logStr = QStringLiteral ("RX %1: controller=%2 level=%3 interval=%4 " "success=%5") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toInt ()) .arg (params[3].toInt () == 1); break; case ResponseShape::PurgeSettings: logStr = QStringLiteral ( "RX %1: controller=%2 interval=%3 time=%4 success=%5") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toInt ()) .arg (params[3].toInt () == 1); break; case ResponseShape::JcJettingParams: logStr = QStringLiteral ("RX %1: controller=%2 result=%3") .arg (cmd) .arg (cid) .arg (params[6].toInt ()); break; case ResponseShape::PhJettingParams: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 result=%4") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[7].toInt ()); break; case ResponseShape::JcSetter: logStr = QStringLiteral ("RX %1: controller=%2 setter=%3 result=%4") .arg (cmd) .arg (cid) .arg (params[2].toInt ()) .arg (params[4].toInt ()); break; case ResponseShape::PhSetter: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 setter=%4 result=%5") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[3].toInt ()) .arg (params[5].toInt ()); break; case ResponseShape::JcGetter: logStr = QStringLiteral ("RX %1: controller=%2 getter=%3 value=%4 " "success=%5") .arg (cmd) .arg (cid) .arg (params[2].toInt ()) .arg (params[3].toString ()) .arg (params[4].toInt () == 1); break; case ResponseShape::PhGetter: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 getter=%4 " "value=%5 success=%6") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[3].toInt ()) .arg (params[4].toString ()) .arg (params[5].toInt () == 1); break; case ResponseShape::PhConnectionChanged: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 connected=%4") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toInt () == 1); break; case ResponseShape::ErrorCode: logStr = QStringLiteral ("RX %1: controller=%2 error=%3") .arg (cmd) .arg (cid) .arg (params[1].toInt ()); break; case ResponseShape::PhErrorCode: logStr = QStringLiteral ("RX %1: controller=%2 ph=%3 error=%4") .arg (cmd) .arg (cid) .arg (params[1].toInt ()) .arg (params[2].toInt ()); break; } qDebug ("%s %s", qPrintable (logTag (socket)), qPrintable (logStr)); emit statusMessage (logStr); entry.emitter (this, params); return true; } void Xpl2Client::handleEvStatusMsgJc (const QVariantList ¶ms) { Xpl2JcStatus status = Xpl2JcStatus::fromParams (params); qDebug ("%s EV_STATUS_MSG_JC controller=%d level=%d temp=%.1f cpu=%.1f%%", qPrintable (logTag (&m_statusSocket)), status.controllerId, status.statusLevel, status.temperature, status.cpuPercentageBusy); emit statusMessage ( QStringLiteral ("RX EV_STATUS_MSG_JC: controller=%1 level=%2") .arg (status.controllerId) .arg (status.statusLevel)); emit jcStatusReceived (status); } void Xpl2Client::handleEvStatusMsgPh (const QVariantList ¶ms) { Xpl2PhStatus status = Xpl2PhStatus::fromParams (params); qDebug ("%s EV_STATUS_MSG_PH controller=%d level=%d ph=%d temp=%.1f", qPrintable (logTag (&m_statusSocket)), status.controllerId, status.statusLevel, status.printheadId, status.temperature); emit statusMessage ( QStringLiteral ("RX EV_STATUS_MSG_PH: controller=%1 level=%2 ph=%3") .arg (status.controllerId) .arg (status.statusLevel) .arg (status.printheadId)); emit phStatusReceived (status); } /* ------------------------------------------------------------------ */ /* 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_commandPort + 1; } else if (socket == &m_statusSocket) { name = "Status"; port = m_commandPort + 2; } return QStringLiteral ("[%1:%2]").arg (name).arg (port).leftJustified (15); } /* ------------------------------------------------------------------ */ /* Socket slots */ /* ------------------------------------------------------------------ */ void Xpl2Client::onSocketConnected () { auto *socket = qobject_cast (sender ()); qInfo ("%s Connected to proxy", qPrintable (logTag (socket))); updateConnectedState (); if (m_connected) m_retryTimer.stop (); } void Xpl2Client::onSocketDisconnected () { auto *socket = qobject_cast (sender ()); qInfo ("%s Disconnected from proxy", qPrintable (logTag (socket))); updateConnectedState (); if (!m_retryTimer.isActive ()) m_retryTimer.start (2000); } 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 ())); } void Xpl2Client::retryConnection () { if (m_connected) { m_retryTimer.stop (); return; } connectToProxy (); }