280 lines
8.3 KiB
C++
280 lines
8.3 KiB
C++
// controllerlink.cpp - RemoteClient in its own thread, results as signals (see controllerlink.h)
|
|
|
|
#include "controllerlink.h"
|
|
|
|
#include <QElapsedTimer>
|
|
#include <QMetaObject>
|
|
#include <QMutex>
|
|
#include <QMutexLocker>
|
|
#include <QQueue>
|
|
#include <QThread>
|
|
#include <QWaitCondition>
|
|
|
|
#include <utility>
|
|
|
|
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 <typename Function>
|
|
void toLink(Function &&function)
|
|
{
|
|
QMetaObject::invokeMethod(m_link, std::forward<Function>(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<QString> 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<QString> 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
|