From f3930eb5c8068fad3e10efd88d5edce7ac4679f8 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Thu, 4 Jun 2015 01:45:40 +0000 Subject: [PATCH] Polish the UI for WSPR and WSPR band hopping Provide a dialog for band hopping scheduling which replaces tab four. Ensure that split mode is not used for WSPR. Select the correct tab for WSPR operation. Shift WSPR band hopping code out of the MainWindow class. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@5517 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- CMakeLists.txt | 5 +- Configuration.cpp | 20 ++- Configuration.hpp | 4 + Transceiver.hpp | 1 + WSPRBandHopping.cpp | 301 +++++++++++++++++++++++++++++++++++++++ WSPRBandHopping.hpp | 80 +++++++++++ mainwindow.cpp | 233 +++++++----------------------- mainwindow.h | 24 +--- mainwindow.ui | 339 ++++++++------------------------------------ 9 files changed, 531 insertions(+), 476 deletions(-) create mode 100644 WSPRBandHopping.cpp create mode 100644 WSPRBandHopping.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f6496d893..a664f0272 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,6 +237,7 @@ set (wsjtx_CXXSRCS Configuration.cpp main.cpp wsprnet.cpp + WSPRBandHopping.cpp ) if (WIN32) @@ -623,7 +624,9 @@ endif (NOT APPLE) include (FortranCInterface) FortranCInterface_VERIFY (CXX QUIET) FortranCInterface_HEADER (FC.h MACRO_NAMESPACE "FC_" SYMBOL_NAMESPACE "FC_" - SYMBOLS ) + SYMBOLS + hopping + ) # diff --git a/Configuration.cpp b/Configuration.cpp index 1ce678b5c..66bb59498 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -623,9 +623,13 @@ bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests bool Configuration::udpWindowToFront () const {return m_->udpWindowToFront_;} bool Configuration::udpWindowRestore () const {return m_->udpWindowRestore_;} Bands * Configuration::bands () {return &m_->bands_;} +Bands const * Configuration::bands () const {return &m_->bands_;} StationList * Configuration::stations () {return &m_->stations_;} +StationList const * Configuration::stations () const {return &m_->stations_;} FrequencyList * Configuration::frequencies () {return &m_->frequencies_;} +FrequencyList const * Configuration::frequencies () const {return &m_->frequencies_;} QStringListModel * Configuration::macros () {return &m_->macros_;} +QStringListModel const * Configuration::macros () const {return &m_->macros_;} QDir Configuration::save_directory () const {return m_->save_directory_;} QString Configuration::rig_name () const {return m_->rig_params_.rig_name;} @@ -2155,7 +2159,7 @@ void Configuration::impl::transceiver_frequency (Frequency f) void Configuration::impl::transceiver_tx_frequency (Frequency f) { - if (/* set_mode () || */ cached_rig_state_.tx_frequency () != f || cached_rig_state_.split () != !!f) + if (/* set_mode () || */ cached_rig_state_.tx_frequency () != f || !cached_rig_state_.compare_split (!!f)) { cached_rig_state_.split (f); cached_rig_state_.tx_frequency (f); @@ -2259,8 +2263,14 @@ void Configuration::impl::handle_transceiver_update (TransceiverState state) setup_split_ = true; required_tx_frequency_ = 0; - // Q_EMIT self_->transceiver_failure (tr ("Rig split mode setting not consistent with WSJT-X settings. Changing WSJT-X settings for you.")); - Q_EMIT self_->transceiver_failure (tr ("Rig split mode setting not consistent with WSJT-X settings.")); + // Q_EMIT self_->transceiver_failure (tr ("Rig + // split mode setting not consistent with WSJT-X + // settings. Changing WSJT-X settings for + // you.")); + if (cached_rig_state_.split () != state.split ()) + { + Q_EMIT self_->transceiver_failure (tr ("Rig split mode setting not consistent with WSJT-X settings.")); + } } } } @@ -2269,7 +2279,9 @@ void Configuration::impl::handle_transceiver_update (TransceiverState state) // One time rig setup split if (setup_split_ && cached_rig_state_.split () != state.split ()) { - Q_EMIT tx_frequency (TransceiverFactory::split_mode_none != split_mode_selected ? (required_tx_frequency_ ? required_tx_frequency_ : state.tx_frequency ()) : 0, true); + Q_EMIT tx_frequency (TransceiverFactory::split_mode_none != split_mode_selected && cached_rig_state_.split () + ? (required_tx_frequency_ ? required_tx_frequency_ : state.tx_frequency ()) + : 0, true); } setup_split_ = false; required_tx_frequency_ = 0; diff --git a/Configuration.hpp b/Configuration.hpp index e79cd627e..001d1947b 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -117,9 +117,13 @@ public: bool udpWindowToFront () const; bool udpWindowRestore () const; Bands * bands (); + Bands const * bands () const; FrequencyList * frequencies (); + FrequencyList const * frequencies () const; StationList * stations (); + StationList const * stations () const; QStringListModel * macros (); + QStringListModel const * macros () const; QDir save_directory () const; QString rig_name () const; unsigned jt9w_bw_mult () const; diff --git a/Transceiver.hpp b/Transceiver.hpp index 00548440a..983d596d6 100644 --- a/Transceiver.hpp +++ b/Transceiver.hpp @@ -89,6 +89,7 @@ public: Frequency frequency () const {return frequency_[0];} Frequency tx_frequency () const {return frequency_[1];} bool split () const {return on == split_;} + bool compare_split (bool with) const {return split_ == (with ? on : off);} MODE mode () const {return mode_;} bool ptt () const {return ptt_;} diff --git a/WSPRBandHopping.cpp b/WSPRBandHopping.cpp new file mode 100644 index 000000000..754c7fcba --- /dev/null +++ b/WSPRBandHopping.cpp @@ -0,0 +1,301 @@ +#include "WSPRBandHopping.hpp" + +#include +#include +#include +#include + +#include "SettingsGroup.hpp" +#include "Configuration.hpp" +#include "FrequencyList.hpp" +#include "pimpl_impl.hpp" +#include "moc_WSPRBandHopping.cpp" + +extern "C" +{ +#ifndef CMAKE_BUILD +#define FC_hopping hopping_ +#else +#include "FC.h" + void FC_hopping (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid + , int const * nduration, int const * npctx, int * isun, int * iband + , int * ntxnext, int my_grid_len); +#endif +}; + +namespace +{ + // These 10 bands are the hopping candidates and are globally coordinated + 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"}; + size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])}; + char const * const title = "WSPR Band Hopping"; +} + +// +// Dialog - maintenance of band hopping options +// +class Dialog + : public QDialog +{ +public: + Dialog (QSettings *, QBitArray * bands, int * gray_line_duration, QWidget * parent = nullptr); + ~Dialog (); + + void resize_to_maximum (); + +private: + void closeEvent (QCloseEvent *) override; + void save_window_state (); + + QSettings * settings_; + QBitArray * bands_; + int * gray_line_duration_; + QPointer bands_table_; + QPointer gray_line_width_spin_box_; +}; + +Dialog::Dialog (QSettings * settings, QBitArray * bands, int * gray_line_duration, QWidget * parent) + : QDialog {parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint} + , settings_ {settings} + , bands_ {bands} + , gray_line_duration_ {gray_line_duration} + , bands_table_ {new QTableWidget {num_periods, num_bands, this}} + , gray_line_width_spin_box_ {new QSpinBox {this}} +{ + QVBoxLayout * main_layout {new QVBoxLayout}; + + // set up and load the table of check boxes + bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff); + bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff); + 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); + 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_); + // handle changes by updating the underlying flags + connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) { + bands_[item->row ()].setBit (item->column (), Qt::Checked == item->checkState ()); + }); + + // 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 (&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); + setWindowTitle (windowTitle () + ' ' + tr (title)); + { + SettingsGroup g {settings_, title}; + restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ()); + } +} + +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 ()); +} + +// 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 () +{ + int width {bands_table_->verticalHeader ()->width ()}; + int height {bands_table_->horizontalHeader ()->height ()}; + for (auto i = 0; i < bands_table_->columnCount (); ++i) + { + width += bands_table_->columnWidth (i); + } + for (auto i = 0; i < bands_table_->rowCount (); ++i) + { + height += bands_table_->rowHeight (i); + } + bands_table_->setMinimumSize ({width, height}); +} + +class WSPRBandHopping::impl +{ +public: + impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) + : settings_ {settings} + , configuration_ {configuration} + , tx_percent_ {0} + , parent_widget_ {parent_widget} + , bands_ {QBitArray {num_bands}, QBitArray {num_bands}, QBitArray {num_bands}, QBitArray {num_bands}, QBitArray {num_bands}} + { + } + + QSettings * settings_; + Configuration const * configuration_; + int tx_percent_; + 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_; +}; + +WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget) + : m_ {settings, configuration, parent_widget} +{ + // load settings + SettingsGroup g {m_->settings_, title}; + auto size = m_->settings_->beginReadArray ("periods"); + for (auto i = 0; i < size; ++i) + { + m_->settings_->setArrayIndex (i); + m_->bands_[i] = m_->settings_->value ("bands").toBitArray (); + } + m_->settings_->endArray (); + m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt (); +} + +WSPRBandHopping::~WSPRBandHopping () +{ + // save settings + SettingsGroup g {m_->settings_, title}; + m_->settings_->beginWriteArray ("periods"); + 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_) + { + m_->dialog_ = new Dialog {m_->settings_, m_->bands_, &m_->gray_line_duration_, m_->parent_widget_}; + } + 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_->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 +auto WSPRBandHopping::next_hop () -> Hop +{ + 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 + + // look up band for this period + FC_hopping (&year, &month, &day, &uth, my_grid.toLatin1 ().constData () + , &m_->gray_line_duration_, &m_->tx_percent_, &period_index, &band_index + , &tx_next, my_grid.size ()); + + int frequencies_index {-1}; + auto const& frequencies = m_->configuration_->frequencies (); + auto const& filtered_bands = frequencies->filtered_bands (); + if (m_->bands_[period_index].testBit (band_index) + && filtered_bands.contains (hopping_bands[band_index])) + { + // here we have a band that has been enabled in the hopping + // matrix so check it it has a configured working frequency + frequencies_index = frequencies->best_working_frequency (hopping_bands[band_index]); + 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 + // for a random selection from the other enabled bands in the + // hopping matrix + if (frequencies_index < 0) + { + for (auto i = 0u; i < num_bands; ++i) + { + int new_index = static_cast (qrand () % num_bands); // random choice + if (new_index != band_index && m_->bands_[period_index].testBit (new_index)) + { + // here we have a random choice that is enabled in the + // 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) + { + // we can use the random choice + qDebug () << "random:" << hopping_bands[new_index] << "frequency:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList::frequency_column)).toString (); + band_index = new_index; + break; + } + } + } + } + return {periods[period_index] + , frequencies_index + , frequencies_index >= 0 && m_->bands_[4].testBit (band_index) + , frequencies_index >= 0 && !!tx_next}; +} diff --git a/WSPRBandHopping.hpp b/WSPRBandHopping.hpp new file mode 100644 index 000000000..70fcc06c2 --- /dev/null +++ b/WSPRBandHopping.hpp @@ -0,0 +1,80 @@ +#ifndef WSPR_BAND_HOPPING_HPP__ +#define WSPR_BAND_HOPPING_HPP__ + +#include + +#include "pimpl_h.hpp" + +class QSettings; +class Configuration; +class QWidget; + +// +// WSPR Band Hopping Control +// +// WSPR specifies a globally coordinated band hopping schedule and +// this class implements that. +// +// Responsibilities +// +// Provides a maintenance dialog allowing the user to define which +// bands are allowed from the band hopping schedule as defined here: +// +// http://physics.princeton.edu/pulsar/K1JT/doc/wspr/wspr-main.html +// +// Along with selecting bands a flag indicating that a short tune up +// signal is required for specified bands. +// +// Provides a Qt property that holds the Tx percentage which is used +// to generate a semi-randomized schedule of period to transmit. This +// schedule is random but adjusted to limit the number of consecutive +// transmission periods, it also adjusts the schedule to ensure that +// the overall number of transmission periods in any two hour hopping +// schedule reflects the percentage provided. +// +// Collaborations +// +// Settings including the selected bands with periods, the tune up +// flags and the gray line duration are maintained in persistent +// storage using the provided QSettings object instance. +// +// A passed in Configuration object instance is used to query the +// FrequencyList model to determine working frequencies for each +// band. The row index of this model is returned by this classes +// hopping scheduling method so it may be conveniently used to select +// a new working frequency by a client. +// +class WSPRBandHopping + : public QObject +{ + Q_OBJECT; + Q_PROPERTY (int tx_percent READ tx_percent WRITE set_tx_percent); + +public: + WSPRBandHopping (QSettings *, Configuration const *, QWidget * parent = nullptr); + ~WSPRBandHopping (); + + // display the band hopping maintenance dialog + Q_SLOT void show_dialog (bool); + + // Property tx_percent implementation + int tx_percent () const; + Q_SLOT void set_tx_percent (int); + + // structure that defines the results of the next_hop() method + struct Hop + { + QString period_name_; + int frequencies_index_; // may be -1 indicating no change + bool tune_required_; + bool tx_next_; + }; + // return the next band parameters + Hop next_hop (); + +private: + class impl; + pimpl m_; +}; + +#endif diff --git a/mainwindow.cpp b/mainwindow.cpp index e0af94caa..19b60d0fa 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -52,9 +52,6 @@ namespace Radio::Frequency constexpr default_frequency {14076000}; QRegExp message_alphabet {"[- @A-Za-z0-9+./?#]*"}; - // These 10 bands are the hopping candidates and are globally coordinated - QStringList const hopping_bands = {"160m","80m","60m","40m","30m","20m","17m","15m","12m","10m"}; - bool message_is_73 (int type, QStringList const& msg_parts) { return type >= 0 @@ -70,19 +67,22 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme m_dataDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}, m_revision {revision ()}, m_multiple {multiple}, - m_settings (settings), + m_settings {settings}, ui(new Ui::MainWindow), - m_config (settings, this), + m_config {settings, this}, + m_WSPR_band_hopping {settings, &m_config, this}, m_wideGraph (new WideGraph (settings)), m_logDlg (new LogQSO (program_title (), settings, this)), m_dialFreq {std::numeric_limits::max ()}, m_detector (RX_SAMPLE_RATE, NTMAX, 6912 / 2, downSampleFactor), m_modulator (TX_SAMPLE_RATE, NTMAX), m_audioThread {new QThread}, + m_pctx {0}, m_diskData {false}, m_sentFirst73 {false}, m_currentMessageType {-1}, m_lastMessageType {-1}, + m_nonWSPRTab {-1}, m_appDir {QApplication::applicationDirPath ()}, mem_jt9 {shdmem}, m_msAudioOutputBuffered (0u), @@ -202,6 +202,12 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme } }); + // Hook up WSPR band hopping + connect (ui->band_hopping_schedule_push_button, &QPushButton::clicked + , &m_WSPR_band_hopping, &WSPRBandHopping::show_dialog); + connect (ui->sbTxPercent, static_cast (&QSpinBox::valueChanged) + , &m_WSPR_band_hopping, &WSPRBandHopping::set_tx_percent); + on_EraseButton_clicked (); QActionGroup* modeGroup = new QActionGroup(this); @@ -393,7 +399,6 @@ MainWindow::MainWindow(bool multiple, QSettings * settings, QSharedMemory *shdme m_bShMsgs=false; m_bDopplerTracking0=false; m_uploading=false; - m_hopTest=false; m_bTxTime=false; m_rxDone=false; m_bHaveTransmitted=false; @@ -617,13 +622,7 @@ void MainWindow::writeSettings() m_settings->setValue("PctTx",m_pctx); m_settings->setValue("dBm",m_dBm); m_settings->setValue("UploadSpots",m_uploadSpots); - m_settings->setValue("BandHopping",m_bandHopping); - m_settings->setValue("SunriseBands",ui->sunriseBands->text()); - m_settings->setValue("DayBands",ui->dayBands->text()); - m_settings->setValue("SunsetBands",ui->sunsetBands->text()); - m_settings->setValue("NightBands",ui->nightBands->text()); - m_settings->setValue("TuneBands",ui->tuneBands->text()); - m_settings->setValue("GrayLineDuration",ui->graylineDuration->text()); + m_settings->setValue ("BandHopping", ui->band_hopping_group_box->isChecked ()); m_settings->endGroup(); } @@ -691,8 +690,7 @@ void MainWindow::readSettings() m_uploadSpots=m_settings->value("UploadSpots",false).toBool(); ui->cbUploadWSPR_Spots->setChecked(m_uploadSpots); if(!m_uploadSpots) ui->cbUploadWSPR_Spots->setStyleSheet("QCheckBox{background-color: yellow}"); - m_bandHopping=m_settings->value("BandHopping",false).toBool(); - ui->cbBandHop->setChecked(m_bandHopping); + ui->band_hopping_group_box->setChecked (m_settings->value ("BandHopping", false).toBool()); // setup initial value of tx attenuator ui->outAttenuation->setValue (m_settings->value ("OutAttenuation", 0).toInt ()); on_outAttenuation_valueChanged (ui->outAttenuation->value ()); @@ -703,18 +701,6 @@ void MainWindow::readSettings() outBufSize=m_settings->value("OutBufSize",4096).toInt(); m_lockTxFreq=m_settings->value("LockTxFreq",false).toBool(); ui->cbTxLock->setChecked(m_lockTxFreq); - ui->sunriseBands->setText(m_settings->value("SunriseBands","").toString()); - on_sunriseBands_editingFinished(); - ui->dayBands->setText(m_settings->value("DayBands","").toString()); - on_dayBands_editingFinished(); - ui->sunsetBands->setText(m_settings->value("SunsetBands","").toString()); - on_sunsetBands_editingFinished(); - ui->nightBands->setText(m_settings->value("NightBands","").toString()); - on_nightBands_editingFinished(); - ui->tuneBands->setText(m_settings->value("TuneBands","").toString()); - on_tuneBands_editingFinished(); - ui->graylineDuration->setText(m_settings->value("GrayLineDuration","").toString()); - on_graylineDuration_editingFinished(); m_settings->endGroup(); // use these initialisation settings to tune the audio o/p buffer @@ -1896,7 +1882,7 @@ void MainWindow::guiUpdate() m_btxok=false; } if(m_ntr==1) { -// if(m_bandHopping) { +// if(ui->band_hopping_group_box->isChecked ()) { // qDebug() << "Call bandHopping after Rx" << m_nseq << m_ntr << m_nrx << m_rxDone; bandHopping(); // } @@ -2264,7 +2250,7 @@ void MainWindow::stopTx2() } if(m_mode.mid(0,4)=="WSPR" and m_ntr==-1 and !m_tuneup) { m_wideGraph->setWSPRtransmitted(); -// if(m_bandHopping) { +// if(ui->band_hopping_group_box->isChecked ()) { // qDebug () << "Call bandHopping after Tx" << m_tuneup; bandHopping(); // } @@ -3212,9 +3198,12 @@ void MainWindow::WSPR_config(bool b) ui->decodedTextLabel->setText( "UTC dB DT Freq Drift Call Grid dBm Dist"); auto_tx_label->setText(""); + ui->tabWidget->setCurrentIndex (2); + Q_EMIT m_config.transceiver_tx_frequency (0); // turn off split } else { ui->decodedTextLabel->setText("UTC dB DT Freq Message"); auto_tx_label->setText (m_config.quick_call () ? "Tx-Enable Armed" : "Tx-Enable Disarmed"); + ui->tabWidget->setCurrentIndex (m_nonWSPRTab >= 0 ? m_nonWSPRTab : 1); } } @@ -3572,18 +3561,21 @@ void MainWindow::on_pbTxMode_clicked() void MainWindow::setXIT(int n) { m_XIT = 0; - if (m_config.split_mode () and (m_mode != "JT4")) //Don't use XIT in JT4 mode + if (m_mode != "WSPR-2" && m_mode != "WSPR-15") // Don't use split in WSPR { - m_XIT=(n/500)*500 - 1500; - } - - if (m_monitoring or m_transmitting) - { - if (m_config.transceiver_online ()) + if (m_config.split_mode () && m_mode != "JT4") // Don't use XIT in JT4 { - if (m_config.split_mode ()) + m_XIT=(n/500)*500 - 1500; + } + + if (m_monitoring || m_transmitting) + { + if (m_config.transceiver_online ()) { - Q_EMIT m_config.transceiver_tx_frequency (m_dialFreq + m_XIT); + if (m_config.split_mode ()) + { + Q_EMIT m_config.transceiver_tx_frequency (m_dialFreq + m_XIT); + } } } } @@ -4258,118 +4250,39 @@ void MainWindow::on_pbTxNext_clicked(bool b) m_txNext=b; } -void MainWindow::on_cbBandHop_toggled(bool b) -{ - m_bandHopping=b; -} - void MainWindow::bandHopping() { - QDateTime t = QDateTime::currentDateTimeUtc(); - QString date = t.date().toString("yyyy MMM dd").trimmed(); - QString utc = t.time().toString().trimmed(); - int nyear=t.date().year(); - int month=t.date().month(); - int nday=t.date().day(); - int nhr=t.time().hour(); - int nmin=t.time().minute(); - float sec=t.time().second() + 0.001*t.time().msec(); - float uth=nhr + nmin/60.0 + sec/3600.0; - int isun; - int iband0; - int ntxnext; + auto hop_data = m_WSPR_band_hopping.next_hop (); - static int icall=0; - if(m_hopTest) uth+= 2.0*icall/60.0; - icall++; - -// Find grayline status, isun: 0=Sunrise, 1=Day, 2=Sunset, 3=Night - hopping_(&nyear, &month, &nday, &uth, - m_config.my_grid ().toLatin1().constData(), - &m_grayDuration, &m_pctx, &isun, &iband0, &ntxnext, 6); - - - if(m_auto and ntxnext==1) { - m_nrx=0; + if (m_auto &&hop_data.tx_next_) { + m_nrx = 0; } else { - m_nrx=1; + m_nrx = 1; } - if( m_bandHopping ) { - QStringList s; - if(isun==0) s=m_sunriseBands; - if(isun==1) s=m_dayBands; - if(isun==2) s=m_sunsetBands; - if(isun==3) s=m_nightBands; + if (ui->band_hopping_group_box->isChecked ()) { + QThread::msleep(500); //### Is this OK to do ??? ### - // allow numeric only band names - for (auto& item : s) { - if (!item.endsWith ('m')) { - item += 'm'; - } - } + if (hop_data.frequencies_index_ >= 0) { // new band + ui->bandComboBox->setCurrentIndex (hop_data.frequencies_index_); + on_bandComboBox_activated (hop_data.frequencies_index_); - QString new_band; - if (s.contains (hopping_bands[iband0])) { //See if designated band is active - new_band = hopping_bands[iband0]; - } - else { - // If designated band is not active, choose one that is active - // and in the hopping list - for (auto i = 0; i < s.size (); ++i) { // arbitrary number of iterations - auto const& bname = s[qrand() % s.size ()]; // pick a random band - if (bname != m_band00 && hopping_bands.contains (bname)) { - new_band = bname; - break; + auto const& band_name = m_config.bands ()->find (m_dialFreq).remove ('m'); + m_cmnd.clear (); + QStringList prefixes {".bat", ".cmd", ".exe", ""}; + for (auto const& prefix : prefixes) + { + auto const& path = m_appDir + "/user_hardware" + prefix; + QFile f {path}; + if (f.exists ()) { + m_cmnd = QDir::toNativeSeparators (f.fileName ()) + ' ' + band_name; + } } - } - } - qDebug () << "bandHopping: m_band00:" << m_band00 << "new candidate band:" << new_band; - - QThread::msleep(500); //### Is this OK to do ??? ### - - // qDebug() << nhr << nmin << int(sec) << m_band00 << f0 << 0.000001*f0; - - auto const& row = m_config.frequencies ()->best_working_frequency (new_band); - if (row >= 0) { // band is configured - m_band00 = new_band; - ui->bandComboBox->setCurrentIndex (row); - on_bandComboBox_activated (row); - - m_cmnd=""; - QFile f1 {m_appDir + "/user_hardware.bat"}; - if(f1.exists()) { - m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.bat ") + m_band00; - } - QFile f2 {m_appDir + "/user_hardware.cmd"}; - if(f2.exists()) { - m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.cmd ") + m_band00; - } - QFile f3 {m_appDir + "/user_hardware.exe"}; - if(f3.exists()) { - m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware.exe ") + m_band00; - } - QFile f4 {m_appDir + "/user_hardware"}; - if(f4.exists()) { - m_cmnd=QDir::toNativeSeparators (m_appDir + "/user_hardware ") + m_band00; - } - - int n=m_cmnd.length(); - if(m_cmnd.mid(n-1,1)=="m") { - m_cmnd=m_cmnd.mid(0,n-1); //### Temporary? ### Strip trailimg "m" - } if(m_cmnd!="") p3.start(m_cmnd); // Execute user's hardware controller // Produce a short tuneup signal m_tuneup = false; - auto tu_bands = m_tuneBands; - // allow numeric only band names - for (auto& item : tu_bands) { - if (!item.endsWith ('m')) { - item += 'm'; - } - } - if (tu_bands.contains (m_band00)) { + if (hop_data.tune_required_) { m_tuneup = true; on_tuneButton_clicked (true); tuneATU_Timer->start (2500); @@ -4377,49 +4290,13 @@ void MainWindow::bandHopping() } // Display grayline status - QString dailySequence[4]={"Sunrise grayline","Day","Sunset grayline","Night"}; - auto_tx_label->setText(dailySequence[isun]); + auto_tx_label->setText (hop_data.period_name_); } } -void MainWindow::on_pushButton_clicked() +void MainWindow::on_tabWidget_currentChanged (int new_value) { - qDebug() << "A" << m_config.data_dir(); - qDebug() << "B" << m_config.data_dir().absolutePath(); - qDebug() << "C" << m_config.data_dir().absoluteFilePath("JPLEPH"); -/* - m_hopTest=true; - bandHopping(); - m_hopTest=false; -*/ -} - -void MainWindow::on_sunriseBands_editingFinished() -{ - m_sunriseBands=ui->sunriseBands->text().split(" ", QString::SkipEmptyParts); -} - -void MainWindow::on_dayBands_editingFinished() -{ - m_dayBands=ui->dayBands->text().split(" ", QString::SkipEmptyParts); -} - -void MainWindow::on_sunsetBands_editingFinished() -{ - m_sunsetBands=ui->sunsetBands->text().split(" ", QString::SkipEmptyParts); -} - -void MainWindow::on_nightBands_editingFinished() -{ - m_nightBands=ui->nightBands->text().split(" ", QString::SkipEmptyParts); -} - -void MainWindow::on_tuneBands_editingFinished() -{ - m_tuneBands=ui->tuneBands->text().split(" ", QString::SkipEmptyParts); -} - -void MainWindow::on_graylineDuration_editingFinished() -{ - m_grayDuration=ui->graylineDuration->text().toInt(); + if (2 != new_value) { // WSPR + m_nonWSPRTab = new_value; + } } diff --git a/mainwindow.h b/mainwindow.h index 74bf5d99b..a62d42a5c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -16,6 +16,7 @@ #include #include #include +#include #include "soundin.h" #include "AudioDevice.hpp" @@ -24,6 +25,7 @@ #include "Radio.hpp" #include "Modes.hpp" #include "Configuration.hpp" +#include "WSPRBandHopping.hpp" #include "Transceiver.hpp" #include "psk_reporter.h" #include "signalmeter.h" @@ -59,6 +61,7 @@ class Astro; class MessageAveraging; class MessageClient; class QTime; +class WSPRBandHopping; class MainWindow : public QMainWindow { @@ -225,14 +228,7 @@ private slots: void p3Error(QProcess::ProcessError e); void on_WSPRfreqSpinBox_valueChanged(int n); void on_pbTxNext_clicked(bool b); - void on_cbBandHop_toggled(bool b); - void on_sunriseBands_editingFinished(); - void on_pushButton_clicked(); - void on_dayBands_editingFinished(); - void on_sunsetBands_editingFinished(); - void on_nightBands_editingFinished(); - void on_tuneBands_editingFinished(); - void on_graylineDuration_editingFinished(); + void on_tabWidget_currentChanged (int); private: Q_SIGNAL void initializeAudioOutputStream (QAudioDeviceInfo, @@ -266,6 +262,7 @@ private: // other windows Configuration m_config; + WSPRBandHopping m_WSPR_band_hopping; QMessageBox m_rigErrorMessageBox; QScopedPointer m_wideGraph; @@ -327,8 +324,6 @@ private: qint32 m_dBm; qint32 m_pctx; qint32 m_nseq; - qint32 m_grayDuration; - QString m_band00; bool m_btxok; //True if OK to transmit bool m_diskData; @@ -379,12 +374,11 @@ private: bool m_uploading; bool m_txNext; bool m_grid6; - bool m_bandHopping; - bool m_hopTest; bool m_tuneup; bool m_bTxTime; bool m_rxDone; - bool m_bHaveTransmitted; //Can be used to prohibit consecutive WSPR transmittions + bool m_bHaveTransmitted; //Can be used to prohibit consecutive WSPR transmissions + int m_nonWSPRTab; float m_pctZap; @@ -549,10 +543,6 @@ extern "C" { void wspr_downsample_(short int d2[], int* k); void savec2_(char* fname, int* m_TRseconds, double* m_dialFreq, int len1); - - void hopping_(int* nyear, int* month, int* nday, float* uth, char const * MyGrid, - int* nduration, int* npctx, int* isun, int* iband, - int* ntxnext, int len); } #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 776ef0a84..99f7d4663 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -2237,11 +2237,37 @@ list. The list can be maintained in Settings (F2). 30 22 - 200 - 171 + 221 + 161 + + + + Band Hopping + + + true + + + + + + Schedule ... + + + + + + + + + + Upload spots + + + @@ -2263,6 +2289,27 @@ list. The list can be maintained in Settings (F2). + + + + QPushButton:checked { + background-color: red; + border-style: outset; + border-width: 1px; + border-radius: 5px; + border-color: black; + min-width: 5em; + padding: 3px; +} + + + Tx Next + + + true + + + @@ -2285,12 +2332,21 @@ list. The list can be maintained in Settings (F2). - - - - Upload spots + + + + + + + Qt::Horizontal - + + + 5 + 17 + + + @@ -2311,275 +2367,6 @@ list. The list can be maintained in Settings (F2). - - - - Qt::Horizontal - - - - 5 - 17 - - - - - - - - true - - - Band hopping - - - - - - - QPushButton:checked { - background-color: red; - border-style: outset; - border-width: 1px; - border-radius: 5px; - border-color: black; - min-width: 5em; - padding: 3px; -} - - - Tx Next - - - true - - - - - - - - - - - - 4 - - - - - 100 - 190 - 75 - 23 - - - - Test - - - - - - 30 - 30 - 211 - 152 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Sunrise: - - - - - - - - 160 - 16777215 - - - - 160 80 40 30 20 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Day: - - - - - - - - 160 - 16777215 - - - - 30 20 17 15 12 10 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Sunset: - - - - - - - - 160 - 16777215 - - - - 160 80 40 30 20 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Night: - - - - - - - - 160 - 16777215 - - - - 160 80 40 30 20 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Tune: - - - - - - - - 160 - 16777215 - - - - 80 40 30 20 17 15 12 10 - - - - - - - - 55 - 0 - - - - - 60 - 16777215 - - - - Gray time: - - - - - - - - 160 - 16777215 - - - - 60 - - -