| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #include "WSPRBandHopping.hpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  | #include <random>
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #include <QPointer>
 | 
					
						
							|  |  |  | #include <QSettings>
 | 
					
						
							|  |  |  | #include <QBitArray>
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  | #include <QList>
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  | #include <QSet>
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #include <QtWidgets>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "SettingsGroup.hpp"
 | 
					
						
							|  |  |  | #include "Configuration.hpp"
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  | #include "Bands.hpp"
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #include "FrequencyList.hpp"
 | 
					
						
							| 
									
										
										
										
											2015-06-05 19:48:53 +00:00
										 |  |  | #include "WsprTxScheduler.h"
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #include "pimpl_impl.hpp"
 | 
					
						
							|  |  |  | #include "moc_WSPRBandHopping.cpp"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | extern "C" | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | #ifndef CMAKE_BUILD
 | 
					
						
							| 
									
										
										
										
											2015-06-06 13:05:04 +00:00
										 |  |  | #define FC_grayline grayline_
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #else
 | 
					
						
							|  |  |  | #include "FC.h"
 | 
					
						
							| 
									
										
										
										
											2015-06-06 13:05:04 +00:00
										 |  |  |   void FC_grayline (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid | 
					
						
							| 
									
										
										
										
											2015-06-05 23:03:32 +00:00
										 |  |  |                    , int const * nduration, int * isun | 
					
						
							|  |  |  |                    , int my_grid_len); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | #endif
 | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   char const * const title = "WSPR Band Hopping"; | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  |   char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"}; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])}; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   // These 10 bands are globally coordinated
 | 
					
						
							|  |  |  |   QList<QString> const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"}; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // Dialog - maintenance of band hopping options
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | class Dialog | 
					
						
							|  |  |  |   : public QDialog | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   using BandList = QList<QString>; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands | 
					
						
							|  |  |  |           , int * gray_line_duration, QWidget * parent = nullptr); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   ~Dialog (); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   Q_SLOT void frequencies_changed (); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   void resize_to_maximum (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | private: | 
					
						
							|  |  |  |   void closeEvent (QCloseEvent *) override; | 
					
						
							|  |  |  |   void save_window_state (); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QSettings * settings_; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   Configuration const * configuration_; | 
					
						
							|  |  |  |   BandList const * WSPR_bands_; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   QBitArray * bands_; | 
					
						
							|  |  |  |   int * gray_line_duration_; | 
					
						
							|  |  |  |   QPointer<QTableWidget> bands_table_; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   QBrush coord_background_brush_; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   QPointer<QSpinBox> gray_line_width_spin_box_; | 
					
						
							| 
									
										
										
										
											2016-05-01 00:40:51 +00:00
										 |  |  |   static int constexpr band_index_role {Qt::UserRole}; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  | Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands | 
					
						
							|  |  |  |                 , QBitArray * bands, int * gray_line_duration, QWidget * parent) | 
					
						
							| 
									
										
										
										
											2015-06-12 12:22:59 +00:00
										 |  |  |   : QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint} | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   , settings_ {settings} | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   , configuration_ {configuration} | 
					
						
							|  |  |  |   , WSPR_bands_ {WSPR_bands} | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   , bands_ {bands} | 
					
						
							|  |  |  |   , gray_line_duration_ {gray_line_duration} | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   , bands_table_ {new QTableWidget {this}} | 
					
						
							|  |  |  |   , coord_background_brush_ {Qt::yellow} | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   , gray_line_width_spin_box_ {new QSpinBox {this}} | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   setWindowTitle (windowTitle () + ' ' + tr (title)); | 
					
						
							|  |  |  |   { | 
					
						
							|  |  |  |     SettingsGroup g {settings_, title}; | 
					
						
							|  |  |  |     restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   QVBoxLayout * main_layout {new QVBoxLayout}; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   bands_table_->setRowCount (num_periods); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); | 
					
						
							|  |  |  |   bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding); | 
					
						
							|  |  |  |   frequencies_changed (); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   main_layout->addWidget (bands_table_); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   // recalculate table when frequencies change
 | 
					
						
							|  |  |  |   connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged | 
					
						
							|  |  |  |            , this, &Dialog::frequencies_changed); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   // handle changes by updating the underlying flags
 | 
					
						
							|  |  |  |   connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       auto band_number = item->data (band_index_role).toInt (); | 
					
						
							|  |  |  |       bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ()); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // set up the gray line duration spin box
 | 
					
						
							|  |  |  |   gray_line_width_spin_box_->setRange (1, 60 * 2); | 
					
						
							|  |  |  |   gray_line_width_spin_box_->setSuffix ("min"); | 
					
						
							|  |  |  |   gray_line_width_spin_box_->setValue (*gray_line_duration_); | 
					
						
							|  |  |  |   QFormLayout * form_layout = new QFormLayout; | 
					
						
							|  |  |  |   form_layout->addRow (tr ("Gray time:"), gray_line_width_spin_box_); | 
					
						
							|  |  |  |   connect (gray_line_width_spin_box_.data () | 
					
						
							|  |  |  |            , static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged) | 
					
						
							|  |  |  |            , [this] (int new_value) {*gray_line_duration_ = new_value;}); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   QHBoxLayout * bottom_layout = new QHBoxLayout; | 
					
						
							|  |  |  |   bottom_layout->addStretch (); | 
					
						
							|  |  |  |   bottom_layout->addLayout (form_layout); | 
					
						
							|  |  |  |   main_layout->addLayout (bottom_layout); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setLayout (main_layout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | Dialog::~Dialog () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   // do this here too because ESC or parent shutdown closing this
 | 
					
						
							|  |  |  |   // window doesn't queue a close event
 | 
					
						
							|  |  |  |   save_window_state (); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Dialog::closeEvent (QCloseEvent * e) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   save_window_state (); | 
					
						
							|  |  |  |   QDialog::closeEvent (e); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void Dialog::save_window_state () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   SettingsGroup g {settings_, title}; | 
					
						
							|  |  |  |   settings_->setValue ("geometry", saveGeometry ()); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  | void Dialog::frequencies_changed () | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   bands_table_->setColumnCount (WSPR_bands_->size ()); | 
					
						
							|  |  |  |   // set up and load the table of check boxes
 | 
					
						
							|  |  |  |   for (auto row = 0u; row < num_periods; ++row) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       auto vertical_header = new QTableWidgetItem {periods[row]}; | 
					
						
							|  |  |  |       vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter); | 
					
						
							|  |  |  |       bands_table_->setVerticalHeaderItem (row, vertical_header); | 
					
						
							|  |  |  |       int column {0}; | 
					
						
							|  |  |  |       int band_number {0}; | 
					
						
							|  |  |  |       for (auto const& band : *configuration_->bands ()) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           if (WSPR_bands_->contains (band)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               if (0 == row) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   auto horizontal_header = new QTableWidgetItem {band}; | 
					
						
							|  |  |  |                   bands_table_->setHorizontalHeaderItem (column, horizontal_header); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               auto item = new QTableWidgetItem; | 
					
						
							|  |  |  |               item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); | 
					
						
							|  |  |  |               item->setCheckState (bands_[row].testBit (band_number) ? Qt::Checked : Qt::Unchecked); | 
					
						
							|  |  |  |               item->setData (band_index_role, band_number); | 
					
						
							|  |  |  |               if (coordinated_bands.contains (band)) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                   item->setBackground (coord_background_brush_); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               bands_table_->setItem (row, column, item); | 
					
						
							|  |  |  |               ++column; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           ++band_number; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   bands_table_->resizeColumnsToContents (); | 
					
						
							|  |  |  |   auto is_visible = isVisible (); | 
					
						
							|  |  |  |   show (); | 
					
						
							|  |  |  |   resize_to_maximum (); | 
					
						
							|  |  |  |   adjustSize ();   // fix the size
 | 
					
						
							|  |  |  |   if (!is_visible) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       hide (); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // to get the dialog window exactly the right size to contain the
 | 
					
						
							|  |  |  | // widgets without needing scroll bars we need to measure the size of
 | 
					
						
							|  |  |  | // the table widget and set its minimum size to the measured size
 | 
					
						
							|  |  |  | void Dialog::resize_to_maximum () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   bands_table_->setMinimumSize ({ | 
					
						
							|  |  |  |       bands_table_->horizontalHeader ()->length () | 
					
						
							|  |  |  |         + bands_table_->verticalHeader ()->width () | 
					
						
							|  |  |  |         + 2 * bands_table_->frameWidth () | 
					
						
							|  |  |  |       , bands_table_->verticalHeader ()->length () | 
					
						
							|  |  |  |         + bands_table_->horizontalHeader ()->height () | 
					
						
							|  |  |  |         + 2 * bands_table_->frameWidth () | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   bands_table_->setMaximumSize (bands_table_->minimumSize ()); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class WSPRBandHopping::impl | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | public: | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   using BandList = Dialog::BandList; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) | 
					
						
							|  |  |  |     : settings_ {settings} | 
					
						
							|  |  |  |     , configuration_ {configuration} | 
					
						
							|  |  |  |     , tx_percent_ {0} | 
					
						
							|  |  |  |     , parent_widget_ {parent_widget} | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |     , last_was_tx_ {false} | 
					
						
							|  |  |  |     , carry_ {false} | 
					
						
							| 
									
										
										
										
											2017-05-18 21:14:33 +00:00
										 |  |  |     , seed_ {{rand (), rand (), rand (), rand (), rand (), rand (), rand (), rand ()}} | 
					
						
							|  |  |  |     , gen_ {seed_} | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |     , dist_ {1, 100} | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |     auto num_bands = configuration_->bands ()->rowCount (); | 
					
						
							|  |  |  |     for (auto& flags : bands_) | 
					
						
							|  |  |  |       { | 
					
						
							|  |  |  |         flags.resize (num_bands); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |   bool simple_scheduler (); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   QSettings * settings_; | 
					
						
							|  |  |  |   Configuration const * configuration_; | 
					
						
							|  |  |  |   int tx_percent_; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   BandList WSPR_bands_; | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |   BandList rx_permutation_; | 
					
						
							|  |  |  |   BandList tx_permutation_; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   QWidget * parent_widget_; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // 5 x 10 bit flags representing each hopping band in each period
 | 
					
						
							|  |  |  |   // and tune
 | 
					
						
							|  |  |  |   QBitArray bands_[num_periods]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   int gray_line_duration_; | 
					
						
							|  |  |  |   QPointer<Dialog> dialog_; | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |   bool last_was_tx_; | 
					
						
							|  |  |  |   bool carry_; | 
					
						
							| 
									
										
										
										
											2017-05-18 21:14:33 +00:00
										 |  |  |   std::seed_seq seed_; | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |   std::mt19937 gen_; | 
					
						
							|  |  |  |   std::uniform_int_distribution<int> dist_; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  | bool WSPRBandHopping::impl::simple_scheduler () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   auto tx = carry_ || tx_percent_ > dist_ (gen_); | 
					
						
							|  |  |  |   if (carry_) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       carry_ = false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   else if (tx_percent_ < 40 && last_was_tx_ && tx) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       // if percentage is less than 40 then avoid consecutive tx but
 | 
					
						
							|  |  |  |       // always catch up on the next round
 | 
					
						
							|  |  |  |       tx = false; | 
					
						
							|  |  |  |       carry_ = true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   last_was_tx_ = tx; | 
					
						
							|  |  |  |   return tx; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) | 
					
						
							|  |  |  |   : m_ {settings, configuration, parent_widget} | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   // detect changes to the working frequencies model
 | 
					
						
							| 
									
										
										
										
											2017-07-07 23:11:41 +00:00
										 |  |  |   m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList (); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged | 
					
						
							|  |  |  |            , [this] () { | 
					
						
							| 
									
										
										
										
											2017-07-07 23:11:41 +00:00
										 |  |  |              m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList (); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |            }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   // load settings
 | 
					
						
							|  |  |  |   SettingsGroup g {m_->settings_, title}; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   size_t size = m_->settings_->beginReadArray ("phases"); | 
					
						
							|  |  |  |   for (auto i = 0u; i < size; ++i) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       if (i < num_periods) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           m_->settings_->setArrayIndex (i); | 
					
						
							|  |  |  |           m_->bands_[i] = m_->settings_->value ("bands").toBitArray (); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |   m_->settings_->endArray (); | 
					
						
							|  |  |  |   m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt (); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | WSPRBandHopping::~WSPRBandHopping () | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   // save settings
 | 
					
						
							|  |  |  |   SettingsGroup g {m_->settings_, title}; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   m_->settings_->beginWriteArray ("phases"); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   for (auto i = 0u; i < num_periods; ++i) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       m_->settings_->setArrayIndex (i); | 
					
						
							|  |  |  |       m_->settings_->setValue ("bands", m_->bands_[i]); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   m_->settings_->endArray (); | 
					
						
							|  |  |  |   m_->settings_->setValue ("GrayLineDuration", m_->gray_line_duration_); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // pop up the maintenance dialog window
 | 
					
						
							|  |  |  | void WSPRBandHopping::show_dialog (bool /* checked */) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   if (!m_->dialog_) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_ | 
					
						
							|  |  |  |                                 , &m_->gray_line_duration_, m_->parent_widget_}; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     } | 
					
						
							|  |  |  |   m_->dialog_->show (); | 
					
						
							|  |  |  |   m_->dialog_->raise (); | 
					
						
							|  |  |  |   m_->dialog_->activateWindow (); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int WSPRBandHopping::tx_percent () const | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   return m_->tx_percent_; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void WSPRBandHopping::set_tx_percent (int new_value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |   m_->tx_percent_ = new_value; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // determine the parameters of the hop, if any
 | 
					
						
							| 
									
										
										
										
											2015-07-11 22:49:56 +00:00
										 |  |  | auto WSPRBandHopping::next_hop (bool tx_enabled) -> Hop | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | { | 
					
						
							|  |  |  |   auto const& now = QDateTime::currentDateTimeUtc (); | 
					
						
							|  |  |  |   auto const& date = now.date (); | 
					
						
							|  |  |  |   auto year = date.year (); | 
					
						
							|  |  |  |   auto month = date.month (); | 
					
						
							|  |  |  |   auto day = date.day (); | 
					
						
							|  |  |  |   auto const& time = now.time (); | 
					
						
							|  |  |  |   float uth = time.hour () + time.minute () / 60. | 
					
						
							|  |  |  |     + (time.second () + .001 * time.msec ()) / 3600.; | 
					
						
							|  |  |  |   auto my_grid = m_->configuration_->my_grid (); | 
					
						
							|  |  |  |   int period_index; | 
					
						
							|  |  |  |   int band_index; | 
					
						
							|  |  |  |   int tx_next; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   my_grid = (my_grid + "      ").left (6); // hopping doesn't like
 | 
					
						
							|  |  |  |                                            // short grids
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-06 13:05:04 +00:00
										 |  |  |   // look up the period for this time
 | 
					
						
							|  |  |  |   FC_grayline (&year, &month, &day, &uth, my_grid.toLatin1 ().constData () | 
					
						
							|  |  |  |                , &m_->gray_line_duration_, &period_index | 
					
						
							|  |  |  |                , my_grid.size ()); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-05 23:03:32 +00:00
										 |  |  |   band_index = next_hopping_band(); | 
					
						
							| 
									
										
										
										
											2015-06-05 15:36:29 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-11 22:49:56 +00:00
										 |  |  |   tx_next = next_is_tx () && tx_enabled; | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   int frequencies_index {-1}; | 
					
						
							|  |  |  |   auto const& frequencies = m_->configuration_->frequencies (); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   auto const& bands = m_->configuration_->bands (); | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |   auto band_name = bands->data (bands->index (band_index + 3, 0)).toString (); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
 | 
					
						
							|  |  |  |                                                         // coordinated bands
 | 
					
						
							|  |  |  |       && m_->WSPR_bands_.contains (band_name)) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     { | 
					
						
							|  |  |  |       // here we have a band that has been enabled in the hopping
 | 
					
						
							|  |  |  |       // matrix so check it it has a configured working frequency
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       frequencies_index = frequencies->best_working_frequency (band_name); | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |   // if we do not have a configured working frequency on the selected
 | 
					
						
							|  |  |  |   // coordinated hopping band we next pick from a random permutation
 | 
					
						
							|  |  |  |   // of the other enabled bands in the hopping bands matrix
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |   if (frequencies_index < 0) | 
					
						
							|  |  |  |     { | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |       // build sets of available rx and tx bands
 | 
					
						
							|  |  |  |       auto target_rx_bands = m_->WSPR_bands_.toSet (); | 
					
						
							|  |  |  |       auto target_tx_bands = target_rx_bands; | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |       for (auto i = 0; i < m_->bands_[period_index].size (); ++i) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |           auto const& band = bands->data (bands->index (i, 0)).toString (); | 
					
						
							| 
									
										
										
										
											2015-06-09 16:56:49 +00:00
										 |  |  |           // remove bands that are not enabled for hopping in this phase
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |           if (!m_->bands_[period_index].testBit (i)) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               target_rx_bands.remove (band); | 
					
						
							|  |  |  |               target_tx_bands.remove (band); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           // remove rx only bands from transmit list and vice versa
 | 
					
						
							|  |  |  |           if (m_->bands_[5].testBit (i)) | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |             { | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |               target_tx_bands.remove (band); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               target_rx_bands.remove (band); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |       // if we have some bands to permute
 | 
					
						
							|  |  |  |       if (target_rx_bands.size () + target_tx_bands.size ()) | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |         { | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |           if (!(m_->rx_permutation_.size () + m_->tx_permutation_.size ()) // all used up
 | 
					
						
							|  |  |  |               // or rx list contains a band no longer scheduled
 | 
					
						
							|  |  |  |               || !target_rx_bands.contains (m_->rx_permutation_.toSet ()) | 
					
						
							|  |  |  |               // or tx list contains a band no longer scheduled for tx
 | 
					
						
							|  |  |  |               || !target_tx_bands.contains (m_->tx_permutation_.toSet ())) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               // build new random permutations
 | 
					
						
							|  |  |  |               m_->rx_permutation_ = target_rx_bands.toList (); | 
					
						
							|  |  |  |               std::random_shuffle (std::begin (m_->rx_permutation_), std::end (m_->rx_permutation_)); | 
					
						
							|  |  |  |               m_->tx_permutation_ = target_tx_bands.toList (); | 
					
						
							|  |  |  |               std::random_shuffle (std::begin (m_->tx_permutation_), std::end (m_->tx_permutation_)); | 
					
						
							|  |  |  |               // qDebug () << "New random Rx permutation:" << m_->rx_permutation_
 | 
					
						
							|  |  |  |               //           << "random Tx permutation:" << m_->tx_permutation_;
 | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           if ((tx_next && m_->tx_permutation_.size ()) || !m_->rx_permutation_.size ()) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               Q_ASSERT (m_->tx_permutation_.size ()); | 
					
						
							|  |  |  |               // use one from the current random tx permutation
 | 
					
						
							|  |  |  |               band_name = m_->tx_permutation_.takeFirst (); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           else | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |               Q_ASSERT (m_->rx_permutation_.size ()); | 
					
						
							|  |  |  |               // use one from the current random rx permutation
 | 
					
						
							|  |  |  |               band_name = m_->rx_permutation_.takeFirst (); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           // find the first WSPR working frequency for the chosen band
 | 
					
						
							|  |  |  |           frequencies_index = frequencies->best_working_frequency (band_name); | 
					
						
							|  |  |  |           if (frequencies_index >= 0) // should be a redundant check,
 | 
					
						
							|  |  |  |                                       // but to be safe
 | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |             { | 
					
						
							|  |  |  |               // we can use the random choice
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |               // qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
 | 
					
						
							|  |  |  |               band_index = bands->find (band_name); | 
					
						
							|  |  |  |               if (band_index < 0) // this shouldn't happen
 | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |                 { | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |                   Q_ASSERT (band_index >= 0); | 
					
						
							|  |  |  |                   frequencies_index = -1; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |      } | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       band_index += 3; | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |       // qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
 | 
					
						
							|  |  |  |       // remove from random permutations to stop the coordinated bands
 | 
					
						
							|  |  |  |       // getting too high a weighting - not perfect but surely helps
 | 
					
						
							|  |  |  |       m_->rx_permutation_.removeOne (band_name); | 
					
						
							|  |  |  |       m_->tx_permutation_.removeOne (band_name); | 
					
						
							| 
									
										
										
										
											2015-06-06 00:06:46 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  |   return { | 
					
						
							|  |  |  |     periods[period_index] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-11 22:49:56 +00:00
										 |  |  |       , frequencies_index | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-11 22:49:56 +00:00
										 |  |  |       , frequencies_index >= 0               // new band
 | 
					
						
							|  |  |  |       && tx_enabled                          // transmit is allowed
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |       && !tx_next                            // not going to Tx anyway
 | 
					
						
							|  |  |  |       && m_->bands_[4].testBit (band_index)  // tune up required
 | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  |       && !m_->bands_[5].testBit (band_index) // not an Rx only band
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2015-07-11 22:49:56 +00:00
										 |  |  |       , frequencies_index >= 0               // new band
 | 
					
						
							| 
									
										
										
										
											2015-06-15 12:04:09 +00:00
										 |  |  |       && tx_next                             // Tx scheduled
 | 
					
						
							| 
									
										
										
										
											2015-06-04 10:58:36 +00:00
										 |  |  |       && !m_->bands_[5].testBit (band_index) // not an Rx only band
 | 
					
						
							|  |  |  |    }; | 
					
						
							| 
									
										
										
										
											2015-06-04 01:45:40 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2015-06-06 13:28:20 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  | bool WSPRBandHopping::next_is_tx (bool simple_schedule) | 
					
						
							| 
									
										
										
										
											2015-06-06 13:28:20 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2017-05-18 01:52:30 +00:00
										 |  |  |   if (simple_schedule) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       return m_->simple_scheduler (); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2015-06-06 13:28:20 +00:00
										 |  |  |   if (100 == m_->tx_percent_) | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   else | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       // consult scheduler to determine if next period should be a tx interval
 | 
					
						
							|  |  |  |       return next_tx_state(m_->tx_percent_); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |