/** * @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"); } /* ------------------------------------------------------------------ */ /* 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 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 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 (); } /* ------------------------------------------------------------------ */ /* 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 ())); }