mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-08-27 08:36:36 -04:00
When "Auto Grid" is checked in "Settings->General" UDP messages of type "Location" will update a temporary DE grid square. The intent is to allow an external application joining the WSJT-X UDP message protocol to dynamically update the DE grid during mobile operation. This change also tidies up some outstanding issues around logging the operator call. This change adds a new UDP message "Logged ADIF" that is emitted in parallel with "QSO Logged" messages. The new message is valid ADIF file format and contains the logged QSO fields. The intent is that basic UDP server applications might already have ADIF log record capture capabilities and could use this message to feed existing ADIF parsing routines to log QSOs. All that should be needed is to identify this message type and the single field is ADIF compatible ASCII. Thanks to Brian, N9ADG, for the patches that lead to these enhancements. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@8454 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
246 lines
9.3 KiB
C++
246 lines
9.3 KiB
C++
//
|
|
// 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>
|
|
|
|
#include <QCoreApplication>
|
|
#include <QCommandLineParser>
|
|
#include <QDateTime>
|
|
#include <QTime>
|
|
#include <QHash>
|
|
#include <QDebug>
|
|
|
|
#include "MessageServer.hpp"
|
|
#include "Radio.hpp"
|
|
|
|
#include "qt_helpers.hpp"
|
|
|
|
using port_type = MessageServer::port_type;
|
|
using Frequency = MessageServer::Frequency;
|
|
|
|
class Client
|
|
: public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit Client (QString const& id, QObject * parent = nullptr)
|
|
: QObject {parent}
|
|
, id_ {id}
|
|
, dial_frequency_ {0u}
|
|
{
|
|
}
|
|
|
|
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/
|
|
, QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
|
|
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
|
|
, QString const& /*de_call*/, QString const& /*de_grid*/, QString const& /*dx_grid*/
|
|
, bool /* watchdog_timeout */, QString const& sub_mode, bool /*fast_mode*/)
|
|
{
|
|
if (id == id_)
|
|
{
|
|
if (f != dial_frequency_)
|
|
{
|
|
std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl;
|
|
dial_frequency_ = f;
|
|
}
|
|
if (mode + sub_mode != mode_)
|
|
{
|
|
std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl;
|
|
mode_ = mode + sub_mode;
|
|
}
|
|
}
|
|
}
|
|
|
|
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr
|
|
, float delta_time, quint32 delta_frequency, QString const& mode
|
|
, QString const& message, bool low_confidence, bool off_air)
|
|
{
|
|
if (client_id == id_)
|
|
{
|
|
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
|
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
|
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
|
|
<< "On air:" << !off_air;
|
|
std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl;
|
|
}
|
|
}
|
|
|
|
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr
|
|
, float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign
|
|
, QString const& grid, qint32 power, bool off_air)
|
|
{
|
|
if (client_id == id_)
|
|
{
|
|
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
|
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
|
<< "drift:" << drift;
|
|
std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString ()
|
|
<< "On air:" << !off_air << std::endl;
|
|
}
|
|
}
|
|
|
|
Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid
|
|
, 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
|
|
, QString const& operator_call, QString const& my_call, QString const& my_grid)
|
|
{
|
|
if (client_id == id_)
|
|
{
|
|
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid
|
|
<< "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent
|
|
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
|
|
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
|
|
<< "my_grid:" << my_grid;
|
|
std::cout << QByteArray {80, '-'}.data () << '\n';
|
|
std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11")
|
|
.arg (id_).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).toStdString ()
|
|
<< std::endl;
|
|
}
|
|
}
|
|
|
|
Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF)
|
|
{
|
|
if (client_id == id_)
|
|
{
|
|
qDebug () << "ADIF:" << ADIF;
|
|
std::cout << QByteArray {80, '-'}.data () << '\n';
|
|
std::cout << ADIF.data () << std::endl;
|
|
}
|
|
}
|
|
|
|
private:
|
|
QString id_;
|
|
Frequency dial_frequency_;
|
|
QString mode_;
|
|
};
|
|
|
|
class Server
|
|
: public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
Server (port_type port, QHostAddress const& multicast_group)
|
|
: server_ {new MessageServer {this}}
|
|
{
|
|
// connect up server
|
|
connect (server_, &MessageServer::error, [this] (QString const& message) {
|
|
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);
|
|
|
|
server_->start (port, multicast_group);
|
|
}
|
|
|
|
private:
|
|
void add_client (QString const& id, QString const& version, QString const& revision)
|
|
{
|
|
auto client = new Client {id};
|
|
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);
|
|
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
|
|
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
|
|
clients_[id] = client;
|
|
server_->replay (id);
|
|
std::cout << "Discovered WSJT-X instance: " << id.toStdString ();
|
|
if (version.size ())
|
|
{
|
|
std::cout << " v" << version.toStdString ();
|
|
}
|
|
if (revision.size ())
|
|
{
|
|
std::cout << " (" << revision.toStdString () << ")";
|
|
}
|
|
std::cout << std::endl;
|
|
}
|
|
|
|
void remove_client (QString const& id)
|
|
{
|
|
auto iter = clients_.find (id);
|
|
if (iter != std::end (clients_))
|
|
{
|
|
clients_.erase (iter);
|
|
(*iter)->deleteLater ();
|
|
}
|
|
std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl;
|
|
}
|
|
|
|
MessageServer * server_;
|
|
|
|
// maps client id to clients
|
|
QHash<QString, Client *> clients_;
|
|
};
|
|
|
|
#include "UDPDaemon.moc"
|
|
|
|
int main (int argc, char * argv[])
|
|
{
|
|
QCoreApplication app {argc, argv};
|
|
try
|
|
{
|
|
setlocale (LC_NUMERIC, "C"); // ensure number forms are in
|
|
// consistent format, do this after
|
|
// instantiating QApplication so
|
|
// that GUI has correct l18n
|
|
|
|
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 ();
|
|
|
|
QCommandLineOption port_option (QStringList {"p", "port"},
|
|
app.translate ("UDPDaemon",
|
|
"Where <PORT> is the UDP service port number to listen on.\n"
|
|
"The default service port is 2237."),
|
|
app.translate ("UDPDaemon", "PORT"),
|
|
"2237");
|
|
parser.addOption (port_option);
|
|
|
|
QCommandLineOption multicast_addr_option (QStringList {"g", "multicast-group"},
|
|
app.translate ("UDPDaemon",
|
|
"Where <GROUP> is the multicast group to join.\n"
|
|
"The default is a unicast server listening on all interfaces."),
|
|
app.translate ("UDPDaemon", "GROUP"));
|
|
parser.addOption (multicast_addr_option);
|
|
|
|
parser.process (app);
|
|
|
|
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}};
|
|
|
|
return app.exec ();
|
|
}
|
|
catch (std::exception const & e)
|
|
{
|
|
std::cerr << "Error: " << e.what () << '\n';
|
|
}
|
|
catch (...)
|
|
{
|
|
std::cerr << "Unexpected error\n";
|
|
}
|
|
return -1;
|
|
}
|