2015-11-21 15:23:47 -05:00
|
|
|
//
|
|
|
|
// UDPDaemon - an example console application that utilizes the WSJT-X
|
|
|
|
// messaging facility
|
|
|
|
//
|
|
|
|
// This application is only provided as a simple console application
|
|
|
|
// example to demonstrate the WSJT-X messaging facility. It allows
|
|
|
|
// the user to set the server details either as a unicast UDP server
|
|
|
|
// or, if a multicast group address is provided, as a multicast
|
|
|
|
// server. The benefit of the multicast server is that multiple
|
|
|
|
// servers can be active at once each receiving all WSJT-X broadcast
|
|
|
|
// messages and each able to respond to individual WSJT_X clients. To
|
|
|
|
// utilize the multicast group features each WSJT-X client must set
|
|
|
|
// the same multicast group address as the UDP server address for
|
|
|
|
// example 239.255.0.0 for a site local multicast group.
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <exception>
|
2021-03-28 18:29:57 -04:00
|
|
|
#include <locale>
|
2020-11-02 17:08:37 -05:00
|
|
|
#include <cstdlib>
|
2015-11-21 15:23:47 -05:00
|
|
|
|
|
|
|
#include <QCoreApplication>
|
|
|
|
#include <QCommandLineParser>
|
2020-11-02 10:33:44 -05:00
|
|
|
#include <QCommandLineOption>
|
|
|
|
#include <QString>
|
|
|
|
#include <QStringList>
|
2020-11-02 17:08:37 -05:00
|
|
|
#include <QNetworkInterface>
|
2015-11-21 15:23:47 -05:00
|
|
|
#include <QDateTime>
|
|
|
|
#include <QTime>
|
|
|
|
#include <QHash>
|
2016-06-16 08:01:32 -04:00
|
|
|
#include <QDebug>
|
2015-11-21 15:23:47 -05:00
|
|
|
|
2019-07-02 14:00:32 -04:00
|
|
|
#include "MessageServer.hpp"
|
2015-11-21 15:23:47 -05:00
|
|
|
#include "Radio.hpp"
|
|
|
|
|
|
|
|
#include "qt_helpers.hpp"
|
|
|
|
|
|
|
|
using port_type = MessageServer::port_type;
|
|
|
|
using Frequency = MessageServer::Frequency;
|
|
|
|
|
|
|
|
class Client
|
|
|
|
: public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
using ClientKey = MessageServer::ClientKey;
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
public:
|
2020-11-02 16:35:48 -05:00
|
|
|
explicit Client (ClientKey const& key, QObject * parent = nullptr)
|
2015-11-21 15:23:47 -05:00
|
|
|
: QObject {parent}
|
2020-11-02 16:35:48 -05:00
|
|
|
, key_ {key}
|
2015-11-21 15:23:47 -05:00
|
|
|
, dial_frequency_ {0u}
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& /*dx_call*/
|
2015-11-21 15:23:47 -05:00
|
|
|
, QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
|
2016-05-24 06:08:35 -04:00
|
|
|
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
|
2016-12-16 14:36:21 -05:00
|
|
|
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
|
2018-12-02 18:19:08 -05:00
|
|
|
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/
|
2019-06-25 09:35:58 -04:00
|
|
|
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
|
2020-12-20 20:31:57 -05:00
|
|
|
, QString const& /*configuration_name*/, QString const& /*tx_message*/)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
if (key == key_)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
|
|
|
if (f != dial_frequency_)
|
|
|
|
{
|
2020-11-04 22:37:01 -05:00
|
|
|
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
|
|
|
<< QString {"Dial frequency changed to %1"}.arg (f).toStdString () << std::endl;
|
2015-11-21 15:23:47 -05:00
|
|
|
dial_frequency_ = f;
|
|
|
|
}
|
2016-12-16 14:36:21 -05:00
|
|
|
if (mode + sub_mode != mode_)
|
|
|
|
{
|
2020-11-04 22:37:01 -05:00
|
|
|
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
|
|
|
<< QString {"Mode changed to %1"}.arg (mode + sub_mode).toStdString () << std::endl;
|
2016-12-16 14:36:21 -05:00
|
|
|
mode_ = mode + sub_mode;
|
|
|
|
}
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
|
2017-09-16 18:20:59 -04:00
|
|
|
, float delta_time, quint32 delta_frequency, QString const& mode
|
|
|
|
, QString const& message, bool low_confidence, bool off_air)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
if (key == key_)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2016-06-16 08:01:32 -04:00
|
|
|
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
|
|
|
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
2017-09-16 18:20:59 -04:00
|
|
|
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
|
|
|
|
<< "On air:" << !off_air;
|
2020-11-04 22:37:01 -05:00
|
|
|
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
|
|
|
<< QString {"Decoded %1"}.arg (message).toStdString () << std::endl;
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
|
2016-06-16 08:01:32 -04:00
|
|
|
, float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign
|
2017-09-16 18:20:59 -04:00
|
|
|
, QString const& grid, qint32 power, bool off_air)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
if (key == key_)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2016-06-16 08:01:32 -04:00
|
|
|
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
|
|
|
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
|
|
|
<< "drift:" << drift;
|
2020-11-04 22:37:01 -05:00
|
|
|
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
|
|
|
<< QString {"WSPR decode %1 grid %2 power: %3"}
|
|
|
|
.arg (callsign).arg (grid).arg (power).toStdString ()
|
2017-09-16 18:20:59 -04:00
|
|
|
<< "On air:" << !off_air << std::endl;
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
Q_SLOT void qso_logged (ClientKey const& key, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
2018-02-04 17:42:35 -05:00
|
|
|
, Frequency dial_frequency, QString const& mode, QString const& report_sent
|
|
|
|
, QString const& report_received, QString const& tx_power
|
|
|
|
, QString const& comments, QString const& name, QDateTime time_on
|
2018-12-02 10:09:37 -05:00
|
|
|
, QString const& operator_call, QString const& my_call, QString const& my_grid
|
2020-06-26 18:17:03 -04:00
|
|
|
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode)
|
2018-01-25 16:57:21 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
if (key == key_)
|
2018-01-25 16:57:21 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:"
|
|
|
|
<< dx_call << "grid:" << dx_grid
|
2018-01-25 16:57:21 -05:00
|
|
|
<< "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent
|
|
|
|
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
|
2018-02-04 17:42:35 -05:00
|
|
|
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
|
2018-12-02 10:09:37 -05:00
|
|
|
<< "my_grid:" << my_grid << "exchange_sent:" << exchange_sent
|
2020-06-26 18:17:03 -04:00
|
|
|
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
|
2018-02-04 17:42:35 -05:00
|
|
|
std::cout << QByteArray {80, '-'}.data () << '\n';
|
2020-11-04 22:37:01 -05:00
|
|
|
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
|
|
|
<< QString {"Logged %1 grid: %2 power: %3 sent: %4 recd: %5 freq: %6 time_off: %7 op: %8 my_call: %9 my_grid: %10 exchange_sent: %11 exchange_rcvd: %12 comments: %13 prop_mode: %14"}
|
|
|
|
.arg (dx_call).arg (dx_grid).arg (tx_power)
|
|
|
|
.arg (report_sent).arg (report_received)
|
|
|
|
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
|
|
|
|
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
|
|
|
|
.arg (comments).arg (prop_mode).toStdString ()
|
2018-01-25 16:57:21 -05:00
|
|
|
<< std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
Q_SLOT void logged_ADIF (ClientKey const& key, QByteArray const& ADIF)
|
2018-02-04 17:42:35 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
if (key == key_)
|
2018-02-04 17:42:35 -05:00
|
|
|
{
|
|
|
|
qDebug () << "ADIF:" << ADIF;
|
|
|
|
std::cout << QByteArray {80, '-'}.data () << '\n';
|
|
|
|
std::cout << ADIF.data () << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
private:
|
2020-11-02 16:35:48 -05:00
|
|
|
ClientKey key_;
|
2015-11-21 15:23:47 -05:00
|
|
|
Frequency dial_frequency_;
|
2016-12-16 14:36:21 -05:00
|
|
|
QString mode_;
|
2015-11-21 15:23:47 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
class Server
|
|
|
|
: public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
using ClientKey = MessageServer::ClientKey;
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
public:
|
2020-11-02 10:33:44 -05:00
|
|
|
Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names)
|
2015-11-21 15:23:47 -05:00
|
|
|
: server_ {new MessageServer {this}}
|
|
|
|
{
|
|
|
|
// connect up server
|
2019-07-01 22:10:43 -04:00
|
|
|
connect (server_, &MessageServer::error, [] (QString const& message) {
|
2015-11-21 15:23:47 -05:00
|
|
|
std::cerr << tr ("Network Error: %1").arg ( message).toStdString () << std::endl;
|
|
|
|
});
|
|
|
|
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
|
|
|
|
connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
|
|
|
|
|
2020-11-03 18:44:07 -05:00
|
|
|
#if QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)
|
2020-11-03 15:31:11 -05:00
|
|
|
server_->start (port, multicast_group, QSet<QString> {network_interface_names.begin (), network_interface_names.end ()});
|
2020-11-03 18:14:26 -05:00
|
|
|
#else
|
|
|
|
server_->start (port, multicast_group, network_interface_names.toSet ());
|
|
|
|
#endif
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
2020-11-02 16:35:48 -05:00
|
|
|
void add_client (ClientKey const& key, QString const& version, QString const& revision)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
auto client = new Client {key};
|
2015-11-21 15:23:47 -05:00
|
|
|
connect (server_, &MessageServer::status_update, client, &Client::update_status);
|
|
|
|
connect (server_, &MessageServer::decode, client, &Client::decode_added);
|
|
|
|
connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added);
|
2018-01-25 16:57:21 -05:00
|
|
|
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
|
2018-02-04 17:42:35 -05:00
|
|
|
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
|
2020-11-02 16:35:48 -05:00
|
|
|
clients_[key] = client;
|
|
|
|
server_->replay (key);
|
|
|
|
std::cout << "Discovered WSJT-X instance: " << key.second.toStdString ()
|
|
|
|
<< '(' << key.first.toString ().toStdString () << ')';
|
2016-12-03 19:55:15 -05:00
|
|
|
if (version.size ())
|
|
|
|
{
|
|
|
|
std::cout << " v" << version.toStdString ();
|
|
|
|
}
|
|
|
|
if (revision.size ())
|
|
|
|
{
|
|
|
|
std::cout << " (" << revision.toStdString () << ")";
|
|
|
|
}
|
|
|
|
std::cout << std::endl;
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
void remove_client (ClientKey const& key)
|
2015-11-21 15:23:47 -05:00
|
|
|
{
|
2020-11-02 16:35:48 -05:00
|
|
|
auto iter = clients_.find (key);
|
2015-11-21 15:23:47 -05:00
|
|
|
if (iter != std::end (clients_))
|
|
|
|
{
|
|
|
|
clients_.erase (iter);
|
|
|
|
(*iter)->deleteLater ();
|
|
|
|
}
|
2020-11-02 16:35:48 -05:00
|
|
|
std::cout << "Removed WSJT-X instance: " << key.second.toStdString ()
|
|
|
|
<< '(' << key.first.toString ().toStdString () << ')' << std::endl;
|
2015-11-21 15:23:47 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageServer * server_;
|
|
|
|
|
2020-11-02 16:35:48 -05:00
|
|
|
// maps client key to clients
|
|
|
|
QHash<ClientKey, Client *> clients_;
|
2015-11-21 15:23:47 -05:00
|
|
|
};
|
|
|
|
|
2020-11-02 17:08:37 -05:00
|
|
|
void list_interfaces ()
|
|
|
|
{
|
|
|
|
for (auto const& net_if : QNetworkInterface::allInterfaces ())
|
|
|
|
{
|
|
|
|
if (net_if.flags () & QNetworkInterface::IsUp)
|
|
|
|
{
|
|
|
|
std::cout << net_if.humanReadableName ().toStdString () << ":\n"
|
|
|
|
" id: " << net_if.name ().toStdString () << " (" << net_if.index () << ")\n"
|
|
|
|
" addr: " << net_if.hardwareAddress ().toStdString () << "\n"
|
|
|
|
" flags: ";
|
|
|
|
if (net_if.flags () & QNetworkInterface::IsRunning)
|
|
|
|
{
|
|
|
|
std::cout << "Running ";
|
|
|
|
}
|
|
|
|
if (net_if.flags () & QNetworkInterface::CanBroadcast)
|
|
|
|
{
|
|
|
|
std::cout << "Broadcast ";
|
|
|
|
}
|
|
|
|
if (net_if.flags () & QNetworkInterface::CanMulticast)
|
|
|
|
{
|
|
|
|
std::cout << "Multicast ";
|
|
|
|
}
|
|
|
|
if (net_if.flags () & QNetworkInterface::IsLoopBack)
|
|
|
|
{
|
|
|
|
std::cout << "Loop-back ";
|
|
|
|
}
|
|
|
|
std::cout << "\n addresses:\n";
|
|
|
|
for (auto const& ae : net_if.addressEntries ())
|
|
|
|
{
|
|
|
|
std::cout << " " << ae.ip ().toString ().toStdString () << '\n';
|
|
|
|
}
|
|
|
|
std::cout << '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
#include "UDPDaemon.moc"
|
|
|
|
|
|
|
|
int main (int argc, char * argv[])
|
|
|
|
{
|
|
|
|
QCoreApplication app {argc, argv};
|
|
|
|
try
|
|
|
|
{
|
2021-03-28 18:29:57 -04:00
|
|
|
// ensure number forms are in consistent format, do this after
|
|
|
|
// instantiating QApplication so that GUI has correct l18n
|
|
|
|
std::locale::global (std::locale::classic ());
|
2015-11-21 15:23:47 -05:00
|
|
|
|
|
|
|
app.setApplicationName ("WSJT-X UDP Message Server Daemon");
|
|
|
|
app.setApplicationVersion ("1.0");
|
|
|
|
|
|
|
|
QCommandLineParser parser;
|
|
|
|
parser.setApplicationDescription ("\nWSJT-X UDP Message Server Daemon.");
|
|
|
|
auto help_option = parser.addHelpOption ();
|
|
|
|
auto version_option = parser.addVersionOption ();
|
|
|
|
|
2020-11-02 17:08:37 -05:00
|
|
|
QCommandLineOption list_option (QStringList {"l", "list-interfaces"},
|
|
|
|
app.translate ("UDPDaemon",
|
|
|
|
"Print the available network interfaces."));
|
|
|
|
parser.addOption (list_option);
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
QCommandLineOption port_option (QStringList {"p", "port"},
|
|
|
|
app.translate ("UDPDaemon",
|
2016-06-16 08:01:32 -04:00
|
|
|
"Where <PORT> is the UDP service port number to listen on.\n"
|
|
|
|
"The default service port is 2237."),
|
2015-11-21 15:23:47 -05:00
|
|
|
app.translate ("UDPDaemon", "PORT"),
|
|
|
|
"2237");
|
|
|
|
parser.addOption (port_option);
|
|
|
|
|
|
|
|
QCommandLineOption multicast_addr_option (QStringList {"g", "multicast-group"},
|
|
|
|
app.translate ("UDPDaemon",
|
2016-06-16 08:01:32 -04:00
|
|
|
"Where <GROUP> is the multicast group to join.\n"
|
|
|
|
"The default is a unicast server listening on all interfaces."),
|
2015-11-21 15:23:47 -05:00
|
|
|
app.translate ("UDPDaemon", "GROUP"));
|
|
|
|
parser.addOption (multicast_addr_option);
|
|
|
|
|
2020-11-02 10:33:44 -05:00
|
|
|
QCommandLineOption network_interface_option (QStringList {"i", "network-interface"},
|
|
|
|
app.translate ("UDPDaemon",
|
|
|
|
"Where <INTERFACE> is the network interface name to join on.\n"
|
|
|
|
"This option can be passed more than once to specify multiple network interfaces\n"
|
|
|
|
"The default is use just the loop back interface."),
|
|
|
|
app.translate ("UDPDaemon", "INTERFACE"));
|
|
|
|
parser.addOption (network_interface_option);
|
|
|
|
|
2015-11-21 15:23:47 -05:00
|
|
|
parser.process (app);
|
|
|
|
|
2020-11-02 17:08:37 -05:00
|
|
|
if (parser.isSet (list_option))
|
|
|
|
{
|
|
|
|
list_interfaces ();
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2020-11-02 10:33:44 -05:00
|
|
|
Server server {static_cast<port_type> (parser.value (port_option).toUInt ())
|
|
|
|
, QHostAddress {parser.value (multicast_addr_option).trimmed ()}
|
|
|
|
, parser.values (network_interface_option)};
|
2015-11-21 15:23:47 -05:00
|
|
|
|
|
|
|
return app.exec ();
|
|
|
|
}
|
|
|
|
catch (std::exception const & e)
|
|
|
|
{
|
|
|
|
std::cerr << "Error: " << e.what () << '\n';
|
|
|
|
}
|
|
|
|
catch (...)
|
|
|
|
{
|
|
|
|
std::cerr << "Unexpected error\n";
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|