mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-24 21:28:41 -05:00
f2bbf98cbb
The sent/received 'mode' parameter posted to WSPRnet.org has been amended as follows: WSPR-2: "2" FST4W-120: "3" FST4W-300: "5" FST4W-900: "16" FST4W-1800: "30" this change is designed to maintain backwards compatibility with older versions of WSJT-X and other software like WSPR-X which already post these values: WSPR-2: "2" WSPR-15: "15" It is expected that the WSPRnet.org server side will be updated in sync with a WSJT-X v2.3.0 RC2 (or GA) release to account for this change.
343 lines
10 KiB
C++
343 lines
10 KiB
C++
// Interface to WSPRnet website
|
|
//
|
|
// by Edson Pereira - PY2SDR
|
|
|
|
#include "wsprnet.h"
|
|
|
|
#include <cmath>
|
|
|
|
#include <QTimer>
|
|
#include <QFile>
|
|
#include <QRegExp>
|
|
#include <QRegularExpression>
|
|
#include <QNetworkAccessManager>
|
|
#include <QNetworkRequest>
|
|
#include <QNetworkReply>
|
|
#include <QUrl>
|
|
#include <QDebug>
|
|
|
|
#include "moc_wsprnet.cpp"
|
|
|
|
namespace
|
|
{
|
|
char const * const wsprNetUrl = "http://wsprnet.org/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)
|
|
: QObject {parent}
|
|
, network_manager_ {manager}
|
|
, spots_to_send_ {0}
|
|
{
|
|
connect (network_manager_, &QNetworkAccessManager::finished, this, &WSPRNet::networkReply);
|
|
connect (&upload_timer_, &QTimer::timeout, this, &WSPRNet::work);
|
|
}
|
|
|
|
void WSPRNet::upload (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& fileName)
|
|
{
|
|
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;
|
|
m_file = fileName;
|
|
|
|
// Open the wsprd.out file
|
|
if (m_uploadType != 3)
|
|
{
|
|
QFile wsprdOutFile (fileName);
|
|
if (!wsprdOutFile.open (QIODevice::ReadOnly | QIODevice::Text) || !wsprdOutFile.size ())
|
|
{
|
|
spot_queue_.enqueue (urlEncodeNoSpot ());
|
|
m_uploadType = 1;
|
|
}
|
|
else
|
|
{
|
|
// Read the contents
|
|
while (!wsprdOutFile.atEnd())
|
|
{
|
|
SpotQueue::value_type query;
|
|
if (decodeLine (wsprdOutFile.readLine(), query))
|
|
{
|
|
// Prevent reporting data ouside of the current frequency band
|
|
float f = fabs (m_rfreq.toFloat() - query.queryItemValue ("tqrg", QUrl::FullyDecoded).toFloat());
|
|
if (f < 0.01) // MHz
|
|
{
|
|
spot_queue_.enqueue(urlEncodeSpot (query));
|
|
m_uploadType = 2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
spots_to_send_ = spot_queue_.size ();
|
|
upload_timer_.start (200);
|
|
}
|
|
|
|
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 = 3;
|
|
}
|
|
}
|
|
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 - removed by G4WJS to accommodate FST4W spots
|
|
// outside of WSPR segments
|
|
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
|
|
if (m_outstandingRequests.removeOne (reply))
|
|
{
|
|
if (QNetworkReply::NoError != reply->error ())
|
|
{
|
|
Q_EMIT uploadStatus (QString {"Error: %1"}.arg (reply->error ()));
|
|
// not clearing queue or halting queuing as it may be a
|
|
// transient one off request error
|
|
}
|
|
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 (!spot_queue_.size ())
|
|
{
|
|
Q_EMIT uploadStatus("done");
|
|
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) const
|
|
{
|
|
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);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Check for Message Type 3
|
|
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;
|
|
}
|
|
|
|
QString WSPRNet::encode_mode () const
|
|
{
|
|
if (m_mode == "WSPR") return "2";
|
|
if (m_mode == "WSPR-15") return "15";
|
|
if (m_mode == "FST4W")
|
|
{
|
|
auto tr = static_cast<int> ((TR_period_ / 60.)+.5);
|
|
if (2 == tr || 15 == tr)
|
|
{
|
|
tr += 1; // distinguish from WSPR-2 and WSPR-15
|
|
}
|
|
return QString::number (tr);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
auto WSPRNet::urlEncodeNoSpot () const -> SpotQueue::value_type
|
|
{
|
|
SpotQueue::value_type query;
|
|
query.addQueryItem ("function", "wsprstat");
|
|
query.addQueryItem ("rcall", m_call);
|
|
query.addQueryItem ("rgrid", m_grid);
|
|
query.addQueryItem ("rqrg", m_rfreq);
|
|
query.addQueryItem ("tpct", m_tpct);
|
|
query.addQueryItem ("tqrg", m_tfreq);
|
|
query.addQueryItem ("dbm", m_dbm);
|
|
query.addQueryItem ("version", m_vers);
|
|
query.addQueryItem ("mode", encode_mode ());
|
|
return query;;
|
|
}
|
|
|
|
auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) const -> SpotQueue::value_type
|
|
{
|
|
query.addQueryItem ("version", m_vers);
|
|
query.addQueryItem ("rcall", m_call);
|
|
query.addQueryItem ("rgrid", m_grid);
|
|
query.addQueryItem ("rqrg", m_rfreq);
|
|
query.addQueryItem ("mode", encode_mode ());
|
|
return query;
|
|
}
|
|
|
|
void WSPRNet::work()
|
|
{
|
|
if (spots_to_send_ && spot_queue_.size ())
|
|
{
|
|
#if QT_VERSION < QT_VERSION_CHECK (5, 15, 0)
|
|
if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) {
|
|
// try and recover network access for QNAM
|
|
network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible);
|
|
}
|
|
#endif
|
|
QNetworkRequest request (QUrl {wsprNetUrl});
|
|
request.setHeader (QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
|
auto const& spot = spot_queue_.dequeue ();
|
|
m_outstandingRequests << network_manager_->post (request, spot.query (QUrl::FullyEncoded).toUtf8 ());
|
|
Q_EMIT uploadStatus(QString {"Uploading Spot %1/%2"}.arg (spots_to_send_ - spot_queue_.size()).arg (spots_to_send_));
|
|
}
|
|
else
|
|
{
|
|
upload_timer_.stop ();
|
|
}
|
|
}
|
|
|
|
void WSPRNet::abortOutstandingRequests () {
|
|
spot_queue_.clear ();
|
|
for (auto& request : m_outstandingRequests) {
|
|
request->abort ();
|
|
}
|
|
}
|