WSJT-X/MessageClient.cpp

284 lines
7.6 KiB
C++
Raw Normal View History

Send status information to UDP server To facilitate interaction with other applications WSJT-X now sends status updates to a predefined UDP server or multicast group address. The status updates include the information currently posted to the decodes.txt and wsjtx_status.txt files. An optional back communications channel is also implemented allowing the UDP server application to control some basic actions in WSJT-X. A reference implementaion of a typical UDP server written in C++ using Qt is provided to demonstrate these facilities. This application is not intended as a user tool but only as an example of how a third party application may interact with WSJT-X. The UDP messages Use QDataStream based serialization. Messages are documented in NetworkMessage.hpp along with some helper classes that simplify the building and decoding of messages. Two message handling classes are introduced, MessageClient and MessageServer. WSJT-X uses the MessageClient class to manage outgoing and incoming UDP messages that allow communication with other applications. The MessageServer class implements the kind of code that a potential cooperating application might use. Although these classes use Qt serialization facilities, the message formats are easily read and written by applications that do not use the Qt framework. MessageAggregator is a demonstration application that uses MessageServer and presents a GUI that displays messages from one or more WSJT-X instances and allows sending back a CQ or QRZ reply invocation by double clicking a decode. This application is not intended as a user facing tool but rather as a demonstration of the WSJT-X UDP messaging facility. It also demonstrates being a multicast UDP server by allowing multiple instances to run concurrently. This is enabled by using an appropriate multicast group address as the server address. Cooperating applications need not implement multicast techniques but it is recomended otherwise only a single appliaction can act as a broadcast message (from WSJT-X) recipient. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5225 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
2015-04-15 12:40:49 -04:00
#include "MessageClient.hpp"
#include <QUdpSocket>
#include <QHostInfo>
#include <QTimer>
#include "NetworkMessage.hpp"
#include "pimpl_impl.hpp"
#include "moc_MessageClient.cpp"
class MessageClient::impl
: public QUdpSocket
{
Q_OBJECT;
public:
impl (QString const& id, port_type server_port, MessageClient * self)
: self_ {self}
, id_ {id}
, server_port_ {server_port}
, 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 ();
}
void parse_message (QByteArray const& msg);
void pending_datagrams ();
void heartbeat ();
void closedown ();
bool check_status (QDataStream const&) const;
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
QString id_;
QHostAddress server_;
port_type server_port_;
QTimer * heartbeat_timer_;
};
#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 ());
}
else if (host_info.addresses ().size ())
{
server_ = host_info.addresses ()[0];
}
}
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)
{
//
// message format is described in NetworkMessage.hpp
//
NetworkMessage::Reader in {msg};
if (id_ == in.id ()) // for us
{
//
// 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))
{
Q_EMIT self_->reply (time, snr, delta_time, delta_frequency
, QString::fromUtf8 (mode), QString::fromUtf8 (message));
}
}
break;
case NetworkMessage::Replay:
if (check_status (in))
{
Q_EMIT self_->replay ();
}
break;
default:
// Ignore
break;
}
}
}
void MessageClient::impl::heartbeat ()
{
if (server_port_ && !server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_};
if (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_};
if (check_status (out))
{
writeDatagram (message, server_, server_port_);
}
}
}
bool MessageClient::impl::check_status (QDataStream const& stream) const
{
auto stat = stream.status ();
switch (stat)
{
case QDataStream::ReadPastEnd:
Q_EMIT self_->error ("Message serialization error: read failed");
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:
break;
}
return QDataStream::Ok == stat;
}
MessageClient::MessageClient (QString const& id, QString const& server, port_type server_port, QObject * self)
: QObject {self}
, m_ {id, server_port, this}
{
connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
, [this] (impl::SocketError /* e */)
{
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 ();
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)
{
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_};
out << f << mode.toUtf8 () << dx_call.toUtf8 () << report.toUtf8 () << tx_mode.toUtf8 ();
if (m_->check_status (out))
{
m_->writeDatagram (message, m_->server_, m_->server_port_);
}
}
}
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_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_};
out << is_new << time << snr << delta_time << delta_frequency << mode.toUtf8 () << message_text.toUtf8 ();
if (m_->check_status (out))
{
m_->writeDatagram (message, m_->server_, m_->server_port_);
}
}
}
void MessageClient::clear_decodes ()
{
if (m_->server_port_ && !m_->server_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_};
if (m_->check_status (out))
{
m_->writeDatagram (message, m_->server_, m_->server_port_);
}
}
}
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_.isNull ())
{
QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_};
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 ();
if (m_->check_status (out))
{
m_->writeDatagram (message, m_->server_, m_->server_port_);
}
}
}