diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b5b59b..8edfb82 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ set (wsjt_qt_CXXSRCS NetworkMessage.cpp Message.cpp MessageClient.cpp + MessageServer.cpp TCPClient.cpp LettersSpinBox.cpp HintedSpinBox.cpp diff --git a/Configuration.hpp b/Configuration.hpp index 64bb88b..3327b36 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -328,10 +328,12 @@ public: Q_SIGNAL void colors_changed (); // - // This signal is emitted when the UDP server changes + // This signal is emitted when the UDP & TCP server changes // - Q_SIGNAL void udp_server_changed (QString const& udp_server); - Q_SIGNAL void udp_server_port_changed (port_type server_port); + Q_SIGNAL void udp_server_changed (QString const& host); + Q_SIGNAL void udp_server_port_changed (port_type port); + Q_SIGNAL void tcp_server_changed (QString const& host); + Q_SIGNAL void tcp_server_port_changed (port_type port); // This signal is emitted when the band schedule changes Q_SIGNAL void band_schedule_changed (StationList &stations); diff --git a/Message.cpp b/Message.cpp index 7ff7c71..2004982 100644 --- a/Message.cpp +++ b/Message.cpp @@ -53,6 +53,21 @@ Message::Message(QString const &type, QString const &value, QMap params() const { return params_; } private: diff --git a/MessageServer.cpp b/MessageServer.cpp index b5a719e..22f7a89 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -1,495 +1,186 @@ -#include "MessageServer.hpp" +#include "MessageServer.h" -#include +#include -#include -#include -#include -#include -#include "Radio.hpp" -#include "NetworkMessage.hpp" -#include "qt_helpers.hpp" - -#include "pimpl_impl.hpp" - -#include "moc_MessageServer.cpp" - -class MessageServer::impl - : public QUdpSocket +MessageServer::MessageServer(QObject *parent) : + QTcpServer(parent) { - Q_OBJECT; +} -public: - impl (MessageServer * self, QString const& version, QString const& revision) - : self_ {self} - , version_ {version} - , revision_ {revision} - , port_ {0u} - , clock_ {new QTimer {this}} - { - // register the required types with Qt - Radio::register_types (); +MessageServer::~MessageServer(){ + stop(); +} - connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams); - connect (this, static_cast (&impl::error) - , [this] (SocketError /* e */) - { - Q_EMIT self_->error (errorString ()); - }); - connect (clock_, &QTimer::timeout, this, &impl::tick); - clock_->start (NetworkMessage::pulse * 1000); - } +bool MessageServer::start() +{ + if(isListening()){ + qDebug() << "MessageServer already listening:" << m_host << m_port; + return false; + } - enum StreamStatus {Fail, Short, OK}; + auto address = QHostAddress(); + if(!address.setAddress(m_host)){ + qDebug() << "MessageServer address invalid:" << m_host << m_port; + return false; + } - void leave_multicast_group (); - void join_multicast_group (); - void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg); - void tick (); - void pending_datagrams (); - StreamStatus check_status (QDataStream const&) const; - void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port) - { - if (OK == check_status (out)) - { - writeDatagram (message, address, port); + bool listening = listen(address, m_port); + qDebug() << "MessageServer listening:" << listening << m_host << m_port; + + return listening; +} + +void MessageServer::stop() +{ + // disconnect all clients + foreach(auto client, m_clients){ + client->close(); + } + + // then close the server + close(); +} + +void MessageServer::setServer(QString host, quint16 port){ + bool listening = isListening(); + if(listening && (m_host != host || m_port != port)){ + stop(); + } + + m_host = host; + m_port = port; + + if(listening){ + start(); + } +} + +void MessageServer::setPause(bool paused) +{ + m_paused = paused; + + if(paused){ + pauseAccepting(); + } else { + resumeAccepting(); + } +} + +void MessageServer::send(const Message &message){ + foreach(auto client, m_clients){ + if(!client->awaitingResponse(message.id())){ + continue; } - else - { - Q_EMIT self_->error ("Error creating UDP message"); + client->send(message); + } +} + +void MessageServer::incomingConnection(qintptr handle) +{ + qDebug() << "MessageServer incomingConnection" << handle; + + auto client = new Client(this, this); + client->setSocket(handle); + +#if JS8_MESSAGESERVER_IS_SINGLE_CLIENT + while(!m_clients.isEmpty()){ + auto client = m_clients.first(); + client->close(); + m_clients.removeFirst(); + } +#endif + + m_clients.append(client); +} + +Client::Client(MessageServer * server, QObject *parent): + QObject(parent), + m_server {server} +{ + setConnected(true); +} + +void Client::setSocket(qintptr handle){ + m_socket = new QTcpSocket(this); + + connect(m_socket, &QTcpSocket::disconnected, this, &Client::onDisconnected); + connect(m_socket, &QTcpSocket::readyRead, this, &Client::readyRead); + + m_socket->setSocketDescriptor(handle); +} + +void Client::setConnected(bool connected){ + m_connected = connected; +} + +void Client::close(){ + if(!m_socket){ + return; + } + + m_socket->close(); + m_socket = nullptr; +} + +void Client::send(const Message &message){ + if(!isConnected()){ + return; + } + + if(!m_socket){ + return; + } + + if(!m_socket->isOpen()){ + qDebug() << "client socket isn't open"; + return; + } + + qDebug() << "client writing" << message.toJson(); + m_socket->write(message.toJson()); + m_socket->write("\n"); + m_socket->flush(); + + // remove if needed + if(m_requests.contains(message.id())){ + m_requests.remove(message.id()); + } +} + +void Client::onDisconnected(){ + qDebug() << "MessageServer client disconnected"; + setConnected(false); +} + +void Client::readyRead(){ + qDebug() << "MessageServer client readyRead"; + + while(m_socket->canReadLine()){ + auto msg = m_socket->readLine().trimmed(); + qDebug() << "-> Client" << m_socket->socketDescriptor() << msg; + + if(msg.isEmpty()){ + return; } - } - MessageServer * self_; - QString version_; - QString revision_; - port_type port_; - QHostAddress multicast_group_address_; - static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint; - struct Client - { - Client () = default; - Client (QHostAddress const& sender_address, port_type const& sender_port) - : sender_address_ {sender_address} - , sender_port_ {sender_port} - , negotiated_schema_number_ {2} // not 1 because it's broken - , last_activity_ {QDateTime::currentDateTime ()} - { - } - Client (Client const&) = default; - Client& operator= (Client const&) = default; - - QHostAddress sender_address_; - port_type sender_port_; - quint32 negotiated_schema_number_; - QDateTime last_activity_; - }; - QHash clients_; // maps id to Client - QTimer * clock_; -}; - -MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_; - -#include "MessageServer.moc" - -void MessageServer::impl::leave_multicast_group () -{ - if (!multicast_group_address_.isNull () && BoundState == state ()) - { - leaveMulticastGroup (multicast_group_address_); - } -} - -void MessageServer::impl::join_multicast_group () -{ - if (BoundState == state () - && !multicast_group_address_.isNull ()) - { - if (IPv4Protocol == multicast_group_address_.protocol () - && IPv4Protocol != localAddress ().protocol ()) - { - close (); - bind (QHostAddress::AnyIPv4, port_, bind_mode_); + QJsonParseError e; + QJsonDocument d = QJsonDocument::fromJson(msg, &e); + if(e.error != QJsonParseError::NoError){ + //Q_EMIT self_->error(QString {"MessageClient json parse error: %1"}.arg(e.errorString())); + return; } - if (!joinMulticastGroup (multicast_group_address_)) - { - multicast_group_address_.clear (); + + if(!d.isObject()){ + //Q_EMIT self_->error(QString {"MessageClient json parse error: json is not an object"}); + return; } + + Message m; + m.read(d.object()); + auto id = m.ensureId(); + m_requests[id] = m; + + emit m_server->message(m); } } - -void MessageServer::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 (sender_address, sender_port, datagram); - } - } -} - -void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg) -{ - try - { - // - // message format is described in NetworkMessage.hpp - // - NetworkMessage::Reader in {msg}; - - auto id = in.id (); - if (OK == check_status (in)) - { - if (!clients_.contains (id)) - { - auto& client = (clients_[id] = {sender, sender_port}); - QByteArray client_version; - QByteArray client_revision; - - if (NetworkMessage::Heartbeat == in.type ()) - { - // negotiate a working schema number - in >> client.negotiated_schema_number_; - if (OK == check_status (in)) - { - auto sn = NetworkMessage::Builder::schema_number; - client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_); - - // reply to the new client informing it of the - // negotiated schema number - QByteArray message; - NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_}; - hb << NetworkMessage::Builder::schema_number // maximum schema number accepted - << version_.toUtf8 () << revision_.toUtf8 (); - if (impl::OK == check_status (hb)) - { - writeDatagram (message, client.sender_address_, client.sender_port_); - } - else - { - Q_EMIT self_->error ("Error creating UDP message"); - } - } - // we don't care if this fails to read - in >> client_version >> client_revision; - } - Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version), - QString::fromUtf8 (client_revision)); - } - clients_[id].last_activity_ = QDateTime::currentDateTime (); - - // - // message format is described in NetworkMessage.hpp - // - switch (in.type ()) - { - case NetworkMessage::Heartbeat: - //nothing to do here as time out handling deals with lifetime - break; - - case NetworkMessage::Clear: - Q_EMIT self_->clear_decodes (id); - break; - - case NetworkMessage::Status: - { - // unpack message - Frequency f; - QByteArray mode; - QByteArray dx_call; - QByteArray report; - QByteArray tx_mode; - bool tx_enabled {false}; - bool transmitting {false}; - bool decoding {false}; - qint32 rx_df {-1}; - qint32 tx_df {-1}; - QByteArray de_call; - QByteArray de_grid; - QByteArray dx_grid; - bool watchdog_timeout {false}; - QByteArray sub_mode; - bool fast_mode {false}; - in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding - >> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode - >> fast_mode; - if (check_status (in) != Fail) - { - Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call) - , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode) - , tx_enabled, transmitting, decoding, rx_df, tx_df - , QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid) - , QString::fromUtf8 (dx_grid), watchdog_timeout - , QString::fromUtf8 (sub_mode), fast_mode); - } - } - break; - - case NetworkMessage::Decode: - { - // unpack message - bool is_new {true}; - QTime time; - qint32 snr; - float delta_time; - quint32 delta_frequency; - QByteArray mode; - QByteArray message; - bool low_confidence {false}; - bool off_air {false}; - in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode - >> message >> low_confidence >> off_air; - if (check_status (in) != Fail) - { - Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency - , QString::fromUtf8 (mode), QString::fromUtf8 (message) - , low_confidence, off_air); - } - } - break; - - case NetworkMessage::WSPRDecode: - { - // unpack message - bool is_new {true}; - QTime time; - qint32 snr; - float delta_time; - Frequency frequency; - qint32 drift; - QByteArray callsign; - QByteArray grid; - qint32 power; - bool off_air {false}; - in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power - >> off_air; - if (check_status (in) != Fail) - { - Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift - , QString::fromUtf8 (callsign), QString::fromUtf8 (grid) - , power, off_air); - } - } - break; - - case NetworkMessage::QSOLogged: - { - QDateTime time_off; - QByteArray dx_call; - QByteArray dx_grid; - Frequency dial_frequency; - QByteArray mode; - QByteArray report_sent; - QByteArray report_received; - QByteArray tx_power; - QByteArray comments; - QByteArray name; - QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window - QByteArray operator_call; - QByteArray my_call; - QByteArray my_grid; - in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received - >> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid; - if (check_status (in) != Fail) - { - Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid) - , dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent) - , QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power) - , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on - , QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call) - , QString::fromUtf8 (my_grid)); - } - } - break; - - case NetworkMessage::Close: - Q_EMIT self_->client_closed (id); - clients_.remove (id); - break; - - case NetworkMessage::LoggedADIF: - { - QByteArray ADIF; - in >> ADIF; - if (check_status (in) != Fail) - { - Q_EMIT self_->logged_ADIF (id, ADIF); - } - } - break; - - default: - // Ignore - break; - } - } - else - { - Q_EMIT self_->error ("MessageServer warning: invalid UDP message received"); - } - } - catch (std::exception const& e) - { - Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ())); - } - catch (...) - { - Q_EMIT self_->error ("Unexpected exception in MessageServer"); - } -} - -void MessageServer::impl::tick () -{ - auto now = QDateTime::currentDateTime (); - auto iter = std::begin (clients_); - while (iter != std::end (clients_)) - { - if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse)) - { - Q_EMIT self_->clear_decodes (iter.key ()); - Q_EMIT self_->client_closed (iter.key ()); - iter = clients_.erase (iter); // safe while iterating as doesn't rehash - } - else - { - ++iter; - } - } -} - -auto MessageServer::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; -} - -MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision) - : QObject {parent} - , m_ {this, version, revision} -{ -} - -void MessageServer::start (port_type port, QHostAddress const& multicast_group_address) -{ - if (port != m_->port_ - || multicast_group_address != m_->multicast_group_address_) - { - m_->leave_multicast_group (); - if (impl::BoundState == m_->state ()) - { - m_->close (); - } - m_->multicast_group_address_ = multicast_group_address; - auto address = m_->multicast_group_address_.isNull () - || impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4; - if (port && m_->bind (address, port, m_->bind_mode_)) - { - m_->port_ = port; - m_->join_multicast_group (); - } - else - { - m_->port_ = 0; - } - } -} - -void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time - , quint32 delta_frequency, QString const& mode - , QString const& message_text, bool low_confidence, quint8 modifiers) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_}; - out << time << snr << delta_time << delta_frequency << mode.toUtf8 () - << message_text.toUtf8 () << low_confidence << modifiers; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} - -void MessageServer::replay (QString const& id) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_}; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} - -void MessageServer::halt_tx (QString const& id, bool auto_only) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_}; - out << auto_only; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} - -void MessageServer::free_text (QString const& id, QString const& text, bool send) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_}; - out << text.toUtf8 () << send; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} - -void MessageServer::location (QString const& id, QString const& loc) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_}; - out << loc.toUtf8 (); - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} - -void MessageServer::highlight_callsign (QString const& id, QString const& callsign - , QColor const& bg, QColor const& fg, bool last_only) -{ - auto iter = m_->clients_.find (id); - if (iter != std::end (m_->clients_)) - { - QByteArray message; - NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_}; - out << callsign.toUtf8 () << bg << fg << last_only; - m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); - } -} diff --git a/MessageServer.h b/MessageServer.h new file mode 100644 index 0000000..7dd8e9c --- /dev/null +++ b/MessageServer.h @@ -0,0 +1,74 @@ +#ifndef MESSAGESERVER_H +#define MESSAGESERVER_H + +#include +#include +#include +#include +#include + +#include "Message.h" + +class Client; + +class MessageServer : public QTcpServer +{ + Q_OBJECT +public: + explicit MessageServer(QObject *parent = 0); + virtual ~MessageServer(); + +protected: + void incomingConnection(qintptr handle); + +signals: + void message(Message const &message); + void error (QString const&) const; + +public slots: + void setServer(QString host, quint16 port=2442); + void setPause(bool paused); + bool start(); + void stop(); + void setServerHost(const QString &host){ setServer(host, m_port); } + void setServerPort(quint16 port){ setServer(m_host, port); } + void send(Message const &message); + +private: + bool m_paused; + QString m_host; + quint16 m_port; + + QList m_clients; +}; + +class Client : public QObject +{ + Q_OBJECT +public: + explicit Client(MessageServer *server, QObject *parent = 0); + + bool isConnected() const { return m_connected; } + void setSocket(qintptr handle); + void send(const Message &message); + void close(); + bool awaitingResponse(int id){ + return id <= 0 || m_requests.contains(id); + } +signals: + +public slots: + void setConnected(bool connected); + void onDisconnected(); + void readyRead(); + +private: + QMap m_requests; + MessageServer * m_server; + QTcpSocket * m_socket; + bool m_connected; +}; + + + +#endif // MESSAGESERVER_H diff --git a/MessageServer.hpp b/MessageServer.hpp deleted file mode 100644 index c9ec2d2..0000000 --- a/MessageServer.hpp +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef MESSAGE_SERVER_HPP__ -#define MESSAGE_SERVER_HPP__ - -#include -#include -#include -#include -#include - -#include "Radio.hpp" - -#include "pimpl_h.hpp" - -class QString; - -// -// MessageServer - a reference implementation of a message server -// matching the MessageClient class at the other end -// of the wire -// -// This class is fully functioning and suitable for use in C++ -// applications that use the Qt framework. Other applications should -// use this classes' implementation as a reference implementation. -// -class MessageServer - : public QObject -{ - Q_OBJECT; - -public: - using port_type = quint16; - using Frequency = Radio::Frequency; - - MessageServer (QObject * parent = nullptr, - QString const& version = QString {}, QString const& revision = QString {}); - - // start or restart the server, if the multicast_group_address - // argument is given it is assumed to be a multicast group address - // which the server will join - Q_SLOT void start (port_type port, - QHostAddress const& multicast_group_address = QHostAddress {}); - - // ask the client with identification 'id' to make the same action - // as a double click on the decode would - // - // note that the client is not obliged to take any action and only - // takes any action if the decode is present and is a CQ or QRZ message - Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency - , QString const& mode, QString const& message, bool low_confidence, quint8 modifiers); - - // ask the client with identification 'id' to replay all decodes - Q_SLOT void replay (QString const& id); - - // ask the client with identification 'id' to halt transmitting - // auto_only just disables auto Tx, otherwise halt is immediate - Q_SLOT void halt_tx (QString const& id, bool auto_only); - - // ask the client with identification 'id' to set the free text - // message and optionally send it ASAP - Q_SLOT void free_text (QString const& id, QString const& text, bool send); - - // ask the client with identification 'id' to set the location provided - Q_SLOT void location (QString const& id, QString const& location); - - // ask the client with identification 'id' to highlight the callsign - // specified with the given colors - Q_SLOT void highlight_callsign (QString const& id, QString const& callsign - , QColor const& bg = QColor {}, QColor const& fg = QColor {} - , bool last_only = false); - - // the following signals are emitted when a client broadcasts the - // matching message - Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); - Q_SIGNAL void status_update (QString const& id, Frequency, 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); - Q_SIGNAL void client_closed (QString const& id); - Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time - , quint32 delta_frequency, QString const& mode, QString const& message - , bool low_confidence, bool off_air); - Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency - , qint32 drift, QString const& callsign, QString const& grid, qint32 power - , bool off_air); - Q_SIGNAL void qso_logged (QString const& 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); - Q_SIGNAL void clear_decodes (QString const& id); - Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF); - - // this signal is emitted when a network error occurs - Q_SIGNAL void error (QString const&) const; - -private: - class impl; - pimpl m_; -}; - -#endif diff --git a/js8call.pro b/js8call.pro index 9b9d995..727d2dc 100644 --- a/js8call.pro +++ b/js8call.pro @@ -92,7 +92,8 @@ SOURCES += \ ProcessThread.cpp \ DecoderThread.cpp \ Decoder.cpp \ - APRSISClient.cpp + APRSISClient.cpp \ + MessageServer.cpp HEADERS += qt_helpers.hpp \ pimpl_h.hpp pimpl_impl.hpp \ @@ -135,7 +136,8 @@ HEADERS += qt_helpers.hpp \ ProcessThread.h \ DecoderThread.h \ Decoder.h \ - APRSISClient.h + APRSISClient.h \ + MessageServer.h INCLUDEPATH += qmake_only diff --git a/mainwindow.cpp b/mainwindow.cpp index be00918..6e1e834 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -435,6 +435,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, version (), revision (), m_config.udp_server_name (), m_config.udp_server_port (), this}}, + m_messageServer { new MessageServer(this) }, m_n3fjpClient { new TCPClient{this}}, m_spotClient { new SpotClient{m_messageClient, this}}, m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}}, @@ -480,8 +481,19 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // notification audio operates in its own thread at a lower priority m_notification->moveToThread(&m_notificationAudioThread); - // move the aprs client to its own network thread at a lower priority + // move the aprs client and the message server to its own network thread at a lower priority m_aprsClient->moveToThread(&m_networkThread); + m_messageServer->moveToThread(&m_networkThread); + + // hook up the message server slots and signals and disposal + connect (m_messageServer, &MessageServer::error, this, &MainWindow::udpNetworkError); + connect (m_messageServer, &MessageServer::message, this, &MainWindow::networkMessage); + connect (this, &MainWindow::apiSetServer, m_messageServer, &MessageServer::setServer); + connect (this, &MainWindow::apiStartServer, m_messageServer, &MessageServer::start); + connect (this, &MainWindow::apiStopServer, m_messageServer, &MessageServer::stop); + connect (&m_config, &Configuration::tcp_server_changed, m_messageServer, &MessageServer::setServerHost); + connect (&m_config, &Configuration::tcp_server_port_changed, m_messageServer, &MessageServer::setServerPort); + connect (&m_networkThread, &QThread::finished, m_messageServer, &QObject::deleteLater); // hook up the aprs client slots and signals and disposal connect (this, &MainWindow::aprsClientEnqueueSpot, m_aprsClient, &APRSISClient::enqueueSpot); @@ -536,9 +548,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO); connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); - // Network message handlers - connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError); + connect (m_messageClient, &MessageClient::error, this, &MainWindow::udpNetworkError); connect (m_messageClient, &MessageClient::message, this, &MainWindow::networkMessage); #if 0 @@ -1005,7 +1016,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // prep prepareHeartbeatMode(canCurrentModeSendHeartbeat() && ui->actionModeJS8HB->isChecked()); - prepareSpotting(); auto enterFilter = new EnterKeyPressEater(); connect(enterFilter, &EnterKeyPressEater::enterKeyPressed, this, [this](QObject *, QKeyEvent *, bool *pProcessed){ @@ -1595,6 +1605,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->dialFreqDownButton->setFixedSize(30, 24); // Prepare spotting configuration... + prepareApi(); prepareSpotting(); displayActivity(true); @@ -3107,6 +3118,7 @@ void MainWindow::openSettings(int tab){ enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook + prepareApi(); prepareSpotting(); if(m_config.restart_audio_input ()) { @@ -3167,6 +3179,16 @@ void MainWindow::openSettings(int tab){ } } +void MainWindow::prepareApi(){ + bool enabled = true; + if(enabled){ + emit apiSetServer("0.0.0.0", 2442); + emit apiStartServer(); + } else { + emit apiStopServer(); + } +} + void MainWindow::prepareSpotting(){ if(m_config.spot_to_reporting_networks ()){ spotSetLocal(); @@ -3186,6 +3208,7 @@ void MainWindow::on_spotButton_clicked(bool checked){ m_config.set_spot_to_reporting_networks(checked); // 2. prepare + prepareApi(); prepareSpotting(); } @@ -3400,6 +3423,12 @@ void MainWindow::updateCurrentBand(){ m_wideGraph->setRxBand (band_name); qDebug() << "setting band" << band_name; + sendNetworkMessage("RIG.FREQ", "", { + {"_ID", QVariant(-1)}, + {"BAND", QVariant(band_name)}, + {"DIAL", QVariant((quint64)dialFrequency())}, + {"OFFSET", QVariant((quint64)currentFreqOffset())} + }); m_lastBand = band_name; band_changed(dial_frequency); @@ -7330,6 +7359,7 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, // Log to JS8Call API if(m_config.udpEnabled()){ sendNetworkMessage("LOG.QSO", QString(ADIF), { + {"_ID", QVariant(-1)}, {"UTC.ON", QVariant(QSO_date_on.toMSecsSinceEpoch())}, {"UTC.OFF", QVariant(QSO_date_off.toMSecsSinceEpoch())}, {"CALL", QVariant(call)}, @@ -10518,6 +10548,7 @@ void MainWindow::processRxActivity() { if(canSendNetworkMessage()){ sendNetworkMessage("RX.ACTIVITY", d.text, { + {"_ID", QVariant(-1)}, {"FREQ", QVariant(d.freq)}, {"SNR", QVariant(d.snr)}, {"SPEED", QVariant(d.submode)}, @@ -11017,6 +11048,7 @@ void MainWindow::processCommandActivity() { // and send it to the network in case we want to interact with it from an external app... if(canSendNetworkMessage()){ sendNetworkMessage("RX.DIRECTED", ad.text, { + {"_ID", QVariant(-1)}, {"FROM", QVariant(d.from)}, {"TO", QVariant(d.to)}, {"CMD", QVariant(d.cmd)}, @@ -11763,6 +11795,7 @@ void MainWindow::processSpots() { if(canSendNetworkMessage()){ sendNetworkMessage("RX.SPOT", "", { + {"_ID", QVariant(-1)}, {"DIAL", QVariant(dial)}, {"OFFSET", QVariant(d.freq)}, {"CALL", QVariant(d.call)}, @@ -12552,6 +12585,7 @@ void MainWindow::emitPTT(bool on){ // emit to network sendNetworkMessage("RIG.PTT", on ? "on" : "off", { + {"_ID", QVariant(-1)}, {"PTT", QVariant(on)}, {"UTC", QVariant(DriftingDateTime::currentDateTimeUtc().toMSecsSinceEpoch())}, }); @@ -12570,11 +12604,12 @@ void MainWindow::emitTones(){ } sendNetworkMessage("TX.FRAME", "", { + {"_ID", QVariant(-1)}, {"TONES", t} }); } -void MainWindow::networkMessage(Message const &message) +void MainWindow::udpNetworkMessage(Message const &message) { if(!m_config.udpEnabled()){ return; @@ -12584,16 +12619,33 @@ void MainWindow::networkMessage(Message const &message) return; } + networkMessage(message); +} + +void MainWindow::tcpNetworkMessage(Message const &message) +{ + // if(!m_config.tcpEnabled()){ + // return; + // } + // + // if(!m_config.accept_tcp_requests()){ + // return; + // } + + networkMessage(message); +} + +void MainWindow::networkMessage(Message const &message) +{ auto type = message.type(); if(type == "PING"){ return; } - qDebug() << "try processing network message" << type; + auto id = message.id(); - auto params = message.params(); - auto id = params.value("_ID", QVariant(0)); + qDebug() << "try processing network message" << type << id; // Inspired by FLDigi // TODO: MAIN.RX - Turn on RX @@ -12840,23 +12892,27 @@ bool MainWindow::canSendNetworkMessage(){ } void MainWindow::sendNetworkMessage(QString const &type, QString const &message){ - if(!m_config.udpEnabled()){ + if(!canSendNetworkMessage()){ return; } - m_messageClient->send(Message(type, message)); + auto m = Message(type, message); + m_messageClient->send(m); + m_messageServer->send(m); } void MainWindow::sendNetworkMessage(QString const &type, QString const &message, QMap const ¶ms) { - if(!m_config.udpEnabled()){ + if(!canSendNetworkMessage()){ return; } - m_messageClient->send(Message(type, message, params)); + auto m = Message(type, message, params); + m_messageClient->send(m); + m_messageServer->send(m); } -void MainWindow::networkError (QString const& e) +void MainWindow::udpNetworkError (QString const& e) { if(!m_config.udpEnabled()){ return; @@ -12865,7 +12921,7 @@ void MainWindow::networkError (QString const& e) if(!m_config.accept_udp_requests()){ return; } - if (m_splash && m_splash->isVisible ()) m_splash->hide (); + if (MessageBox::Retry == MessageBox::warning_message (this, tr ("Network Error") , tr ("Error: %1\nUDP server %2:%3") .arg (e) @@ -12880,6 +12936,32 @@ void MainWindow::networkError (QString const& e) } } +void MainWindow::tcpNetworkError (QString const& e) +{ + /* + if(!m_config.tcpEnabled()){ + return; + } + + if(!m_config.accept_tcp_requests()){ + return; + } + + if (MessageBox::Retry == MessageBox::warning_message (this, tr ("Network Error") + , tr ("Error: %1\nTCP server %2:%3") + .arg (e) + .arg (m_config.tcp_server_name ()) + .arg (m_config.tcp_server_port ()) + , QString {} + , MessageBox::Cancel | MessageBox::Retry + , MessageBox::Cancel)) + { + // retry server lookup + //m_messageClient->set_server (m_config.udp_server_name ()); + } + */ +} + void MainWindow::on_syncSpinBox_valueChanged(int n) { m_minSync=n; diff --git a/mainwindow.h b/mainwindow.h index ae140dc..265dc39 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -46,6 +46,7 @@ #include "qpriorityqueue.h" #include "varicode.h" #include "MessageClient.hpp" +#include "MessageServer.h" #include "TCPClient.h" #include "SpotClient.h" #include "APRSISClient.h" @@ -207,6 +208,7 @@ private slots: void on_actionReset_Window_Sizes_triggered(); void on_actionSettings_triggered(); void openSettings(int tab=0); + void prepareApi(); void prepareSpotting(); void on_spotButton_clicked(bool checked); void on_monitorButton_clicked (bool); @@ -409,11 +411,14 @@ private slots: void on_cbTx6_toggled(bool b); void emitPTT(bool on); void emitTones(); + void udpNetworkMessage(Message const &message); + void tcpNetworkMessage(Message const &message); void networkMessage(Message const &message); bool canSendNetworkMessage(); void sendNetworkMessage(QString const &type, QString const &message); void sendNetworkMessage(QString const &type, QString const &message, const QMap ¶ms); - void networkError (QString const&); + void udpNetworkError (QString const&); + void tcpNetworkError (QString const&); void on_ClrAvgButton_clicked(); void on_syncSpinBox_valueChanged(int n); void on_TxPowerComboBox_currentIndexChanged(const QString &arg1); @@ -436,6 +441,10 @@ private slots: void refreshTextDisplay(); private: + Q_SIGNAL void apiSetServer(QString host, quint16 port); + Q_SIGNAL void apiStartServer(); + Q_SIGNAL void apiStopServer(); + Q_SIGNAL void aprsClientEnqueueSpot(QString by_call, QString from_call, QString grid, QString comment); Q_SIGNAL void aprsClientEnqueueThirdParty(QString by_call, QString from_call, QString text); Q_SIGNAL void aprsClientSetServer(QString host, quint16 port); @@ -932,6 +941,7 @@ private: double m_toneSpacing; int m_firstDecode; MessageClient * m_messageClient; + MessageServer * m_messageServer; TCPClient * m_n3fjpClient; PSK_Reporter *psk_Reporter; SpotClient *m_spotClient;