// controllerlink.cpp - RemoteClient in its own thread, results as signals (see controllerlink.h) #include "controllerlink.h" #include #include #include #include #include #include #include #include namespace AdvCtrl { // Owns the serial port: everything below run() happens in this thread. Results go to the // ControllerLink as queued calls, so they arrive in the thread of the ControllerLink. class LinkThread : public QThread { public: LinkThread(ControllerLink *link, const QString &portName, int baudRate) : m_link(link), m_portName(portName), m_baudRate(baudRate) { } void post(const QString &commandLine) { QMutexLocker lock(&m_mutex); m_queue.enqueue(commandLine); m_wake.wakeOne(); } void stop() { requestInterruption(); QMutexLocker lock(&m_mutex); m_wake.wakeOne(); } protected: void run() override; private: bool takeCommand(QString *commandLine, int waitMs); void dropCommands(const QString &why); template void toLink(Function &&function) { QMetaObject::invokeMethod(m_link, std::forward(function), Qt::QueuedConnection); } void reportError(const QString &message) { ControllerLink *const link = m_link; toLink([link, message] { emit link->error(message); }); } ControllerLink *const m_link; const QString m_portName; const int m_baudRate; QMutex m_mutex; QWaitCondition m_wake; QQueue m_queue; }; void LinkThread::run() { ControllerLink *const link = m_link; RemoteClient rc; if (!rc.open(m_portName, m_baudRate)) { reportError(QStringLiteral("cannot open %1: %2").arg(m_portName, rc.errorString())); toLink([link] { link->handleClosed(); }); return; } bool detected = false; bool ledsSupported = true; int failures = 0; // consecutive requests without an answer QElapsedTimer clock; clock.start(); qint64 nextLeds = 0; qint64 nextDetect = 0; while (!isInterruptionRequested() && !rc.portFailed()) { if (!detected) { dropCommands(QStringLiteral("controller not connected")); if (clock.elapsed() >= nextDetect) { nextDetect = clock.elapsed() + ControllerLink::DetectRetryMs; if (rc.detect()) { detected = true; failures = 0; Fields ident; bool known = false; for (int i = 0; i < 3 && !known; ++i) { // ID; a late PONG may come first known = rc.identify(&ident); if (!known) reportError(QStringLiteral("ID: ") + rc.lastReply().toString()); if (rc.portFailed() || isInterruptionRequested()) break; } const QString unknown = QStringLiteral("?"); const QString firmware = known ? ident.value(QStringLiteral("FW"), unknown) : unknown; const QString cameras = known ? ident.value(QStringLiteral("CAMS"), unknown) : unknown; const QString uid = known ? ident.value(QStringLiteral("UID"), unknown) : unknown; toLink([link, firmware, cameras, uid] { link->handleDetected(firmware, cameras, uid); }); } else if (!rc.portFailed() && !isInterruptionRequested()) { toLink([link] { link->handleNotDetected(); }); } } QThread::msleep(50); continue; } QString commandLine; if (takeCommand(&commandLine, 50)) { const Reply r = rc.command(commandLine); QString result; if (r.status == Reply::Ok || r.status == Reply::DeviceError) { failures = 0; // ERR is an answer, not a link failure result = r.toString(); } else { ++failures; result = QStringLiteral("!! ") + r.text; } toLink([link, commandLine, result] { emit link->reply(commandLine, result); }); } else if (clock.elapsed() >= nextLeds) { nextLeds = clock.elapsed() + ControllerLink::LedsPeriodMs; if (ledsSupported) { quint32 mask = 0; if (rc.leds(&mask)) { failures = 0; toLink([link, mask] { emit link->leds(mask); }); } else if (rc.lastReply().status == Reply::DeviceError) { ledsSupported = false; reportError(QStringLiteral("LEDS not supported by this firmware (%1) - firmware update required") .arg(rc.lastReply().text)); } else { ++failures; reportError(rc.lastReply().text); } } else if (rc.ping() || rc.lastReply().status == Reply::Ok || rc.lastReply().status == Reply::DeviceError) { failures = 0; // old firmware: keep the link check alive } else { ++failures; reportError(rc.lastReply().text); } } if (failures >= ControllerLink::LostAfter) { detected = false; // back to detection, retried every 2 s nextDetect = 0; toLink([link] { link->handleLost(); }); } } if (rc.portFailed()) // e.g. the USB adapter was unplugged reportError(m_portName + QStringLiteral(": ") + rc.errorString()); rc.close(); toLink([link] { link->handleClosed(); }); } bool LinkThread::takeCommand(QString *commandLine, int waitMs) { QMutexLocker lock(&m_mutex); if (m_queue.isEmpty() && !isInterruptionRequested()) m_wake.wait(&m_mutex, waitMs); if (m_queue.isEmpty()) return false; *commandLine = m_queue.dequeue(); return true; } void LinkThread::dropCommands(const QString &why) { QQueue dropped; { QMutexLocker lock(&m_mutex); dropped.swap(m_queue); } ControllerLink *const link = m_link; const QString result = QStringLiteral("!! ") + why; for (const QString &commandLine : std::as_const(dropped)) toLink([link, commandLine, result] { emit link->reply(commandLine, result); }); } // ------------------------------------------------------------------ ControllerLink ControllerLink::ControllerLink(QObject *parent) : QObject(parent) { } ControllerLink::~ControllerLink() { if (m_thread) { m_thread->stop(); m_thread->wait(); delete m_thread; } } ControllerLink::State ControllerLink::state() const { return m_state; } QString ControllerLink::portName() const { return m_portName; } void ControllerLink::open(const QString &portName, int baudRate) { if (m_thread) // already open: close() first return; m_portName = portName; m_thread = new LinkThread(this, portName, baudRate); m_thread->start(); setState(NoLink); } void ControllerLink::close() { if (m_thread) m_thread->stop(); } void ControllerLink::send(const QString &commandLine) { if (m_thread) m_thread->post(commandLine); } void ControllerLink::setState(State state) { if (state == m_state) return; m_state = state; emit stateChanged(state); } void ControllerLink::handleDetected(const QString &firmware, const QString &cameras, const QString &uid) { if (!m_thread) return; setState(Link); emit detected(firmware, cameras, uid); } void ControllerLink::handleNotDetected() { if (!m_thread) return; setState(NoLink); emit notDetected(); } void ControllerLink::handleLost() { if (!m_thread) return; setState(NoLink); emit lost(); } void ControllerLink::handleClosed() { if (m_thread) { m_thread->wait(); delete m_thread; m_thread = nullptr; } m_portName.clear(); setState(Closed); emit closed(); } } // namespace AdvCtrl