| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | #include "PSKReporter.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | // Interface for posting spots to PSK Reporter web site
 | 
					
						
							|  |  |  | // Implemented by Edson Pereira PY2SDR
 | 
					
						
							|  |  |  | // Updated by Bill Somerville, G4WJS
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Reports will be sent in batch mode every 5 minutes.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <cmath>
 | 
					
						
							|  |  |  | #include <QObject>
 | 
					
						
							|  |  |  | #include <QString>
 | 
					
						
							|  |  |  | #include <QDateTime>
 | 
					
						
							|  |  |  | #include <QSharedPointer>
 | 
					
						
							|  |  |  | #include <QUdpSocket>
 | 
					
						
							|  |  |  | #include <QTcpSocket>
 | 
					
						
							|  |  |  | #include <QHostInfo>
 | 
					
						
							|  |  |  | #include <QQueue>
 | 
					
						
							|  |  |  | #include <QByteArray>
 | 
					
						
							|  |  |  | #include <QDataStream>
 | 
					
						
							|  |  |  | #include <QTimer>
 | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  | #include <QRandomGenerator>
 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  | #include "Logger.hpp"
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | #include "Configuration.hpp"
 | 
					
						
							|  |  |  | #include "pimpl_impl.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | #include "moc_PSKReporter.cpp"
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-24 12:51:20 +01:00
										 |  |  |   QLatin1String HOST {"report.pskreporter.info"}; | 
					
						
							|  |  |  |   // QLatin1String HOST {"127.0.0.1"};
 | 
					
						
							|  |  |  |   quint16 SERVICE_PORT {4739}; | 
					
						
							|  |  |  |   // quint16 SERVICE_PORT {14739};
 | 
					
						
							|  |  |  |   int MIN_SEND_INTERVAL {15}; // in seconds
 | 
					
						
							|  |  |  |   int FLUSH_INTERVAL {4 * 5}; // in send intervals
 | 
					
						
							|  |  |  |   bool ALIGNMENT_PADDING {true}; | 
					
						
							|  |  |  |   int MIN_PAYLOAD_LENGTH {508}; | 
					
						
							|  |  |  |   int MAX_PAYLOAD_LENGTH {1400}; | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | class PSKReporter::impl final | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   : public QObject | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   Q_OBJECT | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   using logger_type = boost::log::sources::severity_channel_logger_mt<boost::log::trivial::severity_level>; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | public: | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  |   impl (PSKReporter * self, Configuration const * config, QString const& program_info) | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |     : logger_ {boost::log::keywords::channel = "PSKRPRT"} | 
					
						
							|  |  |  |     , self_ {self} | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |     , config_ {config} | 
					
						
							|  |  |  |     , sequence_number_ {0u} | 
					
						
							|  |  |  |     , send_descriptors_ {0} | 
					
						
							|  |  |  |     , send_receiver_data_ {0} | 
					
						
							|  |  |  |     , flush_counter_ {0u} | 
					
						
							|  |  |  |     , prog_id_ {program_info} | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							|  |  |  |     observation_id_ = qrand(); | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |     observation_id_ = QRandomGenerator::global ()->generate (); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // This timer sets the interval to check for spots to send.
 | 
					
						
							|  |  |  |     connect (&report_timer_, &QTimer::timeout, [this] () {send_report ();}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // This timer repeats the sending of IPFIX templates and receiver
 | 
					
						
							|  |  |  |     // information if we are using UDP, in case server has been
 | 
					
						
							|  |  |  |     // restarted ans lost cached information.
 | 
					
						
							|  |  |  |     connect (&descriptor_timer_, &QTimer::timeout, [this] () { | 
					
						
							|  |  |  |                                                      if (socket_ | 
					
						
							|  |  |  |                                                          && QAbstractSocket::UdpSocket == socket_->socketType ()) | 
					
						
							|  |  |  |                                                        { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |                                                          LOG_LOG_LOCATION (logger_, trace, "enable descriptor resend"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |                                                          // send templates again
 | 
					
						
							|  |  |  |                                                          send_descriptors_ = 3; // three times
 | 
					
						
							|  |  |  |                                                          // send receiver data set again
 | 
					
						
							|  |  |  |                                                          send_receiver_data_ = 3; // three times
 | 
					
						
							|  |  |  |                                                        } | 
					
						
							|  |  |  |                                                    }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void check_connection () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (!socket_ | 
					
						
							|  |  |  |         || QAbstractSocket::UnconnectedState == socket_->state () | 
					
						
							| 
									
										
										
										
											2020-10-23 04:36:28 +01:00
										 |  |  |         || (socket_->socketType () != (config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket))) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |       { | 
					
						
							|  |  |  |         // we need to create the appropriate socket
 | 
					
						
							|  |  |  |         if (socket_ | 
					
						
							|  |  |  |             && QAbstractSocket::UnconnectedState != socket_->state () | 
					
						
							|  |  |  |             && QAbstractSocket::ClosingState != socket_->state ()) | 
					
						
							|  |  |  |           { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |             LOG_LOG_LOCATION (logger_, trace, "create/recreate socket"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |             // handle re-opening asynchronously
 | 
					
						
							|  |  |  |             auto connection = QSharedPointer<QMetaObject::Connection>::create (); | 
					
						
							| 
									
										
										
										
											2020-07-26 22:58:17 +01:00
										 |  |  |             *connection = connect (socket_.data (), &QAbstractSocket::disconnected, [this, connection] () { | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |                                                                                      disconnect (*connection); | 
					
						
							|  |  |  |                                                                                      check_connection (); | 
					
						
							|  |  |  |                                                                                    }); | 
					
						
							|  |  |  |             // close gracefully
 | 
					
						
							| 
									
										
										
										
											2020-08-18 12:47:17 +01:00
										 |  |  |             send_report (true); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |             socket_->close (); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         else | 
					
						
							|  |  |  |           { | 
					
						
							|  |  |  |             reconnect (); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void handle_socket_error (QAbstractSocket::SocketError e) | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2021-01-27 18:11:50 +00:00
										 |  |  |     LOG_LOG_LOCATION (logger_, warning, "socket error: " << socket_->errorString ()); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |     switch (e) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |       case QAbstractSocket::RemoteHostClosedError: | 
					
						
							|  |  |  |         socket_->disconnectFromHost (); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       case QAbstractSocket::TemporaryError: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         spots_.clear (); | 
					
						
							|  |  |  |         Q_EMIT self_->errorOccurred (socket_->errorString ()); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void reconnect () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     // Using deleteLater for the deleter as we may eventually
 | 
					
						
							|  |  |  |     // be called from the disconnected handler above.
 | 
					
						
							|  |  |  |     if (config_->psk_reporter_tcpip ()) | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |         LOG_LOG_LOCATION (logger_, trace, "create TCP/IP socket"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |         socket_.reset (new QTcpSocket, &QObject::deleteLater); | 
					
						
							|  |  |  |         send_descriptors_ = 1; | 
					
						
							|  |  |  |         send_receiver_data_ = 1; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |         LOG_LOG_LOCATION (logger_, trace, "create UDP/IP socket"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |         socket_.reset (new QUdpSocket, &QObject::deleteLater); | 
					
						
							|  |  |  |         send_descriptors_ = 3; | 
					
						
							|  |  |  |         send_receiver_data_ = 3; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  |     connect (socket_.get (), &QAbstractSocket::errorOccurred, this, &PSKReporter::impl::handle_socket_error); | 
					
						
							| 
									
										
										
										
											2020-09-28 12:18:44 +01:00
										 |  |  | #elif QT_VERSION >= QT_VERSION_CHECK(5, 7, 0)
 | 
					
						
							| 
									
										
										
										
											2020-07-26 22:58:17 +01:00
										 |  |  |     connect (socket_.data (), QOverload<QAbstractSocket::SocketError>::of (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error); | 
					
						
							| 
									
										
										
										
											2020-09-28 12:18:44 +01:00
										 |  |  | #else
 | 
					
						
							|  |  |  |     connect (socket_.data (), static_cast<void (QAbstractSocket::*) (QAbstractSocket::SocketError)> (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // use this for pseudo connection with UDP, allows us to use
 | 
					
						
							|  |  |  |     // QIODevice::write() instead of QUDPSocket::writeDatagram()
 | 
					
						
							|  |  |  |     socket_->connectToHost (HOST, SERVICE_PORT, QAbstractSocket::WriteOnly); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |     LOG_LOG_LOCATION (logger_, debug, "remote host: " << HOST << " port: " << SERVICE_PORT); | 
					
						
							| 
									
										
										
										
											2020-08-15 23:32:10 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!report_timer_.isActive ()) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         report_timer_.start (MIN_SEND_INTERVAL * 1000); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     if (!descriptor_timer_.isActive ()) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         descriptor_timer_.start (1 * 60 * 60 * 1000); // hourly
 | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-16 00:38:19 +01:00
										 |  |  |   void stop () | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     if (socket_) | 
					
						
							|  |  |  |       { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |         LOG_LOG_LOCATION (logger_, trace, "disconnecting"); | 
					
						
							| 
									
										
										
										
											2020-08-16 00:38:19 +01:00
										 |  |  |         socket_->disconnectFromHost (); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     descriptor_timer_.stop (); | 
					
						
							|  |  |  |     report_timer_.stop (); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   void send_report (bool send_residue = false); | 
					
						
							|  |  |  |   void build_preamble (QDataStream&); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   bool flushing () | 
					
						
							|  |  |  |   { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |     bool flush =  FLUSH_INTERVAL && !(++flush_counter_ % FLUSH_INTERVAL); | 
					
						
							|  |  |  |     LOG_LOG_LOCATION (logger_, trace, "flush: " << flush); | 
					
						
							|  |  |  |     return flush; | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   logger_type mutable logger_; | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  |   PSKReporter * self_; | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   Configuration const * config_; | 
					
						
							|  |  |  |   QSharedPointer<QAbstractSocket> socket_; | 
					
						
							|  |  |  |   int dns_lookup_id_; | 
					
						
							|  |  |  |   QByteArray payload_; | 
					
						
							|  |  |  |   quint32 sequence_number_; | 
					
						
							|  |  |  |   int send_descriptors_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // Currently PSK Reporter requires that  a receiver data set is sent
 | 
					
						
							|  |  |  |   // in every  data flow. This  memeber variable  can be used  to only
 | 
					
						
							|  |  |  |   // send that information at session start (3 times for UDP), when it
 | 
					
						
							|  |  |  |   // changes (3  times for UDP), or  once per hour (3  times) if using
 | 
					
						
							|  |  |  |   // UDP. Uncomment the relevant code to enable that fuctionality.
 | 
					
						
							|  |  |  |   int send_receiver_data_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   unsigned flush_counter_; | 
					
						
							|  |  |  |   quint32 observation_id_; | 
					
						
							|  |  |  |   QString rx_call_; | 
					
						
							|  |  |  |   QString rx_grid_; | 
					
						
							|  |  |  |   QString rx_ant_; | 
					
						
							|  |  |  |   QString prog_id_; | 
					
						
							|  |  |  |   QByteArray tx_data_; | 
					
						
							|  |  |  |   QByteArray tx_residue_; | 
					
						
							|  |  |  |   struct Spot | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     bool operator == (Spot const& rhs) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |         call_ == rhs.call_ | 
					
						
							|  |  |  |         && grid_ == rhs.grid_ | 
					
						
							|  |  |  |         && mode_ == rhs.mode_ | 
					
						
							|  |  |  |         && std::abs (Radio::FrequencyDelta (freq_ - rhs.freq_)) < 50; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     QString call_; | 
					
						
							|  |  |  |     QString grid_; | 
					
						
							|  |  |  |     int snr_; | 
					
						
							|  |  |  |     Radio::Frequency freq_; | 
					
						
							|  |  |  |     QString mode_; | 
					
						
							|  |  |  |     QDateTime time_; | 
					
						
							|  |  |  |   }; | 
					
						
							|  |  |  |   QQueue<Spot> spots_; | 
					
						
							|  |  |  |   QTimer report_timer_; | 
					
						
							|  |  |  |   QTimer descriptor_timer_; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  |    | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | #include "PSKReporter.moc"
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   void writeUtfString (QDataStream& out, QString const& s) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     auto const& utf = s.toUtf8 ().left (254); | 
					
						
							|  |  |  |     out << quint8 (utf.size ()); | 
					
						
							|  |  |  |     out.writeRawData (utf, utf.size ()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   int num_pad_bytes (int len) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     return ALIGNMENT_PADDING ? (4 - len % 4) % 4 : 0; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void set_length (QDataStream& out, QByteArray& b) | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     // pad with nulls modulo 4
 | 
					
						
							|  |  |  |     auto pad_len = num_pad_bytes (b.size ()); | 
					
						
							|  |  |  |     out.writeRawData (QByteArray {pad_len, '\0'}.constData (), pad_len); | 
					
						
							|  |  |  |     auto pos = out.device ()->pos (); | 
					
						
							|  |  |  |     out.device ()->seek (sizeof (quint16)); | 
					
						
							|  |  |  |     // insert length
 | 
					
						
							|  |  |  |     out << static_cast<quint16> (b.size ()); | 
					
						
							|  |  |  |     out.device ()->seek (pos); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | void PSKReporter::impl::build_preamble (QDataStream& message) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							|  |  |  |   // Message Header
 | 
					
						
							|  |  |  |   message | 
					
						
							|  |  |  |     << quint16 (10u)          // Version Number
 | 
					
						
							|  |  |  |     << quint16 (0u)           // Length (place-holder filled in later)
 | 
					
						
							|  |  |  |     << quint32 (0u)           // Export Time (place-holder filled in later)
 | 
					
						
							|  |  |  |     << ++sequence_number_     // Sequence Number
 | 
					
						
							|  |  |  |     << observation_id_;       // Observation Domain ID
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (logger_, trace, "#: " << sequence_number_); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |   if (send_descriptors_) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       --send_descriptors_; | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         // Sender Information descriptor
 | 
					
						
							|  |  |  |         QByteArray descriptor; | 
					
						
							|  |  |  |         QDataStream out {&descriptor, QIODevice::WriteOnly}; | 
					
						
							|  |  |  |         out | 
					
						
							|  |  |  |           << quint16 (2u)           // Template Set ID
 | 
					
						
							|  |  |  |           << quint16 (0u)           // Length (place-holder)
 | 
					
						
							|  |  |  |           << quint16 (0x50e3)       // Link ID
 | 
					
						
							|  |  |  |           << quint16 (7u)           // Field Count
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 1u)  // Option 1 Information Element ID (senderCallsign)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)       // Option 1 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 1 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 5u)  // Option 2 Information Element ID (frequency)
 | 
					
						
							|  |  |  |           << quint16 (4u)           // Option 2 Field Length
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 2 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 6u)  // Option 3 Information Element ID (sNR)
 | 
					
						
							|  |  |  |           << quint16 (1u)           // Option 3 Field Length
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 3 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 10u) // Option 4 Information Element ID (mode)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)       // Option 4 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 4 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 3u)  // Option 5 Information Element ID (senderLocator)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)       // Option 5 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 5 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 11u) // Option 6 Information Element ID (informationSource)
 | 
					
						
							|  |  |  |           << quint16 (1u)           // Option 6 Field Length
 | 
					
						
							|  |  |  |           << quint32 (30351u)       // Option 6 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (150u)         // Option 7 Information Element ID (dateTimeSeconds)
 | 
					
						
							|  |  |  |           << quint16 (4u);          // Option 7 Field Length
 | 
					
						
							|  |  |  |         // insert Length and move to payload
 | 
					
						
							|  |  |  |         set_length (out, descriptor); | 
					
						
							|  |  |  |         message.writeRawData (descriptor.constData (), descriptor.size ()); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         // Receiver Information descriptor
 | 
					
						
							|  |  |  |         QByteArray descriptor; | 
					
						
							|  |  |  |         QDataStream out {&descriptor, QIODevice::WriteOnly}; | 
					
						
							|  |  |  |         out | 
					
						
							|  |  |  |           << quint16 (3u)          // Options Template Set ID
 | 
					
						
							|  |  |  |           << quint16 (0u)          // Length (place-holder)
 | 
					
						
							|  |  |  |           << quint16 (0x50e2)      // Link ID
 | 
					
						
							|  |  |  |           << quint16 (4u)          // Field Count
 | 
					
						
							|  |  |  |           << quint16 (0u)          // Scope Field Count
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 2u) // Option 1 Information Element ID (receiverCallsign)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)      // Option 1 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)      // Option 1 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 4u) // Option 2 Information Element ID (receiverLocator)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)      // Option 2 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)      // Option 2 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 8u) // Option 3 Information Element ID (decodingSoftware)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)      // Option 3 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u)      // Option 3 Enterprise Number
 | 
					
						
							|  |  |  |           << quint16 (0x8000 + 9u) // Option 4 Information Element ID (antennaInformation)
 | 
					
						
							|  |  |  |           << quint16 (0xffff)      // Option 4 Field Length (variable)
 | 
					
						
							|  |  |  |           << quint32 (30351u);     // Option 4 Enterprise Number
 | 
					
						
							|  |  |  |         // insert Length
 | 
					
						
							|  |  |  |         set_length (out, descriptor); | 
					
						
							|  |  |  |         message.writeRawData (descriptor.constData (), descriptor.size ()); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |         LOG_LOG_LOCATION (logger_, debug, "sent descriptors"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // if (send_receiver_data_)
 | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     // --send_receiver_data_;
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Receiver information
 | 
					
						
							|  |  |  |     QByteArray data; | 
					
						
							|  |  |  |     QDataStream out {&data, QIODevice::WriteOnly}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Set Header
 | 
					
						
							|  |  |  |     out | 
					
						
							|  |  |  |       << quint16 (0x50e2)     // Template ID
 | 
					
						
							|  |  |  |       << quint16 (0u);        // Length (place-holder)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Set data
 | 
					
						
							|  |  |  |     writeUtfString (out, rx_call_); | 
					
						
							|  |  |  |     writeUtfString (out, rx_grid_); | 
					
						
							|  |  |  |     writeUtfString (out, prog_id_); | 
					
						
							|  |  |  |     writeUtfString (out, rx_ant_); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // insert Length and move to payload
 | 
					
						
							|  |  |  |     set_length (out, data); | 
					
						
							|  |  |  |     message.writeRawData (data.constData (), data.size ()); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |     LOG_LOG_LOCATION (logger_, debug, "sent local information"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | void PSKReporter::impl::send_report (bool send_residue) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (logger_, trace, "sending residue: " << send_residue); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   if (QAbstractSocket::ConnectedState != socket_->state ()) return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QDataStream message {&payload_, QIODevice::WriteOnly | QIODevice::Append}; | 
					
						
							|  |  |  |   QDataStream tx_out {&tx_data_, QIODevice::WriteOnly | QIODevice::Append}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (!payload_.size ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       // Build header, optional descriptors, and receiver information
 | 
					
						
							|  |  |  |       build_preamble (message); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   auto flush = flushing () || send_residue; | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |   while (spots_.size () || flush) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |     { | 
					
						
							|  |  |  |       if (!payload_.size ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           // Build header, optional descriptors, and receiver information
 | 
					
						
							|  |  |  |           build_preamble (message); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |       if (!tx_data_.size () && (spots_.size () || tx_residue_.size ())) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |         { | 
					
						
							|  |  |  |           // Set Header
 | 
					
						
							|  |  |  |           tx_out | 
					
						
							|  |  |  |             << quint16 (0x50e3)     // Template ID
 | 
					
						
							|  |  |  |             << quint16 (0u);        // Length (place-holder)
 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // insert any residue
 | 
					
						
							|  |  |  |       if (tx_residue_.size ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           tx_out.writeRawData (tx_residue_.constData (), tx_residue_.size ()); | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |           LOG_LOG_LOCATION (logger_, debug, "sent residue"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |           tx_residue_.clear (); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |       LOG_LOG_LOCATION (logger_, debug, "pending spots: " << spots_.size ()); | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |       while (spots_.size () || flush) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |         { | 
					
						
							|  |  |  |           auto tx_data_size = tx_data_.size (); | 
					
						
							|  |  |  |           if (spots_.size ()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               auto const& spot = spots_.dequeue (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               // Sender information
 | 
					
						
							|  |  |  |               writeUtfString (tx_out, spot.call_); | 
					
						
							|  |  |  |               tx_out | 
					
						
							|  |  |  |                 << static_cast<quint32> (spot.freq_) | 
					
						
							|  |  |  |                 << static_cast<qint8> (spot.snr_); | 
					
						
							|  |  |  |               writeUtfString (tx_out, spot.mode_); | 
					
						
							|  |  |  |               writeUtfString (tx_out, spot.grid_); | 
					
						
							|  |  |  |               tx_out | 
					
						
							|  |  |  |                 << quint8 (1u)          // REPORTER_SOURCE_AUTOMATIC
 | 
					
						
							| 
									
										
										
										
											2020-08-07 22:53:21 +01:00
										 |  |  |                 << static_cast<quint32> ( | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
 | 
					
						
							|  |  |  |                                          spot.time_.toSecsSinceEpoch () | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                          spot.time_.toMSecsSinceEpoch () / 1000 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                          ); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           auto len = payload_.size () + tx_data_.size (); | 
					
						
							|  |  |  |           len += num_pad_bytes (tx_data_.size ()); | 
					
						
							|  |  |  |           len += num_pad_bytes (len); | 
					
						
							|  |  |  |           if (len > MAX_PAYLOAD_LENGTH // our upper datagram size limit
 | 
					
						
							|  |  |  |               || (!spots_.size () && len > MIN_PAYLOAD_LENGTH) // spots drained and above lower datagram size limit
 | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |               || (flush && !spots_.size ())) // send what we have, possibly no spots
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |             { | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |               if (tx_data_.size ()) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |                 { | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |                   if (len <= MAX_PAYLOAD_LENGTH) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                       tx_data_size = tx_data_.size (); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                   QByteArray tx {tx_data_.left (tx_data_size)}; | 
					
						
							|  |  |  |                   QDataStream out {&tx, QIODevice::WriteOnly | QIODevice::Append}; | 
					
						
							|  |  |  |                   // insert Length
 | 
					
						
							|  |  |  |                   set_length (out, tx); | 
					
						
							|  |  |  |                   message.writeRawData (tx.constData (), tx.size ()); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |               // insert Length and Export Time
 | 
					
						
							|  |  |  |               set_length (message, payload_); | 
					
						
							|  |  |  |               message.device ()->seek (2 * sizeof (quint16)); | 
					
						
							| 
									
										
										
										
											2020-08-07 22:53:21 +01:00
										 |  |  |               message << static_cast<quint32> ( | 
					
						
							|  |  |  | #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
 | 
					
						
							|  |  |  |                                                QDateTime::currentDateTime ().toSecsSinceEpoch () | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  |                                                QDateTime::currentDateTime ().toMSecsSinceEpoch () / 1000 | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  |                                                ); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |               // Send data to PSK Reporter site
 | 
					
						
							|  |  |  |               socket_->write (payload_); // TODO: handle errors
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |               LOG_LOG_LOCATION (logger_, debug, "sent spots"); | 
					
						
							| 
									
										
										
										
											2020-06-21 16:11:36 +01:00
										 |  |  |               flush = false;    // break loop
 | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |               message.device ()->seek (0u); | 
					
						
							|  |  |  |               payload_.clear ();  // Fresh message
 | 
					
						
							|  |  |  |               // Save unsent spots
 | 
					
						
							|  |  |  |               tx_residue_ = tx_data_.right (tx_data_.size () - tx_data_size); | 
					
						
							|  |  |  |               tx_out.device ()->seek (0u); | 
					
						
							|  |  |  |               tx_data_.clear (); | 
					
						
							|  |  |  |               break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |       LOG_LOG_LOCATION (logger_, debug, "remaining spots: " << spots_.size ()); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | PSKReporter::PSKReporter (Configuration const * config, QString const& program_info) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   : m_ {this, config, program_info} | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, "Started for: " << program_info); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | PSKReporter::~PSKReporter () | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							|  |  |  |   // m_->send_report (true);       // send any pending spots
 | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, "Ended"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | void PSKReporter::reconnect () | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, ""); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   m_->reconnect (); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | void PSKReporter::setLocalStation (QString const& call, QString const& gridSquare, QString const& antenna) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, "call: " << call << " grid: " << gridSquare << " ant: " << antenna); | 
					
						
							| 
									
										
										
										
											2020-08-16 00:38:19 +01:00
										 |  |  |   m_->check_connection (); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   if (call != m_->rx_call_ || gridSquare != m_->rx_grid_ || antenna != m_->rx_ant_) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |       LOG_LOG_LOCATION (m_->logger_, trace, "updating information"); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |       m_->send_receiver_data_ = m_->socket_ | 
					
						
							|  |  |  |         && QAbstractSocket::UdpSocket == m_->socket_->socketType () ? 3 : 1; | 
					
						
							|  |  |  |       m_->rx_call_ = call; | 
					
						
							|  |  |  |       m_->rx_grid_ = gridSquare; | 
					
						
							|  |  |  |       m_->rx_ant_ = antenna; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-21 02:15:31 +01:00
										 |  |  | bool PSKReporter::addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |                                      , QString const& mode, int snr) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, "call: " << call << " grid: " << grid << " freq: " << freq << " mode: " << mode << " snr: " << snr); | 
					
						
							| 
									
										
										
										
											2020-08-15 23:32:10 +01:00
										 |  |  |   m_->check_connection (); | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  |   if (m_->socket_ && m_->socket_->isValid ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       if (QAbstractSocket::UnconnectedState == m_->socket_->state ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |            reconnect (); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       m_->spots_.enqueue ({call, grid, snr, freq, mode, QDateTime::currentDateTimeUtc ()}); | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   return false; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-16 00:38:19 +01:00
										 |  |  | void PSKReporter::sendReport (bool last) | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2021-01-26 14:36:13 +00:00
										 |  |  |   LOG_LOG_LOCATION (m_->logger_, trace, "last: " << last); | 
					
						
							| 
									
										
										
										
											2020-08-18 12:47:17 +01:00
										 |  |  |   m_->check_connection (); | 
					
						
							| 
									
										
										
										
											2020-08-16 00:38:19 +01:00
										 |  |  |   if (m_->socket_ && QAbstractSocket::ConnectedState == m_->socket_->state ()) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       m_->send_report (true); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   if (last) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       m_->stop (); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-21 01:36:30 +01:00
										 |  |  | } |