mirror of
				https://github.com/saitohirga/WSJT-X.git
				synced 2025-10-30 20:40:28 -04:00 
			
		
		
		
	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 ();
 | |
|   }
 | |
| }
 |