From dcf98f8beee2cf14e33fd5048fc053df629cb635 Mon Sep 17 00:00:00 2001 From: aa5sh <84428382+aa5sh@users.noreply.github.com> Date: Sun, 10 Aug 2025 13:59:43 -0500 Subject: [PATCH] DXLab Commander Server This is an initial attempt at a proof of concept. We often see requests for ways to use other applications that need rig control, but if using rig control in QLog it is limited to the typical single connection of the serial port. This adds a basic DXLab Commander Server to QLog so applications like wsjtx can be set to talk to port 52002 using the DXLab Commander radio option. I was curious if it would work and this does seem to be a possible option to be considered. --- QLog.pro | 6 +- core/dxlabserver.cpp | 440 +++++++++++++++++++++++++++++++++++++++++++ core/dxlabserver.h | 102 ++++++++++ ui/MainWindow.cpp | 11 ++ ui/MainWindow.h | 2 + 5 files changed, 559 insertions(+), 2 deletions(-) create mode 100644 core/dxlabserver.cpp create mode 100644 core/dxlabserver.h diff --git a/QLog.pro b/QLog.pro index c904102c..6ef75664 100644 --- a/QLog.pro +++ b/QLog.pro @@ -62,6 +62,7 @@ SOURCES += \ core/QSOFilterManager.cpp \ core/WsjtxUDPReceiver.cpp \ core/debug.cpp \ + core/dxlabserver.cpp \ core/main.cpp \ core/zonedetect.c \ cwkey/CWKeyer.cpp \ @@ -192,6 +193,7 @@ HEADERS += \ core/QuadKeyCache.h \ core/WsjtxUDPReceiver.h \ core/debug.h \ + core/dxlabserver.h \ core/zonedetect.h \ cwkey/CWKeyer.h \ cwkey/drivers/CWCatKey.h \ @@ -476,8 +478,8 @@ macx: { INSTALLS += target } - INCLUDEPATH += /usr/local/include /opt/homebrew/include - LIBS += -L/usr/local/lib -L/opt/homebrew/lib -lhamlib -lsqlite3 + INCLUDEPATH += /usr/local/include /opt/homebrew/include /opt/local/include + LIBS += -L/usr/local/lib -L/opt/homebrew/lib -lhamlib -lsqlite3 -L/opt/local/lib equals(QT_MAJOR_VERSION, 6): LIBS += -lqt6keychain equals(QT_MAJOR_VERSION, 5): LIBS += -lqt5keychain DISTFILES += diff --git a/core/dxlabserver.cpp b/core/dxlabserver.cpp new file mode 100644 index 00000000..ed48aebf --- /dev/null +++ b/core/dxlabserver.cpp @@ -0,0 +1,440 @@ +#include "dxlabserver.h" +#include +#include "debug.h" + +MODULE_IDENTIFICATION("qlog.core.dxlabcommanderserver"); + +static inline bool eq(const QString &a, const char *b) +{ return a.compare(QLatin1String(b), Qt::CaseInsensitive) == 0; } + +DxlabServer::DxlabServer(Rig *rig, QObject *parent) + : QObject(parent), _rig(rig) +{ + FCT_IDENTIFICATION; + buildModeMappingHash(); + connect(&_srv, &QTcpServer::newConnection, this, &DxlabServer::onNewConn); +} + +DxlabServer::~DxlabServer() { stop(); } + +bool DxlabServer::start(quint16 port) { + FCT_IDENTIFICATION; + + if (_srv.isListening()) stop(); + if (!_srv.listen(QHostAddress::Any, port)) { + emit warn(QStringLiteral("Commander TCP listen failed on %1: %2") + .arg(port).arg(_srv.errorString())); + return false; + } + emit info(QStringLiteral("Commander TCP listening on %1").arg(port)); + return true; +} + +void DxlabServer::stop() { + FCT_IDENTIFICATION; + + for (QTcpSocket *s : _srv.findChildren()) { + s->disconnect(this); s->close(); s->deleteLater(); + } + _srv.close(); +} + +void DxlabServer::onNewConn() { + FCT_IDENTIFICATION; + + while (_srv.hasPendingConnections()) { + auto *s = _srv.nextPendingConnection(); + auto *st = new ClientState(); + st->flushTimer.setSingleShot(true); + st->flushTimer.setInterval(50); // ms; tune if needed + // When timer fires, finalize any pending command + connect(&st->flushTimer, &QTimer::timeout, this, [this, s, st](){ + if (!st->inflight.name.isEmpty()) { + processInflight(s, st->inflight); + st->inflight = Inflight{}; + } + }); + s->setProperty("state", QVariant::fromValue(reinterpret_cast(st))); + + connect(s, &QTcpSocket::readyRead, this, &DxlabServer::onReady); + connect(s, &QTcpSocket::disconnected, this, &DxlabServer::onGone); + emit info(QStringLiteral("Commander client %1 connected").arg(s->peerAddress().toString())); + } +} + +void DxlabServer::onGone() { + FCT_IDENTIFICATION; + + auto *s = qobject_cast(sender()); + if (!s) return; + if (auto p = s->property("state"); p.isValid()) + delete reinterpret_cast(p.value()); + s->deleteLater(); +} + +void DxlabServer::onReady() { + FCT_IDENTIFICATION; + + auto *s = qobject_cast(sender()); + if (!s) return; + auto p = s->property("state"); if (!p.isValid()) return; + auto *st = reinterpret_cast(p.value()); + + st->rx += s->readAll(); + + Token t; + while (extractOneToken(st->rx, t)) handleToken(s, *st, t); +} + +bool DxlabServer::extractOneToken(QByteArray &buf, Token &tok) { + FCT_IDENTIFICATION; + + tok = Token{}; + int lt = buf.indexOf('<'); if (lt < 0) return false; + if (lt > 0) buf.remove(0, lt); + int colon = buf.indexOf(':', lt+1); + int gt = buf.indexOf('>', colon+1); + if (colon < 0 || gt < 0) return false; + QByteArray nameBA = buf.mid(lt+1, colon-(lt+1)); + QByteArray lenBA = buf.mid(colon+1, gt-(colon+1)); + bool ok=false; int n = lenBA.toInt(&ok); + if (!ok || n < 0) { buf.remove(0, gt+1); return true; } + int need = gt + 1 + n; + if (buf.size() < need) return false; + tok.name = QString::fromLatin1(nameBA); + tok.len = n; + tok.val = buf.mid(gt+1, n); + buf.remove(0, need); + return true; +} + +void DxlabServer::handleToken(QTcpSocket *s, ClientState &st, const Token &t) { + FCT_IDENTIFICATION; + + // New command starts a new in-flight; if something was pending, finalize it first + + if (t.name.compare("command", Qt::CaseInsensitive) == 0) { + if (!st.inflight.name.isEmpty()) { + processInflight(s, st.inflight); + st.inflight = Inflight{}; + } + st.inflight.name = QString::fromLatin1(t.val); + st.inflight.params.clear(); + st.inflight.sawParameters = false; + st.flushTimer.start(); // safety for commands with no parameters + return; + } + + if (t.name.compare("parameters", Qt::CaseInsensitive) == 0) { + st.inflight.sawParameters = true; + QByteArray sub = t.val; + Token p; + while (extractOneToken(sub, p)) { + st.inflight.params.insert(p.name, p.val); + } + st.flushTimer.stop(); + // We now have all parameters: execute exactly once + if (!st.inflight.name.isEmpty()) { + processInflight(s, st.inflight); + st.inflight = Inflight{}; + } + return; + } + + // Loose parameter outside — accept and accumulate + if (!st.inflight.name.isEmpty()) { + st.inflight.params.insert(t.name, t.val); + // We'll execute on next or when the timer fires + return; + } + qDebug() << "Unprocessed" << t.name << t.val; +} + + + +void DxlabServer::sendToken(QTcpSocket *s, const QString &name, const QByteArray &val) { + FCT_IDENTIFICATION; + + QByteArray header = "<" + name.toLatin1() + ":" + QByteArray::number(val.size()) + ">"; + s->write(header); s->write(val); s->flush(); +} + +// ===== Replies ===== +void DxlabServer::replyRxFreqStd(QTcpSocket *s) { + FCT_IDENTIFICATION; + + double rxHz = currFrequency * 1000; + QByteArray tok = QString::number(rxHz,'f',3).toUtf8(); + sendToken(s, "CmdFreq", tok); + +} + +void DxlabServer::replyTxFreqStd(QTcpSocket *s) { + FCT_IDENTIFICATION; + + double rxHz = currFrequency * 1000; + QByteArray tok = QString::number(rxHz,'f',3).toUtf8(); + sendToken(s, "CmdFreq", tok); +} + +void DxlabServer::replyMode(QTcpSocket *s) { + FCT_IDENTIFICATION; + + QString mode; + RigMode rigmode = raw2ADIFModeMapping.value(rawModeText); + + if(rigmode.mode == "SSB" && rigmode.submode == "USB" && rigmode.digiMode) { mode = "Data-U";} + if(rigmode.mode == "SSB" && rigmode.submode == "LSB" && rigmode.digiMode) { mode = "Data-L";} + if(rigmode.mode == "SSB" && rigmode.submode == "USB" && !rigmode.digiMode) { mode = "USB";} + if(rigmode.mode == "SSB" && rigmode.submode == "LSB" && !rigmode.digiMode) { mode = "LSB";} + if(rigmode.mode == "CW" ) { mode = "CW";} + if(rigmode.mode == "AM" ) { mode = "AM";} + if(rigmode.mode == "FM" ) { mode = "FM";} + + sendToken(s, "CmdMode", mode.toUtf8()); // must be spec names +} + +void DxlabServer::replySplit(QTcpSocket *s) { + FCT_IDENTIFICATION; + + sendToken(s, "CmdSplit", "OFF"); +} + +void DxlabServer::replyPTT(QTcpSocket *s) { + FCT_IDENTIFICATION; + + sendToken(s, "CmdTX", QByteArray(currPTT ? "ON" : "OFF")); +} + +bool DxlabServer::parseKhzToHz(const QByteArray &s, qint64 &outHz) { + FCT_IDENTIFICATION; + + QByteArray t = s; t.replace(",", ""); + bool ok=false; double khz = t.toDouble(&ok); + if (!ok) return false; + outHz = static_cast(khz) * 1000; + return true; +} + +static const char* kModes[] = {"AM","CW","CW-R","DATA-L","DATA-U","FM","LSB","USB","RTTY","RTTY-R","WBFM"}; +bool DxlabServer::isValidMode(const QString &m) { + FCT_IDENTIFICATION; + + for (auto *x: kModes) if (m.compare(QLatin1String(x), Qt::CaseInsensitive)==0) return true; + return false; +} + +QString DxlabServer::normMode(const QString &m) { + FCT_IDENTIFICATION; + + const QString u = m.trimmed().toUpper(); + for (auto *x: kModes) if (u == QLatin1String(x)) return QLatin1String(x); + return u; +} + +void DxlabServer::updateFrequency(VFOID vfoid, double vfoFreq, double ritFreq, double xitFreq) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid) + + currFrequency = vfoFreq; + currRIT = ritFreq; + currXIT = xitFreq; +} + +void DxlabServer::updateMode(VFOID vfoid, const QString &rawMode, const QString &mode, + const QString &submode, qint32 pb) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(submode) + Q_UNUSED(vfoid) + + currMode = mode; + currSubMode = submode; + rawModeText = rawMode; + currPBWidth = pb; +} + +void DxlabServer::updatePWR(VFOID vfoid, double pwr) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid) + + currPower = pwr; +} + +void DxlabServer::updateVFO(VFOID vfoid, const QString &vfo) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid) + + currVFO = vfo; +} + +void DxlabServer::updateXIT(VFOID vfoid, double xit) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid); + + currXIT = xit; +} + +void DxlabServer::updateRIT(VFOID vfoid, double rit) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid); + + currXIT = rit; +} + +void DxlabServer::updatePTT(VFOID vfoid, bool ptt) +{ + FCT_IDENTIFICATION; + + Q_UNUSED(vfoid); + + currPTT = ptt; +} + +void DxlabServer::rigConnected() +{ + FCT_IDENTIFICATION; + + rigOnline = true; +} + +void DxlabServer::rigDisconnected() +{ + FCT_IDENTIFICATION; + + rigOnline = false; +} + +void DxlabServer::processInflight(QTcpSocket *s, Inflight &in) { + FCT_IDENTIFICATION; + + const QString cmd = in.name; + + if (!rigOnline) return; + + if (eq(cmd, "CmdGetFreq") || eq(cmd, "CmdSendFreq")) { replyRxFreqStd(s); QThread::msleep(25); return; } + if (eq(cmd, "CmdGetTXFreq") || eq(cmd, "CmdSendTXFreq")) { replyTxFreqStd(s); QThread::msleep(25); return; } + if (eq(cmd, "CmdSendMode")) { replyMode(s); QThread::msleep(25); return; } + if (eq(cmd, "CmdSendSplit")) { replySplit(s); QThread::msleep(25); return; } + if (eq(cmd, "CmdSendTX")) { replyPTT(s); QThread::msleep(25); return; } + + if (eq(cmd, "CmdTX")) { _rig->setPTT(true); QThread::msleep(25);return; } + if (eq(cmd, "CmdRX")) { _rig->setPTT(false); QThread::msleep(25); return; } + + if (eq(cmd, "CmdSetFreqMode")) { + qint64 hz{}; const bool haveHz = parseKhzToHz(in.params.value("xcvrfreq"), hz); + const QString mode = normMode(QString::fromLatin1(in.params.value("xcvrmode"))); + const bool haveMode = isValidMode(mode); + const bool preserve = QString::fromLatin1(in.params.value("preservesplitanddual")).trimmed().compare("Y", Qt::CaseInsensitive)==0; + if (haveHz) { _rig->setFrequency(hz); currFrequency = hz / 1e6; } + if (haveMode) + { + /*_rig->setModeByName(mode);*/ + _qsxMode = mode; + rawModeText = mode; + if(rawModeText == "DATA-U") { _rig->setMode("SSB","USB",true);} + if(rawModeText == "DATA-L") { _rig->setMode("SSB","LSB",true);} + if(rawModeText == "USB") { _rig->setMode("SSB","USB",false);} + if(rawModeText == "LSB") { _rig->setMode("SSB","LSB",false);} + if(rawModeText == "CW") { _rig->setMode("CW","",false);} + if(rawModeText == "AM") { _rig->setMode("AM","",false);} + if(rawModeText == "FM") { _rig->setMode("FM","",false);} + if(rawModeText == "RTTY") { _rig->setMode("SSB","USB",true);} + if(rawModeText == "RTTY-R") { _rig->setMode("SSB","LSB",true);} + + qWarning() << rawModeText; + } + if (!preserve) { /* _rig->setSplit(false); */ } + QThread::msleep(25); + + return; + } + + if (eq(cmd, "CmdSetFreq")) { + qint64 hz{}; if (parseKhzToHz(in.params.value("xcvrfreq"), hz)) { + _rig->setFrequency(hz); + } + QThread::msleep(25); + return; + } + + if (eq(cmd, "CmdSetTXFreq")) { + qint64 hz{}; if (parseKhzToHz(in.params.value("xcvrfreq"), hz)) { + /* if you support a TX VFO setter, call it here */ + } + QThread::msleep(25); + return; + } + + if (eq(cmd, "CmdSetMode")) { + const QString m = normMode(QString::fromLatin1(in.params.value("1"))); // spec uses <1:n>MODE + if (isValidMode(m)) { + + /* _rig->setModeByName(m); */ rawModeText = m; + if(rawModeText == "DATA-U") { _rig->setMode("SSB","USB",true);} + if(rawModeText == "DATA-L") { _rig->setMode("SSB","LSB",true);} + if(rawModeText == "USB") { _rig->setMode("SSB","USB",false);} + if(rawModeText == "LSB") { _rig->setMode("SSB","LSB",false);} + if(rawModeText == "CW") { _rig->setMode("CW","",false);} + if(rawModeText == "AM") { _rig->setMode("AM","",false);} + if(rawModeText == "FM") { _rig->setMode("FM","",false);} + if(rawModeText == "RTTY") { _rig->setMode("SSB","USB",true);} + if(rawModeText == "RTTY-R") { _rig->setMode("SSB","LSB",true);} + } + QThread::msleep(25); + return; + } + + if (eq(cmd, "CmdSplit")) { + const QString v = QString::fromLatin1(in.params.value("1")).trimmed(); + //const bool on = (v.compare("on", Qt::CaseInsensitive)==0 || v=="1"); + /* _rig->setSplit(on); */ // you can track a local bool if needed + QThread::msleep(25); + return; + } + + if (eq(cmd, "CmdQSXSplit")) { + qint64 hz{}; if (parseKhzToHz(in.params.value("xcvrfreq"), hz)) { + /* enable split and set TX freq if your rig supports it */ + } + const bool suppressMode = QString::fromLatin1(in.params.value("SuppressModeChange")).trimmed().compare("Y", Qt::CaseInsensitive)==0; + if (!suppressMode && isValidMode(_qsxMode)) { + /* _rig->setModeByName(_qsxMode); */ + } + QThread::msleep(25); + return; + } + qDebug() << "unknown" << in.name << in.params << in.sawParameters; +} + +void DxlabServer::buildModeMappingHash() +{ + FCT_IDENTIFICATION; + + raw2ADIFModeMapping = { + { "CW", { "CW", "", false } }, + { "AM", { "AM", "", false } }, + { "CWR", { "CW", "", false } }, + { "PKTUSB", { "SSB", "USB", true } }, + { "PKTLSB", { "SSB", "LSB", true } }, + { "FM", { "FM", "", false } }, + { "FMN", { "FM", "", false } }, + { "LSB", { "SSB", "LSB", false } }, + { "RTTY", { "RTTY", "", true } }, + { "RTTYR", { "RTTY", "", true } }, + { "USB", { "SSB", "USB", false } }, + { "WFM", { "FM", "", false } } + }; + +} diff --git a/core/dxlabserver.h b/core/dxlabserver.h new file mode 100644 index 00000000..0b5d3f4b --- /dev/null +++ b/core/dxlabserver.h @@ -0,0 +1,102 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +class Rig; // forward + +class DxlabServer : public QObject { + Q_OBJECT +public: + explicit DxlabServer(Rig *rig, QObject *parent = nullptr); + ~DxlabServer() override; + + bool start(quint16 port = 52002); // Commander default: base=52000, Commander=+2 + void stop(); + bool isRunning() const { return _srv.isListening(); } + +signals: + void info(const QString&); + void warn(const QString&); + +private slots: + void onNewConn(); + void onReady(); + void onGone(); + +public slots: + void updateFrequency(VFOID, double, double, double); + void updateMode(VFOID, const QString&, const QString&, + const QString&, qint32); + void updatePWR(VFOID, double); + void updateVFO(VFOID, const QString&); + void updateXIT(VFOID, double); + void updateRIT(VFOID, double); + void updatePTT(VFOID, bool); + void rigConnected(); + void rigDisconnected(); + +private: + struct Inflight { + QString name; + QHash params; + bool sawParameters = false; + }; + struct ClientState { + QByteArray rx; + Inflight inflight; + QTimer flushTimer; // short timer to finalize commands that have no + }; + struct Token { QString name; int len = 0; QByteArray val; }; + + // parsing + static bool extractOneToken(QByteArray &buffer, Token &tok); + void handleToken(QTcpSocket *sock, ClientState &st, const Token &tok); + + // dispatch + // void handleCommand(QTcpSocket *sock, ClientState &st, const QString &cmd); + // void handleParam(QTcpSocket *sock, ClientState &st, const Token &tok); + void processInflight(QTcpSocket *sock, Inflight &in); + + // replies + void replyRxFreqStd(QTcpSocket *s); // CmdGetFreq (comma thousands + period decimal) + void replyTxFreqStd(QTcpSocket *s); // CmdGetTXFreq + void replyMode(QTcpSocket *s); // CmdSendMode + void replySplit(QTcpSocket *s); // CmdSendSplit + void replyPTT(QTcpSocket *s); // CmdSendTX + static void sendToken(QTcpSocket *sock, const QString &name, const QByteArray &value); + + // helpers + static bool parseKhzToHz(const QByteArray &s, qint64 &outHz); // accepts “14,080.055” or “14080.055” + static bool isValidMode(const QString &m); + static QString normMode(const QString &m); + void buildModeMappingHash(); + +private: + struct RigMode + { + QString mode; + QString submode; + bool digiMode; + }; + + QTcpServer _srv; + Rig *_rig = nullptr; + QLocale _loc{QLocale::system()}; + QString _qsxMode{"USB"}; + double currFrequency = 0; + double currRIT = 0; + double currXIT = 0; + QString rawModeText; + QString currMode; + QString currSubMode; + qint32 currPBWidth; + double currPower; + QString currVFO; + bool currPTT; + bool rigOnline; + QHash raw2ADIFModeMapping; +}; diff --git a/ui/MainWindow.cpp b/ui/MainWindow.cpp index 57ba9c4b..dbfd3e6f 100644 --- a/ui/MainWindow.cpp +++ b/ui/MainWindow.cpp @@ -190,6 +190,7 @@ MainWindow::MainWindow(QWidget* parent) : connect(darkLightModeSwith, &SwitchButton::stateChanged, this, &MainWindow::darkModeToggle); darkLightModeSwith->setChecked(LogParam::getMainWindowDarkMode()); + dxlab = new DxlabServer(Rig::instance()); connect(Rig::instance(), &Rig::rigErrorPresent, this, &MainWindow::rigErrorHandler); connect(Rig::instance(), &Rig::rigCWKeyOpenRequest, this, &MainWindow::cwKeyerConnectProfile); connect(Rig::instance(), &Rig::rigCWKeyCloseRequest, this, &MainWindow::cwKeyerDisconnectProfile); @@ -198,22 +199,32 @@ MainWindow::MainWindow(QWidget* parent) : connect(Rig::instance(), &Rig::frequencyChanged, ui->newContactWidget, &NewContactWidget::changeFrequency); connect(Rig::instance(), &Rig::frequencyChanged, ui->rigWidget, &RigWidget::updateFrequency); connect(Rig::instance(), &Rig::frequencyChanged, ui->dxWidget , &DxWidget::setTunedFrequency); + connect(Rig::instance(), &Rig::frequencyChanged, dxlab, &DxlabServer::updateFrequency); connect(Rig::instance(), &Rig::modeChanged, ui->bandmapWidget, &BandmapWidget::updateMode); connect(Rig::instance(), &Rig::modeChanged, ui->newContactWidget, &NewContactWidget::changeModefromRig); connect(Rig::instance(), &Rig::modeChanged, ui->rigWidget, &RigWidget::updateMode); + connect(Rig::instance(), &Rig::modeChanged, dxlab, &DxlabServer::updateMode); connect(Rig::instance(), &Rig::powerChanged, ui->newContactWidget, &NewContactWidget::changePower); connect(Rig::instance(), &Rig::powerChanged, ui->rigWidget, &RigWidget::updatePWR); + connect(Rig::instance(), &Rig::powerChanged, dxlab, &DxlabServer::updatePWR); connect(Rig::instance(), &Rig::rigConnected, ui->newContactWidget, &NewContactWidget::rigConnected); connect(Rig::instance(), &Rig::rigConnected, ui->rigWidget, &RigWidget::rigConnected); connect(Rig::instance(), &Rig::rigConnected, ui->cwconsoleWidget, &CWConsoleWidget::rigConnectHandler); + connect(Rig::instance(), &Rig::rigConnected, dxlab, &DxlabServer::rigConnected); connect(Rig::instance(), &Rig::rigDisconnected, ui->cwconsoleWidget, &CWConsoleWidget::rigDisconnectHandler); connect(Rig::instance(), &Rig::rigDisconnected, ui->newContactWidget, &NewContactWidget::rigDisconnected); connect(Rig::instance(), &Rig::rigDisconnected, ui->rigWidget, &RigWidget::rigDisconnected); + connect(Rig::instance(), &Rig::rigDisconnected, dxlab, &DxlabServer::rigDisconnected); connect(Rig::instance(), &Rig::vfoChanged, ui->rigWidget, &RigWidget::updateVFO); + connect(Rig::instance(), &Rig::vfoChanged, dxlab, &DxlabServer::updateVFO); connect(Rig::instance(), &Rig::xitChanged, ui->rigWidget, &RigWidget::updateXIT); connect(Rig::instance(), &Rig::ritChanged, ui->rigWidget, &RigWidget::updateRIT); connect(Rig::instance(), &Rig::pttChanged, ui->rigWidget, &RigWidget::updatePTT); + connect(Rig::instance(), &Rig::xitChanged, dxlab, &DxlabServer::updateXIT); + connect(Rig::instance(), &Rig::ritChanged, dxlab, &DxlabServer::updateRIT); + connect(Rig::instance(), &Rig::pttChanged, dxlab, &DxlabServer::updatePTT); connect(Rig::instance(), &Rig::rigStatusChanged, &networknotification, &NetworkNotification::rigStatus); + dxlab->start(52002); connect(Rotator::instance(), &Rotator::rotErrorPresent, this, &MainWindow::rotErrorHandler); connect(Rotator::instance(), &Rotator::positionChanged, ui->onlineMapWidget, &OnlineMapWidget::antPositionChanged); diff --git a/ui/MainWindow.h b/ui/MainWindow.h index 63c5819e..98049b42 100644 --- a/ui/MainWindow.h +++ b/ui/MainWindow.h @@ -8,6 +8,7 @@ #include "core/AlertEvaluator.h" #include "core/PropConditions.h" #include "service/clublog/ClubLog.h" +#include "core/dxlabserver.h" namespace Ui { class MainWindow; @@ -108,6 +109,7 @@ private slots: bool isFusionStyle; ClubLogUploader* clublogRT; WsjtxUDPReceiver* wsjtx; + DxlabServer* dxlab; QActionGroup *seqGroup; QActionGroup *dupeGroup; QActionGroup *linkExchangeGroup;