From 3c384f7cbb32bbec2ce4f49dddb72bab44306301 Mon Sep 17 00:00:00 2001 From: Bill Somerville Date: Sun, 4 Feb 2018 22:42:35 +0000 Subject: [PATCH] Add UDP message to set temporary Maidenhead locator When "Auto Grid" is checked in "Settings->General" UDP messages of type "Location" will update a temporary DE grid square. The intent is to allow an external application joining the WSJT-X UDP message protocol to dynamically update the DE grid during mobile operation. This change also tidies up some outstanding issues around logging the operator call. This change adds a new UDP message "Logged ADIF" that is emitted in parallel with "QSO Logged" messages. The new message is valid ADIF file format and contains the logged QSO fields. The intent is that basic UDP server applications might already have ADIF log record capture capabilities and could use this message to feed existing ADIF parsing routines to log QSOs. All that should be needed is to identify this message type and the single field is ADIF compatible ASCII. Thanks to Brian, N9ADG, for the patches that lead to these enhancements. git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/branches/wsjtx@8454 ab8295b8-cf94-4d9e-aec4-7959e3be5d79 --- CMakeLists.txt | 1 + Configuration.cpp | 31 ++++++++++++++- Configuration.hpp | 3 ++ Configuration.ui | 21 ++++++++-- MessageClient.cpp | 29 +++++++++++++- MessageClient.hpp | 12 +++++- MessageServer.cpp | 31 ++++++++++++++- MessageServer.hpp | 7 +++- NetworkMessage.hpp | 43 +++++++++++++++++++++ UDPExamples/ClientWidget.cpp | 14 ++++++- UDPExamples/ClientWidget.hpp | 3 ++ UDPExamples/MessageAggregatorMainWindow.cpp | 19 ++++++--- UDPExamples/MessageAggregatorMainWindow.hpp | 3 +- UDPExamples/UDPDaemon.cpp | 29 ++++++++++---- logbook/adif.cpp | 18 ++++----- logbook/adif.h | 10 +++-- logqso.cpp | 11 +++--- logqso.h | 6 ++- mainwindow.cpp | 43 ++++++++++++++++++--- mainwindow.h | 6 ++- 20 files changed, 287 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a91121d7d..2dd2e4e13 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -683,6 +683,7 @@ set (message_aggregator_CXXSRCS UDPExamples/DecodesModel.cpp UDPExamples/BeaconsModel.cpp UDPExamples/ClientWidget.cpp + MaidenheadLocatorValidator.cpp ) set (message_aggregator_STYLESHEETS diff --git a/Configuration.cpp b/Configuration.cpp index 3d19c0878..b1cab5b65 100644 --- a/Configuration.cpp +++ b/Configuration.cpp @@ -524,6 +524,7 @@ private: CalibrationParams calibration_; bool frequency_calibration_disabled_; // not persistent unsigned transceiver_command_number_; + QString dynamic_grid_; // configuration fields that we publish QString my_callsign_; @@ -569,6 +570,7 @@ private: bool bHound_; bool x2ToneSpacing_; bool x4ToneSpacing_; + bool use_dynamic_grid_; QString opCall_; QString udp_server_name_; port_type udp_server_port_; @@ -627,7 +629,6 @@ bool Configuration::restart_audio_input () const {return m_->restart_sound_input bool Configuration::restart_audio_output () const {return m_->restart_sound_output_device_;} auto Configuration::type_2_msg_gen () const -> Type2MsgGen {return m_->type_2_msg_gen_;} QString Configuration::my_callsign () const {return m_->my_callsign_;} -QString Configuration::my_grid () const {return m_->my_grid_;} QColor Configuration::color_CQ () const {return m_->color_CQ_;} QColor Configuration::color_MyCall () const {return m_->color_MyCall_;} QColor Configuration::color_TxMsg () const {return m_->color_TxMsg_;} @@ -797,6 +798,22 @@ bool Configuration::valid_n1mm_info () const return(!(server_name.trimmed().isEmpty() || port_number == 0)); } +QString Configuration::my_grid() const +{ + auto the_grid = m_->my_grid_; + if (m_->use_dynamic_grid_ && m_->dynamic_grid_.size () >= 4) { + the_grid = m_->dynamic_grid_; + } + return the_grid; +} + +void Configuration::set_location (QString const& grid_descriptor) +{ + // change the dynamic grid + qDebug () << "Configuration::set_location - location:" << grid_descriptor; + m_->dynamic_grid_ = grid_descriptor.trimmed (); +} + namespace { #if defined (Q_OS_MAC) @@ -1102,6 +1119,7 @@ void Configuration::impl::initialize_models () ui_->grid_line_edit->setPalette (pal); ui_->callsign_line_edit->setText (my_callsign_); ui_->grid_line_edit->setText (my_grid_); + ui_->use_dynamic_grid->setChecked(use_dynamic_grid_); ui_->labCQ->setStyleSheet(QString("background: %1").arg(color_CQ_.name())); ui_->labMyCall->setStyleSheet(QString("background: %1").arg(color_MyCall_.name())); ui_->labTx->setStyleSheet(QString("background: %1").arg(color_TxMsg_.name())); @@ -1316,6 +1334,7 @@ void Configuration::impl::read_settings () spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool (); id_after_73_ = settings_->value ("After73", false).toBool (); tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool (); + use_dynamic_grid_ = settings_->value ("AutoGrid", false).toBool (); macros_.setStringList (settings_->value ("Macros", QStringList {"TNX 73 GL"}).toStringList ()); @@ -1496,6 +1515,7 @@ void Configuration::impl::write_settings () settings_->setValue ("pwrBandTxMemory", pwrBandTxMemory_); settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_); settings_->setValue ("Region", QVariant::fromValue (region_)); + settings_->setValue ("AutoGrid", use_dynamic_grid_); } void Configuration::impl::set_rig_invariants () @@ -1931,7 +1951,14 @@ void Configuration::impl::accept () stations_.station_list(next_stations_.station_list ()); stations_.sort (StationList::band_column); } - + + if (ui_->use_dynamic_grid->isChecked() && !use_dynamic_grid_ ) + { + // turning on so clear it so only the next location update gets used + dynamic_grid_.clear (); + } + use_dynamic_grid_ = ui_->use_dynamic_grid->isChecked(); + write_settings (); // make visible to all } diff --git a/Configuration.hpp b/Configuration.hpp index a87e33814..886bed085 100644 --- a/Configuration.hpp +++ b/Configuration.hpp @@ -192,6 +192,9 @@ public: // Set the calibration parameters and enable calibration corrections. void set_calibration (CalibrationParams); + // Set the dynamic grid which is only used if configuration setting is enabled. + void set_location (QString const&); + // This method queries if a CAT and PTT connection is operational. bool is_transceiver_online () const; diff --git a/Configuration.ui b/Configuration.ui index c31fe3f22..2c1f9b173 100644 --- a/Configuration.ui +++ b/Configuration.ui @@ -80,11 +80,22 @@ + + + + + + Check to allow grid changes from external programs + + + AutoGrid + + + + + - - - @@ -95,6 +106,9 @@ + + + @@ -2599,6 +2613,7 @@ soundcard changes configuration_tabs callsign_line_edit grid_line_edit + use_dynamic_grid region_combo_box type_2_msg_gen_combo_box insert_blank_check_box diff --git a/MessageClient.cpp b/MessageClient.cpp index 799179ab7..2b6d9e308 100644 --- a/MessageClient.cpp +++ b/MessageClient.cpp @@ -207,6 +207,17 @@ void MessageClient::impl::parse_message (QByteArray const& msg) } break; + case NetworkMessage::Location: + { + QByteArray location; + in >> location; + if (check_status (in) != Fail) + { + Q_EMIT self_->location (QString::fromUtf8 (location)); + } + } + break; + default: // Ignore // @@ -433,7 +444,9 @@ void MessageClient::clear_decodes () void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QString const& dx_grid , Frequency dial_frequency, QString const& mode, QString const& report_sent , QString const& report_received, QString const& tx_power - , QString const& comments, QString const& name, QDateTime time_on, QString const& operator_call) + , QString const& comments, QString const& name, QDateTime time_on + , QString const& operator_call, QString const& my_call + , QString const& my_grid) { if (m_->server_port_ && !m_->server_string_.isEmpty ()) { @@ -441,7 +454,19 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_}; out << time_off << dx_call.toUtf8 () << dx_grid.toUtf8 () << dial_frequency << mode.toUtf8 () << report_sent.toUtf8 () << report_received.toUtf8 () << tx_power.toUtf8 () << comments.toUtf8 () - << name.toUtf8 () << time_on << operator_call.toUtf8 (); + << name.toUtf8 () << time_on << operator_call.toUtf8 () << my_call.toUtf8 () << my_grid.toUtf8 (); + m_->send_message (out, message); + } +} + +void MessageClient::logged_ADIF (QByteArray const& ADIF_record) +{ + if (m_->server_port_ && !m_->server_string_.isEmpty ()) + { + QByteArray message; + NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_}; + QByteArray ADIF {"\n3.0.7\nWSJT-X\n\n" + ADIF_record + " "}; + out << ADIF; m_->send_message (out, message); } } diff --git a/MessageClient.hpp b/MessageClient.hpp index 6cb3ea87f..f0719ac55 100644 --- a/MessageClient.hpp +++ b/MessageClient.hpp @@ -62,7 +62,12 @@ public: Q_SLOT void qso_logged (QDateTime time_off, QString const& dx_call, QString const& dx_grid , Frequency dial_frequency, QString const& mode, QString const& report_sent , QString const& report_received, QString const& tx_power, QString const& comments - , QString const& name, QDateTime time_on, QString const& operator_call); + , QString const& name, QDateTime time_on, QString const& operator_call + , QString const& my_call, QString const& my_grid); + + // ADIF_record argument should be valid ADIF excluding any end + // of record marker + Q_SLOT void logged_ADIF (QByteArray const& ADIF_record); // this slot may be used to send arbitrary UDP datagrams to and // destination allowing the underlying socket to be used for general @@ -94,6 +99,11 @@ public: // lookup fails Q_SIGNAL void error (QString const&) const; + // this signal is emitted if the message obtains a location from a + // server. (It doesn't have to be new, could be a periodic location + // update) + Q_SIGNAL void location (QString const&); + private: class impl; pimpl m_; diff --git a/MessageServer.cpp b/MessageServer.cpp index e6e100762..724da4afc 100644 --- a/MessageServer.cpp +++ b/MessageServer.cpp @@ -292,14 +292,18 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s QByteArray name; QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window QByteArray operator_call; + QByteArray my_call; + QByteArray my_grid; in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received - >> tx_power >> comments >> name >> time_on >> operator_call; + >> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid; if (check_status (in) != Fail) { Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid) , dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent) , QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power) - , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on, QString::fromUtf8 (operator_call)); + , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on + , QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call) + , QString::fromUtf8 (my_grid)); } } break; @@ -309,6 +313,17 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s clients_.remove (id); break; + case NetworkMessage::LoggedADIF: + { + QByteArray ADIF; + in >> ADIF; + if (check_status (in) != Fail) + { + Q_EMIT self_->logged_ADIF (id, ADIF); + } + } + break; + default: // Ignore break; @@ -453,3 +468,15 @@ void MessageServer::free_text (QString const& id, QString const& text, bool send m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); } } + +void MessageServer::location (QString const& id, QString const& loc) +{ + auto iter = m_->clients_.find (id); + if (iter != std::end (m_->clients_)) + { + QByteArray message; + NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_}; + out << loc.toUtf8 (); + m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); + } +} diff --git a/MessageServer.hpp b/MessageServer.hpp index 83fb4ff83..77c976fa4 100644 --- a/MessageServer.hpp +++ b/MessageServer.hpp @@ -59,6 +59,9 @@ public: // message and optionally send it ASAP Q_SLOT void free_text (QString const& id, QString const& text, bool send); + // ask the client with identification 'id' to set the location provided + Q_SLOT void location (QString const& id, QString const& location); + // the following signals are emitted when a client broadcasts the // matching message Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); @@ -77,8 +80,10 @@ public: Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid , Frequency dial_frequency, QString const& mode, QString const& report_sent , QString const& report_received, QString const& tx_power, QString const& comments - , QString const& name, QDateTime time_on, QString const& operator_call); + , QString const& name, QDateTime time_on, QString const& operator_call + , QString const& my_call, QString const& my_grid); Q_SIGNAL void clear_decodes (QString const& id); + Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF); // this signal is emitted when a network error occurs Q_SIGNAL void error (QString const&) const; diff --git a/NetworkMessage.hpp b/NetworkMessage.hpp index a14d6d1b8..479575a32 100644 --- a/NetworkMessage.hpp +++ b/NetworkMessage.hpp @@ -237,6 +237,9 @@ * Comments utf8 * Name utf8 * Date & Time On QDateTime + * Operator call utf8 + * My call utf8 + * My grid utf8 * * The QSO logged message is sent to the server(s) when the * WSJT-X user accepts the "Log QSO" dialog by clicking the "OK" @@ -304,6 +307,7 @@ * command to determine the contents of the current free text * message. * + * * WSPRDecode Out 10 quint32 * Id (unique key) utf8 * New bool @@ -327,6 +331,43 @@ * from a played back recording. * * + * Location In 11 + * Id (unique key) utf8 + * Location utf8 + * + * This message allows the server to set the current current + * geographical location of operation. The supplied location is + * not persistent but is used as a session lifetime replacement + * loction that overrides the Maidenhead grid locater set in the + * application settings. The intent is to allow an external + * application to update the operating location dynamically + * during a mobile period of operation. + * + * Currently only Maidenhead grid squares or sub-squares are + * accepted, i.e. 4- or 6-digit locators. Other formats may be + * accepted in future. + * + * + * Logged ADIF Out 12 quint32 + * Id (unique key) utf8 + * ADIF text ASCII (serialized like utf8) + * + * The logged ADIF message is sent to the server(s) when the + * WSJT-X user accepts the "Log QSO" dialog by clicking the "OK" + * button. The "ADIF text" field consists of a valid ADIF file + * such that the WSJT-X UDP header information is encapsulated + * into a valid ADIF header. E.g.: + * + * <32-bit-count> # binary encoded fields + * # the remainder is the contents of the ADIF text field + * 3.0.7 + * WSJT-X + * + * ADIF log data fields ... + * + * Note that receiving applications can treat the whole message + * as a valid ADIF file with one record without special parsing. + * */ #include @@ -353,6 +394,8 @@ namespace NetworkMessage HaltTx, FreeText, WSPRDecode, + Location, + LoggedADIF, maximum_message_type_ // ONLY add new message types // immediately before here }; diff --git a/UDPExamples/ClientWidget.cpp b/UDPExamples/ClientWidget.cpp index de77286b8..001041cae 100644 --- a/UDPExamples/ClientWidget.cpp +++ b/UDPExamples/ClientWidget.cpp @@ -3,6 +3,8 @@ #include #include +#include "MaidenheadLocatorValidator.hpp" + namespace { //QRegExp message_alphabet {"[- A-Za-z0-9+./?]*"}; @@ -120,9 +122,11 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod , decodes_table_view_ {new QTableView} , beacons_table_view_ {new QTableView} , message_line_edit_ {new QLineEdit} + , grid_line_edit_ {new QLineEdit} , decodes_stack_ {new QStackedLayout} , auto_off_button_ {new QPushButton {tr ("&Auto Off")}} , halt_tx_button_ {new QPushButton {tr ("&Halt Tx")}} + , de_label_ {new QLabel} , mode_label_ {new QLabel} , fast_mode_ {false} , frequency_label_ {new QLabel} @@ -141,13 +145,18 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod auto form_layout = new QFormLayout; form_layout->addRow (tr ("Free text:"), message_line_edit_); + form_layout->addRow (tr ("Temporary grid:"), grid_line_edit_); message_line_edit_->setValidator (new QRegExpValidator {message_alphabet, this}); + grid_line_edit_->setValidator (new MaidenheadLocatorValidator {this}); connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) { Q_EMIT do_free_text (id_, text, false); }); connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { Q_EMIT do_free_text (id_, message_line_edit_->text (), true); }); + connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () { + Q_EMIT location (id_, grid_line_edit_->text ()); + }); auto decodes_page = new QWidget; auto decodes_layout = new QVBoxLayout {decodes_page}; @@ -189,6 +198,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod // set up status area auto status_bar = new QStatusBar; + status_bar->addPermanentWidget (de_label_); status_bar->addPermanentWidget (mode_label_); status_bar->addPermanentWidget (frequency_label_); status_bar->addPermanentWidget (dx_label_); @@ -216,7 +226,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call , QString const& report, QString const& tx_mode, bool tx_enabled , bool transmitting, bool decoding, qint32 rx_df, qint32 tx_df - , QString const& de_call, QString const& /*de_grid*/, QString const& dx_grid + , QString const& de_call, QString const& de_grid, QString const& dx_grid , bool watchdog_timeout, QString const& sub_mode, bool fast_mode) { if (id == id_) @@ -224,6 +234,8 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const& fast_mode_ = fast_mode; decodes_proxy_model_.de_call (de_call); decodes_proxy_model_.rx_df (rx_df); + de_label_->setText (de_call.size () >= 0 ? QString {"DE: %1%2"}.arg (de_call) + .arg (de_grid.size () ? '(' + de_grid + ')' : QString {}) : QString {}); mode_label_->setText (QString {"Mode: %1%2%3%4"} .arg (mode) .arg (sub_mode) diff --git a/UDPExamples/ClientWidget.hpp b/UDPExamples/ClientWidget.hpp index 1f5ae6012..0cec23e7a 100644 --- a/UDPExamples/ClientWidget.hpp +++ b/UDPExamples/ClientWidget.hpp @@ -43,6 +43,7 @@ public: Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier); Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only); Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool); + Q_SIGNAL void location (QString const &id, QString const &text); private: QString id_; @@ -69,9 +70,11 @@ private: QTableView * decodes_table_view_; QTableView * beacons_table_view_; QLineEdit * message_line_edit_; + QLineEdit * grid_line_edit_; QStackedLayout * decodes_stack_; QAbstractButton * auto_off_button_; QAbstractButton * halt_tx_button_; + QLabel * de_label_; QLabel * mode_label_; bool fast_mode_; QLabel * frequency_label_; diff --git a/UDPExamples/MessageAggregatorMainWindow.cpp b/UDPExamples/MessageAggregatorMainWindow.cpp index 196282d61..324c7d0e5 100644 --- a/UDPExamples/MessageAggregatorMainWindow.cpp +++ b/UDPExamples/MessageAggregatorMainWindow.cpp @@ -22,12 +22,15 @@ namespace QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Sent"), QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Rec'd"), QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Power"), + QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Operator"), + QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Call"), + QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Grid"), QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Comments"), }; } MessageAggregatorMainWindow::MessageAggregatorMainWindow () - : log_ {new QStandardItemModel {0, 11, this}} + : log_ {new QStandardItemModel {0, 14, this}} , decodes_model_ {new DecodesModel {this}} , beacons_model_ {new BeaconsModel {this}} , server_ {new MessageServer {this}} @@ -111,10 +114,12 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow () show (); } -void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid - , Frequency dial_frequency, QString const& mode, QString const& report_sent - , QString const& report_received, QString const& tx_power, QString const& comments - , QString const& name, QDateTime time_on) +void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call + , QString const& dx_grid, Frequency dial_frequency, QString const& mode + , QString const& report_sent, QString const& report_received + , QString const& tx_power, QString const& comments + , QString const& name, QDateTime time_on, QString const& operator_call + , QString const& my_call, QString const& my_grid) { QList row; row << new QStandardItem {time_on.toString ("dd-MMM-yyyy hh:mm:ss")} @@ -127,6 +132,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time << new QStandardItem {report_sent} << new QStandardItem {report_received} << new QStandardItem {tx_power} + << new QStandardItem {operator_call} + << new QStandardItem {my_call} + << new QStandardItem {my_grid} << new QStandardItem {comments}; log_->appendRow (row); log_table_view_->resizeColumnsToContents (); @@ -149,6 +157,7 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const& connect (dock, &ClientWidget::do_reply, decodes_model_, &DecodesModel::do_reply); connect (dock, &ClientWidget::do_halt_tx, server_, &MessageServer::halt_tx); connect (dock, &ClientWidget::do_free_text, server_, &MessageServer::free_text); + connect (dock, &ClientWidget::location, server_, &MessageServer::location); connect (view_action, &QAction::toggled, dock, &ClientWidget::setVisible); dock_widgets_[id] = dock; server_->replay (id); diff --git a/UDPExamples/MessageAggregatorMainWindow.hpp b/UDPExamples/MessageAggregatorMainWindow.hpp index 4039c60ab..4ee5bface 100644 --- a/UDPExamples/MessageAggregatorMainWindow.hpp +++ b/UDPExamples/MessageAggregatorMainWindow.hpp @@ -29,7 +29,8 @@ public: Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid , Frequency dial_frequency, QString const& mode, QString const& report_sent , QString const& report_received, QString const& tx_power, QString const& comments - , QString const& name, QDateTime time_on); + , QString const& name, QDateTime time_on, QString const& operator_call + , QString const& my_call, QString const& my_grid); private: void add_client (QString const& id, QString const& version, QString const& revision); diff --git a/UDPExamples/UDPDaemon.cpp b/UDPExamples/UDPDaemon.cpp index 731f31661..b75a8adec 100644 --- a/UDPExamples/UDPDaemon.cpp +++ b/UDPExamples/UDPDaemon.cpp @@ -96,23 +96,37 @@ public: } Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid - , Frequency dial_frequency, QString const& mode, QString const& report_sent - , QString const& report_received, QString const& tx_power - , QString const& comments, QString const& name, QDateTime time_on, QString const& operator_call) + , Frequency dial_frequency, QString const& mode, QString const& report_sent + , QString const& report_received, QString const& tx_power + , QString const& comments, QString const& name, QDateTime time_on + , QString const& operator_call, QString const& my_call, QString const& my_grid) { if (client_id == id_) { qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid << "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent << "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments - << "name:" << name << "operator_call:" << operator_call; - std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 op: %8").arg (id_) - .arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received).arg (dial_frequency).arg (operator_call).toStdString () - << tr (" @ %1").arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).toStdString() + << "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call + << "my_grid:" << my_grid; + std::cout << QByteArray {80, '-'}.data () << '\n'; + std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11") + .arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received) + .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) + .arg (my_call).arg (my_grid).toStdString () << std::endl; } } + Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF) + { + if (client_id == id_) + { + qDebug () << "ADIF:" << ADIF; + std::cout << QByteArray {80, '-'}.data () << '\n'; + std::cout << ADIF.data () << std::endl; + } + } + private: QString id_; Frequency dial_frequency_; @@ -146,6 +160,7 @@ private: connect (server_, &MessageServer::decode, client, &Client::decode_added); connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added); connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged); + connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF); clients_[id] = client; server_->replay (id); std::cout << "Discovered WSJT-X instance: " << id.toStdString (); diff --git a/logbook/adif.cpp b/logbook/adif.cpp index 31cd39a9d..568a2eb1c 100644 --- a/logbook/adif.cpp +++ b/logbook/adif.cpp @@ -174,8 +174,11 @@ int ADIF::getCount() const return _data.size(); } -QString ADIF::QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, QString const& band, - QString const& comments, QString const& name, QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid, QString const& m_txPower, QString const& operator_call) +QByteArray ADIF::QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode + , QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn + , QDateTime const& dateTimeOff, QString const& band, QString const& comments + , QString const& name, QString const& strDialFreq, QString const& m_myCall + , QString const& m_myGrid, QString const& m_txPower, QString const& operator_call) { QString t; t = "" + hisCall; @@ -205,14 +208,12 @@ QString ADIF::QSOToADIF(QString const& hisCall, QString const& hisGrid, QString if (operator_call!="") t+=" " + operator_call; - t += " "; - return t; + return t.toLatin1 (); } // open ADIF file and append the QSO details. Return true on success -bool ADIF::addQSOToFile(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, QString const& band, - QString const& comments, QString const& name, QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid, QString const& m_txPower, QString const& operator_call) +bool ADIF::addQSOToFile(QByteArray const& ADIF_record) { QFile f2(_filename); if (!f2.open(QIODevice::Text | QIODevice::Append)) @@ -223,10 +224,7 @@ bool ADIF::addQSOToFile(QString const& hisCall, QString const& hisGrid, QString if (f2.size()==0) out << "WSJT-X ADIF Export" << endl; // new file - QString t; - t = QSOToADIF(hisCall,hisGrid,mode,rptSent,rptRcvd,dateTimeOn,dateTimeOff, - band,comments,name,strDialFreq,m_myCall,m_myGrid,m_txPower,operator_call); - out << t << endl; + out << ADIF_record << " " << endl; f2.close(); } return true; diff --git a/logbook/adif.h b/logbook/adif.h index 2c82c2144..727ca9952 100644 --- a/logbook/adif.h +++ b/logbook/adif.h @@ -29,11 +29,13 @@ class ADIF int getCount() const; // open ADIF file and append the QSO details. Return true on success - bool addQSOToFile(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, QString const& band, - QString const& comments, QString const& name, QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid, QString const& m_txPower, QString const& operator_call); + bool addQSOToFile(QByteArray const& ADIF_record); - QString QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent, QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff, QString const& band, - QString const& comments, QString const& name, QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid, QString const& m_txPower, QString const& operator_call); + QByteArray QSOToADIF(QString const& hisCall, QString const& hisGrid, QString const& mode, QString const& rptSent + , QString const& rptRcvd, QDateTime const& dateTimeOn, QDateTime const& dateTimeOff + , QString const& band, QString const& comments, QString const& name + , QString const& strDialFreq, QString const& m_myCall, QString const& m_myGrid + , QString const& m_txPower, QString const& operator_call); private: diff --git a/logqso.cpp b/logqso.cpp index c0c7572d6..3fa454c6a 100644 --- a/logqso.cpp +++ b/logqso.cpp @@ -117,7 +117,9 @@ void LogQSO::accept() auto adifilePath = QDir {QStandardPaths::writableLocation (QStandardPaths::DataLocation)}.absoluteFilePath ("wsjtx_log.adi"); adifile.init(adifilePath); - if (!adifile.addQSOToFile(hisCall,hisGrid,mode,rptSent,rptRcvd,m_dateTimeOn,m_dateTimeOff,band,comments,name,strDialFreq,m_myCall,m_myGrid,m_txPower, operator_call)) + QByteArray ADIF {adifile.QSOToADIF (hisCall, hisGrid, mode, rptSent, rptRcvd, m_dateTimeOn, m_dateTimeOff, band + , comments, name, strDialFreq, m_myCall, m_myGrid, m_txPower, operator_call)}; + if (!adifile.addQSOToFile (ADIF)) { MessageBox::warning_message (this, tr ("Log file error"), tr ("Cannot open \"%1\"").arg (adifilePath)); @@ -125,12 +127,9 @@ void LogQSO::accept() // Log to N1MM Logger if (m_config->broadcast_to_n1mm() && m_config->valid_n1mm_info()) { - QString adif = adifile.QSOToADIF(hisCall,hisGrid,mode,rptSent,rptRcvd,m_dateTimeOn,m_dateTimeOff,band,comments,name,strDialFreq,m_myCall,m_myGrid,m_txPower, operator_call); const QHostAddress n1mmhost = QHostAddress(m_config->n1mm_server_name()); - QByteArray qba_adif = adif.toLatin1(); QUdpSocket _sock; - - auto rzult = _sock.writeDatagram ( qba_adif, n1mmhost, quint16(m_config->n1mm_server_port())); + auto rzult = _sock.writeDatagram (ADIF + " ", n1mmhost, quint16(m_config->n1mm_server_port())); if (rzult == -1) { MessageBox::warning_message (this, tr ("Error sending log to N1MM"), tr ("Write returned \"%1\"").arg (rzult)); @@ -157,7 +156,7 @@ void LogQSO::accept() } //Clean up and finish logging - Q_EMIT acceptQSO (m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, rptSent, rptRcvd, m_txPower, comments, name,m_dateTimeOn, operator_call); + Q_EMIT acceptQSO (m_dateTimeOff, hisCall, hisGrid, m_dialFreq, mode, rptSent, rptRcvd, m_txPower, comments, name,m_dateTimeOn, operator_call, m_myCall, m_myGrid, ADIF); QDialog::accept(); } diff --git a/logqso.h b/logqso.h index 4f22649b8..fb92201e1 100644 --- a/logqso.h +++ b/logqso.h @@ -8,7 +8,9 @@ #include #endif +#include #include +#include #include "Radio.hpp" @@ -18,6 +20,7 @@ namespace Ui { class QSettings; class Configuration; +class QByteArray; class LogQSO : public QDialog { @@ -40,7 +43,8 @@ signals: , Radio::Frequency dial_freq, QString const& mode , QString const& rpt_sent, QString const& rpt_received , QString const& tx_power, QString const& comments - , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call); + , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call + , QString const& my_call, QString const& my_grid, QByteArray const& ADIF); protected: void hideEvent (QHideEvent *); diff --git a/mainwindow.cpp b/mainwindow.cpp index f2182b3af..556bc8be5 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -446,12 +446,13 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple, connect (this, &MainWindow::finished, m_fastGraph.data (), &FastGraph::close); // setup the log QSO dialog - connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO2); + connect (m_logDlg.data (), &LogQSO::acceptQSO, this, &MainWindow::acceptQSO); connect (this, &MainWindow::finished, m_logDlg.data (), &LogQSO::close); // Network message handlers connect (m_messageClient, &MessageClient::reply, this, &MainWindow::replyToCQ); connect (m_messageClient, &MessageClient::replay, this, &MainWindow::replayDecodes); + connect (m_messageClient, &MessageClient::location, this, &MainWindow::locationChange); connect (m_messageClient, &MessageClient::halt_tx, [this] (bool auto_only) { if (m_config.accept_udp_requests ()) { if (auto_only) { @@ -1581,13 +1582,16 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog { // things that might change that we need know about auto callsign = m_config.my_callsign (); + auto my_grid = m_config.my_grid (); if (QDialog::Accepted == m_config.exec ()) { if (m_config.my_callsign () != callsign) { m_baseCall = Radio::base_callsign (m_config.my_callsign ()); morse_(const_cast (m_config.my_callsign ().toLatin1().constData()), const_cast (icw), &m_ncw, m_config.my_callsign ().length()); } - + if (m_config.my_callsign () != callsign || m_config.my_grid () != my_grid) { + statusUpdate (); + } on_dxGridEntry_textChanged (m_hisGrid); // recalculate distances in case of units change enable_DXCC_entity (m_config.DXCC ()); // sets text window proportions and (re)inits the logbook @@ -4811,16 +4815,18 @@ void MainWindow::on_logQSOButton_clicked() //Log QSO button m_config.bFox(), m_opCall); } -void MainWindow::acceptQSO2(QDateTime const& QSO_date_off, QString const& call, QString const& grid +void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call, QString const& grid , Frequency dial_freq, QString const& mode , QString const& rpt_sent, QString const& rpt_received , QString const& tx_power, QString const& comments - , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call) + , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call + , QString const& my_call, QString const& my_grid, QByteArray const& ADIF) { QString date = QSO_date_on.toString("yyyyMMdd"); m_logBook.addAsWorked (m_hisCall, m_config.bands ()->find (m_freqNominal), m_modeTx, date); - m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received, tx_power, comments, name, QSO_date_on, operator_call); + m_messageClient->qso_logged (QSO_date_off, call, grid, dial_freq, mode, rpt_sent, rpt_received, tx_power, comments, name, QSO_date_on, operator_call, my_call, my_grid); + m_messageClient->logged_ADIF (ADIF); if (m_config.clear_DX ()) { @@ -6522,6 +6528,33 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de } } +void MainWindow::locationChange (QString const& location) +{ + QString grid {location.trimmed ()}; + int len; + + // string 6 chars or fewer, interpret as a grid, or use with a 'GRID:' prefix + if (grid.size () > 6) { + if (grid.toUpper ().startsWith ("GRID:")) { + grid = grid.mid (5).trimmed (); + } + else { + // TODO - support any other formats, e.g. latlong? Or have that conversion done external to wsjtx + return; + } + } + if (MaidenheadLocatorValidator::Acceptable == MaidenheadLocatorValidator ().validate (grid, len)) { + qDebug() << "locationChange: Grid supplied is " << grid; + if (m_config.my_grid () != grid) { + m_config.set_location (grid); + genStdMsgs (m_rpt, false); + statusUpdate (); + } + } else { + qDebug() << "locationChange: Invalid grid " << grid; + } +} + void MainWindow::replayDecodes () { // we accept this request even if the setting to accept UDP requests diff --git a/mainwindow.h b/mainwindow.h index 5f48e9891..5e912a097 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -218,11 +218,12 @@ private slots: void on_tuneButton_clicked (bool); void on_pbR2T_clicked(); void on_pbT2R_clicked(); - void acceptQSO2(QDateTime const&, QString const& call, QString const& grid + void acceptQSO (QDateTime const&, QString const& call, QString const& grid , Frequency dial_freq, QString const& mode , QString const& rpt_sent, QString const& rpt_received , QString const& tx_power, QString const& comments - , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call); + , QString const& name, QDateTime const& QSO_date_on, QString const& operator_call + , QString const& my_call, QString const& my_grid, QByteArray const& ADIF); void on_bandComboBox_currentIndexChanged (int index); void on_bandComboBox_activated (int index); void on_readFreq_clicked(); @@ -645,6 +646,7 @@ private: void transmitDisplay (bool); void processMessage(DecodedText const&, Qt::KeyboardModifiers = 0); void replyToCQ (QTime, qint32 snr, float delta_time, quint32 delta_frequency, QString const& mode, QString const& message_text, bool low_confidence, quint8 modifiers); + void locationChange(QString const& location); void replayDecodes (); void postDecode (bool is_new, QString const& message); void postWSPRDecode (bool is_new, QStringList message_parts);