diff --git a/QLog.pro b/QLog.pro index c40c6844..35f07d3a 100644 --- a/QLog.pro +++ b/QLog.pro @@ -78,6 +78,7 @@ SOURCES += \ awards/AwardWPX.cpp \ awards/AwardWWFF.cpp \ awards/BandTableAward.cpp \ + core/AdifUDPReceiver.cpp \ core/AlertEvaluator.cpp \ core/AdifRecovery.cpp \ core/AppGuard.cpp \ @@ -263,6 +264,7 @@ HEADERS += \ awards/AwardWPX.h \ awards/AwardWWFF.h \ awards/BandTableAward.h \ + core/AdifUDPReceiver.h \ core/AlertEvaluator.h \ core/AdifRecovery.h \ core/AppGuard.h \ diff --git a/core/AdifUDPReceiver.cpp b/core/AdifUDPReceiver.cpp new file mode 100644 index 00000000..e757256a --- /dev/null +++ b/core/AdifUDPReceiver.cpp @@ -0,0 +1,219 @@ +#include +#include +#include +#include +#include + +#include "AdifUDPReceiver.h" +#include "core/LogParam.h" +#include "core/debug.h" +#include "logformat/AdiFormat.h" + +MODULE_IDENTIFICATION("qlog.core.adifudp"); + +AdifUDPReceiver::AdifUDPReceiver(QObject *parent) : + QObject(parent), + socket(new QUdpSocket(this)) +{ + FCT_IDENTIFICATION; + + connect(socket, &QUdpSocket::readyRead, this, &AdifUDPReceiver::readPendingDatagrams); + reloadSetting(); +} + +quint16 AdifUDPReceiver::getConfigPort() +{ + FCT_IDENTIFICATION; + + const quint16 ret = LogParam::getNetworkAdifListenerPort(DEFAULT_PORT); + + qCDebug(runtime) << "ADIF UDP configured port" << ret; + + return ret; +} + +void AdifUDPReceiver::saveConfigPort(quint16 port) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << port; + + LogParam::setNetworkAdifListenerPort(port); +} + +bool AdifUDPReceiver::getConfigEnabled() +{ + FCT_IDENTIFICATION; + + const bool ret = LogParam::getNetworkAdifListenerEnabled(); + + qCDebug(runtime) << "ADIF UDP listener enabled" << ret; + + return ret; +} + +void AdifUDPReceiver::saveConfigEnabled(bool enabled) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << enabled; + + LogParam::setNetworkAdifListenerEnabled(enabled); +} + +void AdifUDPReceiver::reloadSetting() +{ + FCT_IDENTIFICATION; + + const bool enabled = getConfigEnabled(); + + qCDebug(runtime) << "Reloading ADIF UDP listener settings; enabled" << enabled; + + if ( enabled ) + openPort(); + else + closePort(); +} + +void AdifUDPReceiver::openPort() +{ + FCT_IDENTIFICATION; + + if ( !socket ) + { + qWarning() << "Cannot open ADIF UDP port - socket is not initialized"; + return; + } + + if ( socket->state() == QAbstractSocket::BoundState ) + { + qCDebug(runtime) << "Closing previously bound ADIF UDP port"; + socket->close(); + } + + const quint16 newPort = getConfigPort(); + + qCDebug(runtime) << "ADIF UDP listen port" << newPort; + + if ( !socket->bind(QHostAddress::Any, newPort) ) + { + qWarning() << "Cannot bind the Port for ADIF UDP"; + return; + } + + qCDebug(runtime) << "ADIF UDP listening on all interfaces"; +} + +void AdifUDPReceiver::closePort() +{ + FCT_IDENTIFICATION; + + if ( socket && socket->state() == QAbstractSocket::BoundState ) + { + qCDebug(runtime) << "Closing ADIF UDP listener"; + socket->close(); + } + else + { + qCDebug(runtime) << "ADIF UDP listener is already closed"; + } +} + +void AdifUDPReceiver::readPendingDatagrams() +{ + FCT_IDENTIFICATION; + + while ( socket->hasPendingDatagrams() ) + { + const QNetworkDatagram datagram = socket->receiveDatagram(); + qCDebug(runtime) << "Received ADIF UDP datagram from" << datagram.senderAddress() + << datagram.senderPort() + << "bytes" << datagram.data().size(); + processDatagram(datagram.data()); + } +} + +void AdifUDPReceiver::processDatagram(const QByteArray &data) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << "bytes" << data.size(); + + QString adif = QString::fromUtf8(data); + if ( adif.isEmpty() ) + { + qCDebug(runtime) << "ADIF UDP datagram is not valid UTF-8, trying Latin-1"; + adif = QString::fromLatin1(data); + } + + const int start = adif.indexOf(QLatin1Char('<')); + if ( start < 0 ) + { + qWarning() << "Ignoring ADIF UDP datagram without ADIF tags"; + return; + } + + if ( start > 0 ) + qCDebug(runtime) << "Skipping ADIF UDP datagram prefix bytes" << start; + + emitRecords(adif.mid(start)); +} + +void AdifUDPReceiver::emitRecords(const QString &adif) +{ + FCT_IDENTIFICATION; + + qCDebug(function_parameters) << "characters" << adif.size(); + + QString input(adif); + QTextStream in(&input); + AdiFormat adi(in); + + QSqlTableModel model; + model.setTable("contacts"); + model.removeColumn(model.fieldIndex("id")); + + bool imported = false; + int recordCount = 0; + while ( true ) + { + QSqlRecord record = model.record(0); + if ( !adi.importNext(record) ) + break; + + if ( record.value("callsign").toString().isEmpty() ) + { + qWarning() << "Ignoring ADIF UDP record without callsign" << record; + continue; + } + + const int qslSentIndex = record.indexOf("qsl_sent"); + if ( qslSentIndex >= 0 ) + record.remove(qslSentIndex); + + const int lotwQslSentIndex = record.indexOf("lotw_qsl_sent"); + if ( lotwQslSentIndex >= 0 ) + record.remove(lotwQslSentIndex); + + const int eqslQslSentIndex = record.indexOf("eqsl_qsl_sent"); + if ( eqslQslSentIndex >= 0 ) + record.remove(eqslQslSentIndex); + + qCDebug(runtime) << "Emitting ADIF UDP contact" + << record.value("callsign").toString() + << record.value("start_time") + << record.value("freq") + << record.value("band") + << record.value("mode"); + qCDebug(function_parameters) << record; + + emit addContact(record); + imported = true; + recordCount++; + } + + if ( !imported ) + qWarning() << "ADIF UDP datagram did not contain an importable QSO record"; + else + qCDebug(runtime) << "Imported ADIF UDP records" << recordCount; +} diff --git a/core/AdifUDPReceiver.h b/core/AdifUDPReceiver.h new file mode 100644 index 00000000..7a3e851b --- /dev/null +++ b/core/AdifUDPReceiver.h @@ -0,0 +1,40 @@ +#ifndef QLOG_CORE_ADIFUDPRECEIVER_H +#define QLOG_CORE_ADIFUDPRECEIVER_H + +#include +#include + +class QUdpSocket; + +class AdifUDPReceiver : public QObject +{ + Q_OBJECT +public: + explicit AdifUDPReceiver(QObject *parent = nullptr); + + static quint16 getConfigPort(); + static void saveConfigPort(quint16 port); + static bool getConfigEnabled(); + static void saveConfigEnabled(bool enabled); + +signals: + void addContact(QSqlRecord record); + +public slots: + void reloadSetting(); + +private slots: + void readPendingDatagrams(); + +private: + void openPort(); + void closePort(); + void processDatagram(const QByteArray &data); + void emitRecords(const QString &adif); + + QUdpSocket *socket; + + static const int DEFAULT_PORT = 2333; +}; + +#endif // QLOG_CORE_ADIFUDPRECEIVER_H diff --git a/core/LogParam.cpp b/core/LogParam.cpp index 2e4421f9..8793bbcd 100644 --- a/core/LogParam.cpp +++ b/core/LogParam.cpp @@ -777,6 +777,26 @@ void LogParam::setNetworkWsjtxListenerMulticastTTL(int ttl) setParam("network/listener/wsjtx/multicast/ttl", ttl); } +bool LogParam::getNetworkAdifListenerEnabled() +{ + return getParam("network/listener/adif/enabled", false).toBool(); +} + +void LogParam::setNetworkAdifListenerEnabled(bool state) +{ + setParam("network/listener/adif/enabled", state); +} + +int LogParam::getNetworkAdifListenerPort(int defaultPort) +{ + return getParam("network/listener/adif/port", defaultPort).toInt(); +} + +void LogParam::setNetworkAdifListenerPort(int port) +{ + setParam("network/listener/adif/port", port); +} + QStringList LogParam::getEnabledMemberlists() { return getParamStringList("memberlist/enabledlists"); diff --git a/core/LogParam.h b/core/LogParam.h index a1f5bacc..12b7900b 100644 --- a/core/LogParam.h +++ b/core/LogParam.h @@ -243,6 +243,10 @@ class LogParam : public QObject static void setNetworkWsjtxListenerMulticastAddr(const QString &addr); static int getNetworkWsjtxListenerMulticastTTL(); static void setNetworkWsjtxListenerMulticastTTL(int ttl); + static bool getNetworkAdifListenerEnabled(); + static void setNetworkAdifListenerEnabled(bool state); + static int getNetworkAdifListenerPort(int defaultPort); + static void setNetworkAdifListenerPort(int port); /******************** * Club Member Lists diff --git a/ui/MainWindow.cpp b/ui/MainWindow.cpp index b3b4120c..1d0382fc 100644 --- a/ui/MainWindow.cpp +++ b/ui/MainWindow.cpp @@ -23,6 +23,7 @@ #include "rotator/Rotator.h" #include "cwkey/CWKeyer.h" #include "core/WsjtxUDPReceiver.h" +#include "core/AdifUDPReceiver.h" #include "core/debug.h" #include "ui/NewContactWidget.h" #include "ui/QSOFilterDialog.h" @@ -69,7 +70,9 @@ MainWindow::MainWindow(QWidget* parent) : ui(new Ui::MainWindow), stats(new StatisticsWidget), clublogRT(new ClubLogUploader(this)), - adifRecoveryManager(new AdifRecoveryManager(this)) + adifRecoveryManager(new AdifRecoveryManager(this)), + wsjtx(nullptr), + adifUDP(nullptr) { FCT_IDENTIFICATION; @@ -299,6 +302,9 @@ MainWindow::MainWindow(QWidget* parent) : FldigiTCPServer* fldigi = new FldigiTCPServer(this); connect(fldigi, &FldigiTCPServer::addContact, ui->newContactWidget, &NewContactWidget::saveExternalContact); + adifUDP = new AdifUDPReceiver(this); + connect(adifUDP, &AdifUDPReceiver::addContact, ui->newContactWidget, &NewContactWidget::saveExternalContact); + connect(adifRecoveryManager, &AdifRecoveryManager::contactsRecovered, ui->logbookWidget, &LogbookWidget::updateTable); connect(adifRecoveryManager, &AdifRecoveryManager::problem, this, [this](const QString &message) { @@ -325,6 +331,7 @@ MainWindow::MainWindow(QWidget* parent) : connect(ui->wsjtxWidget, &WsjtxWidget::modeChanged, ui->newContactWidget, &NewContactWidget::changeModefromRig); connect(this, &MainWindow::settingsChanged, wsjtx, &WsjtxUDPReceiver::reloadSetting); + connect(this, &MainWindow::settingsChanged, adifUDP, &AdifUDPReceiver::reloadSetting); connect(this, &MainWindow::settingsChanged, adifRecoveryManager, &AdifRecoveryManager::reloadSettings); connect(this, &MainWindow::settingsChanged, ui->rotatorWidget, &RotatorWidget::reloadSettings); connect(this, &MainWindow::settingsChanged, ui->rigWidget, &RigWidget::reloadSettings); @@ -2251,6 +2258,8 @@ MainWindow::~MainWindow() clublogRT->deleteLater(); if ( wsjtx ) wsjtx->deleteLater(); + if ( adifUDP ) + adifUDP->deleteLater(); seqGroup->deleteLater(); dupeGroup->deleteLater(); diff --git a/ui/MainWindow.h b/ui/MainWindow.h index 143664da..9d9efff0 100644 --- a/ui/MainWindow.h +++ b/ui/MainWindow.h @@ -16,6 +16,7 @@ class MainWindow; class QLabel; class WsjtxUDPReceiver; +class AdifUDPReceiver; class AdifRecoveryManager; class MainWindow : public QMainWindow { @@ -121,6 +122,7 @@ private slots: ClubLogUploader* clublogRT; AdifRecoveryManager* adifRecoveryManager; WsjtxUDPReceiver* wsjtx; + AdifUDPReceiver* adifUDP; QActionGroup *seqGroup; QActionGroup *dupeGroup; QActionGroup *linkExchangeGroup; diff --git a/ui/SettingsDialog.cpp b/ui/SettingsDialog.cpp index 9d2cf0b2..0c680178 100644 --- a/ui/SettingsDialog.cpp +++ b/ui/SettingsDialog.cpp @@ -34,6 +34,7 @@ #include "data/Data.h" #include "data/Gridsquare.h" #include "core/WsjtxUDPReceiver.h" +#include "core/AdifUDPReceiver.h" #include "core/NetworkNotification.h" #include "rig/Rig.h" #include "rig/RigCaps.h" @@ -686,6 +687,15 @@ void SettingsDialog::save() } } + if ( ui->adifUdpEnabledCheckBox->isChecked() + && ui->adifUdpPortSpin->value() == ui->wsjtPortSpin->value() ) + { + ui->tabWidget->setCurrentIndex(8); + QMessageBox::warning(nullptr, QMessageBox::tr("QLog Warning"), + QMessageBox::tr("ADIF UDP Listener cannot use the same port as the WSJT-X receiving port.")); + return; + } + writeSettings(); accept(); } @@ -2754,6 +2764,8 @@ void SettingsDialog::readSettings() ui->wsjtMulticastAddressEdit->setText(WsjtxUDPReceiver::getConfigMulticastAddress()); ui->wsjtMulticastTTLSpin->setValue(WsjtxUDPReceiver::getConfigMulticastTTL()); ui->wsjtColorCqSpotsCheckbox->setChecked(WsjtxUDPReceiver::getConfigOutputColorCQSpot()); + ui->adifUdpEnabledCheckBox->setChecked(AdifUDPReceiver::getConfigEnabled()); + ui->adifUdpPortSpin->setValue(AdifUDPReceiver::getConfigPort()); ui->notifLogIDEdit->setText(LogParam::getLogID()); ui->notifQSOEdit->setText(NetworkNotification::getNotifQSOAdiAddrs()); @@ -2893,6 +2905,8 @@ void SettingsDialog::writeSettings() WsjtxUDPReceiver::saveConfigMulticastAddress(ui->wsjtMulticastAddressEdit->text()); WsjtxUDPReceiver::saveConfigMulticastTTL(ui->wsjtMulticastTTLSpin->value()); WsjtxUDPReceiver::saveConfigOutputColorCQSpot(ui->wsjtColorCqSpotsCheckbox->isChecked()); + AdifUDPReceiver::saveConfigEnabled(ui->adifUdpEnabledCheckBox->isChecked()); + AdifUDPReceiver::saveConfigPort(ui->adifUdpPortSpin->value()); NetworkNotification::saveNotifQSOAdiAddrs(ui->notifQSOEdit->text()); NetworkNotification::saveNotifDXSpotAddrs(ui->notifDXSpotsEdit->text()); NetworkNotification::saveNotifWSJTXCQSpotAddrs(ui->notifWSJTXCQSpotsEdit->text()); diff --git a/ui/SettingsDialog.ui b/ui/SettingsDialog.ui index a5486d68..573a4b54 100644 --- a/ui/SettingsDialog.ui +++ b/ui/SettingsDialog.ui @@ -4652,6 +4652,61 @@ + + + + + + ADIF UDP Listener + + + + + + Enabled + + + + + + + Enable/Disable receiving ADIF log records from other applications over UDP + + + + + + + + + + Port + + + + + + + + 0 + 0 + + + + Port where QLog listens for incoming UDP ADIF log records + + + 1025 + + + 65534 + + + 2333 + + + +