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 - - -