diff --git a/CMakeLists.txt b/CMakeLists.txt index 544a8650e..dfea6bc9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -260,6 +260,8 @@ set (wsjt_qt_CXXSRCS CallsignValidator.cpp SplashScreen.cpp EqualizationToolsDialog.cpp + DoubleClickablePushButton.cpp + DoubleClickableRadioButton.cpp ) set (wsjt_qtmm_CXXSRCS @@ -376,6 +378,7 @@ set (wsjt_FSRCS lib/fsk4hf/chkcrc10.f90 lib/fsk4hf/chkcrc12.f90 lib/fsk4hf/chkcrc12a.f90 + lib/chkcall.f90 lib/chkhist.f90 lib/chkmsg.f90 lib/chkss2.f90 @@ -433,6 +436,7 @@ set (wsjt_FSRCS lib/fqso_first.f90 lib/freqcal.f90 lib/fsk4hf/fsk4hf.f90 + lib/fsk4hf/ft8apset.f90 lib/fsk4hf/ft8b.f90 lib/fsk4hf/ft8_downsample.f90 lib/fsk4hf/ft8sim.f90 @@ -473,6 +477,7 @@ set (wsjt_FSRCS lib/iscat.f90 lib/jplsubs.f lib/jt9fano.f90 + lib/jtmsg.f90 lib/ldpcsim144.f90 lib/fsk4hf/ldpcsim120.f90 lib/fsk4hf/ldpcsim168.f90 diff --git a/Configuration.cpp b/Configuration.cpp index 03400aea6..d60b62286 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -603,6 +603,7 @@ QDir Configuration::data_dir () const {return m_->data_dir_;} QDir Configuration::writeable_data_dir () const {return m_->writeable_data_dir_;} QDir Configuration::temp_dir () const {return m_->temp_dir_;} +void Configuration::select_tab (int index) {m_->ui_->configuration_tabs->setCurrentIndex (index);} int Configuration::exec () {return m_->exec ();} bool Configuration::is_active () const {return m_->isVisible ();} diff --git a/Configuration.hpp b/Configuration.hpp index 924ebab82..bdeae5fb8 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -73,6 +73,7 @@ public: QWidget * parent = nullptr); ~Configuration (); + void select_tab (int); int exec (); bool is_active () const; diff --git a/Configuration.ui b/Configuration.ui index 03e3c4c3d..dfcb6b110 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -6,7 +6,7 @@ 0 0 - 521 + 536 507 @@ -188,6 +188,12 @@ Qt::Horizontal + + + 0 + 0 + + @@ -237,6 +243,12 @@ Qt::Vertical + + + 0 + 0 + + @@ -301,6 +313,12 @@ Qt::Horizontal + + + 0 + 0 + + @@ -390,6 +408,12 @@ quiet period when decoding is done. Qt::Horizontal + + + 0 + 0 + + @@ -1882,6 +1906,12 @@ for assessing propagation and system performance. Qt::Horizontal + + + 0 + 0 + + @@ -2178,6 +2208,12 @@ Right click for insert and delete options. Qt::Horizontal + + + 0 + 0 + + @@ -2198,6 +2234,12 @@ Right click for insert and delete options. Qt::Horizontal + + + 0 + 0 + + @@ -2300,7 +2342,7 @@ Right click for insert and delete options. <html><head/><body><p>Exchange 4-character grid locators instead of reports. See User Guide for details.</p></body></html> - MSK144 Contest Mode + FT8 and MSK144 Contest Mode @@ -2595,9 +2637,9 @@ soundcard changes - - + + diff --git a/DoubleClickablePushButton.cpp b/DoubleClickablePushButton.cpp new file mode 100644 index 000000000..2c37a55e6 --- /dev/null +++ b/DoubleClickablePushButton.cpp @@ -0,0 +1,14 @@ +#include "DoubleClickablePushButton.hpp" + +#include "moc_DoubleClickablePushButton.cpp" + +DoubleClickablePushButton::DoubleClickablePushButton (QWidget * parent) + : QPushButton {parent} +{ +} + +void DoubleClickablePushButton::mouseDoubleClickEvent (QMouseEvent * event) +{ + Q_EMIT doubleClicked (); + QPushButton::mouseDoubleClickEvent (event); +} diff --git a/DoubleClickablePushButton.hpp b/DoubleClickablePushButton.hpp new file mode 100644 index 000000000..ebc1ab84f --- /dev/null +++ b/DoubleClickablePushButton.hpp @@ -0,0 +1,27 @@ +#ifndef DOUBLE_CLICKABLE_PUSH_BUTTON_HPP_ +#define DOUBLE_CLICKABLE_PUSH_BUTTON_HPP_ + +#include + +// +// DoubleClickablePushButton - QPushButton that emits a mouse double +// click signal +// +// Clients should be aware of the QWidget::mouseDoubleClickEvent() +// notes about receipt of mouse press and mouse release events. +// +class DoubleClickablePushButton + : public QPushButton +{ + Q_OBJECT + +public: + DoubleClickablePushButton (QWidget * = nullptr); + + Q_SIGNAL void doubleClicked (); + +protected: + void mouseDoubleClickEvent (QMouseEvent *) override; +}; + +#endif diff --git a/DoubleClickableRadioButton.cpp b/DoubleClickableRadioButton.cpp new file mode 100644 index 000000000..892b3c295 --- /dev/null +++ b/DoubleClickableRadioButton.cpp @@ -0,0 +1,14 @@ +#include "DoubleClickableRadioButton.hpp" + +#include "moc_DoubleClickableRadioButton.cpp" + +DoubleClickableRadioButton::DoubleClickableRadioButton (QWidget * parent) + : QRadioButton {parent} +{ +} + +void DoubleClickableRadioButton::mouseDoubleClickEvent (QMouseEvent * event) +{ + Q_EMIT doubleClicked (); + QRadioButton::mouseDoubleClickEvent (event); +} diff --git a/DoubleClickableRadioButton.hpp b/DoubleClickableRadioButton.hpp new file mode 100644 index 000000000..dec715ab7 --- /dev/null +++ b/DoubleClickableRadioButton.hpp @@ -0,0 +1,27 @@ +#ifndef DOUBLE_CLICKABLE_RADIO_BUTTON_HPP_ +#define DOUBLE_CLICKABLE_RADIO_BUTTON_HPP_ + +#include + +// +// DoubleClickableRadioButton - QRadioButton that emits a mouse double +// click signal +// +// Clients should be aware of the QWidget::mouseDoubleClickEvent() +// notes about receipt of mouse press and mouse release events. +// +class DoubleClickableRadioButton + : public QRadioButton +{ + Q_OBJECT + +public: + DoubleClickableRadioButton (QWidget * = nullptr); + + Q_SIGNAL void doubleClicked (); + +protected: + void mouseDoubleClickEvent (QMouseEvent *) override; +}; + +#endif diff --git a/MessageClient.cpp b/MessageClient.cpp index ea657d329..ac820f096 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -151,11 +151,13 @@ void MessageClient::impl::parse_message (QByteArray const& msg) quint32 delta_frequency; QByteArray mode; QByteArray message; - in >> time >> snr >> delta_time >> delta_frequency >> mode >> message; + bool low_confidence {false}; + in >> time >> snr >> delta_time >> delta_frequency >> mode >> message >> low_confidence; if (check_status (in) != Fail) { Q_EMIT self_->reply (time, snr, delta_time, delta_frequency - , QString::fromUtf8 (mode), QString::fromUtf8 (message)); + , QString::fromUtf8 (mode), QString::fromUtf8 (message) + , low_confidence); } } break; @@ -366,13 +368,14 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con } void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency - , QString const& mode, QString const& message_text) + , QString const& mode, QString const& message_text, bool low_confidence) { if (m_->server_port_ && !m_->server_string_.isEmpty ()) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_}; - out << is_new << time << snr << delta_time << delta_frequency << mode.toUtf8 () << message_text.toUtf8 (); + out << is_new << time << snr << delta_time << delta_frequency << mode.toUtf8 () + << message_text.toUtf8 () << low_confidence; m_->send_message (out, message); } } diff --git a/MessageClient.hpp b/MessageClient.hpp index 9807c4d2f..0d3dd47ff 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -53,7 +53,7 @@ public: , QString const& dx_grid, bool watchdog_timeout, QString const& sub_mode , bool fast_mode); Q_SLOT void decode (bool is_new, QTime time, qint32 snr, float delta_time, quint32 delta_frequency - , QString const& mode, QString const& message); + , QString const& mode, QString const& message, bool low_confidence); Q_SLOT void WSPR_decode (bool is_new, QTime time, qint32 snr, float delta_time, Frequency , qint32 drift, QString const& callsign, QString const& grid, qint32 power); Q_SLOT void clear_decodes (); @@ -70,7 +70,7 @@ public: // this signal is emitted if the server sends us a reply, the only // reply supported is reply to a prior CQ or QRZ message Q_SIGNAL void reply (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode - , QString const& message_text); + , QString const& message_text, bool low_confidence); // this signal is emitted if the server has requested a replay of // all decodes diff --git a/MessageServer.cpp b/MessageServer.cpp index e73203a40..caad76a9f 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -241,11 +241,14 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s quint32 delta_frequency; QByteArray mode; QByteArray message; - in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode >> message; + bool low_confidence; + in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode + >> message >> low_confidence; if (check_status (in) != Fail) { Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency - , QString::fromUtf8 (mode), QString::fromUtf8 (message)); + , QString::fromUtf8 (mode), QString::fromUtf8 (message) + , low_confidence); } } break; @@ -396,14 +399,15 @@ void MessageServer::start (port_type port, QHostAddress const& multicast_group_a } } -void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text) +void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text, bool low_confidence) { auto iter = m_->clients_.find (id); if (iter != std::end (m_->clients_)) { QByteArray message; NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_}; - out << time << snr << delta_time << delta_frequency << mode.toUtf8 () << message_text.toUtf8 (); + out << time << snr << delta_time << delta_frequency << mode.toUtf8 () + << message_text.toUtf8 () << low_confidence; m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); } } diff --git a/MessageServer.hpp b/MessageServer.hpp index 7338cbe4e..9a80aa0e7 100644 --- a/MessageServer.hpp +++ b/MessageServer.hpp @@ -46,7 +46,7 @@ public: // note that the client is not obliged to take any action and only // takes any action if the decode is present and is a CQ or QRZ message Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency - , QString const& mode, QString const& message); + , QString const& mode, QString const& message, bool low_confidence); // ask the client with identification 'id' to replay all decodes Q_SLOT void replay (QString const& id); @@ -69,7 +69,8 @@ public: , bool watchdog_timeout, QString const& sub_mode, bool fast_mode); Q_SIGNAL void client_closed (QString const& id); Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time - , quint32 delta_frequency, QString const& mode, QString const& message); + , quint32 delta_frequency, QString const& mode, QString const& message + , bool low_confidence); Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency , qint32 drift, QString const& callsign, QString const& grid, qint32 power); Q_SIGNAL void qso_logged (QString const& id, QDateTime timeOff, QString const& dx_call, QString const& dx_grid diff --git a/NetworkMessage.hpp b/NetworkMessage.hpp index 00664b126..b248e40de 100644 --- a/NetworkMessage.hpp +++ b/NetworkMessage.hpp @@ -156,13 +156,19 @@ * Delta frequency (Hz) quint32 * Mode utf8 * Message utf8 + * Low confidence bool * * The decode message is sent when a new decode is completed, in * this case the 'New' field is true. It is also used in response * to a "Replay" message where each old decode in the "Band * activity" window, that has not been erased, is sent in order - * as a one of these messages with the 'New' field set to - * false. See the "Replay" message below for details of usage. + * as a one of these messages with the 'New' field set to false. + * See the "Replay" message below for details of usage. Low + * confidence decodes are flagged in protocols where the decoder + * has knows that a decode has a higher than normal probability + * of being false, they should not be reported on publicly + * accessible services without some attached warning or further + * validation. * * * Clear Out 3 quint32 @@ -184,6 +190,7 @@ * Delta frequency (Hz) quint32 * Mode utf8 * Message utf8 + * Low confidence bool * * In order for a server to provide a useful cooperative service * to WSJT-X it is possible for it to initiate a QSO by sending diff --git a/UDPExamples/ClientWidget.cpp b/UDPExamples/ClientWidget.cpp index 3ff1a561f..d24c6d697 100644 --- a/UDPExamples/ClientWidget.cpp +++ b/UDPExamples/ClientWidget.cpp @@ -244,7 +244,7 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const& void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/ - , QString const& /*message*/) + , QString const& /*message*/, bool /*low_confidence*/) { if (client_id == id_) { diff --git a/UDPExamples/ClientWidget.hpp b/UDPExamples/ClientWidget.hpp index 1e7779c87..8643df705 100644 --- a/UDPExamples/ClientWidget.hpp +++ b/UDPExamples/ClientWidget.hpp @@ -33,7 +33,7 @@ public: , bool watchdog_timeout, QString const& sub_mode, bool fast_mode); Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr , float delta_time, quint32 delta_frequency, QString const& mode - , QString const& message); + , QString const& message, bool low_confidence); Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime, qint32 snr , float delta_time, Frequency delta_frequency, qint32 drift , QString const& callsign, QString const& grid, qint32 power); diff --git a/UDPExamples/DecodesModel.cpp b/UDPExamples/DecodesModel.cpp index 72ab4b61f..3ca94dde1 100644 --- a/UDPExamples/DecodesModel.cpp +++ b/UDPExamples/DecodesModel.cpp @@ -17,13 +17,19 @@ namespace QT_TRANSLATE_NOOP ("DecodesModel", "DF"), QT_TRANSLATE_NOOP ("DecodesModel", "Md"), QT_TRANSLATE_NOOP ("DecodesModel", "Message"), + QT_TRANSLATE_NOOP ("DecodesModel", "Confidence"), }; + QString confidence_string (bool low_confidence) + { + return low_confidence ? QT_TRANSLATE_NOOP ("DecodesModel", "low") : QT_TRANSLATE_NOOP ("DecodesModel", "high"); + } + QFont text_font {"Courier", 10}; QList make_row (QString const& client_id, QTime time, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode, QString const& message - , bool is_fast) + , bool low_confidence, bool is_fast) { auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")}; time_item->setData (time); @@ -44,8 +50,11 @@ namespace auto md = new QStandardItem {mode}; md->setTextAlignment (Qt::AlignHCenter); + auto confidence = new QStandardItem {confidence_string (low_confidence)}; + confidence->setTextAlignment (Qt::AlignHCenter); + QList row { - new QStandardItem {client_id}, time_item, snr_item, dt, df, md, new QStandardItem {message}}; + new QStandardItem {client_id}, time_item, snr_item, dt, df, md, new QStandardItem {message}, confidence}; Q_FOREACH (auto& item, row) { item->setEditable (false); @@ -57,7 +66,7 @@ namespace } DecodesModel::DecodesModel (QObject * parent) - : QStandardItemModel {0, 7, parent} + : QStandardItemModel {0, sizeof (headings) / sizeof (headings[0]), parent} { int column {0}; for (auto const& heading : headings) @@ -68,7 +77,7 @@ DecodesModel::DecodesModel (QObject * parent) void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode, QString const& message - , bool is_fast) + , bool low_confidence, bool is_fast) { if (!is_new) { @@ -83,7 +92,8 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time && item (row, 3)->data ().toFloat () == delta_time && item (row, 4)->data ().toUInt () == delta_frequency && data (index (row, 5)).toString () == mode - && data (index (row, 6)).toString () == message) + && data (index (row, 6)).toString () == message + && data (index (row, 7)).toString () == confidence_string (low_confidence)) { return; } @@ -96,12 +106,12 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time if (target_row >= 0) { insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode - , message, is_fast)); + , message, low_confidence, is_fast)); return; } } - appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, is_fast)); + appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, low_confidence, is_fast)); } void DecodesModel::clear_decodes (QString const& client_id) @@ -124,7 +134,8 @@ void DecodesModel::do_reply (QModelIndex const& source) , item (row, 3)->data ().toFloat () , item (row, 4)->data ().toInt () , data (index (row, 5)).toString () - , data (index (row, 6)).toString ()); + , data (index (row, 6)).toString () + , confidence_string (true) == data (index (row, 7)).toString ()); } #include "moc_DecodesModel.cpp" diff --git a/UDPExamples/DecodesModel.hpp b/UDPExamples/DecodesModel.hpp index 03eaab39f..43bfd679d 100644 --- a/UDPExamples/DecodesModel.hpp +++ b/UDPExamples/DecodesModel.hpp @@ -32,12 +32,13 @@ public: explicit DecodesModel (QObject * parent = nullptr); Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time - , quint32 delta_frequency, QString const& mode, QString const& message, bool is_fast); + , quint32 delta_frequency, QString const& mode, QString const& message + , bool low_confidence, bool is_fast); Q_SLOT void clear_decodes (QString const& client_id); Q_SLOT void do_reply (QModelIndex const& source); Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency - , QString const& mode, QString const& message); + , QString const& mode, QString const& message, bool low_confidence); }; #endif diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index 2cdb27015..ecb364141 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -91,9 +91,9 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time , qint32 snr, float delta_time , quint32 delta_frequency, QString const& mode - , QString const& message) { + , QString const& message, bool low_confidence) { decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message - , dock_widgets_[id]->fast_mode ());}); + , low_confidence, dock_widgets_[id]->fast_mode ());}); connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot); connect (server_, &MessageServer::clear_decodes, decodes_model_, &DecodesModel::clear_decodes); connect (server_, &MessageServer::clear_decodes, beacons_model_, &BeaconsModel::clear_decodes); diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 14a531bbf..7afc3d2f9 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -69,13 +69,13 @@ public: Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr , float delta_time, quint32 delta_frequency, QString const& mode - , QString const& message) + , QString const& message, bool low_confidence) { if (client_id == id_) { qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr << "Dt:" << delta_time << "Df:" << delta_frequency - << "mode:" << mode; + << "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high"); std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl; } } diff --git a/commons.h b/commons.h index fea9ed4ef..32b4c082e 100644 --- a/commons.h +++ b/commons.h @@ -26,7 +26,10 @@ extern struct dec_data { int nutc; //UTC as integer, HHMM bool ndiskdat; //true ==> data read from *.wav file int ntrperiod; //TR period (seconds) + int nQSOProgress; /* QSO state machine state */ int nfqso; //User-selected QSO freq (kHz) + int nftx; /* Transmit audio offset where + replies might be expected */ bool newdat; //true ==> new data, must do long FFT int npts8; //npts for c0() array int nfa; //Low decode limit (Hz) @@ -38,6 +41,8 @@ extern struct dec_data { int nsubmode; bool nagain; int ndepth; + bool lapon; + int napwid; int ntxmode; int nmode; int minw; diff --git a/decodedtext.cpp b/decodedtext.cpp index e71ace45b..e81ea1d39 100644 --- a/decodedtext.cpp +++ b/decodedtext.cpp @@ -3,6 +3,11 @@ #include #include + +extern "C" { + bool stdmsg_(const char* msg, int len); +} + QString DecodedText::CQersCall() { // extract the CQer's call TODO: does this work with all call formats? @@ -37,12 +42,12 @@ QString DecodedText::CQersCall() bool DecodedText::isJT65() { - return _string.indexOf("#") == column_mode; + return _string.indexOf("#") == column_mode + padding_; } bool DecodedText::isJT9() { - return _string.indexOf("@") == column_mode; + return _string.indexOf("@") == column_mode + padding_; } bool DecodedText::isTX() @@ -51,9 +56,14 @@ bool DecodedText::isTX() return (i >= 0 && i < 15); // TODO guessing those numbers. Does Tx ever move? } +bool DecodedText::isLowConfidence () +{ + return QChar {'?'} == _string.mid (padding_ + column_qsoText + 21, 1); +} + int DecodedText::frequencyOffset() { - return _string.mid(column_freq,4).toInt(); + return _string.mid(column_freq + padding_,4).toInt(); } int DecodedText::snr() @@ -64,7 +74,7 @@ int DecodedText::snr() float DecodedText::dt() { - return _string.mid(column_dt,5).toFloat(); + return _string.mid(column_dt + padding_,5).toFloat(); } /* @@ -79,7 +89,7 @@ float DecodedText::dt() // find and extract any report. Returns true if this is a standard message bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report) { - QString msg=_string.mid(column_qsoText).trimmed(); + QString msg=_string.mid(column_qsoText + padding_).trimmed(); if(msg.length() < 1) return false; msg = msg.left (22).remove (QRegularExpression {"[<>]"}); int i1=msg.indexOf('\r'); @@ -124,7 +134,7 @@ bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, / QString DecodedText::call() { auto call = _string; - call = call.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText); + call = call.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_); int i = call.indexOf(" "); return call.mid(0,i); } @@ -134,7 +144,7 @@ void DecodedText::deCallAndGrid(/*out*/QString& call, QString& grid) { auto msg = _string; if(msg.mid(4,1)!=" ") msg=msg.mid(0,4)+msg.mid(6,-1); //Remove seconds from UTC - msg = msg.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText); + msg = msg.replace (QRegularExpression {" CQ ([A-Z]{2,2}|[0-9]{3,3}) "}, " CQ_\\1 ").mid (column_qsoText + padding_); int i1 = msg.indexOf (" "); call = msg.mid (i1 + 1); int i2 = call.indexOf (" "); diff --git a/decodedtext.h b/decodedtext.h index bdc8c8862..952120932 100644 --- a/decodedtext.h +++ b/decodedtext.h @@ -15,79 +15,79 @@ /* 0123456789012345678901234567890123456789 -^ ^ ^ ^ ^ ^ -2343 -11 0.8 1259 # YV6BFE F6GUU R-08 -2343 -19 0.3 718 # VE6WQ SQ2NIJ -14 -2343 -7 0.3 815 # KK4DSD W7VP -16 -2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02 +^ ^ ^ ^ ^ ^ +2343 -11 0.8 1259 # YV6BFE F6GUU R-08 +2343 -19 0.3 718 # VE6WQ SQ2NIJ -14 +2343 -7 0.3 815 # KK4DSD W7VP -16 +2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02 -0605 Tx 1259 # CQ VK3ACF QF22 +0605 Tx 1259 # CQ VK3ACF QF22 */ class DecodedText { public: - // These define the columns in the decoded text where fields are to be found. - // We rely on these columns being the same in the fortran code (lib/decoder.f90) that formats the decoded text - enum Columns { column_time = 0, - column_snr = 5, - column_dt = 9, - column_freq = 14, - column_mode = 19, - column_qsoText = 22 }; + void operator=(const QString &rhs) + { + _string = rhs; + padding_ = _string.indexOf (" ") > 4 ? 2 : 0; // allow for seconds + }; + void operator=(const QByteArray &rhs) + { + _string = rhs; + padding_ = _string.indexOf (" ") > 4 ? 2 : 0; // allow for seconds + }; - void operator=(const QString &rhs) - { - _string = rhs; - }; - void operator=(const QByteArray &rhs) - { - _string = rhs; - }; + void operator+=(const QString &rhs) + { + _string += rhs; + }; - void operator+=(const QString &rhs) - { - _string += rhs; - }; + QString string() { return _string; }; - QString string() { return _string; }; + int indexOf(QString s) { return _string.indexOf(s); }; + int indexOf(QString s, int i) { return _string.indexOf(s,i); }; + QString mid(int f, int t) { return _string.mid(f,t); }; + QString left(int i) { return _string.left(i); }; - int indexOf(QString s) { return _string.indexOf(s); }; - int indexOf(QString s, int i) { return _string.indexOf(s,i); }; - QString mid(int f, int t) { return _string.mid(f,t); }; - QString left(int i) { return _string.left(i); }; + void clear() { _string.clear(); }; - void clear() { _string.clear(); }; + QString CQersCall(); - QString CQersCall(); + bool isJT65(); + bool isJT9(); + bool isTX(); + bool isLowConfidence (); + int frequencyOffset(); // hertz offset from the tuned dial or rx frequency, aka audio frequency + int snr(); + float dt(); - bool isJT65(); - bool isJT9(); - bool isTX(); - int frequencyOffset(); // hertz offset from the tuned dial or rx frequency, aka audio frequency - int snr(); - float dt(); - - // find and extract any report. Returns true if this is a standard message + // find and extract any report. Returns true if this is a standard message bool report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report); - // get the first text word, usually the call - QString call(); + // get the first message text word, usually the call + QString call(); - // get the second word, most likely the de call and the third word, most likely grid - void deCallAndGrid(/*out*/QString& call, QString& grid); + // get the second word, most likely the de call and the third word, most likely grid + void deCallAndGrid(/*out*/QString& call, QString& grid); - int timeInSeconds(); + int timeInSeconds(); - // returns a string of the SNR field with a leading + or - followed by two digits - QString report(); + // returns a string of the SNR field with a leading + or - followed by two digits + QString report(); private: - QString _string; - + // These define the columns in the decoded text where fields are to be found. + // We rely on these columns being the same in the fortran code (lib/decoder.f90) that formats the decoded text + enum Columns {column_time = 0, + column_snr = 5, + column_dt = 9, + column_freq = 14, + column_mode = 19, + column_qsoText = 22 }; + + QString _string; + int padding_; }; - -extern "C" { bool stdmsg_(const char* msg, int len); } - #endif // DECODEDTEXT_H diff --git a/displaytext.cpp b/displaytext.cpp index 442435cb2..8cd763a56 100644 --- a/displaytext.cpp +++ b/displaytext.cpp @@ -40,8 +40,8 @@ void DisplayText::setContentFont(QFont const& font) void DisplayText::mouseDoubleClickEvent(QMouseEvent *e) { bool ctrl = (e->modifiers() & Qt::ControlModifier); - bool shift = (e->modifiers() & Qt::ShiftModifier); - emit(selectCallsign(shift,ctrl)); + bool alt = (e->modifiers() & Qt::AltModifier); + emit(selectCallsign(alt,ctrl)); QTextEdit::mouseDoubleClickEvent(e); } @@ -72,24 +72,24 @@ void DisplayText::appendText(QString const& text, QString const& bg) } -void DisplayText::_appendDXCCWorkedB4(DecodedText& t1, QString& bg, +QString DisplayText::_appendDXCCWorkedB4(QString message, QString const& callsign, QString * bg, LogBook logBook, QColor color_CQ, QColor color_DXCC, QColor color_NewCall) { - QString call = t1.CQersCall (); + QString call = callsign; QString countryName; bool callWorkedBefore; bool countryWorkedBefore; if(call.length()==2) { - int i0=t1.indexOf("CQ "+call); - call=t1.mid(i0+6,-1); + int i0=message.indexOf("CQ "+call); + call=message.mid(i0+6,-1); i0=call.indexOf(" "); call=call.mid(0,i0); } - if(call.length()<3) return; - if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return; + if(call.length()<3) return message; + if(!call.contains(QRegExp("[0-9]|[A-Z]"))) return message; logBook.match(/*in*/call,/*out*/countryName,callWorkedBefore,countryWorkedBefore); int charsAvail = 48; @@ -97,31 +97,31 @@ void DisplayText::_appendDXCCWorkedB4(DecodedText& t1, QString& bg, // the decoder (seems) to always generate 41 chars. For a normal CQ call, the last five are spaces // TODO this magic 37 characters is also referenced in MainWindow::doubleClickOnCall() int nmin=37; - int i=t1.indexOf(" CQ "); - int k=t1.string().mid(i+4,3).toInt(); + int i=message.indexOf(" CQ "); + int k=message.mid(i+4,3).toInt(); if(k>0 and k<999) nmin += 4; - int s3 = t1.indexOf(" ",nmin); + int s3 = message.indexOf(" ",nmin); if (s3 < nmin) s3 = nmin; // always want at least the characters to position 35 s3 += 1; // convert the index into a character count - t1 = t1.left(s3); // reduce trailing white space + message = message.left(s3); // reduce trailing white space charsAvail -= s3; if (charsAvail > 4) { if (!countryWorkedBefore) // therefore not worked call either { - t1 += "!"; - bg=color_DXCC.name(); + message += "!"; + *bg = color_DXCC.name(); } else if (!callWorkedBefore) // but have worked the country { - t1 += "~"; - bg=color_NewCall.name(); + message += "~"; + *bg = color_NewCall.name(); } else { - t1 += " "; // have worked this call before - bg=color_CQ.name(); + message += " "; // have worked this call before + *bg = color_CQ.name(); } charsAvail -= 1; @@ -155,8 +155,9 @@ void DisplayText::_appendDXCCWorkedB4(DecodedText& t1, QString& bg, countryName.replace ("Guantanamo Bay", "U.S.A."); } - t1 += countryName; + message += countryName; } + return message; } void DisplayText::displayDecodedText(DecodedText decodedText, QString myCall, @@ -181,11 +182,13 @@ void DisplayText::displayDecodedText(DecodedText decodedText, QString myCall, or decodedText.indexOf (" " + myCall + ">") >= 0)) { bg=color_MyCall.name(); } - // if enabled add the DXCC entity and B4 status to the end of the preformated text line t1 + // if enabled add the DXCC entity and B4 status to the end of the + // preformated text line t1 + auto message = decodedText.string (); if (displayDXCCEntity && CQcall) - _appendDXCCWorkedB4(/*mod*/decodedText,bg,logBook,color_CQ, - color_DXCC,color_NewCall); - appendText(decodedText.string(),bg); + message = _appendDXCCWorkedB4 (message, decodedText.CQersCall (), &bg, logBook, color_CQ, + color_DXCC, color_NewCall); + appendText (message, bg); } diff --git a/displaytext.h b/displaytext.h index dc8764541..65e8bfbb5 100644 --- a/displaytext.h +++ b/displaytext.h @@ -23,7 +23,7 @@ public: void displayQSY(QString text); signals: - void selectCallsign(bool shift, bool ctrl); + void selectCallsign(bool alt, bool ctrl); public slots: void appendText(QString const& text, QString const& bg = "white"); @@ -32,7 +32,7 @@ protected: void mouseDoubleClickEvent(QMouseEvent *e); private: - void _appendDXCCWorkedB4(/*mod*/DecodedText& t1, QString &bg, LogBook logBook, + QString _appendDXCCWorkedB4(QString message, QString const& callsign, QString * bg, LogBook logBook, QColor color_CQ, QColor color_DXCC, QColor color_NewCall); QTextCharFormat m_charFormat; diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 5cb8d3f36..295a3e205 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -63,6 +63,7 @@ set (UG_IMGS images/170709_135615.wav.png images/AstroData_2.png images/Astronomical_data.png + images/auto-seq.png images/band-settings.png images/colors.png images/config-menu.png @@ -104,6 +105,7 @@ set (UG_IMGS images/setup-menu.png images/special-mouse-commands.png images/status-bar-a.png + images/tools-menu.png images/traditional-msg-box.png images/tx-macros.png images/view-menu.png diff --git a/doc/user_guide/en/astro_data.adoc b/doc/user_guide/en/astro_data.adoc index dcdc2800e..361ee9b00 100644 --- a/doc/user_guide/en/astro_data.adoc +++ b/doc/user_guide/en/astro_data.adoc @@ -1,46 +1,57 @@ -A text box entitled Astronomical Data provides information needed for -tracking the sun or moon, compensating for EME Doppler shift, and -estimating EME Doppler spread and path degradation. Toggle the -*Astronomical data* on the *View* menu to display or hide this window. - -image::AstroData_2.png[align="center",alt="Astronomical Data"] - -Available information includes the current UTC *Date* and time; *Az* -and *El*, azimuth and elevation of the moon at your own location, in -degrees; *SelfDop*, *Width*, and *Delay*, the Doppler shift, full -limb-to-limb Doppler spread in Hz, and delay of your own EME echoes in -seconds; and *DxAz* and *DxEl*, *DxDop*, and *DxWid*, corresponding -parameters for a station located at the *DX Grid* entered on the main -window. These numbers are followed by *Dec*, the declination of the -moon; *SunAz* and *SunEl*, the azimuth and elevation of the Sun; -*Freq*, your stated operating frequency in MHz; *Tsky*, the estimated -sky background temperature in the direction of the moon, scaled to the -operating frequency; *Dpol*, the spatial polarization offset in -degrees; *MNR*, the maximum non-reciprocity of the EME path in dB, -owing to a combination of Faraday rotation and spatial polarization; -and finally *Dgrd*, an estimate of the signal degradation in dB, -relative to the best possible time with the moon at perigee in a cold -part of the sky. - -The state of the art for establishing three-dimensional locations of -the sun, moon, and planets at a specified time is embodied in a -numerical model of the solar system maintained at the Jet Propulsion -Laboratory. The model has been numerically integrated to produce -tabular data that can be interpolated with very high accuracy. For -example, the celestial coordinates of the moon or a planet can be -determined at a specified time to within about 0.0000003 degrees. The -JPL ephemeris tables and interpolation routines have been incorporated -into _WSJT-X_. Further details on accuracy, especially concerning -calculated EME Doppler shifts, are described in {lunarEchoes} for -November-December, 2016. - -The sky background temperatures reported by _WSJT-X_ are derived from -the all-sky 408 MHz map of Haslam et al. (Astronomy and Astrophysics -Supplement Series, 47, 1, 1982), scaled by frequency to the -2.6 -power. This map has angular resolution of about 1 degree, and of -course most amateur EME antennas have much broader beamwidths than -this. Your antenna will therefore smooth out the hot spots -considerably, and the observed extremes of sky temperature will be -less. Unless you understand your sidelobes and ground reflections -extremely well, it is unlikely that more accurate sky temperatures -would be of much practical use. +A text box entitled Astronomical Data provides information needed for +tracking the sun or moon, compensating for EME Doppler shift, and +estimating EME Doppler spread and path degradation. Toggle the +*Astronomical data* on the *View* menu to display or hide this window. + +image::AstroData_2.png[align="center",alt="Astronomical Data"] + +Available information includes the current UTC *Date* and time; *Az* +and *El*, azimuth and elevation of the moon at your own location, in +degrees; *SelfDop*, *Width*, and *Delay*, the Doppler shift, full +limb-to-limb Doppler spread in Hz, and delay of your own EME echoes in +seconds; and *DxAz* and *DxEl*, *DxDop*, and *DxWid*, corresponding +parameters for a station located at the *DX Grid* entered on the main +window. These numbers are followed by *Dec*, the declination of the +moon; *SunAz* and *SunEl*, the azimuth and elevation of the Sun; +*Freq*, your stated operating frequency in MHz; *Tsky*, the estimated +sky background temperature in the direction of the moon, scaled to the +operating frequency; *Dpol*, the spatial polarization offset in +degrees; *MNR*, the maximum non-reciprocity of the EME path in dB, +owing to a combination of Faraday rotation and spatial polarization; +and finally *Dgrd*, an estimate of the signal degradation in dB, +relative to the best possible time with the moon at perigee in a cold +part of the sky. + +On the higher microwave bands, where Faraday rotation is minimal and +linear polarization is often used, spatial offset will reduce signal +levels. Some stations have implemented mechanical polarisation +adjustment to overcome this loss, and the amount of rotation needed is +predicted in real time by the value of *Dpol*. Positive Dpol means +that the antenna should be rotated in a clockwise direction looking +from behind the antenna towards the moon. For a dish antenna, the +feed should similarly be rotated clockwise looking into the mouth of +the feed. A negative value for Dpol means anticlockwise rotation. + + +The state of the art for establishing three-dimensional locations of +the sun, moon, and planets at a specified time is embodied in a +numerical model of the solar system maintained at the Jet Propulsion +Laboratory. The model has been numerically integrated to produce +tabular data that can be interpolated with very high accuracy. For +example, the celestial coordinates of the moon or a planet can be +determined at a specified time to within about 0.0000003 degrees. The +JPL ephemeris tables and interpolation routines have been incorporated +into _WSJT-X_. Further details on accuracy, especially concerning +calculated EME Doppler shifts, are described in {lunarEchoes} for +November-December, 2016. + +The sky background temperatures reported by _WSJT-X_ are derived from +the all-sky 408 MHz map of Haslam et al. (Astronomy and Astrophysics +Supplement Series, 47, 1, 1982), scaled by frequency to the -2.6 +power. This map has angular resolution of about 1 degree, and of +course most amateur EME antennas have much broader beamwidths than +this. Your antenna will therefore smooth out the hot spots +considerably, and the observed extremes of sky temperature will be +less. Unless you understand your sidelobes and ground reflections +extremely well, it is unlikely that more accurate sky temperatures +would be of much practical use. diff --git a/doc/user_guide/en/controls-functions-center.adoc b/doc/user_guide/en/controls-functions-center.adoc index decfe09bd..986465264 100644 --- a/doc/user_guide/en/controls-functions-center.adoc +++ b/doc/user_guide/en/controls-functions-center.adoc @@ -18,16 +18,23 @@ double-clicking on decoded text or a signal in the waterfall. They can also be adjusted using the spinner controls. * You can force Tx frequency to the current Rx frequency by clicking -the *Tx<-Rx* button, and vice-versa for *Rx<-Tx*. Check the box *Lock -Tx=Rx* to make the frequencies always track one another. The -on-the-air frequency of your lowest JT9 or JT65 tone is the sum of -dial frequency and audio Tx frequency. +the *Tx<-Rx* button, and vice-versa for *Rx<-Tx*. The on-the-air +frequency of your lowest JT9 or JT65 tone is the sum of dial frequency +and audio Tx frequency. + +* Check the box *Lock Tx=Rx* to make the frequencies always track one +another. TIP: In general we do not recommend using *Lock Tx=Rx* since it -encourages poor radio etiquette when running a frequency. With *Lock -Tx=Rx* checked, your own Tx frequency will move around following your +encourages poor radio etiquette when running a frequency. With this +box checked, your own Tx frequency will move around following your callers. +* For modes lacking a multi-decode feature, or when *Enable +VHF/UHF/Microwave features* has been checked on the *Settings -> +General* tab, the *F Tol* control sets a frequency toilerance range +over which decoding will be attempted, centered on the Rx frequency. + * The *Report* control lets you change a signal report that has been inserted automatically. Typical reports for the various modes fall in the range –30 to +20 dB. Remember that JT65 reports saturate at an @@ -37,13 +44,36 @@ TIP: Consider reducing power if your QSO partner reports your signal above -5 dB in one of the _WSJT-X_ slow modes. These are supposed to be weak signal modes! -* With *Split operation* activated on the *Settings -> Radio* tab, you -can activate the spinner control *Tx CQ nnn* by checking the box to -its right. The program will then generate something like `CQ nnn -K1ABC FN42` for your CQ message, where `nnn` is the kHz portion of -your current operating frequency. Your CQ message *Tx6* will then be -transmitted at the calling frequency selected in the *Tx CQ nnn* spinner -control. All other messages will be transmitted at your current -operating frequency. On reception, when you double-click on a message -like `CQ nnn K1ABC FN42` your rig will QSY to the specified frequency -so you can call the station at his specified response frequency. +* In some circumstances, especially on VHF and higher bands, you can +select a supported submode of the active mode by using the *Submode* +control. The *Sync* control sets a minimum threshold for establishing +time and frequency synchronization with a received signal. + +* Spinner control *T/R xx s* sets sequence lengths for transmission +and reception in ISCAT, MSK144, and the fast JT9 modes. + +* With *Split operation* activated on the *Settings -> Radio* tab, in +MSK144 and the fast JT9 submodes you can activate the spinner control +*Tx CQ nnn* by checking the box to its right. The program will then +generate something like `CQ nnn K1ABC FN42` for your CQ message, where +`nnn` is the kHz portion of your current operating frequency. Your CQ +message *Tx6* will then be transmitted at the calling frequency +selected in the *Tx CQ nnn* spinner control. All other messages will +be transmitted at your current operating frequency. On reception, +when you double-click on a message like `CQ nnn K1ABC FN42` your rig +will QSY to the specified frequency so you can call the station at his +specified response frequency. + +* Checkboxes at bottom center of the main window control special +features for particular operating modes: + +** *Sh* enables shorthand messages in JT4, JT65, and MSK144 modes + +** *Fast* enables fast JT9 submodes + +** *Auto Seq* enables auto-sequencing of Tx messages + +** *Call 1st* enables automatic response to the first decoded +responder to your CQ + +** *Tx6* toggles between two types of shorthand messages in JT4 mode \ No newline at end of file diff --git a/doc/user_guide/en/controls-functions-left.adoc b/doc/user_guide/en/controls-functions-left.adoc index fb3250495..338346dae 100644 --- a/doc/user_guide/en/controls-functions-left.adoc +++ b/doc/user_guide/en/controls-functions-left.adoc @@ -18,10 +18,9 @@ recognized ADIF format, for example 630m, 20m, or 70cm. The band-name format works only if a working frequency has been set for that band and mode, in which case the first such match is selected. -TIP: You can also enter a frequency increment in kHz above the -currently displayed integer MHz. For example, if the displayed -frequency is 10,368.100, enter `165k` (don't forget the `k`!) to QSY -to 10,368.165. +* You can also enter a frequency increment in kHz above the currently +displayed integer MHz. For example, if the displayed frequency is +10,368.100, enter `165k` (don't forget the `k`!) to QSY to 10,368.165. * A small colored circle appears in green if the CAT control is activated and functional. The green circle contains the character S @@ -34,10 +33,6 @@ split transmit frequency. When using _WSJT-X_ with such radios you should not change the current VFO, split status or dial frequency using controls on the radio. -* The slider adjacent to the level meter can be used to adjust the -signal level sent to the Fast Graph. If *Flatten* is not checked, -the same is true for the Wide Graph. - * If *DX Grid* contains a valid Maidenhead locator, the corresponding great-circle azimuth and distance from your location are displayed. diff --git a/doc/user_guide/en/controls-functions-menus.adoc b/doc/user_guide/en/controls-functions-menus.adoc index c9463cbe3..d931d15c7 100644 --- a/doc/user_guide/en/controls-functions-menus.adoc +++ b/doc/user_guide/en/controls-functions-menus.adoc @@ -24,7 +24,7 @@ Many users prefer to create and use entries on the *Configurations* menu for switching between modes. Simply *Clone* the *Default* entry, *Rename* it as desired, and then make all desired settings for that configuration. These settings will be restored whenever you select -that entry. +that configuration. [[VIEW_MENU]] ==== View Menu @@ -43,6 +43,9 @@ image::decode-menu.png[align="left",alt="Decode Menu"] ==== Save Menu image::save-menu.png[align="left",alt="Save Menu"] +==== Tools Menu +image::tools-menu.png[align="left",alt="Tools Menu"] + [[HELP_MENU]] ==== Help Menu image::help-menu.png[align="left",alt="Help Menu"] diff --git a/doc/user_guide/en/controls-functions-messages.adoc b/doc/user_guide/en/controls-functions-messages.adoc index 5fabeabc1..cb4f0f8b4 100644 --- a/doc/user_guide/en/controls-functions-messages.adoc +++ b/doc/user_guide/en/controls-functions-messages.adoc @@ -29,6 +29,12 @@ pre-stored messages entered on the *Settings | Tx Macros* tab. Pressing *Enter* on a modified message #5 automatically adds that message to the stored macros. +* In some circumstances it may be desirable to make your QSOs as +shiort as possible. To configure the program to start contacts with +message #2, disable message #1 by double-clicking on its round +radio-button or rectangular *Tx 1* button. Similarly, to send RR73 +rather than RRR for message #4, double-click on one of its buttons. + The second arrangement of controls for generating and selecting Tx messages appears on *Tab 2* of the Message Control Panel: diff --git a/doc/user_guide/en/images/170709_135615.wav.png b/doc/user_guide/en/images/170709_135615.wav.png index 1e6b426de..373adbcae 100644 Binary files a/doc/user_guide/en/images/170709_135615.wav.png and b/doc/user_guide/en/images/170709_135615.wav.png differ diff --git a/doc/user_guide/en/images/Astronomical_data.png b/doc/user_guide/en/images/Astronomical_data.png index cccceb02d..caf4bb1ba 100644 Binary files a/doc/user_guide/en/images/Astronomical_data.png and b/doc/user_guide/en/images/Astronomical_data.png differ diff --git a/doc/user_guide/en/images/HotA.png b/doc/user_guide/en/images/HotA.png new file mode 100644 index 000000000..4bada45d7 Binary files /dev/null and b/doc/user_guide/en/images/HotA.png differ diff --git a/doc/user_guide/en/images/JT4F.png b/doc/user_guide/en/images/JT4F.png index c8944d81b..ddb41b45c 100644 Binary files a/doc/user_guide/en/images/JT4F.png and b/doc/user_guide/en/images/JT4F.png differ diff --git a/doc/user_guide/en/images/MSK144.png b/doc/user_guide/en/images/MSK144.png index c979716dc..c6603281f 100644 Binary files a/doc/user_guide/en/images/MSK144.png and b/doc/user_guide/en/images/MSK144.png differ diff --git a/doc/user_guide/en/images/QRA64.png b/doc/user_guide/en/images/QRA64.png index 7137b8796..514eb0f64 100644 Binary files a/doc/user_guide/en/images/QRA64.png and b/doc/user_guide/en/images/QRA64.png differ diff --git a/doc/user_guide/en/images/auto-seq.png b/doc/user_guide/en/images/auto-seq.png new file mode 100644 index 000000000..0a2bb60ac Binary files /dev/null and b/doc/user_guide/en/images/auto-seq.png differ diff --git a/doc/user_guide/en/images/decode-menu.png b/doc/user_guide/en/images/decode-menu.png index ff44cd5e1..4235369d8 100644 Binary files a/doc/user_guide/en/images/decode-menu.png and b/doc/user_guide/en/images/decode-menu.png differ diff --git a/doc/user_guide/en/images/file-menu.png b/doc/user_guide/en/images/file-menu.png index 8db8338aa..e7a765fa7 100644 Binary files a/doc/user_guide/en/images/file-menu.png and b/doc/user_guide/en/images/file-menu.png differ diff --git a/doc/user_guide/en/images/ft8_decodes.png b/doc/user_guide/en/images/ft8_decodes.png index 2766ec859..2b5b4e959 100644 Binary files a/doc/user_guide/en/images/ft8_decodes.png and b/doc/user_guide/en/images/ft8_decodes.png differ diff --git a/doc/user_guide/en/images/help-menu.png b/doc/user_guide/en/images/help-menu.png index b870c6801..ba297c7e3 100644 Binary files a/doc/user_guide/en/images/help-menu.png and b/doc/user_guide/en/images/help-menu.png differ diff --git a/doc/user_guide/en/images/keyboard-shortcuts.png b/doc/user_guide/en/images/keyboard-shortcuts.png index 8b8d326ff..bde37adda 100644 Binary files a/doc/user_guide/en/images/keyboard-shortcuts.png and b/doc/user_guide/en/images/keyboard-shortcuts.png differ diff --git a/doc/user_guide/en/images/log-qso.png b/doc/user_guide/en/images/log-qso.png index 722927fe1..867251bc7 100644 Binary files a/doc/user_guide/en/images/log-qso.png and b/doc/user_guide/en/images/log-qso.png differ diff --git a/doc/user_guide/en/images/main-ui.png b/doc/user_guide/en/images/main-ui.png index 89ebf11a4..73e31455b 100644 Binary files a/doc/user_guide/en/images/main-ui.png and b/doc/user_guide/en/images/main-ui.png differ diff --git a/doc/user_guide/en/images/misc-controls-center.png b/doc/user_guide/en/images/misc-controls-center.png index 4778e03ea..2677e2c8e 100644 Binary files a/doc/user_guide/en/images/misc-controls-center.png and b/doc/user_guide/en/images/misc-controls-center.png differ diff --git a/doc/user_guide/en/images/misc-main-ui.png b/doc/user_guide/en/images/misc-main-ui.png index 267dca830..1889992a8 100644 Binary files a/doc/user_guide/en/images/misc-main-ui.png and b/doc/user_guide/en/images/misc-main-ui.png differ diff --git a/doc/user_guide/en/images/mode-menu.png b/doc/user_guide/en/images/mode-menu.png index e810907e0..8e63a02e2 100644 Binary files a/doc/user_guide/en/images/mode-menu.png and b/doc/user_guide/en/images/mode-menu.png differ diff --git a/doc/user_guide/en/images/save-menu.png b/doc/user_guide/en/images/save-menu.png index b76760784..6d1ff7a1a 100644 Binary files a/doc/user_guide/en/images/save-menu.png and b/doc/user_guide/en/images/save-menu.png differ diff --git a/doc/user_guide/en/images/settings-advanced.png b/doc/user_guide/en/images/settings-advanced.png index 68336f996..bfd89a46f 100644 Binary files a/doc/user_guide/en/images/settings-advanced.png and b/doc/user_guide/en/images/settings-advanced.png differ diff --git a/doc/user_guide/en/images/special-mouse-commands.png b/doc/user_guide/en/images/special-mouse-commands.png index b319a9f5c..6991fe3a4 100644 Binary files a/doc/user_guide/en/images/special-mouse-commands.png and b/doc/user_guide/en/images/special-mouse-commands.png differ diff --git a/doc/user_guide/en/images/tools-menu.png b/doc/user_guide/en/images/tools-menu.png new file mode 100644 index 000000000..6c7a432e1 Binary files /dev/null and b/doc/user_guide/en/images/tools-menu.png differ diff --git a/doc/user_guide/en/images/view-menu.png b/doc/user_guide/en/images/view-menu.png index 602565b0f..3387a5784 100644 Binary files a/doc/user_guide/en/images/view-menu.png and b/doc/user_guide/en/images/view-menu.png differ diff --git a/doc/user_guide/en/make-qso.adoc b/doc/user_guide/en/make-qso.adoc index 5bd32d045..fa8c053aa 100644 --- a/doc/user_guide/en/make-qso.adoc +++ b/doc/user_guide/en/make-qso.adoc @@ -47,6 +47,26 @@ compound callsign. It should be obvious that the JT4, JT9, and JT65 protocols are not designed or well suited for extensive conversations or rag-chewing. +=== Auto-Sequencing + +The slow modes JT4, JT9, JT65, and QRA64 allow nearly 10 seconds at +the end of each one-minute receiving sequence in which you can inspect +decoded messages and decide how to reply. With its 15-second T/R +cycles, FT8 allows only about two seconds for this task. For this +reason a basic auto-sequencing feature is offered. Check *Auto Seq* +on the main window to enable this feature: + +image::auto-seq.png[align="center",alt="AutoSeq"] + +When calling CQ you may also choose to check the box *Call 1st*. +_WSJT-X_ will then respond automatically to the first decoded +responder to your CQ. + +TIP: When *Auto-Seq* is enabled the program will de-activate *Enable +Tx* at the end of each QSO. We do not want _WSJT-X_ to make fully +automated QSOs. + + [[COMP-CALL]] === Compound Callsigns diff --git a/doc/user_guide/en/new_features.adoc b/doc/user_guide/en/new_features.adoc index 108df42f2..664e763e8 100644 --- a/doc/user_guide/en/new_features.adoc +++ b/doc/user_guide/en/new_features.adoc @@ -3,7 +3,9 @@ For quick reference, here's a short list of features and capabilities added to _WSJT-X_ since Version 1.7.0: -- New modes: *FT8* and *FreqCal* +- New mode *FT8* designed for fast QSOs + +- New tool *FreqCal* for accurate frequency calibration of your radio - Improved decoding performance for JT65, QRA64, and MSK144 @@ -11,18 +13,19 @@ added to _WSJT-X_ since Version 1.7.0: - Experimental amplitude and phase equalization for MSK144 -- Options to minimize screen space used by the *Main* and *Wide Graph* +- Options to minimize screen space used by *Main* and *Wide Graph* windows - New set of suggested default frequencies specific to the three IARU Regions. -- Enhanced scheme for managing table of default operating frequencies +- Enhanced scheme for managing table of suggested default operating +frequencies -- Improved CAT control for many rigs, including those controlled +- Improved CAT control for many radios, including those controlled through Commander or OmniRig. -- Bug fixes and tweaks to the user interface +- Bug fixes and tweaks to user interface === Documentation Conventions diff --git a/doc/user_guide/en/odds_and_ends.adoc b/doc/user_guide/en/odds_and_ends.adoc index be30bebd0..dde0b90ae 100644 --- a/doc/user_guide/en/odds_and_ends.adoc +++ b/doc/user_guide/en/odds_and_ends.adoc @@ -1,3 +1,55 @@ +=== AP Decoding + +With the QRA64 decoder Nico Palermo, IV3NWV, introduced a technique +for decoding with the aid of information that naturally accumulates +during a minimal QSO. This _a priori_ (AP) information can be +used to increase the sensitivity of the decoder. + +When an operator decides to answer a CQ, he already knows his own +callsign and that of his potential QSO partner. He therefore knows +what to expect for at least 56 of the 72 message bits in a +standard-format response to his call. The _WSJT-X_ decoders for QRA64 +and FT8 can use these AP bits to decode messages containing them with +higher sensitivity than otherwise possible. + +We have implemented AP decoding in slightly different ways in QRA64 +and FT8. To provide some explicit examples for users, we provide here +a brief description of the FT8 behavior. + +The FT8 decoder always tries first to decode a signal without using +any AP information. If this attempt fails, and if *Enable AP* is +checked on the *Decode* menu, a second attempt hypothesizes that the +message contains callsigns MyCall and DxCall. If the QSO has +progressed to the point where signal reports have been exchanged, a +third attempt hypothesizes that the message contains the known +callsigns followed by RRR, RR73, or 73. + +AP decoding attempts effectively set the AP bits to the hypothesized +values, as if they had been received perfectly. The decoder then +proceeds to determine whether the remaining message and parity bits +are consistent with the hypothesized AP bits. If a codeword is found +that the decoder judges to have high (but not overwhelmingly high) +probability of being correct, a ? character is appended when the +decoded message is displayed. + +Successful AP decodes are always labeled with an end-of-line indicator +of the form aP, where P is one of the single-digit AP decoding types +listed in Table 1. For example, an a2 designator says that the +successful decode used MyCall as hypothetically known information. + +[[AP_INFO_TABLE]] +.AP information types +[width="25%",cols="h10,>. A wider displayed bandwidth may also be helpful at VHF and above, where -JT4, JT65, and QRA64 signals are found over much wider ranges of -frequencies. +FT8, JT4, JT65, and QRA64 signals may be found over much wider ranges +of frequencies. - If you have only a standard SSB filter you won’t be able to display more than about 2.7 kHz bandwidth. Depending on the exact dial frequency setting, on HF bands you can display the full sub-band -generally used for one mode (JT65 or JT9) and part of the sub-band for -the other mode. +generally used for one mode. - Of course, you might prefer to concentrate on one mode at a time, setting your dial frequency to (say) 14.074 for FT8, 14.076 for JT65, diff --git a/doc/user_guide/en/tutorial-example3.adoc b/doc/user_guide/en/tutorial-example3.adoc index 9fbc9d0c2..32aae25c6 100644 --- a/doc/user_guide/en/tutorial-example3.adoc +++ b/doc/user_guide/en/tutorial-example3.adoc @@ -6,22 +6,45 @@ .Wide Graph Settings: -- *Bins/Pixel* = 4 +- *Bins/Pixel* = 4, *Start* = 200 Hz, *N Avg* = 2 - Adjust the width of the Wide Graph window so that the upper -frequency limit is approximately 2500 Hz. +frequency limit is approximately 2600 Hz. .Open a Wave File: -- Select *File | Open* and navigate to +...\save\samples\FT8\170709_135615.wav+. -The waterfall should look something like this: +- Select *File | Open* and navigate to ++...\save\samples\FT8\170709_135615.wav+. The waterfall and decoded +text window should look something like the following screen shots: [[X15]] image::170709_135615.wav.png[align="left",alt="Wide Graph Decode 170709_135615"] -- You should see decodes of the three FT8 signals in the *Band Activity* -text box, as shown below: - image::ft8_decodes.png[align="left"] +- Click with the mouse anywhere on the waterfall display. The green Rx +frequency marker will jump to your selected frequency, and the Rx +frequency control on the main window will be updated accordingly. + +- Do the same thing with the Shift key held down. Now the red Tx +frequency marker and its associated control on the main window will +follow your frequency selections. + +- Do the same thing with the Ctrl key held down. Now the both colored +markers and both spinner controls will follow your selections. + +- Double-clicking at any frequency on the waterfall does all the +things just described and also invokes the decoder in a small range +around that frequency. + +- Now double-click on any of the the lines of decoded text in the main +window. Unless you have *My Call* set to K1JT or KY7M on the +*Settings -> General* tab, all three lines will show the same +behavior, setting both RxFreq and TxFreq to the frequency of the +selected message. However, if MyCall is set to K1JT then clicking on +a message directed to K1JT will move only the Rx frequency setting. +This behavior is desirable so that you will not inadvertently change +your Tx frequency to that of a tail-ender who called you somewhere +else in the FT8 subband. + IMPORTANT: When finished with this Tutorial, don’t forget to re-enter your own callsign as *My Call* on the *Settings | General* tab. diff --git a/doc/user_guide/en/vhf-features.adoc b/doc/user_guide/en/vhf-features.adoc index 68f00d8ee..63f621ca9 100644 --- a/doc/user_guide/en/vhf-features.adoc +++ b/doc/user_guide/en/vhf-features.adoc @@ -1,9 +1,6 @@ _WSJT-X_ v1.8 suppports a number of features designed for use on the VHF and higher bands. These features now include: -- *FT8*, a mode optimized for weak, fading signals such as those often -encountered with multi-hop sporadic E propagation on 50 MHz. - - *JT4*, a mode particularly useful for EME on the microwave bands - *JT9* fast modes, useful for scatter propagation on VHF bands @@ -24,9 +21,6 @@ propagation - *Doppler tracking*, which becomes increasingly important for EME on bands above 1.2 GHz. -- *Auto-sequencing* of transmitted messages for FT8 and the fast modes -with forward error control - [[VHF_SETUP]] === VHF Setup @@ -114,7 +108,11 @@ is generally used for EME on the 5.7 and 10 GHz bands. - For EME QSOs some operators use short-form JT4 messages consisting of a single tone. To activate automatic generation of these messages, -check the box labeled *Sh*. +check the box labeled *Sh*. This also enables the generation of a +single tone at 1000Hz by selecting Tx6, to assist in finding signals +initially. The box labeled *Tx6* toggles the Tx6 message from 1000Hz +to 1250Hz to indicate to the other station that you are ready to +receive messages. - Select *Deep* from the *Decode* menu. You may also choose to *Enable averaging* over successive transmissions and/or *Enable deep @@ -157,15 +155,15 @@ image::JT65B.png[align="center",alt="JT65B"] === QRA64 -QRA64 is an experimental mode in Version 1.7 of _WSJT-X_. The mode is +QRA64 is an experimental mode in Version 1.8 of _WSJT-X_. The mode is designed especially for EME on VHF and higher bands; its operation is -generally similar to JT65. The following screen shot shows an example -of a QRA64C transmission from DL7YC recorded at G3WDG over the EME -path at 24 GHz. Doppler spread on the path was 78 Hz, so although the -signal is reasonably strong its tones are broadened enough to make -them hard to see on the waterfall. The red curve shows that the -decoder has achieved synchronization with a signal at approximately -967 Hz. +generally similar to JT4 and JT65. The following screen shot shows an +example of a QRA64C transmission from DL7YC recorded at G3WDG over the +EME path at 24 GHz. Doppler spread on the path was 78 Hz, so although +the signal is reasonably strong its tones are broadened enough to make +them hard to see on the waterfall. The triangular red marker below +the frequency scale shows that the decoder has achieved +synchronization with a signal at approximately 967 Hz. image::QRA64.png[align="center",alt="QRA64"] @@ -183,12 +181,19 @@ most likely value for each of the message's 12 six-bit information symbols. A decode is declared only when the total probability for all 12 symbols has converged to an unambiguous value very close to 1. -TIP: In _WSJT-X_ Version 1.7 QRA64 is different from JT65 in that the -decoder attempts to find and decode only a single signal in the -receiver passband. If many signals are present you may be able to -decode them by double-clicking on the lowest tone of each one in the -waterfall. A multi-decoder like those for JT65 and JT9 has not -yet been written. +For EME QSOs some operators use short-form QRA64 messages consisting +of a single tone. To activate automatic generation of these messages, +check the box labeled *Sh*. This also enables the generation of a +single tone at 1000Hz by selecting Tx6, to assist in finding signals +initially, as the QRA64 tones are often not visible on the waterfall. +The box labeled *Tx6* switches the Tx6 message from 1000Hz to 1250Hz +to indicate to the other station that you are ready to receive messages. + + +TIP: QRA64 is different from JT65 in that the decoder attempts to find +and decode only a single signal in the receiver passband. If many +signals are present you may be able to decode them by double-clicking +on the lowest tone of each one in the waterfall. === ISCAT @@ -207,7 +212,7 @@ longer at distances close to the upper limit. But with patience, 100 Watts or more, and a single yagi it can usually be done. The following screen shot shows two 15-second MSK144 transmissions from W5ADD during a 50 MHz QSO with K1JT, at a distance of about 1800 km -(1100 mi). The decoded segments have been encircled on the *Fast +(1100 mi). The decoded segments have been marked on the *Fast Graph* spectral display. image::MSK144.png[align="center",alt="MSK144"] diff --git a/lib/addit.f90 b/lib/addit.f90 index 7d54ff8db..48082833f 100644 --- a/lib/addit.f90 +++ b/lib/addit.f90 @@ -9,26 +9,32 @@ subroutine addit(itone,nfsample,nsym,nsps,ifreq,sig,dat) fsample=12000.d0 !Sample rate (Hz) dt=1.d0/fsample !Sample interval (s) twopi=8.d0*atan(1.d0) + dphi=0. - f=ifreq - phi=0. - k=12000 !Start audio at t = 1.0 s - ntot=nsym*tsym/dt - t=0. - isym0=-1 - do i=1,ntot - t=t+dt - isym=nint(t/tsym) + 1 - if(isym.ne.isym0) then - freq=f + itone(isym)*baud - dphi=twopi*freq*dt - isym0=isym - endif - phi=phi + dphi - if(phi.gt.twopi) phi=phi-twopi - xphi=phi - k=k+1 - dat(k)=dat(k) + sig*sin(xphi) + iters=1 + if(nsym.eq.79) iters=2 + do iter=1,iters + f=ifreq + phi=0. + ntot=nsym*tsym/dt + k=12000 !Start audio at t = 1.0 s + t=0. + if(nsym.eq.79) k=12000 + (iter-1)*12000*30 !Special case for FT8 + isym0=-1 + do i=1,ntot + t=t+dt + isym=nint(t/tsym) + 1 + if(isym.ne.isym0) then + freq=f + itone(isym)*baud + dphi=twopi*freq*dt + isym0=isym + endif + phi=phi + dphi + if(phi.gt.twopi) phi=phi-twopi + xphi=phi + k=k+1 + dat(k)=dat(k) + sig*sin(xphi) + enddo enddo return diff --git a/lib/allsim.f90 b/lib/allsim.f90 index 0aa156864..591a584d6 100644 --- a/lib/allsim.f90 +++ b/lib/allsim.f90 @@ -11,8 +11,10 @@ program allsim integer*2 iwave(NMAX) !Generated waveform (no noise) integer itone(206) !Channel symbols (values 0-8) integer icw(250) + integer*1 msgbits(87) + logical*1 bcontest real*4 dat(NMAX) - character message*22,msgsent*22,arg*8 + character message*22,msgsent*22,arg*8,mygrid*6 nargs=iargc() if(nargs.ne.1) then @@ -24,6 +26,8 @@ program allsim read(arg,*) snrdb !S/N in dB (2500 hz reference BW) message='CQ KA2ABC FN20' + mygrid='FN20 ' + bcontest=.false. rmsdb=25. rms=10.0**(0.05*rmsdb) sig=10.0**(0.05*snrdb) @@ -56,11 +60,14 @@ program allsim call gen4(message,0,msgsent,itone,itype) call addit(itone,11025,206,2520,1200,sig,dat) !JT4 + call genft8(message,mygrid,bcontest,msgsent,msgbits,itone) + call addit(itone,12000,79,1920,1400,sig,dat) !FT8 + call genqra64(message,0,msgsent,itone,itype) - call addit(itone,12000,84,6912,1400,sig,dat) !QRA64 + call addit(itone,12000,84,6912,1600,sig,dat) !QRA64 call gen65(message,0,msgsent,itone,itype) - call addit(itone,11025,126,4096,1600,sig,dat) !JT65 + call addit(itone,11025,126,4096,1800,sig,dat) !JT65 iwave(1:npts)=nint(rms*dat(1:npts)) diff --git a/lib/chkcall.f90 b/lib/chkcall.f90 new file mode 100644 index 000000000..23900748e --- /dev/null +++ b/lib/chkcall.f90 @@ -0,0 +1,58 @@ +subroutine chkcall(w,bc,cok) + +! Check "w" to see if it could be a valid standard callsign or a valid +! compound callsign. +! Return base call "bc" and a logical "cok" indicator. + + character w*13 !A putative callsign + character bc*6 !Base call (tentative) + character c*1 + logical cok,isdigit,isletter + + isdigit(c)=(ichar(c).ge.ichar('0')) .and. (ichar(c).le.ichar('9')) + isletter(c)=(ichar(c).ge.ichar('A')) .and. (ichar(c).le.ichar('Z')) + + cok=.true. + bc=w(1:6) + n1=len_trim(w) + if(n1.gt.11) go to 100 + if(index(w,'.').ge.1) go to 100 + if(index(w,'+').ge.1) go to 100 + if(index(w,'-').ge.1) go to 100 + if(index(w,'?').ge.1) go to 100 + if(n1.gt.6 .and. index(w,'/').le.0) go to 100 + + i0=index(w,'/') + if(max(i0-1,n1-i0).gt.6) go to 100 !Base call must be < 7 characters + if(i0.ge.2 .and. i0.le.n1-1) then !Extract base call from compound call + if(i0-1.le.n1-i0) bc=w(i0+1:n1)//' ' + if(i0-1.gt.n1-i0) bc=w(1:i0-1)//' ' + endif + + nbc=len_trim(bc) + if(nbc.gt.6) go to 100 !Base call should have no more than 6 characters + +! One of first two characters (c1 or c2) must be a letter + if((.not.isletter(bc(1:1))) .and. (.not.isletter(bc(2:2)))) go to 100 + if(bc(1:1).eq.'Q') go to 100 !Calls don't start with Q + +! Must have a digit in 2nd or 3rd position + i1=0 + if(isdigit(bc(2:2))) i1=2 + if(isdigit(bc(3:3))) i1=3 + if(i1.eq.0) go to 100 + +! Callsign must have a suffix of 1-3 letters + if(i1.eq.nbc) go to 100 + n=0 + do i=i1+1,nbc + j=ichar(bc(i:i)) + if(j.lt.ichar('A') .or. j.gt.ichar('Z')) go to 100 + n=n+1 + enddo + if(n.ge.1 .and. n.le.3) go to 200 + +100 cok=.false. + +200 return +end subroutine chkcall diff --git a/lib/contest72.f90 b/lib/contest72.f90 new file mode 100644 index 000000000..2a23c1181 --- /dev/null +++ b/lib/contest72.f90 @@ -0,0 +1,91 @@ +program contest72 + + use packjt + integer dat(12) + logical text,bcontest,ok + character*22 msg,msg0,msg1 + character*72 ct1,ct2 + character*12 callsign1,callsign2 + character*1 c0 + character*42 c + character*6 mygrid + data c/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ +-./?'/ + data bcontest/.true./ + data mygrid/"EM48 "/ + +! itype Message Type +!-------------------- +! 1 Standardd message +! 2 Type 1 prefix +! 3 Type 1 suffix +! 4 Type 2 prefix +! 5 Type 2 suffix +! 6 Free text +! -1 Does not decode correctly + + nargs=iargc() + if(nargs.eq.0) open(10,file='contest_msgs.txt',status='old') + + nn=0 + do imsg=1,9999 + if(nargs.eq.1) then + if(imsg.gt.1) exit + call getarg(1,msg0) + else + read(10,1001,end=999) msg0 +1001 format(a22) + endif + msg=msg0 + if(bcontest) call to_contest_msg(msg0,msg) + call packmsg(msg,dat,itype) + call unpackmsg(dat,msg1) + call fix_contest_msg(mygrid,msg1) + ok=msg1.eq.msg0 + if(msg0.eq.' ') then + write(*,1002) + else + if(jt_c2(1:1).eq.'W') msg0=' '//msg0(1:20) + nn=nn+1 + write(*,1002) nn,msg0,ok,jt_itype,jt_nc1,jt_nc2,jt_ng,jt_k1,jt_k2 +1002 format(i1,'. ',a22,L2,i2,2i10,i6,2i8) + if(index(msg1,' 73 ').gt.4) nn=0 + endif + if(.not.ok) print*,msg0,msg1 + if(itype.lt.0 .or. itype.eq.6) cycle + + if(msg(1:3).eq.'CQ ') then + m=2 + write(ct1,1010) dat +1010 format(12b6.6) +! write(*,1014) ct1 +1014 format(a72) + cycle + endif + + i1=index(msg,'<') + if(i1.eq.1) then + m=0 + cycle + endif + + if(i.ge.5) then + m=3 + cycle + endif + + if(msg(1:6).eq.'73 CQ ') then + m=4 + cycle + endif + + call packmsg(msg,dat,itype) + write(ct1,1010) dat + call packtext(msg,nc1,nc2,ng) +! write(ct2,1012) nc1,nc2,ng+32768 +!1012 format(2b28.28,b16.16) +! write(*,1014) ct1 +! write(*,1014) ct2 +! write(*,1014) + enddo + +999 end program contest72 diff --git a/lib/decoder.f90 b/lib/decoder.f90 index 7cff716c5..c913cdb19 100644 --- a/lib/decoder.f90 +++ b/lib/decoder.f90 @@ -61,7 +61,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample) if(ios.ne.0) then nfail=nfail+1 if(nfail.le.3) then - call sleep_msec(100) + call sleep_msec(10) go to 10 endif endif @@ -70,11 +70,11 @@ subroutine multimode_decoder(ss,id2,params,nfsample) ! We're in FT8 mode call timer('decft8 ',0) newdat=params%newdat - call my_ft8%decode(ft8_decoded,id2,params%nfqso, & - newdat,params%nutc,params%nfa, & - params%nfb,logical(params%nagain), & - params%ndepth,params%nsubmode, & - params%mycall,params%hiscall,params%hisgrid) + call my_ft8%decode(ft8_decoded,id2,params%nQSOProgress,params%nfqso, & + params%nftx,newdat,params%nutc,params%nfa,params%nfb, & + params%nexp_decode,params%ndepth,logical(params%nagain), & + logical(params%lapon),params%napwid,params%mycall, & + params%mygrid,params%hiscall,params%hisgrid) call timer('decft8 ',1) go to 800 endif @@ -390,7 +390,7 @@ contains end select end subroutine jt9_decoded - subroutine ft8_decoded (this,sync,snr,dt,freq,nbadcrc,decoded) + subroutine ft8_decoded (this,sync,snr,dt,freq,decoded,nap,qual) use ft8_decode implicit none @@ -399,17 +399,24 @@ contains integer, intent(in) :: snr real, intent(in) :: dt real, intent(in) :: freq - integer, intent(in) :: nbadcrc character(len=22), intent(in) :: decoded - - if(nbadcrc.eq.0) then - write(*,1000) params%nutc,snr,dt,nint(freq),decoded -1000 format(i6.6,i4,f5.1,i5,' ~ ',1x,a22) - write(13,1002) params%nutc,nint(sync),snr,dt,freq,0,decoded -1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a22,' FT8') - call flush(6) - call flush(13) + integer, intent(in) :: nap + real, intent(in) :: qual + character*2 annot + character*22 decoded0 + + decoded0=decoded + annot=' ' + if(nap.ne.0) then + write(annot,'(a1,i1)') 'a',nap + if(qual.lt.0.17) decoded0(22:22)='?' endif + write(*,1000) params%nutc,snr,dt,nint(freq),decoded0,annot +1000 format(i6.6,i4,f5.1,i5,' ~ ',1x,a22,1x,a2) + write(13,1002) params%nutc,nint(sync),snr,dt,freq,0,decoded0 +1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a22,' FT8') + call flush(6) + call flush(13) select type(this) type is (counting_ft8_decoder) diff --git a/lib/fix_contest_msg.f90 b/lib/fix_contest_msg.f90 index d0ad519fb..8c0345e87 100644 --- a/lib/fix_contest_msg.f90 +++ b/lib/fix_contest_msg.f90 @@ -1,16 +1,20 @@ -subroutine fix_contest_msg(mycall,mygrid,hiscall,msg) +subroutine fix_contest_msg(mygrid,msg) -! If msg is "mycall hiscall grid1" and distance from mygrid to grid1 is more -! thsn 10000 km, change "grid1" to "R grid2" where grid2 is the antipodes -! of grid1. +! If distance from mygrid to grid1 is more thsn 10000 km, change "grid1" +! to "R grid2" where grid2 is the antipodes of grid1. - character*6 mycall,mygrid,hiscall + character*6 mygrid character*22 msg character*6 g1,g2 logical isgrid + isgrid(g1)=g1(1:1).ge.'A' .and. g1(1:1).le.'R' .and. g1(2:2).ge.'A' .and. & + g1(2:2).le.'R' .and. g1(3:3).ge.'0' .and. g1(3:3).le.'9' .and. & + g1(4:4).ge.'0' .and. g1(4:4).le.'9' .and. g1(1:4).ne.'RR73' + n=len(trim(msg)) if(n.lt.4) return + g1=msg(n-3:n)//' ' if(isgrid(g1)) then call azdist(mygrid,g1,0.d0,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter) diff --git a/lib/fsk4hf/bpdecode174.f90 b/lib/fsk4hf/bpdecode174.f90 index aec0fcd3a..9442cc4a1 100644 --- a/lib/fsk4hf/bpdecode174.f90 +++ b/lib/fsk4hf/bpdecode174.f90 @@ -1,4 +1,4 @@ -subroutine bpdecode174(llr,apmask,maxiterations,decoded,cw,nharderror) +subroutine bpdecode174(llr,apmask,maxiterations,decoded,cw,nharderror,iter) ! ! A log-domain belief propagation decoder for the (174,87) code. ! diff --git a/lib/fsk4hf/ft8_params.f90 b/lib/fsk4hf/ft8_params.f90 index 8441fdb42..858e61e96 100644 --- a/lib/fsk4hf/ft8_params.f90 +++ b/lib/fsk4hf/ft8_params.f90 @@ -7,5 +7,6 @@ parameter (NSPS=1920) !Samples per symbol at 12000 S/s parameter (NZ=NSPS*NN) !Samples in full 15 s waveform (151,680) parameter (NMAX=15*12000) !Samples in iwave (180,000) parameter (NFFT1=2*NSPS, NH1=NFFT1/2) !Length of FFTs for symbol spectra -parameter (NHSYM=2*NMAX/NH1-1) !Number of symbol spectra (1/2-sym steps) +parameter (NSTEP=NSPS/4) !Rough time-sync step size +parameter (NHSYM=NMAX/NSTEP-3) !Number of symbol spectra (1/4-sym steps) parameter (NDOWN=60) !Downsample factor diff --git a/lib/fsk4hf/ft8apset.f90 b/lib/fsk4hf/ft8apset.f90 new file mode 100644 index 000000000..8f34aae2f --- /dev/null +++ b/lib/fsk4hf/ft8apset.f90 @@ -0,0 +1,30 @@ +subroutine ft8apset(mycall12,mygrid6,hiscall12,hisgrid6,bcontest,apsym,iaptype) + parameter(NAPM=4,KK=87) + character*12 mycall12,hiscall12 + character*22 msg,msgsent + character*6 mycall,hiscall + character*6 mygrid6,hisgrid6 + character*4 hisgrid + logical bcontest + integer apsym(KK) + integer*1 msgbits(KK) + integer itone(KK) + + mycall=mycall12(1:6) + hiscall=hiscall12(1:6) + hisgrid=hisgrid6(1:4) + if(len_trim(hiscall).eq.0) then + iaptype=1 + hiscall="K9AN" + else + iaptype=2 + endif + hisgrid=hisgrid6(1:4) +! if(len_trim(hisgrid).eq.0) hisgrid="EN50" + if(index(hisgrid," ").eq.0) hisgrid="EN50" + msg=mycall//' '//hiscall//' '//hisgrid + call genft8(msg,mygrid6,bcontest,msgsent,msgbits,itone) + apsym=2*msgbits-1 + + return +end subroutine ft8apset diff --git a/lib/fsk4hf/ft8b.f90 b/lib/fsk4hf/ft8b.f90 index b79840beb..af9bf3fb6 100644 --- a/lib/fsk4hf/ft8b.f90 +++ b/lib/fsk4hf/ft8b.f90 @@ -1,29 +1,75 @@ -subroutine ft8b(dd0,newdat,nfqso,ndepth,icand,sync0,f1,xdt,apsym,nharderrors, & - dmin,nbadcrc,message,xsnr) +subroutine ft8b(dd0,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,napwid, & + lsubtract,nagain,iaptype,mygrid6,bcontest,sync0,f1,xdt,apsym,nharderrors,& + dmin,nbadcrc,ipass,iera,message,xsnr) use timer_module, only: timer include 'ft8_params.f90' parameter(NRECENT=10,NP2=2812) character message*22,msgsent*22 character*12 recent_calls(NRECENT) + character*6 mygrid6 + logical bcontest real a(5) real s1(0:7,ND),s2(0:7,NN) real ps(0:7) - real rxdata(3*ND),llr(3*ND),llrap(3*ND) !Soft symbols + real rxdata(3*ND),rxdatap(3*ND) + real llr(3*ND),llra(3*ND),llr0(3*ND),llrap(3*ND) !Soft symbols real dd0(15*12000) integer*1 decoded(KK),apmask(3*ND),cw(3*ND) integer*1 msgbits(KK) - integer apsym(KK),rr73(11) + integer apsym(KK) + integer mcq(28),mde(28),mrrr(16),m73(16),mrr73(16) integer itone(NN) + integer icos7(0:6),ip(1) + integer nappasses(0:5) ! the number of decoding passes to use for each QSO state + integer naptypes(0:5,4) ! (nQSOProgress, decoding pass) maximum of 4 passes for now complex cd0(3200) complex ctwk(32) complex csymb(32) - logical newdat - data rr73/-1,1,1,1,1,1,1,-1,1,1,-1/ + logical first,newdat,lsubtract,lapon,nagain + data icos7/2,5,6,0,4,1,3/ + data mcq/1,1,1,1,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,1,0,0,0,1,1,0,0,1/ + data mrrr/0,1,1,1,1,1,1,0,1,1,0,0,1,1,1,1/ + data m73/0,1,1,1,1,1,1,0,1,1,0,1,0,0,0,0/ + data mde/1,1,1,1,1,1,1,1,0,1,1,0,0,1,0,0,0,0,0,1,1,1,0,1,0,0,0,1/ + data mrr73/0,0,0,0,0,0,1,0,0,0,0,1,0,1,0,1/ + data first/.true./ + save nappasses,naptypes - max_iterations=40 - norder=2 - if(ndepth.eq.3 .and. abs(nfqso-f1).lt.10.0) norder=3 + if(first) then + mcq=2*mcq-1 + mde=2*mde-1 + mrrr=2*mrrr-1 + m73=2*m73-1 + mrr73=2*mrr73-1 + nappasses(0)=2 + nappasses(1)=2 + nappasses(2)=2 + nappasses(3)=4 + nappasses(4)=4 + nappasses(5)=3 + +! iaptype +!------------------------ +! 1 CQ ??? ??? +! 2 MyCall ??? ??? +! 3 MyCall DxCall ??? +! 4 MyCall DxCall RRR +! 5 MyCall DxCall 73 +! 6 MyCall DxCall RR73 +! 7 ??? DxCall ??? + + naptypes(0,1:4)=(/1,2,0,0/) + naptypes(1,1:4)=(/2,3,0,0/) + naptypes(2,1:4)=(/2,3,0,0/) + naptypes(3,1:4)=(/3,4,5,6/) + naptypes(4,1:4)=(/3,4,5,6/) + naptypes(5,1:4)=(/3,1,2,0/) !? + first=.false. + endif + + max_iterations=30 + nharderrors=-1 fs2=12000.0/NDOWN dt2=1.0/fs2 twopi=8.0*atan(1.0) @@ -34,9 +80,9 @@ subroutine ft8b(dd0,newdat,nfqso,ndepth,icand,sync0,f1,xdt,apsym,nharderrors, call ft8_downsample(dd0,newdat,f1,cd0) !Mix f1 to baseband and downsample call timer('ft8_down',1) - i0=nint(xdt*fs2) !Initial guess for start of signal + i0=nint((xdt+0.5)*fs2) !Initial guess for start of signal smax=0.0 - do idt=i0-16,i0+16 !Search over +/- half a symbol + do idt=i0-8,i0+8 !Search over +/- one quarter symbol call sync8d(cd0,idt,ctwk,0,sync) if(sync.gt.smax) then smax=sync @@ -78,6 +124,26 @@ subroutine ft8b(dd0,newdat,nfqso,ndepth,icand,sync0,f1,xdt,apsym,nharderrors, call four2a(csymb,32,1,-1,1) s2(0:7,k)=abs(csymb(1:8)) enddo + +! sync quality check + is1=0 + is2=0 + is3=0 + do k=1,7 + ip=maxloc(s2(:,k)) + if(icos7(k-1).eq.(ip(1)-1)) is1=is1+1 + ip=maxloc(s2(:,k+36)) + if(icos7(k-1).eq.(ip(1)-1)) is2=is2+1 + ip=maxloc(s2(:,k+72)) + if(icos7(k-1).eq.(ip(1)-1)) is3=is3+1 + enddo +! hard sync sum - max is 21 + nsync=is1+is2+is3 + if(nsync .le. 6) then ! bail out + nbadcrc=1 + return + endif + j=0 do k=1,NN if(k.le.7) cycle @@ -93,9 +159,54 @@ subroutine ft8b(dd0,newdat,nfqso,ndepth,icand,sync0,f1,xdt,apsym,nharderrors, r1=max(ps(1),ps(3),ps(5),ps(7))-max(ps(0),ps(2),ps(4),ps(6)) r2=max(ps(2),ps(3),ps(6),ps(7))-max(ps(0),ps(1),ps(4),ps(5)) r4=max(ps(4),ps(5),ps(6),ps(7))-max(ps(0),ps(1),ps(2),ps(3)) - rxdata(3*j-2)=r4 - rxdata(3*j-1)=r2 - rxdata(3*j)=r1 + i4=3*j-2 + i2=3*j-1 + i1=3*j + rxdata(i4)=r4 + rxdata(i2)=r2 + rxdata(i1)=r1 + rxdatap(i4)=r4 + rxdatap(i2)=r2 + rxdatap(i1)=r1 + + if(nQSOProgress .eq. 0 .or. nQSOProgress .eq. 5) then +! When bits 88:115 are set as ap bits, bit 115 lives in symbol 39 along +! with no-ap bits 116 and 117. Take care of metrics for bits 116 and 117. + if(j.eq.39) then ! take care of bits that live in symbol 39 + if(apsym(28).lt.0) then + rxdatap(i2)=max(ps(2),ps(3))-max(ps(0),ps(1)) + rxdatap(i1)=max(ps(1),ps(3))-max(ps(0),ps(2)) + else + rxdatap(i2)=max(ps(6),ps(7))-max(ps(4),ps(5)) + rxdatap(i1)=max(ps(5),ps(7))-max(ps(4),ps(6)) + endif + endif + endif + +! When bits 116:143 are set as ap bits, bit 115 lives in symbol 39 along +! with ap bits 116 and 117. Take care of metric for bit 115. +! if(j.eq.39) then ! take care of bit 115 +! iii=2*(apsym(29)+1)/2 + (apsym(30)+1)/2 ! known values of bits 116 & 117 +! if(iii.eq.0) rxdatap(i4)=ps(4)-ps(0) +! if(iii.eq.1) rxdatap(i4)=ps(5)-ps(1) +! if(iii.eq.2) rxdatap(i4)=ps(6)-ps(2) +! if(iii.eq.3) rxdatap(i4)=ps(7)-ps(3) +! endif + +! bit 144 lives in symbol 48 and will be 1 if it is set as an ap bit. +! take care of metrics for bits 142 and 143 + if(j.eq.48) then ! bit 144 is always 1 + rxdatap(i4)=max(ps(5),ps(7))-max(ps(1),ps(3)) + rxdatap(i2)=max(ps(3),ps(7))-max(ps(1),ps(5)) + endif + +! bit 154 lives in symbol 52 and will be 0 if it is set as an ap bit +! take care of metrics for bits 155 and 156 + if(j.eq.52) then ! bit 154 will be 0 if it is set as an ap bit. + rxdatap(i2)=max(ps(2),ps(3))-max(ps(0),ps(1)) + rxdatap(i1)=max(ps(1),ps(3))-max(ps(0),ps(2)) + endif + enddo rxav=sum(rxdata)/(3.0*ND) @@ -107,84 +218,141 @@ subroutine ft8b(dd0,newdat,nfqso,ndepth,icand,sync0,f1,xdt,apsym,nharderrors, rxsig=sqrt(rx2av) endif rxdata=rxdata/rxsig - ss=0.84 - llr=2.0*rxdata/(ss*ss) +! Let's just assume that rxsig is OK for rxdatap too... + rxdatap=rxdatap/rxsig -! do iap=0,3 - do iap=0,0 !### Temporary ### - if(iap.eq.0) then - apmask=0 - apmask(160:162)=1 - llrap=llr - llrap(160:162)=5.0*apsym(73:75)/ss - elseif(iap.eq.1) then - apmask=0 - apmask(88:115)=1 ! mycall - apmask(160:162)=1 ! 3 extra bits - llrap=0.0 - llrap(88:115)=5.0*apsym(1:28)/ss - llrap(160:162)=5.0*apsym(73:75)/ss - where(apmask.eq.0) llrap=llr - elseif(iap.eq.2) then - apmask=0 - apmask(88:115)=1 ! mycall - apmask(116:143)=1 ! hiscall - apmask(160:162)=1 ! 3 extra bits - llrap=0.0 - llrap(88:143)=5.0*apsym(1:56)/ss - llrap(160:162)=5.0*apsym(73:75)/ss - where(apmask.eq.0) llrap=llr - elseif(iap.eq.3) then - apmask=0 - apmask(88:115)=1 ! mycall - apmask(116:143)=1 ! hiscall - apmask(144:154)=1 ! RRR or 73 - apmask(160:162)=1 ! 3 extra bits - llrap=0.0 - llrap(88:143)=5.0*apsym(1:56)/ss - llrap(144:154)=5.0*rr73/ss - llrap(160:162)=5.0*apsym(73:75)/ss - where(apmask.eq.0) llrap=llr - endif - cw=0 - call timer('bpd174 ',0) - call bpdecode174(llrap,apmask,max_iterations,decoded,cw,nharderrors) - call timer('bpd174 ',1) - dmin=0.0 - if(nharderrors.lt.0 .and. ndepth.ge.2) then - call timer('osd174 ',0) - call osd174(llrap,norder,decoded,cw,nharderrors,dmin) - call timer('osd174 ',1) - endif - nbadcrc=1 - message=' ' - xsnr=-99.0 - if(count(cw.eq.0).eq.174) cycle !Reject the all-zero codeword - if( nharderrors.ge.0 .and. dmin.le.30.0 .and. nharderrors .lt. 30) then - call chkcrc12a(decoded,nbadcrc) - else - nharderrors=-1 - cycle - endif - if(nbadcrc.eq.0) then - call extractmessage174(decoded,message,ncrcflag,recent_calls,nrecent) - call genft8(message,msgsent,msgbits,itone) -! call subtractft8(dd0,itone,f1,xdt2) - xsig=0.0 - xnoi=0.0 - do i=1,79 - xsig=xsig+s2(itone(i),i)**2 - ios=mod(itone(i)+4,7) - xnoi=xnoi+s2(ios,i)**2 - enddo - xsnr=0.001 - if( xnoi.gt.0 .and. xnoi.lt.xsig ) xsnr=xsig/xnoi-1.0 - xsnr=10.0*log10(xsnr)-27.0 - if( xsnr .lt. -24.0 ) xsnr=-24.0 -! write(50,3050) icand,sync0,f1,xdt,nharderrors,dmin,message,iap -!3050 format(i3,3f10.3,i5,f10.3,2x,a22,i3) - return - endif + ss=0.84 + llr0=2.0*rxdata/(ss*ss) + llra=2.0*rxdatap/(ss*ss) ! llr's for use with ap + apmag=4.0 + +! pass # +!------------------------------ +! 1 regular decoding +! 2 erase 24 +! 3 erase 48 +! 4 ap pass 1 +! 5 ap pass 2 +! 6 ap pass 3 +! 7 ap pass 4, etc. + + if(lapon) then + npasses=3+nappasses(nQSOProgress) + else + npasses=3 + endif + + do ipass=1,npasses + + llr=llr0 + if(ipass.ne.2 .and. ipass.ne.3) nblank=0 + if(ipass.eq.2) nblank=24 + if(ipass.eq.3) nblank=48 + if(nblank.gt.0) llr(1:nblank)=0. + + if(ipass.le.3) then + apmask=0 + llrap=llr + iaptype=0 + endif + + if(ipass .gt. 3) then + iaptype=naptypes(nQSOProgress,ipass-3) + if(iaptype.ge.3 .and. (abs(f1-nfqso).gt.napwid .and. abs(f1-nftx).gt.napwid) ) cycle + if(iaptype.eq.1 .or. iaptype.eq.2 ) then ! AP,???,??? + apmask=0 + apmask(88:115)=1 ! first 28 bits are AP + apmask(144)=1 ! not free text + llrap=llr + if(iaptype.eq.1) llrap(88:115)=apmag*mcq/ss + if(iaptype.eq.2) llrap(88:115)=apmag*apsym(1:28)/ss + llrap(116:117)=llra(116:117) + llrap(142:143)=llra(142:143) + llrap(144)=-apmag/ss + endif + if(iaptype.eq.3) then ! mycall, dxcall, ??? + apmask=0 + apmask(88:115)=1 ! mycall + apmask(116:143)=1 ! hiscall + apmask(144)=1 ! not free text + llrap=llr + llrap(88:143)=apmag*apsym(1:56)/ss + llrap(144)=-apmag/ss + endif + if(iaptype.eq.4 .or. iaptype.eq.5 .or. iaptype.eq.6) then + apmask=0 + apmask(88:115)=1 ! mycall + apmask(116:143)=1 ! hiscall + apmask(144:159)=1 ! RRR or 73 or RR73 + llrap=llr + llrap(88:143)=apmag*apsym(1:56)/ss + if(iaptype.eq.4) llrap(144:159)=apmag*mrrr/ss + if(iaptype.eq.5) llrap(144:159)=apmag*m73/ss + if(iaptype.eq.6) llrap(144:159)=apmag*mrr73/ss + endif + if(iaptype.eq.7) then ! ???, dxcall, ??? + apmask=0 + apmask(116:143)=1 ! hiscall + apmask(144)=1 ! not free text + llrap=llr + llrap(115)=llra(115) + llrap(116:143)=apmag*apsym(29:56)/ss + llrap(144)=-apmag/ss + endif + endif + + cw=0 + call timer('bpd174 ',0) + call bpdecode174(llrap,apmask,max_iterations,decoded,cw,nharderrors, & + niterations) + call timer('bpd174 ',1) + dmin=0.0 + if(ndepth.eq.3 .and. nharderrors.lt.0) then + norder=1 + if(abs(nfqso-f1).le.napwid .or. abs(nftx-f1).le.napwid) then + if(ipass.le.3 .and. .not.nagain) then + norder=2 + else ! norder=3 for nagain and AP decodes + norder=3 + endif + endif + call timer('osd174 ',0) + call osd174(llrap,apmask,norder,decoded,cw,nharderrors,dmin) + call timer('osd174 ',1) + endif + nbadcrc=1 + message=' ' + xsnr=-99.0 + if(count(cw.eq.0).eq.174) cycle !Reject the all-zero codeword + if(any(decoded(75:75).ne.0)) cycle !Reject if any of the 3 extra bits are nonzero + if(nharderrors.ge.0 .and. nharderrors+dmin.lt.60.0 .and. & + .not.(sync.lt.2.0 .and. nharderrors.gt.35) .and. & + .not.(ipass.gt.1 .and. nharderrors.gt.39) .and. & + .not.(ipass.eq.3 .and. nharderrors.gt.30) & + ) then + call chkcrc12a(decoded,nbadcrc) + else + nharderrors=-1 + cycle + endif + if(nbadcrc.eq.0) then + call extractmessage174(decoded,message,ncrcflag,recent_calls,nrecent) + call genft8(message,mygrid6,bcontest,msgsent,msgbits,itone) + if(lsubtract) call subtractft8(dd0,itone,f1,xdt2) + xsig=0.0 + xnoi=0.0 + do i=1,79 + xsig=xsig+s2(itone(i),i)**2 + ios=mod(itone(i)+4,7) + xnoi=xnoi+s2(ios,i)**2 + enddo + xsnr=0.001 + if(xnoi.gt.0 .and. xnoi.lt.xsig) xsnr=xsig/xnoi-1.0 + xsnr=10.0*log10(xsnr)-27.0 + if(xsnr .lt. -24.0) xsnr=-24.0 + return + endif enddo + return end subroutine ft8b diff --git a/lib/fsk4hf/ft8sim.f90 b/lib/fsk4hf/ft8sim.f90 index 07041acab..2801717d4 100644 --- a/lib/fsk4hf/ft8sim.f90 +++ b/lib/fsk4hf/ft8sim.f90 @@ -8,19 +8,23 @@ program ft8sim type(hdr) h !Header for .wav file character arg*12,fname*17,sorm*1 character msg*22,msgsent*22 + character*6 mygrid6 + logical bcontest complex c0(0:NMAX-1) complex c(0:NMAX-1) integer itone(NN) integer*1 msgbits(KK) - integer*2 iwave(NMAX) !Generated full-length waveform + integer*2 iwave(NMAX) !Generated full-length waveform + data mygrid6/'EM48 '/ ! Get command-line argument(s) nargs=iargc() if(nargs.ne.8) then - print*,'Usage: ft8sim "message" sorm f0 DT fdop del nfiles snr' + print*,'Usage: ft8sim "message" s|m f0 DT fdop del nfiles snr' print*,'Example: ft8sim "K1ABC W9XYZ EN37" m 1500.0 0.0 0.1 1.0 10 -18' - print*,'sorm: "s" for single signal at 1500 Hz, "m" for 25 signals' + print*,'s|m: "s" for single signal at 1500 Hz, "m" for 25 signals' print*,'f0 is ignored when sorm = m' + print*,'Make nfiles negative to invoke 72-bit contest mode.' go to 999 endif call getarg(1,msg) !Message to be transmitted @@ -48,6 +52,8 @@ program ft8sim call getarg(8,arg) read(arg,*) snrdb !SNR_2500 + bcontest=nfiles.lt.0 + nfiles=abs(nfiles) twopi=8.0*atan(1.0) fs=12000.0 !Sample rate (Hz) dt=1.0/fs !Sample interval (s) @@ -60,7 +66,8 @@ program ft8sim if(snrdb.gt.90.0) sig=1.0 txt=NN*NSPS/12000.0 - call genft8(msg,msgsent,msgbits,itone) !Source-encode, then get itone() +! Source-encode, then get itone() + call genft8(msg,mygrid6,bcontest,msgsent,msgbits,itone) write(*,1000) f0,xdt,txt,snrdb,bw,msgsent 1000 format('f0:',f9.3,' DT:',f6.2,' TxT:',f6.1,' SNR:',f6.1, & ' BW:',f4.1,2x,a22) diff --git a/lib/fsk4hf/genft8.f90 b/lib/fsk4hf/genft8.f90 index c5306cc8e..9c7962b98 100644 --- a/lib/fsk4hf/genft8.f90 +++ b/lib/fsk4hf/genft8.f90 @@ -1,4 +1,4 @@ -subroutine genft8(msg,msgsent,msgbits,itone) +subroutine genft8(msg,mygrid,bcontest,msgsent,msgbits,itone) ! Encode an FT8 message, producing array itone(). @@ -6,8 +6,10 @@ subroutine genft8(msg,msgsent,msgbits,itone) use packjt include 'ft8_params.f90' character*22 msg,msgsent + character*6 mygrid,g1,g2,g3,g4 character*87 cbits -! logical checksumok + logical*1 bcontest + logical isgrid integer*4 i4Msg6BitWords(12) !72-bit message as 6-bit words integer*1 msgbits(KK),codeword(3*ND) integer*1, target:: i1Msg8BitBytes(11) @@ -15,8 +17,42 @@ subroutine genft8(msg,msgsent,msgbits,itone) integer icos7(0:6) data icos7/2,5,6,0,4,1,3/ !Costas 7x7 tone pattern + isgrid(g1)=g1(1:1).ge.'A' .and. g1(1:1).le.'R' .and. g1(2:2).ge.'A' .and. & + g1(2:2).le.'R' .and. g1(3:3).ge.'0' .and. g1(3:3).le.'9' .and. & + g1(4:4).ge.'0' .and. g1(4:4).le.'9' .and. g1(1:4).ne.'RR73' + + if(bcontest) then + i0=index(msg,' R ') + 3 !Check for ' R ' in message + g1=msg(i0:i0+3)//' ' + if(isgrid(g1)) then !Check for ' R grid' + call grid2deg(g1,dlong,dlat) + dlong=dlong+180.0 + if(dlong.gt.180.0) dlong=dlong-360.0 + dlat=-dlat + call deg2grid(dlong,dlat,g2) !g2=antipodes grid + msg=msg(1:i0-3)//g2(1:4) !Send message with g2 + endif + endif + call packmsg(msg,i4Msg6BitWords,itype) !Pack into 12 6-bit bytes call unpackmsg(i4Msg6BitWords,msgsent) !Unpack to get msgsent + + if(bcontest) then + i1=index(msgsent(8:22),' ') + 8 + g3=msgsent(i1:i1+3)//' ' + if(isgrid(g3)) then + call azdist(mygrid,g3,0.d0,nAz,nEl,nDmiles,nDkm,nHotAz,nHotABetter) + if(ndkm.gt.10000) then + call grid2deg(g3,dlong,dlat) + dlong=dlong+180.0 + if(dlong.gt.180.0) dlong=dlong-360.0 + dlat=-dlat + call deg2grid(dlong,dlat,g4) + msgsent=msgsent(1:i1-1)//'R '//g4(1:4) + endif + endif + endif + i3bit=0 !### temporary ### write(cbits,1000) i4Msg6BitWords,32*i3bit 1000 format(12b6.6,b8.8) diff --git a/lib/fsk4hf/osd174.f90 b/lib/fsk4hf/osd174.f90 index 1056cf61c..a54a48cf7 100644 --- a/lib/fsk4hf/osd174.f90 +++ b/lib/fsk4hf/osd174.f90 @@ -1,9 +1,10 @@ -subroutine osd174(llr,norder,decoded,cw,nhardmin,dmin) +subroutine osd174(llr,apmask,norder,decoded,cw,nhardmin,dmin) ! ! An ordered-statistics decoder for the (174,87) code. ! include "ldpc_174_87_params.f90" +integer*1 apmask(N),apmaskr(N) integer*1 gen(K,N) integer*1 genmrb(K,N),g2(N,K) integer*1 temp(K),m0(K),me(K),mi(K) @@ -36,6 +37,8 @@ endif ! re-order received vector to place systematic msg bits at the end rx=llr(colorder+1) +apmaskr=apmask(colorder+1) + ! hard decode the received word hdec=0 @@ -71,7 +74,7 @@ do id=1,K ! diagonal element indices endif do ii=1,K if( ii .ne. id .and. genmrb(ii,id) .eq. 1 ) then - genmrb(ii,1:N)=mod(genmrb(ii,1:N)+genmrb(id,1:N),2) + genmrb(ii,1:N)=ieor(genmrb(ii,1:N),genmrb(id,1:N)) endif enddo exit @@ -91,6 +94,7 @@ hdec=hdec(indices) ! hard decisions from received symbols m0=hdec(1:K) ! zero'th order message absrx=absrx(indices) rx=rx(indices) +apmaskr=apmaskr(indices) s1=sum(absrx(1:K)) s2=sum(absrx(K+1:N)) @@ -110,22 +114,24 @@ do iorder=1,norder mi(K-iorder+1:K)=1 iflag=0 do while(iflag .ge. 0 ) - dpat=sum(mi*absrx(1:K)) - nt=nt+1 - if( dpat .lt. thresh ) then ! reject unlikely error patterns - me=ieor(m0,mi) - call mrbencode(me,ce,g2,N,K) - nxor=ieor(ce,hdec) - dd=sum(nxor*absrx) - if( dd .lt. dmin ) then - dmin=dd - cw=ce - nhardmin=sum(nxor) - thresh=rho*dmin + if(all(iand(apmaskr(1:K),mi).eq.0)) then ! reject patterns with ap bits + dpat=sum(mi*absrx(1:K)) + nt=nt+1 + if( dpat .lt. thresh ) then ! reject unlikely error patterns + me=ieor(m0,mi) + call mrbencode(me,ce,g2,N,K) + nxor=ieor(ce,hdec) + dd=sum(nxor*absrx) + if( dd .lt. dmin ) then + dmin=dd + cw=ce + nhardmin=sum(nxor) + thresh=rho*dmin + endif + else + nrejected=nrejected+1 endif - else - nrejected=nrejected+1 - endif + endif ! get the next test error pattern, iflag will go negative ! when the last pattern with weight iorder has been generated call nextpat(mi,k,iorder,iflag) diff --git a/lib/fsk4hf/subtractft8.f90 b/lib/fsk4hf/subtractft8.f90 index dcc089031..1031cf2b7 100644 --- a/lib/fsk4hf/subtractft8.f90 +++ b/lib/fsk4hf/subtractft8.f90 @@ -10,7 +10,7 @@ subroutine subtractft8(dd,itone,f0,dt) use timer_module, only: timer parameter (NMAX=15*12000,NFRAME=1920*79) - parameter (NFFT=NMAX,NFILT=400) + parameter (NFFT=NMAX,NFILT=1400) real*4 dd(NMAX), window(-NFILT/2:NFILT/2) complex cref,camp,cfilt,cw integer itone(79) diff --git a/lib/fsk4hf/sync8.f90 b/lib/fsk4hf/sync8.f90 index 35c402f58..0813d7f88 100644 --- a/lib/fsk4hf/sync8.f90 +++ b/lib/fsk4hf/sync8.f90 @@ -1,7 +1,8 @@ -subroutine sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) +subroutine sync8(dd,nfa,nfb,syncmin,nfqso,s,candidate,ncand) include 'ft8_params.f90' - parameter (JZ=31) !DT up to +/- 2.5 s +! Search over +/- 1.5s relative to 0.5s TX start time. + parameter (JZ=38) complex cx(0:NH1) real s(NH1,NHSYM) real savg(NH1) @@ -18,16 +19,13 @@ subroutine sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) data icos7/2,5,6,0,4,1,3/ !Costas 7x7 tone pattern equivalence (x,cx) -! Compute symbol spectra at half-symbol steps. +! Compute symbol spectra, stepping by NSTEP steps. savg=0. - istep=NSPS/2 !960 - tstep=istep/12000.0 !0.08 s + tstep=NSTEP/12000.0 df=12000.0/NFFT1 !3.125 Hz - -! Compute symbol spectra at half-symbol steps fac=1.0/300.0 do j=1,NHSYM - ia=(j-1)*istep + 1 + ia=(j-1)*NSTEP + 1 ib=ia+NSPS-1 x(1:NSPS)=fac*dd(ia:ib) x(NSPS+1:)=0. @@ -45,25 +43,41 @@ subroutine sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) ia=max(1,nint(nfa/df)) ib=nint(nfb/df) + nssy=NSPS/NSTEP ! # steps per symbol + nfos=NFFT1/NSPS ! # frequency bin oversampling factor + jstrt=0.5/tstep + do i=ia,ib - do j=-JZ,JZ - t=0. - t0=0. + do j=-JZ,+JZ + ta=0. + tb=0. + tc=0. + t0a=0. + t0b=0. + t0c=0. do n=0,6 - k=j+2*n - if(k.ge.1) then - t=t + s(i+2*icos7(n),k) - t0=t0 + sum(s(i:i+12:2,k)) + k=j+jstrt+nssy*n + if(k.ge.1.and.k.le.NHSYM) then + ta=ta + s(i+nfos*icos7(n),k) + t0a=t0a + sum(s(i:i+nfos*6:nfos,k)) endif - t=t + s(i+2*icos7(n),k+72) - t0=t0 + sum(s(i:i+12:2,k+72)) - if(k+144.le.NHSYM) then - t=t + s(i+2*icos7(n),k+144) - t0=t0 + sum(s(i:i+12:2,k+144)) + tb=tb + s(i+nfos*icos7(n),k+nssy*36) + t0b=t0b + sum(s(i:i+nfos*6:nfos,k+nssy*36)) + if(k+nssy*72.le.NHSYM) then + tc=tc + s(i+nfos*icos7(n),k+nssy*72) + t0c=t0c + sum(s(i:i+nfos*6:nfos,k+nssy*72)) endif enddo + t=ta+tb+tc + t0=t0a+t0b+t0c t0=(t0-t)/6.0 - sync2d(i,j)=t/t0 + sync_abc=t/t0 + + t=tb+tc + t0=t0b+t0c + t0=(t0-t)/6.0 + sync_bc=t/t0 + sync2d(i,j)=max(sync_abc,sync_bc) enddo enddo @@ -84,8 +98,7 @@ subroutine sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) candidate0=0. k=0 - syncmin=2.0 - do i=1,100 + do i=1,200 n=ia + indx(iz+1-i) - 1 if(red(n).lt.syncmin) exit if(k.lt.200) k=k+1 @@ -115,13 +128,21 @@ subroutine sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) fac=20.0/maxval(s) s=fac*s +! Sort by sync +! call indexx(candidate0(3,1:ncand),ncand,indx) +! Sort by frequency call indexx(candidate0(1,1:ncand),ncand,indx) + k=1 +! do i=ncand,1,-1 do i=1,ncand j=indx(i) - candidate(1,i)=abs(candidate0(1,j)) - candidate(2,i)=candidate0(2,j) - candidate(3,i)=candidate0(3,j) + if( candidate0(3,j) .ge. syncmin .and. candidate0(2,j).ge.-1.5 ) then + candidate(1,k)=abs(candidate0(1,j)) + candidate(2,k)=candidate0(2,j) + candidate(3,k)=candidate0(3,j) + k=k+1 + endif enddo - + ncand=k-1 return end subroutine sync8 diff --git a/lib/ft8_decode.f90 b/lib/ft8_decode.f90 index bb751a539..845896b9d 100644 --- a/lib/ft8_decode.f90 +++ b/lib/ft8_decode.f90 @@ -7,7 +7,7 @@ module ft8_decode end type ft8_decoder abstract interface - subroutine ft8_decode_callback (this,sync,snr,dt,freq,nbadcrc,decoded) + subroutine ft8_decode_callback (this,sync,snr,dt,freq,decoded,nap,qual) import ft8_decoder implicit none class(ft8_decoder), intent(inout) :: this @@ -15,93 +15,125 @@ module ft8_decode integer, intent(in) :: snr real, intent(in) :: dt real, intent(in) :: freq - integer, intent(in) :: nbadcrc character(len=22), intent(in) :: decoded + integer, intent(in) :: nap + real, intent(in) :: qual end subroutine ft8_decode_callback end interface contains - subroutine decode(this,callback,iwave,nfqso,newdat,nutc,nfa, & - nfb,nagain,ndepth,nsubmode,mycall12,hiscall12,hisgrid6) -!use wavhdr + subroutine decode(this,callback,iwave,nQSOProgress,nfqso,nftx,newdat, & + nutc,nfa,nfb,nexp_decode,ndepth,nagain,lapon,napwid,mycall12, & + mygrid6,hiscall12,hisgrid6) +! use wavhdr use timer_module, only: timer include 'fsk4hf/ft8_params.f90' -!type(hdr) h +! type(hdr) h class(ft8_decoder), intent(inout) :: this procedure(ft8_decode_callback) :: callback real s(NH1,NHSYM) real candidate(3,200) real dd(15*12000) - logical, intent(in) :: newdat, nagain + logical, intent(in) :: lapon,nagain + logical newdat,lsubtract,ldupe,bcontest character*12 mycall12, hiscall12 - character*6 hisgrid6 + character*6 mygrid6,hisgrid6 integer*2 iwave(15*12000) integer apsym(KK) character datetime*13,message*22 + character*22 allmessages(100) + integer allsnrs(100) save s,dd + bcontest=iand(nexp_decode,128).ne.0 this%callback => callback write(datetime,1001) nutc !### TEMPORARY ### 1001 format("000000_",i6.6) - if(index(hisgrid6," ").eq.0) hisgrid6="EN50" - call ft8apset(mycall12,hiscall12,hisgrid6,apsym) - + call ft8apset(mycall12,mygrid6,hiscall12,hisgrid6,bcontest,apsym,iaptype) dd=iwave - call timer('sync8 ',0) - call sync8(dd,nfa,nfb,nfqso,s,candidate,ncand) - call timer('sync8 ',1) + ndecodes=0 + allmessages=' ' + allsnrs=0 + ifa=nfa + ifb=nfb + if(nagain) then + ifa=nfqso-10 + ifb=nfqso+10 + endif - syncmin=2.0 - do icand=1,ncand - sync=candidate(3,icand) - if(sync.lt.syncmin) cycle - f1=candidate(1,icand) - xdt=candidate(2,icand) - nsnr0=min(99,nint(10.0*log10(sync) - 25.5)) !### empirical ### - call timer('ft8b ',0) - call ft8b(dd,newdat,nfqso,ndepth,icand,sync,f1,xdt,apsym,nharderrors, & - dmin,nbadcrc,message,xsnr) - nsnr=xsnr - xdt=xdt-0.6 - call timer('ft8b ',1) - if (associated(this%callback)) call this%callback(sync,nsnr,xdt, & - f1,nbadcrc,message) -! write(*,'(f7.2,i5,f7.2,f9.1,i5,f7.2,2x,a22)') sync,nsnr,xdt,f1,nharderrors,dmin,message -! write(13,1110) datetime,0,nsnr,xdt,f1,nharderrors,dmin,message -!1110 format(a13,2i4,f6.2,f7.1,i4,' ~ ',f6.2,2x,a22,' FT8') -! write(51,3051) xdt,f1,sync,dmin,nsnr,nharderrors,nbadcrc,message -!3051 format(4f9.1,3i5,2x,a22) -! flush(51) - enddo -!h=default_header(12000,NMAX) -!open(10,file='subtract.wav',status='unknown',access='stream') -!iwave=nint(dd) -!write(10) h,iwave -!close(10) - return +! For now: +! ndepth=1: no subtraction, 1 pass, belief propagation only +! ndepth=2: subtraction, 2 passes, belief propagation only +! ndepth=3: subtraction, 2 passes, bp+osd2 at and near nfqso + if(ndepth.eq.1) npass=1 + if(ndepth.ge.2) npass=2 + do ipass=1,npass + newdat=.true. ! Is this a problem? I hijacked newdat. + if(ipass.eq.1) then + lsubtract=.true. + if(ndepth.eq.1) lsubtract=.false. + syncmin=1.5 + else + lsubtract=.false. + syncmin=1.5 + endif + call timer('sync8 ',0) + call sync8(dd,ifa,ifb,syncmin,nfqso,s,candidate,ncand) + call timer('sync8 ',1) + do icand=1,ncand + sync=candidate(3,icand) + f1=candidate(1,icand) + xdt=candidate(2,icand) + nsnr0=min(99,nint(10.0*log10(sync) - 25.5)) !### empirical ### + call timer('ft8b ',0) + call ft8b(dd,newdat,nQSOProgress,nfqso,nftx,ndepth,lapon,napwid, & + lsubtract,nagain,iaptype,mygrid6,bcontest,sync,f1,xdt,apsym, & + nharderrors,dmin,nbadcrc,iappass,iera,message,xsnr) + nsnr=nint(xsnr) + xdt=xdt-0.5 + hd=nharderrors+dmin + call timer('ft8b ',1) + if(nbadcrc.eq.0) then + call jtmsg(message,iflag) + if(bcontest) call fix_contest_msg(mygrid6,message) + if(iand(iflag,16).ne.0) message(22:22)='?' + if(iand(iflag,15).eq.0) then + ldupe=.false. + do id=1,ndecodes + if(message.eq.allmessages(id).and.nsnr.le.allsnrs(id)) ldupe=.true. + enddo + if(.not.ldupe) then + ndecodes=ndecodes+1 + allmessages(ndecodes)=message + allsnrs(ndecodes)=nsnr + endif +! write(81,1004) nutc,ncand,icand,ipass,iaptype,iappass, & +! iflag,nharderrors,dmin,hd,min(sync,999.0),nint(xsnr), & +! xdt,nint(f1),message +! flush(81) + if(.not.ldupe .and. associated(this%callback)) then + qual=1.0-(nharderrors+dmin)/60.0 ! scale qual to [0.0,1.0] + call this%callback(sync,nsnr,xdt,f1,message,iaptype,qual) + endif + else + write(19,1004) nutc,ncand,icand,ipass,iaptype,iappass, & + iflag,nharderrors,dmin,hd,min(sync,999.0),nint(xsnr), & + xdt,nint(f1),message +1004 format(i6.6,2i4,3i2,2i3,3f6.1,i4,f6.2,i5,2x,a22) + flush(19) + endif + endif + enddo +! h=default_header(12000,NMAX) +! open(10,file='subtract.wav',status='unknown',access='stream') +! iwave=nint(dd) +! write(10) h,iwave +! close(10) + enddo + return end subroutine decode end module ft8_decode - -subroutine ft8apset(mycall12,hiscall12,hisgrid6,apsym) - parameter(NAPM=4,KK=87) - character*12 mycall12,hiscall12 - character*22 msg,msgsent - character*6 mycall,hiscall - character*6 hisgrid6 - character*4 hisgrid - integer apsym(KK) - integer*1 msgbits(KK) - integer itone(KK) - - mycall=mycall12(1:6) - hiscall=hiscall12(1:6) - hisgrid=hisgrid6(1:4) - msg=mycall//' '//hiscall//' '//hisgrid - call genft8(msg,msgsent,msgbits,itone) - apsym=2*msgbits-1 - return - end subroutine ft8apset diff --git a/lib/genmsk144.f90 b/lib/genmsk144.f90 index 9496e8837..7d4d200e7 100644 --- a/lib/genmsk144.f90 +++ b/lib/genmsk144.f90 @@ -41,13 +41,17 @@ subroutine genmsk144(msg0,mygrid,ichk,bcontest,msgsent,i4tone,itype) data first/.true./ save - if( first ) then + isgrid(g1)=g1(1:1).ge.'A' .and. g1(1:1).le.'R' .and. g1(2:2).ge.'A' .and. & + g1(2:2).le.'R' .and. g1(3:3).ge.'0' .and. g1(3:3).le.'9' .and. & + g1(4:4).ge.'0' .and. g1(4:4).le.'9' .and. g1(1:4).ne.'RR73' + + if(first) then first=.false. nsym=128 - pi=4.*atan(1.0) + pi=4.0*atan(1.0) twopi=8.*atan(1.0) do i=1,12 - pp(i)=sin( (i-1)*pi/12 ) + pp(i)=sin((i-1)*pi/12) enddo endif @@ -174,14 +178,3 @@ subroutine genmsk144(msg0,mygrid,ichk,bcontest,msgsent,i4tone,itype) 999 return end subroutine genmsk144 - -logical function isgrid(g1) - - character*4 g1 - - isgrid=g1(1:1).ge.'A' .and. g1(1:1).le.'R' .and. g1(2:2).ge.'A' .and. & - g1(2:2).le.'R' .and. g1(3:3).ge.'0' .and. g1(3:3).le.'9' .and. & - g1(4:4).ge.'0' .and. g1(4:4).le.'9' - - return -end function isgrid diff --git a/lib/jt9.f90 b/lib/jt9.f90 index 63a9443ef..469a07a1e 100644 --- a/lib/jt9.f90 +++ b/lib/jt9.f90 @@ -161,6 +161,8 @@ program jt9 ! Import FFTW wisdom, if available wisfile=trim(data_dir)//'/jt9_wisdom.dat'// C_NULL_CHAR iret=fftwf_import_wisdom_from_filename(wisfile) + open(19,file=trim(data_dir)//'/false_decodes.txt',status='unknown', & + position='append') ntry65a=0 ntry65b=0 @@ -187,7 +189,7 @@ program jt9 if(infile(i1-5:i1-5).eq.'_') then read(infile(i1-4:i1-1),*,err=1) nutc else - read(infile(i1-6:i1-3),*,err=1) nutc + read(infile(i1-6:i1-1),*,err=1) nutc endif go to 2 1 nutc=0 @@ -238,8 +240,8 @@ program jt9 ingain=0 call timer('symspec ',0) nminw=1 - call symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb,s,df3, & - ihsym,npts8,pxdbmax) + call symspec(shared_data,k,ntrperiod,nsps,ingain,nminw,pxdb, & + s,df3,ihsym,npts8,pxdbmax) call timer('symspec ',1) endif nhsym0=nhsym @@ -260,9 +262,11 @@ program jt9 shared_data%params%kin=64800 shared_data%params%nzhsym=181 shared_data%params%ndepth=ndepth + shared_data%params%lapon=.true. + shared_data%params%napwid=75 shared_data%params%dttol=3. - shared_data%params%minsync=0 !### TEST ONLY +! shared_data%params%minsync=0 !### TEST ONLY ! shared_data%params%nfqso=1500 !### TEST ONLY ! mycall="G3WDG " !### TEST ONLY ! hiscall="VK7MO " !### TEST ONLY diff --git a/lib/jt9com.f90 b/lib/jt9com.f90 index 94ea719b0..f42e55d1d 100644 --- a/lib/jt9com.f90 +++ b/lib/jt9com.f90 @@ -9,7 +9,9 @@ integer(c_int) :: nutc logical(c_bool) :: ndiskdat integer(c_int) :: ntr + integer(c_int) :: nQSOProgress ! See MainWindow::m_QSOProgress for values integer(c_int) :: nfqso + integer(c_int) :: nftx logical(c_bool) :: newdat integer(c_int) :: npts8 integer(c_int) :: nfa @@ -21,6 +23,8 @@ integer(c_int) :: nsubmode logical(c_bool) :: nagain integer(c_int) :: ndepth + logical(c_bool) :: lapon + integer(c_int) :: napwid integer(c_int) :: ntxmode integer(c_int) :: nmode integer(c_int) :: minw diff --git a/lib/jtmsg.f90 b/lib/jtmsg.f90 new file mode 100644 index 000000000..ff7483ad1 --- /dev/null +++ b/lib/jtmsg.f90 @@ -0,0 +1,131 @@ +subroutine jtmsg(msg,iflag) + +! Attempts to identify false decodes in JT-style messages. + +! Returns iflag with sum of bits as follows: +! ------------------------------------------ +! 1 Grid/Report invalid +! 2 Second callsign invalid +! 4 First callsign invalid +! 8 Very unlikely free text +! 16 Questionable free text +! 0 Message is probably OK +! ------------------------------------------ + + character*22 msg,t + character*13 w1,w2,w3,w + character*6 bc1,bc2,bc3 + character*1 c + logical c1ok,c2ok,c3ok,isdigit,isletter,isgrid4 + +! Statement functions + isdigit(c)=(ichar(c).ge.ichar('0')) .and. (ichar(c).le.ichar('9')) + isletter(c)=(ichar(c).ge.ichar('A')) .and. (ichar(c).le.ichar('Z')) + isgrid4(w)=(len_trim(w).eq.4 .and. & + ichar(w(1:1)).ge.ichar('A') .and. ichar(w(1:1)).le.ichar('R') .and. & + ichar(w(2:2)).ge.ichar('A') .and. ichar(w(2:2)).le.ichar('R') .and. & + ichar(w(3:3)).ge.ichar('0') .and. ichar(w(3:3)).le.ichar('9') .and. & + ichar(w(4:4)).ge.ichar('0') .and. ichar(w(4:4)).le.ichar('9')) + + t=trim(msg) !Temporary copy of msg + nt=len_trim(t) + +! Check for standard messages +! Insert underscore in "CQ AA " to "CQ ZZ ", "CQ nnn " to make them one word. + if(t(1:3).eq.'CQ ' .and. isletter(t(4:4)) .and. & + isletter(t(5:5)) .and. t(6:6).eq.' ') t(3:3)='_' + if(t(1:3).eq.'CQ ' .and. isdigit(t(4:4)) .and. & + isdigit(t(5:5)) .and. isdigit(t(6:6)) .and. t(7:7).eq.' ') t(3:3)='_' + +! Parse first three words + w1=' ' + w2=' ' + w3=' ' + i1=index(t,' ') + if(i1.gt.0) w1(1:i1-1)=t(1:i1-1) + t=t(i1+1:) + i2=index(t,' ') + if(i2.gt.0) w2(1:i2-1)=t(1:i2-1) + t=t(i2+1:) + i3=index(t,' ') + if(i3.gt.0) w3(1:i3-1)=t(1:i3-1) + + if(w1(1:3).eq.'CQ ' .or. w1(1:3).eq.'CQ_' .or. w1(1:3).eq.'DE ' .or. & + w1(1:4).eq.'QRZ ') then +! CQ/DE/QRZ: Should have one good callsign in w2 and maybe a grid/rpt in w3 + call chkcall(w2,bc2,c2ok) + iflag=0 + if(.not.c2ok) iflag=iflag+2 + if(len_trim(w3).ne.0 .and. (.not.isgrid4(w3))) iflag=iflag+1 + if(w1(1:3).eq.'DE ' .and. c2ok) iflag=0 + if(iflag.eq.0) return + endif + +! Check for two calls and maybe a grid, rpt, R+rpr, RRR, or 73 + iflag=0 + call chkcall(w1,bc1,c1ok) + call chkcall(w2,bc2,c2ok) + if(.not.c1ok) iflag=iflag+4 + if(.not.c2ok) iflag=iflag+2 + if(len_trim(w3).ne.0 .and. (.not.isgrid4(w3)) .and. & + w3(1:1).ne.'+' .and. w3(1:1).ne.'-' .and. & + w3(1:2).ne.'R+' .and. w3(1:2).ne.'R-' .and. & + w3(1:3).ne.'73 ' .and. w3(1:4).ne.'RRR ') iflag=iflag+1 + call chkcall(w3,bc3,c3ok) +! Allow(?) non-standard messages of the form CQ AS OC K1JT + if(w1(1:3).eq.'CQ_'.and.isletter(w2(1:1)).and.isletter(w2(2:2)).and. & + w2(3:3).eq.' '.and.c3ok) iflag=0 + if(iflag.eq.0 .or. nt.gt.13) return + +! Check for plausible free text + + nc=0 + np=0 + do i=1,13 + c=msg(i:i) + if(c.ne.' ') nc=nc+1 !Number of non-blank characters + if(c.eq.'+') np=np+1 !Number of punctuation characters + if(c.eq.'-') np=np+1 + if(c.eq.'.') np=np+1 + if(c.eq.'/') np=np+1 + if(c.eq.'?') np=np+1 + enddo + nb=13-nc !Number of blanks + iflag=16 !Mark as potentially questionable + if(nc.ge.12 .or. (nc.ge.11 .and. np.gt.0)) then + iflag=8 !Unlikely free text, flag it + endif + +! Save messages containing some common words + if(msg(1:3).eq.'CQ ') iflag=0 + if(index(msg,'DE ').gt.0) iflag=0 + if(index(msg,'TU ').gt.0) iflag=0 + if(index(msg,' TU').gt.0) iflag=0 + if(index(msg,'73 ').gt.0) iflag=0 + if(index(msg,' 73').gt.0) iflag=0 + if(index(msg,'TNX').gt.0) iflag=0 + if(index(msg,'THX').gt.0) iflag=0 + if(index(msg,'EQSL').gt.0) iflag=0 + if(index(msg,'LOTW').gt.0) iflag=0 + if(index(msg,'DECOD').gt.0) iflag=0 + if(index(msg,'CHK').gt.0) iflag=0 + if(index(msg,'CLK').gt.0) iflag=0 + if(index(msg,'CLOCK').gt.0) iflag=0 + if(index(msg,'LOG').gt.0) iflag=0 + if(index(msg,'QRM').gt.0) iflag=0 + if(index(msg,'QSY').gt.0) iflag=0 + if(index(msg,'TEST').gt.0) iflag=0 + if(index(msg,'CQDX').gt.0) iflag=0 + if(index(msg,'CALL').gt.0) iflag=0 + if(index(msg,'QRZ').gt.0) iflag=0 + if(index(msg,'AUTO').gt.0) iflag=0 + if(index(msg,'PHOTO').gt.0) iflag=0 + if(index(msg,'HYBRID').gt.0) iflag=0 + + if(c1ok .and. w1(1:6).eq.bc1) iflag=0 + if(c2ok .and. w2(1:6).eq.bc2) iflag=0 + + if(nb.ge.4) iflag=0 + + return +end subroutine jtmsg diff --git a/lib/msk144d2.f90 b/lib/msk144d2.f90 index 88f1c436a..47eb43b68 100644 --- a/lib/msk144d2.f90 +++ b/lib/msk144d2.f90 @@ -9,6 +9,7 @@ program msk144d2 character c character*80 line + character*512 datadir character*500 infile character*12 mycall,hiscall character*6 mygrid @@ -17,7 +18,7 @@ program msk144d2 logical :: display_help=.false. logical*1 bShMsgs logical*1 bcontest - logical*1 brxequal + logical*1 btrain logical*1 bswl type(wav_header) :: wav @@ -25,6 +26,8 @@ program msk144d2 integer*2 id2(30*12000) integer*2 ichunk(7*1024) + real*8 pcoeffs(5) + type (option) :: long_options(9) = [ & option ('ndepth',.true.,'c','ndepth',''), & option ('dxcall',.true.,'d','hiscall',''), & @@ -45,8 +48,10 @@ program msk144d2 hiscall='' bShMsgs=.false. bcontest=.false. - brxequal=.false. + btrain=.false. bswl=.false. + datadir='.' + pcoeffs=0.d0 do call getopt('c:d:ef:hm:n:rs',long_options,c,optarg,narglen,nstat,noffset,nremain,.true.) @@ -69,7 +74,7 @@ program msk144d2 case ('n') read (optarg(:narglen), *) ntol case ('r') - brxequal=.true. + btrain=.true. case ('s') bShMsgs=.true. end select @@ -111,7 +116,7 @@ program msk144d2 tt=sum(float(abs(id2(i:i+7*512-1)))) if( tt .ne. 0.0 ) then call mskrtd(ichunk,nutc,tsec,ntol,nrxfreq,ndepth,mycall,mygrid,hiscall,bShMsgs, & - bcontest,brxequal,bswl,line) + bcontest,btrain,pcoeffs,bswl,datadir,line) if( index(line,"&") .ne. 0 .or. & index(line,"^") .ne. 0 .or. & index(line,"!") .ne. 0 .or. & diff --git a/lib/mskrtd.f90 b/lib/mskrtd.f90 index 26517bec4..71a1f0396 100644 --- a/lib/mskrtd.f90 +++ b/lib/mskrtd.f90 @@ -209,7 +209,7 @@ subroutine mskrtd(id2,nutc0,tsec,ntol,nrxfreq,ndepth,mycall,mygrid,hiscall, & if(.not. bshdecode) then call update_hasharray(recent_calls,nrecent,nhasharray) ! Should we call fix_contest_msg() only if bcontest is true? - call fix_contest_msg(mycall(1:6),mygrid,hiscall(1:6),msgreceived) + call fix_contest_msg(mygrid,msgreceived) endif write(line,1020) nutc0,nsnr,tdec,nint(fest),decsym,msgreceived, & navg,ncorrected,eyeopening,char(0) diff --git a/lib/packjt.f90 b/lib/packjt.f90 index 049af22ee..26a20cb94 100644 --- a/lib/packjt.f90 +++ b/lib/packjt.f90 @@ -1,5 +1,9 @@ module packjt +! These variables are accessible from outside via "use packjt": + integer jt_itype,jt_nc1,jt_nc2,jt_ng,jt_k1,jt_k2 + character*6 jt_c1,jt_c2,jt_c3 + contains subroutine packbits(dbits,nsymd,m0,sym) @@ -494,8 +498,17 @@ subroutine packbits(dbits,nsymd,m0,sym) ng=ng+32768 ! Encode data into 6-bit words - 20 continue +20 continue if(itype.ne.6) itype=max(nv2a,nv2b) + jt_itype=itype + jt_c1=c1 + jt_c2=c2 + jt_c3=c3 + jt_k1=k1 + jt_k2=k2 + jt_nc1=nc1 + jt_nc2=nc2 + jt_ng=ng dat(1)=iand(ishft(nc1,-22),63) !6 bits dat(2)=iand(ishft(nc1,-16),63) !6 bits dat(3)=iand(ishft(nc1,-10),63) !6 bits diff --git a/lib/refspectrum.f90 b/lib/refspectrum.f90 index 8d0730df9..bc5bede33 100644 --- a/lib/refspectrum.f90 +++ b/lib/refspectrum.f90 @@ -1,17 +1,14 @@ -subroutine refspectrum(id2,id2b,kk,bclear,brefspec,buseref,fname) +subroutine refspectrum(id2,bclear,brefspec,buseref,fname) ! Input: ! id2 i*2 Raw 16-bit integer data, 12000 Hz sample rate ! brefspec logical True when accumulating a reference spectrum parameter (NFFT=6912,NH=NFFT/2,NPOLYLOW=400,NPOLYHIGH=2600) - integer*2 id2(NFFT),id2b(120*12000) + integer*2 id2(NFFT) logical*1 bclear,brefspec,buseref,blastuse - real x0(0:NH-1) !Input samples - real x1(0:NH-1) !Output samples (delayed by one block) - real x0s(0:NH-1) !Saved upper half of input samples - real x1s(0:NH-1) !Saved upper half of output samples + real xs(0:NH-1) !Saved upper half of input chunk convolved with h(t) real x(0:NFFT-1) !Work array real*4 w(0:NFFT-1) !Window function real*4 s(0:NH) !Average spectrum @@ -19,6 +16,7 @@ subroutine refspectrum(id2,id2b,kk,bclear,brefspec,buseref,fname) real*8 xfit(1500),yfit(1500),sigmay(1500),a(5),chisqr !Polyfit arrays logical first complex cx(0:NH) !Complex frequency-domain work array + complex cfil(0:NH) character*(*) fname common/spectra/syellow(6827),ref(0:NH),filter(0:NH) equivalence(x,cx) @@ -34,8 +32,7 @@ subroutine refspectrum(id2,id2b,kk,bclear,brefspec,buseref,fname) nsave=0 s=0.0 filter=1.0 - x0s=0. - x1s=0. + xs=0. first=.false. endif if(bclear) s=0. @@ -134,21 +131,27 @@ subroutine refspectrum(id2,id2b,kk,bclear,brefspec,buseref,fname) 30 do i=1,NH read(16,1005,err=100,end=100) freq,s(i),ref(i),fil(i),filter(i) enddo +! Make the filter causal for overlap and add. + cx(0)=0.0 + cx(1:NH)=fil(1:NH)/NFFT + call four2a(x,NFFT,1,1,-1) + x=cshift(x,-400) + x(800:NH)=0.0 + call four2a(cx,NFFT,1,-1,0) + cfil=cx 100 close(16) 110 continue endif - x0=id2(1:NH) - x(0:NH-1)=x0s !Previous 2nd half to new 1st half - x(NH:NFFT-1)=x0 !New 2nd half - x0s=x0 !Save the new 2nd half - x=w*x !Apply window - call four2a(x,NFFT,1,-1,0) !r2c FFT (to frequency domain) - cx=fil*cx - call four2a(cx,NFFT,1,1,-1) !c2r FFT (back to time domain) - x1=x1s + x(0:NH-1) !Add previous segment's 2nd half -! id2(1:NH)=nint(x1) - if(kk.ge.6912) id2b(kk-6192+1:kk-6192+NH)=nint(x1) - x1s=x(NH:NFFT-1) !Save the new 2nd half +! Use overlap and add method to apply causal reference filter. + x(0:NH-1)=id2(1:NH) + x(NH:NFFT-1)=0.0 + x=x/NFFT + call four2a(x,NFFT,1,-1,0) + cx=cfil*cx + call four2a(cx,NFFT,1,1,-1) + x(0:NH-1)=x(0:NH-1)+xs + xs=x(NH:NFFT-1) + id2(1:NH)=nint(x(0:NH-1)) endif blastuse=buseref diff --git a/lib/t3.f90 b/lib/t3.f90 deleted file mode 100644 index 82050f932..000000000 --- a/lib/t3.f90 +++ /dev/null @@ -1,76 +0,0 @@ -program t3 - - parameter (NBLK=3456,NZ=10*NBLK) - real x0(NZ) - real x1(NZ) - - twopi=8.0*atan(1.0) - dphi=twopi*1000.0/12000.0 - phi=0. - do i=1,NZ - phi=phi+dphi - x0(i)=sin(phi) - if(mod(i,10007).eq.100) x0(i)=2.0 - enddo - - do j=1,10 - ib=j*NBLK - ia=ib-NBLK+1 - call filter(x0(ia:ib),x1(ia:ib)) - enddo - - x1(1:NZ-NBLK)=x1(NBLK+1:NZ) - do i=1,NZ-NBLK - write(13,1001) i,x0(i),x1(i),x1(i)-x0(i) -1001 format(i6,3f13.9) - enddo - -end program t3 - -subroutine filter(x0,x1) - -! Process time-domain data sequentially, optionally using a frequency-domain -! filter to alter the spectrum. - -! NB: uses a sin^2 window with 50% overlap. - - parameter (NFFT=6912,NH=NFFT/2) - real x0(0:NH-1) !Input samples - real x1(0:NH-1) !Output samples (delayed by one block) - real x0s(0:NH-1) !Saved upper half of input samples - real x1s(0:NH-1) !Saved upper half of output samples - real x(0:NFFT-1) !Work array - real*4 w(0:NFFT-1) !Window function - real f(0:NH) !Filter to be applied - real*4 s(0:NH) !Average spectrum - logical first - complex cx(0:NH) !Complex frequency-domain work array - equivalence (x,cx) - data first/.true./ - save - - if(first) then - pi=4.0*atan(1.0) - do i=0,NFFT-1 - ww=sin(i*pi/NFFT) - w(i)=ww*ww/NFFT - enddo - s=0.0 - f=1.0 - x0s=0. - x1s=0. - first=.false. - endif - - x(0:NH-1)=x0s !Previous 2nd half to new 1st half - x(NH:NFFT-1)=x0 !New 2nd half - x0s=x0 !Save the new 2nd half - x=w*x !Apply window - call four2a(x,NFFT,1,-1,0) !r2c FFT (to frequency domain) - cx=f*cx - call four2a(cx,NFFT,1,1,-1) !c2r FFT (back to time domain) - x1=x1s + x(0:NH-1) !Add previous segment's 2nd half - x1s=x(NH:NFFT-1) !Save the new 2nd half - - return -end subroutine filter diff --git a/lib/to_contest_msg.f90 b/lib/to_contest_msg.f90 new file mode 100644 index 000000000..426919518 --- /dev/null +++ b/lib/to_contest_msg.f90 @@ -0,0 +1,27 @@ +subroutine to_contest_msg(msg0,msg) + +! If the message has "R grid4" istead of "grid4", remove the "R " +! and substitute the diametrically opposite grid. + + character*6 g1,g2 + character*22 msg0,msg + logical isgrid + isgrid(g1)=g1(1:1).ge.'A' .and. g1(1:1).le.'R' .and. g1(2:2).ge.'A' .and. & + g1(2:2).le.'R' .and. g1(3:3).ge.'0' .and. g1(3:3).le.'9' .and. & + g1(4:4).ge.'0' .and. g1(4:4).le.'9' .and. g1(1:4).ne.'RR73' + + i0=index(msg0,' R ') + 3 !Check for ' R ' in message + g1=msg0(i0:i0+3)//' ' + if(isgrid(g1)) then !Check for ' R grid' + call grid2deg(g1,dlong,dlat) + dlong=dlong+180.0 + if(dlong.gt.180.0) dlong=dlong-360.0 + dlat=-dlat + call deg2grid(dlong,dlat,g2) !g2=antipodes grid + msg=msg0(1:i0-3)//g2(1:4) !Send message with g2 + else + msg=msg0 + endif + + return +end subroutine to_contest_msg diff --git a/mainwindow.cpp b/mainwindow.cpp index 7cb9e7923..88fa53cbe 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -75,7 +75,8 @@ extern "C" { int len1, int len2, int len3, int len4, int len5); // float s[], int* jh, char line[], char mygrid[], - void genft8_(char* msg, char* msgsent, char ft8msgbits[], int itone[], int len1, int len2); + void genft8_(char* msg, char* MyGrid, bool* bcontest, char* msgsent, + char ft8msgbits[], int itone[], int len1, int len2, int len3); void gen4_(char* msg, int* ichk, char* msgsent, int itone[], int* itext, int len1, int len2); @@ -124,16 +125,16 @@ extern "C" { void wav12_(short d2[], short d1[], int* nbytes, short* nbitsam2); - void refspectrum_(short int d2[], short int d2b[], int* k, bool* bclearrefspec, + void refspectrum_(short int d2[], bool* bclearrefspec, bool* brefspec, bool* buseref, const char* c_fname, int len); void freqcal_(short d2[], int* k, int* nkhz,int* noffset, int* ntol, char line[], int len); } -int volatile itone[NUM_ISCAT_SYMBOLS]; //Audio tones for all Tx symbols -char volatile ft8msgbits[75]; //packed 75 bit ft8 message -int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID +int volatile itone[NUM_ISCAT_SYMBOLS]; //Audio tones for all Tx symbols +char volatile ft8msgbits[75]; //packed 75 bit ft8 message +int volatile icw[NUM_CW_SYMBOLS]; //Dits for CW ID struct dec_data dec_data; // for sharing with Fortran int outBufSize; @@ -154,16 +155,15 @@ namespace { Radio::Frequency constexpr default_frequency {14076000}; QRegExp message_alphabet {"[- @A-Za-z0-9+./?#<>]*"}; - QRegExp grid_regexp {QRegExp {"[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}"}}; + // grid exact match excluding RR73 + QRegularExpression grid_regexp {"\\A(?![Rr]{2}73)[A-Ra-r]{2}[0-9]{2}([A-Xa-x]{2}){0,1}\\z"}; bool message_is_73 (int type, QStringList const& msg_parts) { - bool b; - b = type >= 0 - && (((type < 6 || 7 == type) - && (msg_parts.contains ("73") or msg_parts.contains ("RR73"))) - || (type == 6 && !msg_parts.filter ("73").isEmpty ())); - return b; + return type >= 0 + && (((type < 6 || 7 == type) + && (msg_parts.contains ("73") || msg_parts.contains ("RR73"))) + || (type == 6 && !msg_parts.filter ("73").isEmpty ())); } int ms_minute_error () @@ -216,9 +216,10 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_waterfallAvg {1}, m_ntx {1}, m_gen_message_is_cq {false}, + m_send_RR73 {false}, m_XIT {0}, m_sec0 {-1}, - m_RxLog {1}, //Write Date and Time to RxLog + m_RxLog {1}, //Write Date and Time to RxLog m_nutc0 {999999}, m_ntr {0}, m_tx {0}, @@ -270,6 +271,8 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, m_bRefSpec {false}, m_bClearRefSpec {false}, m_bTrain {false}, + m_bAutoReply {false}, + m_QSOProgress {CALLING}, m_ihsym {0}, m_nzap {0}, m_px {0.0}, @@ -713,7 +716,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, auto t = "UTC dB DT Freq Message"; ui->decodedTextLabel->setText(t); ui->decodedTextLabel2->setText(t); - readSettings(); //Restore user's setup params + readSettings(); //Restore user's setup params m_audioThread.start (m_audioThreadPriority); #ifdef WIN32 @@ -779,7 +782,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, QByteArray cfname=fname.toLocal8Bit(); fftwf_import_wisdom_from_filename(cfname); - genStdMsgs(m_rpt); + //genStdMsgs(m_rpt); m_ntx = 6; ui->txrb6->setChecked(true); @@ -833,7 +836,6 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, } m_saveDecoded=ui->actionSave_decoded->isChecked(); m_saveAll=ui->actionSave_all->isChecked(); - ui->inGain->setValue(m_inGain); ui->sbTxPercent->setValue(m_pctx); ui->TxPowerComboBox->setCurrentIndex(int(0.3*(m_dBm + 30.0)+0.2)); ui->cbUploadWSPR_Spots->setChecked(m_uploadSpots); @@ -954,7 +956,6 @@ void MainWindow::writeSettings() m_settings->setValue ("FreeText", ui->freeTextMsg->currentText ()); m_settings->setValue("ShowMenus",ui->cbMenus->isChecked()); m_settings->setValue("CallFirst",ui->cbFirst->isChecked()); - m_settings->setValue("CallWeak",ui->cbWeak->isChecked()); m_settings->endGroup(); m_settings->beginGroup("Common"); @@ -975,7 +976,6 @@ void MainWindow::writeSettings() m_settings->setValue("ShMsgs",m_bShMsgs); m_settings->setValue("SWL",ui->cbSWL->isChecked()); m_settings->setValue ("DialFreq", QVariant::fromValue(m_lastMonitoredFrequency)); - m_settings->setValue("InGain",m_inGain); m_settings->setValue("OutAttenuation", ui->outAttenuation->value ()); m_settings->setValue("NoSuffix",m_noSuffix); m_settings->setValue("GUItab",ui->tabWidget->currentIndex()); @@ -992,6 +992,7 @@ void MainWindow::writeSettings() m_settings->setValue ("CQTxfreq", ui->sbCQTxFreq->value ()); m_settings->setValue("pwrBandTxMemory",m_pwrBandTxMemory); m_settings->setValue("pwrBandTuneMemory",m_pwrBandTuneMemory); + m_settings->setValue ("FT8AP", ui->actionEnable_AP->isChecked ()); { QList coeffs; // suitable for QSettings for (auto const& coeff : m_phaseEqCoefficients) @@ -1020,7 +1021,6 @@ void MainWindow::readSettings() m_settings->value ("FreeText").toString ()); ui->cbMenus->setChecked(m_settings->value("ShowMenus",true).toBool()); ui->cbFirst->setChecked(m_settings->value("CallFirst",true).toBool()); - ui->cbWeak->setChecked(m_settings->value("CallWeak",true).toBool()); m_settings->endGroup(); // do this outside of settings group because it uses groups internally @@ -1053,7 +1053,6 @@ void MainWindow::readSettings() ui->TxFreqSpinBox->setValue(0); // ensure a change is signaled ui->TxFreqSpinBox->setValue(m_settings->value("TxFreq",1500).toInt()); m_ndepth=m_settings->value("NDepth",3).toInt(); - m_inGain=m_settings->value("InGain",0).toInt(); m_pctx=m_settings->value("PctTx",20).toInt(); m_dBm=m_settings->value("dBm",37).toInt(); ui->WSPR_prefer_type_1_check_box->setChecked (m_settings->value ("WSPRPreferType1", true).toBool ()); @@ -1072,6 +1071,7 @@ void MainWindow::readSettings() m_lockTxFreq=m_settings->value("LockTxFreq",false).toBool(); m_pwrBandTxMemory=m_settings->value("pwrBandTxMemory").toHash(); m_pwrBandTuneMemory=m_settings->value("pwrBandTuneMemory").toHash(); + ui->actionEnable_AP->setChecked (m_settings->value ("FT8AP", false).toBool()); { auto const& coeffs = m_settings->value ("PhaseEqualizationCoefficients" , QList {0., 0., 0., 0., 0.}).toList (); @@ -1154,7 +1154,7 @@ void MainWindow::dataSink(qint64 frames) } m_bUseRef=m_wideGraph->useRef(); - refspectrum_(&dec_data.d2[k-m_nsps/2],&dec_data.d2[0],&k,&m_bClearRefSpec,&m_bRefSpec, + refspectrum_(&dec_data.d2[k-m_nsps/2],&m_bClearRefSpec,&m_bRefSpec, &m_bUseRef,c_fname,len); m_bClearRefSpec=false; @@ -1399,12 +1399,12 @@ void MainWindow::fastSink(qint64 frames) int RxFreq=ui->RxFreqSpinBox->value (); int nTRpDepth=m_TRperiod + 1000*(m_ndepth & 3); qint64 ms0 = QDateTime::currentMSecsSinceEpoch(); - strncpy(dec_data.params.mycall, (m_config.my_callsign()+" ").toLatin1(),12); + strncpy(dec_data.params.mycall, (m_baseCall+" ").toLatin1(),12); QString hisCall {ui->dxCallEntry->text ()}; bool bshmsg=ui->cbShMsgs->isChecked(); bool bcontest=m_config.contestMode(); bool bswl=ui->cbSWL->isChecked(); - strncpy(dec_data.params.hiscall,(hisCall + " ").toLatin1 ().constData (), 12); + strncpy(dec_data.params.hiscall,(Radio::base_callsign (hisCall) + " ").toLatin1 ().constData (), 12); strncpy(dec_data.params.mygrid, (m_config.my_grid()+" ").toLatin1(),6); QString dataDir; dataDir = m_config.writeable_data_dir ().absolutePath (); @@ -1433,20 +1433,13 @@ void MainWindow::fastSink(qint64 frames) m_logBook,m_config.color_CQ(),m_config.color_MyCall(),m_config.color_DXCC(), m_config.color_NewCall()); m_bDecoded=true; - int i1=message.indexOf(m_baseCall); - int i2=message.indexOf(m_hisCall); - if(i1>10 and i2>i1+3) { - if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked () - && (message.indexOf (" 73") < 0 || m_ntx != 6)) { - if(!m_sentFirst73) processMessage (message,43,false); - } - } + auto_sequence (message, ui->sbFtol->value (), std::numeric_limits::max ()); if (m_mode != "ISCAT") postDecode (true, decodedtext.string ()); writeAllTxt(message); bool stdMsg = decodedtext.report(m_baseCall, Radio::base_callsign(ui->dxCallEntry->text()),m_rptRcvd); decodedtext=message.mid(0,4) + message.mid(6,-1); - if(m_config.spot_to_psk_reporter() and stdMsg and !m_diskData) pskPost(decodedtext); + if (stdMsg) pskPost (decodedtext); } float fracTR=float(k)/(12000.0*m_TRperiod); @@ -1520,8 +1513,8 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog { // things that might change that we need know about auto callsign = m_config.my_callsign (); - bool bvhf0=m_config.enable_VHF_features(); - bool bcontest0=m_config.contestMode(); + //bool bvhf0=m_config.enable_VHF_features(); + //bool bcontest0=m_config.contestMode(); if (QDialog::Accepted == m_config.exec ()) { if (m_config.my_callsign () != callsign) { m_baseCall = Radio::base_callsign (m_config.my_callsign ()); @@ -1561,14 +1554,14 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog if(m_mode=="JT9+JT65") on_actionJT9_JT65_triggered(); if(m_mode=="JT65") { on_actionJT65_triggered(); - if(m_config.enable_VHF_features() != bvhf0) genStdMsgs(m_rpt); + //if(m_config.enable_VHF_features() != bvhf0) genStdMsgs(m_rpt); } if(m_mode=="QRA64") on_actionQRA64_triggered(); if(m_mode=="FreqCal") on_actionFreqCal_triggered(); if(m_mode=="ISCAT") on_actionISCAT_triggered(); if(m_mode=="MSK144") { on_actionMSK144_triggered(); - if(m_config.contestMode() != bcontest0) genStdMsgs(m_rpt); + //if(m_config.contestMode() != bcontest0) genStdMsgs(m_rpt); } if(m_mode=="WSPR") on_actionWSPR_triggered(); if(m_mode=="WSPR-LF") on_actionWSPR_LF_triggered(); @@ -1587,8 +1580,8 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog update_watchdog_label (); if(!m_splitMode) ui->cbCQTx->setChecked(false); if(!m_config.enable_VHF_features()) { - ui->actionInclude_averaging->setEnabled(false); - ui->actionInclude_correlation->setEnabled(false); + ui->actionInclude_averaging->setVisible(false); + ui->actionInclude_correlation->setVisible (false); ui->actionInclude_averaging->setChecked(false); ui->actionInclude_correlation->setChecked(false); } @@ -1627,7 +1620,7 @@ void MainWindow::monitor (bool state) { ui->monitorButton->setChecked (state); if (state) { - m_diskData = false; // no longer reading WAV files + m_diskData = false; // no longer reading WAV files if (!m_monitoring) Q_EMIT resumeAudioInputStream (); } else { Q_EMIT suspendAudioInputStream (); @@ -1643,6 +1636,17 @@ void MainWindow::on_actionAbout_triggered() //Display "About" void MainWindow::on_autoButton_clicked (bool checked) { m_auto = checked; + if (checked + && ui->cbFirst->isVisible () && ui->cbFirst->isChecked() + && CALLING == m_QSOProgress) { + m_bAutoReply = false; // ready for next + m_bCallingCQ = true; // allows tail-enders to be picked up + ui->cbFirst->setStyleSheet ("QCheckBox{color:red}"); + } + else { + ui->cbFirst->setStyleSheet(""); + } + if (!checked) m_bCallingCQ = false; statusUpdate (); m_bEchoTxOK=false; if(m_auto and (m_mode=="Echo")) { @@ -1711,9 +1715,12 @@ void MainWindow::keyPressEvent (QKeyEvent * e) n=11; if(e->modifiers() & Qt::ControlModifier) n+=100; if(e->modifiers() & Qt::ShiftModifier) { + /* int f=ui->TxFreqSpinBox->value()/50; if((ui->TxFreqSpinBox->value() % 50) == 0) f=f-1; ui->TxFreqSpinBox->setValue(50*f); + */ + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()-60); } else{ bumpFqso(n); } @@ -1722,8 +1729,12 @@ void MainWindow::keyPressEvent (QKeyEvent * e) n=12; if(e->modifiers() & Qt::ControlModifier) n+=100; if(e->modifiers() & Qt::ShiftModifier) { + /* int f=ui->TxFreqSpinBox->value()/50; ui->TxFreqSpinBox->setValue(50*(f+1)); + */ + ui->TxFreqSpinBox->setValue(ui->TxFreqSpinBox->value()+60); + } else { bumpFqso(n); } @@ -1752,7 +1763,7 @@ void MainWindow::keyPressEvent (QKeyEvent * e) break; case Qt::Key_G: if(e->modifiers() & Qt::AltModifier) { - genStdMsgs(m_rpt); + genStdMsgs (m_rpt, true); return; } break; @@ -2358,7 +2369,7 @@ void MainWindow::on_actionSpecial_mouse_commands_triggered() m_mouseCmnds->raise (); } -void MainWindow::on_DecodeButton_clicked (bool /* checked */) //Decode request +void MainWindow::on_DecodeButton_clicked (bool /* checked */) //Decode request { if(m_mode=="MSK144") { ui->DecodeButton->setChecked(false); @@ -2423,11 +2434,13 @@ void MainWindow::decode() //decode() dec_data.params.nutc=10000*ihr + 100*imin + isec; } if(m_nPick==2) dec_data.params.nutc=m_nutc0; + dec_data.params.nQSOProgress = m_QSOProgress; dec_data.params.nfqso=m_wideGraph->rxFreq(); + dec_data.params.nftx = ui->TxFreqSpinBox->value (); qint32 depth {m_ndepth}; - if (!ui->actionInclude_averaging->isEnabled ()) depth &= ~16; - if (!ui->actionInclude_correlation->isEnabled ()) depth &= ~32; - if (!ui->actionEnable_AP_DXcall->isEnabled ()) depth &= ~64; + if (!ui->actionInclude_averaging->isVisible ()) depth &= ~16; + if (!ui->actionInclude_correlation->isVisible ()) depth &= ~32; + if (!ui->actionEnable_AP_DXcall->isVisible ()) depth &= ~64; dec_data.params.ndepth=depth; dec_data.params.n2pass=1; if(m_config.twoPass()) dec_data.params.n2pass=2; @@ -2458,6 +2471,8 @@ void MainWindow::decode() //decode() dec_data.params.ntxmode=4; } if(m_mode=="FT8") dec_data.params.nmode=8; + if(m_mode=="FT8") dec_data.params.lapon = ui->actionEnable_AP->isVisible () && ui->actionEnable_AP->isChecked (); + if(m_mode=="FT8") dec_data.params.napwid=50; dec_data.params.ntrperiod=m_TRperiod; dec_data.params.nsubmode=m_nSubMode; if(m_mode=="QRA64") dec_data.params.nsubmode=100 + m_nSubMode; @@ -2474,7 +2489,7 @@ void MainWindow::decode() //decode() dec_data.params.nexp_decode=0; if(m_config.single_decode()) dec_data.params.nexp_decode += 32; if(m_config.enable_VHF_features()) dec_data.params.nexp_decode += 64; - + if(m_config.contestMode()) dec_data.params.nexp_decode += 128; strncpy(dec_data.params.datetime, m_dateTime.toLatin1(), 20); strncpy(dec_data.params.mycall, (m_config.my_callsign()+" ").toLatin1(),12); @@ -2546,15 +2561,7 @@ void::MainWindow::fast_decode_done() dec_data.params.ndiskdat=false; // if(m_msg[0][0]==0) m_bDecoded=false; for(int i=0; i<100; i++) { - int i1=msg0.indexOf(m_baseCall); - int i2=msg0.indexOf(m_hisCall); - if ((m_mode == "MSK144" || m_bFast9) - && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked () - && tmax >= 0.0 && i1 > 10 && i2 > i1 + 3) { - if (msg0.indexOf (" 73") < 0 || m_ntx != 6) { - if(!m_sentFirst73) processMessage(msg0,43,false); - } - } + if (tmax >= 0.0) auto_sequence (msg0, ui->sbFtol->value (), ui->sbFtol->value ()); if(m_msg[i][0]==0) break; QString message=QString::fromLatin1(m_msg[i]); m_msg[i][0]=0; @@ -2587,9 +2594,7 @@ void::MainWindow::fast_decode_done() Radio::base_callsign(ui->dxCallEntry->text()), m_rptRcvd); // extract details and send to PSKreporter - if(m_config.spot_to_psk_reporter() and stdMsg and !m_diskData) { - pskPost(decodedtext); - } + if (stdMsg) pskPost(decodedtext); } } m_startAnother=m_loopall; @@ -2727,15 +2732,19 @@ void MainWindow::readFromStdout() //readFromStdout int audioFreq=decodedtext.frequencyOffset(); if(m_mode=="FT8") { - int i1=decodedtext.string().indexOf(" "+m_baseCall+" "); - if(m_bCallingCQ and i1>0 and ui->cbFirst->isChecked()) { -// int snr=decodedtext.string().mid(6,4).toInt(); - m_bDoubleClicked=true; - processMessage(decodedtext.string(),43,false); - m_bCallingCQ=false; - } else { - audioFreq=decodedtext.string().mid(16,4).toInt(); - if(i1>0 or (abs(audioFreq - m_wideGraph->rxFreq()) <= 10)) bDisplayRight=true; + auto const& parts = decodedtext.string ().split (' ', QString::SkipEmptyParts); + if (parts.size () > 6) { + auto for_us = parts[5].contains (m_baseCall) + || ("DE" == parts[5] && qAbs (ui->RxFreqSpinBox->value () - audioFreq) <= 10); + if(m_bCallingCQ && !m_bAutoReply && for_us && ui->cbFirst->isChecked()) { + // int snr=decodedtext.string().mid(6,4).toInt(); + m_bDoubleClicked=true; + m_bAutoReply = true; + processMessage (decodedtext.string (), decodedtext.string ().size ()); + ui->cbFirst->setStyleSheet(""); + } else { + if (for_us or (abs(audioFreq - m_wideGraph->rxFreq()) <= 10)) bDisplayRight=true; + } } } else { if(abs(audioFreq - m_wideGraph->rxFreq()) <= 10) bDisplayRight=true; @@ -2754,8 +2763,8 @@ void MainWindow::readFromStdout() //readFromStdout } m_QSOText=decodedtext; } - if(m_mode=="FT8") FT8_AutoSeq(decodedtext.string()); - + if(m_mode=="FT8" or m_mode=="QRA64") auto_sequence (decodedtext.string(), 25, 50); + postDecode (true, decodedtext.string ()); // find and extract any report for myCall @@ -2764,9 +2773,7 @@ void MainWindow::readFromStdout() //readFromStdout // extract details and send to PSKreporter int nsec=QDateTime::currentMSecsSinceEpoch()/1000-m_secBandChanged; bool okToPost=(nsec>(4*m_TRperiod)/5); - if(m_config.spot_to_psk_reporter () and stdMsg and !m_diskData and okToPost) { - pskPost(decodedtext); - } + if (stdMsg && okToPost) pskPost(decodedtext); if((m_mode=="JT4" or m_mode=="JT65" or m_mode=="QRA64") and m_msgAvgWidget!=NULL) { if(m_msgAvgWidget->isVisible()) { @@ -2782,19 +2789,56 @@ void MainWindow::readFromStdout() //readFromStdout } } -void MainWindow::FT8_AutoSeq(QString message) +// +// start_tolerance - only respond to "DE ..." and free text 73 +// messages within +/- this value +// +// stop_tolerance - kill Tx if running station is seen to reply to +// another caller and we are going to transmit within +// +/- this value of the reply to another caller +// +void MainWindow::auto_sequence (QString const& message, unsigned start_tolerance, unsigned stop_tolerance) { - int i1=message.indexOf(m_baseCall); - int i2=message.indexOf(Radio::base_callsign(m_hisCall)); - if(i1>10 and i2>i1+3) { - if ((message.indexOf (" 73") < 0 || m_ntx != 6)) { - if(ui->cbAutoSeq->isChecked() and !m_sentFirst73) processMessage (message,43,false); + auto const& parts = message.split (' ', QString::SkipEmptyParts); + if (parts.size () > 6) { + bool ok; + auto df = parts[3].toInt (&ok); + auto within_tolerance = ok + && (qAbs (ui->RxFreqSpinBox->value () - df) <= int (start_tolerance) + || qAbs (ui->TxFreqSpinBox->value () - df) <= int (start_tolerance)); + if (m_auto + && (REPLYING == m_QSOProgress + || (!ui->tx1->isEnabled () && REPORT == m_QSOProgress)) + && qAbs (ui->TxFreqSpinBox->value () - df) <= int (stop_tolerance) + && !parts[5].contains (QRegularExpression {"(^(CQ|QRZ)$)|" + m_baseCall}) + && parts[6].contains (Radio::base_callsign (ui->dxCallEntry->text ()))) { + // auto stop to avoid accidental QRM + auto_tx_mode (false); } + else if (m_auto // transmit allowed + && ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked() // auto-sequencing allowed + && ((!m_bCallingCQ // not calling CQ/QRZ + && !m_sentFirst73 // finished QSO + && ((parts[5].contains (m_baseCall) + // being called and not already in a QSO + && parts[6].contains (Radio::base_callsign (ui->dxCallEntry->text ()))) + // type 2 compound replies + || (within_tolerance + && ((m_QSOProgress >= ROGER_REPORT && message_is_73 (0, parts)) + || ("DE" == parts[5] && parts[6].contains (Radio::base_callsign (m_hisCall))))))) + || (m_bCallingCQ && m_bAutoReply + && ((within_tolerance && "DE" == parts[5]) // look for type 2 compound call replies on our Tx and Rx offsets + || parts[5].contains (m_baseCall))))) + { + processMessage (message, message.size ()); + } } } -void MainWindow::pskPost(DecodedText decodedtext) +void MainWindow::pskPost (DecodedText decodedtext) { + if (m_diskData || !m_config.spot_to_psk_reporter() || decodedtext.isLowConfidence ()) return; + QString msgmode=m_mode; if(m_mode=="JT9+JT65") { msgmode="JT9"; @@ -2810,7 +2854,7 @@ void MainWindow::pskPost(DecodedText decodedtext) int snr = decodedtext.snr(); Frequency frequency = m_freqNominal + audioFrequency; pskSetLocal (); - if(grid_regexp.exactMatch (grid)) { + if(grid.contains (grid_regexp)) { // qDebug() << "To PSKreporter:" << deCall << grid << frequency << msgmode << snr; psk_Reporter->addRemoteStation(deCall,grid,QString::number(frequency),msgmode, QString::number(snr),QString::number(QDateTime::currentDateTime().toTime_t())); @@ -3049,35 +3093,37 @@ void MainWindow::guiUpdate() itone[0]=0; } else { if(m_mode=="ISCAT") { - int len2=28; - geniscat_(message, msgsent, const_cast (itone),len2, len2); + geniscat_(message, msgsent, const_cast (itone), 28, 28); msgsent[28]=0; } else { - int len1=22; if(m_modeTx=="JT4") gen4_(message, &ichk , msgsent, const_cast (itone), - &m_currentMessageType, len1, len1); + &m_currentMessageType, 22, 22); if(m_modeTx=="JT9") gen9_(message, &ichk, msgsent, const_cast (itone), - &m_currentMessageType, len1, len1); + &m_currentMessageType, 22, 22); if(m_modeTx=="JT65") gen65_(message, &ichk, msgsent, const_cast (itone), - &m_currentMessageType, len1, len1); + &m_currentMessageType, 22, 22); if(m_modeTx=="QRA64") genqra64_(message, &ichk, msgsent, const_cast (itone), - &m_currentMessageType, len1, len1); + &m_currentMessageType, 22, 22); if(m_modeTx=="WSPR") genwspr_(message, msgsent, const_cast (itone), - len1, len1); + 22, 22); if(m_modeTx=="WSPR-LF") genwspr_fsk8_(message, msgsent, const_cast (itone), - len1, len1); - if(m_modeTx=="FT8") genft8_(message, msgsent, const_cast (ft8msgbits), - const_cast (itone), len1, len1); - if(m_modeTx=="MSK144") { + 22, 22); + if(m_modeTx=="MSK144" or m_modeTx=="FT8") { bool bcontest=m_config.contestMode(); char MyGrid[6]; strncpy(MyGrid, (m_config.my_grid()+" ").toLatin1(),6); - genmsk144_(message, MyGrid, &ichk, &bcontest, msgsent, const_cast (itone), - &m_currentMessageType, len1, 6, len1); - if(m_restart) { - int nsym=144; - if(itone[40]==-40) nsym=40; - m_modulator->set_nsym(nsym); + if(m_modeTx=="MSK144") { + genmsk144_(message, MyGrid, &ichk, &bcontest, msgsent, const_cast (itone), + &m_currentMessageType, 22, 6, 22); + if(m_restart) { + int nsym=144; + if(itone[40]==-40) nsym=40; + m_modulator->set_nsym(nsym); + } + } + if(m_modeTx=="FT8") { + genft8_(message, MyGrid, &bcontest, msgsent, const_cast (ft8msgbits), + const_cast (itone), 22, 6, 22); } } msgsent[22]=0; @@ -3085,7 +3131,16 @@ void MainWindow::guiUpdate() } m_currentMessage = QString::fromLatin1(msgsent); - if(m_mode=="FT8") m_bCallingCQ=m_currentMessage.mid(0,3)=="CQ "; + m_bCallingCQ = CALLING == m_QSOProgress + || m_currentMessage.contains (QRegularExpression {"^(CQ|QRZ) "}); + if(m_mode=="FT8") { + if(m_bCallingCQ && ui->cbFirst->isVisible () && ui->cbFirst->isChecked ()) { + ui->cbFirst->setStyleSheet("QCheckBox{color:red}"); + } else { + ui->cbFirst->setStyleSheet(""); + } + } + if (m_tune) { m_currentMessage = "TUNE"; m_currentMessageType = -1; @@ -3107,7 +3162,8 @@ void MainWindow::guiUpdate() msg_parts[0].remove (QChar {'<'}); msg_parts[1].remove (QChar {'>'}); } - auto is_73 = message_is_73 (m_currentMessageType, msg_parts); + auto is_73 = m_QSOProgress >= ROGER_REPORT + && message_is_73 (m_currentMessageType, msg_parts); m_sentFirst73 = is_73 && !message_is_73 (m_lastMessageType, m_lastMessageSent.split (' ', QString::SkipEmptyParts)); if (m_sentFirst73) { @@ -3125,6 +3181,7 @@ void MainWindow::guiUpdate() if(b) { m_ntx=6; ui->txrb6->setChecked(true); + m_QSOProgress = CALLING; } } @@ -3171,6 +3228,7 @@ void MainWindow::guiUpdate() { ui->genMsg->setText(ui->tx6->text()); m_ntx=7; + m_QSOProgress = CALLING; m_gen_message_is_cq = true; ui->rbGenMsg->setChecked(true); } else { @@ -3198,6 +3256,16 @@ void MainWindow::guiUpdate() ui->TxFreqSpinBox->value(),m_config.color_TxMsg(),m_bFastMode); } + switch (m_ntx) + { + case 1: m_QSOProgress = REPLYING; break; + case 2: m_QSOProgress = REPORT; break; + case 3: m_QSOProgress = ROGER_REPORT; break; + case 4: m_QSOProgress = ROGERS; break; + case 5: m_QSOProgress = SIGNOFF; break; + case 6: m_QSOProgress = CALLING; break; + default: break; // determined elsewhere + } m_transmitting = true; transmitDisplay (true); statusUpdate (); @@ -3399,20 +3467,34 @@ void MainWindow::set_ntx(int n) //set_ntx() m_ntx=n; } -void MainWindow::on_txrb1_toggled(bool status) +void MainWindow::on_txrb1_toggled (bool status) { if (status) { - m_ntx=1; - set_dateTimeQSO(-1); // we reset here as tx2/tx3 is used for start times + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + set_dateTimeQSO (-1); // we reset here as tx2/tx3 is used for start times + } + else { + QTimer::singleShot (0, ui->txrb2, SLOT (click ())); + } } } -void MainWindow::on_txrb2_toggled(bool status) +void MainWindow::on_txrb1_doubleClicked () +{ + ui->tx1->setEnabled (!ui->tx1->isEnabled ()); + if (!ui->tx1->isEnabled ()) { + // leave time for clicks to complete before setting txrb2 + QTimer::singleShot (500, ui->txrb2, SLOT (click ())); + } +} + +void MainWindow::on_txrb2_toggled (bool status) { // Tx 2 means we already have CQ'd so good reference if (status) { - m_ntx=2; - set_dateTimeQSO(m_ntx); + m_ntx = 2; + set_dateTimeQSO (m_ntx); } } @@ -3425,38 +3507,61 @@ void MainWindow::on_txrb3_toggled(bool status) } } -void MainWindow::on_txrb4_toggled(bool status) +void MainWindow::on_txrb4_toggled (bool status) { if (status) { m_ntx=4; } } -void MainWindow::on_txrb5_toggled(bool status) +void MainWindow::on_txrb4_doubleClicked () +{ + m_send_RR73 = !m_send_RR73; + genStdMsgs (m_rpt); +} + +void MainWindow::on_txrb5_toggled (bool status) { if (status) { - m_ntx=5; + m_ntx = 5; } } +void MainWindow::on_txrb5_doubleClicked () +{ + genStdMsgs (m_rpt, true); +} + void MainWindow::on_txrb6_toggled(bool status) { if (status) { m_ntx=6; - if (ui->txrb6->text().contains("CQ ")) set_dateTimeQSO(-1); + if (ui->txrb6->text().contains (QRegularExpression {"^(CQ|QRZ) "})) set_dateTimeQSO(-1); } } void MainWindow::on_txb1_clicked() { + if (ui->tx1->isEnabled ()) { m_ntx=1; + m_QSOProgress = REPLYING; ui->txrb1->setChecked(true); if (m_transmitting) m_restart=true; + } + else { + on_txb2_clicked (); + } +} + +void MainWindow::on_txb1_doubleClicked() +{ + ui->tx1->setEnabled (!ui->tx1->isEnabled ()); } void MainWindow::on_txb2_clicked() { m_ntx=2; + m_QSOProgress = REPORT; ui->txrb2->setChecked(true); if (m_transmitting) m_restart=true; } @@ -3464,6 +3569,7 @@ void MainWindow::on_txb2_clicked() void MainWindow::on_txb3_clicked() { m_ntx=3; + m_QSOProgress = ROGER_REPORT; ui->txrb3->setChecked(true); if (m_transmitting) m_restart=true; } @@ -3471,66 +3577,75 @@ void MainWindow::on_txb3_clicked() void MainWindow::on_txb4_clicked() { m_ntx=4; + m_QSOProgress = ROGERS; ui->txrb4->setChecked(true); if (m_transmitting) m_restart=true; } +void MainWindow::on_txb4_doubleClicked() +{ + m_send_RR73 = !m_send_RR73; + genStdMsgs (m_rpt); +} + void MainWindow::on_txb5_clicked() { m_ntx=5; + m_QSOProgress = SIGNOFF; ui->txrb5->setChecked(true); if (m_transmitting) m_restart=true; } +void MainWindow::on_txb5_doubleClicked() +{ + genStdMsgs (m_rpt, true); +} + void MainWindow::on_txb6_clicked() { m_ntx=6; + m_QSOProgress = CALLING; set_dateTimeQSO(-1); ui->txrb6->setChecked(true); if (m_transmitting) m_restart=true; } -void MainWindow::doubleClickOnCall2(bool shift, bool ctrl) +void MainWindow::doubleClickOnCall2(bool alt, bool ctrl) { set_dateTimeQSO(-1); // reset our QSO start time m_decodedText2=true; - doubleClickOnCall(shift,ctrl); + doubleClickOnCall(alt,ctrl); m_decodedText2=false; } -void MainWindow::doubleClickOnCall(bool shift, bool ctrl) +void MainWindow::doubleClickOnCall(bool alt, bool ctrl) { QTextCursor cursor; - QString t; //Full contents if(m_mode=="ISCAT") { MessageBox::information_message (this, "Double-click not presently implemented for ISCAT mode"); } - if(shift) t=""; //Silence compiler warning if(m_decodedText2) { cursor=ui->decodedTextBrowser->textCursor(); - t= ui->decodedTextBrowser->toPlainText(); } else { cursor=ui->decodedTextBrowser2->textCursor(); - t= ui->decodedTextBrowser2->toPlainText(); } cursor.select(QTextCursor::LineUnderCursor); int position {cursor.position()}; - if(shift && position==-9999) return; //Silence compiler warning QString messages; if(!m_decodedText2) messages= ui->decodedTextBrowser2->toPlainText(); if(m_decodedText2) messages= ui->decodedTextBrowser->toPlainText(); if(ui->cbCQTx->isEnabled() && ui->cbCQTx->isChecked()) m_bDoubleClickAfterCQnnn=true; m_bDoubleClicked=true; - processMessage(messages, position, ctrl); + processMessage(messages, position, ctrl, alt); } -void MainWindow::processMessage(QString const& messages, int position, bool ctrl) +void MainWindow::processMessage(QString const& messages, int position, bool ctrl, bool alt) { - QString t1 = messages.mid(0,position); //contents up to \n on selected line + QString t1 = messages.left(position); //contents up to \n on selected line int i1=t1.lastIndexOf(QChar::LineFeed) + 1; //points to first char of line DecodedText decodedtext; QString t2 = messages.mid(i1,position-i1); //selected line @@ -3551,10 +3666,11 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl int ntsec=3600*t2.mid(0,2).toInt() + 60*t2.mid(2,2).toInt(); if(m_bFastMode or m_mode=="FT8") { ntsec = ntsec + t2.mid(4,2).toInt(); - t2a=t2.mid(0,4) + t2.mid(6,-1); //Change hhmmss to hhmm for the message parser - } else { - t2a=t2.left (44); // strip and quality info trailing the decoded message + t2a = t2.left (4) + t2.mid (6); //Change hhmmss to hhmm for the message parser } + t2a = t2.left (44); // strip any quality info trailing the + // decoded message + if(m_bFastMode or m_mode=="FT8") { i1=t2a.indexOf(" CQ "); if(i1>10) { @@ -3589,8 +3705,8 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl ui->RxFreqSpinBox->setValue (frequency); //Set Rx freq } - if(decodedtext.isTX() and !m_config.enable_VHF_features()) { - if (ctrl && ui->TxFreqSpinBox->isEnabled()) { + if (decodedtext.isTX()) { + if (!m_config.enable_VHF_features() && ctrl && ui->TxFreqSpinBox->isEnabled()) { ui->TxFreqSpinBox->setValue(frequency); //Set Tx freq } return; @@ -3608,7 +3724,8 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl && (t4.at (5) == m_baseCall // " 73" || t4.at (5).startsWith (m_baseCall + '/') || t4.at (5).endsWith ('/' + m_baseCall)) - && t4.at (6) == "73")) + && t4.at (6) == "73") + && !(m_QSOProgress >= ROGER_REPORT && message_is_73 (0, t4))) { qDebug () << "Not processing message - hiscall:" << hiscall << "hisgrid:" << hisgrid; return; @@ -3636,10 +3753,10 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl if(!m_bFastMode and (!m_config.enable_VHF_features() or m_mode=="FT8")) { // Don't change Tx freq if in a fast mode, or VHF features enabled; also not if a // station is calling me, unless m_lockTxFreq is true or CTRL is held down. - if ((firstcall!=m_config.my_callsign () and firstcall != m_baseCall) or - m_lockTxFreq or ctrl) { + if ((firstcall != m_config.my_callsign () && firstcall != m_baseCall && firstcall != "DE") + || m_lockTxFreq || ctrl) { if (ui->TxFreqSpinBox->isEnabled ()) { - if(!m_bFastMode) ui->TxFreqSpinBox->setValue(frequency); + if(!m_bFastMode && !alt) ui->TxFreqSpinBox->setValue(frequency); } else if(m_mode != "JT4" && m_mode != "JT65" && !m_mode.startsWith ("JT9") && m_mode != "QRA64") { return; @@ -3647,6 +3764,174 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl } } + // prior DX call (possible QSO partner) + auto qso_partner_base_call = Radio::base_callsign (ui->dxCallEntry-> text ()); + auto base_call = Radio::base_callsign (hiscall); + +// Determine appropriate response to received message + auto dtext = " " + decodedtext.string () + " "; + int gen_msg {0}; + if(dtext.contains (" " + m_baseCall + " ") + || dtext.contains ("<" + m_baseCall + " ") + || dtext.contains ("/" + m_baseCall + " ") + || dtext.contains (" " + m_baseCall + "/") + || (firstcall == "DE" /*&& ((t4.size () > 7 && t4.at(7) != "73") || t4.size () <= 7)*/)) + { + if (t4.size () > 7 // enough fields for a normal msg + && (t4.at (5).contains (m_baseCall) || "DE" == t4.at (5)) + && t4.at (6).contains (qso_partner_base_call) + && !t4.at (7).contains (grid_regexp)) // but no grid on end of msg + { + QString r=t4.at (7); + if(m_QSOProgress >= ROGER_REPORT && (r.mid(0,3)=="RRR" || r.toInt()==73 || "RR73" == r)) { + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 5; + if (ui->rbGenMsg->isChecked ()) m_ntx=7; + m_gen_message_is_cq = false; + } + else { + m_ntx=5; + ui->txrb5->setChecked(true); + } + m_QSOProgress = SIGNOFF; + } else if((m_QSOProgress >= REPORT + || (m_QSOProgress >= REPLYING && "MSK144" == m_mode && m_config.contestMode ())) + && r.mid(0,1)=="R") { + m_ntx=4; + m_QSOProgress = ROGERS; + ui->txrb4->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 4; + m_ntx=7; + m_gen_message_is_cq = false; + } + } else if(m_QSOProgress >= CALLING && r.toInt()>=-50 && r.toInt()<=49) { + m_ntx=3; + m_QSOProgress = ROGER_REPORT; + ui->txrb3->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 3; + m_ntx=7; + m_gen_message_is_cq = false; + } + } + else { // nothing for us + return; + } + } + else if (m_QSOProgress >= ROGERS + && t4.size () >= 7 && t4.at (5).contains (m_baseCall) && t4.at (6) == "73") { + // 73 back to compound call holder + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 5; + if (ui->rbGenMsg->isChecked ()) m_ntx=7; + m_gen_message_is_cq = false; + } + else { + m_ntx=5; + ui->txrb5->setChecked(true); + } + m_QSOProgress = SIGNOFF; + } + else if (!(m_bAutoReply && m_QSOProgress > CALLING)) { + if ((t4.size () >= 9 && t4.at (5).contains (m_baseCall) && t4.at (8) == "OOO") + || (m_mode=="MSK144" && m_config.contestMode())) { + // EME short code report or MSK144 contest mode reply, send back Tx3 + m_ntx = 3; + m_QSOProgress = ROGER_REPORT; + ui->txrb3->setChecked (true); + if (ui->tabWidget->currentIndex () == 1) { + gen_msg = 3; + m_ntx = 7; + m_gen_message_is_cq = false; + } + } else { + m_ntx=2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked(true); + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 2; + m_ntx=7; + m_gen_message_is_cq = false; + } + + if (m_bDoubleClickAfterCQnnn and m_transmitting) { + on_stopTxButton_clicked(); + TxAgainTimer.start(1500); + } + m_bDoubleClickAfterCQnnn=false; + } + } + else { // nothing for us + return; + } + } + else if (firstcall == "DE" && t4.size () >= 8 && t4.at (7) == "73") { + if (m_QSOProgress >= ROGERS && base_call == qso_partner_base_call && m_currentMessageType) { + // 73 back to compound call holder + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 5; + m_ntx=7; + m_gen_message_is_cq = false; + } + else { + m_ntx=5; + ui->txrb5->setChecked(true); + } + m_QSOProgress = SIGNOFF; + } + else { + // treat like a CQ/QRZ + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked (true); + } + else { + m_ntx = 2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked (true); + } + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 1; + m_ntx=7; + m_gen_message_is_cq = false; + } + } + } + else if (m_QSOProgress >= ROGERS && message_is_73 (0, t4)) { + if(ui->tabWidget->currentIndex()==1) { + gen_msg = 5; + if (ui->rbGenMsg->isChecked ()) m_ntx=7; + m_gen_message_is_cq = false; + } + else { + m_ntx=5; + ui->txrb5->setChecked(true); + } + m_QSOProgress = SIGNOFF; + } + else // just work them + { + if (ui->tx1->isEnabled ()) { + m_ntx = 1; + m_QSOProgress = REPLYING; + ui->txrb1->setChecked (true); + } + else { + m_ntx = 2; + m_QSOProgress = REPORT; + ui->txrb2->setChecked (true); + } + if (1 == ui->tabWidget->currentIndex ()) { + gen_msg = m_ntx; + m_ntx=7; + m_gen_message_is_cq = false; + } + } + + // if we get here then we are reacting to the message + if (m_bAutoReply) m_bCallingCQ = CALLING == m_QSOProgress; QString s1=m_QSOText.string().trimmed(); QString s2=t2.trimmed(); if (s1!=s2 and !decodedtext.isTX()) { @@ -3657,13 +3942,10 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl m_QSOText=decodedtext; } - // prior DX call (possible QSO partner) - auto qso_partner_base_call = Radio::base_callsign (ui->dxCallEntry-> text ()); - - auto base_call = Radio::base_callsign (hiscall); - if (base_call != Radio::base_callsign (ui->dxCallEntry-> text ()) || base_call != hiscall) + if (hiscall != "73" + && (base_call != qso_partner_base_call || base_call != hiscall)) { - if (Radio::base_callsign (ui->dxCallEntry->text ()) != base_call) { + if (qso_partner_base_call != base_call) { // clear the DX grid if the base call of his call is different // from the current DX call ui->dxGridEntry->clear (); @@ -3672,7 +3954,7 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl // i.e. compound version of same base call ui->dxCallEntry->setText (hiscall); } - if (grid_regexp.exactMatch (hisgrid)) { + if (hisgrid.contains (grid_regexp)) { if(ui->dxGridEntry->text().mid(0,4) != hisgrid) ui->dxGridEntry->setText(hisgrid); } if (!ui->dxGridEntry->text ().size ()) @@ -3694,117 +3976,22 @@ void MainWindow::processMessage(QString const& messages, int position, bool ctrl } ui->rptSpinBox->setValue(n); - if(m_nTx73==0) genStdMsgs(rpt); //Don't genStdMsgs if we're already sending 73. - -// Determine appropriate response to received message - auto dtext = " " + decodedtext.string () + " "; - if(dtext.contains (" " + m_baseCall + " ") - || dtext.contains ("<" + m_baseCall + " ") - || dtext.contains ("/" + m_baseCall + " ") - || dtext.contains (" " + m_baseCall + "/") - || (firstcall == "DE" && ((t4.size () > 7 && t4.at(7) != "73") || t4.size () <= 7))) - { - - if (t4.size () > 7 // enough fields for a normal msg - and !grid_regexp.exactMatch (t4.at (7))) // but no grid on end of msg - { - QString r=t4.at (7); - if(r.mid(0,3)=="RRR" || (r.toInt()==73)) { - m_ntx=5; - ui->txrb5->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx5->currentText()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } else if(r.mid(0,1)=="R") { - m_ntx=4; - ui->txrb4->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx4->text()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } else if(r.toInt()>=-50 and r.toInt()<=49) { - m_ntx=3; - ui->txrb3->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx3->text()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } - } - else if (t4.size () == 7 && t4.at (6) == "73") { - // 73 back to compound call holder - m_ntx=5; - ui->txrb5->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx5->currentText()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } else { - if(m_mode=="MSK144" and m_config.contestMode()) { - m_ntx=3; - ui->txrb3->setChecked(true); - } else { - m_ntx=2; - ui->txrb2->setChecked(true); - } - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx2->text()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - - if(m_bDoubleClickAfterCQnnn and m_transmitting) { - on_stopTxButton_clicked(); - TxAgainTimer.start(1500); - } - m_bDoubleClickAfterCQnnn=false; + if (!m_nTx73) { // Don't genStdMsgs if we're already sending 73. + genStdMsgs(rpt); + if (gen_msg) { + switch (gen_msg) { + case 1: ui->genMsg->setText (ui->tx1->text ()); break; + case 2: ui->genMsg->setText (ui->tx2->text ()); break; + case 3: ui->genMsg->setText (ui->tx3->text ()); break; + case 4: ui->genMsg->setText (ui->tx4->text ()); break; + case 5: ui->genMsg->setText (ui->tx5->currentText ()); break; } - } - else if (firstcall == "DE" && t4.size () == 8 && t4.at (7) == "73") { - if (base_call == qso_partner_base_call) { - // 73 back to compound call holder - m_ntx=5; - ui->txrb5->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx5->currentText()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } - else { - // treat like a CQ/QRZ - m_ntx=1; - ui->txrb1->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx1->text()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); + if (gen_msg != 5) { // allow user to pre-select a free message + ui->rbGenMsg->setChecked (true); } } } - else // myCall not in msg - { - m_ntx=1; - ui->txrb1->setChecked(true); - if(ui->tabWidget->currentIndex()==1) { - ui->genMsg->setText(ui->tx1->text()); - m_ntx=7; - m_gen_message_is_cq = false; - ui->rbGenMsg->setChecked(true); - } - } + if(m_transmitting) m_restart=true; if (ui->cbAutoSeq->isVisible () && ui->cbAutoSeq->isChecked () && !m_bDoubleClicked) return; if(m_config.quick_call()) auto_tx_mode(true); @@ -3815,19 +4002,27 @@ void MainWindow::genCQMsg () { if(m_config.my_callsign().size () && m_config.my_grid().size ()) { + auto const& grid = m_config.my_callsign () != m_baseCall && shortList (m_config.my_callsign ()) ? QString {} : m_config.my_grid (); if (ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked ()) { msgtype (QString {"CQ %1 %2 %3"} .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'}) .arg (m_config.my_callsign()) - .arg (m_config.my_grid ().left (4)), + .arg (grid.left (4)), ui->tx6); } else { - msgtype (QString {"CQ %1 %2"}.arg (m_config.my_callsign ()).arg (m_config.my_grid ().left (4)), ui->tx6); + msgtype (QString {"CQ %1 %2"}.arg (m_config.my_callsign ()).arg (grid.left (4)), ui->tx6); } - if ((m_mode=="JT4" or m_mode=="QRA64") and ui->cbShMsgs->isChecked()) msgtype ("@1000 (TUNE)", ui->tx6); + if ((m_mode=="JT4" or m_mode=="QRA64") and ui->cbShMsgs->isChecked()) { + if (ui->cbTx6->isChecked ()) { + msgtype ("@1250 (SEND MSGS)", ui->tx6); + } + else { + msgtype ("@1000 (TUNE)", ui->tx6); + } + } } else { @@ -3835,10 +4030,10 @@ void MainWindow::genCQMsg () } } -void MainWindow::genStdMsgs(QString rpt) +void MainWindow::genStdMsgs(QString rpt, bool unconditional) { genCQMsg (); - QString hisCall=ui->dxCallEntry->text(); + auto const& hisCall=ui->dxCallEntry->text(); if(!hisCall.size ()) { ui->labAz->clear (); // ui->labDist->clear (); @@ -3846,126 +4041,141 @@ void MainWindow::genStdMsgs(QString rpt) ui->tx2->clear (); ui->tx3->clear (); ui->tx4->clear (); - ui->tx5->lineEdit ()->clear (); + if (unconditional) { // leave in place in case it needs + // sending again + ui->tx5->lineEdit ()->clear (); + } ui->genMsg->clear (); m_gen_message_is_cq = false; return; } - QString hisBase = Radio::base_callsign (hisCall); - + auto const& my_callsign = m_config.my_callsign (); + auto is_compound = my_callsign != m_baseCall; + auto is_type_one = is_compound && shortList (my_callsign); + auto const& my_grid = m_config.my_grid ().left (4); + auto const& hisBase = Radio::base_callsign (hisCall); + auto eme_short_codes = m_config.enable_VHF_features () && ui->cbShMsgs->isChecked () && m_mode == "JT65"; QString t0=hisBase + " " + m_baseCall + " "; QString t00=t0; - QString t {t0 + m_config.my_grid ().left (4)}; + QString t {t0 + my_grid}; msgtype(t, ui->tx1); - if(m_config.enable_VHF_features() and ui->cbShMsgs->isChecked() and m_mode=="JT65") { + if (eme_short_codes) { t=t+" OOO"; msgtype(t, ui->tx2); msgtype("RO", ui->tx3); - msgtype("RRR", ui->tx4); + msgtype(m_send_RR73 ? "RR73" : "RRR", ui->tx4); msgtype("73", ui->tx5->lineEdit ()); } else { int n=rpt.toInt(); rpt.sprintf("%+2.2d",n); - if(m_mode=="MSK144") { + if(m_mode=="MSK144" or m_mode=="FT8") { if(m_config.contestMode()) { - t=t0 + m_config.my_grid().mid(0,4); + t=t0 + my_grid; msgtype(t, ui->tx2); - t=t0 + "R " + m_config.my_grid().mid(0,4); + t=t0 + "R " + my_grid; msgtype(t, ui->tx3); } - if(m_bShMsgs) { - int i=t0.length()-1; - t0="<" + t0.mid(0,i) + "> "; - if(!m_config.contestMode()) { - if(n<=-2) n=-3; - if(n>=-1 and n<=1) n=0; - if(n>=2 and n<=4) n=3; - if(n>=5 and n<=7) n=6; - if(n>=8 and n<=11) n=10; - if(n>=12 and n<=14) n=13; - if(n>=15) n=16; - rpt.sprintf("%+2.2d",n); - } + } + if(m_mode=="MSK144" and m_bShMsgs) { + int i=t0.length()-1; + t0="<" + t0.mid(0,i) + "> "; + if(!m_config.contestMode()) { + if(n<=-2) n=-3; + if(n>=-1 and n<=1) n=0; + if(n>=2 and n<=4) n=3; + if(n>=5 and n<=7) n=6; + if(n>=8 and n<=11) n=10; + if(n>=12 and n<=14) n=13; + if(n>=15) n=16; + rpt.sprintf("%+2.2d",n); } } - if(m_mode!="MSK144" or !m_config.contestMode()) { + if((m_mode!="MSK144" and m_mode!="FT8") or !m_config.contestMode()) { t=t00 + rpt; msgtype(t, ui->tx2); t=t0 + "R" + rpt; msgtype(t, ui->tx3); } - t=t0 + "RRR"; - if((m_mode=="JT4" or m_mode=="QRA64") and m_bShMsgs) t="@1500 (RRR)"; + t=t0 + (m_send_RR73 ? "RR73" : "RRR"); + if ((m_mode=="JT4" || m_mode=="QRA64") && m_bShMsgs) t="@1500 (RRR)"; msgtype(t, ui->tx4); t=t0 + "73"; - if(m_mode=="JT4" or m_mode=="QRA64") { - if (m_bShMsgs) t="@1750 (73)"; - msgtype(t, ui->tx5->lineEdit ()); + if (m_mode=="JT4" || m_mode=="QRA64") { + if (m_bShMsgs) t="@1750 (73)"; + msgtype(t, ui->tx5->lineEdit ()); } - else if (hisBase != m_lastCallsign) { // only update tx5 when callsign changes - + else if ("MSK144" == m_mode && m_bShMsgs) { + msgtype(t, ui->tx5->lineEdit ()); + } + else if (unconditional || hisBase != m_lastCallsign || !m_lastCallsign.size ()) { + // only update tx5 when forced or callsign changes msgtype(t, ui->tx5->lineEdit ()); m_lastCallsign = hisBase; } - } + } - if(m_config.my_callsign () != m_baseCall) { - if(shortList(m_config.my_callsign ())) { - t=hisBase + " " + m_config.my_callsign (); + if (is_compound) { + if (is_type_one) { + t=hisBase + " " + my_callsign; msgtype(t, ui->tx1); - t="CQ " + m_config.my_callsign (); - if(ui->cbCQTx->isEnabled () && ui->cbCQTx->isVisible () && ui->cbCQTx->isChecked()) { - msgtype ( - QString {"CQ %1 %2"} - .arg (m_freqNominal / 1000 - m_freqNominal / 1000000 * 1000, 3, 10, QChar {'0'}) - .arg (m_config.my_callsign()), - ui->tx6); - } - else msgtype(t, ui->tx6); } else { switch (m_config.type_2_msg_gen ()) { case Configuration::type_2_msg_1_full: - t="DE " + m_config.my_callsign () + " " + m_config.my_grid ().mid(0,4); + t="DE " + my_callsign + " " + my_grid; msgtype(t, ui->tx1); - t=t0 + "R" + rpt; - msgtype(t, ui->tx3); + if (!eme_short_codes) { + t=t0 + "R" + rpt; + msgtype(t, ui->tx3); + if ((m_mode != "JT4" && m_mode != "QRA64") || !m_bShMsgs) { + t="DE " + my_callsign + " 73"; + msgtype(t, ui->tx5->lineEdit ()); + } + } break; case Configuration::type_2_msg_3_full: - t = t0 + m_config.my_grid ().mid(0,4); + t = t00 + my_grid; msgtype(t, ui->tx1); - t="DE " + m_config.my_callsign () + " R" + rpt; + t="DE " + my_callsign + " R" + rpt; msgtype(t, ui->tx3); + if (!eme_short_codes && ((m_mode != "JT4" && m_mode != "QRA64") || !m_bShMsgs)) { + t="DE " + my_callsign + " 73"; + msgtype(t, ui->tx5->lineEdit ()); + } break; case Configuration::type_2_msg_5_only: - t = t0 + m_config.my_grid ().mid(0,4); + t = t00 + my_grid; msgtype(t, ui->tx1); - t=t0 + "R" + rpt; - msgtype(t, ui->tx3); + if (!eme_short_codes) { + t=t0 + "R" + rpt; + msgtype(t, ui->tx3); + } + t="DE " + my_callsign + " 73"; + msgtype(t, ui->tx5->lineEdit ()); break; } - t="DE " + m_config.my_callsign () + " 73"; - msgtype(t, ui->tx5->lineEdit ()); } if (hisCall != hisBase - && m_config.type_2_msg_gen () != Configuration::type_2_msg_5_only) { + && m_config.type_2_msg_gen () != Configuration::type_2_msg_5_only + && !eme_short_codes) { // cfm we have his full call copied as we could not do this earlier t = hisCall + " 73"; msgtype(t, ui->tx5->lineEdit ()); } } else { - if(hisCall!=hisBase) { - if(shortList(hisCall)) { + if (hisCall != hisBase) { + if (shortList(hisCall)) { // cfm we know his full call with a type 1 tx1 message - t=hisCall + " " + m_config.my_callsign (); + t = hisCall + " " + my_callsign; msgtype(t, ui->tx1); } - else { + else if (!eme_short_codes + && ("MSK144" != m_mode || !m_bShMsgs)) { t=hisCall + " 73"; - msgtype(t, ui->tx5->lineEdit()); + msgtype(t, ui->tx5->lineEdit ()); } } } @@ -3981,6 +4191,7 @@ void MainWindow::clearDX () { ui->dxCallEntry->clear (); ui->dxGridEntry->clear (); + m_lastCallsign.clear (); m_rptSent.clear (); m_rptRcvd.clear (); m_qsoStart.clear (); @@ -3998,6 +4209,7 @@ void MainWindow::clearDX () m_ntx=6; ui->txrb6->setChecked(true); } + m_QSOProgress = CALLING; } void MainWindow::lookup() //lookup() @@ -4126,7 +4338,7 @@ void MainWindow::msgtype(QString t, QLineEdit* tx) //msgtype() { char message[29]; char msgsent[29]; - int itone0[NUM_ISCAT_SYMBOLS]; //Dummy array, data not used + int itone0[NUM_ISCAT_SYMBOLS]; //Dummy array, data not used int len1=22; QByteArray s=t.toUpper().toLocal8Bit(); ba2msg(s,message); @@ -4139,7 +4351,7 @@ void MainWindow::msgtype(QString t, QLineEdit* tx) //msgtype() if(itype==7 and m_config.enable_VHF_features() and m_mode=="JT65") shortMsg=true; if(m_mode=="MSK144" and t.mid(0,1)=="<") text=false; - if(m_mode=="MSK144" and m_config.contestMode()) { + if((m_mode=="MSK144" or m_mode=="FT8") and m_config.contestMode()) { int i0=t.trimmed().length()-7; if(t.mid(i0,3)==" R ") text=false; } @@ -4324,8 +4536,8 @@ void MainWindow::displayWidgets(int n) if(i==19) ui->actionQuickDecode->setEnabled(b); if(i==19) ui->actionMediumDecode->setEnabled(b); if(i==19) ui->actionDeepestDecode->setEnabled(b); - if(i==20) ui->actionInclude_averaging->setEnabled(b); - if(i==21) ui->actionInclude_correlation->setEnabled(b); + if(i==20) ui->actionInclude_averaging->setVisible (b); + if(i==21) ui->actionInclude_correlation->setVisible (b); if(i==22) { if(b && !m_echoGraph->isVisible()) { m_echoGraph->show(); @@ -4338,9 +4550,10 @@ void MainWindow::displayWidgets(int n) } j=j>>1; } - b=m_mode=="FT8"; - ui->cbFirst->setVisible(b); - ui->cbWeak->setVisible(false); + ui->cbFirst->setVisible ("FT8" == m_mode); + ui->actionEnable_AP->setVisible ("FT8" == m_mode); + m_lastCallsign.clear (); // ensures Tx5 is updated for new modes + genStdMsgs (m_rpt, true); } void MainWindow::on_actionFT8_triggered() @@ -4356,7 +4569,6 @@ void MainWindow::on_actionFT8_triggered() */ m_mode="FT8"; bool bVHF=false; - displayWidgets(nWidgets("111010000000111000010000")); m_bFast9=false; m_bFastMode=false; WSPR_config(false); @@ -4372,7 +4584,6 @@ void MainWindow::on_actionFT8_triggered() m_wideGraph->setMode(m_mode); m_wideGraph->setModeTx(m_modeTx); VHF_features_enabled(bVHF); - ui->cbAutoSeq->setVisible(true); ui->cbAutoSeq->setChecked(true); m_TRperiod=15; m_fastGraph->hide(); @@ -4384,6 +4595,7 @@ void MainWindow::on_actionFT8_triggered() m_detector->setPeriod(m_TRperiod); // TODO - not thread safe ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); + displayWidgets(nWidgets("111010000100111000010000")); statusChanged(); } @@ -4391,11 +4603,6 @@ void MainWindow::on_actionJT4_triggered() { m_mode="JT4"; bool bVHF=m_config.enable_VHF_features(); - if(bVHF) { - displayWidgets(nWidgets("111110010010111110110000")); - } else { - displayWidgets(nWidgets("111010000000111000111100")); - } WSPR_config(false); switch_mode (Modes::JT4); m_modeTx="JT4"; @@ -4416,7 +4623,6 @@ void MainWindow::on_actionJT4_triggered() m_bFastMode=false; m_bFast9=false; setup_status_bar (bVHF); - fast_config(false); ui->sbSubmode->setMaximum(6); ui->label_6->setText("Single-Period Decodes"); ui->label_7->setText("Average Decodes"); @@ -4427,6 +4633,12 @@ void MainWindow::on_actionJT4_triggered() } else { ui->sbSubmode->setValue(0); } + if(bVHF) { + displayWidgets(nWidgets("111110010010111110110000")); + } else { + displayWidgets(nWidgets("111010000000111000111100")); + } + fast_config(false); statusChanged(); } @@ -4434,11 +4646,6 @@ void MainWindow::on_actionJT9_triggered() { m_mode="JT9"; bool bVHF=m_config.enable_VHF_features(); - if(bVHF) { - displayWidgets(nWidgets("111110101100111110010000")); - } else { - displayWidgets(nWidgets("111010000000111000010000")); - } m_bFast9=ui->cbFast9->isChecked(); m_bFastMode=m_bFast9; WSPR_config(false); @@ -4461,9 +4668,7 @@ void MainWindow::on_actionJT9_triggered() ui->cbFast9->setEnabled(false); ui->cbFast9->setChecked(false); } - ui->cbAutoSeq->setVisible(m_bFast9); ui->sbSubmode->setMaximum(7); - fast_config(m_bFastMode); if(m_bFast9) { m_TRperiod = ui->sbTR->value (); m_wideGraph->hide(); @@ -4483,13 +4688,19 @@ void MainWindow::on_actionJT9_triggered() m_detector->setPeriod(m_TRperiod); // TODO - not thread safe ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); + if(bVHF) { + displayWidgets(nWidgets("111110101000111110010000")); + } else { + displayWidgets(nWidgets("111010000000111000010000")); + } + fast_config(m_bFastMode); + ui->cbAutoSeq->setVisible(m_bFast9); statusChanged(); } void MainWindow::on_actionJT9_JT65_triggered() { m_mode="JT9+JT65"; - displayWidgets(nWidgets("111010000001111000010000")); WSPR_config(false); switch_mode (Modes::JT65); if(m_modeTx != "JT65") { @@ -4514,12 +4725,13 @@ void MainWindow::on_actionJT9_JT65_triggered() m_wideGraph->setModeTx(m_modeTx); m_bFastMode=false; m_bFast9=false; - fast_config(false); ui->sbSubmode->setValue(0); ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); ui->decodedTextLabel->setText("UTC dB DT Freq Message"); ui->decodedTextLabel2->setText("UTC dB DT Freq Message"); + displayWidgets(nWidgets("111010000001111000010000")); + fast_config(false); statusChanged(); } @@ -4534,11 +4746,6 @@ void MainWindow::on_actionJT65_triggered() on_actionJT9_triggered(); m_mode="JT65"; bool bVHF=m_config.enable_VHF_features(); - if(bVHF) { - displayWidgets(nWidgets("111110010000111110110000")); - } else { - displayWidgets(nWidgets("111010000000111000011100")); - } WSPR_config(false); switch_mode (Modes::JT65); if(m_modeTx!="JT65") on_pbTxMode_clicked(); @@ -4559,7 +4766,6 @@ void MainWindow::on_actionJT65_triggered() setup_status_bar (bVHF); m_bFastMode=false; m_bFast9=false; - fast_config(false); ui->sbSubmode->setMaximum(2); if(bVHF) { ui->sbSubmode->setValue(m_nSubMode); @@ -4570,6 +4776,12 @@ void MainWindow::on_actionJT65_triggered() ui->label_6->setText("Band Activity"); ui->label_7->setText("Rx Frequency"); } + if(bVHF) { + displayWidgets(nWidgets("111110010000111110110000")); + } else { + displayWidgets(nWidgets("111010000000111000011100")); + } + fast_config(false); statusChanged(); } @@ -4579,11 +4791,9 @@ void MainWindow::on_actionQRA64_triggered() on_actionJT65_triggered(); m_nSubMode=n; m_mode="QRA64"; - displayWidgets(nWidgets("111110010010111110000000")); m_modeTx="QRA64"; ui->actionQRA64->setChecked(true); switch_mode (Modes::QRA64); - statusChanged(); setup_status_bar (true); m_hsymStop=180; if(m_config.decode_at_52s()) m_hsymStop=188; @@ -4591,8 +4801,8 @@ void MainWindow::on_actionQRA64_triggered() m_wideGraph->setModeTx(m_modeTx); ui->sbSubmode->setMaximum(4); ui->sbSubmode->setValue(m_nSubMode); - ui->actionInclude_averaging->setEnabled(false); - ui->actionInclude_correlation->setEnabled(false); + ui->actionInclude_averaging->setVisible (false); + ui->actionInclude_correlation->setVisible (false); QString fname {QDir::toNativeSeparators(m_config.temp_dir ().absoluteFilePath ("red.dat"))}; m_wideGraph->setRedFile(fname); QFile f(m_appDir + "/old_qra_sync"); @@ -4601,12 +4811,13 @@ void MainWindow::on_actionQRA64_triggered() "Using old QRA64 sync pattern."); m_bQRAsyncWarned=true; } + displayWidgets(nWidgets("111110010110111110000000")); + statusChanged(); } void MainWindow::on_actionISCAT_triggered() { m_mode="ISCAT"; - displayWidgets(nWidgets("100111000000000110000000")); m_modeTx="ISCAT"; ui->actionISCAT->setChecked(true); m_TRperiod = ui->sbTR->value (); @@ -4626,7 +4837,6 @@ void MainWindow::on_actionISCAT_triggered() if(!m_fastGraph->isVisible()) m_fastGraph->show(); if(m_wideGraph->isVisible()) m_wideGraph->hide(); setup_status_bar (true); - fast_config(true); ui->cbShMsgs->setChecked(false); ui->label_7->setText(""); ui->decodedTextBrowser2->setVisible(false); @@ -4637,14 +4847,14 @@ void MainWindow::on_actionISCAT_triggered() ui->sbSubmode->setMaximum(1); if(m_nSubMode==0) ui->TxFreqSpinBox->setValue(1012); if(m_nSubMode==1) ui->TxFreqSpinBox->setValue(560); + displayWidgets(nWidgets("100111000000000110000000")); + fast_config(true); statusChanged (); } void MainWindow::on_actionMSK144_triggered() { m_mode="MSK144"; - displayWidgets(nWidgets("101111110100000000010001")); -// displayWidgets(nWidgets("101111111100000000010001")); m_modeTx="MSK144"; ui->actionMSK144->setChecked(true); switch_mode (Modes::MSK144); @@ -4657,7 +4867,6 @@ void MainWindow::on_actionMSK144_triggered() VHF_features_enabled(true); m_bFastMode=true; m_bFast9=false; - fast_config(m_bFastMode); m_TRperiod = ui->sbTR->value (); m_wideGraph->hide(); m_fastGraph->show(); @@ -4679,13 +4888,15 @@ void MainWindow::on_actionMSK144_triggered() ui->rptSpinBox->setValue(0); ui->rptSpinBox->setSingleStep(1); ui->sbFtol->values ({20, 50, 100, 200}); + displayWidgets(nWidgets("101111110100000000010001")); +// displayWidgets(nWidgets("101111111100000000010001")); + fast_config(m_bFastMode); statusChanged(); } void MainWindow::on_actionWSPR_triggered() { m_mode="WSPR"; - displayWidgets(nWidgets("000000000000000001010000")); WSPR_config(true); switch_mode (Modes::WSPR); m_modeTx="WSPR"; @@ -4705,8 +4916,9 @@ void MainWindow::on_actionWSPR_triggered() m_wideGraph->setModeTx(m_modeTx); m_bFastMode=false; m_bFast9=false; - fast_config(false); ui->TxFreqSpinBox->setValue(ui->WSPRfreqSpinBox->value()); + displayWidgets(nWidgets("000000000000000001010000")); + fast_config(false); statusChanged(); } @@ -4731,7 +4943,6 @@ void MainWindow::on_actionEcho_triggered() { on_actionJT4_triggered(); m_mode="Echo"; - displayWidgets(nWidgets("000000000000000000000010")); ui->actionEcho->setChecked(true); m_TRperiod=3; m_modulator->setPeriod(m_TRperiod); // TODO - not thread safe @@ -4754,9 +4965,10 @@ void MainWindow::on_actionEcho_triggered() } m_bFastMode=false; m_bFast9=false; - fast_config(false); WSPR_config(true); ui->decodedTextLabel->setText(" UTC N Level Sig DF Width Q"); + displayWidgets(nWidgets("000000000000000000000010")); + fast_config(false); statusChanged(); } @@ -4764,11 +4976,9 @@ void MainWindow::on_actionFreqCal_triggered() { on_actionJT9_triggered(); m_mode="FreqCal"; - displayWidgets(nWidgets("001101000000000000000000")); ui->actionFreqCal->setChecked(true); switch_mode(Modes::FreqCal); m_wideGraph->setMode(m_mode); - statusChanged(); m_TRperiod = ui->sbTR->value (); m_modulator->setPeriod(m_TRperiod); // TODO - not thread safe m_detector->setPeriod(m_TRperiod); // TODO - not thread safe @@ -4781,6 +4991,8 @@ void MainWindow::on_actionFreqCal_triggered() setup_status_bar (true); // 18:15:47 0 1 1500 1550.349 0.100 3.5 10.2 ui->decodedTextLabel->setText(" UTC Freq CAL Offset fMeas DF Level S/N"); + displayWidgets(nWidgets("001101000000000000000000")); + statusChanged(); } void MainWindow::switch_mode (Mode mode) @@ -4849,7 +5061,7 @@ void MainWindow::fast_config(bool b) { m_bFastMode=b; ui->TxFreqSpinBox->setEnabled(!b); -// ui->sbTR->setVisible(b); + ui->sbTR->setVisible(b); if(b and (m_bFast9 or m_mode=="MSK144" or m_mode=="ISCAT")) { m_wideGraph->hide(); m_fastGraph->show(); @@ -4917,11 +5129,6 @@ void MainWindow::on_actionEnable_AP_DXcall_toggled (bool checked) m_ndepth ^= (-checked ^ m_ndepth) & 0x00000040; } -void MainWindow::on_inGain_valueChanged(int n) -{ - m_inGain=n; -} - void MainWindow::on_actionErase_ALL_TXT_triggered() //Erase ALL.TXT { int ret = MessageBox::query_message (this, tr ("Confirm Erase"), @@ -5010,6 +5217,7 @@ void MainWindow::band_changed (Frequency f) // disable auto Tx if "blind" QSY outside of waterfall ui->stopTxButton->click (); // halt any transmission auto_tx_mode (false); // disable auto Tx + m_send_RR73 = false; // force user to reassess on new band } } m_lastBand.clear (); @@ -5057,6 +5265,7 @@ void MainWindow::on_pbCallCQ_clicked() genStdMsgs(m_rpt); ui->genMsg->setText(ui->tx6->text()); m_ntx=7; + m_QSOProgress = CALLING; m_gen_message_is_cq = true; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5072,6 +5281,7 @@ void MainWindow::on_pbAnswerCaller_clicked() t=t.mid(0,i0+1)+t.mid(i0+2,3); ui->genMsg->setText(t); m_ntx=7; + m_QSOProgress = REPORT; m_gen_message_is_cq = false; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5083,6 +5293,7 @@ void MainWindow::on_pbSendRRR_clicked() genStdMsgs(m_rpt); ui->genMsg->setText(ui->tx4->text()); m_ntx=7; + m_QSOProgress = ROGERS; m_gen_message_is_cq = false; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5097,6 +5308,7 @@ void MainWindow::on_pbAnswerCQ_clicked() int i1=t.indexOf(" "); if(i0>0 and i0genMsg->setText(t); m_ntx=7; + m_QSOProgress = REPLYING; m_gen_message_is_cq = false; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5107,6 +5319,7 @@ void MainWindow::on_pbSendReport_clicked() genStdMsgs(m_rpt); ui->genMsg->setText(ui->tx3->text()); m_ntx=7; + m_QSOProgress = ROGER_REPORT; m_gen_message_is_cq = false; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5118,6 +5331,7 @@ void MainWindow::on_pbSend73_clicked() genStdMsgs(m_rpt); ui->genMsg->setText(ui->tx5->currentText()); m_ntx=7; + m_QSOProgress = SIGNOFF; m_gen_message_is_cq = false; ui->rbGenMsg->setChecked(true); if(m_transmitting) m_restart=true; @@ -5129,6 +5343,8 @@ void MainWindow::on_rbGenMsg_clicked(bool checked) if(!m_freeText) { if(m_ntx != 7 && m_transmitting) m_restart=true; m_ntx=7; + // would like to set m_QSOProgress but what to? So leave alone and + // assume it is correct } } @@ -5137,6 +5353,9 @@ void MainWindow::on_rbFreeText_clicked(bool checked) m_freeText=checked; if(m_freeText) { m_ntx=8; + // would like to set m_QSOProgress but what to? So leave alone and + // assume it is correct. Perhaps should store old value to be + // restored above in on_rbGenMsg_clicked if (m_transmitting) m_restart=true; } } @@ -5155,9 +5374,7 @@ void MainWindow::on_rptSpinBox_valueChanged(int n) } m_rpt=QString::number(n); int ntx0=m_ntx; - QString t=ui->tx5->currentText(); genStdMsgs(m_rpt); - if(!m_bDoubleClicked) ui->tx5->setCurrentText(t); m_ntx=ntx0; if(m_ntx==1) ui->txrb1->setChecked(true); if(m_ntx==2) ui->txrb2->setChecked(true); @@ -5222,6 +5439,9 @@ void MainWindow::on_stopTxButton_clicked() //Stop Tx if (m_tune) stop_tuning (); if (m_auto and !m_tuneup) auto_tx_mode (false); m_btxok=false; + m_bCallingCQ = false; + m_bAutoReply = false; // ready for next + ui->cbFirst->setStyleSheet (""); } void MainWindow::rigOpen () @@ -5355,7 +5575,12 @@ void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const& } m_rigState = s; auto old_freqNominal = m_freqNominal; - m_freqNominal = s.frequency () - m_astroCorrection.rx; + if (!old_freqNominal) + { + // always take initial rig frequency to avoid start up problems + // with bogus Tx frequencies + m_freqNominal = s.frequency (); + } if (old_state.online () == false && s.online () == true) { // initializing @@ -5364,9 +5589,10 @@ void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const& if (s.frequency () != old_state.frequency () || s.split () != m_splitMode) { m_splitMode = s.split (); - if (!s.ptt ()) //!m_transmitting) + if (!s.ptt ()) { - if (old_freqNominal != m_freqNominal) + m_freqNominal = s.frequency () - m_astroCorrection.rx; + if (old_freqNominal != m_freqNominal) { m_freqTxNominal = m_freqNominal; genCQMsg (); @@ -5450,6 +5676,7 @@ void MainWindow::rigFailure (QString const& reason) switch (m_rigErrorMessageBox.standardButton (clicked_button)) { case MessageBox::Ok: + m_config.select_tab (1); QTimer::singleShot (0, this, SLOT (on_actionSettings_triggered ())); break; @@ -5720,10 +5947,10 @@ void::MainWindow::VHF_features_enabled(bool b) ui->actionInclude_correlation->isChecked())) { ui->actionDeepestDecode->setChecked (true); } - ui->actionInclude_averaging->setEnabled(b); - ui->actionInclude_correlation->setEnabled(b); + ui->actionInclude_averaging->setVisible (b); + ui->actionInclude_correlation->setVisible (b); ui->actionMessage_averaging->setEnabled(b); - ui->actionEnable_AP_DXcall->setEnabled(m_mode=="QRA64"); + ui->actionEnable_AP_DXcall->setVisible (m_mode=="QRA64"); if(!b && m_msgAvgWidget) { if(m_msgAvgWidget->isVisible()) m_msgAvgWidget->close(); } @@ -5822,6 +6049,7 @@ void MainWindow::on_cbShMsgs_toggled(bool b) if(m_bShMsgs and (m_mode=="MSK144")) ui->rptSpinBox->setValue(1); int itone0=itone[0]; int ntx=m_ntx; + m_lastCallsign.clear (); // ensure Tx5 gets updated genStdMsgs(m_rpt); itone[0]=itone0; if(ntx==1) ui->txrb1->setChecked(true); @@ -5837,15 +6065,9 @@ void MainWindow::on_cbSWL_toggled(bool b) if(b) ui->cbShMsgs->setChecked(false); } -void MainWindow::on_cbTx6_toggled(bool b) +void MainWindow::on_cbTx6_toggled(bool) { - QString t; - if(b) { - t="@1250 (SEND MSGS)"; - } else { - t="@1000 (TUNE)"; - } - ui->tx6->setText(t); + genCQMsg (); } // Takes a decoded CQ line and sets it up for reply @@ -5897,7 +6119,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de // find the linefeed at the end of the line position = ui->decodedTextBrowser->toPlainText().indexOf(QChar::LineFeed,position); m_bDoubleClicked = true; - processMessage (messages, position, false); + processMessage (messages, position); tx_watchdog (false); QApplication::alert (this); } @@ -5949,14 +6171,15 @@ void MainWindow::postDecode (bool is_new, QString const& message) { auto const& decode = message.trimmed (); auto const& parts = decode.left (22).split (' ', QString::SkipEmptyParts); - if (parts.size () >= 5) + if (!m_diskData && parts.size () >= 5) { auto has_seconds = parts[0].size () > 4; m_messageClient->decode (is_new , QTime::fromString (parts[0], has_seconds ? "hhmmss" : "hhmm") , parts[1].toInt () , parts[2].toFloat (), parts[3].toUInt (), parts[4][0] - , decode.mid (has_seconds ? 24 : 22)); + , decode.mid (has_seconds ? 24 : 22, 21) + , QChar {'?'} == decode.mid (has_seconds ? 24 + 21 : 22 + 21, 1)); } } @@ -6241,14 +6464,14 @@ void MainWindow::astroUpdate () { if (m_astroWidget) { + // no Doppler correction while CTRL pressed allows manual tuning + if (Qt::ControlModifier & QApplication::queryKeyboardModifiers ()) return; + auto correction = m_astroWidget->astroUpdate(QDateTime::currentDateTimeUtc (), m_config.my_grid(), m_hisGrid, m_freqNominal, "Echo" == m_mode, m_transmitting, !m_config.tx_QSY_allowed (), m_TRperiod); - // no Doppler correction while CTRL pressed allows manual tuning - if (Qt::ControlModifier & QApplication::queryKeyboardModifiers ()) return; - // no Doppler correction in Tx if rig can't do it if (m_transmitting && !m_config.tx_QSY_allowed ()) return; if (!m_astroWidget->doppler_tracking ()) return; @@ -6386,6 +6609,7 @@ void MainWindow::on_cbCQTx_toggled(bool b) if(b) { ui->txrb6->setChecked(true); m_ntx=6; + m_QSOProgress = CALLING; } setRig (); setXIT (ui->TxFreqSpinBox->value ()); @@ -6490,12 +6714,13 @@ void MainWindow::on_cbMenus_toggled(bool b) void MainWindow::on_cbFirst_toggled(bool b) { - if(b) ui->cbWeak->setChecked(!b); -} - -void MainWindow::on_cbWeak_toggled(bool b) -{ - if(b) ui->cbFirst->setChecked(!b); + if (b) { + if (m_auto && CALLING == m_QSOProgress) { + ui->cbFirst->setStyleSheet ("QCheckBox{color:red}"); + } + } else { + ui->cbFirst->setStyleSheet (""); + } } void MainWindow::on_cbAutoSeq_toggled(bool b) diff --git a/mainwindow.h b/mainwindow.h index 50a457e12..c490a2e38 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -156,16 +156,22 @@ private slots: void set_dateTimeQSO(int m_ntx); void set_ntx(int n); void on_txrb1_toggled(bool status); + void on_txrb1_doubleClicked (); void on_txrb2_toggled(bool status); void on_txrb3_toggled(bool status); void on_txrb4_toggled(bool status); + void on_txrb4_doubleClicked (); void on_txrb5_toggled(bool status); + void on_txrb5_doubleClicked (); void on_txrb6_toggled(bool status); void on_txb1_clicked(); + void on_txb1_doubleClicked (); void on_txb2_clicked(); void on_txb3_clicked(); void on_txb4_clicked(); + void on_txb4_doubleClicked (); void on_txb5_clicked(); + void on_txb5_doubleClicked (); void on_txb6_clicked(); void on_lookupButton_clicked(); void on_addButton_clicked(); @@ -184,7 +190,6 @@ private slots: void on_actionQuickDecode_toggled (bool); void on_actionMediumDecode_toggled (bool); void on_actionDeepestDecode_toggled (bool); - void on_inGain_valueChanged(int n); void bumpFqso(int n); void on_actionErase_ALL_TXT_triggered(); void on_actionErase_wsjtx_log_adi_triggered(); @@ -239,7 +244,6 @@ private slots: void on_cbTx6_toggled(bool b); void on_cbMenus_toggled(bool b); void on_cbFirst_toggled(bool b); - void on_cbWeak_toggled(bool b); void on_cbAutoSeq_toggled(bool b); void networkError (QString const&); void on_ClrAvgButton_clicked(); @@ -300,7 +304,7 @@ private: private: void astroUpdate (); void writeAllTxt(QString message); - void FT8_AutoSeq(QString message); + void auto_sequence (QString const& message, unsigned start_tolerance, unsigned stop_tolerance); void hideMenus(bool b); NetworkAccessManager m_network_manager; @@ -364,6 +368,7 @@ private: qint32 m_waterfallAvg; qint32 m_ntx; bool m_gen_message_is_cq; + bool m_send_RR73; qint32 m_timeout; qint32 m_XIT; qint32 m_setftx; @@ -447,6 +452,17 @@ private: bool m_bQRAsyncWarned; bool m_bDoubleClicked; bool m_bCallingCQ; + bool m_bAutoReply; + enum + { + CALLING, + REPLYING, + REPORT, + ROGER_REPORT, + ROGERS, + SIGNOFF + } + m_QSOProgress; int m_ihsym; int m_nzap; @@ -558,7 +574,7 @@ private: void writeSettings(); void createStatusBar(); void updateStatusBar(); - void genStdMsgs(QString rpt); + void genStdMsgs(QString rpt, bool unconditional = false); void genCQMsg(); void clearDX (); void lookup(); @@ -574,7 +590,7 @@ private: void pskPost(DecodedText decodedtext); void displayDialFrequency (); void transmitDisplay (bool); - void processMessage(QString const& messages, qint32 position, bool ctrl); + void processMessage(QString const& messages, qint32 position, bool ctrl = false, bool alt = false); void replyToCQ (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text); void replayDecodes (); void postDecode (bool is_new, QString const& message); diff --git a/mainwindow.ui b/mainwindow.ui index 2a7cdec33..6ab1010bc 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -761,16 +761,6 @@ QLabel[oob="true"] { - - - - <html><head/><body><p>Call the weakest decoded responder to my CQ.</p></body></html> - - - Weak - - - @@ -838,7 +828,7 @@ QLabel[oob="true"] { - Tx frequency tracks Rx frequency + <html><head/><body><p>Tx frequency tracks Rx frequency. </p><p>Not recommeded for general use!</p></body></html> Lock Tx=Rx @@ -975,7 +965,7 @@ QLabel[oob="true"] { - Check to Tx in even minutes, uncheck for odd minutes + <html><head/><body><p>Check to Tx in even-numbered minutes or sequences, starting at 0; uncheck for odd sequences.</p></body></html> Tx even/1st @@ -1097,9 +1087,9 @@ QLabel[oob="true"] { - + - Send this message in next Tx interval + <html><head/><body><p>Send this message in next Tx interval</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station</p></body></html> @@ -1113,7 +1103,7 @@ QLabel[oob="true"] { - + 30 @@ -1121,7 +1111,7 @@ QLabel[oob="true"] { - Switch to this Tx message NOW + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double click to toggle the use of the Tx1 message to start a QSO with a station</p></body></html> Qt::LeftToRight @@ -1235,9 +1225,9 @@ QLabel[oob="true"] { - + - Send this message in next Tx interval + <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to toggle between RRR and RR73 messages in Tx4</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> @@ -1251,7 +1241,7 @@ QLabel[oob="true"] { - + 30 @@ -1259,7 +1249,7 @@ QLabel[oob="true"] { - Switch to this Tx message NOW + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to toggle between RRR and RR73 messages in Tx4</p><p>RR73 messages should only be used when you are reasonably confident that no message repititions will be required</p></body></html> Tx &4 @@ -1292,9 +1282,9 @@ list. The list can be maintained in Settings (F2). - + - Send this message in next Tx interval + <html><head/><body><p>Send this message in next Tx interval</p><p>Double-click to reset to the standard 73 message</p></body></html> @@ -1308,7 +1298,7 @@ list. The list can be maintained in Settings (F2). - + 30 @@ -1316,7 +1306,7 @@ list. The list can be maintained in Settings (F2). - Switch to this Tx message NOW + <html><head/><body><p>Switch to this Tx message NOW</p><p>Double-click to reset to the standard 73 message</p></body></html> Tx &5 @@ -1935,28 +1925,6 @@ list. The list can be maintained in Settings (F2). - - - - Digital gain for graph windows - - - -50 - - - 50 - - - 20 - - - QSlider::TicksBelow - - - 10 - - - @@ -2409,6 +2377,7 @@ QPushButton[state="ok"] { + @@ -2943,9 +2912,6 @@ QPushButton[state="ok"] { Enable AP for DX Call - - - @@ -3011,6 +2977,14 @@ QPushButton[state="ok"] { FT8 + + + true + + + Enable AP + + @@ -3040,6 +3014,16 @@ QPushButton[state="ok"] { QSpinBox
RestrictedSpinBox.hpp
+ + DoubleClickableRadioButton + QRadioButton +
DoubleClickableRadioButton.hpp
+
+ + DoubleClickablePushButton + QPushButton +
DoubleClickablePushButton.hpp
+
logQSOButton @@ -3051,7 +3035,6 @@ QPushButton[state="ok"] { autoButton stopTxButton tuneButton - inGain dxCallEntry dxGridEntry lookupButton diff --git a/mouse_commands.txt b/mouse_commands.txt index b087d573c..60df57c0f 100644 --- a/mouse_commands.txt +++ b/mouse_commands.txt @@ -18,7 +18,10 @@ locator to Dx Grid; change Rx and Tx frequencies to
decoded signal's frequency; generate standard messages.
If first callsign is your own, Tx frequency is not
- changed unless Ctrl is held down when double-clicking. + changed unless Ctrl is held down when double-clicking.
+
+ Hold down Alt to only move the Rx frequency when
+ double-clicking to reply to a CQ or QRZ caller. diff --git a/plotter.cpp b/plotter.cpp index 9d7375b50..550bdda5f 100644 --- a/plotter.cpp +++ b/plotter.cpp @@ -361,7 +361,7 @@ void CPlotter::DrawOverlay() //DrawOverlay() float bw=9.0*12000.0/m_nsps; //JT9 - if(m_mode=="FT8") bw=8*12000.0/1920.0; //FT8 + if(m_mode=="FT8") bw=7*12000.0/1920.0; //FT8 if(m_mode=="JT4") { //JT4 bw=3*11025.0/2520.0; //Max tone spacing (3/4 of actual BW) diff --git a/shortcuts.txt b/shortcuts.txt index 2b67dfd39..f516ad400 100644 --- a/shortcuts.txt +++ b/shortcuts.txt @@ -1,9 +1,9 @@ - + - + diff --git a/widegraph.cpp b/widegraph.cpp index d6295a84c..d182aeeb3 100644 --- a/widegraph.cpp +++ b/widegraph.cpp @@ -360,7 +360,7 @@ void WideGraph::setRxBand (QString const& band) else { ui->fSplitSpinBox->setValue (m_fMinPerBand.value (band, 2500).toUInt ()); - ui->fSplitSpinBox->setEnabled (true); + ui->fSplitSpinBox->setEnabled (m_mode=="JT9+JT65"); } ui->widePlot->setRxBand(band); setRxRange ();
F1 Online User's Guide
Ctrl+F1 About WSJT-X
F2 Open configuration window
F2 Open settings window
F3 Display keyboard shortcuts
F4 Clear DX Call, DX Grid, Tx messages 1-5
F4 Clear DX Call, DX Grid, Tx messages 1-4
Alt+F4 Exit program
F5 Display special mouse commands
F6 Open next file in directory