mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2026-06-05 15:34:39 -04:00
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
This commit is contained in:
+148
-30
@@ -16,6 +16,7 @@
|
||||
#include <QDebug>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QProgressDialog>
|
||||
#include <QHostInfo>
|
||||
|
||||
#include "revision_utils.hpp"
|
||||
#include "soundout.h"
|
||||
@@ -33,6 +34,7 @@
|
||||
#include "StationList.hpp"
|
||||
#include "LiveFrequencyValidator.hpp"
|
||||
#include "FrequencyItemDelegate.hpp"
|
||||
#include "MessageClient.hpp"
|
||||
|
||||
#include "ui_mainwindow.h"
|
||||
#include "moc_mainwindow.cpp"
|
||||
@@ -102,7 +104,6 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
|
||||
m_lastMessageType {-1},
|
||||
m_appDir {QApplication::applicationDirPath ()},
|
||||
mem_jt9 {shdmem},
|
||||
psk_Reporter (new PSK_Reporter (this)),
|
||||
m_msAudioOutputBuffered (0u),
|
||||
m_framesAudioInputBuffered (RX_SAMPLE_RATE / 10),
|
||||
m_downSampleFactor (downSampleFactor),
|
||||
@@ -117,7 +118,9 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
|
||||
m_firstDecode {0},
|
||||
m_optimizingProgress {"Optimizing decoder FFTs for your CPU.\n"
|
||||
"Please be patient,\n"
|
||||
"this may take a few minutes", QString {}, 0, 1, this}
|
||||
"this may take a few minutes", QString {}, 0, 1, this},
|
||||
m_messageClient {new MessageClient {QApplication::applicationName (), m_config.udp_server_name (), m_config.udp_server_port (), this}},
|
||||
psk_Reporter {new PSK_Reporter {m_messageClient, this}}
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
@@ -188,7 +191,12 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
|
||||
connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close);
|
||||
|
||||
|
||||
on_EraseButton_clicked();
|
||||
// Network message handlers
|
||||
connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ);
|
||||
connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes);
|
||||
connect (m_messageClient, &MessageClient::error, this, &MainWindow::networkError);
|
||||
|
||||
on_EraseButton_clicked ();
|
||||
|
||||
QActionGroup* modeGroup = new QActionGroup(this);
|
||||
ui->actionJT9_1->setActionGroup(modeGroup);
|
||||
@@ -264,6 +272,8 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme
|
||||
// hook up configuration signals
|
||||
connect (&m_config, &Configuration::transceiver_update, this, &MainWindow::handle_transceiver_update);
|
||||
connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
|
||||
connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
|
||||
connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
|
||||
|
||||
// set up message text validators
|
||||
ui->tx1->setValidator (new QRegExpValidator {message_alphabet, this});
|
||||
@@ -673,6 +683,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
|
||||
ui->readFreq->setStyleSheet("");
|
||||
ui->readFreq->setEnabled(false);
|
||||
|
||||
// things that might change that we need know about
|
||||
auto callsign = m_config.my_callsign ();
|
||||
|
||||
if (QDialog::Accepted == m_config.exec ())
|
||||
@@ -973,6 +984,8 @@ void MainWindow::displayDialFrequency ()
|
||||
|
||||
void MainWindow::statusChanged()
|
||||
{
|
||||
m_messageClient->status_update (m_dialFreq, m_mode, m_hisCall, QString::number (ui->rptSpinBox->value ()), m_modeTx);
|
||||
|
||||
QFile f {m_config.temp_dir ().absoluteFilePath ("wsjtx_status.txt")};
|
||||
if(f.open(QFile::WriteOnly | QIODevice::Text)) {
|
||||
QTextStream out(&f);
|
||||
@@ -1351,6 +1364,8 @@ void MainWindow::readFromStderr() //readFromStderr
|
||||
|
||||
void MainWindow::readFromStdout() //readFromStdout
|
||||
{
|
||||
QString band = m_config.bands ()->data (m_config.bands ()->find (m_dialFreq)).toString();
|
||||
|
||||
while(proc_jt9.canReadLine())
|
||||
{
|
||||
QByteArray t=proc_jt9.readLine();
|
||||
@@ -1430,6 +1445,8 @@ void MainWindow::readFromStdout() //readFromStdout
|
||||
m_QSOText=decodedtext;
|
||||
}
|
||||
|
||||
postDecode (true, decodedtext.string ());
|
||||
|
||||
// find and extract any report for myCall
|
||||
bool stdMsg = decodedtext.report(m_baseCall
|
||||
, Radio::base_callsign (ui->dxCallEntry-> text ().toUpper ().trimmed ())
|
||||
@@ -1476,6 +1493,7 @@ void MainWindow::on_EraseButton_clicked() //Erase
|
||||
m_QSOText.clear();
|
||||
if((ms-m_msErase)<500) {
|
||||
ui->decodedTextBrowser->clear();
|
||||
m_messageClient->clear_decodes ();
|
||||
QFile f(m_config.temp_dir ().absoluteFilePath ("decoded.txt"));
|
||||
if(f.exists()) f.remove();
|
||||
}
|
||||
@@ -1949,17 +1967,23 @@ void MainWindow::doubleClickOnCall(bool shift, bool ctrl)
|
||||
if(!m_decodedText2) cursor=ui->decodedTextBrowser2->textCursor();
|
||||
if(m_decodedText2) cursor=ui->decodedTextBrowser->textCursor();
|
||||
cursor.select(QTextCursor::LineUnderCursor);
|
||||
int i2=cursor.position();
|
||||
if(shift and i2==-9999) return; //Silence compiler warning
|
||||
int position {cursor.position()};
|
||||
if(shift && position==-9999) return; //Silence compiler warning
|
||||
|
||||
QString t;
|
||||
if(!m_decodedText2) t= ui->decodedTextBrowser2->toPlainText(); //Full contents
|
||||
if(m_decodedText2) t= ui->decodedTextBrowser->toPlainText();
|
||||
QString messages;
|
||||
if(!m_decodedText2) messages= ui->decodedTextBrowser2->toPlainText();
|
||||
//Full contents
|
||||
if(m_decodedText2) messages= ui->decodedTextBrowser->toPlainText();
|
||||
|
||||
QString t1 = t.mid(0,i2); //contents up to \n on selected line
|
||||
processMessage(messages, position, ctrl);
|
||||
}
|
||||
|
||||
void MainWindow::processMessage(QString const& messages, int position, bool ctrl)
|
||||
{
|
||||
QString t1 = messages.mid(0,position); //contents up to \n on selected line
|
||||
int i1=t1.lastIndexOf("\n") + 1; //points to first char of line
|
||||
DecodedText decodedtext;
|
||||
decodedtext = t1.mid(i1,i2-i1); //selected line
|
||||
decodedtext = messages.mid(i1,position-i1); //selected line
|
||||
|
||||
if (decodedtext.indexOf(" CQ ") > 0)
|
||||
{
|
||||
@@ -2509,7 +2533,7 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button
|
||||
, m_rptSent
|
||||
, m_rptRcvd
|
||||
, m_dateTimeQSO
|
||||
, (m_dialFreq + ui->TxFreqSpinBox->value ()) / 1.e6
|
||||
, m_dialFreq + ui->TxFreqSpinBox->value ()
|
||||
, m_config.my_callsign ()
|
||||
, m_config.my_grid ()
|
||||
, m_noSuffix
|
||||
@@ -2518,27 +2542,28 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button
|
||||
);
|
||||
}
|
||||
|
||||
void MainWindow::acceptQSO2(bool accepted)
|
||||
void MainWindow::acceptQSO2(QDateTime const& QSO_date, QString const& call, QString const& grid
|
||||
, Frequency dial_freq, QString const& mode
|
||||
, QString const& rpt_sent, QString const& rpt_received
|
||||
, QString const& tx_power, QString const& comments
|
||||
, QString const& name)
|
||||
{
|
||||
if(accepted)
|
||||
{
|
||||
auto const& bands_model = m_config.bands ();
|
||||
auto band = bands_model->data (bands_model->find (m_dialFreq + ui->TxFreqSpinBox->value ())).toString ();
|
||||
QString date = m_dateTimeQSO.toString("yyyy-MM-dd");
|
||||
date=date.mid(0,4) + date.mid(5,2) + date.mid(8,2);
|
||||
m_logBook.addAsWorked(m_hisCall,band,m_modeTx,date);
|
||||
QString band = ADIF::bandFromFrequency ((m_dialFreq + ui->TxFreqSpinBox->value ()) / 1.e6);
|
||||
QString date = m_dateTimeQSO.toString("yyyyMMdd");
|
||||
m_logBook.addAsWorked(m_hisCall,band,m_modeTx,date);
|
||||
|
||||
if (m_config.clear_DX ())
|
||||
{
|
||||
m_hisCall="";
|
||||
ui->dxCallEntry->setText("");
|
||||
m_hisGrid="";
|
||||
ui->dxGridEntry->setText("");
|
||||
m_rptSent="";
|
||||
m_rptRcvd="";
|
||||
m_qsoStart="";
|
||||
m_qsoStop="";
|
||||
}
|
||||
m_messageClient->qso_logged (QSO_date, call, grid, dial_freq, mode, rpt_sent, rpt_received, tx_power, comments, name);
|
||||
|
||||
if (m_config.clear_DX ())
|
||||
{
|
||||
m_hisCall="";
|
||||
ui->dxCallEntry->setText("");
|
||||
m_hisGrid="";
|
||||
ui->dxGridEntry->setText("");
|
||||
m_rptSent="";
|
||||
m_rptRcvd="";
|
||||
m_qsoStart="";
|
||||
m_qsoStop="";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3260,3 +3285,96 @@ void MainWindow::transmitDisplay (bool transmitting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a decoded CQ line and sets it up for reply
|
||||
void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text)
|
||||
{
|
||||
if (!m_config.accept_udp_requests ())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto decode_parts = message_text.split (' ', QString::SkipEmptyParts);
|
||||
|
||||
if (decode_parts.contains ("CQ") || decode_parts.contains ("QRZ"))
|
||||
{
|
||||
// a message we are willing to accept
|
||||
auto cqtext = QString {"%1 %2 %3 %4 %5 %6"}.arg (time.toString ("hhmm"))
|
||||
.arg (snr, 3)
|
||||
.arg (delta_time, 4, 'f', 1)
|
||||
.arg (delta_frequency, 4)
|
||||
.arg (mode)
|
||||
.arg (message_text);
|
||||
auto messages = ui->decodedTextBrowser->toPlainText ();
|
||||
auto position = messages.lastIndexOf (cqtext);
|
||||
if (position >= 0)
|
||||
{
|
||||
if (m_config.udpWindowToFront ())
|
||||
{
|
||||
show ();
|
||||
raise ();
|
||||
activateWindow ();
|
||||
}
|
||||
if (m_config.udpWindowRestore () && isMinimized ())
|
||||
{
|
||||
showNormal ();
|
||||
raise ();
|
||||
}
|
||||
// find the linefeed at the end of the line
|
||||
position = ui->decodedTextBrowser->toPlainText().indexOf("\n",position);
|
||||
processMessage (messages, position, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug () << "reply to CQ request ignored, decode not found:" << cqtext;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug () << "rejecting UDP request to reply as decode is not a CQ or QRZ";
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::replayDecodes ()
|
||||
{
|
||||
// we accept this request even if the setting to accept UDP requests
|
||||
// is not checked
|
||||
Q_FOREACH (auto const& message, ui->decodedTextBrowser->toPlainText ().split ('\n', QString::SkipEmptyParts))
|
||||
{
|
||||
if (message.size() >= 4 && message.left (4) != "----")
|
||||
{
|
||||
auto eom_pos = message.indexOf (' ', 35);
|
||||
// we always want at least the characters to position 35
|
||||
if (eom_pos < 35)
|
||||
{
|
||||
eom_pos = message.size () - 1;
|
||||
}
|
||||
postDecode (false, message.left (eom_pos + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::postDecode (bool is_new, QString const& message)
|
||||
{
|
||||
auto decode = message.trimmed ();
|
||||
auto parts = decode.left (21).split (' ', QString::SkipEmptyParts);
|
||||
if (parts.size () >= 5)
|
||||
{
|
||||
m_messageClient->decode (is_new, QTime::fromString (parts[0], "hhmm"), parts[1].toInt ()
|
||||
, parts[2].toFloat (), parts[3].toUInt (), parts[4], decode.mid (21));
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::networkError (QString const& e)
|
||||
{
|
||||
if (QMessageBox::Retry == QMessageBox::warning (this, tr ("Network Error")
|
||||
, tr ("Error: %1\nUDP server %2:%3")
|
||||
.arg (e)
|
||||
.arg (m_config.udp_server_name ())
|
||||
.arg (m_config.udp_server_port ())
|
||||
, QMessageBox::Cancel | QMessageBox::Retry, QMessageBox::Cancel))
|
||||
{
|
||||
// retry server lookup
|
||||
m_messageClient->set_server (m_config.udp_server_name ());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user