From f4688b44d322c4cb8121a733501ac5307446d215 Mon Sep 17 00:00:00 2001 From: Jordan Sherer Date: Wed, 8 Aug 2018 17:15:49 -0400 Subject: [PATCH] Working through some common api commands --- MessageClient.cpp | 103 ++++++++++++++++++++++++++++------ MessageClient.hpp | 30 +++++++++- mainwindow.cpp | 139 ++++++++++++++++++++++++++++++++++++++-------- mainwindow.h | 10 +++- udp.py | 104 ++++++++++++++++++++++++++-------- 5 files changed, 317 insertions(+), 69 deletions(-) diff --git a/MessageClient.cpp b/MessageClient.cpp index 1bf07ac..3ff6d04 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -16,10 +17,66 @@ #include "moc_MessageClient.cpp" +Message::Message() +{ +} + +Message::Message(QString const &type, QString const &value): + type_{ type }, + value_{ value } +{ +} + +Message::Message(QString const &type, QString const &value, QMap const ¶ms): + type_{ type }, + value_{ value }, + params_{ params } +{ +} + +void Message::read(const QJsonObject &json){ + if(json.contains("type") && json["type"].isString()){ + type_ = json["type"].toString(); + } + + if(json.contains("value") && json["value"].isString()){ + value_ = json["value"].toString(); + } + + if(json.contains("params") && json["params"].isObject()){ + params_.clear(); + + QJsonObject params = json["params"].toObject(); + foreach(auto key, params.keys()){ + params_[key] = params[key].toVariant(); + } + } +} + +void Message::write(QJsonObject &json) const{ + json["type"] = type_; + json["value"] = value_; + + QJsonObject params; + foreach(auto key, params_.keys()){ + params.insert(key, QJsonValue::fromVariant(params_[key])); + } + json["params"] = params; +} + +QByteArray Message::toJson() const { + QJsonObject o; + write(o); + + QJsonDocument d(o); + return d.toJson(); +} + + class MessageClient::impl : public QUdpSocket { - Q_OBJECT; + Q_OBJECT public: impl (QString const& id, QString const& version, QString const& revision, @@ -84,6 +141,7 @@ public: QByteArray last_message_; }; + #include "MessageClient.moc" void MessageClient::impl::host_info_results (QHostInfo host_info) @@ -136,14 +194,26 @@ void MessageClient::impl::parse_message (QByteArray const& msg) { try { - QList segments = msg.split('|'); - if(segments.isEmpty()){ + if(msg.isEmpty()){ return; } - QString type(segments.first()); - QString message(segments.mid(1).join('|')); - Q_EMIT self_->message_received(type, message); + 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(!d.isObject()){ + Q_EMIT self_->error(QString {"MessageClient json parse error: json is not an object"}); + return; + } + + Message m; + m.read(d.object()); + Q_EMIT self_->message(m); + } catch (std::exception const& e) { @@ -159,8 +229,12 @@ void MessageClient::impl::heartbeat () { if (server_port_ && !server_.isNull ()) { - QByteArray message("PING|"); - writeDatagram (message, server_, server_port_); + Message m("PING", "", QMap{ + {"NAME", QVariant(QApplication::applicationName())}, + {"VERSION", QVariant(QApplication::applicationVersion())}, + {"UTC", QVariant(QDateTime::currentDateTimeUtc().toSecsSinceEpoch())} + }); + writeDatagram (m.toJson(), server_, server_port_); } } @@ -168,8 +242,8 @@ void MessageClient::impl::closedown () { if (server_port_ && !server_.isNull ()) { - QByteArray message("EXIT|"); - writeDatagram (message, server_, server_port_); + Message m("CLOSE"); + writeDatagram (m.toJson(), server_, server_port_); } } @@ -181,7 +255,6 @@ void MessageClient::impl::send_message (QByteArray const& message) { if (message != last_message_) // avoid duplicates { - qDebug() << "outgoing udp message" << message; writeDatagram (message, server_, server_port_); last_message_ = message; } @@ -270,12 +343,8 @@ void MessageClient::set_server_port (port_type server_port) m_->server_port_ = server_port; } -void MessageClient::send_message(QString const &type, QString const &message){ - QByteArray b; - b.append(type); - b.append('|'); - b.append(message); - m_->send_message(b); +void MessageClient::send(Message const &message){ + m_->send_message(message.toJson()); } void MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address diff --git a/MessageClient.hpp b/MessageClient.hpp index 4afac71..69efcee 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include "Radio.hpp" #include "pimpl_h.hpp" @@ -13,6 +15,29 @@ class QByteArray; class QHostAddress; class QColor; + +class Message { +public: + Message(); + Message(QString const &type, QString const &value=""); + Message(QString const &type, QString const &value, QMap const ¶ms); + + void read(const QJsonObject &json); + void write(QJsonObject &json) const; + + QByteArray toJson() const; + + QString type() const { return type_; } + QString value() const { return value_; } + QMap params() const { return params_; } + +private: + QString type_; + QString value_; + QMap params_; +}; + + // // MessageClient - Manage messages sent and replies received from a // matching server (MessageServer) at the other end of @@ -24,7 +49,7 @@ class QColor; class MessageClient : public QObject { - Q_OBJECT; + Q_OBJECT public: using Frequency = Radio::Frequency; @@ -48,7 +73,7 @@ public: Q_SLOT void set_server_port (port_type server_port = 0u); // this slot is used to send an arbitrary message - Q_SLOT void send_message(QString const &type, QString const &message); + Q_SLOT void send(Message const &message); // this slot may be used to send arbitrary UDP datagrams to and // destination allowing the underlying socket to be used for general @@ -59,6 +84,7 @@ public: // with send_raw_datagram() above) Q_SLOT void add_blocked_destination (QHostAddress const&); + Q_SIGNAL void message(Message const &message); // this signal is emitted when the a reply a message is received Q_SIGNAL void message_received(QString const &type, QString const &message); diff --git a/mainwindow.cpp b/mainwindow.cpp index f571bae..a1de5ff 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -547,7 +547,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, // Network message handlers connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError); - connect (m_messageClient, &MessageClient::message_received, this, &MainWindow::networkMessage); + connect (m_messageClient, &MessageClient::message, this, &MainWindow::networkMessage); #if 0 // Hook up WSPR band hopping @@ -1002,7 +1002,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, ui->dxCallEntry->clear(); ui->dxGridEntry->clear(); auto f = findFreeFreqOffset(500, 2000, 50); - setFreqForRestore(f, false); + setFreqOffsetForRestore(f, false); ui->cbVHFcontest->setChecked(false); // this needs to always be false ui->spotButton->setChecked(m_config.spot_to_psk_reporter()); @@ -1117,7 +1117,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, bool isAllCall = isAllCallIncluded(selectedCall); bool missingCallsign = selectedCall.isEmpty(); if(!missingCallsign && m_callActivity.contains(selectedCall)){ - setFreqForRestore(m_callActivity[selectedCall].freq, true); + setFreqOffsetForRestore(m_callActivity[selectedCall].freq, true); } auto directedMenu = menu->addMenu("Directed"); @@ -2192,10 +2192,14 @@ void MainWindow::bumpFqso(int n) //bumpFqso() } } +Radio::Frequency MainWindow::dialFrequency() { + return Frequency {m_rigState.ptt () && m_rigState.split () ? + m_rigState.tx_frequency () : m_rigState.frequency ()}; +} + void MainWindow::displayDialFrequency () { - Frequency dial_frequency {m_rigState.ptt () && m_rigState.split () ? - m_rigState.tx_frequency () : m_rigState.frequency ()}; + auto dial_frequency = dialFrequency(); // lookup band auto const& band_name = m_config.bands ()->find (dial_frequency); @@ -4440,11 +4444,7 @@ void MainWindow::stopTx() ui->extFreeTextMsgEdit->setReadOnly(false); update_dynamic_property(ui->extFreeTextMsgEdit, "transmitting", false); on_stopTxButton_clicked(); - - // if we have a previousFrequency, and should jump to it, do it - if(m_previousFreq && m_shouldRestoreFreq){ - setFreqForRestore(m_previousFreq, false); - } + tryRestoreFreqOffset(); } ptt0Timer.start(200); //end-of-transmission sequencer delay @@ -5571,6 +5571,12 @@ void MainWindow::displayTextForFreq(QString text, int freq, QDateTime date, bool if(newLine){ block = -1; } + + sendNetworkMessage("RECV", text, { + {"UTC", QVariant(date.toSecsSinceEpoch())}, + {"OFFSET", QVariant(freq)}, + }); + m_rxFrameBlockNumbers[freq] = writeMessageTextToUI(date, text, freq, bold, block); } @@ -5673,7 +5679,7 @@ void MainWindow::createMessageTransmitQueue(QString const& text){ m_txFrameQueue.append(frames); m_txFrameCount = frames.length(); - int freq = currentFreq(); + int freq = currentFreqOffset(); QStringList lines; foreach(auto frame, frames){ @@ -5743,7 +5749,7 @@ void MainWindow::on_extFreeTextMsgEdit_currentTextChanged (QString const& text) } } -int MainWindow::currentFreq(){ +int MainWindow::currentFreqOffset(){ return ui->RxFreqSpinBox->value(); } @@ -6041,7 +6047,7 @@ bool MainWindow::prepareNextMessageFrame() bool MainWindow::isFreqOffsetFree(int f, int bw){ // if this frequency is our current frequency, it's always "free" - if(currentFreq() == f){ + if(currentFreqOffset() == f){ return true; } @@ -6160,7 +6166,7 @@ void MainWindow::prepareBacon(){ } // Queue the beacon - enqueueMessage(PriorityLow, lines.join(QChar('\n')), currentFreq(), [this](){ + enqueueMessage(PriorityLow, lines.join(QChar('\n')), currentFreqOffset(), [this](){ m_nextBeaconQueued = false; }); @@ -7184,7 +7190,7 @@ void MainWindow::on_tableWidgetRXAll_cellClicked(int row, int /*col*/){ auto item = ui->tableWidgetRXAll->item(row, 0); int offset = item->text().toInt(); - setFreqForRestore(offset, false); + setFreqOffsetForRestore(offset, false); ui->tableWidgetCalls->selectionModel()->select( ui->tableWidgetCalls->selectionModel()->selection(), @@ -7244,7 +7250,7 @@ void MainWindow::on_tableWidgetCalls_cellClicked(int /*row*/, int /*col*/){ } auto d = m_callActivity[call]; - setFreqForRestore(d.freq, false); + setFreqOffsetForRestore(d.freq, false); ui->tableWidgetRXAll->selectionModel()->select( ui->tableWidgetRXAll->selectionModel()->selection(), @@ -7443,7 +7449,7 @@ void MainWindow::setXIT(int n, Frequency base) Q_EMIT transmitFrequency (ui->TxFreqSpinBox->value () - m_XIT); } -void MainWindow::setFreqForRestore(int freq, bool shouldRestore){ +void MainWindow::setFreqOffsetForRestore(int freq, bool shouldRestore){ setFreq4(freq, freq); if(shouldRestore){ m_shouldRestoreFreq = true; @@ -7453,13 +7459,23 @@ void MainWindow::setFreqForRestore(int freq, bool shouldRestore){ } } +bool MainWindow::tryRestoreFreqOffset(){ + if(!m_shouldRestoreFreq || m_previousFreq == 0){ + return false; + } + + setFreqOffsetForRestore(m_previousFreq, false); + + return true; +} + void MainWindow::setFreq4(int rxFreq, int txFreq) { if(rxFreq != txFreq){ txFreq = rxFreq; } - m_previousFreq = currentFreq(); + m_previousFreq = currentFreqOffset(); if (ui->RxFreqSpinBox->isEnabled ()) ui->RxFreqSpinBox->setValue(rxFreq); ui->labDialFreqOffset->setText(QString("%1 Hz").arg(rxFreq)); @@ -8269,7 +8285,7 @@ void MainWindow::processRxActivity() { int freq = d.freq / 10 * 10; - bool shouldDisplay = abs(freq - currentFreq()) <= 10; + bool shouldDisplay = abs(freq - currentFreqOffset()) <= 10; // if this is a (recent) directed offset, bump the cache, and display... // this will allow a directed free text command followed by non-buffered data frames. @@ -8684,6 +8700,10 @@ void MainWindow::processTxQueue(){ // decide if it's ok to transmit... int f = head.freq; + if(f == -1){ + f = currentFreqOffset(); + } + if(!isFreqOffsetFree(f, 60)){ f = findFreeFreqOffset(500, 2500, 60); } @@ -8720,7 +8740,7 @@ void MainWindow::processTxQueue(){ (ui->beaconButton->isChecked() && message.message.contains("BEACON")) ){ // then try to set the frequency... - setFreqForRestore(f, true); + setFreqOffsetForRestore(f, true); // then prepare to transmit... toggleTx(true); @@ -8940,30 +8960,101 @@ void MainWindow::postWSPRDecode (bool is_new, QStringList parts) #endif } -void MainWindow::networkMessage(QString const &type, QString const &message) +void MainWindow::networkMessage(Message const &message) { if(!m_config.accept_udp_requests()){ return; } + auto type = message.type(); + + if(type == "PONG"){ + return; + } + + if(type == "GET_CALL_ACTIVITY"){ + QMap calls; + foreach(auto cd, m_callActivity.values()){ + QMap detail; + detail["SNR"] = QVariant(cd.snr); + detail["GRID"] = QVariant(cd.grid); + detail["UTC"] = QVariant(cd.utcTimestamp.toSecsSinceEpoch()); + calls[cd.call] = QVariant(detail); + } + + sendNetworkMessage("CALL_ACTIVITY", "", calls); + return; + } + + if(type == "GET_CALLSIGN"){ + sendNetworkMessage("CALLSIGN", m_config.my_callsign()); + return; + } + if(type == "GET_GRID"){ sendNetworkMessage("GRID", m_config.my_grid()); return; } if(type == "SET_GRID"){ - m_config.set_location(message); + m_config.set_location(message.value()); return; } + + if(type == "GET_FREQ"){ + sendNetworkMessage("FREQ", "", { + {"DIAL", QVariant((quint64)dialFrequency())}, + {"OFFSET", QVariant((quint64)currentFreqOffset())} + }); + return; + } + + if(type == "SET_FREQ"){ + auto params = message.params(); + if(params.contains("DIAL")){ + bool ok = false; + auto f = params["DIAL"].toInt(&ok); + if(ok){ + setRig(f); + displayDialFrequency(); + } + } + + if(params.contains("OFFSET")){ + bool ok = false; + auto f = params["OFFSET"].toInt(&ok); + if(ok){ + setFreqOffsetForRestore(f, false); + } + } + return; + } + + if(type == "SEND_MESSAGE"){ + auto text = message.value(); + if(!text.isEmpty()){ + enqueueMessage(PriorityNormal, text, -1, nullptr); + return; + } + } + + qDebug() << "Unable to process networkMessage:" << type; } -void MainWindow::sendNetworkMessage(QString const &type, QString const &message) +void MainWindow::sendNetworkMessage(QString const &type, QString const &message){ + m_messageClient->send(Message(type, message)); +} + +void MainWindow::sendNetworkMessage(QString const &type, QString const &message, QMap const ¶ms) { - m_messageClient->send_message(type, message); + m_messageClient->send(Message(type, message, params)); } 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") diff --git a/mainwindow.h b/mainwindow.h index f67e925..de5ce5f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -43,6 +43,7 @@ #include "qorderedmap.h" #include "qpriorityqueue.h" #include "varicode.h" +#include "MessageClient.hpp" #define NUM_JT4_SYMBOLS 206 //(72+31)*2, embedded sync #define NUM_JT65_SYMBOLS 126 //63 data + 63 sync @@ -123,7 +124,8 @@ public slots: void readFromStdout(); void p1ReadFromStdout(); void setXIT(int n, Frequency base = 0u); - void setFreqForRestore(int freq, bool shouldRestore); + void setFreqOffsetForRestore(int freq, bool shouldRestore); + bool tryRestoreFreqOffset(); void setFreq4(int rxFreq, int txFreq); void msgAvgDecode2(); void fastPick(int x0, int x1, int y); @@ -259,7 +261,7 @@ private slots: void on_freeTextMsg_currentTextChanged (QString const&); void on_nextFreeTextMsg_currentTextChanged (QString const&); void on_extFreeTextMsgEdit_currentTextChanged (QString const&); - int currentFreq(); + int currentFreqOffset(); int countFT8MessageFrames(QString const& text); QStringList buildFT8MessageFrames(QString const& text); QString parseFT8Message(QString input, bool *isFree); @@ -313,8 +315,9 @@ private slots: void on_cbCQonly_toggled(bool b); void on_cbFirst_toggled(bool b); void on_cbAutoSeq_toggled(bool b); - void networkMessage(QString const &type, QString const &message); + void networkMessage(Message const &message); void sendNetworkMessage(QString const &type, QString const &message); + void sendNetworkMessage(QString const &type, QString const &message, const QMap ¶ms); void networkError (QString const&); void on_ClrAvgButton_clicked(); void on_syncSpinBox_valueChanged(int n); @@ -811,6 +814,7 @@ private: void pskSetLocal (); void pskPost(DecodedText const& decodedtext); void pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid); + Radio::Frequency dialFrequency(); void displayDialFrequency (); void transmitDisplay (bool); void processMessage(DecodedText const&, Qt::KeyboardModifiers = 0); diff --git a/udp.py b/udp.py index 0fe6ce9..13411d2 100644 --- a/udp.py +++ b/udp.py @@ -1,39 +1,97 @@ from __future__ import print_function +import json from socket import socket, AF_INET, SOCK_DGRAM import time listen = ('127.0.0.1', 2237) -def main(): - sock = socket(AF_INET, SOCK_DGRAM) - print("listening on", ':'.join(map(str, listen))) - sock.bind(listen) + +def from_message(content): try: - while True: - content, addr = sock.recvfrom(65500) - print("from:", ":".join(map(str, addr))) + return json.loads(content) + except ValueError: + return {} - typ, msg = content.split('|') - print("->", typ) - print("->", msg) - if typ == "PING": - print("sending pong reply...", end="") - sock.sendto("PONG", addr) - print("done") +def to_message(typ, value='', params=None): + if params is None: + params = {} + return json.dumps({'type': typ, 'value': value, 'params': params}) - sock.sendto("SET_GRID|EM73NA99", addr) - time.sleep(1) - sock.sendto("SET_GRID|EM73NA98", addr) - time.sleep(1) - sock.sendto("SET_GRID|EM73NA97", addr) - if typ == "EXIT": - break - finally: - sock.close() +class Server(object): + def process(self, message): + typ = message.get('type', '') + value = message.get('value', '') + params = message.get('params', {}) + if not typ: + return + + print('->', typ) + + if value: + print('-> value', value) + + if params: + print('-> params: ', params) + + if typ == 'PING': + self.send('GET_GRID') + self.send('GET_FREQ') + self.send('GET_CALLSIGN') + self.send('GET_CALL_ACTIVITY') + + #### elif typ == 'GRID': + #### if value != 'EM73TU49TQ': + #### self.send('SET_GRID', 'EM73TU49TQ') + + #### elif typ == 'FREQ': + #### if params.get('DIAL', 0) != 14064000: + #### self.send('SET_FREQ', '', {"DIAL": 14064000, "OFFSET": 977}) + #### self.send('SEND_MESSAGE', 'HELLO WORLD') + + elif typ == 'CLOSE': + self.close() + + def send(self, *args, **kwargs): + message = to_message(*args, **kwargs) + print('outgoing message:', message) + self.sock.sendto(message, self.reply_to) + + def listen(self): + print('listening on', ':'.join(map(str, listen))) + self.sock = socket(AF_INET, SOCK_DGRAM) + self.sock.bind(listen) + self.listening = True + try: + while self.listening: + content, addr = self.sock.recvfrom(65500) + print('incoming message:', ':'.join(map(str, addr))) + + try: + message = json.loads(content) + except ValueError: + message = {} + + if not message: + continue + + self.reply_to = addr + self.process(message) + + finally: + self.sock.close() + + def close(self): + self.listening = False + + + +def main(): + s = Server() + s.listen() if __name__ == '__main__': main()