Merge branch 'develop' into feat-boost-log

This commit is contained in:
Bill Somerville 2020-11-12 13:36:25 +00:00
commit 9ea903b259
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
50 changed files with 8148 additions and 6575 deletions

View File

@ -235,6 +235,7 @@ set (wsjt_qt_CXXSRCS
logbook/Multiplier.cpp logbook/Multiplier.cpp
Network/NetworkAccessManager.cpp Network/NetworkAccessManager.cpp
widgets/LazyFillComboBox.cpp widgets/LazyFillComboBox.cpp
widgets/CheckableItemComboBox.cpp
) )
set (wsjt_qtmm_CXXSRCS set (wsjt_qtmm_CXXSRCS
@ -1158,6 +1159,11 @@ target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
add_executable (fst4sim lib/fst4/fst4sim.f90) add_executable (fst4sim lib/fst4/fst4sim.f90)
target_link_libraries (fst4sim wsjt_fort wsjt_cxx) target_link_libraries (fst4sim wsjt_fort wsjt_cxx)
if (WIN32)
set_target_properties (fst4sim PROPERTIES
LINK_FLAGS -Wl,--stack,0x4000000,--heap,0x6000000
)
endif ()
add_executable (ldpcsim240_101 lib/fst4/ldpcsim240_101.f90) add_executable (ldpcsim240_101 lib/fst4/ldpcsim240_101.f90)
target_link_libraries (ldpcsim240_101 wsjt_fort wsjt_cxx) target_link_libraries (ldpcsim240_101 wsjt_fort wsjt_cxx)
@ -1323,7 +1329,7 @@ generate_version_info (jt9_VERSION_RESOURCES
NAME jt9 NAME jt9
BUNDLE ${PROJECT_BUNDLE_NAME} BUNDLE ${PROJECT_BUNDLE_NAME}
ICON ${WSJTX_ICON_FILE} ICON ${WSJTX_ICON_FILE}
FILE_DESCRIPTION "Slow mode decoder" FILE_DESCRIPTION "jt9 - WSJT-X slow mode decoder"
) )
add_executable (record_time_signal Audio/tools/record_time_signal.cpp) add_executable (record_time_signal Audio/tools/record_time_signal.cpp)
@ -1415,7 +1421,7 @@ else ()
) )
if (WIN32) if (WIN32)
set_target_properties (wsjtx PROPERTIES set_target_properties (wsjtx PROPERTIES
LINK_FLAGS -Wl,--stack,16777216 LINK_FLAGS -Wl,--stack,0x1000000,--heap,0x20000000
) )
endif () endif ()
endif () endif ()
@ -1468,7 +1474,7 @@ add_executable (message_aggregator
${message_aggregator_RESOURCES_RCC} ${message_aggregator_RESOURCES_RCC}
${message_aggregator_VERSION_RESOURCES} ${message_aggregator_VERSION_RESOURCES}
) )
target_link_libraries (message_aggregator Qt5::Widgets wsjtx_udp-static) target_link_libraries (message_aggregator wsjt_qt Qt5::Widgets wsjtx_udp-static)
if (WSJT_CREATE_WINMAIN) if (WSJT_CREATE_WINMAIN)
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON) set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)

View File

@ -163,6 +163,10 @@
#include <QFontDialog> #include <QFontDialog>
#include <QSerialPortInfo> #include <QSerialPortInfo>
#include <QScopedPointer> #include <QScopedPointer>
#include <QNetworkInterface>
#include <QHostInfo>
#include <QHostAddress>
#include <QStandardItem>
#include <QDebug> #include <QDebug>
#include "pimpl_impl.hpp" #include "pimpl_impl.hpp"
@ -440,6 +444,12 @@ private:
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *); void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
void update_audio_channels (QComboBox const *, int, QComboBox *, bool); void update_audio_channels (QComboBox const *, int, QComboBox *, bool);
void load_network_interfaces (CheckableItemComboBox *, QStringList current);
Q_SLOT void validate_network_interfaces (QString const&);
QStringList get_selected_network_interfaces (CheckableItemComboBox *);
Q_SLOT void host_info_results (QHostInfo);
void check_multicast (QHostAddress const&);
void find_tab (QWidget *); void find_tab (QWidget *);
void initialize_models (); void initialize_models ();
@ -493,6 +503,8 @@ private:
Q_SLOT void on_add_macro_line_edit_editingFinished (); Q_SLOT void on_add_macro_line_edit_editingFinished ();
Q_SLOT void delete_macro (); Q_SLOT void delete_macro ();
void delete_selected_macros (QModelIndexList); void delete_selected_macros (QModelIndexList);
Q_SLOT void on_udp_server_line_edit_textChanged (QString const&);
Q_SLOT void on_udp_server_line_edit_editingFinished ();
Q_SLOT void on_save_path_select_push_button_clicked (bool); Q_SLOT void on_save_path_select_push_button_clicked (bool);
Q_SLOT void on_azel_path_select_push_button_clicked (bool); Q_SLOT void on_azel_path_select_push_button_clicked (bool);
Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double); Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double);
@ -642,7 +654,12 @@ private:
bool use_dynamic_grid_; bool use_dynamic_grid_;
QString opCall_; QString opCall_;
QString udp_server_name_; QString udp_server_name_;
bool udp_server_name_edited_;
int dns_lookup_id_;
port_type udp_server_port_; port_type udp_server_port_;
QStringList udp_interface_names_;
QString loopback_interface_name_;
int udp_TTL_;
QString n1mm_server_name_; QString n1mm_server_name_;
port_type n1mm_server_port_; port_type n1mm_server_port_;
bool broadcast_to_n1mm_; bool broadcast_to_n1mm_;
@ -742,6 +759,8 @@ QString Configuration::opCall() const {return m_->opCall_;}
void Configuration::opCall (QString const& call) {m_->opCall_ = call;} void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
QString Configuration::udp_server_name () const {return m_->udp_server_name_;} QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;} auto Configuration::udp_server_port () const -> port_type {return m_->udp_server_port_;}
QStringList Configuration::udp_interface_names () const {return m_->udp_interface_names_;}
int Configuration::udp_TTL () const {return m_->udp_TTL_;}
bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;} bool Configuration::accept_udp_requests () const {return m_->accept_udp_requests_;}
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;} QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;} auto Configuration::n1mm_server_port () const -> port_type {return m_->n1mm_server_port_;}
@ -976,6 +995,8 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, transceiver_command_number_ {0} , transceiver_command_number_ {0}
, degrade_ {0.} // initialize to zero each run, not , degrade_ {0.} // initialize to zero each run, not
// saved in settings // saved in settings
, udp_server_name_edited_ {false}
, dns_lookup_id_ {-1}
{ {
ui_->setupUi (this); ui_->setupUi (this);
@ -1025,6 +1046,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
// this must be done after the default paths above are set // this must be done after the default paths above are set
read_settings (); read_settings ();
// set up dynamic loading of audio devices
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () { connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor}); QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_); load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_);
@ -1040,14 +1062,22 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
QGuiApplication::restoreOverrideCursor (); QGuiApplication::restoreOverrideCursor ();
}); });
// set up dynamic loading of network interfaces
connect (ui_->udp_interfaces_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
QGuiApplication::restoreOverrideCursor ();
});
connect (ui_->udp_interfaces_combo_box, &QComboBox::currentTextChanged, this, &Configuration::impl::validate_network_interfaces);
// set up LoTW users CSV file fetching // set up LoTW users CSV file fetching
connect (&lotw_users_, &LotWUsers::load_finished, [this] () { connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
ui_->LotW_CSV_fetch_push_button->setEnabled (true); ui_->LotW_CSV_fetch_push_button->setEnabled (true);
}); });
lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv")); lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv"));
// load the dictionary if it exists // load the dictionary if it exists, fetch and load if it doesn't
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), false); lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text ());
// //
// validation // validation
@ -1316,7 +1346,14 @@ void Configuration::impl::initialize_models ()
ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval); ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval);
ui_->opCallEntry->setText (opCall_); ui_->opCallEntry->setText (opCall_);
ui_->udp_server_line_edit->setText (udp_server_name_); ui_->udp_server_line_edit->setText (udp_server_name_);
on_udp_server_line_edit_editingFinished ();
ui_->udp_server_port_spin_box->setValue (udp_server_port_); ui_->udp_server_port_spin_box->setValue (udp_server_port_);
load_network_interfaces (ui_->udp_interfaces_combo_box, udp_interface_names_);
if (!udp_interface_names_.size ())
{
udp_interface_names_ = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
}
ui_->udp_TTL_spin_box->setValue (udp_TTL_);
ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_); ui_->accept_udp_requests_check_box->setChecked (accept_udp_requests_);
ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_); ui_->n1mm_server_name_line_edit->setText (n1mm_server_name_);
ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_); ui_->n1mm_server_port_spin_box->setValue (n1mm_server_port_);
@ -1494,6 +1531,8 @@ void Configuration::impl::read_settings ()
rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value<TransceiverFactory::SplitMode> (); rig_params_.split_mode = settings_->value ("SplitMode", QVariant::fromValue (TransceiverFactory::split_mode_none)).value<TransceiverFactory::SplitMode> ();
opCall_ = settings_->value ("OpCall", "").toString (); opCall_ = settings_->value ("OpCall", "").toString ();
udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString (); udp_server_name_ = settings_->value ("UDPServer", "127.0.0.1").toString ();
udp_interface_names_ = settings_->value ("UDPInterface").toStringList ();
udp_TTL_ = settings_->value ("UDPTTL", 1).toInt ();
udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt (); udp_server_port_ = settings_->value ("UDPServerPort", 2237).toUInt ();
n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString (); n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString ();
n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt (); n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt ();
@ -1622,6 +1661,8 @@ void Configuration::impl::write_settings ()
settings_->setValue ("OpCall", opCall_); settings_->setValue ("OpCall", opCall_);
settings_->setValue ("UDPServer", udp_server_name_); settings_->setValue ("UDPServer", udp_server_name_);
settings_->setValue ("UDPServerPort", udp_server_port_); settings_->setValue ("UDPServerPort", udp_server_port_);
settings_->setValue ("UDPInterface", QVariant::fromValue (udp_interface_names_));
settings_->setValue ("UDPTTL", udp_TTL_);
settings_->setValue ("N1MMServer", n1mm_server_name_); settings_->setValue ("N1MMServer", n1mm_server_name_);
settings_->setValue ("N1MMServerPort", n1mm_server_port_); settings_->setValue ("N1MMServerPort", n1mm_server_port_);
settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_); settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_);
@ -1824,6 +1865,12 @@ bool Configuration::impl::validate ()
return false; return false;
} }
if (dns_lookup_id_ > -1)
{
MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later"));
return false;
}
return true; return true;
} }
@ -2042,20 +2089,30 @@ void Configuration::impl::accept ()
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked (); pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked ();
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked (); pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked ();
opCall_=ui_->opCallEntry->text(); opCall_=ui_->opCallEntry->text();
auto new_server = ui_->udp_server_line_edit->text ();
if (new_server != udp_server_name_) auto new_server = ui_->udp_server_line_edit->text ().trimmed ();
auto new_interfaces = get_selected_network_interfaces (ui_->udp_interfaces_combo_box);
if (new_server != udp_server_name_ || new_interfaces != udp_interface_names_)
{ {
udp_server_name_ = new_server; udp_server_name_ = new_server;
Q_EMIT self_->udp_server_changed (new_server); udp_interface_names_ = new_interfaces;
Q_EMIT self_->udp_server_changed (udp_server_name_, udp_interface_names_);
} }
auto new_port = ui_->udp_server_port_spin_box->value (); auto new_port = ui_->udp_server_port_spin_box->value ();
if (new_port != udp_server_port_) if (new_port != udp_server_port_)
{ {
udp_server_port_ = new_port; udp_server_port_ = new_port;
Q_EMIT self_->udp_server_port_changed (new_port); Q_EMIT self_->udp_server_port_changed (udp_server_port_);
} }
auto new_TTL = ui_->udp_TTL_spin_box->value ();
if (new_TTL != udp_TTL_)
{
udp_TTL_ = new_TTL;
Q_EMIT self_->udp_TTL_changed (udp_TTL_);
}
if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_) if (ui_->accept_udp_requests_check_box->isChecked () != accept_udp_requests_)
{ {
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked (); accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
@ -2111,6 +2168,12 @@ void Configuration::impl::accept ()
void Configuration::impl::reject () void Configuration::impl::reject ()
{ {
if (dns_lookup_id_ > -1)
{
QHostInfo::abortHostLookup (dns_lookup_id_);
dns_lookup_id_ = -1;
}
initialize_models (); // reverts to settings as at exec () initialize_models (); // reverts to settings as at exec ()
// check if the Transceiver instance changed, in which case we need // check if the Transceiver instance changed, in which case we need
@ -2325,6 +2388,72 @@ void Configuration::impl::on_add_macro_push_button_clicked (bool /* checked */)
} }
} }
void Configuration::impl::on_udp_server_line_edit_textChanged (QString const&)
{
udp_server_name_edited_ = true;
}
void Configuration::impl::on_udp_server_line_edit_editingFinished ()
{
if (udp_server_name_edited_)
{
auto const& server = ui_->udp_server_line_edit->text ().trimmed ();
QHostAddress ha {server};
if (server.size () && ha.isNull ())
{
// queue a host address lookup
// qDebug () << "server host DNS lookup:" << server;
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
dns_lookup_id_ = QHostInfo::lookupHost (server, this, &Configuration::impl::host_info_results);
#else
dns_lookup_id_ = QHostInfo::lookupHost (server, this, SLOT (host_info_results (QHostInfo)));
#endif
}
else
{
check_multicast (ha);
}
}
}
void Configuration::impl::host_info_results (QHostInfo host_info)
{
if (host_info.lookupId () != dns_lookup_id_) return;
dns_lookup_id_ = -1;
if (QHostInfo::NoError != host_info.error ())
{
MessageBox::critical_message (this, tr ("UDP server DNS lookup failed"), host_info.errorString ());
}
else
{
auto const& server_addresses = host_info.addresses ();
// qDebug () << "message server addresses:" << server_addresses;
if (server_addresses.size ())
{
check_multicast (server_addresses[0]);
}
}
}
void Configuration::impl::check_multicast (QHostAddress const& ha)
{
auto is_multicast = is_multicast_address (ha);
ui_->udp_interfaces_label->setVisible (is_multicast);
ui_->udp_interfaces_combo_box->setVisible (is_multicast);
ui_->udp_TTL_label->setVisible (is_multicast);
ui_->udp_TTL_spin_box->setVisible (is_multicast);
if (isVisible ())
{
if (is_MAC_ambiguous_multicast_address (ha))
{
MessageBox::warning_message (this, tr ("MAC-ambiguous multicast groups addresses not supported"));
find_tab (ui_->udp_server_line_edit);
ui_->udp_server_line_edit->clear ();
}
}
udp_server_name_edited_ = false;
}
void Configuration::impl::delete_frequencies () void Configuration::impl::delete_frequencies ()
{ {
auto selection_model = ui_->frequencies_table_view->selectionModel (); auto selection_model = ui_->frequencies_table_view->selectionModel ();
@ -2844,6 +2973,85 @@ void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * com
combo_box->setCurrentIndex (current_index); combo_box->setCurrentIndex (current_index);
} }
// load the available network interfaces into the selection combo box
void Configuration::impl::load_network_interfaces (CheckableItemComboBox * combo_box, QStringList current)
{
combo_box->clear ();
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsUp | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
loopback_interface_name_ = net_if.name ();
}
auto item = combo_box->addCheckItem (net_if.humanReadableName ()
, net_if.name ()
, current.contains (net_if.name ()) ? Qt::Checked : Qt::Unchecked);
auto tip = QString {"name(index): %1(%2) - %3"}.arg (net_if.name ()).arg (net_if.index ())
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
auto hw_addr = net_if.hardwareAddress ();
if (hw_addr.size ())
{
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
}
auto aes = net_if.addressEntries ();
if (aes.size ())
{
tip += "\naddresses:";
for (auto const& ae : aes)
{
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
}
}
item->setToolTip (tip);
}
}
}
// get the select network interfaces from the selection combo box
void Configuration::impl::validate_network_interfaces (QString const& /*text*/)
{
auto model = static_cast<QStandardItemModel *> (ui_->udp_interfaces_combo_box->model ());
bool has_checked {false};
int loopback_row {-1};
for (int row = 0; row < model->rowCount (); ++row)
{
if (model->item (row)->data ().toString () == loopback_interface_name_)
{
loopback_row = row;
}
else if (Qt::Checked == model->item (row)->checkState ())
{
has_checked = true;
}
}
if (loopback_row >= 0)
{
if (!has_checked)
{
model->item (loopback_row)->setCheckState (Qt::Checked);
}
model->item (loopback_row)->setEnabled (has_checked);
}
}
// get the select network interfaces from the selection combo box
QStringList Configuration::impl::get_selected_network_interfaces (CheckableItemComboBox * combo_box)
{
QStringList interfaces;
auto model = static_cast<QStandardItemModel *> (combo_box->model ());
for (int row = 0; row < model->rowCount (); ++row)
{
if (Qt::Checked == model->item (row)->checkState ())
{
interfaces << model->item (row)->data ().toString ();
}
}
return interfaces;
}
// enable only the channels that are supported by the selected audio device // enable only the channels that are supported by the selected audio device
void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both) void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both)
{ {

View File

@ -3,6 +3,7 @@
#include <QObject> #include <QObject>
#include <QFont> #include <QFont>
#include <QString>
#include "Radio.hpp" #include "Radio.hpp"
#include "models/IARURegions.hpp" #include "models/IARURegions.hpp"
@ -14,14 +15,12 @@
class QSettings; class QSettings;
class QWidget; class QWidget;
class QAudioDeviceInfo; class QAudioDeviceInfo;
class QString;
class QDir; class QDir;
class QNetworkAccessManager; class QNetworkAccessManager;
class Bands; class Bands;
class FrequencyList_v2; class FrequencyList_v2;
class StationList; class StationList;
class QStringListModel; class QStringListModel;
class QHostAddress;
class LotWUsers; class LotWUsers;
class DecodeHighlightingModel; class DecodeHighlightingModel;
class LogBook; class LogBook;
@ -152,6 +151,8 @@ public:
void opCall (QString const&); void opCall (QString const&);
QString udp_server_name () const; QString udp_server_name () const;
port_type udp_server_port () const; port_type udp_server_port () const;
QStringList udp_interface_names () const;
int udp_TTL () const;
QString n1mm_server_name () const; QString n1mm_server_name () const;
port_type n1mm_server_port () const; port_type n1mm_server_port () const;
bool valid_n1mm_info () const; bool valid_n1mm_info () const;
@ -273,8 +274,9 @@ public:
// //
// This signal is emitted when the UDP server changes // This signal is emitted when the UDP server changes
// //
Q_SIGNAL void udp_server_changed (QString const& udp_server) const; Q_SIGNAL void udp_server_changed (QString& udp_server_name, QStringList const& network_interfaces) const;
Q_SIGNAL void udp_server_port_changed (port_type server_port) const; Q_SIGNAL void udp_server_port_changed (port_type server_port) const;
Q_SIGNAL void udp_TTL_changed (int TTL) const;
Q_SIGNAL void accept_udp_requests_changed (bool checked) const; Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
// signal updates to decode highlighting // signal updates to decode highlighting

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>554</width> <width>554</width>
<height>556</height> <height>560</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent.</string>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="udp_server_line_edit"> <widget class="QLineEdit" name="udp_server_line_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Optional hostname of network service to receive decodes.&lt;/p&gt;&lt;p&gt;Formats:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 multicast group address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 multicast group address&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Clearing this field will disable the broadcasting of UDP status updates.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Optional hostname of network service to receive decodes.&lt;/p&gt;&lt;p&gt;Formats:&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;hostname&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv4 multicast group address&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;IPv6 multicast group address&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Clearing this field will disable the broadcasting of UDP status updates.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
@ -1891,13 +1885,53 @@ and DX Grid fields when a 73 or free text message is sent.</string>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QSpinBox" name="udp_server_port_spin_box"> <widget class="QSpinBox" name="udp_server_port_spin_box">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be broadcast.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enter the service port number of the UDP server that WSJT-X should send updates to. If this is zero no updates will be sent.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="maximum"> <property name="maximum">
<number>65534</number> <number>65534</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QLabel" name="udp_interfaces_label">
<property name="text">
<string>Outgoing interfaces:</string>
</property>
<property name="buddy">
<cstring>udp_interfaces_combo_box</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="CheckableItemComboBox" name="udp_interfaces_combo_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When sending updates to a multicast group address it is necessary to specify which network interface(s) to send them to. If the loop-back interface is multicast capable then at least that one will be selected.&lt;/p&gt;&lt;p&gt;For most users the loop-back interface is all that is needed, that will allow multiple other applications on the same machine to interoperate with WSJT-X. If applications running on other hosts are to receive status updates then a suitable network interface should be used.&lt;/p&gt;&lt;p&gt;On some Linux systems it may be necessary to enable multicast on the loop-back network interface.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="udp_TTL_spin_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sets the number or router hops that multicast datagrams are allowed to make. Almost everyone should set this to 1 to keep outgoing multicast traffic withn the local subnet.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="maximum">
<number>255</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="udp_TTL_label">
<property name="text">
<string>Multicast TTL:</string>
</property>
<property name="buddy">
<cstring>udp_TTL_spin_box</cstring>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
@ -3002,6 +3036,11 @@ Right click for insert and delete options.</string>
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>widgets/LazyFillComboBox.hpp</header> <header>widgets/LazyFillComboBox.hpp</header>
</customwidget> </customwidget>
<customwidget>
<class>CheckableItemComboBox</class>
<extends>QComboBox</extends>
<header>widgets/CheckableItemComboBox.hpp</header>
</customwidget>
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>configuration_tabs</tabstop> <tabstop>configuration_tabs</tabstop>
@ -3084,6 +3123,8 @@ Right click for insert and delete options.</string>
<tabstop>psk_reporter_tcpip_check_box</tabstop> <tabstop>psk_reporter_tcpip_check_box</tabstop>
<tabstop>udp_server_line_edit</tabstop> <tabstop>udp_server_line_edit</tabstop>
<tabstop>udp_server_port_spin_box</tabstop> <tabstop>udp_server_port_spin_box</tabstop>
<tabstop>udp_interfaces_combo_box</tabstop>
<tabstop>udp_TTL_spin_box</tabstop>
<tabstop>accept_udp_requests_check_box</tabstop> <tabstop>accept_udp_requests_check_box</tabstop>
<tabstop>udpWindowToFront</tabstop> <tabstop>udpWindowToFront</tabstop>
<tabstop>udpWindowRestore</tabstop> <tabstop>udpWindowRestore</tabstop>
@ -3101,8 +3142,8 @@ Right click for insert and delete options.</string>
<tabstop>include_WAE_check_box</tabstop> <tabstop>include_WAE_check_box</tabstop>
<tabstop>rescan_log_push_button</tabstop> <tabstop>rescan_log_push_button</tabstop>
<tabstop>LotW_CSV_URL_line_edit</tabstop> <tabstop>LotW_CSV_URL_line_edit</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
<tabstop>LotW_CSV_fetch_push_button</tabstop> <tabstop>LotW_CSV_fetch_push_button</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
<tabstop>sbNtrials</tabstop> <tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop> <tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop> <tabstop>cbTwoPass</tabstop>
@ -3118,11 +3159,11 @@ Right click for insert and delete options.</string>
<tabstop>rbHound</tabstop> <tabstop>rbHound</tabstop>
<tabstop>rbNA_VHF_Contest</tabstop> <tabstop>rbNA_VHF_Contest</tabstop>
<tabstop>rbField_Day</tabstop> <tabstop>rbField_Day</tabstop>
<tabstop>Field_Day_Exchange</tabstop>
<tabstop>rbEU_VHF_Contest</tabstop> <tabstop>rbEU_VHF_Contest</tabstop>
<tabstop>rbRTTY_Roundup</tabstop> <tabstop>rbRTTY_Roundup</tabstop>
<tabstop>RTTY_Exchange</tabstop>
<tabstop>rbWW_DIGI</tabstop> <tabstop>rbWW_DIGI</tabstop>
<tabstop>Field_Day_Exchange</tabstop>
<tabstop>RTTY_Exchange</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections> <connections>
@ -3192,13 +3233,13 @@ Right click for insert and delete options.</string>
</connection> </connection>
</connections> </connections>
<buttongroups> <buttongroups>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="TX_audio_source_button_group"/> <buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="split_mode_button_group"/> <buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_handshake_button_group"/> <buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="split_mode_button_group"/>
</buttongroups> </buttongroups>
</ui> </ui>

View File

@ -6,16 +6,16 @@
#include <limits> #include <limits>
#include <QUdpSocket> #include <QUdpSocket>
#include <QNetworkInterface>
#include <QHostInfo> #include <QHostInfo>
#include <QTimer> #include <QTimer>
#include <QQueue> #include <QQueue>
#include <QByteArray> #include <QByteArray>
#include <QHostAddress>
#include <QColor> #include <QColor>
#include <QDebug> #include <QDebug>
#include "NetworkMessage.hpp" #include "NetworkMessage.hpp"
#include "qt_helpers.hpp"
#include "pimpl_impl.hpp" #include "pimpl_impl.hpp"
#include "moc_MessageClient.cpp" #include "moc_MessageClient.cpp"
@ -34,14 +34,16 @@ class MessageClient::impl
public: public:
impl (QString const& id, QString const& version, QString const& revision, impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self) port_type server_port, int TTL, MessageClient * self)
: self_ {self} : self_ {self}
, dns_lookup_id_ {-1} , dns_lookup_id_ {-1}
, enabled_ {false} , enabled_ {false}
, id_ {id} , id_ {id}
, version_ {version} , version_ {version}
, revision_ {revision} , revision_ {revision}
, dns_lookup_id_ {-1}
, server_port_ {server_port} , server_port_ {server_port}
, TTL_ {TTL}
, schema_ {2} // use 2 prior to negotiation not 1 which is broken , schema_ {2} // use 2 prior to negotiation not 1 which is broken
, heartbeat_timer_ {new QTimer {this}} , heartbeat_timer_ {new QTimer {this}}
{ {
@ -49,9 +51,6 @@ public:
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams); connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
heartbeat_timer_->start (NetworkMessage::pulse * 1000); heartbeat_timer_->start (NetworkMessage::pulse * 1000);
// bind to an ephemeral port
bind ();
} }
~impl () ~impl ()
@ -65,35 +64,37 @@ public:
enum StreamStatus {Fail, Short, OK}; enum StreamStatus {Fail, Short, OK};
void parse_message (QByteArray const& msg); void set_server (QString const& server_name, QStringList const& network_interface_names);
Q_SLOT void host_info_results (QHostInfo);
void start ();
void parse_message (QByteArray const&);
void pending_datagrams (); void pending_datagrams ();
void heartbeat (); void heartbeat ();
void closedown (); void closedown ();
StreamStatus check_status (QDataStream const&) const; StreamStatus check_status (QDataStream const&) const;
void send_message (QByteArray const&); void send_message (QByteArray const&, bool queue_if_pending = true);
void send_message (QDataStream const& out, QByteArray const& message) void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true)
{ {
if (OK == check_status (out)) if (OK == check_status (out))
{ {
send_message (message); send_message (message, queue_if_pending);
} }
else else
{ {
Q_EMIT self_->error ("Error creating UDP message"); Q_EMIT self_->error ("Error creating UDP message");
} }
} }
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_; MessageClient * self_;
int dns_lookup_id_;
bool enabled_; bool enabled_;
QString id_; QString id_;
QString version_; QString version_;
QString revision_; QString revision_;
QString server_string_; int dns_lookup_id_;
port_type server_port_;
QHostAddress server_; QHostAddress server_;
port_type server_port_;
int TTL_;
std::vector<QNetworkInterface> network_interfaces_;
quint32 schema_; quint32 schema_;
QTimer * heartbeat_timer_; QTimer * heartbeat_timer_;
std::vector<QHostAddress> blocked_addresses_; std::vector<QHostAddress> blocked_addresses_;
@ -105,37 +106,99 @@ public:
#include "MessageClient.moc" #include "MessageClient.moc"
void MessageClient::impl::set_server (QString const& server_name, QStringList const& network_interface_names)
{
// qDebug () << "MessageClient server:" << server_name << "port:" << server_port_ << "interfaces:" << network_interface_names;
server_.setAddress (server_name);
network_interfaces_.clear ();
for (auto const& net_if_name : network_interface_names)
{
network_interfaces_.push_back (QNetworkInterface::interfaceFromName (net_if_name));
}
if (server_.isNull () && server_name.size ()) // DNS lookup required
{
// queue a host address lookup
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, &MessageClient::impl::host_info_results);
#else
dns_lookup_id_ = QHostInfo::lookupHost (server_name, this, SLOT (host_info_results (QHostInfo)));
#endif
}
else
{
start ();
}
}
void MessageClient::impl::host_info_results (QHostInfo host_info) void MessageClient::impl::host_info_results (QHostInfo host_info)
{ {
if (host_info.lookupId () != dns_lookup_id_) return; if (host_info.lookupId () != dns_lookup_id_) return;
dns_lookup_id_ = -1; dns_lookup_id_ = -1;
if (QHostInfo::NoError != host_info.error ()) if (QHostInfo::NoError != host_info.error ())
{ {
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ()); Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
pending_messages_.clear (); // discard
} }
else if (host_info.addresses ().size ()) else
{ {
auto server = host_info.addresses ()[0]; auto const& server_addresses = host_info.addresses ();
if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server)) if (server_addresses.size ())
{ {
server_ = server; server_ = server_addresses[0];
TRACE_UDP ("resulting server:" << server);
// send initial heartbeat which allows schema negotiation
heartbeat ();
// clear any backlog
while (pending_messages_.size ())
{
send_message (pending_messages_.dequeue ());
}
} }
else }
start ();
}
void MessageClient::impl::start ()
{
if (server_.isNull ())
{
Q_EMIT self_->close ();
pending_messages_.clear (); // discard
return;
}
if (is_broadcast_address (server_))
{
Q_EMIT self_->error ("IPv4 broadcast not supported, please specify the loop-back address, a server host address, or multicast group address");
pending_messages_.clear (); // discard
return;
}
if (blocked_addresses_.end () != std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server_))
{
Q_EMIT self_->error ("UDP server blocked, please try another");
pending_messages_.clear (); // discard
return;
}
TRACE_UDP ("Trying server:" << server_.toString ());
QHostAddress interface_addr {IPv6Protocol == server_.protocol () ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4};
if (localAddress () != interface_addr)
{
if (UnconnectedState != state () || state ())
{ {
Q_EMIT self_->error ("UDP server blocked, please try another"); close ();
pending_messages_.clear (); // discard
} }
// bind to an ephemeral port on the selected interface and set
// up for sending datagrams
bind (interface_addr);
// qDebug () << "Bound to UDP port:" << localPort () << "on:" << localAddress ();
// set multicast TTL to limit scope when sending to multicast
// group addresses
setSocketOption (MulticastTtlOption, TTL_);
}
// send initial heartbeat which allows schema negotiation
heartbeat ();
// clear any backlog
while (pending_messages_.size ())
{
send_message (pending_messages_.dequeue (), false);
} }
} }
@ -363,14 +426,11 @@ void MessageClient::impl::heartbeat ()
if (server_port_ && !server_.isNull ()) if (server_port_ && !server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_}; NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_};
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted out << NetworkMessage::Builder::schema_number // maximum schema number accepted
<< version_.toUtf8 () << revision_.toUtf8 (); << version_.toUtf8 () << revision_.toUtf8 ();
if (OK == check_status (hb)) TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
{ send_message (out, message, false);
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
writeDatagram (message, server_, server_port_);
}
} }
} }
@ -380,15 +440,12 @@ void MessageClient::impl::closedown ()
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_}; NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
if (OK == check_status (out)) TRACE_UDP ("");
{ send_message (out, message, false);
TRACE_UDP ("");
writeDatagram (message, server_, server_port_);
}
} }
} }
void MessageClient::impl::send_message (QByteArray const& message) void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending)
{ {
if (server_port_) if (server_port_)
{ {
@ -396,11 +453,25 @@ void MessageClient::impl::send_message (QByteArray const& message)
{ {
if (message != last_message_) // avoid duplicates if (message != last_message_) // avoid duplicates
{ {
writeDatagram (message, server_, server_port_); if (is_multicast_address (server_))
{
// send datagram on each selected network interface
std::for_each (network_interfaces_.begin (), network_interfaces_.end ()
, [&] (QNetworkInterface const& net_if) {
setMulticastInterface (net_if);
// qDebug () << "Multicast UDP datagram sent to:" << server_ << "port:" << server_port_ << "on:" << multicastInterface ().humanReadableName ();
writeDatagram (message, server_, server_port_);
});
}
else
{
// qDebug () << "Unicast UDP datagram sent to:" << server_ << "port:" << server_port_;
writeDatagram (message, server_, server_port_);
}
last_message_ = message; last_message_ = message;
} }
} }
else else if (queue_if_pending)
{ {
pending_messages_.enqueue (message); pending_messages_.enqueue (message);
} }
@ -433,9 +504,11 @@ auto MessageClient::impl::check_status (QDataStream const& stream) const -> Stre
} }
MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision, MessageClient::MessageClient (QString const& id, QString const& version, QString const& revision,
QString const& server, port_type server_port, QObject * self) QString const& server_name, port_type server_port,
QStringList const& network_interface_names,
int TTL, QObject * self)
: QObject {self} : QObject {self}
, m_ {id, version, revision, server_port, this} , m_ {id, version, revision, server_port, TTL, this}
{ {
connect (&*m_ connect (&*m_
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
@ -454,8 +527,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
#endif #endif
Q_EMIT error (m_->errorString ()); Q_EMIT error (m_->errorString ());
} }
}); });
set_server (server); m_->set_server (server_name, network_interface_names);
} }
QHostAddress MessageClient::server_address () const QHostAddress MessageClient::server_address () const
@ -468,20 +541,9 @@ auto MessageClient::server_port () const -> port_type
return m_->server_port_; return m_->server_port_;
} }
void MessageClient::set_server (QString const& server) void MessageClient::set_server (QString const& server_name, QStringList const& network_interface_names)
{ {
m_->server_.clear (); m_->set_server (server_name, network_interface_names);
m_->server_string_ = server;
if (server.size ())
{
// queue a host address lookup
TRACE_UDP ("server host DNS lookup:" << server);
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
#else
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
#endif
}
} }
void MessageClient::set_server_port (port_type server_port) void MessageClient::set_server_port (port_type server_port)
@ -489,6 +551,12 @@ void MessageClient::set_server_port (port_type server_port)
m_->server_port_ = server_port; m_->server_port_ = server_port;
} }
void MessageClient::set_TTL (int TTL)
{
m_->TTL_ = TTL;
m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
}
void MessageClient::enable (bool flag) void MessageClient::enable (bool flag)
{ {
m_->enabled_ = flag; m_->enabled_ = flag;
@ -504,7 +572,7 @@ void MessageClient::status_update (Frequency f, QString const& mode, QString con
, quint32 frequency_tolerance, quint32 tr_period , quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name) , QString const& configuration_name)
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::Status, m_->id_, m_->schema_};
@ -521,7 +589,7 @@ void MessageClient::decode (bool is_new, QTime time, qint32 snr, float delta_tim
, QString const& mode, QString const& message_text, bool low_confidence , QString const& mode, QString const& message_text, bool low_confidence
, bool off_air) , bool off_air)
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::Decode, m_->id_, m_->schema_};
@ -536,7 +604,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
, qint32 drift, QString const& callsign, QString const& grid, qint32 power , qint32 drift, QString const& callsign, QString const& grid, qint32 power
, bool off_air) , bool off_air)
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::WSPRDecode, m_->id_, m_->schema_};
@ -549,7 +617,7 @@ void MessageClient::WSPR_decode (bool is_new, QTime time, qint32 snr, float delt
void MessageClient::decodes_cleared () void MessageClient::decodes_cleared ()
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::Clear, m_->id_, m_->schema_};
@ -566,7 +634,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
, QString const& my_grid, QString const& exchange_sent , QString const& my_grid, QString const& exchange_sent
, QString const& exchange_rcvd, QString const& propmode) , QString const& exchange_rcvd, QString const& propmode)
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::QSOLogged, m_->id_, m_->schema_};
@ -581,7 +649,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
void MessageClient::logged_ADIF (QByteArray const& ADIF_record) void MessageClient::logged_ADIF (QByteArray const& ADIF_record)
{ {
if (m_->server_port_ && !m_->server_string_.isEmpty ()) if (m_->server_port_ && !m_->server_.isNull ())
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_}; NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};

View File

@ -5,6 +5,7 @@
#include <QTime> #include <QTime>
#include <QDateTime> #include <QDateTime>
#include <QString> #include <QString>
#include <QHostAddress>
#include "Radio.hpp" #include "Radio.hpp"
#include "pimpl_h.hpp" #include "pimpl_h.hpp"
@ -34,19 +35,25 @@ public:
// //
// messages will be silently dropped until a server host lookup is complete // messages will be silently dropped until a server host lookup is complete
MessageClient (QString const& id, QString const& version, QString const& revision, MessageClient (QString const& id, QString const& version, QString const& revision,
QString const& server, port_type server_port, QObject * parent = nullptr); QString const& server_name, port_type server_port,
QStringList const& network_interface_names,
int TTL, QObject * parent = nullptr);
// query server details // query server details
QHostAddress server_address () const; QHostAddress server_address () const;
port_type server_port () const; port_type server_port () const;
// initiate a new server host lookup or is the server name is empty // initiate a new server host lookup or if the server name is empty
// the sending of messages is disabled // the sending of messages is disabled, if an interface is specified
Q_SLOT void set_server (QString const& server = QString {}); // then that interface is used for outgoing datagrams
Q_SLOT void set_server (QString const& server_name, QStringList const& network_interface_names);
// change the server port messages are sent to // change the server port messages are sent to
Q_SLOT void set_server_port (port_type server_port = 0u); Q_SLOT void set_server_port (port_type server_port = 0u);
// change the server port messages are sent to
Q_SLOT void set_TTL (int TTL);
// enable incoming messages // enable incoming messages
Q_SLOT void enable (bool); Q_SLOT void enable (bool);

View File

@ -28,13 +28,13 @@ namespace NetworkMessage
{ {
setVersion (QDataStream::Qt_5_0); // Qt schema version setVersion (QDataStream::Qt_5_0); // Qt schema version
} }
#if QT_VERSION >= 0x050200 #if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
else if (schema <= 2) else if (schema <= 2)
{ {
setVersion (QDataStream::Qt_5_2); // Qt schema version setVersion (QDataStream::Qt_5_2); // Qt schema version
} }
#endif #endif
#if QT_VERSION >= 0x050400 #if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
else if (schema <= 3) else if (schema <= 3)
{ {
setVersion (QDataStream::Qt_5_4); // Qt schema version setVersion (QDataStream::Qt_5_4); // Qt schema version
@ -73,13 +73,13 @@ namespace NetworkMessage
{ {
parent->setVersion (QDataStream::Qt_5_0); parent->setVersion (QDataStream::Qt_5_0);
} }
#if QT_VERSION >= 0x050200 #if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
else if (schema_ <= 2) else if (schema_ <= 2)
{ {
parent->setVersion (QDataStream::Qt_5_2); parent->setVersion (QDataStream::Qt_5_2);
} }
#endif #endif
#if QT_VERSION >= 0x050400 #if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
else if (schema_ <= 3) else if (schema_ <= 3)
{ {
parent->setVersion (QDataStream::Qt_5_4); parent->setVersion (QDataStream::Qt_5_4);

View File

@ -540,9 +540,9 @@ namespace NetworkMessage
// increment this if a newer Qt schema is required and add decode // increment this if a newer Qt schema is required and add decode
// logic to the Builder and Reader class implementations // logic to the Builder and Reader class implementations
#if QT_VERSION >= 0x050400 #if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
static quint32 constexpr schema_number {3}; static quint32 constexpr schema_number {3};
#elif QT_VERSION >= 0x050200 #elif QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
static quint32 constexpr schema_number {2}; static quint32 constexpr schema_number {2};
#else #else
// Schema 1 (Qt_5_0) is broken // Schema 1 (Qt_5_0) is broken

View File

@ -84,7 +84,7 @@ public:
{ {
if (!socket_ if (!socket_
|| QAbstractSocket::UnconnectedState == socket_->state () || QAbstractSocket::UnconnectedState == socket_->state ()
|| (socket_->socketType () != config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket)) || (socket_->socketType () != (config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket)))
{ {
// we need to create the appropriate socket // we need to create the appropriate socket
if (socket_ if (socket_

View File

@ -211,7 +211,7 @@ void WSPRNet::networkReply (QNetworkReply * reply)
} }
} }
bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query) bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query) const
{ {
auto const& rx_match = wspr_re.match (line); auto const& rx_match = wspr_re.match (line);
if (rx_match.hasMatch ()) { if (rx_match.hasMatch ()) {
@ -271,7 +271,23 @@ bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
return true; return true;
} }
auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type QString WSPRNet::encode_mode () const
{
if (m_mode == "WSPR") return "2";
if (m_mode == "WSPR-15") return "15";
if (m_mode == "FST4W")
{
auto tr = static_cast<int> ((TR_period_ / 60.)+.5);
if (2 == tr || 15 == tr)
{
tr += 1; // distinguish from WSPR-2 and WSPR-15
}
return QString::number (tr);
}
return "";
}
auto WSPRNet::urlEncodeNoSpot () const -> SpotQueue::value_type
{ {
SpotQueue::value_type query; SpotQueue::value_type query;
query.addQueryItem ("function", "wsprstat"); query.addQueryItem ("function", "wsprstat");
@ -282,28 +298,18 @@ auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
query.addQueryItem ("tqrg", m_tfreq); query.addQueryItem ("tqrg", m_tfreq);
query.addQueryItem ("dbm", m_dbm); query.addQueryItem ("dbm", m_dbm);
query.addQueryItem ("version", m_vers); query.addQueryItem ("version", m_vers);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2"); query.addQueryItem ("mode", encode_mode ());
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
return query;; return query;;
} }
auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) -> SpotQueue::value_type auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) const -> SpotQueue::value_type
{ {
query.addQueryItem ("version", m_vers); query.addQueryItem ("version", m_vers);
query.addQueryItem ("rcall", m_call); query.addQueryItem ("rcall", m_call);
query.addQueryItem ("rgrid", m_grid); query.addQueryItem ("rgrid", m_grid);
query.addQueryItem ("rqrg", m_rfreq); query.addQueryItem ("rqrg", m_rfreq);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2"); query.addQueryItem ("mode", encode_mode ());
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15"); return query;
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
return query;
} }
void WSPRNet::work() void WSPRNet::work()

View File

@ -34,9 +34,10 @@ public slots:
void abortOutstandingRequests (); void abortOutstandingRequests ();
private: private:
bool decodeLine (QString const& line, SpotQueue::value_type& query); bool decodeLine (QString const& line, SpotQueue::value_type& query) const;
SpotQueue::value_type urlEncodeNoSpot (); SpotQueue::value_type urlEncodeNoSpot () const;
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot); SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot) const;
QString encode_mode () const;
QNetworkAccessManager * network_manager_; QNetworkAccessManager * network_manager_;
QList<QNetworkReply *> m_outstandingRequests; QList<QNetworkReply *> m_outstandingRequests;

View File

@ -14,10 +14,16 @@ namespace Radio
double constexpr MHz_factor {1.e6}; double constexpr MHz_factor {1.e6};
int constexpr frequency_precsion {6}; int constexpr frequency_precsion {6};
// valid callsign alphabet
QRegularExpression callsign_alphabet_re {R"(^[A-Z0-9/]{3,11}$)"};
// very loose validation - callsign must contain a letter next to // very loose validation - callsign must contain a letter next to
// a number // a number
QRegularExpression valid_callsign_regexp {R"(\d[[:alpha:]]|[[:alpha:]]\d)"}; QRegularExpression valid_callsign_regexp {R"(\d[[:alpha:]]|[[:alpha:]]\d)"};
// standard callsign
QRegularExpression strict_standard_callsign_re {R"(^([A-Z][0-9]?|[0-9][A-Z])[0-9][A-Z]{0,3}$)"};
// suffixes that are often used and should not be interpreted as a // suffixes that are often used and should not be interpreted as a
// DXCC Entity prefix used as a suffix // DXCC Entity prefix used as a suffix
QRegularExpression non_prefix_suffix {R"(\A([0-9AMPQR]|QRP|F[DF]|[AM]M|L[HT]|LGT)\z)"}; QRegularExpression non_prefix_suffix {R"(\A([0-9AMPQR]|QRP|F[DF]|[AM]M|L[HT]|LGT)\z)"};
@ -112,6 +118,12 @@ namespace Radio
return callsign.contains ('/'); return callsign.contains ('/');
} }
bool is_77bit_nonstandard_callsign (QString const& callsign)
{
return callsign.contains (callsign_alphabet_re)
&& !callsign.contains (strict_standard_callsign_re);
}
// split on first '/' and return the larger portion or the whole if // split on first '/' and return the larger portion or the whole if
// there is no '/' // there is no '/'
QString base_callsign (QString callsign) QString base_callsign (QString callsign)

View File

@ -53,6 +53,7 @@ namespace Radio
// //
bool UDP_EXPORT is_callsign (QString const&); bool UDP_EXPORT is_callsign (QString const&);
bool UDP_EXPORT is_compound_callsign (QString const&); bool UDP_EXPORT is_compound_callsign (QString const&);
bool is_77bit_nonstandard_callsign (QString const&);
QString UDP_EXPORT base_callsign (QString); QString UDP_EXPORT base_callsign (QString);
QString UDP_EXPORT effective_prefix (QString); QString UDP_EXPORT effective_prefix (QString);
} }

View File

@ -25,10 +25,13 @@ namespace
QFont text_font {"Courier", 10}; QFont text_font {"Courier", 10};
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr, float delta_time
, Frequency frequency, qint32 drift, QString const& callsign , Frequency frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air) , QString const& grid, qint32 power, bool off_air)
{ {
auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
client_item->setData (QVariant::fromValue (key));
auto time_item = new QStandardItem {time.toString ("hh:mm")}; auto time_item = new QStandardItem {time.toString ("hh:mm")};
time_item->setData (time); time_item->setData (time);
time_item->setTextAlignment (Qt::AlignRight); time_item->setTextAlignment (Qt::AlignRight);
@ -60,7 +63,7 @@ namespace
live->setTextAlignment (Qt::AlignHCenter); live->setTextAlignment (Qt::AlignHCenter);
QList<QStandardItem *> row { QList<QStandardItem *> row {
new QStandardItem {client_id}, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}}; client_item, time_item, snr_item, dt, freq, dri, gd, pwr, live, new QStandardItem {callsign}};
Q_FOREACH (auto& item, row) Q_FOREACH (auto& item, row)
{ {
item->setEditable (false); item->setEditable (false);
@ -81,7 +84,7 @@ BeaconsModel::BeaconsModel (QObject * parent)
} }
} }
void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time void BeaconsModel::add_beacon_spot (bool is_new, ClientKey const& key, QTime time, qint32 snr, float delta_time
, Frequency frequency, qint32 drift, QString const& callsign , Frequency frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air) , QString const& grid, qint32 power, bool off_air)
{ {
@ -90,7 +93,7 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
int target_row {-1}; int target_row {-1};
for (auto row = 0; row < rowCount (); ++row) for (auto row = 0; row < rowCount (); ++row)
{ {
if (data (index (row, 0)).toString () == client_id) if (item (row, 0)->data ().value<ClientKey> () == key)
{ {
auto row_time = item (row, 1)->data ().toTime (); auto row_time = item (row, 1)->data ().toTime ();
if (row_time == time if (row_time == time
@ -113,19 +116,19 @@ void BeaconsModel::add_beacon_spot (bool is_new, QString const& client_id, QTime
} }
if (target_row >= 0) if (target_row >= 0)
{ {
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); insertRow (target_row + 1, make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
return; return;
} }
} }
appendRow (make_row (client_id, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air)); appendRow (make_row (key, time, snr, delta_time, frequency, drift, callsign, grid, power, off_air));
} }
void BeaconsModel::decodes_cleared (QString const& client_id) void BeaconsModel::decodes_cleared (ClientKey const& key)
{ {
for (auto row = rowCount () - 1; row >= 0; --row) for (auto row = rowCount () - 1; row >= 0; --row)
{ {
if (data (index (row, 0)).toString () == client_id) if (item (row, 0)->data ().value<ClientKey> () == key)
{ {
removeRow (row); removeRow (row);
} }

View File

@ -26,13 +26,15 @@ class BeaconsModel
{ {
Q_OBJECT; Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public: public:
explicit BeaconsModel (QObject * parent = nullptr); explicit BeaconsModel (QObject * parent = nullptr);
Q_SLOT void add_beacon_spot (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time Q_SLOT void add_beacon_spot (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time
, Frequency frequency, qint32 drift, QString const& callsign, QString const& grid , Frequency frequency, qint32 drift, QString const& callsign, QString const& grid
, qint32 power, bool off_air); , qint32 power, bool off_air);
Q_SLOT void decodes_cleared (QString const& client_id); Q_SLOT void decodes_cleared (ClientKey const&);
}; };
#endif #endif

View File

@ -27,9 +27,9 @@ namespace
} }
} }
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent) ClientWidget::IdFilterModel::IdFilterModel (ClientKey const& key, QObject * parent)
: QSortFilterProxyModel {parent} : QSortFilterProxyModel {parent}
, client_id_ {client_id} , key_ {key}
, rx_df_ (quint32_max) , rx_df_ (quint32_max)
{ {
} }
@ -73,7 +73,7 @@ bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row
, QModelIndex const& source_parent) const , QModelIndex const& source_parent) const
{ {
auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent); auto source_index_col0 = sourceModel ()->index (source_row, 0, source_parent);
return sourceModel ()->data (source_index_col0).toString () == client_id_; return sourceModel ()->data (source_index_col0).value<ClientKey> () == key_;
} }
void ClientWidget::IdFilterModel::de_call (QString const& call) void ClientWidget::IdFilterModel::de_call (QString const& call)
@ -106,9 +106,9 @@ void ClientWidget::IdFilterModel::rx_df (quint32 df)
namespace namespace
{ {
QString make_title (QString const& id, QString const& version, QString const& revision) QString make_title (MessageServer::ClientKey const& key, QString const& version, QString const& revision)
{ {
QString title {id}; QString title {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
if (version.size ()) if (version.size ())
{ {
title += QString {" v%1"}.arg (version); title += QString {" v%1"}.arg (version);
@ -122,14 +122,14 @@ namespace
} }
ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision , ClientKey const& key, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent) , QListWidget const * calls_of_interest, QWidget * parent)
: QDockWidget {make_title (id, version, revision), parent} : QDockWidget {make_title (key, version, revision), parent}
, id_ {id} , key_ {key}
, done_ {false} , done_ {false}
, calls_of_interest_ {calls_of_interest} , calls_of_interest_ {calls_of_interest}
, decodes_proxy_model_ {id} , decodes_proxy_model_ {key}
, beacons_proxy_model_ {id} , beacons_proxy_model_ {key}
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}} , erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}} , erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), this}}
, erase_both_action_ {new QAction {tr ("Erase &Both"), this}} , erase_both_action_ {new QAction {tr ("Erase &Both"), this}}
@ -209,56 +209,56 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
horizontal_layout_->addLayout (subform3_layout_); horizontal_layout_->addLayout (subform3_layout_);
connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) { connect (message_line_edit_, &QLineEdit::textEdited, [this] (QString const& text) {
Q_EMIT do_free_text (id_, text, false); Q_EMIT do_free_text (key_, text, false);
}); });
connect (message_line_edit_, &QLineEdit::editingFinished, [this] () { connect (message_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT do_free_text (id_, message_line_edit_->text (), true); Q_EMIT do_free_text (key_, message_line_edit_->text (), true);
}); });
connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () { connect (grid_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT location (id_, grid_line_edit_->text ()); Q_EMIT location (key_, grid_line_edit_->text ());
}); });
connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () { connect (configuration_line_edit_, &QLineEdit::editingFinished, [this] () {
Q_EMIT switch_configuration (id_, configuration_line_edit_->text ()); Q_EMIT switch_configuration (key_, configuration_line_edit_->text ());
}); });
connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () { connect (mode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty; QString empty;
Q_EMIT configure (id_, mode_line_edit_->text (), quint32_max, empty, fast_mode () Q_EMIT configure (key_, mode_line_edit_->text (), quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false); , quint32_max, quint32_max, empty, empty, false);
}); });
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) { connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty; QString empty;
auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i; auto f = frequency_tolerance_spin_box_->specialValueText ().size () ? quint32_max : i;
Q_EMIT configure (id_, empty, f, empty, fast_mode () Q_EMIT configure (key_, empty, f, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, false); , quint32_max, quint32_max, empty, empty, false);
}); });
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () { connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, submode_line_edit_->text (), fast_mode () Q_EMIT configure (key_, empty, quint32_max, submode_line_edit_->text (), fast_mode ()
, quint32_max, quint32_max, empty, empty, false); , quint32_max, quint32_max, empty, empty, false);
}); });
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) { connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, Qt::Checked == state Q_EMIT configure (key_, empty, quint32_max, empty, Qt::Checked == state
, quint32_max, quint32_max, empty, empty, false); , quint32_max, quint32_max, empty, empty, false);
}); });
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) { connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, i, quint32_max, empty, empty, false); , i, quint32_max, empty, empty, false);
}); });
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) { connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, quint32_max, i, empty, empty, false); , quint32_max, i, empty, empty, false);
}); });
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () { connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false); , quint32_max, quint32_max, dx_call_line_edit_->text (), empty, false);
}); });
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () { connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false); , quint32_max, quint32_max, empty, dx_grid_line_edit_->text (), false);
}); });
@ -289,14 +289,14 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole); halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) { connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
QString empty; QString empty;
Q_EMIT configure (id_, empty, quint32_max, empty, fast_mode () Q_EMIT configure (key_, empty, quint32_max, empty, fast_mode ()
, quint32_max, quint32_max, empty, empty, true); , quint32_max, quint32_max, empty, empty, true);
}); });
connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { connect (auto_off_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, true); Q_EMIT do_halt_tx (key_, true);
}); });
connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) { connect (halt_tx_button_, &QAbstractButton::clicked, [this] (bool /* checked */) {
Q_EMIT do_halt_tx (id_, false); Q_EMIT do_halt_tx (key_, false);
}); });
content_layout_->addWidget (control_button_box_); content_layout_->addWidget (control_button_box_);
@ -318,13 +318,13 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
// connect context menu actions // connect context menu actions
connect (erase_action_, &QAction::triggered, [this] (bool /*checked*/) { connect (erase_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_); Q_EMIT do_clear_decodes (key_);
}); });
connect (erase_rx_frequency_action_, &QAction::triggered, [this] (bool /*checked*/) { connect (erase_rx_frequency_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_, 1); Q_EMIT do_clear_decodes (key_, 1);
}); });
connect (erase_both_action_, &QAction::triggered, [this] (bool /*checked*/) { connect (erase_both_action_, &QAction::triggered, [this] (bool /*checked*/) {
Q_EMIT do_clear_decodes (id_, 2); Q_EMIT do_clear_decodes (key_, 2);
}); });
// connect up table view signals // connect up table view signals
@ -335,7 +335,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
// tell new client about calls of interest // tell new client about calls of interest
for (int row = 0; row < calls_of_interest_->count (); ++row) for (int row = 0; row < calls_of_interest_->count (); ++row)
{ {
Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow}); Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text (), QColor {Qt::blue}, QColor {Qt::yellow});
} }
} }
@ -349,7 +349,7 @@ void ClientWidget::closeEvent (QCloseEvent *e)
{ {
if (!done_) if (!done_)
{ {
Q_EMIT do_close (id_); Q_EMIT do_close (key_);
e->ignore (); // defer closure until client actually closes e->ignore (); // defer closure until client actually closes
} }
else else
@ -363,7 +363,7 @@ ClientWidget::~ClientWidget ()
for (int row = 0; row < calls_of_interest_->count (); ++row) for (int row = 0; row < calls_of_interest_->count (); ++row)
{ {
// tell client to forget calls of interest // tell client to forget calls of interest
Q_EMIT highlight_callsign (id_, calls_of_interest_->item (row)->text ()); Q_EMIT highlight_callsign (key_, calls_of_interest_->item (row)->text ());
} }
} }
@ -395,7 +395,7 @@ namespace
} }
} }
void ClientWidget::update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call void ClientWidget::update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled , QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df , bool transmitting, bool decoding, quint32 rx_df, quint32 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
@ -403,7 +403,7 @@ void ClientWidget::update_status (QString const& id, Frequency f, QString const&
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period , quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name) , QString const& configuration_name)
{ {
if (id == id_) if (key == key_)
{ {
fast_mode_check_box_->setChecked (fast_mode); fast_mode_check_box_->setChecked (fast_mode);
decodes_proxy_model_.de_call (de_call); decodes_proxy_model_.de_call (de_call);
@ -447,11 +447,11 @@ 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*/ void ClientWidget::decode_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/
, float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/ , float /*delta_time*/, quint32 /*delta_frequency*/, QString const& /*mode*/
, QString const& /*message*/, bool /*low_confidence*/, bool /*off_air*/) , QString const& /*message*/, bool /*low_confidence*/, bool /*off_air*/)
{ {
if (client_id == id_ && !columns_resized_) if (key == key_ && !columns_resized_)
{ {
decodes_stack_->setCurrentIndex (0); decodes_stack_->setCurrentIndex (0);
decodes_table_view_->resizeColumnsToContents (); decodes_table_view_->resizeColumnsToContents ();
@ -460,12 +460,12 @@ void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTim
decodes_table_view_->scrollToBottom (); decodes_table_view_->scrollToBottom ();
} }
void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id, QTime /*time*/, qint32 /*snr*/ void ClientWidget::beacon_spot_added (bool /*is_new*/, ClientKey const& key, QTime /*time*/, qint32 /*snr*/
, float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/ , float /*delta_time*/, Frequency /*delta_frequency*/, qint32 /*drift*/
, QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/ , QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/
, bool /*off_air*/) , bool /*off_air*/)
{ {
if (client_id == id_ && !columns_resized_) if (key == key_ && !columns_resized_)
{ {
decodes_stack_->setCurrentIndex (1); decodes_stack_->setCurrentIndex (1);
beacons_table_view_->resizeColumnsToContents (); beacons_table_view_->resizeColumnsToContents ();
@ -474,9 +474,9 @@ void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id,
beacons_table_view_->scrollToBottom (); beacons_table_view_->scrollToBottom ();
} }
void ClientWidget::decodes_cleared (QString const& client_id) void ClientWidget::decodes_cleared (ClientKey const& key)
{ {
if (client_id == id_) if (key == key_)
{ {
columns_resized_ = false; columns_resized_ = false;
} }

View File

@ -35,42 +35,44 @@ class ClientWidget
{ {
Q_OBJECT; Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public: public:
explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model explicit ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemModel * beacons_model
, QString const& id, QString const& version, QString const& revision , ClientKey const& key, QString const& version, QString const& revision
, QListWidget const * calls_of_interest, QWidget * parent = nullptr); , QListWidget const * calls_of_interest, QWidget * parent = nullptr);
void dispose (); void dispose ();
~ClientWidget (); ~ClientWidget ();
bool fast_mode () const; bool fast_mode () const;
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& dx_call Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled , QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df , bool transmitting, bool decoding, quint32 rx_df, quint32 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 , bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period , quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name); , QString const& configuration_name);
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime, qint32 snr Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode , float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air); , QString const& message, bool low_confidence, bool off_air);
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime, qint32 snr Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime, qint32 snr
, float delta_time, Frequency delta_frequency, qint32 drift , float delta_time, Frequency delta_frequency, qint32 drift
, QString const& callsign, QString const& grid, qint32 power , QString const& callsign, QString const& grid, qint32 power
, bool off_air); , bool off_air);
Q_SLOT void decodes_cleared (QString const& client_id); Q_SLOT void decodes_cleared (ClientKey const& key);
Q_SIGNAL void do_clear_decodes (QString const& id, quint8 window = 0); Q_SIGNAL void do_clear_decodes (ClientKey const& key, quint8 window = 0);
Q_SIGNAL void do_close (QString const& id); Q_SIGNAL void do_close (ClientKey const& key);
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier); 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_halt_tx (ClientKey const& key, bool auto_only);
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool); Q_SIGNAL void do_free_text (ClientKey const& key, QString const& text, bool);
Q_SIGNAL void location (QString const& id, QString const& text); Q_SIGNAL void location (ClientKey const& key, QString const& text);
Q_SIGNAL void highlight_callsign (QString const& id, QString const& call Q_SIGNAL void highlight_callsign (ClientKey const& key, QString const& call
, QColor const& bg = QColor {}, QColor const& fg = QColor {} , QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false); , bool last_only = false);
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name); Q_SIGNAL void switch_configuration (ClientKey const& key, QString const& configuration_name);
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance Q_SIGNAL void configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages); , QString const& dx_call, QString const& dx_grid, bool generate_messages);
@ -79,7 +81,7 @@ private:
: public QSortFilterProxyModel : public QSortFilterProxyModel
{ {
public: public:
IdFilterModel (QString const& client_id, QObject * = nullptr); IdFilterModel (ClientKey const& key, QObject * = nullptr);
void de_call (QString const&); void de_call (QString const&);
void rx_df (quint32); void rx_df (quint32);
@ -88,7 +90,7 @@ private:
private: private:
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override; bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
QString client_id_; ClientKey key_;
QString call_; QString call_;
QRegularExpression base_call_re_; QRegularExpression base_call_re_;
quint32 rx_df_; quint32 rx_df_;
@ -96,7 +98,7 @@ private:
void closeEvent (QCloseEvent *) override; void closeEvent (QCloseEvent *) override;
QString id_; ClientKey key_;
bool done_; bool done_;
QListWidget const * calls_of_interest_; QListWidget const * calls_of_interest_;
IdFilterModel decodes_proxy_model_; IdFilterModel decodes_proxy_model_;

View File

@ -2,6 +2,7 @@
#include <QStandardItem> #include <QStandardItem>
#include <QModelIndex> #include <QModelIndex>
#include <QVariant>
#include <QTime> #include <QTime>
#include <QString> #include <QString>
#include <QFont> #include <QFont>
@ -33,10 +34,13 @@ namespace
QFont text_font {"Courier", 10}; QFont text_font {"Courier", 10};
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr
, quint32 delta_frequency, QString const& mode, QString const& message , float delta_time, quint32 delta_frequency, QString const& mode
, bool low_confidence, bool off_air, bool is_fast) , QString const& message, bool low_confidence, bool off_air, bool is_fast)
{ {
auto client_item = new QStandardItem {QString {"%1(%2)"}.arg (key.second).arg (key.first.toString ())};
client_item->setData (QVariant::fromValue (key));
auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")}; auto time_item = new QStandardItem {time.toString (is_fast || "~" == mode ? "hh:mm:ss" : "hh:mm")};
time_item->setData (time); time_item->setData (time);
time_item->setTextAlignment (Qt::AlignRight); time_item->setTextAlignment (Qt::AlignRight);
@ -63,7 +67,7 @@ namespace
live->setTextAlignment (Qt::AlignHCenter); live->setTextAlignment (Qt::AlignHCenter);
QList<QStandardItem *> row { QList<QStandardItem *> row {
new QStandardItem {client_id}, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}}; client_item, time_item, snr_item, dt, df, md, confidence, live, new QStandardItem {message}};
Q_FOREACH (auto& item, row) Q_FOREACH (auto& item, row)
{ {
item->setEditable (false); item->setEditable (false);
@ -84,7 +88,7 @@ DecodesModel::DecodesModel (QObject * parent)
} }
} }
void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time void DecodesModel::add_decode (bool is_new, ClientKey const& key, 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, bool off_air, bool is_fast) , bool low_confidence, bool off_air, bool is_fast)
{ {
@ -93,7 +97,7 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
int target_row {-1}; int target_row {-1};
for (auto row = 0; row < rowCount (); ++row) for (auto row = 0; row < rowCount (); ++row)
{ {
if (data (index (row, 0)).toString () == client_id) if (item (row, 0)->data ().value<ClientKey> () == key)
{ {
auto row_time = item (row, 1)->data ().toTime (); auto row_time = item (row, 1)->data ().toTime ();
if (row_time == time if (row_time == time
@ -115,21 +119,21 @@ void DecodesModel::add_decode (bool is_new, QString const& client_id, QTime time
} }
if (target_row >= 0) if (target_row >= 0)
{ {
insertRow (target_row + 1, make_row (client_id, time, snr, delta_time, delta_frequency, mode insertRow (target_row + 1, make_row (key, time, snr, delta_time, delta_frequency, mode
, message, low_confidence, off_air, is_fast)); , message, low_confidence, off_air, is_fast));
return; return;
} }
} }
appendRow (make_row (client_id, time, snr, delta_time, delta_frequency, mode, message, low_confidence appendRow (make_row (key, time, snr, delta_time, delta_frequency, mode, message, low_confidence
, off_air, is_fast)); , off_air, is_fast));
} }
void DecodesModel::decodes_cleared (QString const& client_id) void DecodesModel::decodes_cleared (ClientKey const& key)
{ {
for (auto row = rowCount () - 1; row >= 0; --row) for (auto row = rowCount () - 1; row >= 0; --row)
{ {
if (data (index (row, 0)).toString () == client_id) if (item (row, 0)->data ().value<ClientKey> () == key)
{ {
removeRow (row); removeRow (row);
} }
@ -139,7 +143,7 @@ void DecodesModel::decodes_cleared (QString const& client_id)
void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers) void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers)
{ {
auto row = source.row (); auto row = source.row ();
Q_EMIT reply (data (index (row, 0)).toString () Q_EMIT reply (item (row, 0)->data ().value<ClientKey> ()
, item (row, 1)->data ().toTime () , item (row, 1)->data ().toTime ()
, item (row, 2)->data ().toInt () , item (row, 2)->data ().toInt ()
, item (row, 3)->data ().toFloat () , item (row, 3)->data ().toFloat ()

View File

@ -5,8 +5,6 @@
#include "MessageServer.hpp" #include "MessageServer.hpp"
using Frequency = MessageServer::Frequency;
class QTime; class QTime;
class QString; class QString;
class QModelIndex; class QModelIndex;
@ -28,16 +26,18 @@ class DecodesModel
{ {
Q_OBJECT; Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public: public:
explicit DecodesModel (QObject * parent = nullptr); explicit DecodesModel (QObject * parent = nullptr);
Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time Q_SLOT void add_decode (bool is_new, ClientKey const&, QTime, 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, bool off_air, bool is_fast); , bool low_confidence, bool off_air, bool is_fast);
Q_SLOT void decodes_cleared (QString const& client_id); Q_SLOT void decodes_cleared (ClientKey const&);
Q_SLOT void do_reply (QModelIndex const& source, quint8 modifiers); Q_SLOT void do_reply (QModelIndex const& source, quint8 modifiers);
Q_SIGNAL void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency Q_SIGNAL void reply (ClientKey const&, QTime, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers); , QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
}; };

View File

@ -2,6 +2,8 @@
#include <QtWidgets> #include <QtWidgets>
#include <QDateTime> #include <QDateTime>
#include <QNetworkInterface>
#include <QSet>
#include "DecodesModel.hpp" #include "DecodesModel.hpp"
#include "BeaconsModel.hpp" #include "BeaconsModel.hpp"
@ -37,8 +39,10 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
, decodes_model_ {new DecodesModel {this}} , decodes_model_ {new DecodesModel {this}}
, beacons_model_ {new BeaconsModel {this}} , beacons_model_ {new BeaconsModel {this}}
, server_ {new MessageServer {this}} , server_ {new MessageServer {this}}
, multicast_group_line_edit_ {new QLineEdit} , port_spin_box_ {new QSpinBox {this}}
, log_table_view_ {new QTableView} , multicast_group_line_edit_ {new QLineEdit {this}}
, network_interfaces_combo_box_ {new CheckableItemComboBox {this}}
, log_table_view_ {new QTableView {this}}
, add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}} , add_call_of_interest_action_ {new QAction {tr ("&Add callsign"), this}}
, delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}} , delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}}
, last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}} , last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}}
@ -68,16 +72,71 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
auto central_layout = new QVBoxLayout; auto central_layout = new QVBoxLayout;
// server details // server details
auto port_spin_box = new QSpinBox; port_spin_box_->setMinimum (1);
port_spin_box->setMinimum (1); port_spin_box_->setMaximum (std::numeric_limits<port_type>::max ());
port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
auto group_box_layout = new QFormLayout; auto group_box_layout = new QFormLayout;
group_box_layout->addRow (tr ("Port number:"), port_spin_box); group_box_layout->addRow (tr ("Port number:"), port_spin_box_);
group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_); group_box_layout->addRow (tr ("Multicast Group (blank for unicast server):"), multicast_group_line_edit_);
group_box_layout->addRow (tr ("Network interfaces:"), network_interfaces_combo_box_);
int row;
QFormLayout::ItemRole role;
group_box_layout->getWidgetPosition (network_interfaces_combo_box_, &row, &role);
Q_ASSERT (row >= 0);
network_interfaces_form_label_widget_ = static_cast<QLabel *> (group_box_layout->itemAt (row, QFormLayout::LabelRole)->widget ());
network_interfaces_form_label_widget_->hide ();
network_interfaces_form_label_widget_->buddy ()->hide ();
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] {
if (multicast_group_line_edit_->text ().size ())
{
network_interfaces_form_label_widget_->show ();
network_interfaces_form_label_widget_->buddy ()->show ();
}
else
{
network_interfaces_form_label_widget_->hide ();
network_interfaces_form_label_widget_->buddy ()->hide ();
}
});
auto group_box = new QGroupBox {tr ("Server Details")}; auto group_box = new QGroupBox {tr ("Server Details")};
group_box->setLayout (group_box_layout); group_box->setLayout (group_box_layout);
central_layout->addWidget (group_box); central_layout->addWidget (group_box);
// populate network interface list
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsRunning | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
loopback_interface_name_ = net_if.name ();
}
auto item = network_interfaces_combo_box_->addCheckItem (net_if.humanReadableName ()
, net_if.name ()
, Qt::Unchecked);
auto tip = QString {"name(index): %1(%2) - %3"}
.arg (net_if.name ()).arg (net_if.index ())
.arg (net_if.flags () & QNetworkInterface::IsUp ? "Up" : "Down");
auto hw_addr = net_if.hardwareAddress ();
if (hw_addr.size ())
{
tip += QString {"\nhw: %1"}.arg (net_if.hardwareAddress ());
}
auto aes = net_if.addressEntries ();
if (aes.size ())
{
tip += "\naddresses:";
for (auto const& ae : aes)
{
tip += QString {"\n ip: %1/%2"}.arg (ae.ip ().toString ()).arg (ae.prefixLength ());
}
}
item->setToolTip (tip);
}
}
connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, this, &MessageAggregatorMainWindow::validate_network_interfaces);
validate_network_interfaces (QString {});
log_table_view_->setModel (log_); log_table_view_->setModel (log_);
log_table_view_->verticalHeader ()->hide (); log_table_view_->verticalHeader ()->hide ();
central_layout->addWidget (log_table_view_); central_layout->addWidget (log_table_view_);
@ -184,30 +243,52 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client); connect (server_, &MessageServer::client_closed, this, &MessageAggregatorMainWindow::remove_client);
connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::decodes_cleared); connect (server_, &MessageServer::client_closed, decodes_model_, &DecodesModel::decodes_cleared);
connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::decodes_cleared); connect (server_, &MessageServer::client_closed, beacons_model_, &BeaconsModel::decodes_cleared);
connect (server_, &MessageServer::decode, [this] (bool is_new, QString const& id, QTime time connect (server_, &MessageServer::decode, [this] (bool is_new, ClientKey const& key, QTime time
, qint32 snr, float delta_time , qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode , quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence , QString const& message, bool low_confidence
, bool off_air) { , bool off_air) {
decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message decodes_model_->add_decode (is_new, key, time, snr, delta_time
, low_confidence, off_air, dock_widgets_[id]->fast_mode ());}); , delta_frequency, mode, message
, low_confidence, off_air
, dock_widgets_[key]->fast_mode ());
});
connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot); connect (server_, &MessageServer::WSPR_decode, beacons_model_, &BeaconsModel::add_beacon_spot);
connect (server_, &MessageServer::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared); connect (server_, &MessageServer::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared);
connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared); connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared);
connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply); connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply);
// UI behaviour // UI behaviour
connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged) connect (port_spin_box_, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
, [this] (port_type port) {server_->start (port);}); , [this] (int /*port*/) {restart_server ();});
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () { connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] () {restart_server ();});
server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()}); connect (network_interfaces_combo_box_, &QComboBox::currentTextChanged, [this] () {restart_server ();});
});
port_spin_box->setValue (2237); // start up in unicast mode port_spin_box_->setValue (2237); // start up in unicast mode
show (); show ();
} }
void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call void MessageAggregatorMainWindow::restart_server ()
{
QSet<QString> net_ifs;
if (network_interfaces_combo_box_->isVisible ())
{
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
for (int row = 0; row < model->rowCount (); ++row)
{
if (Qt::Checked == model->item (row)->checkState ())
{
net_ifs << model->item (row)->data ().toString ();
}
}
}
server_->start (port_spin_box_->value ()
, QHostAddress {multicast_group_line_edit_->text ()}
, net_ifs);
}
void MessageAggregatorMainWindow::log_qso (ClientKey const& /*key*/, QDateTime time_off
, QString const& dx_call
, QString const& dx_grid, Frequency dial_frequency, QString const& mode , QString const& dx_grid, Frequency dial_frequency, QString const& mode
, QString const& report_sent, QString const& report_received , QString const& report_sent, QString const& report_received
, QString const& tx_power, QString const& comments , QString const& tx_power, QString const& comments
@ -240,9 +321,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
log_table_view_->scrollToBottom (); log_table_view_->scrollToBottom ();
} }
void MessageAggregatorMainWindow::add_client (QString const& id, QString const& version, QString const& revision) void MessageAggregatorMainWindow::add_client (ClientKey const& key, QString const& version, QString const& revision)
{ {
auto dock = new ClientWidget {decodes_model_, beacons_model_, id, version, revision, calls_of_interest_, this}; auto dock = new ClientWidget {decodes_model_, beacons_model_, key, version, revision, calls_of_interest_, this};
dock->setAttribute (Qt::WA_DeleteOnClose); dock->setAttribute (Qt::WA_DeleteOnClose);
auto view_action = dock->toggleViewAction (); auto view_action = dock->toggleViewAction ();
view_action->setEnabled (true); view_action->setEnabled (true);
@ -262,13 +343,13 @@ void MessageAggregatorMainWindow::add_client (QString const& id, QString const&
connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign); connect (dock, &ClientWidget::highlight_callsign, server_, &MessageServer::highlight_callsign);
connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration); connect (dock, &ClientWidget::switch_configuration, server_, &MessageServer::switch_configuration);
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure); connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
dock_widgets_[id] = dock; dock_widgets_[key] = dock;
server_->replay (id); // request decodes and status server_->replay (key); // request decodes and status
} }
void MessageAggregatorMainWindow::remove_client (QString const& id) void MessageAggregatorMainWindow::remove_client (ClientKey const& key)
{ {
auto iter = dock_widgets_.find (id); auto iter = dock_widgets_.find (key);
if (iter != std::end (dock_widgets_)) if (iter != std::end (dock_widgets_))
{ {
(*iter)->dispose (); (*iter)->dispose ();
@ -287,9 +368,35 @@ MessageAggregatorMainWindow::~MessageAggregatorMainWindow ()
void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg
, bool last_only) , bool last_only)
{ {
for (auto id : dock_widgets_.keys ()) for (auto key : dock_widgets_.keys ())
{ {
server_->highlight_callsign (id, call, bg, fg, last_only); server_->highlight_callsign (key, call, bg, fg, last_only);
}
}
void MessageAggregatorMainWindow::validate_network_interfaces (QString const& /*text*/)
{
auto model = static_cast<QStandardItemModel *> (network_interfaces_combo_box_->model ());
bool has_checked {false};
int loopback_row {-1};
for (int row = 0; row < model->rowCount (); ++row)
{
if (model->item (row)->data ().toString () == loopback_interface_name_)
{
loopback_row = row;
}
else if (Qt::Checked == model->item (row)->checkState ())
{
has_checked = true;
}
}
if (loopback_row >= 0)
{
if (!has_checked)
{
model->item (loopback_row)->setCheckState (Qt::Checked);
}
model->item (loopback_row)->setEnabled (has_checked);
} }
} }

View File

@ -6,6 +6,7 @@
#include <QString> #include <QString>
#include "MessageServer.hpp" #include "MessageServer.hpp"
#include "widgets/CheckableItemComboBox.hpp"
class QDateTime; class QDateTime;
class QStandardItemModel; class QStandardItemModel;
@ -16,6 +17,8 @@ class QLineEdit;
class QTableView; class QTableView;
class ClientWidget; class ClientWidget;
class QListWidget; class QListWidget;
class QLabel;
class QSpinBox;
using Frequency = MessageServer::Frequency; using Frequency = MessageServer::Frequency;
@ -24,11 +27,13 @@ class MessageAggregatorMainWindow
{ {
Q_OBJECT; Q_OBJECT;
using ClientKey = MessageServer::ClientKey;
public: public:
MessageAggregatorMainWindow (); MessageAggregatorMainWindow ();
~MessageAggregatorMainWindow (); ~MessageAggregatorMainWindow ();
Q_SLOT void log_qso (QString const& /*id*/, QDateTime time_off, QString const& dx_call, QString const& dx_grid Q_SLOT void log_qso (ClientKey const&, QDateTime time_off, QString const& dx_call, QString const& dx_grid
, Frequency dial_frequency, QString const& mode, QString const& report_sent , Frequency dial_frequency, QString const& mode, QString const& report_sent
, QString const& report_received, QString const& tx_power, QString const& comments , 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
@ -36,13 +41,15 @@ public:
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode); , QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
private: private:
void add_client (QString const& id, QString const& version, QString const& revision); void restart_server ();
void remove_client (QString const& id); void add_client (ClientKey const&, QString const& version, QString const& revision);
void remove_client (ClientKey const&);
void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {}, void change_highlighting (QString const& call, QColor const& bg = QColor {}, QColor const& fg = QColor {},
bool last_only = false); bool last_only = false);
Q_SLOT void validate_network_interfaces (QString const&);
// maps client id to widgets // maps client id to widgets
using ClientsDictionary = QHash<QString, ClientWidget *>; using ClientsDictionary = QHash<ClientKey, ClientWidget *>;
ClientsDictionary dock_widgets_; ClientsDictionary dock_widgets_;
QStandardItemModel * log_; QStandardItemModel * log_;
@ -50,7 +57,11 @@ private:
DecodesModel * decodes_model_; DecodesModel * decodes_model_;
BeaconsModel * beacons_model_; BeaconsModel * beacons_model_;
MessageServer * server_; MessageServer * server_;
QSpinBox * port_spin_box_;
QLineEdit * multicast_group_line_edit_; QLineEdit * multicast_group_line_edit_;
CheckableItemComboBox * network_interfaces_combo_box_;
QString loopback_interface_name_;
QLabel * network_interfaces_form_label_widget_;
QTableView * log_table_view_; QTableView * log_table_view_;
QListWidget * calls_of_interest_; QListWidget * calls_of_interest_;
QAction * add_call_of_interest_action_; QAction * add_call_of_interest_action_;

View File

@ -5,7 +5,6 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QUdpSocket> #include <QUdpSocket>
#include <QString>
#include <QTimer> #include <QTimer>
#include <QHash> #include <QHash>
@ -32,7 +31,6 @@ public:
: self_ {self} : self_ {self}
, version_ {version} , version_ {version}
, revision_ {revision} , revision_ {revision}
, port_ {0u}
, clock_ {new QTimer {this}} , clock_ {new QTimer {this}}
{ {
// register the required types with Qt // register the required types with Qt
@ -78,15 +76,14 @@ public:
MessageServer * self_; MessageServer * self_;
QString version_; QString version_;
QString revision_; QString revision_;
port_type port_;
QHostAddress multicast_group_address_; QHostAddress multicast_group_address_;
QSet<QString> network_interfaces_;
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint; static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
struct Client struct Client
{ {
Client () = default; Client () = default;
Client (QHostAddress const& sender_address, port_type const& sender_port) Client (port_type const& sender_port)
: sender_address_ {sender_address} : sender_port_ {sender_port}
, sender_port_ {sender_port}
, negotiated_schema_number_ {2} // not 1 because it's broken , negotiated_schema_number_ {2} // not 1 because it's broken
, last_activity_ {QDateTime::currentDateTime ()} , last_activity_ {QDateTime::currentDateTime ()}
{ {
@ -94,12 +91,11 @@ public:
Client (Client const&) = default; Client (Client const&) = default;
Client& operator= (Client const&) = default; Client& operator= (Client const&) = default;
QHostAddress sender_address_;
port_type sender_port_; port_type sender_port_;
quint32 negotiated_schema_number_; quint32 negotiated_schema_number_;
QDateTime last_activity_; QDateTime last_activity_;
}; };
QHash<QString, Client> clients_; // maps id to Client QHash<ClientKey, Client> clients_; // maps id to Client
QTimer * clock_; QTimer * clock_;
}; };
@ -109,56 +105,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
void MessageServer::impl::leave_multicast_group () void MessageServer::impl::leave_multicast_group ()
{ {
if (!multicast_group_address_.isNull () && BoundState == state () if (BoundState == state () && is_multicast_address (multicast_group_address_))
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
{ {
for (auto const& interface : QNetworkInterface::allInterfaces ()) for (auto const& if_name : network_interfaces_)
{ {
if (QNetworkInterface::CanMulticast & interface.flags ()) leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
{
leaveMulticastGroup (multicast_group_address_, interface);
}
} }
} }
} }
void MessageServer::impl::join_multicast_group () void MessageServer::impl::join_multicast_group ()
{ {
if (BoundState == state () if (BoundState == state () && is_multicast_address (multicast_group_address_))
&& !multicast_group_address_.isNull ()
#if QT_VERSION >= 0x050600
&& multicast_group_address_.isMulticast ()
#endif
)
{ {
auto mcast_iface = multicastInterface (); if (network_interfaces_.size ())
if (IPv4Protocol == multicast_group_address_.protocol ()
&& IPv4Protocol != localAddress ().protocol ())
{ {
close (); for (auto const& if_name : network_interfaces_)
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
}
bool joined {false};
for (auto const& interface : QNetworkInterface::allInterfaces ())
{
if (QNetworkInterface::CanMulticast & interface.flags ())
{ {
// Windows requires outgoing interface to match joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
// interface to be joined while joining, at least for
// IPv4 it seems to
setMulticastInterface (interface);
joined |= joinMulticastGroup (multicast_group_address_, interface);
} }
} }
if (!joined) else
{ {
multicast_group_address_.clear (); // find the loop-back interface and join on that
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
auto flags = QNetworkInterface::IsUp | QNetworkInterface::IsLoopBack | QNetworkInterface::CanMulticast;
if ((net_if.flags () & flags) == flags)
{
joinMulticastGroup (multicast_group_address_, net_if);
break;
}
}
} }
setMulticastInterface (mcast_iface);
} }
} }
@ -189,9 +168,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
auto id = in.id (); auto id = in.id ();
if (OK == check_status (in)) if (OK == check_status (in))
{ {
if (!clients_.contains (id)) auto client_key = ClientKey {sender, id};
if (!clients_.contains (client_key))
{ {
auto& client = (clients_[id] = {sender, sender_port}); auto& client = (clients_[client_key] = {sender_port});
QByteArray client_version; QByteArray client_version;
QByteArray client_revision; QByteArray client_revision;
@ -212,7 +192,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
<< version_.toUtf8 () << revision_.toUtf8 (); << version_.toUtf8 () << revision_.toUtf8 ();
if (impl::OK == check_status (hb)) if (impl::OK == check_status (hb))
{ {
writeDatagram (message, client.sender_address_, client.sender_port_); writeDatagram (message, client_key.first, sender_port);
} }
else else
{ {
@ -222,10 +202,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
// we don't care if this fails to read // we don't care if this fails to read
in >> client_version >> client_revision; in >> client_version >> client_revision;
} }
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version), Q_EMIT self_->client_opened (client_key, QString::fromUtf8 (client_version),
QString::fromUtf8 (client_revision)); QString::fromUtf8 (client_revision));
} }
clients_[id].last_activity_ = QDateTime::currentDateTime (); clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
// //
// message format is described in NetworkMessage.hpp // message format is described in NetworkMessage.hpp
@ -237,7 +217,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
break; break;
case NetworkMessage::Clear: case NetworkMessage::Clear:
Q_EMIT self_->decodes_cleared (id); Q_EMIT self_->decodes_cleared (client_key);
break; break;
case NetworkMessage::Status: case NetworkMessage::Status:
@ -268,7 +248,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name; >> fast_mode >> special_op_mode >> frequency_tolerance >> tr_period >> configuration_name;
if (check_status (in) != Fail) if (check_status (in) != Fail)
{ {
Q_EMIT self_->status_update (id, f, QString::fromUtf8 (mode), QString::fromUtf8 (dx_call) Q_EMIT self_->status_update (client_key, f, QString::fromUtf8 (mode)
, QString::fromUtf8 (dx_call)
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode) , QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
, tx_enabled, transmitting, decoding, rx_df, tx_df , tx_enabled, transmitting, decoding, rx_df, tx_df
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid) , QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
@ -296,7 +277,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> message >> low_confidence >> off_air; >> message >> low_confidence >> off_air;
if (check_status (in) != Fail) if (check_status (in) != Fail)
{ {
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency Q_EMIT self_->decode (is_new, client_key, time, snr, delta_time, delta_frequency
, QString::fromUtf8 (mode), QString::fromUtf8 (message) , QString::fromUtf8 (mode), QString::fromUtf8 (message)
, low_confidence, off_air); , low_confidence, off_air);
} }
@ -320,7 +301,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> off_air; >> off_air;
if (check_status (in) != Fail) if (check_status (in) != Fail)
{ {
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift Q_EMIT self_->WSPR_decode (is_new, client_key, time, snr, delta_time, frequency, drift
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid) , QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
, power, off_air); , power, off_air);
} }
@ -351,8 +332,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
>> exchange_sent >> exchange_rcvd >> prop_mode; >> exchange_sent >> exchange_rcvd >> prop_mode;
if (check_status (in) != Fail) if (check_status (in) != Fail)
{ {
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid) Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call)
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent) , QString::fromUtf8 (dx_grid)
, dial_frequency, QString::fromUtf8 (mode)
, QString::fromUtf8 (report_sent)
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power) , QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on , QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call) , QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
@ -363,8 +346,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
break; break;
case NetworkMessage::Close: case NetworkMessage::Close:
Q_EMIT self_->client_closed (id); Q_EMIT self_->client_closed (client_key);
clients_.remove (id); clients_.remove (client_key);
break; break;
case NetworkMessage::LoggedADIF: case NetworkMessage::LoggedADIF:
@ -373,7 +356,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
in >> ADIF; in >> ADIF;
if (check_status (in) != Fail) if (check_status (in) != Fail)
{ {
Q_EMIT self_->logged_ADIF (id, ADIF); Q_EMIT self_->logged_ADIF (client_key, ADIF);
} }
} }
break; break;
@ -406,7 +389,7 @@ void MessageServer::impl::tick ()
{ {
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse)) if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
{ {
Q_EMIT self_->clear_decodes (iter.key ()); Q_EMIT self_->decodes_cleared (iter.key ());
Q_EMIT self_->client_closed (iter.key ()); Q_EMIT self_->client_closed (iter.key ());
iter = clients_.erase (iter); // safe while iterating as doesn't rehash iter = clients_.erase (iter); // safe while iterating as doesn't rehash
} }
@ -448,152 +431,162 @@ MessageServer::MessageServer (QObject * parent, QString const& version, QString
{ {
} }
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address) void MessageServer::start (port_type port, QHostAddress const& multicast_group_address
, QSet<QString> const& network_interface_names)
{ {
if (port != m_->port_ // qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names;
|| multicast_group_address != m_->multicast_group_address_) if (port != m_->localPort ()
|| multicast_group_address != m_->multicast_group_address_
|| network_interface_names != m_->network_interfaces_)
{ {
m_->leave_multicast_group (); m_->leave_multicast_group ();
if (impl::BoundState == m_->state ()) if (impl::UnconnectedState != m_->state ())
{ {
m_->close (); m_->close ();
} }
m_->multicast_group_address_ = multicast_group_address; if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
auto address = m_->multicast_group_address_.isNull ()
|| impl::IPv4Protocol != m_->multicast_group_address_.protocol () ? QHostAddress::Any : QHostAddress::AnyIPv4;
if (port && m_->bind (address, port, m_->bind_mode_))
{ {
m_->port_ = port; Q_EMIT error ("Invalid multicast group address");
m_->join_multicast_group (); }
else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
{
Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
} }
else else
{ {
m_->port_ = 0; m_->multicast_group_address_ = multicast_group_address;
m_->network_interfaces_ = network_interface_names;
QHostAddress local_addr {is_multicast_address (multicast_group_address)
&& impl::IPv4Protocol == multicast_group_address.protocol () ? QHostAddress::AnyIPv4 : QHostAddress::Any};
if (port && m_->bind (local_addr, port, m_->bind_mode_))
{
m_->join_multicast_group ();
}
} }
} }
} }
void MessageServer::clear_decodes (QString const& id, quint8 window) void MessageServer::clear_decodes (ClientKey const& key, quint8 window)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Clear, key.second, (*iter).negotiated_schema_number_};
out << window; out << window;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time void MessageServer::reply (ClientKey const& key, QTime time, qint32 snr, float delta_time
, quint32 delta_frequency, QString const& mode , quint32 delta_frequency, QString const& mode
, QString const& message_text, bool low_confidence, quint8 modifiers) , QString const& message_text, bool low_confidence, quint8 modifiers)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Reply, key.second, (*iter).negotiated_schema_number_};
out << time << snr << delta_time << delta_frequency << mode.toUtf8 () out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
<< message_text.toUtf8 () << low_confidence << modifiers; << message_text.toUtf8 () << low_confidence << modifiers;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::replay (QString const& id) void MessageServer::replay (ClientKey const& key)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_};
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::close (QString const& id) void MessageServer::close (ClientKey const& key)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_};
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::halt_tx (QString const& id, bool auto_only) void MessageServer::halt_tx (ClientKey const& key, bool auto_only)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, key.second, (*iter).negotiated_schema_number_};
out << auto_only; out << auto_only;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::free_text (QString const& id, QString const& text, bool send) void MessageServer::free_text (ClientKey const& key, QString const& text, bool send)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::FreeText, key.second, (*iter).negotiated_schema_number_};
out << text.toUtf8 () << send; out << text.toUtf8 () << send;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::location (QString const& id, QString const& loc) void MessageServer::location (ClientKey const& key, QString const& loc)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Location, key.second, (*iter).negotiated_schema_number_};
out << loc.toUtf8 (); out << loc.toUtf8 ();
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::highlight_callsign (QString const& id, QString const& callsign void MessageServer::highlight_callsign (ClientKey const& key, QString const& callsign
, QColor const& bg, QColor const& fg, bool last_only) , QColor const& bg, QColor const& fg, bool last_only)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, key.second, (*iter).negotiated_schema_number_};
out << callsign.toUtf8 () << bg << fg << last_only; out << callsign.toUtf8 () << bg << fg << last_only;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name) void MessageServer::switch_configuration (ClientKey const& key, QString const& configuration_name)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, key.second, (*iter).negotiated_schema_number_};
out << configuration_name.toUtf8 (); out << configuration_name.toUtf8 ();
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }
void MessageServer::configure (QString const& id, QString const& mode, quint32 frequency_tolerance void MessageServer::configure (ClientKey const& key, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages) , QString const& dx_call, QString const& dx_grid, bool generate_messages)
{ {
auto iter = m_->clients_.find (id); auto iter = m_->clients_.find (key);
if (iter != std::end (m_->clients_)) if (iter != std::end (m_->clients_))
{ {
QByteArray message; QByteArray message;
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*iter).negotiated_schema_number_}; NetworkMessage::Builder out {&message, NetworkMessage::Configure, key.second, (*iter).negotiated_schema_number_};
out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df out << mode.toUtf8 () << frequency_tolerance << submode.toUtf8 () << fast_mode << tr_period << rx_df
<< dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages; << dx_call.toUtf8 () << dx_grid.toUtf8 () << generate_messages;
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_); m_->send_message (out, message, key.first, (*iter).sender_port_);
} }
} }

View File

@ -2,6 +2,9 @@
#define MESSAGE_SERVER_HPP__ #define MESSAGE_SERVER_HPP__
#include <QObject> #include <QObject>
#include <QPair>
#include <QString>
#include <QSet>
#include <QTime> #include <QTime>
#include <QDateTime> #include <QDateTime>
#include <QHostAddress> #include <QHostAddress>
@ -31,6 +34,7 @@ class UDP_EXPORT MessageServer
public: public:
using port_type = quint16; using port_type = quint16;
using Frequency = Radio::Frequency; using Frequency = Radio::Frequency;
using ClientKey = QPair<QHostAddress, QString>;
MessageServer (QObject * parent = nullptr, MessageServer (QObject * parent = nullptr,
QString const& version = QString {}, QString const& revision = QString {}); QString const& version = QString {}, QString const& revision = QString {});
@ -38,77 +42,77 @@ public:
// start or restart the server, if the multicast_group_address // start or restart the server, if the multicast_group_address
// argument is given it is assumed to be a multicast group address // argument is given it is assumed to be a multicast group address
// which the server will join // which the server will join
Q_SLOT void start (port_type port, Q_SLOT void start (port_type port
QHostAddress const& multicast_group_address = QHostAddress {}); , QHostAddress const& multicast_group_address = QHostAddress {}
, QSet<QString> const& network_interface_names = QSet<QString> {});
// ask the client to clear one or both of the decode windows // ask the client to clear one or both of the decode windows
Q_SLOT void clear_decodes (QString const& id, quint8 window = 0); Q_SLOT void clear_decodes (ClientKey const&, quint8 window = 0);
// ask the client with identification 'id' to make the same action // ask the client with identification 'id' to make the same action
// as a double click on the decode would // as a double click on the decode would
// //
// note that the client is not obliged to take any action and only // 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 // 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 Q_SLOT void reply (ClientKey const&, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
, QString const& mode, QString const& message, bool low_confidence, quint8 modifiers); , QString const& mode, QString const& message, bool low_confidence, quint8 modifiers);
// ask the client with identification 'id' to close down gracefully // ask the client to close down gracefully
Q_SLOT void close (QString const& id); Q_SLOT void close (ClientKey const&);
// ask the client with identification 'id' to replay all decodes // ask the client to replay all decodes
Q_SLOT void replay (QString const& id); Q_SLOT void replay (ClientKey const&);
// ask the client with identification 'id' to halt transmitting // ask the client to halt transmitting auto_only just disables auto
// auto_only just disables auto Tx, otherwise halt is immediate // Tx, otherwise halt is immediate
Q_SLOT void halt_tx (QString const& id, bool auto_only); Q_SLOT void halt_tx (ClientKey const&, bool auto_only);
// ask the client with identification 'id' to set the free text // ask the client to set the free text message and optionally send
// message and optionally send it ASAP // it ASAP
Q_SLOT void free_text (QString const& id, QString const& text, bool send); Q_SLOT void free_text (ClientKey const&, QString const& text, bool send);
// ask the client with identification 'id' to set the location provided // ask the client to set the location provided
Q_SLOT void location (QString const& id, QString const& location); Q_SLOT void location (ClientKey const&, QString const& location);
// ask the client with identification 'id' to highlight the callsign // ask the client to highlight the callsign specified with the given
// specified with the given colors // colors
Q_SLOT void highlight_callsign (QString const& id, QString const& callsign Q_SLOT void highlight_callsign (ClientKey const&, QString const& callsign
, QColor const& bg = QColor {}, QColor const& fg = QColor {} , QColor const& bg = QColor {}, QColor const& fg = QColor {}
, bool last_only = false); , bool last_only = false);
// ask the client with identification 'id' to switch to // ask the client to switch to configuration 'configuration_name'
// configuration 'configuration_name' Q_SLOT void switch_configuration (ClientKey const&, QString const& configuration_name);
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
// ask the client with identification 'id' to change configuration // ask the client to change configuration
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance Q_SLOT void configure (ClientKey const&, QString const& mode, quint32 frequency_tolerance
, QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df , QString const& submode, bool fast_mode, quint32 tr_period, quint32 rx_df
, QString const& dx_call, QString const& dx_grid, bool generate_messages); , QString const& dx_call, QString const& dx_grid, bool generate_messages);
// the following signals are emitted when a client broadcasts the // the following signals are emitted when a client broadcasts the
// matching message // matching message
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision); Q_SIGNAL void client_opened (ClientKey const&, QString const& version, QString const& revision);
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call Q_SIGNAL void status_update (ClientKey const&, Frequency, QString const& mode, QString const& dx_call
, QString const& report, QString const& tx_mode, bool tx_enabled , QString const& report, QString const& tx_mode, bool tx_enabled
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df , bool transmitting, bool decoding, quint32 rx_df, quint32 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 , bool watchdog_timeout, QString const& sub_mode, bool fast_mode
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period , quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
, QString const& configuration_name); , QString const& configuration_name);
Q_SIGNAL void client_closed (QString const& id); Q_SIGNAL void client_closed (ClientKey const&);
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time Q_SIGNAL void decode (bool is_new, ClientKey const&, 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, bool off_air); , bool low_confidence, bool off_air);
Q_SIGNAL void WSPR_decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time, Frequency Q_SIGNAL void WSPR_decode (bool is_new, ClientKey const&, QTime time, qint32 snr, float delta_time, Frequency
, qint32 drift, QString const& callsign, QString const& grid, qint32 power , qint32 drift, QString const& callsign, QString const& grid, qint32 power
, bool off_air); , bool off_air);
Q_SIGNAL void qso_logged (QString const& id, QDateTime time_off, QString const& dx_call, QString const& dx_grid Q_SIGNAL void qso_logged (ClientKey const&, QDateTime time_off, QString const& dx_call, QString const& dx_grid
, Frequency dial_frequency, QString const& mode, QString const& report_sent , Frequency dial_frequency, QString const& mode, QString const& report_sent
, QString const& report_received, QString const& tx_power, QString const& comments , 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 , QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode); , QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
Q_SIGNAL void decodes_cleared (QString const& id); Q_SIGNAL void decodes_cleared (ClientKey const&);
Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF); Q_SIGNAL void logged_ADIF (ClientKey const&, QByteArray const& ADIF);
// this signal is emitted when a network error occurs // this signal is emitted when a network error occurs
Q_SIGNAL void error (QString const&) const; Q_SIGNAL void error (QString const&) const;
@ -118,4 +122,6 @@ private:
pimpl<impl> m_; pimpl<impl> m_;
}; };
Q_DECLARE_METATYPE (MessageServer::ClientKey);
#endif #endif

View File

@ -17,9 +17,14 @@
#include <iostream> #include <iostream>
#include <exception> #include <exception>
#include <cstdlib>
#include <QCoreApplication> #include <QCoreApplication>
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QCommandLineOption>
#include <QString>
#include <QStringList>
#include <QNetworkInterface>
#include <QDateTime> #include <QDateTime>
#include <QTime> #include <QTime>
#include <QHash> #include <QHash>
@ -38,15 +43,17 @@ class Client
{ {
Q_OBJECT Q_OBJECT
using ClientKey = MessageServer::ClientKey;
public: public:
explicit Client (QString const& id, QObject * parent = nullptr) explicit Client (ClientKey const& key, QObject * parent = nullptr)
: QObject {parent} : QObject {parent}
, id_ {id} , key_ {key}
, dial_frequency_ {0u} , dial_frequency_ {0u}
{ {
} }
Q_SLOT void update_status (QString const& id, Frequency f, QString const& mode, QString const& /*dx_call*/ Q_SLOT void update_status (ClientKey const& key, Frequency f, QString const& mode, QString const& /*dx_call*/
, QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/ , QString const& /*report*/, QString const& /*tx_mode*/, bool /*tx_enabled*/
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/ , 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*/
@ -54,77 +61,85 @@ public:
, quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/ , quint8 /*special_op_mode*/, quint32 /*frequency_tolerance*/, quint32 /*tr_period*/
, QString const& /*configuration_name*/) , QString const& /*configuration_name*/)
{ {
if (id == id_) if (key == key_)
{ {
if (f != dial_frequency_) if (f != dial_frequency_)
{ {
std::cout << tr ("%1: Dial frequency changed to %2").arg (id_).arg (f).toStdString () << std::endl; std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Dial frequency changed to %1"}.arg (f).toStdString () << std::endl;
dial_frequency_ = f; dial_frequency_ = f;
} }
if (mode + sub_mode != mode_) if (mode + sub_mode != mode_)
{ {
std::cout << tr ("%1: Mode changed to %2").arg (id_).arg (mode + sub_mode).toStdString () << std::endl; std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Mode changed to %1"}.arg (mode + sub_mode).toStdString () << std::endl;
mode_ = mode + sub_mode; mode_ = mode + sub_mode;
} }
} }
} }
Q_SLOT void decode_added (bool is_new, QString const& client_id, QTime time, qint32 snr Q_SLOT void decode_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
, float delta_time, quint32 delta_frequency, QString const& mode , float delta_time, quint32 delta_frequency, QString const& mode
, QString const& message, bool low_confidence, bool off_air) , QString const& message, bool low_confidence, bool off_air)
{ {
if (client_id == id_) if (key == key_)
{ {
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
<< "Dt:" << delta_time << "Df:" << delta_frequency << "Dt:" << delta_time << "Df:" << delta_frequency
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high") << "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
<< "On air:" << !off_air; << "On air:" << !off_air;
std::cout << tr ("%1: Decoded %2").arg (id_).arg (message).toStdString () << std::endl; std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"Decoded %1"}.arg (message).toStdString () << std::endl;
} }
} }
Q_SLOT void beacon_spot_added (bool is_new, QString const& client_id, QTime time, qint32 snr Q_SLOT void beacon_spot_added (bool is_new, ClientKey const& key, QTime time, qint32 snr
, float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign , float delta_time, Frequency delta_frequency, qint32 drift, QString const& callsign
, QString const& grid, qint32 power, bool off_air) , QString const& grid, qint32 power, bool off_air)
{ {
if (client_id == id_) if (key == key_)
{ {
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
<< "Dt:" << delta_time << "Df:" << delta_frequency << "Dt:" << delta_time << "Df:" << delta_frequency
<< "drift:" << drift; << "drift:" << drift;
std::cout << tr ("%1: WSPR decode %2 grid %3 power: %4").arg (id_).arg (callsign).arg (grid).arg (power).toStdString () std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
<< QString {"WSPR decode %1 grid %2 power: %3"}
.arg (callsign).arg (grid).arg (power).toStdString ()
<< "On air:" << !off_air << std::endl; << "On air:" << !off_air << std::endl;
} }
} }
Q_SLOT void qso_logged (QString const&client_id, QDateTime time_off, QString const& dx_call, QString const& dx_grid Q_SLOT void qso_logged (ClientKey const& key, QDateTime time_off, QString const& dx_call, QString const& dx_grid
, Frequency dial_frequency, QString const& mode, QString const& report_sent , Frequency dial_frequency, QString const& mode, QString const& report_sent
, QString const& report_received, QString const& tx_power , QString const& report_received, QString const& tx_power
, QString const& comments, QString const& name, QDateTime time_on , QString const& comments, QString const& name, QDateTime time_on
, QString const& operator_call, QString const& my_call, QString const& my_grid , QString const& operator_call, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode) , QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode)
{ {
if (client_id == id_) if (key == key_)
{ {
qDebug () << "time_on:" << time_on << "time_off:" << time_off << "dx_call:" << dx_call << "grid:" << dx_grid 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 << "freq:" << dial_frequency << "mode:" << mode << "rpt_sent:" << report_sent
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments << "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call << "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
<< "my_grid:" << my_grid << "exchange_sent:" << exchange_sent << "my_grid:" << my_grid << "exchange_sent:" << exchange_sent
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode; << "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
std::cout << QByteArray {80, '-'}.data () << '\n'; 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 exchange_sent: %12 exchange_rcvd: %13 comments: %14 prop_mode: %15") std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
.arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received) << QString {"Logged %1 grid: %2 power: %3 sent: %4 recd: %5 freq: %6 time_off: %7 op: %8 my_call: %9 my_grid: %10 exchange_sent: %11 exchange_rcvd: %12 comments: %13 prop_mode: %14"}
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call) .arg (dx_call).arg (dx_grid).arg (tx_power)
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd) .arg (report_sent).arg (report_received)
.arg (comments).arg (prop_mode).toStdString () .arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
.arg (comments).arg (prop_mode).toStdString ()
<< std::endl; << std::endl;
} }
} }
Q_SLOT void logged_ADIF (QString const&client_id, QByteArray const& ADIF) Q_SLOT void logged_ADIF (ClientKey const& key, QByteArray const& ADIF)
{ {
if (client_id == id_) if (key == key_)
{ {
qDebug () << "ADIF:" << ADIF; qDebug () << "ADIF:" << ADIF;
std::cout << QByteArray {80, '-'}.data () << '\n'; std::cout << QByteArray {80, '-'}.data () << '\n';
@ -133,7 +148,7 @@ public:
} }
private: private:
QString id_; ClientKey key_;
Frequency dial_frequency_; Frequency dial_frequency_;
QString mode_; QString mode_;
}; };
@ -143,8 +158,10 @@ class Server
{ {
Q_OBJECT Q_OBJECT
using ClientKey = MessageServer::ClientKey;
public: public:
Server (port_type port, QHostAddress const& multicast_group) Server (port_type port, QHostAddress const& multicast_group, QStringList const& network_interface_names)
: server_ {new MessageServer {this}} : server_ {new MessageServer {this}}
{ {
// connect up server // connect up server
@ -154,21 +171,26 @@ public:
connect (server_, &MessageServer::client_opened, this, &Server::add_client); connect (server_, &MessageServer::client_opened, this, &Server::add_client);
connect (server_, &MessageServer::client_closed, this, &Server::remove_client); connect (server_, &MessageServer::client_closed, this, &Server::remove_client);
server_->start (port, multicast_group); #if QT_VERSION >= QT_VERSION_CHECK (5, 14, 0)
server_->start (port, multicast_group, QSet<QString> {network_interface_names.begin (), network_interface_names.end ()});
#else
server_->start (port, multicast_group, network_interface_names.toSet ());
#endif
} }
private: private:
void add_client (QString const& id, QString const& version, QString const& revision) void add_client (ClientKey const& key, QString const& version, QString const& revision)
{ {
auto client = new Client {id}; auto client = new Client {key};
connect (server_, &MessageServer::status_update, client, &Client::update_status); connect (server_, &MessageServer::status_update, client, &Client::update_status);
connect (server_, &MessageServer::decode, client, &Client::decode_added); connect (server_, &MessageServer::decode, client, &Client::decode_added);
connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added); connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added);
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged); connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF); connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
clients_[id] = client; clients_[key] = client;
server_->replay (id); server_->replay (key);
std::cout << "Discovered WSJT-X instance: " << id.toStdString (); std::cout << "Discovered WSJT-X instance: " << key.second.toStdString ()
<< '(' << key.first.toString ().toStdString () << ')';
if (version.size ()) if (version.size ())
{ {
std::cout << " v" << version.toStdString (); std::cout << " v" << version.toStdString ();
@ -180,23 +202,60 @@ private:
std::cout << std::endl; std::cout << std::endl;
} }
void remove_client (QString const& id) void remove_client (ClientKey const& key)
{ {
auto iter = clients_.find (id); auto iter = clients_.find (key);
if (iter != std::end (clients_)) if (iter != std::end (clients_))
{ {
clients_.erase (iter); clients_.erase (iter);
(*iter)->deleteLater (); (*iter)->deleteLater ();
} }
std::cout << "Removed WSJT-X instance: " << id.toStdString () << std::endl; std::cout << "Removed WSJT-X instance: " << key.second.toStdString ()
<< '(' << key.first.toString ().toStdString () << ')' << std::endl;
} }
MessageServer * server_; MessageServer * server_;
// maps client id to clients // maps client key to clients
QHash<QString, Client *> clients_; QHash<ClientKey, Client *> clients_;
}; };
void list_interfaces ()
{
for (auto const& net_if : QNetworkInterface::allInterfaces ())
{
if (net_if.flags () & QNetworkInterface::IsUp)
{
std::cout << net_if.humanReadableName ().toStdString () << ":\n"
" id: " << net_if.name ().toStdString () << " (" << net_if.index () << ")\n"
" addr: " << net_if.hardwareAddress ().toStdString () << "\n"
" flags: ";
if (net_if.flags () & QNetworkInterface::IsRunning)
{
std::cout << "Running ";
}
if (net_if.flags () & QNetworkInterface::CanBroadcast)
{
std::cout << "Broadcast ";
}
if (net_if.flags () & QNetworkInterface::CanMulticast)
{
std::cout << "Multicast ";
}
if (net_if.flags () & QNetworkInterface::IsLoopBack)
{
std::cout << "Loop-back ";
}
std::cout << "\n addresses:\n";
for (auto const& ae : net_if.addressEntries ())
{
std::cout << " " << ae.ip ().toString ().toStdString () << '\n';
}
std::cout << '\n';
}
}
}
#include "UDPDaemon.moc" #include "UDPDaemon.moc"
int main (int argc, char * argv[]) int main (int argc, char * argv[])
@ -217,6 +276,11 @@ int main (int argc, char * argv[])
auto help_option = parser.addHelpOption (); auto help_option = parser.addHelpOption ();
auto version_option = parser.addVersionOption (); auto version_option = parser.addVersionOption ();
QCommandLineOption list_option (QStringList {"l", "list-interfaces"},
app.translate ("UDPDaemon",
"Print the available network interfaces."));
parser.addOption (list_option);
QCommandLineOption port_option (QStringList {"p", "port"}, QCommandLineOption port_option (QStringList {"p", "port"},
app.translate ("UDPDaemon", app.translate ("UDPDaemon",
"Where <PORT> is the UDP service port number to listen on.\n" "Where <PORT> is the UDP service port number to listen on.\n"
@ -232,9 +296,25 @@ int main (int argc, char * argv[])
app.translate ("UDPDaemon", "GROUP")); app.translate ("UDPDaemon", "GROUP"));
parser.addOption (multicast_addr_option); parser.addOption (multicast_addr_option);
QCommandLineOption network_interface_option (QStringList {"i", "network-interface"},
app.translate ("UDPDaemon",
"Where <INTERFACE> is the network interface name to join on.\n"
"This option can be passed more than once to specify multiple network interfaces\n"
"The default is use just the loop back interface."),
app.translate ("UDPDaemon", "INTERFACE"));
parser.addOption (network_interface_option);
parser.process (app); parser.process (app);
Server server {static_cast<port_type> (parser.value (port_option).toUInt ()), QHostAddress {parser.value (multicast_addr_option)}}; if (parser.isSet (list_option))
{
list_interfaces ();
return EXIT_SUCCESS;
}
Server server {static_cast<port_type> (parser.value (port_option).toUInt ())
, QHostAddress {parser.value (multicast_addr_option).trimmed ()}
, parser.values (network_interface_option)};
return app.exec (); return app.exec ();
} }

View File

@ -0,0 +1,169 @@
Building WSJT-X on MS Windows
=============================
Here I describe my set up, you may have other preferences due to your
favoured file system layout for tools, utilities, and libraries. Many
variations are possible, use this guide as a template. The aim is to
provide an environment suitable for general development.
Prerequisite Tools and Libraries
================================
Here is an overview list, details are filled out below:
* Qt Open Source Framework
* Cmake build system generator
* FFTW v3 DFT library
* libusb usb device library
* MSYS2 *nix like command line environment
* Hamlib rig control library
* Pkg Config Lite
* Boost C++ libraries
Qt Framework
------------
At the time of writing I recommend using Qt v5.15.0 64-bit, v5.15.1
has a defect that affects us so is best avoided. You need the MinGW
version as we do not support the MSVC version due to lack of a
suitable FOSS Fortran complier. To install the Qt developer SDK you
should download the official Qt on-line installer, this allows you to
install one or more variants of the Qt SDK and also to maintain and
update the installation at a later date. There are many versions and
components within versions available, you only need the base 64-bit
MinGW framework for Qt v5.15.0 and the matching MinGW 8.1.0 64-bit
developer tools, other components can be unchecked within the on-line
installer. The default install location is C:\Qt which is fine, do
not attempt to move the location of the installed libraries if you
decide you want it elsewhere, the installer patches the libraries for
the installed location and it is easier to un-install and re-install
if you wish to change the installed location.
CMake
-----
Download and install a recent version from the official CMake web
site. A default installation is fine. I am currently using v3.18.3,
versions as old as v3.9 should work.
Other tools and libraries without installers
--------------------------------------------
For small libraries that don't have a Windows installer and change
often I locate them under a directory C:\Tools. This location is
arbitrary and just used to aggregate such items in one place, but this
document assumes that location so you may need to adjust paths etc. if
you use a different location.
FFTW v3 DFT library
-------------------
The MS Windows builds of FFTW3 can be downloaded from
http://www.fftw.org/install/windows.html and unzipped into C:\Tools.
You only need the 64-bit package.
libusb library
--------------
This library is available from https://libusb.info/, download the .7z
archive as the .zip archive does not contain the libraries we
require. Unzip this package into C:\Tools.
MSYS2
-----
This utility is available from https://www.msys2.org/. Follow the
download, installation, and initial upgrading instructions there. Once
installed and updated you will need to install some packages, these
are needed to provide the necessary *nix tools and utilities to build
Hamlib from sources.
pacman -S autoconf automake libtool make
Hamlib
------
Currently we statically link Hamlib to avoid clashes with
pre-installed DLLs that may be older versions than we support. Once
Hamlib v1.4 is officially released and commonly available we will move
to dynamic linking. Until then Hamlib must be built from
sources. There is a fork of the official Hamlib project which we keep
up to date with the official project master branch, we recommend
building from the 'integration' branch of that fork. The fork is a git
repository which can be cloned with this command:
mkdir -p ~/src/sf/bsomervi
cd !$
git clone git://git.code.sf.net/u/bsomervi/hamlib hamlib
cd hamlib
git checkout integration
Next you must build Hamlib using the MinGW compiler tools bundled with
Qt. As you will build Hamlib again when there are updates you should
set up you Msys2 command line environment for this. I use a
$HOME/.bash_profile file containing these lines:
dll_paths_64bit=/c/Tools/libusb-1.0.23/MinGW64/dll
export PATH=/c/Qt/Tools/mingw810_64/bin:$dll_paths_64bit:$PATH
Test the amended ~/.bash_profile file by opening a new Msys2 shell and
typing:
which gcc
which libusb-1.0.dll
The first time you checkout the Hamlib sources you must bootstrap the
configuration script, this is done with a script at the root of the
Hamlib sources:
cd ~/src/sf/bsomervi/hamlib
./bootstrap
Now you need to configure and build Hamlib from an Msys2 shell. Create
a build directory outside of the Hamlib sources you have just cloned,
then change working directory to that build directory.
mkdir -p ~/build/hamlib/release
cd !$
~/src/sf/bsomervi/hamlib/configure --disable-shared \
--prefix=$HOME/local/hamlib/mingw64/release \
CFLAGS="-DNDEBUG -g -O2 -fdata-sections -ffunction-sections -I/c/Tools/libusb-1.0.23/include" \
CXXFLAGS="-DNDEBUG -g -O2 -fdata-sections -ffunction-sections" \
LDFLAGS="-Wl,--gc-sections" \
LIBUSB_LIBS="-L/c/Tools/libusb-1.0.23/MinGW64/dll -lusb-1.0"
Then build and install the Hamlib package into a local directory:
make & make install-strip
If you wish you can make a debug configuration build of Hamlib which
can be useful if you intended to contribute to the Hamlib project, or
for tracking down issues:
mkdir -p ~/build/hamlib/debug
cd !$
~/src/sf/bsomervi/hamlib/configure --disable-shared \
--prefix=$HOME/local/hamlib/mingw64/debug \
CFLAGS="-g -O0 -I/c/Tools/libusb-1.0.23/include" \
CXXFLAGS="-g -O0" \
LIBUSB_LIBS="-L/c/Tools/libusb-1.0.23/MinGW64/dll -lusb-1.0"
make && make install
To update the Hamlib sources to the latest commit and rebuild:
cd ~/src/sf/bsomervi/hamlib
git pull
cd ~/build/hamlib/release
make & make install-strip
cd ~/build/hamlib/debug
make && make install
Pkg Config Lite
---------------
This package allows the WSJT-X CMake configuration to locate and learn
the options needed to consume the Hamlib package. You can download it
from https://sourceforge.net/projects/pkgconfiglite/files/0.28-1/ and
unzip it into a convenient location, as with other ancillary tools and
libraries I put these under C:\Tools\.

View File

@ -93,8 +93,8 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK] :sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK]
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice] :ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice]
:win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages] :win_openssl_packages: https://slproweb.com/products/Win32OpenSSL.html[Windows OpenSSL Packages]
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_1_1g.msi[Win32 OpenSSL Light Package] :win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_1_1h.msi[Win32 OpenSSL Light Package]
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1g.msi[Win64 OpenSSL Light Package] :win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1h.msi[Win64 OpenSSL Light Package]
:writelog: https://writelog.com/[Writelog] :writelog: https://writelog.com/[Writelog]
:wsjtx_group: https://groups.io/g/WSJTX[WSJTX Group] :wsjtx_group: https://groups.io/g/WSJTX[WSJTX Group]
:wsjtx: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X] :wsjtx: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]

BIN
lib/fsk4hf/.DS_Store vendored

Binary file not shown.

View File

@ -25,11 +25,11 @@ program jt9
integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, & integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, &
fhigh=4000,nrxfreq=1500,ndepth=1,nexp_decode=0,nQSOProg=0 fhigh=4000,nrxfreq=1500,ndepth=1,nexp_decode=0,nQSOProg=0
logical :: read_files = .true., tx9 = .false., display_help = .false., & logical :: read_files = .true., tx9 = .false., display_help = .false., &
bLowSidelobes = .false. bLowSidelobes = .false., nexp_decode_set = .false.
type (option) :: long_options(29) = [ & type (option) :: long_options(30) = [ &
option ('help', .false., 'h', 'Display this help message', ''), & option ('help', .false., 'h', 'Display this help message', ''), &
option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), & option ('shmem',.true.,'s','Use shared memory for sample data','KEY'), &
option ('tr-period', .true., 'p', 'Tx/Rx period, default SECONDS=60', & option ('tr-period', .true., 'p', 'Tx/Rx period, default SECONDS=60', &
'SECONDS'), & 'SECONDS'), &
option ('executable-path', .true., 'e', & option ('executable-path', .true., 'e', &
'Location of subordinate executables (KVASD) default PATH="."', & 'Location of subordinate executables (KVASD) default PATH="."', &
@ -46,6 +46,8 @@ program jt9
'Lowest JT9 frequency decoded, default HERTZ=2700', 'HERTZ'), & 'Lowest JT9 frequency decoded, default HERTZ=2700', 'HERTZ'), &
option ('rx-frequency', .true., 'f', & option ('rx-frequency', .true., 'f', &
'Receive frequency offset, default HERTZ=1500', 'HERTZ'), & 'Receive frequency offset, default HERTZ=1500', 'HERTZ'), &
option ('freq-tolerance', .true., 'F', &
'Receive frequency tolerance, default HERTZ=20', 'HERTZ'), &
option ('patience', .true., 'w', & option ('patience', .true., 'w', &
'FFTW3 planing patience (0-4), default PATIENCE=1', 'PATIENCE'), & 'FFTW3 planing patience (0-4), default PATIENCE=1', 'PATIENCE'), &
option ('fft-threads', .true., 'm', & option ('fft-threads', .true., 'm', &
@ -54,8 +56,8 @@ program jt9
option ('jt4', .false., '4', 'JT4 mode', ''), & option ('jt4', .false., '4', 'JT4 mode', ''), &
option ('ft4', .false., '5', 'FT4 mode', ''), & option ('ft4', .false., '5', 'FT4 mode', ''), &
option ('jt65', .false.,'6', 'JT65 mode', ''), & option ('jt65', .false.,'6', 'JT65 mode', ''), &
option ('fst4', .false., '7', 'FST4 mode', ''), & option ('fst4', .false., '7', 'FST4 mode', ''), &
option ('fst4w', .false., 'W', 'FST4W mode', ''), & option ('fst4w', .false., 'W', 'FST4W mode', ''), &
option ('ft8', .false., '8', 'FT8 mode', ''), & option ('ft8', .false., '8', 'FT8 mode', ''), &
option ('jt9', .false., '9', 'JT9 mode', ''), & option ('jt9', .false., '9', 'JT9 mode', ''), &
option ('qra64', .false., 'q', 'QRA64 mode', ''), & option ('qra64', .false., 'q', 'QRA64 mode', ''), &
@ -83,10 +85,11 @@ program jt9
iwspr=0 iwspr=0
nsubmode = 0 nsubmode = 0
ntol = 20
TRperiod=60.d0 TRperiod=60.d0
do do
call getopt('hs:e:a:b:r:m:p:d:f:w:t:987654WqTL:S:H:c:G:x:g:X:Q:', & call getopt('hs:e:a:b:r:m:p:d:f:F:w:t:987654WqTL:S:H:c:G:x:g:X:Q:', &
long_options,c,optarg,arglen,stat,offset,remain,.true.) long_options,c,optarg,arglen,stat,offset,remain,.true.)
if (stat .ne. 0) then if (stat .ne. 0) then
exit exit
@ -113,6 +116,8 @@ program jt9
read (optarg(:arglen), *) ndepth read (optarg(:arglen), *) ndepth
case ('f') case ('f')
read (optarg(:arglen), *) nrxfreq read (optarg(:arglen), *) nrxfreq
case ('F')
read (optarg(:arglen), *) ntol
case ('L') case ('L')
read (optarg(:arglen), *) flow read (optarg(:arglen), *) flow
case ('S') case ('S')
@ -153,6 +158,7 @@ program jt9
read (optarg(:arglen), *) hisgrid read (optarg(:arglen), *) hisgrid
case ('X') case ('X')
read (optarg(:arglen), *) nexp_decode read (optarg(:arglen), *) nexp_decode
nexp_decode_set = .true.
end select end select
end do end do
@ -195,6 +201,18 @@ program jt9
go to 999 go to 999
endif endif
if (mode .eq. 241) then
ntol = min (ntol, 100)
else if (mode .eq. 65 + 9) then
ntol = 20
else
ntol = min (ntol, 1000)
end if
if (.not. nexp_decode_set) then
if (mode .eq. 240 .or. mode .eq. 241) then
nexp_decode = 3 * 256 ! single decode off and nb=0
end if
end if
allocate(shared_data) allocate(shared_data)
nflatten=0 nflatten=0
do iarg = offset + 1, offset + remain do iarg = offset + 1, offset + remain
@ -258,7 +276,7 @@ program jt9
shared_data%params%nfa=flow shared_data%params%nfa=flow
shared_data%params%nfsplit=fsplit shared_data%params%nfsplit=fsplit
shared_data%params%nfb=fhigh shared_data%params%nfb=fhigh
shared_data%params%ntol=20 shared_data%params%ntol=ntol
shared_data%params%kin=64800 shared_data%params%kin=64800
if(mode.eq.240) shared_data%params%kin=720000 !### 60 s periods ### if(mode.eq.240) shared_data%params%kin=720000 !### 60 s periods ###
shared_data%params%nzhsym=nhsym shared_data%params%nzhsym=nhsym

20
lib/wsprd/wspr_params.f90 Normal file
View File

@ -0,0 +1,20 @@
parameter (NN=162)
parameter (NSPS0=8192) !Samples per symbol at 12000 S/s
parameter (NDOWN=32)
parameter (NSPS=NSPS0/NDOWN)
parameter (NZ=NSPS*NN) !Samples in waveform at 12000 S/s
parameter (NZ0=NSPS0*NN) !Samples in waveform at 375 S/s
parameter (NMAX=120*12000) !Samples in waveform at 375 S/s
! Define the sync vector:
integer*1 sync(162)
data sync/ &
1,1,0,0,0,0,0,0,1,0,0,0,1,1,1,0,0,0,1,0, &
0,1,0,1,1,1,1,0,0,0,0,0,0,0,1,0,0,1,0,1, &
0,0,0,0,0,0,1,0,1,1,0,0,1,1,0,1,0,0,0,1, &
1,0,1,0,0,0,0,1,1,0,1,0,1,0,1,0,1,0,0,1, &
0,0,1,0,1,1,0,0,0,1,1,0,1,0,1,0,0,0,1,0, &
0,0,0,0,1,0,0,1,0,0,1,1,1,0,1,1,0,0,1,1, &
0,1,0,0,0,1,1,1,0,0,0,0,0,1,0,1,0,0,1,1, &
0,0,0,0,0,0,0,1,1,0,1,0,1,1,0,0,0,1,1,0, &
0,0/

49
lib/wsprd/wspr_wav.f90 Normal file
View File

@ -0,0 +1,49 @@
subroutine wspr_wav(baud,xdt,h,f0,itone,snrdb,iwave)
! Generate iwave() from itone().
include 'wspr_params.f90'
integer itone(NN)
integer*2 iwave(NMAX)
real*8 twopi,dt,dphi0,dphi1,dphi,phi
real dat(NMAX)
twopi=8.d0*atan(1.d0)
dt=1.d0/12000.d0
baud=375.0/256.0
dat=0.
if(snrdb.lt.90) then
do i=1,NMAX
dat(i)=gran() !Generate gaussian noise
enddo
bandwidth_ratio=2500.0/6000.0
sig=sqrt(2*bandwidth_ratio)*10.0**(0.05*snrdb)
else
sig=1.0
endif
phi=0.d0
k=nint(xdt/dt)
do j=1,NN
dphi=twopi*(f0+h*(itone(j)-1.5)*baud)*dt
do i=1,NSPS0
k=k+1
phi=mod(phi+dphi,twopi)
if(k.gt.0 .and. k.le.NMAX) dat(k)=dat(k) + sig*sin(phi)
enddo
enddo
rms=100.0
if(snrdb.lt.90.0) then
dat=rms*dat;
if(maxval(abs(dat)).gt.32767.0) print*,"Warning - data will be clipped."
else
datpk=maxval(abs(dat))
fac=32767.9/datpk
dat=fac*dat
endif
iwave=nint(dat)
return
end subroutine wspr_wav

113
lib/wsprd/wsprsimf.f90 Normal file
View File

@ -0,0 +1,113 @@
!-------------------------------------------------------------------------------
!
! This file is part of the WSPR application, Weak Signal Propagation Reporter
!
!-------------------------------------------------------------------------------
program wsprsim
use wavhdr
include 'wspr_params.f90'
type(hdr) hwav
character arg*12,fname14*14,fname15*15
character*22 msg,msgsent
complex c0(0:NMAX/NDOWN-1)
complex c(0:NMAX/NDOWN-1)
integer itone(NN)
integer*2 iwave(NMAX)
real*8 fMHz
! Get command-line argument(s)
nargs=iargc()
if(nargs.ne.8) then
print*,'Usage: wsprsim "message" f0 DT fsp del nwav nfiles snr'
print*,'Example: wsprsim "K1ABC FN42 30" 50 0.0 0.1 1.0 1 10 -33'
go to 999
endif
call getarg(1,msg) !Message to be transmitted
call getarg(2,arg)
read(arg,*) f0 !Freq relative to WSPR-band center (Hz)
call getarg(3,arg)
read(arg,*) xdt !Time offset from nominal (s)
call getarg(4,arg)
read(arg,*) fspread !Watterson frequency spread (Hz)
call getarg(5,arg)
read(arg,*) delay !Watterson delay (ms)
call getarg(6,arg)
read(arg,*) nwav !1 for *.wav file, 0 for *.c2 file
call getarg(7,arg)
read(arg,*) nfiles !Number of files
call getarg(8,arg)
read(arg,*) snrdb !SNR_2500
twopi=8.0*atan(1.0)
fs=12000.0/NDOWN
dt=1.0/fs
tt=NSPS*dt
baud=12000.0/8192.0
txt=NZ*dt !Transmission length (s)
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
txt=NN*NSPS0/12000.0
call genwspr(msg,msgsent,itone) !Encode the message, get itone
write(*,1000) f0,xdt,txt,snrdb,fspread,delay,nfiles,msgsent
1000 format('f0:',f9.3,' DT:',f6.2,' txt:',f6.1,' SNR:',f6.1, &
' fspread:',f6.1,' delay:',f6.1,' nfiles:',i3,2x,a22)
! write(*,*) "Channel symbols: "
! write(*,'(162i2)') itone
h=1.0
phi=0.0
c0=0.
k=-1 + nint(xdt/dt)
do j=1,NN
dphi=-twopi*(f0+h*(itone(j)-1.5)*baud)*dt
do i=1,NSPS
k=k+1
phi=mod(phi+dphi,twopi)
if(k.ge.0 .and. k.lt.NMAX/NDOWN) c0(k)=cmplx(cos(phi),sin(phi))
enddo
enddo
call sgran()
do ifile=1,nfiles
c=c0
if(nwav.eq.0) then
if( fspread .ne. 0.0 .or. delay .ne. 0.0 ) then
call watterson(c,NMAX/NDOWN,NN*NSPS,fs,delay,fspread)
endif
c=c*sig
if(snrdb.lt.90) then
do i=0,NMAX/NDOWN-1 !Add gaussian noise at specified SNR
xnoise=gran()
ynoise=gran()
c(i)=c(i) + cmplx(xnoise,ynoise)
enddo
endif
write(fname14,1100) ifile
1100 format('000000_',i4.4,'.c2')
open(10,file=fname14,status='unknown',access='stream')
fMHz=10.1387d0
nmin=2
write(10) fname14,nmin,fMHz,c !Save to *.c2 file
close(10)
write(*,1108) ifile,xdt,f0,snrdb,fname14
1108 format(i4,f7.2,f8.2,f7.1,2x,a14)
else
freq=1500.0+f0
call wspr_wav(baud,xdt,h,freq,itone,snrdb,iwave)
hwav=default_header(12000,NMAX)
write(fname15,1102) ifile
1102 format('000000_',i4.4,'.wav')
open(10,file=fname15,status='unknown',access='stream')
write(10) hwav,iwave !Save to *.wav file
close(10)
write(*,1110) ifile,xdt,f0,snrdb,fname15
1110 format(i4,f7.2,f8.2,f7.1,2x,a15)
endif
enddo
999 end program wsprsim

View File

@ -9,6 +9,7 @@
#include <fftw3.h> #include <fftw3.h>
#include <QSharedMemory> #include <QSharedMemory>
#include <QProcessEnvironment>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QDateTime> #include <QDateTime>
#include <QLocale> #include <QLocale>
@ -107,6 +108,8 @@ int main(int argc, char *argv[])
// Multiple instances communicate with jt9 via this // Multiple instances communicate with jt9 via this
QSharedMemory mem_jt9; QSharedMemory mem_jt9;
auto const env = QProcessEnvironment::systemEnvironment ();
QApplication a(argc, argv); QApplication a(argc, argv);
try try
{ {
@ -421,7 +424,7 @@ int main(int argc, char *argv[])
} }
// run the application UI // run the application UI
MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash); MainWindow w(temp_dir, multiple, &multi_settings, &mem_jt9, downSampleFactor, &splash, env);
w.show(); w.show();
splash.raise (); splash.raise ();
QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit())); QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));

View File

@ -130,6 +130,42 @@ namespace std
} }
#endif #endif
inline
bool is_broadcast_address (QHostAddress const& host_addr)
{
#if QT_VERSION >= QT_VERSION_CHECK (5, 11, 0)
return host_addr.isBroadcast ();
#else
bool ok;
return host_addr.toIPv4Address (&ok) == 0xffffffffu && ok;
#endif
}
inline
bool is_multicast_address (QHostAddress const& host_addr)
{
#if QT_VERSION >= QT_VERSION_CHECK (5, 6, 0)
return host_addr.isMulticast ();
#else
bool ok;
return (((host_addr.toIPv4Address (&ok) & 0xf0000000u) == 0xe0000000u) && ok)
|| host_addr.toIPv6Address ()[0] == 0xff;
#endif
}
inline
bool is_MAC_ambiguous_multicast_address (QHostAddress const& host_addr)
{
// sub-ranges 224.128.0.0/24, 225.0.0.0/24, 225.128.0.0/24,
// 226.0.0.0/24, 226.128.0.0/24, ..., 239.0.0.0/24, 239.128.0.0/24
// are not supported as they are inefficient due to ambiguous
// mappings to Ethernet MAC addresses. 224.0.0.0/24 alone is allowed
// from these ranges
bool ok;
auto ipv4 = host_addr.toIPv4Address (&ok);
return ok && !((ipv4 & 0xffffff00u) == 0xe0000000) && (ipv4 & 0xf07fff00) == 0xe0000000;
}
// Register some useful Qt types with QMetaType // Register some useful Qt types with QMetaType
Q_DECLARE_METATYPE (QHostAddress); Q_DECLARE_METATYPE (QHostAddress);

View File

@ -131,6 +131,80 @@ private:
QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}}; QDateTime dt {QDate {2020, 8, 6}, QTime {14, 15, 22, 501}};
QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500))); QCOMPARE (qt_truncate_date_time_to (dt, 7500), QDateTime (QDate (2020, 8, 6), QTime (14, 15, 22, 500)));
} }
Q_SLOT void is_multicast_address_data ()
{
QTest::addColumn<QString> ("addr");
QTest::addColumn<bool> ("result");
QTest::newRow ("loopback") << "127.0.0.1" << false;
QTest::newRow ("looback IPv6") << "::1" << false;
QTest::newRow ("lowest-") << "223.255.255.255" << false;
QTest::newRow ("lowest") << "224.0.0.0" << true;
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
QTest::newRow ("lowest IPv6") << "ff00::" << true;
QTest::newRow ("highest") << "239.255.255.255" << true;
QTest::newRow ("highest+") << "240.0.0.0" << false;
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << true;
}
Q_SLOT void is_multicast_address ()
{
QFETCH (QString, addr);
QFETCH (bool, result);
QCOMPARE (::is_multicast_address (QHostAddress {addr}), result);
}
Q_SLOT void is_MAC_ambiguous_multicast_address_data ()
{
QTest::addColumn<QString> ("addr");
QTest::addColumn<bool> ("result");
QTest::newRow ("loopback") << "127.0.0.1" << false;
QTest::newRow ("looback IPv6") << "::1" << false;
QTest::newRow ("lowest- R1") << "223.255.255.255" << false;
QTest::newRow ("lowest R1") << "224.0.0.0" << false;
QTest::newRow ("highest R1") << "224.0.0.255" << false;
QTest::newRow ("highest+ R1") << "224.0.1.0" << false;
QTest::newRow ("lowest- R1A") << "224.127.255.255" << false;
QTest::newRow ("lowest R1A") << "224.128.0.0" << true;
QTest::newRow ("highest R1A") << "224.128.0.255" << true;
QTest::newRow ("highest+ R1A") << "224.128.1.0" << false;
QTest::newRow ("lowest- R2") << "224.255.255.255" << false;
QTest::newRow ("lowest R2") << "225.0.0.0" << true;
QTest::newRow ("highest R2") << "225.0.0.255" << true;
QTest::newRow ("highest+ R2") << "225.0.1.0" << false;
QTest::newRow ("lowest- R2A") << "225.127.255.255" << false;
QTest::newRow ("lowest R2A") << "225.128.0.0" << true;
QTest::newRow ("highest R2A") << "225.128.0.255" << true;
QTest::newRow ("highest+ R2A") << "225.128.1.0" << false;
QTest::newRow ("lowest- R3") << "238.255.255.255" << false;
QTest::newRow ("lowest R3") << "239.0.0.0" << true;
QTest::newRow ("highest R3") << "239.0.0.255" << true;
QTest::newRow ("highest+ R3") << "239.0.1.0" << false;
QTest::newRow ("lowest- R3A") << "239.127.255.255" << false;
QTest::newRow ("lowest R3A") << "239.128.0.0" << true;
QTest::newRow ("highest R3A") << "239.128.0.255" << true;
QTest::newRow ("highest+ R3A") << "239.128.1.0" << false;
QTest::newRow ("lowest- IPv6") << "feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
QTest::newRow ("lowest IPv6") << "ff00::" << false;
QTest::newRow ("highest") << "239.255.255.255" << false;
QTest::newRow ("highest+") << "240.0.0.0" << false;
QTest::newRow ("highest IPv6") << "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff" << false;
}
Q_SLOT void is_MAC_ambiguous_multicast_address ()
{
QFETCH (QString, addr);
QFETCH (bool, result);
QCOMPARE (::is_MAC_ambiguous_multicast_address (QHostAddress {addr}), result);
}
}; };
QTEST_MAIN (TestQtHelpers); QTEST_MAIN (TestQtHelpers);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
#include "CheckableItemComboBox.hpp"
#include <QStyledItemDelegate>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QLineEdit>
#include <QEvent>
#include <QListView>
class CheckableItemComboBoxStyledItemDelegate
: public QStyledItemDelegate
{
public:
explicit CheckableItemComboBoxStyledItemDelegate (QObject * parent = nullptr)
: QStyledItemDelegate {parent}
{
}
void paint (QPainter * painter, QStyleOptionViewItem const& option, QModelIndex const& index) const override
{
QStyleOptionViewItem& mutable_option = const_cast<QStyleOptionViewItem&> (option);
mutable_option.showDecorationSelected = false;
QStyledItemDelegate::paint (painter, mutable_option, index);
}
};
CheckableItemComboBox::CheckableItemComboBox (QWidget * parent)
: LazyFillComboBox {parent}
, model_ {new QStandardItemModel()}
{
setModel (model_.data ());
setEditable (true);
lineEdit ()->setReadOnly (true);
lineEdit ()->installEventFilter (this);
setItemDelegate (new CheckableItemComboBoxStyledItemDelegate {this});
connect (lineEdit(), &QLineEdit::selectionChanged, lineEdit(), &QLineEdit::deselect);
connect (static_cast<QListView *> (view ()), &QListView::pressed, this, &CheckableItemComboBox::item_pressed);
connect (model_.data (), &QStandardItemModel::dataChanged, this, &CheckableItemComboBox::model_data_changed);
}
QStandardItem * CheckableItemComboBox::addCheckItem (QString const& label, QVariant const& data
, Qt::CheckState checkState)
{
auto * item = new QStandardItem {label};
item->setCheckState (checkState);
item->setData (data);
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
model_->appendRow (item);
update_text ();
return item;
}
bool CheckableItemComboBox::eventFilter (QObject * object, QEvent * event)
{
if (object == lineEdit() && event->type () == QEvent::MouseButtonPress)
{
showPopup();
return true;
}
return false;
}
void CheckableItemComboBox::update_text()
{
QString text;
for (int i = 0; i < model_->rowCount (); ++i)
{
if (model_->item (i)->checkState () == Qt::Checked)
{
if (text.size ())
{
text+= ", ";
}
text += model_->item (i)->data ().toString ();
}
}
lineEdit ()->setText (text);
}
void CheckableItemComboBox::model_data_changed ()
{
update_text ();
}
void CheckableItemComboBox::item_pressed (QModelIndex const& index)
{
QStandardItem * item = model_->itemFromIndex (index);
item->setCheckState (item->checkState () == Qt::Checked ? Qt::Unchecked : Qt::Checked);
}
#include "widgets/moc_CheckableItemComboBox.cpp"

View File

@ -0,0 +1,37 @@
#ifndef CHECKABLE_ITEM_COMBO_BOX_HPP__
#define CHECKABLE_ITEM_COMBO_BOX_HPP__
#include <QScopedPointer>
#include "LazyFillComboBox.hpp"
class QStandardItemModel;
class QStandardItem;
/**
* @brief QComboBox with support of checkboxes
* http://stackoverflow.com/questions/8422760/combobox-of-checkboxes
*/
class CheckableItemComboBox
: public LazyFillComboBox
{
Q_OBJECT
public:
explicit CheckableItemComboBox (QWidget * parent = nullptr);
QStandardItem * addCheckItem (QString const& label, QVariant const& data, Qt::CheckState checkState);
protected:
bool eventFilter (QObject *, QEvent *) override;
private:
void update_text();
Q_SLOT void model_data_changed ();
Q_SLOT void item_pressed (QModelIndex const&);
private:
QScopedPointer<QStandardItemModel> model_;
};
#endif

View File

@ -10,7 +10,7 @@ class QWidget;
// //
// QComboBox derivative that signals show and hide of the pop up list. // QComboBox derivative that signals show and hide of the pop up list.
// //
class LazyFillComboBox final class LazyFillComboBox
: public QComboBox : public QComboBox
{ {
Q_OBJECT Q_OBJECT
@ -24,6 +24,7 @@ public:
{ {
} }
#if QT_VERSION >= QT_VERSION_CHECK (5, 12, 0)
void showPopup () override void showPopup () override
{ {
Q_EMIT about_to_show_popup (); Q_EMIT about_to_show_popup ();
@ -35,6 +36,19 @@ public:
QComboBox::hidePopup (); QComboBox::hidePopup ();
Q_EMIT popup_hidden (); Q_EMIT popup_hidden ();
} }
#else
void mousePressEvent (QMouseEvent * e) override
{
Q_EMIT about_to_show_popup ();
QComboBox::mousePressEvent (e);
}
void mouseReleaseEvent (QMouseEvent * e) override
{
QComboBox::mouseReleaseEvent (e);
Q_EMIT popup_hidden ();
}
#endif
}; };
#endif #endif

View File

@ -2,6 +2,8 @@
#ifndef ASTRO_H #ifndef ASTRO_H
#define ASTRO_H #define ASTRO_H
#include <utility>
#include <QDialog> #include <QDialog>
#include <QScopedPointer> #include <QScopedPointer>
@ -34,9 +36,17 @@ public:
Correction (Correction const&) = default; Correction (Correction const&) = default;
Correction& operator = (Correction const&) = default; Correction& operator = (Correction const&) = default;
// testing facility used to test Doppler corrections on
// terrestrial links
void reverse ()
{
std::swap (rx, tx);
}
FrequencyDelta rx; FrequencyDelta rx;
FrequencyDelta tx; FrequencyDelta tx;
}; };
Correction astroUpdate(QDateTime const& t, Correction astroUpdate(QDateTime const& t,
QString const& mygrid, QString const& mygrid,
QString const& hisgrid, QString const& hisgrid,

View File

@ -14,6 +14,7 @@
#include <QStringListModel> #include <QStringListModel>
#include <QSettings> #include <QSettings>
#include <QKeyEvent> #include <QKeyEvent>
#include <QProcessEnvironment>
#include <QSharedMemory> #include <QSharedMemory>
#include <QFileDialog> #include <QFileDialog>
#include <QTextBlock> #include <QTextBlock>
@ -234,8 +235,9 @@ namespace
MainWindow::MainWindow(QDir const& temp_directory, bool multiple, MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
MultiSettings * multi_settings, QSharedMemory *shdmem, MultiSettings * multi_settings, QSharedMemory *shdmem,
unsigned downSampleFactor, unsigned downSampleFactor,
QSplashScreen * splash, QWidget *parent) : QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) :
QMainWindow(parent), QMainWindow(parent),
m_env {env},
m_network_manager {this}, m_network_manager {this},
m_valid {true}, m_valid {true},
m_splash {splash}, m_splash {splash},
@ -267,6 +269,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_secBandChanged {0}, m_secBandChanged {0},
m_freqNominal {0}, m_freqNominal {0},
m_freqTxNominal {0}, m_freqTxNominal {0},
m_reverse_Doppler {"1" == env.value ("WSJT_REVERSE_DOPPLER", "0")},
m_s6 {0.}, m_s6 {0.},
m_tRemaining {0.}, m_tRemaining {0.},
m_TRperiod {60.0}, m_TRperiod {60.0},
@ -415,6 +418,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_messageClient {new MessageClient {QApplication::applicationName (), m_messageClient {new MessageClient {QApplication::applicationName (),
version (), revision (), version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (), m_config.udp_server_name (), m_config.udp_server_port (),
m_config.udp_interface_names (), m_config.udp_TTL (),
this}}, this}},
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()}, m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
m_manual {&m_network_manager}, m_manual {&m_network_manager},
@ -783,6 +787,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure); connect (&m_config, &Configuration::transceiver_failure, this, &MainWindow::handle_transceiver_failure);
connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server); connect (&m_config, &Configuration::udp_server_changed, m_messageClient, &MessageClient::set_server);
connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port); connect (&m_config, &Configuration::udp_server_port_changed, m_messageClient, &MessageClient::set_server_port);
connect (&m_config, &Configuration::udp_TTL_changed, m_messageClient, &MessageClient::set_TTL);
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable); connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
connect (&m_config, &Configuration::enumerating_audio_devices, [this] () { connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
showStatusMessage (tr ("Enumerating audio devices")); showStatusMessage (tr ("Enumerating audio devices"));
@ -927,9 +932,9 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
, "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ()) , "-a", QDir::toNativeSeparators (m_config.writeable_data_dir ().absolutePath ())
, "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ()) , "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ())
}; };
QProcessEnvironment env {QProcessEnvironment::systemEnvironment ()}; QProcessEnvironment new_env {m_env};
env.insert ("OMP_STACKSIZE", "4M"); new_env.insert ("OMP_STACKSIZE", "4M");
proc_jt9.setProcessEnvironment (env); proc_jt9.setProcessEnvironment (new_env);
proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () + proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () +
"jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered); "jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered);
@ -1813,6 +1818,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
checkMSK144ContestType(); checkMSK144ContestType();
if (m_config.my_callsign () != callsign) { if (m_config.my_callsign () != callsign) {
m_baseCall = Radio::base_callsign (m_config.my_callsign ()); m_baseCall = Radio::base_callsign (m_config.my_callsign ());
ui->tx1->setEnabled (elide_tx1_not_allowed () || ui->tx1->isEnabled ());
morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData()), morse_(const_cast<char *> (m_config.my_callsign ().toLatin1().constData()),
const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length()); const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length());
} }
@ -4228,7 +4234,15 @@ void MainWindow::guiUpdate()
transmitDisplay (true); transmitDisplay (true);
statusUpdate (); statusUpdate ();
} }
if(!m_btxok && m_btxok0 && g_iptt==1) stopTx(); if(!m_btxok && m_btxok0 && g_iptt==1)
{
stopTx();
if ("1" == m_env.value ("WSJT_TX_BOTH", "0"))
{
m_txFirst = !m_txFirst;
ui->txFirstCheckBox->setChecked (m_txFirst);
}
}
if(m_startAnother) { if(m_startAnother) {
if(m_mode=="MSK144") { if(m_mode=="MSK144") {
@ -4492,13 +4506,19 @@ void MainWindow::on_txrb1_toggled (bool status)
} }
} }
bool MainWindow::elide_tx1_not_allowed () const
{
auto const& my_callsign = m_config.my_callsign ();
return
(m_mode=="FT8" && SpecOp::HOUND == m_config.special_op_id())
|| ((m_mode.startsWith ("FT") || "MSK144" == m_mode || "Q65" == m_mode || "FST4" == m_mode)
&& Radio::is_77bit_nonstandard_callsign (my_callsign))
|| (my_callsign != m_baseCall && !shortList (my_callsign));
}
void MainWindow::on_txrb1_doubleClicked () void MainWindow::on_txrb1_doubleClicked ()
{ {
if(m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return; ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
// skip Tx1, only allowed if not a type 2 compound callsign
auto const& my_callsign = m_config.my_callsign ();
auto is_compound = my_callsign != m_baseCall;
ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ());
if (!ui->tx1->isEnabled ()) { if (!ui->tx1->isEnabled ()) {
// leave time for clicks to complete before setting txrb2 // leave time for clicks to complete before setting txrb2
QTimer::singleShot (500, ui->txrb2, SLOT (click ())); QTimer::singleShot (500, ui->txrb2, SLOT (click ()));
@ -4575,11 +4595,7 @@ void MainWindow::on_txb1_clicked()
void MainWindow::on_txb1_doubleClicked() void MainWindow::on_txb1_doubleClicked()
{ {
if (m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return; ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
// skip Tx1, only allowed if not a type 1 compound callsign
auto const& my_callsign = m_config.my_callsign ();
auto is_compound = my_callsign != m_baseCall;
ui->tx1->setEnabled ((is_compound && shortList (my_callsign)) || !ui->tx1->isEnabled ());
} }
void MainWindow::on_txb2_clicked() void MainWindow::on_txb2_clicked()
@ -4917,34 +4933,38 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
m_QSOProgress=ROGER_REPORT; m_QSOProgress=ROGER_REPORT;
} }
} else { // no grid on end of msg } else { // no grid on end of msg
QString r=message_words.at (3); auto const& word_3 = message_words.at (3);
if(m_QSOProgress >= ROGER_REPORT && (r=="RRR" || r.toInt()==73 || "RR73" == r)) { bool word_3_is_int;
if(m_mode=="FT4" and r=="RR73") m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc(); auto word_3_as_number = word_3.toInt (&word_3_is_int);
m_bTUmsg=false; if(m_QSOProgress >= ROGER_REPORT && ("RRR" == word_3
m_nextCall=""; //### Temporary: disable use of "TU;" message || (word_3_is_int && word_3_as_number == 73)
if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") { || "RR73" == word_3)) {
// We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message if(m_mode=="FT4" and "RR73" == word_3) m_dateTimeRcvdRR73=QDateTime::currentDateTimeUtc();
m_bTUmsg=false;
m_nextCall=""; //### Temporary: disable use of "TU;" message
if(SpecOp::RTTY == m_config.special_op_id() and m_nextCall!="") {
// We're in RTTY contest and have "nextCall" queued up: send a "TU; ..." message
logQSOTimer.start(0);
ui->tx3->setText(ui->tx3->text().remove("TU; "));
useNextCall();
QString t="TU; " + ui->tx3->text();
ui->tx3->setText(t);
m_bTUmsg=true;
} else {
if(SpecOp::RTTY == m_config.special_op_id()) {
logQSOTimer.start(0); logQSOTimer.start(0);
ui->tx3->setText(ui->tx3->text().remove("TU; ")); m_ntx=6;
useNextCall(); ui->txrb6->setChecked(true);
QString t="TU; " + ui->tx3->text();
ui->tx3->setText(t);
m_bTUmsg=true;
} else { } else {
if(SpecOp::RTTY == m_config.special_op_id()) { m_ntx=5;
logQSOTimer.start(0); ui->txrb5->setChecked(true);
m_ntx=6;
ui->txrb6->setChecked(true);
} else {
m_ntx=5;
ui->txrb5->setChecked(true);
}
} }
}
m_QSOProgress = SIGNOFF; m_QSOProgress = SIGNOFF;
} else if((m_QSOProgress >= REPORT } else if((m_QSOProgress >= REPORT
|| (m_QSOProgress >= REPLYING && || (m_QSOProgress >= REPLYING &&
(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4"))) (m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4")))
&& r.mid(0,1)=="R") { && word_3.startsWith ('R')) {
m_ntx=4; m_ntx=4;
m_QSOProgress = ROGERS; m_QSOProgress = ROGERS;
if(SpecOp::RTTY == m_config.special_op_id()) { if(SpecOp::RTTY == m_config.special_op_id()) {
@ -4953,23 +4973,30 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
if(nRpt>=529 and nRpt<=599) m_xRcvd=t[n-2] + " " + t[n-1]; if(nRpt>=529 and nRpt<=599) m_xRcvd=t[n-2] + " " + t[n-1];
} }
ui->txrb4->setChecked(true); ui->txrb4->setChecked(true);
} else if(m_QSOProgress>=CALLING and } else if (m_QSOProgress >= CALLING)
((r.toInt()>=-50 && r.toInt()<=49) or (r.toInt()>=529 && r.toInt()<=599))) { {
if(SpecOp::EU_VHF==m_config.special_op_id() or if (word_3_is_int
SpecOp::FIELD_DAY==m_config.special_op_id() or && ((word_3_as_number >= -50 && word_3_as_number <= 49)
SpecOp::RTTY==m_config.special_op_id()) { || (word_3_as_number >= 529 && word_3_as_number <= 599))) {
setTxMsg(2); if(SpecOp::EU_VHF==m_config.special_op_id() or
m_QSOProgress=REPORT; SpecOp::FIELD_DAY==m_config.special_op_id() or
} else { SpecOp::RTTY==m_config.special_op_id()) {
if(r.left(2)=="R-" or r.left(2)=="R+") { setTxMsg(2);
setTxMsg(4); m_QSOProgress=REPORT;
m_QSOProgress=ROGERS; }
else {
setTxMsg (3);
m_QSOProgress = ROGER_REPORT;
}
} else { } else {
setTxMsg(3); if (word_3.startsWith ("R-") || word_3.startsWith ("R+")) {
m_QSOProgress=ROGER_REPORT; setTxMsg(4);
m_QSOProgress=ROGERS;
} }
} }
} else { // nothing for us }
else
{ // nothing for us
return; return;
} }
} }
@ -7424,7 +7451,7 @@ Type 1 Suffixes: /0 /1 /2 /3 /4 /5 /6 /7 /8 /9 /A /P)", {"Courier", 10}});
m_prefixes->raise (); m_prefixes->raise ();
} }
bool MainWindow::shortList(QString callsign) bool MainWindow::shortList(QString callsign) const
{ {
int n=callsign.length(); int n=callsign.length();
int i1=callsign.indexOf("/"); int i1=callsign.indexOf("/");
@ -7669,13 +7696,21 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
QString format_string {"%1 %2 %3 %4 %5 %6"}; QString format_string {"%1 %2 %3 %4 %5 %6"};
auto const& time_string = time.toString ("~" == mode || "&" == mode auto const& time_string = time.toString ("~" == mode || "&" == mode
|| "+" == mode ? "hhmmss" : "hhmm"); || "+" == mode ? "hhmmss" : "hhmm");
auto text = message_text;
auto ap_pos = text.lastIndexOf (QRegularExpression {R"((?:\?\s)?a[0-9]$)"});
if (ap_pos >= 0)
{
// beware of decodes ending on shorter version of wanted call so
// add a space
text = text.left (ap_pos).trimmed () + ' ';
}
auto message_line = format_string auto message_line = format_string
.arg (time_string) .arg (time_string)
.arg (snr, 3) .arg (snr, 3)
.arg (delta_time, 4, 'f', 1) .arg (delta_time, 4, 'f', 1)
.arg (delta_frequency, 4) .arg (delta_frequency, 4)
.arg (mode, -2) .arg (mode, -2)
.arg (message_text); .arg (text);
QTextCursor start {ui->decodedTextBrowser->document ()}; QTextCursor start {ui->decodedTextBrowser->document ()};
start.movePosition (QTextCursor::End); start.movePosition (QTextCursor::End);
auto cursor = ui->decodedTextBrowser->document ()->find (message_line, start, QTextDocument::FindBackward); auto cursor = ui->decodedTextBrowser->document ()->find (message_line, start, QTextDocument::FindBackward);
@ -7688,7 +7723,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
.arg ('-' + QString::number (delta_time, 'f', 1), 4) .arg ('-' + QString::number (delta_time, 'f', 1), 4)
.arg (delta_frequency, 4) .arg (delta_frequency, 4)
.arg (mode, -2) .arg (mode, -2)
.arg (message_text), start, QTextDocument::FindBackward); .arg (text), start, QTextDocument::FindBackward);
} }
if (!cursor.isNull ()) if (!cursor.isNull ())
{ {
@ -7703,7 +7738,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
showNormal (); showNormal ();
raise (); raise ();
} }
if (message_text.contains (QRegularExpression {R"(^(CQ |CQDX |QRZ ))"})) { if (text.contains (QRegularExpression {R"(^(CQ |CQDX |QRZ ))"})) {
// a message we are willing to accept and auto reply to // a message we are willing to accept and auto reply to
m_bDoubleClicked = true; m_bDoubleClicked = true;
} }
@ -7817,7 +7852,7 @@ void MainWindow::networkError (QString const& e)
, MessageBox::Cancel)) , MessageBox::Cancel))
{ {
// retry server lookup // retry server lookup
m_messageClient->set_server (m_config.udp_server_name ()); m_messageClient->set_server (m_config.udp_server_name (), m_config.udp_interface_names ());
} }
} }
@ -8186,6 +8221,10 @@ void MainWindow::astroUpdate ()
correction.tx = correction.tx / 10 * 10; correction.tx = correction.tx / 10 * 10;
} }
m_astroCorrection = correction; m_astroCorrection = correction;
if (m_reverse_Doppler)
{
m_astroCorrection.reverse ();
}
} }
else else
{ {
@ -9110,15 +9149,15 @@ void MainWindow::write_all(QString txRx, QString message)
QString msg; QString msg;
QString mode_string; QString mode_string;
if (message[4]==' ') { if (message.size () > 5 && message[4]==' ') {
msg=message.mid(4,-1); msg=message.mid(4,-1);
} else { } else {
msg=message.mid(6,-1); msg=message.mid(6,-1);
} }
if (message[19]=='#') { if (message.size () > 19 && message[19]=='#') {
mode_string="JT65 "; mode_string="JT65 ";
} else if (message[19]=='@') { } else if (message.size () > 19 && message[19]=='@') {
mode_string="JT9 "; mode_string="JT9 ";
} else { } else {
mode_string=m_mode.leftJustified(6,' '); mode_string=m_mode.leftJustified(6,' ');

View File

@ -64,6 +64,7 @@ namespace Ui {
class MainWindow; class MainWindow;
} }
class QProcessEnvironment;
class QSharedMemory; class QSharedMemory;
class QSplashScreen; class QSplashScreen;
class QSettings; class QSettings;
@ -104,7 +105,7 @@ public:
explicit MainWindow(QDir const& temp_directory, bool multiple, MultiSettings *, explicit MainWindow(QDir const& temp_directory, bool multiple, MultiSettings *,
QSharedMemory *shdmem, unsigned downSampleFactor, QSharedMemory *shdmem, unsigned downSampleFactor,
QSplashScreen *, QSplashScreen *, QProcessEnvironment const&,
QWidget *parent = nullptr); QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
@ -356,7 +357,9 @@ private:
void foxTest(); void foxTest();
void setColorHighlighting(); void setColorHighlighting();
void chkFT4(); void chkFT4();
bool elide_tx1_not_allowed () const;
QProcessEnvironment const& m_env;
NetworkAccessManager m_network_manager; NetworkAccessManager m_network_manager;
bool m_valid; bool m_valid;
QSplashScreen * m_splash; QSplashScreen * m_splash;
@ -408,6 +411,7 @@ private:
Frequency m_freqNominal; Frequency m_freqNominal;
Frequency m_freqTxNominal; Frequency m_freqTxNominal;
Astro::Correction m_astroCorrection; Astro::Correction m_astroCorrection;
bool m_reverse_Doppler;
double m_s6; double m_s6;
double m_tRemaining; double m_tRemaining;
@ -707,7 +711,7 @@ private:
void stub(); void stub();
void statusChanged(); void statusChanged();
void fixStop(); void fixStop();
bool shortList(QString callsign); bool shortList(QString callsign) const;
void transmit (double snr = 99.); void transmit (double snr = 99.);
void rigFailure (QString const& reason); void rigFailure (QString const& reason);
void pskSetLocal (); void pskSetLocal ();

View File

@ -10,7 +10,9 @@ SOURCES += \
widgets/AbstractLogWindow.cpp \ widgets/AbstractLogWindow.cpp \
widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.cpp \ widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.cpp \
widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp \ widgets/FoxLogWindow.cpp widgets/CabrilloLogWindow.cpp \
widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp widgets/HelpTextWindow.cpp widgets/RestrictedSpinBox.cpp \
widgets/LazyFillComboBox.cpp widgets/CheckableItemComboBox.cpp
HEADERS += \ HEADERS += \
widgets/mainwindow.h widgets/plotter.h \ widgets/mainwindow.h widgets/plotter.h \
widgets/about.h widgets/widegraph.h \ widgets/about.h widgets/widegraph.h \
@ -22,7 +24,8 @@ HEADERS += \
widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \ widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \
widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \ widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \
widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \ widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \
widgets/RestrictedSpinBox.hpp widgets/RestrictedSpinBox.hpp \
widgets/LazyFillComboBox.hpp widgets/CheckableItemComboBox.hpp
FORMS += \ FORMS += \
widgets/mainwindow.ui widgets/about.ui \ widgets/mainwindow.ui widgets/about.ui \