Post FST4W spots to WSPRNet.org

Includes a re-factoring  of the WSPRNet class,  particularly to handle
direct spot posts as well as via  a file from wsprd. Switched from GET
http request method to POST method.

FST4W spots  post the same information  a WSPR spots except  the drift
field is  always zero (FST4W  has no  drift compensation, so  no drift
figure is calculated by the decoder),  and the mode field reflects the
T/R  period in  minutes.  This  means  FST4W-120A will  be similar  to
WSPR-2, an FST4W-900  will be similar to WSPR-15. I  don't see any way
to  view the  mode field  on  either the  new or  old database  format
queries on WSPRnet,  so it is hard  to tell if that  field is actually
stored.
This commit is contained in:
Bill Somerville 2020-07-26 02:58:04 +01:00
parent 63b1b0729a
commit 7566f3548d
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
4 changed files with 354 additions and 220 deletions

View File

@ -8,6 +8,8 @@
#include <QTimer> #include <QTimer>
#include <QFile> #include <QFile>
#include <QRegExp>
#include <QRegularExpression>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QNetworkReply> #include <QNetworkReply>
@ -18,215 +20,314 @@
namespace namespace
{ {
char const * const wsprNetUrl = "http://wsprnet.org/post?"; char const * const wsprNetUrl = "http://wsprnet.org/post/";
// char const * const wsprNetUrl = "http://127.0.0.1/post?"; //char const * const wsprNetUrl = "http://127.0.0.1:5000/post/";
//
// tested with this python REST mock of WSPRNet.org
//
/*
# Mock WSPRNet.org RESTful API
from flask import Flask, request, url_for
from flask_restful import Resource, Api
app = Flask(__name__)
@app.route ('/post/', methods=['GET', 'POST'])
def spot ():
if request.method == 'POST':
print (request.form)
return "1 spot(s) added"
with app.test_request_context ():
print (url_for ('spot'))
*/
// regexp to parse FST4W decodes
QRegularExpression fst4_re {R"(
(?<time>\d{4})
\s+(?<db>[-+]?\d+)
\s+(?<dt>[-+]?\d+\.\d+)
\s+(?<freq>\d+)
\s+`
\s+<?(?<call>[A-Z0-9/]+)>?(?:\s(?<grid>[A-R]{2}[0-9]{2}(?:[A-X]{2})?))?(?:\s+(?<dBm>\d+))?
)", QRegularExpression::ExtendedPatternSyntaxOption};
// regexp to parse wspr_spots.txt from wsprd
//
// 130223 2256 7 -21 -0.3 14.097090 DU1MGA PK04 37 0 40 0
// Date Time Sync dBm DT Freq Msg
// 1 2 3 4 5 6 -------7------ 8 9 10
QRegularExpression wspr_re(R"(^(\d+)\s+(\d+)\s+(\d+)\s+([+-]?\d+)\s+([+-]?\d+\.\d+)\s+(\d+\.\d+)\s+([^ ].*[^ ])\s+([+-]?\d+)\s+([+-]?\d+)\s+([+-]?\d+))");
}; };
WSPRNet::WSPRNet(QNetworkAccessManager * manager, QObject *parent) WSPRNet::WSPRNet (QNetworkAccessManager * manager, QObject *parent)
: QObject{parent} : QObject {parent}
, networkManager {manager} , network_manager_ {manager}
, uploadTimer {new QTimer {this}} , spots_to_send_ {0}
, m_urlQueueSize {0}
{ {
connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReply(QNetworkReply*))); connect (network_manager_, &QNetworkAccessManager::finished, this, &WSPRNet::networkReply);
connect( uploadTimer, SIGNAL(timeout()), this, SLOT(work())); connect (&upload_timer_, &QTimer::timeout, this, &WSPRNet::work);
} }
void WSPRNet::upload(QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq, void WSPRNet::upload (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, QString const& tpct, QString const& dbm, QString const& version, QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& fileName) QString const& version, QString const& fileName)
{ {
m_call = call; m_call = call;
m_grid = grid; m_grid = grid;
m_rfreq = rfreq; m_rfreq = rfreq;
m_tfreq = tfreq; m_tfreq = tfreq;
m_mode = mode; m_mode = mode;
m_tpct = tpct; TR_period_ = TR_period;
m_dbm = dbm; m_tpct = tpct;
m_vers = version; m_dbm = dbm;
m_file = fileName; m_vers = version;
m_file = fileName;
// Open the wsprd.out file // Open the wsprd.out file
QFile wsprdOutFile(fileName); QFile wsprdOutFile (fileName);
if (!wsprdOutFile.open(QIODevice::ReadOnly | QIODevice::Text) || if (!wsprdOutFile.open (QIODevice::ReadOnly | QIODevice::Text) || !wsprdOutFile.size ())
wsprdOutFile.size() == 0) { {
urlQueue.enqueue( wsprNetUrl + urlEncodeNoSpot()); spot_queue_.enqueue (urlEncodeNoSpot ());
m_uploadType = 1; m_uploadType = 1;
uploadTimer->start(200);
return;
} }
else
// Read the contents {
while (!wsprdOutFile.atEnd()) { // Read the contents
QHash<QString,QString> query; while (!wsprdOutFile.atEnd())
if ( decodeLine(wsprdOutFile.readLine(), query) ) { {
// Prevent reporting data ouside of the current frequency band SpotQueue::value_type query;
float f = fabs(m_rfreq.toFloat() - query["tqrg"].toFloat()); if (decodeLine (wsprdOutFile.readLine(), query))
if (f < 0.0002) { {
urlQueue.enqueue( wsprNetUrl + urlEncodeSpot(query)); // Prevent reporting data ouside of the current frequency band
m_uploadType = 2; float f = fabs (m_rfreq.toFloat() - query.queryItemValue ("tqrg", QUrl::FullyDecoded).toFloat());
if (f < 0.0002)
{
spot_queue_.enqueue(urlEncodeSpot (query));
m_uploadType = 2;
}
}
} }
}
} }
m_urlQueueSize = urlQueue.size(); spots_to_send_ = spot_queue_.size ();
uploadTimer->start(200); upload_timer_.start (200);
} }
void WSPRNet::networkReply(QNetworkReply *reply) void WSPRNet::post (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& version, QString const& decode_text)
{
m_call = call;
m_grid = grid;
m_rfreq = rfreq;
m_tfreq = tfreq;
m_mode = mode;
TR_period_ = TR_period;
m_tpct = tpct;
m_dbm = dbm;
m_vers = version;
if (!decode_text.size ())
{
if (!spot_queue_.size ())
{
spot_queue_.enqueue (urlEncodeNoSpot ());
m_uploadType = 1;
}
spots_to_send_ = spot_queue_.size ();
upload_timer_.start (200);
}
else
{
auto const& match = fst4_re.match (decode_text);
if (match.hasMatch ())
{
SpotQueue::value_type query;
// Prevent reporting data ouside of the current frequency band
auto tqrg = match.captured ("freq").toInt ();
if (tqrg >= 1400 && tqrg <= 1600)
{
query.addQueryItem ("function", "wspr");
// use time as at 3/4 of T/R period before current to
// ensure date is in Rx period
auto const& date = QDateTime::currentDateTimeUtc ().addSecs (-TR_period * 3. / 4.).date ();
query.addQueryItem ("date", date.toString ("yyMMdd"));
query.addQueryItem ("time", match.captured ("time"));
query.addQueryItem ("sig", match.captured ("db"));
query.addQueryItem ("dt", match.captured ("dt"));
query.addQueryItem ("tqrg", QString::number (rfreq.toDouble () + (tqrg - 1500) / 1e6, 'f', 6));
query.addQueryItem ("tcall", match.captured ("call"));
query.addQueryItem ("drift", "0");
query.addQueryItem ("tgrid", match.captured ("grid"));
query.addQueryItem ("dbm", match.captured ("dBm"));
spot_queue_.enqueue (urlEncodeSpot (query));
m_uploadType = 2;
}
}
}
}
void WSPRNet::networkReply (QNetworkReply * reply)
{ {
// check if request was ours // check if request was ours
if (m_outstandingRequests.removeOne (reply)) { if (m_outstandingRequests.removeOne (reply))
if (QNetworkReply::NoError != reply->error ()) { {
Q_EMIT uploadStatus (QString {"Error: %1"}.arg (reply->error ())); if (QNetworkReply::NoError != reply->error ())
// not clearing queue or halting queuing as it may be a transient {
// one off request error Q_EMIT uploadStatus (QString {"Error: %1"}.arg (reply->error ()));
} // not clearing queue or halting queuing as it may be a
else { // transient one off request error
QString serverResponse = reply->readAll();
if( m_uploadType == 2) {
if (!serverResponse.contains(QRegExp("spot\\(s\\) added"))) {
emit uploadStatus(QString {"Upload Failed: %1"}.arg (serverResponse));
urlQueue.clear();
uploadTimer->stop();
} }
} else
{
QString serverResponse = reply->readAll ();
if (m_uploadType == 2)
{
if (!serverResponse.contains(QRegExp("spot\\(s\\) added")))
{
Q_EMIT uploadStatus (QString {"Upload Failed: %1"}.arg (serverResponse));
spot_queue_.clear ();
upload_timer_.stop ();
}
}
if (urlQueue.isEmpty()) { if (!spot_queue_.size ())
emit uploadStatus("done"); {
QFile::remove(m_file); Q_EMIT uploadStatus("done");
uploadTimer->stop(); QFile f {m_file};
} if (f.exists ()) f.remove ();
upload_timer_.stop ();
}
}
qDebug () << QString {"WSPRnet.org %1 outstanding requests"}.arg (m_outstandingRequests.size ());
// delete request object instance on return to the event loop otherwise it is leaked
reply->deleteLater ();
}
}
bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
{
auto const& rx_match = wspr_re.match (line);
if (rx_match.hasMatch ()) {
int msgType = 0;
QString msg = rx_match.captured (7);
QString call, grid, dbm;
QRegularExpression msgRx;
// Check for Message Type 1
msgRx.setPattern(R"(^([A-Z0-9]{3,6})\s+([A-R]{2}\d{2})\s+(\d+))");
auto match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 1;
call = match.captured (1);
grid = match.captured (2);
dbm = match.captured (3);
} }
qDebug () << QString {"WSPRnet.org %1 outstanding requests"}.arg (m_outstandingRequests.size ()); // Check for Message Type 2
msgRx.setPattern(R"(^([A-Z0-9/]+)\s+(\d+))");
match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 2;
call = match.captured (1);
grid = "";
dbm = match.captured (2);
}
// delete request object instance on return to the event loop otherwise it is leaked // Check for Message Type 3
reply->deleteLater (); msgRx.setPattern(R"(^<([A-Z0-9/]+)>\s+([A-R]{2}\d{2}[A-X]{2})\s+(\d+))");
match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 3;
call = match.captured (1);
grid = match.captured (2);
dbm = match.captured (3);
}
// Unknown message format
if (!msgType) {
return false;
}
query.addQueryItem ("function", "wspr");
query.addQueryItem ("date", rx_match.captured (1));
query.addQueryItem ("time", rx_match.captured (2));
query.addQueryItem ("sig", rx_match.captured (4));
query.addQueryItem ("dt", rx_match.captured(5));
query.addQueryItem ("drift", rx_match.captured(8));
query.addQueryItem ("tqrg", rx_match.captured(6));
query.addQueryItem ("tcall", call);
query.addQueryItem ("tgrid", grid);
query.addQueryItem ("dbm", dbm);
} else {
return false;
} }
return true;
} }
bool WSPRNet::decodeLine(QString const& line, QHash<QString,QString> &query) auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
{ {
// 130223 2256 7 -21 -0.3 14.097090 DU1MGA PK04 37 0 40 0 SpotQueue::value_type query;
// Date Time Sync dBm DT Freq Msg query.addQueryItem ("function", "wsprstat");
// 1 2 3 4 5 6 -------7------ 8 9 10 query.addQueryItem ("rcall", m_call);
QRegExp rx("^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+\\.\\d+)\\s+(\\d+\\.\\d+)\\s+(.*)\\s+([+-]?\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+)"); query.addQueryItem ("rgrid", m_grid);
if (rx.indexIn(line) != -1) { query.addQueryItem ("rqrg", m_rfreq);
int msgType = 0; query.addQueryItem ("tpct", m_tpct);
QString msg = rx.cap(7); query.addQueryItem ("tqrg", m_tfreq);
msg.remove(QRegExp("\\s+$")); query.addQueryItem ("dbm", m_dbm);
msg.remove(QRegExp("^\\s+")); query.addQueryItem ("version", m_vers);
QString call, grid, dbm; if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
QRegExp msgRx; if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
// Check for Message Type 1 {
msgRx.setPattern("^([A-Z0-9]{3,6})\\s+([A-Z]{2}\\d{2})\\s+(\\d+)"); query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
if (msgRx.indexIn(msg) != -1) {
msgType = 1;
call = msgRx.cap(1);
grid = msgRx.cap(2);
dbm = msgRx.cap(3);
}
// Check for Message Type 2
msgRx.setPattern("^([A-Z0-9/]+)\\s+(\\d+)");
if (msgRx.indexIn(msg) != -1) {
msgType = 2;
call = msgRx.cap(1);
grid = "";
dbm = msgRx.cap(2);
}
// Check for Message Type 3
msgRx.setPattern("^<([A-Z0-9/]+)>\\s+([A-Z]{2}\\d{2}[A-Z]{2})\\s+(\\d+)");
if (msgRx.indexIn(msg) != -1) {
msgType = 3;
call = msgRx.cap(1);
grid = msgRx.cap(2);
dbm = msgRx.cap(3);
}
// Unknown message format
if (!msgType) {
return false;
}
query["function"] = "wspr";
query["date"] = rx.cap(1);
query["time"] = rx.cap(2);
query["sig"] = rx.cap(4);
query["dt"] = rx.cap(5);
query["drift"] = rx.cap(8);
query["tqrg"] = rx.cap(6);
query["tcall"] = call;
query["tgrid"] = grid;
query["dbm"] = dbm;
} else {
return false;
} }
return true; return query;;
} }
QString WSPRNet::urlEncodeNoSpot() auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) -> SpotQueue::value_type
{ {
QString queryString; query.addQueryItem ("version", m_vers);
queryString += "function=wsprstat&"; query.addQueryItem ("rcall", m_call);
queryString += "rcall=" + m_call + "&"; query.addQueryItem ("rgrid", m_grid);
queryString += "rgrid=" + m_grid + "&"; query.addQueryItem ("rqrg", m_rfreq);
queryString += "rqrg=" + m_rfreq + "&"; if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
queryString += "tpct=" + m_tpct + "&"; if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
queryString += "tqrg=" + m_tfreq + "&"; if (m_mode == "FST4W")
queryString += "dbm=" + m_dbm + "&"; {
queryString += "version=" + m_vers; query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
if(m_mode=="WSPR") queryString += "&mode=2"; }
if(m_mode=="WSPR-15") queryString += "&mode=15"; return query;
return queryString;;
}
QString WSPRNet::urlEncodeSpot(QHash<QString,QString> const& query)
{
QString queryString;
queryString += "function=" + query["function"] + "&";
queryString += "rcall=" + m_call + "&";
queryString += "rgrid=" + m_grid + "&";
queryString += "rqrg=" + m_rfreq + "&";
queryString += "date=" + query["date"] + "&";
queryString += "time=" + query["time"] + "&";
queryString += "sig=" + query["sig"] + "&";
queryString += "dt=" + query["dt"] + "&";
queryString += "drift=" + query["drift"] + "&";
queryString += "tqrg=" + query["tqrg"] + "&";
queryString += "tcall=" + query["tcall"] + "&";
queryString += "tgrid=" + query["tgrid"] + "&";
queryString += "dbm=" + query["dbm"] + "&";
queryString += "version=" + m_vers;
if(m_mode=="WSPR") queryString += "&mode=2";
if(m_mode=="WSPR-15") queryString += "&mode=15";
return queryString;
} }
void WSPRNet::work() void WSPRNet::work()
{ {
if (!urlQueue.isEmpty()) { if (spots_to_send_ && spot_queue_.size ())
{
#if QT_VERSION < QT_VERSION_CHECK (5, 15, 0) #if QT_VERSION < QT_VERSION_CHECK (5, 15, 0)
if (QNetworkAccessManager::Accessible != networkManager->networkAccessible ()) { if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) {
// try and recover network access for QNAM // try and recover network access for QNAM
networkManager->setNetworkAccessible (QNetworkAccessManager::Accessible); network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible);
} }
#endif #endif
QUrl url(urlQueue.dequeue()); QNetworkRequest request (QUrl {wsprNetUrl});
QNetworkRequest request(url); request.setHeader (QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
m_outstandingRequests << networkManager->get(request); auto const& spot = spot_queue_.dequeue ();
emit uploadStatus(QString {"Uploading Spot %1/%2"}.arg (m_urlQueueSize - urlQueue.size()).arg (m_urlQueueSize)); m_outstandingRequests << network_manager_->post (request, spot.query (QUrl::FullyEncoded).toUtf8 ());
} else { Q_EMIT uploadStatus(QString {"Uploading Spot %1/%2"}.arg (spots_to_send_ - spot_queue_.size()).arg (spots_to_send_));
uploadTimer->stop(); }
} else
{
upload_timer_.stop ();
}
} }
void WSPRNet::abortOutstandingRequests () { void WSPRNet::abortOutstandingRequests () {
urlQueue.clear (); spot_queue_.clear ();
for (auto& request : m_outstandingRequests) { for (auto& request : m_outstandingRequests) {
request->abort (); request->abort ();
} }
m_urlQueueSize = 0;
} }

View File

@ -2,45 +2,58 @@
#define WSPRNET_H #define WSPRNET_H
#include <QObject> #include <QObject>
#include <QTimer>
#include <QString> #include <QString>
#include <QList> #include <QList>
#include <QHash> #include <QUrlQuery>
#include <QQueue> #include <QQueue>
class QNetworkAccessManager; class QNetworkAccessManager;
class QTimer;
class QNetworkReply; class QNetworkReply;
class WSPRNet : public QObject class WSPRNet : public QObject
{ {
Q_OBJECT; Q_OBJECT
using SpotQueue = QQueue<QUrlQuery>;
public: public:
explicit WSPRNet(QNetworkAccessManager *, QObject *parent = nullptr); explicit WSPRNet (QNetworkAccessManager *, QObject *parent = nullptr);
void upload(QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq, void upload (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, QString const& tpct, QString const& dbm, QString const& version, QString const& mode, float TR_peirod, QString const& tpct, QString const& dbm,
QString const& fileName); QString const& version, QString const& fileName);
static bool decodeLine(QString const& line, QHash<QString,QString> &query); void post (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& version, QString const& decode_text = QString {});
signals: signals:
void uploadStatus(QString); void uploadStatus (QString);
public slots: public slots:
void networkReply(QNetworkReply *); void networkReply (QNetworkReply *);
void work(); void work ();
void abortOutstandingRequests (); void abortOutstandingRequests ();
private: private:
QNetworkAccessManager *networkManager; bool decodeLine (QString const& line, SpotQueue::value_type& query);
QList<QNetworkReply *> m_outstandingRequests; SpotQueue::value_type urlEncodeNoSpot ();
QString m_call, m_grid, m_rfreq, m_tfreq, m_mode, m_tpct, m_dbm, m_vers, m_file; SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot);
QQueue<QString> urlQueue;
QTimer *uploadTimer;
int m_urlQueueSize;
int m_uploadType;
QString urlEncodeNoSpot(); QNetworkAccessManager * network_manager_;
QString urlEncodeSpot(QHash<QString,QString> const& spot); QList<QNetworkReply *> m_outstandingRequests;
QString m_call;
QString m_grid;;
QString m_rfreq;
QString m_tfreq;
QString m_mode;
QString m_tpct;
QString m_dbm;
QString m_vers;
QString m_file;
float TR_period_;
int spots_to_send_;
SpotQueue spot_queue_;
QTimer upload_timer_;
int m_uploadType;
}; };
#endif // WSPRNET_H #endif // WSPRNET_H

View File

@ -803,7 +803,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(&killFileTimer, &QTimer::timeout, this, &MainWindow::killFile); connect(&killFileTimer, &QTimer::timeout, this, &MainWindow::killFile);
uploadTimer.setSingleShot(true); uploadTimer.setSingleShot(true);
connect(&uploadTimer, SIGNAL(timeout()), this, SLOT(uploadSpots())); connect(&uploadTimer, &QTimer::timeout, [this] () {uploadWSPRSpots ();});
TxAgainTimer.setSingleShot(true); TxAgainTimer.setSingleShot(true);
connect(&TxAgainTimer, SIGNAL(timeout()), this, SLOT(TxAgain())); connect(&TxAgainTimer, SIGNAL(timeout()), this, SLOT(TxAgain()));
@ -944,7 +944,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_saveAll=ui->actionSave_all->isChecked(); m_saveAll=ui->actionSave_all->isChecked();
ui->sbTxPercent->setValue(m_pctx); ui->sbTxPercent->setValue(m_pctx);
ui->TxPowerComboBox->setCurrentIndex(int(.3 * m_dBm + .2)); ui->TxPowerComboBox->setCurrentIndex(int(.3 * m_dBm + .2));
ui->cbUploadWSPR_Spots->setChecked(m_uploadSpots); ui->cbUploadWSPR_Spots->setChecked(m_uploadWSPRSpots);
if((m_ndepth&7)==1) ui->actionQuickDecode->setChecked(true); if((m_ndepth&7)==1) ui->actionQuickDecode->setChecked(true);
if((m_ndepth&7)==2) ui->actionMediumDecode->setChecked(true); if((m_ndepth&7)==2) ui->actionMediumDecode->setChecked(true);
if((m_ndepth&7)==3) ui->actionDeepestDecode->setChecked(true); if((m_ndepth&7)==3) ui->actionDeepestDecode->setChecked(true);
@ -1118,7 +1118,7 @@ void MainWindow::writeSettings()
m_settings->setValue("dBm",m_dBm); m_settings->setValue("dBm",m_dBm);
m_settings->setValue("RR73",m_send_RR73); m_settings->setValue("RR73",m_send_RR73);
m_settings->setValue ("WSPRPreferType1", ui->WSPR_prefer_type_1_check_box->isChecked ()); m_settings->setValue ("WSPRPreferType1", ui->WSPR_prefer_type_1_check_box->isChecked ());
m_settings->setValue("UploadSpots",m_uploadSpots); m_settings->setValue("UploadSpots",m_uploadWSPRSpots);
m_settings->setValue("NoOwnCall",ui->cbNoOwnCall->isChecked()); m_settings->setValue("NoOwnCall",ui->cbNoOwnCall->isChecked());
m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ()); m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ());
m_settings->setValue ("TRPeriod", ui->sbTR->value ()); m_settings->setValue ("TRPeriod", ui->sbTR->value ());
@ -1213,8 +1213,8 @@ void MainWindow::readSettings()
on_txrb4_doubleClicked(); on_txrb4_doubleClicked();
} }
ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ()); ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ());
m_uploadSpots=m_settings->value("UploadSpots",false).toBool(); m_uploadWSPRSpots=m_settings->value("UploadSpots",false).toBool();
if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet("QCheckBox{background-color: yellow}"); if(!m_uploadWSPRSpots) ui->cbUploadWSPR_Spots->setStyleSheet("QCheckBox{background-color: yellow}");
ui->cbNoOwnCall->setChecked(m_settings->value("NoOwnCall",false).toBool()); ui->cbNoOwnCall->setChecked(m_settings->value("NoOwnCall",false).toBool());
ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool()); ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool());
// setup initial value of tx attenuator // setup initial value of tx attenuator
@ -1540,7 +1540,7 @@ void MainWindow::dataSink(qint64 frames)
if(m_mode=="WSPR") { if(m_mode=="WSPR") {
QStringList t2; QStringList t2;
QStringList depth_args; QStringList depth_args;
t2 << "-f" << QString {"%1"}.arg (m_dialFreqRxWSPR / 1000000.0, 0, 'f', 6); t2 << "-f" << QString {"%1"}.arg (m_dialFreqRxWSPR / 1e6, 0, 'f', 6);
if((m_ndepth&7)==1) depth_args << "-qB"; //2 pass w subtract, no Block detection, no shift jittering if((m_ndepth&7)==1) depth_args << "-qB"; //2 pass w subtract, no Block detection, no shift jittering
if((m_ndepth&7)==2) depth_args << "-C" << "500" << "-o" << "4"; //3 pass, subtract, Block detection, OSD if((m_ndepth&7)==2) depth_args << "-C" << "500" << "-o" << "4"; //3 pass, subtract, Block detection, OSD
if((m_ndepth&7)==3) depth_args << "-C" << "500" << "-o" << "4" << "-d"; //3 pass, subtract, Block detect, OSD, more candidates if((m_ndepth&7)==3) depth_args << "-C" << "500" << "-o" << "4" << "-d"; //3 pass, subtract, Block detect, OSD, more candidates
@ -3112,6 +3112,10 @@ void MainWindow::decodeDone ()
{ {
if(m_mode!="FT8" or dec_data.params.nzhsym==50) m_nDecodes=0; if(m_mode!="FT8" or dec_data.params.nzhsym==50) m_nDecodes=0;
if(m_mode=="QRA64") m_wideGraph->drawRed(0,0); if(m_mode=="QRA64") m_wideGraph->drawRed(0,0);
if ("FST4W" == m_mode)
{
uploadWSPRSpots (true); // DE station info and trigger posts
}
auto tnow = QDateTime::currentDateTimeUtc (); auto tnow = QDateTime::currentDateTimeUtc ();
double tdone = fmod(double(tnow.time().second()),m_TRperiod); double tdone = fmod(double(tnow.time().second()),m_TRperiod);
int mswait; int mswait;
@ -3210,6 +3214,10 @@ void MainWindow::readFromStdout() //readFromStdout
} }
m_tBlankLine = line_read.left(ntime); m_tBlankLine = line_read.left(ntime);
} }
if ("FST4W" == m_mode)
{
uploadWSPRSpots (true, line_read);
}
DecodedText decodedtext0 {QString::fromUtf8(line_read.constData())}; DecodedText decodedtext0 {QString::fromUtf8(line_read.constData())};
DecodedText decodedtext {QString::fromUtf8(line_read.constData()).remove("TU; ")}; DecodedText decodedtext {QString::fromUtf8(line_read.constData()).remove("TU; ")};
@ -7786,16 +7794,16 @@ void MainWindow::p1ReadFromStdout() //p1readFromStdout
} }
m_nWSPRdecodes=0; m_nWSPRdecodes=0;
ui->DecodeButton->setChecked (false); ui->DecodeButton->setChecked (false);
if(m_uploadSpots if (m_uploadWSPRSpots
&& m_config.is_transceiver_online ()) { // need working rig control && m_config.is_transceiver_online ()) { // need working rig control
#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0) #if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay uploadTimer.start(QRandomGenerator::global ()->bounded (0, 20000)); // Upload delay
#else #else
uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay uploadTimer.start(20000 * qrand()/((double)RAND_MAX + 1.0)); // Upload delay
#endif #endif
} else { } else {
QFile f(QDir::toNativeSeparators(m_config.writeable_data_dir ().absolutePath()) + "/wspr_spots.txt"); QFile f {QDir::toNativeSeparators (m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt"))};
if(f.exists()) f.remove(); if (f.exists ()) f.remove ();
} }
m_RxLog=0; m_RxLog=0;
m_startAnother=m_loopall; m_startAnother=m_loopall;
@ -7910,23 +7918,35 @@ void MainWindow::WSPR_history(Frequency dialFreq, int ndecodes)
} }
} }
void MainWindow::uploadSpots() void MainWindow::uploadWSPRSpots (bool direct_post, QString const& decode_text)
{ {
// do not spot replays or if rig control not working // do not spot if disabled, replays, or if rig control not working
if(m_diskData || !m_config.is_transceiver_online ()) return; if(!m_uploadWSPRSpots || m_diskData || !m_config.is_transceiver_online ()) return;
if(m_uploading) { if(m_uploading) {
qDebug() << "Previous upload has not completed, spots were lost"; qDebug() << "Previous upload has not completed, spots were lost";
wsprNet->abortOutstandingRequests (); wsprNet->abortOutstandingRequests ();
m_uploading = false; m_uploading = false;
} }
QString rfreq = QString("%1").arg(0.000001*(m_dialFreqRxWSPR + 1500), 0, 'f', 6); QString rfreq = QString("%1").arg((m_dialFreqRxWSPR + 1500) / 1e6, 0, 'f', 6);
QString tfreq = QString("%1").arg(0.000001*(m_dialFreqRxWSPR + QString tfreq = QString("%1").arg((m_dialFreqRxWSPR +
ui->TxFreqSpinBox->value()), 0, 'f', 6); ui->TxFreqSpinBox->value()) / 1e6, 0, 'f', 6);
wsprNet->upload(m_config.my_callsign(), m_config.my_grid(), rfreq, tfreq, if (!direct_post)
m_mode, QString::number(ui->autoButton->isChecked() ? m_pctx : 0), {
QString::number(m_dBm), version(), wsprNet->upload (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq,
QDir::toNativeSeparators(m_config.writeable_data_dir ().absolutePath()) + "/wspr_spots.txt"); m_mode, m_TRperiod, QString::number (ui->autoButton->isChecked () ? m_pctx : 0),
m_uploading = true; QString::number (m_dBm), version (),
m_config.writeable_data_dir ().absoluteFilePath ("wspr_spots.txt"));
}
else
{
wsprNet->post (m_config.my_callsign (), m_config.my_grid (), rfreq, tfreq,
m_mode, m_TRperiod, QString::number (ui->autoButton->isChecked () ? m_pctx : 0),
QString::number (m_dBm), version (), decode_text);
}
if (!decode_text.size ())
{
m_uploading = true;
}
} }
void MainWindow::uploadResponse(QString response) void MainWindow::uploadResponse(QString response)
@ -7960,9 +7980,9 @@ void MainWindow::on_sbTxPercent_valueChanged(int n)
void MainWindow::on_cbUploadWSPR_Spots_toggled(bool b) void MainWindow::on_cbUploadWSPR_Spots_toggled(bool b)
{ {
m_uploadSpots=b; m_uploadWSPRSpots=b;
if(m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet(""); if(m_uploadWSPRSpots) ui->cbUploadWSPR_Spots->setStyleSheet("");
if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet( if(!m_uploadWSPRSpots) ui->cbUploadWSPR_Spots->setStyleSheet(
"QCheckBox{background-color: yellow}"); "QCheckBox{background-color: yellow}");
} }

View File

@ -282,7 +282,7 @@ private slots:
void on_sbTxPercent_valueChanged(int n); void on_sbTxPercent_valueChanged(int n);
void on_cbUploadWSPR_Spots_toggled(bool b); void on_cbUploadWSPR_Spots_toggled(bool b);
void WSPR_config(bool b); void WSPR_config(bool b);
void uploadSpots(); void uploadWSPRSpots (bool direct_post = false, QString const& decode_text = QString {});
void TxAgain(); void TxAgain();
void uploadResponse(QString response); void uploadResponse(QString response);
void on_WSPRfreqSpinBox_valueChanged(int n); void on_WSPRfreqSpinBox_valueChanged(int n);
@ -500,7 +500,7 @@ private:
QString m_tBlankLine; QString m_tBlankLine;
bool m_bShMsgs; bool m_bShMsgs;
bool m_bSWL; bool m_bSWL;
bool m_uploadSpots; bool m_uploadWSPRSpots;
bool m_uploading; bool m_uploading;
bool m_txNext; bool m_txNext;
bool m_grid6; bool m_grid6;