Working through some common api commands

This commit is contained in:
Jordan Sherer 2018-08-08 17:15:49 -04:00
parent 7037baa0a6
commit f4688b44d3
5 changed files with 317 additions and 69 deletions

View File

@ -4,6 +4,7 @@
#include <vector>
#include <algorithm>
#include <QApplication>
#include <QUdpSocket>
#include <QHostInfo>
#include <QTimer>
@ -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<QString, QVariant> const &params):
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<QByteArray> 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<QString, QVariant>{
{"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

View File

@ -5,6 +5,8 @@
#include <QTime>
#include <QDateTime>
#include <QString>
#include <QJsonDocument>
#include <QJsonObject>
#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<QString, QVariant> const &params);
void read(const QJsonObject &json);
void write(QJsonObject &json) const;
QByteArray toJson() const;
QString type() const { return type_; }
QString value() const { return value_; }
QMap<QString, QVariant> params() const { return params_; }
private:
QString type_;
QString value_;
QMap<QString, QVariant> 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);

View File

@ -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<QString, QVariant> calls;
foreach(auto cd, m_callActivity.values()){
QMap<QString, QVariant> 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<QString, QVariant> const &params)
{
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")

View File

@ -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<QString, QVariant> &params);
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);

104
udp.py
View File

@ -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()