mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-19 10:32:02 -05:00
336eb0c858
Sub mode and fast mode flag are added to the UDP Status message and Status messages after appropriate UI changes. MSK144 decodes produce UDP Decode messages. Fast JT9 messages are treated similarly. Currently ISCAT decodes are not posted as UDP Decode messages as they do not fit well with the model. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@7389 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
417 lines
12 KiB
C++
417 lines
12 KiB
C++
#include "MessageClient.hpp"
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <QUdpSocket>
|
|
#include <QHostInfo>
|
|
#include <QTimer>
|
|
#include <QQueue>
|
|
#include <QByteArray>
|
|
#include <QHostAddress>
|
|
|
|
#include "NetworkMessage.hpp"
|
|
|
|
#include "pimpl_impl.hpp"
|
|
|
|
#include "moc_MessageClient.cpp"
|
|
|
|
class MessageClient::impl
|
|
: public QUdpSocket
|
|
{
|
|
Q_OBJECT;
|
|
|
|
public:
|
|
impl (QString const& id, QString const& version, QString const& revision,
|
|
port_type server_port, MessageClient * self)
|
|
: self_ {self}
|
|
, id_ {id}
|
|
, version_ {version}
|
|
, revision_ {revision}
|
|
, server_port_ {server_port}
|
|
, schema_ {2} // use 2 prior to negotiation not 1 which is broken
|
|
, heartbeat_timer_ {new QTimer {this}}
|
|
{
|
|
connect (heartbeat_timer_, &QTimer::timeout, this, &impl::heartbeat);
|
|
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
|
|
|
|
heartbeat_timer_->start (NetworkMessage::pulse * 1000);
|
|
|
|
// bind to an ephemeral port
|
|
bind ();
|
|
}
|
|
|
|
~impl ()
|
|
{
|
|
closedown ();
|
|
}
|
|
|
|
enum StreamStatus {Fail, Short, OK};
|
|
|
|
void parse_message (QByteArray const& msg);
|
|
void pending_datagrams ();
|
|
void heartbeat ();
|
|
void closedown ();
|
|
StreamStatus check_status (QDataStream const&) const;
|
|
void send_message (QByteArray const&);
|
|
void send_message (QDataStream const& out, QByteArray const& message)
|
|
{
|
|
if (OK == check_status (out))
|
|
{
|
|
send_message (message);
|
|
}
|
|
else
|
|
{
|
|
Q_EMIT self_->error ("Error creating UDP message");
|
|
}
|
|
}
|
|
|
|
Q_SLOT void host_info_results (QHostInfo);
|
|
|
|
MessageClient * self_;
|
|
QString id_;
|
|
QString version_;
|
|
QString revision_;
|
|
QString server_string_;
|
|
port_type server_port_;
|
|
QHostAddress server_;
|
|
quint32 schema_;
|
|
QTimer * heartbeat_timer_;
|
|
|
|
// hold messages sent before host lookup completes asynchronously
|
|
QQueue<QByteArray> pending_messages_;
|
|
QByteArray last_message_;
|
|
};
|
|
|
|
#include "MessageClient.moc"
|
|
|
|
void MessageClient::impl::host_info_results (QHostInfo host_info)
|
|
{
|
|
if (QHostInfo::NoError != host_info.error ())
|
|
{
|
|
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
|
|
pending_messages_.clear (); // discard
|
|
}
|
|
else if (host_info.addresses ().size ())
|
|
{
|
|
server_ = host_info.addresses ()[0];
|
|
|
|
// send initial heartbeat which allows schema negotiation
|
|
heartbeat ();
|
|
|
|
// clear any backlog
|
|
while (pending_messages_.size ())
|
|
{
|
|
send_message (pending_messages_.dequeue ());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MessageClient::impl::pending_datagrams ()
|
|
{
|
|
while (hasPendingDatagrams ())
|
|
{
|
|
QByteArray datagram;
|
|
datagram.resize (pendingDatagramSize ());
|
|
QHostAddress sender_address;
|
|
port_type sender_port;
|
|
if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
|
|
{
|
|
parse_message (datagram);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MessageClient::impl::parse_message (QByteArray const& msg)
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// message format is described in NetworkMessage.hpp
|
|
//
|
|
NetworkMessage::Reader in {msg};
|
|
if (OK == check_status (in) && id_ == in.id ()) // OK and for us
|
|
{
|
|
if (schema_ < in.schema ()) // one time record of server's
|
|
// negotiated schema
|
|
{
|
|
schema_ = in.schema ();
|
|
}
|
|
|
|
//
|
|
// message format is described in NetworkMessage.hpp
|
|
//
|
|
switch (in.type ())
|
|
{
|
|
case NetworkMessage::Reply:
|
|
{
|
|
// unpack message
|
|
QTime time;
|
|
qint32 snr;
|
|
float delta_time;
|
|
quint32 delta_frequency;
|
|
QByteArray mode;
|
|
QByteArray message;
|
|
in >> time >> snr >> delta_time >> delta_frequency >> mode >> message;
|
|
if (check_status (in) != Fail)
|
|
{
|
|
Q_EMIT self_->reply (time, snr, delta_time, delta_frequency
|
|
, QString::fromUtf8 (mode), QString::fromUtf8 (message));
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NetworkMessage::Replay:
|
|
if (check_status (in) != Fail)
|
|
{
|
|
last_message_.clear ();
|
|
Q_EMIT self_->replay ();
|
|
}
|
|
break;
|
|
|
|
case NetworkMessage::HaltTx:
|
|
{
|
|
bool auto_only {false};
|
|
in >> auto_only;
|
|
if (check_status (in) != Fail)
|
|
{
|
|
Q_EMIT self_->halt_tx (auto_only);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case NetworkMessage::FreeText:
|
|
{
|
|
QByteArray message;
|
|
bool send {true};
|
|
in >> message >> send;
|
|
if (check_status (in) != Fail)
|
|
{
|
|
Q_EMIT self_->free_text (QString::fromUtf8 (message), send);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Ignore
|
|
//
|
|
// Note that although server heartbeat messages are not
|
|
// parsed here they are still partially parsed in the
|
|
// message reader class to negotiate the maximum schema
|
|
// number being used on the network.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception const& e)
|
|
{
|
|
Q_EMIT self_->error (QString {"MessageClient exception: %1"}.arg (e.what ()));
|
|
}
|
|
catch (...)
|
|
{
|
|
Q_EMIT self_->error ("Unexpected exception in MessageClient");
|
|
}
|
|
}
|
|
|
|
void MessageClient::impl::heartbeat ()
|
|
{
|
|
if (server_port_ && !server_.isNull ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_};
|
|
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
|
<< version_.toUtf8 () << revision_.toUtf8 ();
|
|
if (OK == check_status (hb))
|
|
{
|
|
writeDatagram (message, server_, server_port_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MessageClient::impl::closedown ()
|
|
{
|
|
if (server_port_ && !server_.isNull ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
|
|
if (OK == check_status (out))
|
|
{
|
|
writeDatagram (message, server_, server_port_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MessageClient::impl::send_message (QByteArray const& message)
|
|
{
|
|
if (server_port_)
|
|
{
|
|
if (!server_.isNull ())
|
|
{
|
|
if (message != last_message_) // avoid duplicates
|
|
{
|
|
writeDatagram (message, server_, server_port_);
|
|
last_message_ = message;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pending_messages_.enqueue (message);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto MessageClient::impl::check_status (QDataStream const& stream) const -> StreamStatus
|
|
{
|
|
auto stat = stream.status ();
|
|
StreamStatus result {Fail};
|
|
switch (stat)
|
|
{
|
|
case QDataStream::ReadPastEnd:
|
|
result = Short;
|
|
break;
|
|
|
|
case QDataStream::ReadCorruptData:
|
|
Q_EMIT self_->error ("Message serialization error: read corrupt data");
|
|
break;
|
|
|
|
case QDataStream::WriteFailed:
|
|
Q_EMIT self_->error ("Message serialization error: write error");
|
|
break;
|
|
|
|
default:
|
|
result = OK;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
|
|
QString const& server, port_type server_port, QObject * self)
|
|
: QObject {self}
|
|
, m_ {id, version, revision, server_port, this}
|
|
{
|
|
connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
|
|
, [this] (impl::SocketError e)
|
|
{
|
|
#if defined (Q_OS_WIN) && QT_VERSION >= 0x050500
|
|
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);
|
|
#endif
|
|
{
|
|
Q_EMIT error (m_->errorString ());
|
|
}
|
|
});
|
|
set_server (server);
|
|
}
|
|
|
|
QHostAddress MessageClient::server_address () const
|
|
{
|
|
return m_->server_;
|
|
}
|
|
|
|
auto MessageClient::server_port () const -> port_type
|
|
{
|
|
return m_->server_port_;
|
|
}
|
|
|
|
void MessageClient::set_server (QString const& server)
|
|
{
|
|
m_->server_.clear ();
|
|
m_->server_string_ = server;
|
|
if (!server.isEmpty ())
|
|
{
|
|
// queue a host address lookup
|
|
QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
|
|
}
|
|
}
|
|
|
|
void MessageClient::set_server_port (port_type server_port)
|
|
{
|
|
m_->server_port_ = server_port;
|
|
}
|
|
|
|
void MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address
|
|
, port_type dest_port)
|
|
{
|
|
if (dest_port && !dest_address.isNull ())
|
|
{
|
|
m_->writeDatagram (message, dest_address, dest_port);
|
|
}
|
|
}
|
|
|
|
void MessageClient::status_update (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 (m_->server_port_ && !m_->server_string_.isEmpty ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
|
|
out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ()
|
|
<< tx_enabled << transmitting << decoding << rx_df << tx_df << de_call.toUtf8 ()
|
|
<< de_grid.toUtf8 () << dx_grid.toUtf8 () << watchdog_timeout << sub_mode.toUtf8 ()
|
|
<< fast_mode;
|
|
m_->send_message (out, message);
|
|
}
|
|
}
|
|
|
|
void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
|
, QString const& mode, QString const& message_text)
|
|
{
|
|
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
|
|
out << is_new << time << snr << delta_time << delta_frequency << mode.toUtf8 () << message_text.toUtf8 ();
|
|
m_->send_message (out, message);
|
|
}
|
|
}
|
|
|
|
void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delta_time, Frequency frequency
|
|
, qint32 drift, QString const& callsign, QString const& grid, qint32 power)
|
|
{
|
|
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
|
|
out << is_new << time << snr << delta_time << frequency << drift << callsign.toUtf8 ()
|
|
<< grid.toUtf8 () << power;
|
|
m_->send_message (out, message);
|
|
}
|
|
}
|
|
|
|
void MessageClient::clear_decodes ()
|
|
{
|
|
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
|
|
m_->send_message (out, message);
|
|
}
|
|
}
|
|
|
|
void MessageClient::qso_logged (QDateTime time, 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)
|
|
{
|
|
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
|
{
|
|
QByteArray message;
|
|
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
|
|
out << time << dx_call.toUtf8 () << dx_grid.toUtf8 () << dial_frequency << mode.toUtf8 ()
|
|
<< report_sent.toUtf8 () << report_received.toUtf8 () << tx_power.toUtf8 () << comments.toUtf8 () << name.toUtf8 ();
|
|
m_->send_message (out, message);
|
|
}
|
|
}
|