WSPR band hopping improvements

Any  band can  be  added to  the  schedule  for a  day  phase, when  a
coordinated band  slot is not available  a random band will  be chosen
from all the bands enabled for the current phase (Day, Night, etc.).

Fine tuned the size of the hopping schedule table widget.

Coloured the coordinated band columns in the schedule table widget.

Due to the  full range of bands now being  supported, the settings key
for the band hopping schedule has been changed forcing current testers
to redefine their schedultes.

git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5543 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
This commit is contained in:
Bill Somerville 2015-06-06 00:06:46 +00:00
parent 1b0ba09639
commit 92bd1700e6
7 changed files with 193 additions and 94 deletions

View File

@ -74,6 +74,19 @@ QString Bands::find (Frequency f) const
return result; return result;
} }
int Bands::find (QString const& band) const
{
int result {-1};
for (auto i = 0u; i < table_rows (); ++i)
{
if (band == ADIF_bands[i].name_)
{
result = i;
}
}
return result;
}
QString const& Bands::oob () QString const& Bands::oob ()
{ {
return oob_name; return oob_name;

View File

@ -55,6 +55,7 @@ public:
// Model API // Model API
// //
QString find (Frequency) const; // find band Frequency is in QString find (Frequency) const; // find band Frequency is in
int find (QString const&) const; // find row of band (-1 if not valid)
static QString const& oob (); static QString const& oob ();
// Iterators // Iterators

View File

@ -583,16 +583,6 @@ auto FrequencyList::end () const -> FrequencyList::const_iterator
return const_iterator (this, rowCount ()); return const_iterator (this, rowCount ());
} }
auto FrequencyList::all_bands () const -> BandSet
{
BandSet result;
for (auto const& item : m_->frequency_list_)
{
result << m_->bands_->find (item.frequency_);
}
return result;
}
auto FrequencyList::filtered_bands () const -> BandSet auto FrequencyList::filtered_bands () const -> BandSet
{ {
BandSet result; BandSet result;
@ -602,3 +592,16 @@ auto FrequencyList::filtered_bands () const -> BandSet
} }
return result; return result;
} }
auto FrequencyList::all_bands (Mode mode) const -> BandSet
{
BandSet result;
for (auto const& item : m_->frequency_list_)
{
if (mode == Modes::NULL_MODE || item.mode_ == mode)
{
result << m_->bands_->find (item.frequency_);
}
}
return result;
}

View File

@ -90,7 +90,7 @@ public:
const_iterator end () const; const_iterator end () const;
// Bands of the frequencies // Bands of the frequencies
BandSet all_bands () const; BandSet all_bands (Mode = Modes::NULL_MODE) const;
BandSet filtered_bands () const; BandSet filtered_bands () const;
// Find the row of the nearest best working frequency given a // Find the row of the nearest best working frequency given a

View File

@ -3,10 +3,12 @@
#include <QPointer> #include <QPointer>
#include <QSettings> #include <QSettings>
#include <QBitArray> #include <QBitArray>
#include <QList>
#include <QtWidgets> #include <QtWidgets>
#include "SettingsGroup.hpp" #include "SettingsGroup.hpp"
#include "Configuration.hpp" #include "Configuration.hpp"
#include "Bands.hpp"
#include "FrequencyList.hpp" #include "FrequencyList.hpp"
#include "WsprTxScheduler.h" #include "WsprTxScheduler.h"
#include "pimpl_impl.hpp" #include "pimpl_impl.hpp"
@ -26,12 +28,11 @@ extern "C"
namespace namespace
{ {
// These 10 bands are the hopping candidates and are globally coordinated char const * const title = "WSPR Band Hopping";
char const * const hopping_bands[] = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"};
size_t constexpr num_bands {sizeof (hopping_bands) / sizeof (hopping_bands[0])};
char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"}; char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"};
size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])}; size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])};
char const * const title = "WSPR Band Hopping"; // These 10 bands are globally coordinated
QList<QString> const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"};
} }
// //
@ -41,9 +42,13 @@ class Dialog
: public QDialog : public QDialog
{ {
public: public:
Dialog (QSettings *, QBitArray * bands, int * gray_line_duration, QWidget * parent = nullptr); using BandList = QList<QString>;
Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands
, int * gray_line_duration, QWidget * parent = nullptr);
~Dialog (); ~Dialog ();
Q_SLOT void frequencies_changed ();
void resize_to_maximum (); void resize_to_maximum ();
private: private:
@ -51,48 +56,49 @@ private:
void save_window_state (); void save_window_state ();
QSettings * settings_; QSettings * settings_;
Configuration const * configuration_;
BandList const * WSPR_bands_;
QBitArray * bands_; QBitArray * bands_;
int * gray_line_duration_; int * gray_line_duration_;
QPointer<QTableWidget> bands_table_; QPointer<QTableWidget> bands_table_;
QBrush coord_background_brush_;
QPointer<QSpinBox> gray_line_width_spin_box_; QPointer<QSpinBox> gray_line_width_spin_box_;
static int const band_index_role {Qt::UserRole};
}; };
Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duration, QWidget * parent) Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
, QBitArray * bands, int * gray_line_duration, QWidget * parent)
: QDialog {parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint} : QDialog {parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint}
, settings_ {settings} , settings_ {settings}
, configuration_ {configuration}
, WSPR_bands_ {WSPR_bands}
, bands_ {bands} , bands_ {bands}
, gray_line_duration_ {gray_line_duration} , gray_line_duration_ {gray_line_duration}
, bands_table_ {new QTableWidget {num_periods, num_bands, this}} , bands_table_ {new QTableWidget {this}}
, coord_background_brush_ {Qt::yellow}
, gray_line_width_spin_box_ {new QSpinBox {this}} , gray_line_width_spin_box_ {new QSpinBox {this}}
{ {
setWindowTitle (windowTitle () + ' ' + tr (title));
{
SettingsGroup g {settings_, title};
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
}
QVBoxLayout * main_layout {new QVBoxLayout}; QVBoxLayout * main_layout {new QVBoxLayout};
// set up and load the table of check boxes bands_table_->setRowCount (num_periods);
bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
for (auto row = 0u; row < num_periods; ++row) bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
{ frequencies_changed ();
auto vertical_header = new QTableWidgetItem {periods[row]};
vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter);
bands_table_->setVerticalHeaderItem (row, vertical_header);
for (auto column = 0u; column < num_bands; ++column)
{
if (0 == row)
{
auto horizontal_header = new QTableWidgetItem {hopping_bands[column]};
bands_table_->setHorizontalHeaderItem (column, horizontal_header);
}
auto item = new QTableWidgetItem;
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setCheckState (bands_[row].testBit (column) ? Qt::Checked : Qt::Unchecked);
bands_table_->setItem (row, column, item);
}
}
bands_table_->resizeColumnsToContents ();
main_layout->addWidget (bands_table_); main_layout->addWidget (bands_table_);
// recalculate table when frequencies change
connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged
, this, &Dialog::frequencies_changed);
// handle changes by updating the underlying flags // handle changes by updating the underlying flags
connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) { connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) {
bands_[item->row ()].setBit (item->column (), Qt::Checked == item->checkState ()); auto band_number = item->data (band_index_role).toInt ();
bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ());
}); });
// set up the gray line duration spin box // set up the gray line duration spin box
@ -111,11 +117,6 @@ Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duratio
main_layout->addLayout (bottom_layout); main_layout->addLayout (bottom_layout);
setLayout (main_layout); setLayout (main_layout);
setWindowTitle (windowTitle () + ' ' + tr (title));
{
SettingsGroup g {settings_, title};
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
}
} }
Dialog::~Dialog () Dialog::~Dialog ()
@ -137,46 +138,89 @@ void Dialog::save_window_state ()
settings_->setValue ("geometry", saveGeometry ()); settings_->setValue ("geometry", saveGeometry ());
} }
void Dialog::frequencies_changed ()
{
bands_table_->setColumnCount (WSPR_bands_->size ());
// set up and load the table of check boxes
for (auto row = 0u; row < num_periods; ++row)
{
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;
}
}
bands_table_->resizeColumnsToContents ();
auto is_visible = isVisible ();
show ();
resize_to_maximum ();
adjustSize (); // fix the size
if (!is_visible)
{
hide ();
}
}
// to get the dialog window exactly the right size to contain the // to get the dialog window exactly the right size to contain the
// widgets without needing scroll bars we need to measure the size of // widgets without needing scroll bars we need to measure the size of
// the table widget and set its minimum size to the measured size // the table widget and set its minimum size to the measured size
void Dialog::resize_to_maximum () void Dialog::resize_to_maximum ()
{ {
int width {bands_table_->verticalHeader ()->width ()}; bands_table_->setMinimumSize ({
int height {bands_table_->horizontalHeader ()->height ()}; bands_table_->horizontalHeader ()->length ()
for (auto i = 0; i < bands_table_->columnCount (); ++i) + bands_table_->verticalHeader ()->width ()
{ + 2 * bands_table_->frameWidth ()
width += bands_table_->columnWidth (i); , bands_table_->verticalHeader ()->length ()
} + bands_table_->horizontalHeader ()->height ()
for (auto i = 0; i < bands_table_->rowCount (); ++i) + 2 * bands_table_->frameWidth ()
{ });
height += bands_table_->rowHeight (i); bands_table_->setMaximumSize (bands_table_->minimumSize ());
}
bands_table_->setMinimumSize ({width, height});
} }
class WSPRBandHopping::impl class WSPRBandHopping::impl
{ {
public: public:
using BandList = Dialog::BandList;
impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
: settings_ {settings} : settings_ {settings}
, configuration_ {configuration} , configuration_ {configuration}
, tx_percent_ {0} , tx_percent_ {0}
, parent_widget_ {parent_widget} , parent_widget_ {parent_widget}
, bands_ {
QBitArray {num_bands},
QBitArray {num_bands},
QBitArray {num_bands},
QBitArray {num_bands},
QBitArray {num_bands},
QBitArray {num_bands},
}
{ {
auto num_bands = configuration_->bands ()->rowCount ();
for (auto& flags : bands_)
{
flags.resize (num_bands);
}
} }
QSettings * settings_; QSettings * settings_;
Configuration const * configuration_; Configuration const * configuration_;
int tx_percent_; int tx_percent_;
BandList WSPR_bands_;
QWidget * parent_widget_; QWidget * parent_widget_;
// 5 x 10 bit flags representing each hopping band in each period // 5 x 10 bit flags representing each hopping band in each period
@ -190,13 +234,23 @@ public:
WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
: m_ {settings, configuration, parent_widget} : m_ {settings, configuration, parent_widget}
{ {
// detect changes to the working frequencies model
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (Modes::WSPR).toList ();
connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged
, [this] () {
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (Modes::WSPR).toList ();
});
// load settings // load settings
SettingsGroup g {m_->settings_, title}; SettingsGroup g {m_->settings_, title};
auto size = m_->settings_->beginReadArray ("periods"); size_t size = m_->settings_->beginReadArray ("phases");
for (auto i = 0; i < size; ++i) for (auto i = 0u; i < size; ++i)
{ {
m_->settings_->setArrayIndex (i); if (i < num_periods)
m_->bands_[i] = m_->settings_->value ("bands").toBitArray (); {
m_->settings_->setArrayIndex (i);
m_->bands_[i] = m_->settings_->value ("bands").toBitArray ();
}
} }
m_->settings_->endArray (); m_->settings_->endArray ();
m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt (); m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt ();
@ -206,7 +260,7 @@ WSPRBandHopping::~WSPRBandHopping ()
{ {
// save settings // save settings
SettingsGroup g {m_->settings_, title}; SettingsGroup g {m_->settings_, title};
m_->settings_->beginWriteArray ("periods"); m_->settings_->beginWriteArray ("phases");
for (auto i = 0u; i < num_periods; ++i) for (auto i = 0u; i < num_periods; ++i)
{ {
m_->settings_->setArrayIndex (i); m_->settings_->setArrayIndex (i);
@ -221,13 +275,10 @@ void WSPRBandHopping::show_dialog (bool /* checked */)
{ {
if (!m_->dialog_) if (!m_->dialog_)
{ {
m_->dialog_ = new Dialog {m_->settings_, m_->bands_, &m_->gray_line_duration_, m_->parent_widget_}; m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_
, &m_->gray_line_duration_, m_->parent_widget_};
} }
m_->dialog_->show (); m_->dialog_->show ();
m_->dialog_->resize_to_maximum ();
m_->dialog_->adjustSize (); // fix the size
m_->dialog_->setMinimumSize (m_->dialog_->size ());
m_->dialog_->setMaximumSize (m_->dialog_->size ());
m_->dialog_->raise (); m_->dialog_->raise ();
m_->dialog_->activateWindow (); m_->dialog_->activateWindow ();
} }
@ -266,51 +317,81 @@ auto WSPRBandHopping::next_hop () -> Hop
, &m_->gray_line_duration_, &period_index , &m_->gray_line_duration_, &period_index
, my_grid.size ()); , my_grid.size ());
// consult scheduler to determine if next period should be a tx interval
tx_next = next_tx_state(m_->tx_percent_);
band_index = next_hopping_band(); band_index = next_hopping_band();
if (100 == m_->tx_percent_) if (100 == m_->tx_percent_)
{ {
tx_next = 1; tx_next = 1;
} }
else
{
// consult scheduler to determine if next period should be a tx interval
tx_next = next_tx_state(m_->tx_percent_);
}
int frequencies_index {-1}; int frequencies_index {-1};
auto const& frequencies = m_->configuration_->frequencies (); auto const& frequencies = m_->configuration_->frequencies ();
auto const& filtered_bands = frequencies->filtered_bands (); auto const& bands = m_->configuration_->bands ();
if (m_->bands_[period_index].testBit (band_index) auto const& band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
&& filtered_bands.contains (hopping_bands[band_index])) if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
// coordinated bands
&& m_->WSPR_bands_.contains (band_name))
{ {
// here we have a band that has been enabled in the hopping // here we have a band that has been enabled in the hopping
// matrix so check it it has a configured working frequency // matrix so check it it has a configured working frequency
frequencies_index = frequencies->best_working_frequency (hopping_bands[band_index]); frequencies_index = frequencies->best_working_frequency (band_name);
qDebug () << "scheduled:" << hopping_bands[band_index] << "frequency:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
} }
// if we do not have a configured working frequency we next check // if we do not have a configured working frequency we next check
// for a random selection from the other enabled bands in the // for a random selection from the other enabled bands in the
// hopping matrix // bands matrix
if (frequencies_index < 0) if (frequencies_index < 0)
{ {
for (auto i = 0u; i < num_bands; ++i) Dialog::BandList target_bands {m_->WSPR_bands_};
// // remove all coordinated bands here since they are
// // scheduled above and including them in the random choice will
// // give them a biased weighting
// for (auto const& band : coordinated_bands)
// {
// target_bands.removeOne (band);
// }
// remove bands that are not enabled for hopping
for (auto i = 0; i < m_->bands_[period_index].size (); ++i)
{ {
int new_index = static_cast<int> (qrand () % num_bands); // random choice if (!m_->bands_[period_index].testBit (i))
if (new_index != band_index && m_->bands_[period_index].testBit (new_index))
{ {
// here we have a random choice that is enabled in the target_bands.removeOne (bands->data (bands->index (i, 0)).toString ());
// hopping matrix and not the scheduled choice so we now }
// check if it has a configured working frequency }
frequencies_index = frequencies->best_working_frequency (hopping_bands[new_index]);
if (frequencies_index >= 0) auto num_bands = target_bands.size ();
if (num_bands) // we have some extra bands available
{
int target_index = static_cast<int> (qrand () % num_bands); // random choice
// here we have a random choice that is enabled in the
// hopping matrix
frequencies_index = frequencies->best_working_frequency (target_bands[target_index]);
if (frequencies_index >= 0)
{
// we can use the random choice
qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
band_index = bands->find (target_bands[target_index]);
if (band_index < 0)
{ {
// we can use the random choice // this shouldn't happen
qDebug () << "random:" << hopping_bands[new_index] << "frequency:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); Q_ASSERT (band_index >= 0);
band_index = new_index; frequencies_index = -1;
break;
} }
} }
} }
} }
else
{
band_index += 3;
qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString ();
}
return { return {
periods[period_index] periods[period_index]

View File

@ -93,6 +93,7 @@ void tx_print()
} }
} }
printf("\n"); printf("\n");
fflush(stdout);
} }
int create_tx_schedule(int pctx) int create_tx_schedule(int pctx)

View File

@ -4311,7 +4311,7 @@ void MainWindow::bandHopping()
} }
if (ui->band_hopping_group_box->isChecked ()) { if (ui->band_hopping_group_box->isChecked ()) {
QThread::msleep(500); //### Is this OK to do ??? ### // QThread::msleep(500); //### Is this OK to do ??? ###
if (hop_data.frequencies_index_ >= 0) { // new band if (hop_data.frequencies_index_ >= 0) { // new band
ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_); ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_);