First iteration of optional TCP/IP client for PSK Reporter

This commit is contained in:
Bill Somerville 2020-06-21 01:36:30 +01:00
parent 7478978305
commit b48b23450d
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
14 changed files with 598 additions and 274 deletions

View File

@ -302,7 +302,7 @@ set (jt9_FSRCS
set (wsjtx_CXXSRCS
logbook/logbook.cpp
Network/psk_reporter.cpp
Network/PSK_Reporter.cpp
Modulator/Modulator.cpp
Detector/Detector.cpp
widgets/logqso.cpp

View File

@ -604,6 +604,7 @@ private:
bool id_after_73_;
bool tx_QSY_allowed_;
bool spot_to_psk_reporter_;
bool psk_reporter_tcpip_;
bool monitor_off_at_startup_;
bool monitor_last_used_;
bool log_as_RTTY_;
@ -701,6 +702,7 @@ bool Configuration::spot_to_psk_reporter () const
// rig must be open and working to spot externally
return is_transceiver_online () && m_->spot_to_psk_reporter_;
}
bool Configuration::psk_reporter_tcpip () const {return m_->psk_reporter_tcpip_;}
bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;}
bool Configuration::monitor_last_used () const {return m_->rig_is_dummy_ || m_->monitor_last_used_;}
bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;}
@ -1241,6 +1243,7 @@ void Configuration::impl::initialize_models ()
ui_->CW_id_after_73_check_box->setChecked (id_after_73_);
ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_);
ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_);
ui_->psk_reporter_tcpip_check_box->setChecked (psk_reporter_tcpip_);
ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_);
ui_->monitor_last_used_check_box->setChecked (monitor_last_used_);
ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_);
@ -1448,6 +1451,7 @@ void Configuration::impl::read_settings ()
monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool ();
monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool ();
spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool ();
psk_reporter_tcpip_ = settings_->value ("PSKReporterTCPIP", false).toBool ();
id_after_73_ = settings_->value ("After73", false).toBool ();
tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool ();
use_dynamic_grid_ = settings_->value ("AutoGrid", false).toBool ();
@ -1587,6 +1591,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("MonitorOFF", monitor_off_at_startup_);
settings_->setValue ("MonitorLastUsed", monitor_last_used_);
settings_->setValue ("PSKReporter", spot_to_psk_reporter_);
settings_->setValue ("PSKReporterTCPIP", psk_reporter_tcpip_);
settings_->setValue ("After73", id_after_73_);
settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_);
settings_->setValue ("Macros", macros_.stringList ());
@ -2041,6 +2046,7 @@ void Configuration::impl::accept ()
FD_exchange_= ui_->Field_Day_Exchange->text ().toUpper ();
RTTY_exchange_= ui_->RTTY_Exchange->text ().toUpper ();
spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked ();
psk_reporter_tcpip_ = ui_->psk_reporter_tcpip_check_box->isChecked ();
id_interval_ = ui_->CW_id_interval_spin_box->value ();
ntrials_ = ui_->sbNtrials->value ();
txDelay_ = ui_->sbTxDelay->value ();

View File

@ -113,6 +113,7 @@ public:
bool id_after_73 () const;
bool tx_QSY_allowed () const;
bool spot_to_psk_reporter () const;
bool psk_reporter_tcpip () const;
bool monitor_off_at_startup () const;
bool monitor_last_used () const;
bool log_as_RTTY () const;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>557</width>
<height>561</height>
<width>559</width>
<height>553</height>
</rect>
</property>
<property name="windowTitle">
@ -1800,20 +1800,27 @@ and DX Grid fields when a 73 or free text message is sent.</string>
<property name="title">
<string>Network Services</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_22">
<item>
<widget class="QCheckBox" name="psk_reporter_check_box">
<property name="toolTip">
<string>The program can send your station details and all
decoded signals as spots to the http://pskreporter.info web site.
This is used for reverse beacon analysis which is very useful
for assessing propagation and system performance.</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The program can send your station details and all decoded signals with grid squares as spots to the http://pskreporter.info web site.&lt;/p&gt;&lt;p&gt;This is used for reverse beacon analysis which is very useful for assessing propagation and system performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable &amp;PSK Reporter Spotting</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="psk_reporter_tcpip_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this option if a reliable connection is needed&lt;/p&gt;&lt;p&gt;Most users do not need this, the default uses UDP which is more efficient. Only check this if you have evidence that UDP traffic from you to PSK Reporter is being lost.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use TCP/IP connection</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -3108,13 +3115,13 @@ Right click for insert and delete options.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
</buttongroups>
</ui>

View File

@ -36,6 +36,7 @@ public:
impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self)
: self_ {self}
, dns_lookup_id_ {0}
, enabled_ {false}
, id_ {id}
, version_ {version}
@ -81,6 +82,7 @@ public:
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
int dns_lookup_id_;
bool enabled_;
QString id_;
QString version_;
@ -101,6 +103,7 @@ public:
void MessageClient::impl::host_info_results (QHostInfo host_info)
{
if (host_info.lookupId () != dns_lookup_id_) return;
if (QHostInfo::NoError != host_info.error ())
{
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
@ -423,30 +426,24 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
: QObject {self}
, m_ {id, version, revision, server_port, this}
{
connect (&*m_
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
, [this] (impl::SocketError e)
{
, static_cast<void (impl::*) (impl::SocketError)> (&impl::error), [this] (impl::SocketError e)
#else
, &impl::errorOccurred, [this] (impl::SocketError e)
#endif
{
#if defined (Q_OS_WIN)
if (e != impl::NetworkError // take this out when Qt 5.5
// stops doing this
// spuriously
&& e != impl::ConnectionRefusedError) // not
// interested
// in this with
// UDP socket
if (e != impl::NetworkError // take this out when Qt 5.5 stops doing this spuriously
&& e != impl::ConnectionRefusedError) // not interested in this with UDP socket
{
#else
Q_UNUSED (e);
{
Q_UNUSED (e);
#endif
{
Q_EMIT error (m_->errorString ());
}
});
#else
connect (&*m_, &impl::errorOccurred, [this] (impl::SocketError) {
Q_EMIT error (m_->errorString ());
});
#endif
}
});
set_server (server);
}
@ -464,11 +461,11 @@ void MessageClient::set_server (QString const& server)
{
m_->server_.clear ();
m_->server_string_ = server;
if (!server.isEmpty ())
if (server.size ())
{
// queue a host address lookup
TRACE_UDP ("server host DNS lookup:" << server);
QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
}
}
@ -477,27 +474,6 @@ void MessageClient::set_server_port (port_type server_port)
m_->server_port_ = server_port;
}
qint64 MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address
, port_type dest_port)
{
if (dest_port && !dest_address.isNull ())
{
return m_->writeDatagram (message, dest_address, dest_port);
}
return 0;
}
void MessageClient::add_blocked_destination (QHostAddress const& a)
{
m_->blocked_addresses_.push_back (a);
if (a == m_->server_)
{
m_->server_.clear ();
Q_EMIT error ("UDP server blocked, please try another");
m_->pending_messages_.clear (); // discard
}
}
void MessageClient::enable (bool flag)
{
m_->enabled_ = flag;

View File

@ -75,15 +75,6 @@ public:
// of record marker
Q_SLOT void logged_ADIF (QByteArray const& ADIF_record);
// this may be used to send arbitrary UDP datagrams to and
// destination allowing the underlying socket to be used for general
// UDP messaging if desired
qint64 send_raw_datagram (QByteArray const&, QHostAddress const& dest_address, port_type dest_port);
// disallowed message destination (does not block datagrams sent
// with send_raw_datagram() above)
Q_SLOT void add_blocked_destination (QHostAddress const&);
// this signal is emitted if the server has requested a decode
// window clear action
Q_SIGNAL void clear_decodes (quint8 window);

495
Network/PSK_Reporter.cpp Normal file
View File

@ -0,0 +1,495 @@
// Interface for posting spots to PSK Reporter web site
// Implemented by Edson Pereira PY2SDR
// Updated by Bill Somerville, G4WJS
//
// Reports will be sent in batch mode every 5 minutes.
#include "PSK_Reporter.hpp"
#include <cmath>
#include <QObject>
#include <QString>
#include <QDateTime>
#include <QSharedPointer>
#include <QUdpSocket>
#include <QTcpSocket>
#include <QHostInfo>
#include <QUDPSocket>
#include <QQueue>
#include <QByteArray>
#include <QDataStream>
#include <QTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
#endif
#include <QDebug>
#include "Configuration.hpp"
#include "pimpl_impl.hpp"
#include "moc_PSK_Reporter.cpp"
namespace
{
constexpr QLatin1String HOST {"report.pskreporter.info"};
// constexpr QLatin1String HOST {"127.0.0.1"};
// constexpr QLatin1String HOST {"192.168.1.195"};
constexpr quint16 SERVICE_PORT {4739};
// constexpr quint16 SERVICE_PORT {14739};
constexpr int MIN_SEND_INTERVAL {60}; // in seconds
constexpr int FLUSH_INTERVAL {5}; // in send intervals
constexpr bool ALIGNMENT_PADDING {true};
constexpr int MIN_PAYLOAD_LENGTH {508};
constexpr int MAX_PAYLOAD_LENGTH {1400};
}
class PSK_Reporter::impl final
: public QObject
{
Q_OBJECT
public:
impl (PSK_Reporter * self, Configuration const * config, QString const& program_info)
: self_ {self}
, config_ {config}
, sequence_number_ {0u}
, send_descriptors_ {0}
, send_receiver_data_ {0}
, flush_counter_ {0u}
, prog_id_ {program_info}
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
observation_id_ = qrand();
#else
observation_id_ = QRandomGenerator::global ()->generate ();
#endif
// This timer sets the interval to check for spots to send.
connect (&report_timer_, &QTimer::timeout, [this] () {send_report ();});
report_timer_.start (MIN_SEND_INTERVAL * 1000);
// This timer repeats the sending of IPFIX templates and receiver
// information if we are using UDP, in case server has been
// restarted ans lost cached information.
connect (&descriptor_timer_, &QTimer::timeout, [this] () {
if (socket_
&& QAbstractSocket::UdpSocket == socket_->socketType ())
{
// send templates again
send_descriptors_ = 3; // three times
// send receiver data set again
send_receiver_data_ = 3; // three times
}
});
descriptor_timer_.start (1 * 60 * 60 * 1000); // hourly
}
void check_connection ()
{
if (!socket_
|| QAbstractSocket::UnconnectedState == socket_->state ()
|| (socket_->socketType () != config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket))
{
// we need to create the appropriate socket
if (socket_
&& QAbstractSocket::UnconnectedState != socket_->state ()
&& QAbstractSocket::ClosingState != socket_->state ())
{
// handle re-opening asynchronously
auto connection = QSharedPointer<QMetaObject::Connection>::create ();
*connection = connect (socket_.get (), &QAbstractSocket::disconnected, [this, connection] () {
qDebug () << "PSK_Reporter::impl::check_connection: disconnected, socket state:" << socket_->state ();
disconnect (*connection);
check_connection ();
});
// close gracefully
socket_->close ();
}
else
{
reconnect ();
}
}
}
void handle_socket_error (QAbstractSocket::SocketError e)
{
switch (e)
{
case QAbstractSocket::RemoteHostClosedError:
socket_->disconnectFromHost ();
break;
case QAbstractSocket::TemporaryError:
break;
default:
spots_.clear ();
qDebug () << "PSK_Reporter::impl::handle_socket_error:" << socket_->errorString ();
Q_EMIT self_->errorOccurred (socket_->errorString ());
break;
}
}
void reconnect ()
{
// Using deleteLater for the deleter as we may eventually
// be called from the disconnected handler above.
if (config_->psk_reporter_tcpip ())
{
socket_.reset (new QTcpSocket, &QObject::deleteLater);
send_descriptors_ = 1;
send_receiver_data_ = 1;
}
else
{
socket_.reset (new QUdpSocket, &QObject::deleteLater);
send_descriptors_ = 3;
send_receiver_data_ = 3;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect (socket_.get (), &QAbstractSocket::errorOccurred, this, &PSK_Reporter::impl::handle_socket_error);
#else
connect (socket_.get (), QOverload<QAbstractSocket::SocketError>::of (&QAbstractSocket::error), this, &PSK_Reporter::impl::handle_socket_error);
#endif
// use this for pseudo connection with UDP, allows us to use
// QIODevice::write() instead of QUDPSocket::writeDatagram()
socket_->connectToHost (HOST, SERVICE_PORT, QAbstractSocket::WriteOnly);
}
void send_report (bool send_residue = false);
void build_preamble (QDataStream&);
bool flushing ()
{
return FLUSH_INTERVAL && !(++flush_counter_ % FLUSH_INTERVAL);
}
PSK_Reporter * self_;
Configuration const * config_;
QSharedPointer<QAbstractSocket> socket_;
int dns_lookup_id_;
QByteArray payload_;
quint32 sequence_number_;
int send_descriptors_;
// Currently PSK Reporter requires that a receiver data set is sent
// in every data flow. This memeber variable can be used to only
// send that information at session start (3 times for UDP), when it
// changes (3 times for UDP), or once per hour (3 times) if using
// UDP. Uncomment the relevant code to enable that fuctionality.
int send_receiver_data_;
unsigned flush_counter_;
quint32 observation_id_;
QString rx_call_;
QString rx_grid_;
QString rx_ant_;
QString prog_id_;
QByteArray tx_data_;
QByteArray tx_residue_;
struct Spot
{
bool operator == (Spot const& rhs)
{
return
call_ == rhs.call_
&& grid_ == rhs.grid_
&& mode_ == rhs.mode_
&& std::abs (Radio::FrequencyDelta (freq_ - rhs.freq_)) < 50;
}
QString call_;
QString grid_;
int snr_;
Radio::Frequency freq_;
QString mode_;
QDateTime time_;
};
QQueue<Spot> spots_;
QTimer report_timer_;
QTimer descriptor_timer_;
};
#include "PSK_Reporter.moc"
namespace
{
void writeUtfString (QDataStream& out, QString const& s)
{
auto const& utf = s.toUtf8 ().left (254);
out << quint8 (utf.size ());
out.writeRawData (utf, utf.size ());
}
constexpr
int num_pad_bytes (int len)
{
return ALIGNMENT_PADDING ? (4 - len % 4) % 4 : 0;
}
void set_length (QDataStream& out, QByteArray& b)
{
// pad with nulls modulo 4
auto pad_len = num_pad_bytes (b.size ());
out.writeRawData (QByteArray {pad_len, '\0'}.constData (), pad_len);
auto pos = out.device ()->pos ();
out.device ()->seek (sizeof (quint16));
// insert length
out << static_cast<quint16> (b.size ());
out.device ()->seek (pos);
}
}
void PSK_Reporter::impl::build_preamble (QDataStream& message)
{
// Message Header
message
<< quint16 (10u) // Version Number
<< quint16 (0u) // Length (place-holder filled in later)
<< quint32 (0u) // Export Time (place-holder filled in later)
<< ++sequence_number_ // Sequence Number
<< observation_id_; // Observation Domain ID
qDebug () << "PSK_Reporter::impl::build_preamble: send_descriptors_:" << send_descriptors_;
if (send_descriptors_)
{
--send_descriptors_;
{
// Sender Information descriptor
QByteArray descriptor;
QDataStream out {&descriptor, QIODevice::WriteOnly};
out
<< quint16 (2u) // Template Set ID
<< quint16 (0u) // Length (place-holder)
<< quint16 (0x50e3) // Link ID
<< quint16 (7u) // Field Count
<< quint16 (0x8000 + 1u) // Option 1 Information Element ID (senderCallsign)
<< quint16 (0xffff) // Option 1 Field Length (variable)
<< quint32 (30351u) // Option 1 Enterprise Number
<< quint16 (0x8000 + 5u) // Option 2 Information Element ID (frequency)
<< quint16 (4u) // Option 2 Field Length
<< quint32 (30351u) // Option 2 Enterprise Number
<< quint16 (0x8000 + 6u) // Option 3 Information Element ID (sNR)
<< quint16 (1u) // Option 3 Field Length
<< quint32 (30351u) // Option 3 Enterprise Number
<< quint16 (0x8000 + 10u) // Option 4 Information Element ID (mode)
<< quint16 (0xffff) // Option 4 Field Length (variable)
<< quint32 (30351u) // Option 4 Enterprise Number
<< quint16 (0x8000 + 3u) // Option 5 Information Element ID (senderLocator)
<< quint16 (0xffff) // Option 5 Field Length (variable)
<< quint32 (30351u) // Option 5 Enterprise Number
<< quint16 (0x8000 + 11u) // Option 6 Information Element ID (informationSource)
<< quint16 (1u) // Option 6 Field Length
<< quint32 (30351u) // Option 6 Enterprise Number
<< quint16 (150u) // Option 7 Information Element ID (dateTimeSeconds)
<< quint16 (4u); // Option 7 Field Length
// insert Length and move to payload
set_length (out, descriptor);
message.writeRawData (descriptor.constData (), descriptor.size ());
}
{
// Receiver Information descriptor
QByteArray descriptor;
QDataStream out {&descriptor, QIODevice::WriteOnly};
out
<< quint16 (3u) // Options Template Set ID
<< quint16 (0u) // Length (place-holder)
<< quint16 (0x50e2) // Link ID
<< quint16 (4u) // Field Count
<< quint16 (0u) // Scope Field Count
<< quint16 (0x8000 + 2u) // Option 1 Information Element ID (receiverCallsign)
<< quint16 (0xffff) // Option 1 Field Length (variable)
<< quint32 (30351u) // Option 1 Enterprise Number
<< quint16 (0x8000 + 4u) // Option 2 Information Element ID (receiverLocator)
<< quint16 (0xffff) // Option 2 Field Length (variable)
<< quint32 (30351u) // Option 2 Enterprise Number
<< quint16 (0x8000 + 8u) // Option 3 Information Element ID (decodingSoftware)
<< quint16 (0xffff) // Option 3 Field Length (variable)
<< quint32 (30351u) // Option 3 Enterprise Number
<< quint16 (0x8000 + 9u) // Option 4 Information Element ID (antennaInformation)
<< quint16 (0xffff) // Option 4 Field Length (variable)
<< quint32 (30351u); // Option 4 Enterprise Number
// insert Length
set_length (out, descriptor);
message.writeRawData (descriptor.constData (), descriptor.size ());
}
}
qDebug () << "PSK_Reporter::impl::build_preamble: send_receiver_data_:" << send_receiver_data_;
// if (send_receiver_data_)
{
// --send_receiver_data_;
// Receiver information
QByteArray data;
QDataStream out {&data, QIODevice::WriteOnly};
// Set Header
out
<< quint16 (0x50e2) // Template ID
<< quint16 (0u); // Length (place-holder)
// Set data
writeUtfString (out, rx_call_);
writeUtfString (out, rx_grid_);
writeUtfString (out, prog_id_);
writeUtfString (out, rx_ant_);
// insert Length and move to payload
set_length (out, data);
message.writeRawData (data.constData (), data.size ());
}
}
void PSK_Reporter::impl::send_report (bool send_residue)
{
check_connection ();
qDebug () << "PSK_Reporter::impl::send_report: send_residue:" << send_residue;
if (QAbstractSocket::ConnectedState != socket_->state ()) return;
QDataStream message {&payload_, QIODevice::WriteOnly | QIODevice::Append};
QDataStream tx_out {&tx_data_, QIODevice::WriteOnly | QIODevice::Append};
if (!payload_.size ())
{
qDebug () << "PSK_Reporter::impl::send_report: building header";
// Build header, optional descriptors, and receiver information
build_preamble (message);
}
auto flush = flushing () || send_residue;
qDebug () << "PSK_Reporter::impl::send_report: flush:" << flush;
while (spots_.size () || (flush && (tx_data_.size () || tx_residue_.size ())))
{
if (!payload_.size ())
{
// Build header, optional descriptors, and receiver information
build_preamble (message);
}
if (!tx_data_.size ())
{
// Set Header
tx_out
<< quint16 (0x50e3) // Template ID
<< quint16 (0u); // Length (place-holder)
qDebug () << "PSK_Reporter::impl::send_report: set data set header";
}
// insert any residue
if (tx_residue_.size ())
{
tx_out.writeRawData (tx_residue_.constData (), tx_residue_.size ());
tx_residue_.clear ();
qDebug () << "PSK_Reporter::impl::send_report: inserted data residue";
}
while (spots_.size () || (flush && tx_data_.size ()))
{
auto tx_data_size = tx_data_.size ();
if (spots_.size ())
{
auto const& spot = spots_.dequeue ();
qDebug () << "PSK_Reporter::impl::send_report: processing spotted call:" << spot.call_;
// Sender information
writeUtfString (tx_out, spot.call_);
tx_out
<< static_cast<quint32> (spot.freq_)
<< static_cast<qint8> (spot.snr_);
writeUtfString (tx_out, spot.mode_);
writeUtfString (tx_out, spot.grid_);
tx_out
<< quint8 (1u) // REPORTER_SOURCE_AUTOMATIC
<< static_cast<quint32> (spot.time_.toSecsSinceEpoch ());
}
auto len = payload_.size () + tx_data_.size ();
len += num_pad_bytes (tx_data_.size ());
len += num_pad_bytes (len);
if (len > MAX_PAYLOAD_LENGTH // our upper datagram size limit
|| (!spots_.size () && len > MIN_PAYLOAD_LENGTH) // spots drained and above lower datagram size limit
|| (flush && !spots_.size () && tx_data_.size ())) // send what we have
{
if (len <= MAX_PAYLOAD_LENGTH)
{
tx_data_size = tx_data_.size ();
qDebug () << "PSK_Reporter::impl::send_report: sending short payload:" << tx_data_size;
}
QByteArray tx {tx_data_.left (tx_data_size)};
QDataStream out {&tx, QIODevice::WriteOnly | QIODevice::Append};
// insert Length
set_length (out, tx);
message.writeRawData (tx.constData (), tx.size ());
// insert Length and Export Time
set_length (message, payload_);
message.device ()->seek (2 * sizeof (quint16));
message << static_cast<quint32> (QDateTime::currentDateTime ().toSecsSinceEpoch ());
// Send data to PSK Reporter site
socket_->write (payload_); // TODO: handle errors
qDebug () << "PSK_Reporter::impl::send_report: sent payload:" << payload_.size () << "observation id:" << observation_id_;
message.device ()->seek (0u);
payload_.clear (); // Fresh message
// Save unsent spots
tx_residue_ = tx_data_.right (tx_data_.size () - tx_data_size);
tx_out.device ()->seek (0u);
tx_data_.clear ();
qDebug () << "PSK_Reporter::impl::send_report: payload sent residue length:" << tx_residue_.size ();
break;
}
}
}
}
PSK_Reporter::PSK_Reporter (Configuration const * config, QString const& program_info)
: m_ {this, config, program_info}
{
}
PSK_Reporter::~PSK_Reporter ()
{
// m_->send_report (true); // send any pending spots
}
void PSK_Reporter::reconnect ()
{
m_->reconnect ();
}
void PSK_Reporter::setLocalStation (QString const& call, QString const& gridSquare, QString const& antenna)
{
if (call != m_->rx_call_ || gridSquare != m_->rx_grid_ || antenna != m_->rx_ant_)
{
m_->send_receiver_data_ = m_->socket_
&& QAbstractSocket::UdpSocket == m_->socket_->socketType () ? 3 : 1;
m_->rx_call_ = call;
m_->rx_grid_ = gridSquare;
m_->rx_ant_ = antenna;
}
}
bool PSK_Reporter::addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq
, QString const& mode, int snr)
{
if (m_->socket_ && m_->socket_->isValid ())
{
if (QAbstractSocket::UnconnectedState == m_->socket_->state ())
{
reconnect ();
}
m_->spots_.enqueue ({call, grid, snr, freq, mode, QDateTime::currentDateTimeUtc ()});
return true;
}
return false;
}
void PSK_Reporter::sendReport ()
{
m_->send_report (true);
}

41
Network/PSK_Reporter.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef PSK_REPORTER_HPP_
#define PSK_REPORTER_HPP_
#include <QObject>
#include "Radio.hpp"
#include "pimpl_h.hpp"
class QString;
class Configuration;
class PSK_Reporter final
: public QObject
{
Q_OBJECT
public:
explicit PSK_Reporter (Configuration const *, QString const& program_info);
~PSK_Reporter ();
void reconnect ();
void setLocalStation (QString const& call, QString const& grid, QString const& antenna);
//
// Returns false if PSK Reporter connection is not available
//
bool addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq, QString const& mode, int snr);
//
// Flush any pending spots to PSK Reporter
//
void sendReport ();
Q_SIGNAL void errorOccurred (QString const& reason);
private:
class impl;
pimpl<impl> m_;
};
#endif

View File

@ -1,140 +0,0 @@
// KISS Interface for posting spots to PSK Reporter web site
// Implemented by Edson Pereira PY2SDR
//
// Reports will be sent in batch mode every 5 minutes.
#include "psk_reporter.h"
#include <QHostInfo>
#include <QTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
#endif
#include "Network/MessageClient.hpp"
#include "moc_psk_reporter.cpp"
namespace
{
int constexpr MAX_PAYLOAD_LENGTH {1400};
}
PSK_Reporter::PSK_Reporter(MessageClient * message_client, QObject *parent) :
QObject {parent},
m_messageClient {message_client},
reportTimer {new QTimer {this}},
m_sequenceNumber {0}
{
m_header_h = "000Allllttttttttssssssssiiiiiiii";
// We use 50E2 and 50E3 for link Id
m_rxInfoDescriptor_h = "0003002C50E200040000"
"8002FFFF0000768F" // 2. Rx Call
"8004FFFF0000768F" // 4. Rx Grid
"8008FFFF0000768F" // 8. Rx Soft
"8009FFFF0000768F" // 9. Rx Antenna
"0000";
m_txInfoDescriptor_h = "0002003C50E30007"
"8001FFFF0000768F" // 1. Tx Call
"800500040000768F" // 5. Tx Freq
"800600010000768F" // 6. Tx snr
"800AFFFF0000768F" // 10. Tx Mode
"8003FFFF0000768F" // 3. Tx Grid
"800B00010000768F" // 11. Tx info src
"00960004"; // Report time
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
m_randomId_h = QString("%1").arg(qrand(),8,16,QChar('0'));
#else
m_randomId_h = QString("%1").arg(QRandomGenerator::global ()->generate (), 8, 16, QChar('0'));
#endif
QHostInfo::lookupHost("report.pskreporter.info", this, SLOT(dnsLookupResult(QHostInfo)));
connect(reportTimer, SIGNAL(timeout()), this, SLOT(sendReport()));
reportTimer->start(5*60*1000); // 5 minutes;
}
void PSK_Reporter::setLocalStation(QString call, QString gridSquare, QString antenna, QString programInfo)
{
m_rxCall = call;
m_rxGrid = gridSquare;
m_rxAnt = antenna;
m_progId = programInfo;
}
void PSK_Reporter::addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time )
{
QHash<QString,QString> spot;
spot["call"] = call;
spot["grid"] = grid;
spot["snr"] = snr;
spot["freq"] = freq;
spot["mode"] = mode;
spot["time"] = time;
m_spotQueue.enqueue(spot);
}
void PSK_Reporter::sendReport()
{
while (!m_spotQueue.isEmpty()) {
QString report_h;
// Header
QString header_h = m_header_h;
header_h.replace("tttttttt", QString("%1").arg(QDateTime::currentDateTime().toTime_t(),8,16,QChar('0')));
header_h.replace("ssssssss", QString("%1").arg(++m_sequenceNumber,8,16,QChar('0')));
header_h.replace("iiiiiiii", m_randomId_h);
// Receiver information
QString rxInfoData_h = "50E2llll";
rxInfoData_h += QString("%1").arg(m_rxCall.length(),2,16,QChar('0')) + m_rxCall.toUtf8().toHex();
rxInfoData_h += QString("%1").arg(m_rxGrid.length(),2,16,QChar('0')) + m_rxGrid.toUtf8().toHex();
rxInfoData_h += QString("%1").arg(m_progId.length(),2,16,QChar('0')) + m_progId.toUtf8().toHex();
rxInfoData_h += QString("%1").arg(m_rxAnt.length(),2,16,QChar('0')) + m_rxAnt.toUtf8().toHex();
rxInfoData_h += "0000";
rxInfoData_h.replace("50E2llll", "50E2" + QString("%1").arg(rxInfoData_h.length()/2,4,16,QChar('0')));
// Sender information
QString txInfoData_h = "50E3llll";
while (!m_spotQueue.isEmpty()
&& (header_h.size () + m_rxInfoDescriptor_h.size () + m_txInfoDescriptor_h.size () + rxInfoData_h.size () + txInfoData_h.size ()) / 2 < MAX_PAYLOAD_LENGTH) {
QHash<QString,QString> spot = m_spotQueue.dequeue();
txInfoData_h += QString("%1").arg(spot["call"].length(),2,16,QChar('0')) + spot["call"].toUtf8().toHex();
txInfoData_h += QString("%1").arg(spot["freq"].toLongLong(),8,16,QChar('0'));
txInfoData_h += QString("%1").arg(spot["snr"].toInt(),8,16,QChar('0')).right(2);
txInfoData_h += QString("%1").arg(spot["mode"].length(),2,16,QChar('0')) + spot["mode"].toUtf8().toHex();
txInfoData_h += QString("%1").arg(spot["grid"].length(),2,16,QChar('0')) + spot["grid"].toUtf8().toHex();
txInfoData_h += QString("%1").arg(1,2,16,QChar('0')); // REPORTER_SOURCE_AUTOMATIC
txInfoData_h += QString("%1").arg(spot["time"].toInt(),8,16,QChar('0'));
}
txInfoData_h += "0000";
txInfoData_h.replace("50E3llll", "50E3" + QString("%1").arg(txInfoData_h.length()/2,4,16,QChar('0')));
report_h = header_h + m_rxInfoDescriptor_h + m_txInfoDescriptor_h + rxInfoData_h + txInfoData_h;
//qDebug() << "Sending Report TX: ";
report_h.replace("000Allll", "000A" + QString("%1").arg(report_h.length()/2,4,16,QChar('0')));
QByteArray report = QByteArray::fromHex(report_h.toUtf8());
// Send data to PSK Reporter site
if (!m_pskReporterAddress.isNull()) {
m_messageClient->send_raw_datagram (report, m_pskReporterAddress, 4739);
}
}
}
void PSK_Reporter::dnsLookupResult(QHostInfo info)
{
if (!info.addresses().isEmpty()) {
m_pskReporterAddress = info.addresses().at(0);
// qDebug() << "PSK Reporter IP: " << m_pskReporterAddress;
// deal with miss-configured settings that attempt to set a
// Pskreporter Internet address for the WSJT-X UDP protocol
// server address
m_messageClient->add_blocked_destination (m_pskReporterAddress);
}
}

View File

@ -1,54 +0,0 @@
// -*- Mode: C++ -*-
#ifndef PSK_REPORTER_H
#define PSK_REPORTER_H
#include <QObject>
#include <QString>
#include <QHostAddress>
#include <QQueue>
#include <QHash>
class MessageClient;
class QTimer;
class QHostInfo;
class PSK_Reporter : public QObject
{
Q_OBJECT
public:
explicit PSK_Reporter(MessageClient *, QObject *parent = nullptr);
void setLocalStation(QString call, QString grid, QString antenna, QString programInfo);
void addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time);
signals:
public slots:
void sendReport();
private slots:
void dnsLookupResult(QHostInfo info);
private:
QString m_header_h;
QString m_rxInfoDescriptor_h;
QString m_txInfoDescriptor_h;
QString m_randomId_h;
QString m_linkId_h;
QString m_rxCall;
QString m_rxGrid;
QString m_rxAnt;
QString m_progId;
QHostAddress m_pskReporterAddress;
QQueue< QHash<QString,QString> > m_spotQueue;
MessageClient * m_messageClient;
QTimer *reportTimer;
int m_sequenceNumber;
};
#endif // PSK_REPORTER_H

View File

@ -82,12 +82,12 @@ namespace
}
catch (std::exception const& e)
{
MessageBox::critical_message (nullptr, translate ("main", "Fatal error"), e.what ());
MessageBox::critical_message (nullptr, "Fatal error", e.what ());
throw;
}
catch (...)
{
MessageBox::critical_message (nullptr, translate ("main", "Unexpected fatal error"));
MessageBox::critical_message (nullptr, "Unexpected fatal error");
throw;
}
}
@ -407,12 +407,12 @@ int main(int argc, char *argv[])
}
catch (std::exception const& e)
{
MessageBox::critical_message (nullptr, QApplication::translate ("main", "Fatal error"), e.what ());
MessageBox::critical_message (nullptr, "Fatal error", e.what ());
std::cerr << "Error: " << e.what () << '\n';
}
catch (...)
{
MessageBox::critical_message (nullptr, QApplication::translate ("main", "Unexpected fatal error"));
MessageBox::critical_message (nullptr, "Unexpected fatal error");
std::cerr << "Unexpected fatal error\n";
throw; // hoping the runtime might tell us more about the exception
}

View File

@ -412,7 +412,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (),
this}},
psk_Reporter {new PSK_Reporter {m_messageClient, this}},
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
m_manual {&m_network_manager},
m_block_udp_status_updates {false}
{
@ -3481,8 +3481,10 @@ void MainWindow::pskPost (DecodedText const& decodedtext)
pskSetLocal ();
if(grid.contains (grid_regexp)) {
// qDebug() << "To PSKreporter:" << deCall << grid << frequency << msgmode << snr;
psk_Reporter->addRemoteStation(deCall,grid,QString::number(frequency),msgmode,
QString::number(snr),QString::number(QDateTime::currentDateTimeUtc ().toTime_t()));
if (!m_psk_Reporter.addRemoteStation (deCall, grid, frequency, msgmode, snr))
{
showStatusMessage (tr ("Spotting to PSK Reporter unavailable"));
}
}
}
@ -6635,7 +6637,7 @@ void MainWindow::band_changed (Frequency f)
}
m_lastBand.clear ();
m_bandEdited = false;
psk_Reporter->sendReport(); // Upload any queued spots before changing band
m_psk_Reporter.sendReport(); // Upload any queued spots before changing band
if (!m_transmitting) monitor (true);
if ("FreqCal" == m_mode)
{
@ -7314,9 +7316,7 @@ void MainWindow::pskSetLocal ()
, StationList::description_column).data ().toString ();
}
// qDebug() << "To PSKreporter: local station details";
psk_Reporter->setLocalStation(m_config.my_callsign (), m_config.my_grid (),
antenna_description, QString {"WSJT-X v" + version() + " " +
m_revision}.simplified ());
m_psk_Reporter.setLocalStation(m_config.my_callsign (), m_config.my_grid (), antenna_description);
}
void MainWindow::transmitDisplay (bool transmitting)

View File

@ -21,6 +21,7 @@
#include <QPointer>
#include <QSet>
#include <QVector>
#include <QQueue>
#include <QFuture>
#include <QFutureWatcher>
@ -33,7 +34,7 @@
#include "WSPR/WSPRBandHopping.hpp"
#include "Transceiver/Transceiver.hpp"
#include "DisplayManual.hpp"
#include "Network/psk_reporter.h"
#include "Network/PSK_Reporter.hpp"
#include "logbook/logbook.h"
#include "astro.h"
#include "MessageBox.hpp"
@ -683,7 +684,7 @@ private:
QProgressDialog m_optimizingProgress;
QTimer m_heartbeat;
MessageClient * m_messageClient;
PSK_Reporter *psk_Reporter;
PSK_Reporter m_psk_Reporter;
DisplayManual m_manual;
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
QHash<QString, QVariant> m_pwrBandTuneMemory; // Remembers power level by band for tuning

View File

@ -60,7 +60,7 @@ SOURCES += \
PollingTransceiver.cpp EmulateSplitTransceiver.cpp \
HRDTransceiver.cpp DXLabSuiteCommanderTransceiver.cpp \
HamlibTransceiver.cpp FrequencyLineEdit.cpp \
Configuration.cpp psk_reporter.cpp AudioDevice.cpp \
Configuration.cpp PSK_Reporter.cpp AudioDevice.cpp \
Modulator.cpp Detector.cpp \
getfile.cpp soundout.cpp soundin.cpp \
WFPalette.cpp \
@ -77,7 +77,7 @@ HEADERS += qt_helpers.hpp qt_db_helpers.hpp \
soundin.h soundout.h \
WFPalette.hpp getfile.h decodedtext.h \
commons.h sleep.h \
AudioDevice.hpp Detector.hpp Modulator.hpp psk_reporter.h \
AudioDevice.hpp Detector.hpp Modulator.hpp PSK_Reporter.hpp \
Transceiver.hpp TransceiverBase.hpp TransceiverFactory.hpp PollingTransceiver.hpp \
EmulateSplitTransceiver.hpp DXLabSuiteCommanderTransceiver.hpp HamlibTransceiver.hpp \
Configuration.hpp wsprnet.h \