mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-23 04:38:37 -05:00
Merge branch 'develop' into feat-boost-log
This commit is contained in:
commit
9ea903b259
@ -235,6 +235,7 @@ set (wsjt_qt_CXXSRCS
|
||||
logbook/Multiplier.cpp
|
||||
Network/NetworkAccessManager.cpp
|
||||
widgets/LazyFillComboBox.cpp
|
||||
widgets/CheckableItemComboBox.cpp
|
||||
)
|
||||
|
||||
set (wsjt_qtmm_CXXSRCS
|
||||
@ -1158,6 +1159,11 @@ target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (fst4sim lib/fst4/fst4sim.f90)
|
||||
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)
|
||||
target_link_libraries (ldpcsim240_101 wsjt_fort wsjt_cxx)
|
||||
@ -1323,7 +1329,7 @@ generate_version_info (jt9_VERSION_RESOURCES
|
||||
NAME jt9
|
||||
BUNDLE ${PROJECT_BUNDLE_NAME}
|
||||
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)
|
||||
@ -1415,7 +1421,7 @@ else ()
|
||||
)
|
||||
if (WIN32)
|
||||
set_target_properties (wsjtx PROPERTIES
|
||||
LINK_FLAGS -Wl,--stack,16777216
|
||||
LINK_FLAGS -Wl,--stack,0x1000000,--heap,0x20000000
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
@ -1468,7 +1474,7 @@ add_executable (message_aggregator
|
||||
${message_aggregator_RESOURCES_RCC}
|
||||
${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)
|
||||
set_target_properties (message_aggregator PROPERTIES WIN32_EXECUTABLE ON)
|
||||
|
@ -163,6 +163,10 @@
|
||||
#include <QFontDialog>
|
||||
#include <QSerialPortInfo>
|
||||
#include <QScopedPointer>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QHostAddress>
|
||||
#include <QStandardItem>
|
||||
#include <QDebug>
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
@ -440,6 +444,12 @@ private:
|
||||
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
|
||||
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 initialize_models ();
|
||||
@ -493,6 +503,8 @@ private:
|
||||
Q_SLOT void on_add_macro_line_edit_editingFinished ();
|
||||
Q_SLOT void delete_macro ();
|
||||
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_azel_path_select_push_button_clicked (bool);
|
||||
Q_SLOT void on_calibration_intercept_spin_box_valueChanged (double);
|
||||
@ -642,7 +654,12 @@ private:
|
||||
bool use_dynamic_grid_;
|
||||
QString opCall_;
|
||||
QString udp_server_name_;
|
||||
bool udp_server_name_edited_;
|
||||
int dns_lookup_id_;
|
||||
port_type udp_server_port_;
|
||||
QStringList udp_interface_names_;
|
||||
QString loopback_interface_name_;
|
||||
int udp_TTL_;
|
||||
QString n1mm_server_name_;
|
||||
port_type n1mm_server_port_;
|
||||
bool broadcast_to_n1mm_;
|
||||
@ -742,6 +759,8 @@ QString Configuration::opCall() const {return m_->opCall_;}
|
||||
void Configuration::opCall (QString const& call) {m_->opCall_ = call;}
|
||||
QString Configuration::udp_server_name () const {return m_->udp_server_name_;}
|
||||
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_;}
|
||||
QString Configuration::n1mm_server_name () const {return m_->n1mm_server_name_;}
|
||||
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}
|
||||
, degrade_ {0.} // initialize to zero each run, not
|
||||
// saved in settings
|
||||
, udp_server_name_edited_ {false}
|
||||
, dns_lookup_id_ {-1}
|
||||
{
|
||||
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
|
||||
read_settings ();
|
||||
|
||||
// set up dynamic loading of audio devices
|
||||
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
|
||||
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
|
||||
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 ();
|
||||
});
|
||||
|
||||
// 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
|
||||
connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
|
||||
ui_->LotW_CSV_fetch_push_button->setEnabled (true);
|
||||
});
|
||||
lotw_users_.set_local_file_path (writeable_data_dir_.absoluteFilePath ("lotw-user-activity.csv"));
|
||||
|
||||
// load the dictionary if it exists
|
||||
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text (), false);
|
||||
// load the dictionary if it exists, fetch and load if it doesn't
|
||||
lotw_users_.load (ui_->LotW_CSV_URL_line_edit->text ());
|
||||
|
||||
//
|
||||
// validation
|
||||
@ -1316,7 +1346,14 @@ void Configuration::impl::initialize_models ()
|
||||
ui_->CAT_poll_interval_spin_box->setValue (rig_params_.poll_interval);
|
||||
ui_->opCallEntry->setText (opCall_);
|
||||
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_);
|
||||
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_->n1mm_server_name_line_edit->setText (n1mm_server_name_);
|
||||
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> ();
|
||||
opCall_ = settings_->value ("OpCall", "").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 ();
|
||||
n1mm_server_name_ = settings_->value ("N1MMServer", "127.0.0.1").toString ();
|
||||
n1mm_server_port_ = settings_->value ("N1MMServerPort", 2333).toUInt ();
|
||||
@ -1622,6 +1661,8 @@ void Configuration::impl::write_settings ()
|
||||
settings_->setValue ("OpCall", opCall_);
|
||||
settings_->setValue ("UDPServer", udp_server_name_);
|
||||
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 ("N1MMServerPort", n1mm_server_port_);
|
||||
settings_->setValue ("BroadcastToN1MM", broadcast_to_n1mm_);
|
||||
@ -1824,6 +1865,12 @@ bool Configuration::impl::validate ()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dns_lookup_id_ > -1)
|
||||
{
|
||||
MessageBox::information_message (this, tr ("Pending DNS lookup, please try again later"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2042,20 +2089,30 @@ void Configuration::impl::accept ()
|
||||
pwrBandTxMemory_ = ui_->checkBoxPwrBandTxMemory->isChecked ();
|
||||
pwrBandTuneMemory_ = ui_->checkBoxPwrBandTuneMemory->isChecked ();
|
||||
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;
|
||||
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 ();
|
||||
if (new_port != udp_server_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_)
|
||||
{
|
||||
accept_udp_requests_ = ui_->accept_udp_requests_check_box->isChecked ();
|
||||
@ -2111,6 +2168,12 @@ void Configuration::impl::accept ()
|
||||
|
||||
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 ()
|
||||
|
||||
// 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 ()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
void Configuration::impl::update_audio_channels (QComboBox const * source_combo_box, int index, QComboBox * combo_box, bool allow_both)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QFont>
|
||||
#include <QString>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
@ -14,14 +15,12 @@
|
||||
class QSettings;
|
||||
class QWidget;
|
||||
class QAudioDeviceInfo;
|
||||
class QString;
|
||||
class QDir;
|
||||
class QNetworkAccessManager;
|
||||
class Bands;
|
||||
class FrequencyList_v2;
|
||||
class StationList;
|
||||
class QStringListModel;
|
||||
class QHostAddress;
|
||||
class LotWUsers;
|
||||
class DecodeHighlightingModel;
|
||||
class LogBook;
|
||||
@ -152,6 +151,8 @@ public:
|
||||
void opCall (QString const&);
|
||||
QString udp_server_name () const;
|
||||
port_type udp_server_port () const;
|
||||
QStringList udp_interface_names () const;
|
||||
int udp_TTL () const;
|
||||
QString n1mm_server_name () const;
|
||||
port_type n1mm_server_port () const;
|
||||
bool valid_n1mm_info () const;
|
||||
@ -273,8 +274,9 @@ public:
|
||||
//
|
||||
// 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_TTL_changed (int TTL) const;
|
||||
Q_SIGNAL void accept_udp_requests_changed (bool checked) const;
|
||||
|
||||
// signal updates to decode highlighting
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>554</width>
|
||||
<height>556</height>
|
||||
<height>560</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -1864,12 +1864,6 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<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">
|
||||
<string><html><head/><body><p>Optional hostname of network service to receive decodes.</p><p>Formats:</p><ul style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;"><li style=" margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">hostname</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv4 multicast group address</li><li style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">IPv6 multicast group address</li></ul><p>Clearing this field will disable the broadcasting of UDP status updates.</p></body></html></string>
|
||||
</property>
|
||||
@ -1891,13 +1885,53 @@ and DX Grid fields when a 73 or free text message is sent.</string>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="udp_server_port_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>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.</p></body></html></string>
|
||||
<string><html><head/><body><p>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.</p></body></html></string>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65534</number>
|
||||
</property>
|
||||
</widget>
|
||||
</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><html><head/><body><p>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.</p><p>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.</p><p>On some Linux systems it may be necessary to enable multicast on the loop-back network interface.</p></body></html></string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QSpinBox" name="udp_TTL_spin_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>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.</p></body></html></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>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
@ -3002,6 +3036,11 @@ Right click for insert and delete options.</string>
|
||||
<extends>QComboBox</extends>
|
||||
<header>widgets/LazyFillComboBox.hpp</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>CheckableItemComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>widgets/CheckableItemComboBox.hpp</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>configuration_tabs</tabstop>
|
||||
@ -3084,6 +3123,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>psk_reporter_tcpip_check_box</tabstop>
|
||||
<tabstop>udp_server_line_edit</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>udpWindowToFront</tabstop>
|
||||
<tabstop>udpWindowRestore</tabstop>
|
||||
@ -3101,8 +3142,8 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>include_WAE_check_box</tabstop>
|
||||
<tabstop>rescan_log_push_button</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_days_since_upload_spin_box</tabstop>
|
||||
<tabstop>sbNtrials</tabstop>
|
||||
<tabstop>sbAggressive</tabstop>
|
||||
<tabstop>cbTwoPass</tabstop>
|
||||
@ -3118,11 +3159,11 @@ Right click for insert and delete options.</string>
|
||||
<tabstop>rbHound</tabstop>
|
||||
<tabstop>rbNA_VHF_Contest</tabstop>
|
||||
<tabstop>rbField_Day</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>rbEU_VHF_Contest</tabstop>
|
||||
<tabstop>rbRTTY_Roundup</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
<tabstop>rbWW_DIGI</tabstop>
|
||||
<tabstop>Field_Day_Exchange</tabstop>
|
||||
<tabstop>RTTY_Exchange</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
@ -3192,13 +3233,13 @@ Right click for insert and delete options.</string>
|
||||
</connection>
|
||||
</connections>
|
||||
<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="split_mode_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_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>
|
||||
</ui>
|
||||
|
@ -6,16 +6,16 @@
|
||||
#include <limits>
|
||||
|
||||
#include <QUdpSocket>
|
||||
#include <QNetworkInterface>
|
||||
#include <QHostInfo>
|
||||
#include <QTimer>
|
||||
#include <QQueue>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
|
||||
#include "NetworkMessage.hpp"
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageClient.cpp"
|
||||
@ -34,14 +34,16 @@ class MessageClient::impl
|
||||
|
||||
public:
|
||||
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}
|
||||
, dns_lookup_id_ {-1}
|
||||
, enabled_ {false}
|
||||
, id_ {id}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, dns_lookup_id_ {-1}
|
||||
, server_port_ {server_port}
|
||||
, TTL_ {TTL}
|
||||
, schema_ {2} // use 2 prior to negotiation not 1 which is broken
|
||||
, heartbeat_timer_ {new QTimer {this}}
|
||||
{
|
||||
@ -49,9 +51,6 @@ public:
|
||||
connect (this, &QIODevice::readyRead, this, &impl::pending_datagrams);
|
||||
|
||||
heartbeat_timer_->start (NetworkMessage::pulse * 1000);
|
||||
|
||||
// bind to an ephemeral port
|
||||
bind ();
|
||||
}
|
||||
|
||||
~impl ()
|
||||
@ -65,35 +64,37 @@ public:
|
||||
|
||||
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 heartbeat ();
|
||||
void closedown ();
|
||||
StreamStatus check_status (QDataStream const&) const;
|
||||
void send_message (QByteArray const&);
|
||||
void send_message (QDataStream const& out, QByteArray const& message)
|
||||
void send_message (QByteArray const&, bool queue_if_pending = true);
|
||||
void send_message (QDataStream const& out, QByteArray const& message, bool queue_if_pending = true)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
send_message (message, queue_if_pending);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
Q_SLOT void host_info_results (QHostInfo);
|
||||
|
||||
MessageClient * self_;
|
||||
int dns_lookup_id_;
|
||||
bool enabled_;
|
||||
QString id_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
QString server_string_;
|
||||
port_type server_port_;
|
||||
int dns_lookup_id_;
|
||||
QHostAddress server_;
|
||||
port_type server_port_;
|
||||
int TTL_;
|
||||
std::vector<QNetworkInterface> network_interfaces_;
|
||||
quint32 schema_;
|
||||
QTimer * heartbeat_timer_;
|
||||
std::vector<QHostAddress> blocked_addresses_;
|
||||
@ -105,37 +106,99 @@ public:
|
||||
|
||||
#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)
|
||||
{
|
||||
if (host_info.lookupId () != dns_lookup_id_) return;
|
||||
dns_lookup_id_ = -1;
|
||||
if (QHostInfo::NoError != host_info.error ())
|
||||
{
|
||||
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
|
||||
pending_messages_.clear (); // discard
|
||||
Q_EMIT self_->error ("UDP server DNS lookup failed: " + host_info.errorString ());
|
||||
}
|
||||
else if (host_info.addresses ().size ())
|
||||
else
|
||||
{
|
||||
auto server = host_info.addresses ()[0];
|
||||
if (blocked_addresses_.end () == std::find (blocked_addresses_.begin (), blocked_addresses_.end (), server))
|
||||
auto const& server_addresses = host_info.addresses ();
|
||||
if (server_addresses.size ())
|
||||
{
|
||||
server_ = server;
|
||||
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 ());
|
||||
}
|
||||
server_ = server_addresses[0];
|
||||
}
|
||||
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");
|
||||
pending_messages_.clear (); // discard
|
||||
close ();
|
||||
}
|
||||
// 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 ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id_, schema_};
|
||||
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (OK == check_status (hb))
|
||||
{
|
||||
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
|
||||
writeDatagram (message, server_, server_port_);
|
||||
}
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Heartbeat, id_, schema_};
|
||||
out << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
TRACE_UDP ("schema:" << schema_ << "max schema:" << NetworkMessage::Builder::schema_number << "version:" << version_ << "revision:" << revision_);
|
||||
send_message (out, message, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -380,15 +440,12 @@ void MessageClient::impl::closedown ()
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, id_, schema_};
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
TRACE_UDP ("");
|
||||
writeDatagram (message, server_, server_port_);
|
||||
}
|
||||
TRACE_UDP ("");
|
||||
send_message (out, message, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageClient::impl::send_message (QByteArray const& message)
|
||||
void MessageClient::impl::send_message (QByteArray const& message, bool queue_if_pending)
|
||||
{
|
||||
if (server_port_)
|
||||
{
|
||||
@ -396,11 +453,25 @@ void MessageClient::impl::send_message (QByteArray const& message)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
else
|
||||
else if (queue_if_pending)
|
||||
{
|
||||
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,
|
||||
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}
|
||||
, m_ {id, version, revision, server_port, this}
|
||||
, m_ {id, version, revision, server_port, TTL, this}
|
||||
{
|
||||
connect (&*m_
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
@ -454,8 +527,8 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
|
||||
#endif
|
||||
Q_EMIT error (m_->errorString ());
|
||||
}
|
||||
});
|
||||
set_server (server);
|
||||
});
|
||||
m_->set_server (server_name, network_interface_names);
|
||||
}
|
||||
|
||||
QHostAddress MessageClient::server_address () const
|
||||
@ -468,20 +541,9 @@ auto MessageClient::server_port () const -> port_type
|
||||
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_->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
|
||||
}
|
||||
m_->set_server (server_name, network_interface_names);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void MessageClient::set_TTL (int TTL)
|
||||
{
|
||||
m_->TTL_ = TTL;
|
||||
m_->setSocketOption (QAbstractSocket::MulticastTtlOption, m_->TTL_);
|
||||
}
|
||||
|
||||
void MessageClient::enable (bool 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
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
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
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
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
|
||||
, bool off_air)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
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 ()
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
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& exchange_rcvd, QString const& propmode)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
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)
|
||||
{
|
||||
if (m_->server_port_ && !m_->server_string_.isEmpty ())
|
||||
if (m_->server_port_ && !m_->server_.isNull ())
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::LoggedADIF, m_->id_, m_->schema_};
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "pimpl_h.hpp"
|
||||
@ -34,19 +35,25 @@ public:
|
||||
//
|
||||
// messages will be silently dropped until a server host lookup is complete
|
||||
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
|
||||
QHostAddress server_address () const;
|
||||
port_type server_port () const;
|
||||
|
||||
// initiate a new server host lookup or is the server name is empty
|
||||
// the sending of messages is disabled
|
||||
Q_SLOT void set_server (QString const& server = QString {});
|
||||
// initiate a new server host lookup or if the server name is empty
|
||||
// the sending of messages is disabled, if an interface is specified
|
||||
// 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
|
||||
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
|
||||
Q_SLOT void enable (bool);
|
||||
|
||||
|
@ -28,13 +28,13 @@ namespace NetworkMessage
|
||||
{
|
||||
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)
|
||||
{
|
||||
setVersion (QDataStream::Qt_5_2); // Qt schema version
|
||||
}
|
||||
#endif
|
||||
#if QT_VERSION >= 0x050400
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
|
||||
else if (schema <= 3)
|
||||
{
|
||||
setVersion (QDataStream::Qt_5_4); // Qt schema version
|
||||
@ -73,13 +73,13 @@ namespace NetworkMessage
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_0);
|
||||
}
|
||||
#if QT_VERSION >= 0x050200
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
|
||||
else if (schema_ <= 2)
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_2);
|
||||
}
|
||||
#endif
|
||||
#if QT_VERSION >= 0x050400
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 4, 0)
|
||||
else if (schema_ <= 3)
|
||||
{
|
||||
parent->setVersion (QDataStream::Qt_5_4);
|
||||
|
@ -540,9 +540,9 @@ namespace NetworkMessage
|
||||
|
||||
// increment this if a newer Qt schema is required and add decode
|
||||
// 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};
|
||||
#elif QT_VERSION >= 0x050200
|
||||
#elif QT_VERSION >= QT_VERSION_CHECK (5, 2, 0)
|
||||
static quint32 constexpr schema_number {2};
|
||||
#else
|
||||
// Schema 1 (Qt_5_0) is broken
|
||||
|
@ -84,7 +84,7 @@ public:
|
||||
{
|
||||
if (!socket_
|
||||
|| 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
|
||||
if (socket_
|
||||
|
@ -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);
|
||||
if (rx_match.hasMatch ()) {
|
||||
@ -271,7 +271,23 @@ bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
|
||||
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;
|
||||
query.addQueryItem ("function", "wsprstat");
|
||||
@ -282,28 +298,18 @@ auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
|
||||
query.addQueryItem ("tqrg", m_tfreq);
|
||||
query.addQueryItem ("dbm", m_dbm);
|
||||
query.addQueryItem ("version", m_vers);
|
||||
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
|
||||
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)));
|
||||
}
|
||||
query.addQueryItem ("mode", encode_mode ());
|
||||
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 ("rcall", m_call);
|
||||
query.addQueryItem ("rgrid", m_grid);
|
||||
query.addQueryItem ("rqrg", m_rfreq);
|
||||
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
|
||||
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;
|
||||
query.addQueryItem ("mode", encode_mode ());
|
||||
return query;
|
||||
}
|
||||
|
||||
void WSPRNet::work()
|
||||
|
@ -34,9 +34,10 @@ public slots:
|
||||
void abortOutstandingRequests ();
|
||||
|
||||
private:
|
||||
bool decodeLine (QString const& line, SpotQueue::value_type& query);
|
||||
SpotQueue::value_type urlEncodeNoSpot ();
|
||||
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot);
|
||||
bool decodeLine (QString const& line, SpotQueue::value_type& query) const;
|
||||
SpotQueue::value_type urlEncodeNoSpot () const;
|
||||
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot) const;
|
||||
QString encode_mode () const;
|
||||
|
||||
QNetworkAccessManager * network_manager_;
|
||||
QList<QNetworkReply *> m_outstandingRequests;
|
||||
|
12
Radio.cpp
12
Radio.cpp
@ -14,10 +14,16 @@ namespace Radio
|
||||
double constexpr MHz_factor {1.e6};
|
||||
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
|
||||
// a number
|
||||
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
|
||||
// DXCC Entity prefix used as a suffix
|
||||
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 ('/');
|
||||
}
|
||||
|
||||
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
|
||||
// there is no '/'
|
||||
QString base_callsign (QString callsign)
|
||||
|
@ -53,6 +53,7 @@ namespace Radio
|
||||
//
|
||||
bool UDP_EXPORT is_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 effective_prefix (QString);
|
||||
}
|
||||
|
@ -25,10 +25,13 @@ namespace
|
||||
|
||||
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
|
||||
, 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")};
|
||||
time_item->setData (time);
|
||||
time_item->setTextAlignment (Qt::AlignRight);
|
||||
@ -60,7 +63,7 @@ namespace
|
||||
live->setTextAlignment (Qt::AlignHCenter);
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
, 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};
|
||||
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 ();
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (data (index (row, 0)).toString () == client_id)
|
||||
if (item (row, 0)->data ().value<ClientKey> () == key)
|
||||
{
|
||||
removeRow (row);
|
||||
}
|
||||
|
@ -26,13 +26,15 @@ class BeaconsModel
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
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
|
||||
, qint32 power, bool off_air);
|
||||
Q_SLOT void decodes_cleared (QString const& client_id);
|
||||
Q_SLOT void decodes_cleared (ClientKey const&);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -27,9 +27,9 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
ClientWidget::IdFilterModel::IdFilterModel (QString const& client_id, QObject * parent)
|
||||
ClientWidget::IdFilterModel::IdFilterModel (ClientKey const& key, QObject * parent)
|
||||
: QSortFilterProxyModel {parent}
|
||||
, client_id_ {client_id}
|
||||
, key_ {key}
|
||||
, rx_df_ (quint32_max)
|
||||
{
|
||||
}
|
||||
@ -73,7 +73,7 @@ bool ClientWidget::IdFilterModel::filterAcceptsRow (int source_row
|
||||
, QModelIndex const& source_parent) const
|
||||
{
|
||||
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)
|
||||
@ -106,9 +106,9 @@ void ClientWidget::IdFilterModel::rx_df (quint32 df)
|
||||
|
||||
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 ())
|
||||
{
|
||||
title += QString {" v%1"}.arg (version);
|
||||
@ -122,14 +122,14 @@ namespace
|
||||
}
|
||||
|
||||
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)
|
||||
: QDockWidget {make_title (id, version, revision), parent}
|
||||
, id_ {id}
|
||||
: QDockWidget {make_title (key, version, revision), parent}
|
||||
, key_ {key}
|
||||
, done_ {false}
|
||||
, calls_of_interest_ {calls_of_interest}
|
||||
, decodes_proxy_model_ {id}
|
||||
, beacons_proxy_model_ {id}
|
||||
, decodes_proxy_model_ {key}
|
||||
, beacons_proxy_model_ {key}
|
||||
, erase_action_ {new QAction {tr ("&Erase Band Activity"), this}}
|
||||
, erase_rx_frequency_action_ {new QAction {tr ("Erase &Rx Frequency"), 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_);
|
||||
|
||||
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] () {
|
||||
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] () {
|
||||
Q_EMIT location (id_, grid_line_edit_->text ());
|
||||
Q_EMIT location (key_, grid_line_edit_->text ());
|
||||
});
|
||||
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] () {
|
||||
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);
|
||||
});
|
||||
connect (frequency_tolerance_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
QString empty;
|
||||
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);
|
||||
});
|
||||
connect (submode_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
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);
|
||||
});
|
||||
connect (fast_mode_check_box_, &QCheckBox::stateChanged, [this] (int state) {
|
||||
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);
|
||||
});
|
||||
connect (tr_period_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
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);
|
||||
});
|
||||
connect (rx_df_spin_box_, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged), [this] (int i) {
|
||||
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);
|
||||
});
|
||||
connect (dx_call_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
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);
|
||||
});
|
||||
connect (dx_grid_line_edit_, &QLineEdit::editingFinished, [this] () {
|
||||
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);
|
||||
});
|
||||
|
||||
@ -289,14 +289,14 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
halt_tx_button_ = control_button_box_->addButton (tr ("&Halt Tx"), QDialogButtonBox::ActionRole);
|
||||
connect (generate_messages_push_button_, &QAbstractButton::clicked, [this] (bool /*checked*/) {
|
||||
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);
|
||||
});
|
||||
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 */) {
|
||||
Q_EMIT do_halt_tx (id_, false);
|
||||
Q_EMIT do_halt_tx (key_, false);
|
||||
});
|
||||
content_layout_->addWidget (control_button_box_);
|
||||
|
||||
@ -318,13 +318,13 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
|
||||
// connect context menu actions
|
||||
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*/) {
|
||||
Q_EMIT do_clear_decodes (id_, 1);
|
||||
Q_EMIT do_clear_decodes (key_, 1);
|
||||
});
|
||||
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
|
||||
@ -335,7 +335,7 @@ ClientWidget::ClientWidget (QAbstractItemModel * decodes_model, QAbstractItemMod
|
||||
// tell new client about calls of interest
|
||||
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_)
|
||||
{
|
||||
Q_EMIT do_close (id_);
|
||||
Q_EMIT do_close (key_);
|
||||
e->ignore (); // defer closure until client actually closes
|
||||
}
|
||||
else
|
||||
@ -363,7 +363,7 @@ ClientWidget::~ClientWidget ()
|
||||
for (int row = 0; row < calls_of_interest_->count (); ++row)
|
||||
{
|
||||
// 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
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, 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
|
||||
, QString const& configuration_name)
|
||||
{
|
||||
if (id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
fast_mode_check_box_->setChecked (fast_mode);
|
||||
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*/
|
||||
, 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_table_view_->resizeColumnsToContents ();
|
||||
@ -460,12 +460,12 @@ void ClientWidget::decode_added (bool /*is_new*/, QString const& client_id, QTim
|
||||
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*/
|
||||
, QString const& /*callsign*/, QString const& /*grid*/, qint32 /*power*/
|
||||
, bool /*off_air*/)
|
||||
{
|
||||
if (client_id == id_ && !columns_resized_)
|
||||
if (key == key_ && !columns_resized_)
|
||||
{
|
||||
decodes_stack_->setCurrentIndex (1);
|
||||
beacons_table_view_->resizeColumnsToContents ();
|
||||
@ -474,9 +474,9 @@ void ClientWidget::beacon_spot_added (bool /*is_new*/, QString const& client_id,
|
||||
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;
|
||||
}
|
||||
|
@ -35,42 +35,44 @@ class ClientWidget
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
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);
|
||||
void dispose ();
|
||||
~ClientWidget ();
|
||||
|
||||
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
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, 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
|
||||
, 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
|
||||
, QString const& callsign, QString const& grid, qint32 power
|
||||
, 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_close (QString const& id);
|
||||
Q_SIGNAL void do_clear_decodes (ClientKey const& key, quint8 window = 0);
|
||||
Q_SIGNAL void do_close (ClientKey const& key);
|
||||
Q_SIGNAL void do_reply (QModelIndex const&, quint8 modifier);
|
||||
Q_SIGNAL void do_halt_tx (QString const& id, bool auto_only);
|
||||
Q_SIGNAL void do_free_text (QString const& id, QString const& text, bool);
|
||||
Q_SIGNAL void location (QString const& id, QString const& text);
|
||||
Q_SIGNAL void highlight_callsign (QString const& id, QString const& call
|
||||
Q_SIGNAL void do_halt_tx (ClientKey const& key, bool auto_only);
|
||||
Q_SIGNAL void do_free_text (ClientKey const& key, QString const& text, bool);
|
||||
Q_SIGNAL void location (ClientKey const& key, QString const& text);
|
||||
Q_SIGNAL void highlight_callsign (ClientKey const& key, QString const& call
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
Q_SIGNAL void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
Q_SIGNAL void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
Q_SIGNAL void switch_configuration (ClientKey const& key, QString const& configuration_name);
|
||||
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& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
@ -79,7 +81,7 @@ private:
|
||||
: public QSortFilterProxyModel
|
||||
{
|
||||
public:
|
||||
IdFilterModel (QString const& client_id, QObject * = nullptr);
|
||||
IdFilterModel (ClientKey const& key, QObject * = nullptr);
|
||||
|
||||
void de_call (QString const&);
|
||||
void rx_df (quint32);
|
||||
@ -88,7 +90,7 @@ private:
|
||||
private:
|
||||
bool filterAcceptsRow (int source_row, QModelIndex const& source_parent) const override;
|
||||
|
||||
QString client_id_;
|
||||
ClientKey key_;
|
||||
QString call_;
|
||||
QRegularExpression base_call_re_;
|
||||
quint32 rx_df_;
|
||||
@ -96,7 +98,7 @@ private:
|
||||
|
||||
void closeEvent (QCloseEvent *) override;
|
||||
|
||||
QString id_;
|
||||
ClientKey key_;
|
||||
bool done_;
|
||||
QListWidget const * calls_of_interest_;
|
||||
IdFilterModel decodes_proxy_model_;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <QStandardItem>
|
||||
#include <QModelIndex>
|
||||
#include <QVariant>
|
||||
#include <QTime>
|
||||
#include <QString>
|
||||
#include <QFont>
|
||||
@ -33,10 +34,13 @@ namespace
|
||||
|
||||
QFont text_font {"Courier", 10};
|
||||
|
||||
QList<QStandardItem *> make_row (QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode, QString const& message
|
||||
, bool low_confidence, bool off_air, bool is_fast)
|
||||
QList<QStandardItem *> make_row (MessageServer::ClientKey const& key, QTime time, qint32 snr
|
||||
, float delta_time, quint32 delta_frequency, QString const& mode
|
||||
, 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")};
|
||||
time_item->setData (time);
|
||||
time_item->setTextAlignment (Qt::AlignRight);
|
||||
@ -63,7 +67,7 @@ namespace
|
||||
live->setTextAlignment (Qt::AlignHCenter);
|
||||
|
||||
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)
|
||||
{
|
||||
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
|
||||
, 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};
|
||||
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 ();
|
||||
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)
|
||||
{
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
void DecodesModel::decodes_cleared (QString const& client_id)
|
||||
void DecodesModel::decodes_cleared (ClientKey const& key)
|
||||
{
|
||||
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);
|
||||
}
|
||||
@ -139,7 +143,7 @@ void DecodesModel::decodes_cleared (QString const& client_id)
|
||||
void DecodesModel::do_reply (QModelIndex const& source, quint8 modifiers)
|
||||
{
|
||||
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, 2)->data ().toInt ()
|
||||
, item (row, 3)->data ().toFloat ()
|
||||
|
@ -5,8 +5,6 @@
|
||||
|
||||
#include "MessageServer.hpp"
|
||||
|
||||
using Frequency = MessageServer::Frequency;
|
||||
|
||||
class QTime;
|
||||
class QString;
|
||||
class QModelIndex;
|
||||
@ -28,16 +26,18 @@ class DecodesModel
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit DecodesModel (QObject * parent = nullptr);
|
||||
|
||||
Q_SLOT void add_decode (bool is_new, QString const& client_id, QTime time, qint32 snr, float delta_time
|
||||
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
|
||||
, 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_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);
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QDateTime>
|
||||
#include <QNetworkInterface>
|
||||
#include <QSet>
|
||||
|
||||
#include "DecodesModel.hpp"
|
||||
#include "BeaconsModel.hpp"
|
||||
@ -37,8 +39,10 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
|
||||
, decodes_model_ {new DecodesModel {this}}
|
||||
, beacons_model_ {new BeaconsModel {this}}
|
||||
, server_ {new MessageServer {this}}
|
||||
, multicast_group_line_edit_ {new QLineEdit}
|
||||
, log_table_view_ {new QTableView}
|
||||
, port_spin_box_ {new QSpinBox {this}}
|
||||
, 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}}
|
||||
, delete_call_of_interest_action_ {new QAction {tr ("&Delete callsign"), this}}
|
||||
, last_call_of_interest_action_ {new QAction {tr ("&Highlight last only"), this}}
|
||||
@ -68,16 +72,71 @@ MessageAggregatorMainWindow::MessageAggregatorMainWindow ()
|
||||
auto central_layout = new QVBoxLayout;
|
||||
|
||||
// server details
|
||||
auto port_spin_box = new QSpinBox;
|
||||
port_spin_box->setMinimum (1);
|
||||
port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
|
||||
port_spin_box_->setMinimum (1);
|
||||
port_spin_box_->setMaximum (std::numeric_limits<port_type>::max ());
|
||||
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 ("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")};
|
||||
group_box->setLayout (group_box_layout);
|
||||
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_->verticalHeader ()->hide ();
|
||||
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, decodes_model_, &DecodesModel::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
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message, bool low_confidence
|
||||
, bool off_air) {
|
||||
decodes_model_->add_decode (is_new, id, time, snr, delta_time, delta_frequency, mode, message
|
||||
, low_confidence, off_air, dock_widgets_[id]->fast_mode ());});
|
||||
decodes_model_->add_decode (is_new, key, time, snr, delta_time
|
||||
, 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::decodes_cleared, decodes_model_, &DecodesModel::decodes_cleared);
|
||||
connect (server_, &MessageServer::decodes_cleared, beacons_model_, &BeaconsModel::decodes_cleared);
|
||||
connect (decodes_model_, &DecodesModel::reply, server_, &MessageServer::reply);
|
||||
|
||||
// UI behaviour
|
||||
connect (port_spin_box, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
|
||||
, [this] (port_type port) {server_->start (port);});
|
||||
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this, port_spin_box] () {
|
||||
server_->start (port_spin_box->value (), QHostAddress {multicast_group_line_edit_->text ()});
|
||||
});
|
||||
connect (port_spin_box_, static_cast<void (QSpinBox::*)(int)> (&QSpinBox::valueChanged)
|
||||
, [this] (int /*port*/) {restart_server ();});
|
||||
connect (multicast_group_line_edit_, &QLineEdit::editingFinished, [this] () {restart_server ();});
|
||||
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 ();
|
||||
}
|
||||
|
||||
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& report_sent, QString const& report_received
|
||||
, QString const& tx_power, QString const& comments
|
||||
@ -240,9 +321,9 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
|
||||
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);
|
||||
auto view_action = dock->toggleViewAction ();
|
||||
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::switch_configuration, server_, &MessageServer::switch_configuration);
|
||||
connect (dock, &ClientWidget::configure, server_, &MessageServer::configure);
|
||||
dock_widgets_[id] = dock;
|
||||
server_->replay (id); // request decodes and status
|
||||
dock_widgets_[key] = dock;
|
||||
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_))
|
||||
{
|
||||
(*iter)->dispose ();
|
||||
@ -287,9 +368,35 @@ MessageAggregatorMainWindow::~MessageAggregatorMainWindow ()
|
||||
void MessageAggregatorMainWindow::change_highlighting (QString const& call, QColor const& bg, QColor const& fg
|
||||
, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <QString>
|
||||
|
||||
#include "MessageServer.hpp"
|
||||
#include "widgets/CheckableItemComboBox.hpp"
|
||||
|
||||
class QDateTime;
|
||||
class QStandardItemModel;
|
||||
@ -16,6 +17,8 @@ class QLineEdit;
|
||||
class QTableView;
|
||||
class ClientWidget;
|
||||
class QListWidget;
|
||||
class QLabel;
|
||||
class QSpinBox;
|
||||
|
||||
using Frequency = MessageServer::Frequency;
|
||||
|
||||
@ -24,11 +27,13 @@ class MessageAggregatorMainWindow
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
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
|
||||
, QString const& report_received, QString const& tx_power, QString const& comments
|
||||
, 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);
|
||||
|
||||
private:
|
||||
void add_client (QString const& id, QString const& version, QString const& revision);
|
||||
void remove_client (QString const& id);
|
||||
void restart_server ();
|
||||
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 {},
|
||||
bool last_only = false);
|
||||
Q_SLOT void validate_network_interfaces (QString const&);
|
||||
|
||||
// maps client id to widgets
|
||||
using ClientsDictionary = QHash<QString, ClientWidget *>;
|
||||
using ClientsDictionary = QHash<ClientKey, ClientWidget *>;
|
||||
ClientsDictionary dock_widgets_;
|
||||
|
||||
QStandardItemModel * log_;
|
||||
@ -50,7 +57,11 @@ private:
|
||||
DecodesModel * decodes_model_;
|
||||
BeaconsModel * beacons_model_;
|
||||
MessageServer * server_;
|
||||
QSpinBox * port_spin_box_;
|
||||
QLineEdit * multicast_group_line_edit_;
|
||||
CheckableItemComboBox * network_interfaces_combo_box_;
|
||||
QString loopback_interface_name_;
|
||||
QLabel * network_interfaces_form_label_widget_;
|
||||
QTableView * log_table_view_;
|
||||
QListWidget * calls_of_interest_;
|
||||
QAction * add_call_of_interest_action_;
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
|
||||
@ -32,7 +31,6 @@ public:
|
||||
: self_ {self}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, port_ {0u}
|
||||
, clock_ {new QTimer {this}}
|
||||
{
|
||||
// register the required types with Qt
|
||||
@ -78,15 +76,14 @@ public:
|
||||
MessageServer * self_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
port_type port_;
|
||||
QHostAddress multicast_group_address_;
|
||||
QSet<QString> network_interfaces_;
|
||||
static BindMode constexpr bind_mode_ = ShareAddress | ReuseAddressHint;
|
||||
struct Client
|
||||
{
|
||||
Client () = default;
|
||||
Client (QHostAddress const& sender_address, port_type const& sender_port)
|
||||
: sender_address_ {sender_address}
|
||||
, sender_port_ {sender_port}
|
||||
Client (port_type const& sender_port)
|
||||
: sender_port_ {sender_port}
|
||||
, negotiated_schema_number_ {2} // not 1 because it's broken
|
||||
, last_activity_ {QDateTime::currentDateTime ()}
|
||||
{
|
||||
@ -94,12 +91,11 @@ public:
|
||||
Client (Client const&) = default;
|
||||
Client& operator= (Client const&) = default;
|
||||
|
||||
QHostAddress sender_address_;
|
||||
port_type sender_port_;
|
||||
quint32 negotiated_schema_number_;
|
||||
QDateTime last_activity_;
|
||||
};
|
||||
QHash<QString, Client> clients_; // maps id to Client
|
||||
QHash<ClientKey, Client> clients_; // maps id to Client
|
||||
QTimer * clock_;
|
||||
};
|
||||
|
||||
@ -109,56 +105,39 @@ MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
|
||||
|
||||
void MessageServer::impl::leave_multicast_group ()
|
||||
{
|
||||
if (!multicast_group_address_.isNull () && BoundState == state ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
if (BoundState == state () && is_multicast_address (multicast_group_address_))
|
||||
{
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
for (auto const& if_name : network_interfaces_)
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
leaveMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
leaveMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::join_multicast_group ()
|
||||
{
|
||||
if (BoundState == state ()
|
||||
&& !multicast_group_address_.isNull ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
if (BoundState == state () && is_multicast_address (multicast_group_address_))
|
||||
{
|
||||
auto mcast_iface = multicastInterface ();
|
||||
if (IPv4Protocol == multicast_group_address_.protocol ()
|
||||
&& IPv4Protocol != localAddress ().protocol ())
|
||||
if (network_interfaces_.size ())
|
||||
{
|
||||
close ();
|
||||
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
|
||||
}
|
||||
bool joined {false};
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
for (auto const& if_name : network_interfaces_)
|
||||
{
|
||||
// Windows requires outgoing interface to match
|
||||
// interface to be joined while joining, at least for
|
||||
// IPv4 it seems to
|
||||
setMulticastInterface (interface);
|
||||
|
||||
joined |= joinMulticastGroup (multicast_group_address_, interface);
|
||||
joinMulticastGroup (multicast_group_address_, QNetworkInterface::interfaceFromName (if_name));
|
||||
}
|
||||
}
|
||||
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 ();
|
||||
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_revision;
|
||||
|
||||
@ -212,7 +192,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (impl::OK == check_status (hb))
|
||||
{
|
||||
writeDatagram (message, client.sender_address_, client.sender_port_);
|
||||
writeDatagram (message, client_key.first, sender_port);
|
||||
}
|
||||
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
|
||||
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));
|
||||
}
|
||||
clients_[id].last_activity_ = QDateTime::currentDateTime ();
|
||||
clients_[client_key].last_activity_ = QDateTime::currentDateTime ();
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
@ -237,7 +217,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
break;
|
||||
|
||||
case NetworkMessage::Clear:
|
||||
Q_EMIT self_->decodes_cleared (id);
|
||||
Q_EMIT self_->decodes_cleared (client_key);
|
||||
break;
|
||||
|
||||
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;
|
||||
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)
|
||||
, tx_enabled, transmitting, decoding, rx_df, tx_df
|
||||
, 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;
|
||||
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)
|
||||
, low_confidence, off_air);
|
||||
}
|
||||
@ -320,7 +301,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> off_air;
|
||||
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)
|
||||
, power, off_air);
|
||||
}
|
||||
@ -351,8 +332,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
>> exchange_sent >> exchange_rcvd >> prop_mode;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
|
||||
Q_EMIT self_->qso_logged (client_key, time_off, QString::fromUtf8 (dx_call)
|
||||
, QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode)
|
||||
, QString::fromUtf8 (report_sent)
|
||||
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
|
||||
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
|
||||
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
|
||||
@ -363,8 +346,8 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
Q_EMIT self_->client_closed (id);
|
||||
clients_.remove (id);
|
||||
Q_EMIT self_->client_closed (client_key);
|
||||
clients_.remove (client_key);
|
||||
break;
|
||||
|
||||
case NetworkMessage::LoggedADIF:
|
||||
@ -373,7 +356,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
|
||||
in >> ADIF;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->logged_ADIF (id, ADIF);
|
||||
Q_EMIT self_->logged_ADIF (client_key, ADIF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -406,7 +389,7 @@ void MessageServer::impl::tick ()
|
||||
{
|
||||
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 ());
|
||||
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_
|
||||
|| multicast_group_address != m_->multicast_group_address_)
|
||||
// qDebug () << "MessageServer::start port:" << port << "multicast addr:" << multicast_group_address.toString () << "network interfaces:" << network_interface_names;
|
||||
if (port != m_->localPort ()
|
||||
|| multicast_group_address != m_->multicast_group_address_
|
||||
|| network_interface_names != m_->network_interfaces_)
|
||||
{
|
||||
m_->leave_multicast_group ();
|
||||
if (impl::BoundState == m_->state ())
|
||||
if (impl::UnconnectedState != m_->state ())
|
||||
{
|
||||
m_->close ();
|
||||
}
|
||||
m_->multicast_group_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_))
|
||||
if (!(multicast_group_address.isNull () || is_multicast_address (multicast_group_address)))
|
||||
{
|
||||
m_->port_ = port;
|
||||
m_->join_multicast_group ();
|
||||
Q_EMIT error ("Invalid multicast group address");
|
||||
}
|
||||
else if (is_MAC_ambiguous_multicast_address (multicast_group_address))
|
||||
{
|
||||
Q_EMIT error ("MAC-ambiguous IPv4 multicast group address not supported");
|
||||
}
|
||||
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_))
|
||||
{
|
||||
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;
|
||||
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
|
||||
, 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_))
|
||||
{
|
||||
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 ()
|
||||
<< 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_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Replay, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Replay, key.second, (*iter).negotiated_schema_number_};
|
||||
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_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, id, (*iter).negotiated_schema_number_};
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Close, key.second, (*iter).negotiated_schema_number_};
|
||||
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_))
|
||||
{
|
||||
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;
|
||||
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_))
|
||||
{
|
||||
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;
|
||||
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_))
|
||||
{
|
||||
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 ();
|
||||
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)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
auto iter = m_->clients_.find (key);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
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;
|
||||
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_))
|
||||
{
|
||||
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 ();
|
||||
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& 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_))
|
||||
{
|
||||
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
|
||||
<< 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_);
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
#define MESSAGE_SERVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QSet>
|
||||
#include <QTime>
|
||||
#include <QDateTime>
|
||||
#include <QHostAddress>
|
||||
@ -31,6 +34,7 @@ class UDP_EXPORT MessageServer
|
||||
public:
|
||||
using port_type = quint16;
|
||||
using Frequency = Radio::Frequency;
|
||||
using ClientKey = QPair<QHostAddress, QString>;
|
||||
|
||||
MessageServer (QObject * parent = nullptr,
|
||||
QString const& version = QString {}, QString const& revision = QString {});
|
||||
@ -38,77 +42,77 @@ public:
|
||||
// start or restart the server, if the multicast_group_address
|
||||
// argument is given it is assumed to be a multicast group address
|
||||
// which the server will join
|
||||
Q_SLOT void start (port_type port,
|
||||
QHostAddress const& multicast_group_address = QHostAddress {});
|
||||
Q_SLOT void start (port_type port
|
||||
, 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
|
||||
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
|
||||
// as a double click on the decode would
|
||||
//
|
||||
// note that the client is not obliged to take any action and only
|
||||
// takes any action if the decode is present and is a CQ or QRZ message
|
||||
Q_SLOT void reply (QString const& id, QTime time, qint32 snr, float delta_time, quint32 delta_frequency
|
||||
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);
|
||||
|
||||
// ask the client with identification 'id' to close down gracefully
|
||||
Q_SLOT void close (QString const& id);
|
||||
// ask the client to close down gracefully
|
||||
Q_SLOT void close (ClientKey const&);
|
||||
|
||||
// ask the client with identification 'id' to replay all decodes
|
||||
Q_SLOT void replay (QString const& id);
|
||||
// ask the client to replay all decodes
|
||||
Q_SLOT void replay (ClientKey const&);
|
||||
|
||||
// ask the client with identification 'id' to halt transmitting
|
||||
// auto_only just disables auto Tx, otherwise halt is immediate
|
||||
Q_SLOT void halt_tx (QString const& id, bool auto_only);
|
||||
// ask the client to halt transmitting auto_only just disables auto
|
||||
// Tx, otherwise halt is immediate
|
||||
Q_SLOT void halt_tx (ClientKey const&, bool auto_only);
|
||||
|
||||
// ask the client with identification 'id' to set the free text
|
||||
// message and optionally send it ASAP
|
||||
Q_SLOT void free_text (QString const& id, QString const& text, bool send);
|
||||
// ask the client to set the free text message and optionally send
|
||||
// it ASAP
|
||||
Q_SLOT void free_text (ClientKey const&, QString const& text, bool send);
|
||||
|
||||
// ask the client with identification 'id' to set the location provided
|
||||
Q_SLOT void location (QString const& id, QString const& location);
|
||||
// ask the client to set the location provided
|
||||
Q_SLOT void location (ClientKey const&, QString const& location);
|
||||
|
||||
// ask the client with identification 'id' to highlight the callsign
|
||||
// specified with the given colors
|
||||
Q_SLOT void highlight_callsign (QString const& id, QString const& callsign
|
||||
// ask the client to highlight the callsign specified with the given
|
||||
// colors
|
||||
Q_SLOT void highlight_callsign (ClientKey const&, QString const& callsign
|
||||
, QColor const& bg = QColor {}, QColor const& fg = QColor {}
|
||||
, bool last_only = false);
|
||||
|
||||
// ask the client with identification 'id' to switch to
|
||||
// configuration 'configuration_name'
|
||||
Q_SLOT void switch_configuration (QString const& id, QString const& configuration_name);
|
||||
// ask the client to switch to configuration 'configuration_name'
|
||||
Q_SLOT void switch_configuration (ClientKey const&, QString const& configuration_name);
|
||||
|
||||
// ask the client with identification 'id' to change configuration
|
||||
Q_SLOT void configure (QString const& id, QString const& mode, quint32 frequency_tolerance
|
||||
// ask the client to change configuration
|
||||
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& dx_call, QString const& dx_grid, bool generate_messages);
|
||||
|
||||
// the following signals are emitted when a client broadcasts the
|
||||
// matching message
|
||||
Q_SIGNAL void client_opened (QString const& id, QString const& version, QString const& revision);
|
||||
Q_SIGNAL void status_update (QString const& id, Frequency, QString const& mode, QString const& dx_call
|
||||
Q_SIGNAL void client_opened (ClientKey const&, QString const& version, QString const& revision);
|
||||
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
|
||||
, bool transmitting, bool decoding, quint32 rx_df, quint32 tx_df
|
||||
, QString const& de_call, QString const& de_grid, QString const& dx_grid
|
||||
, bool watchdog_timeout, QString const& sub_mode, bool fast_mode
|
||||
, quint8 special_op_mode, quint32 frequency_tolerance, quint32 tr_period
|
||||
, QString const& configuration_name);
|
||||
Q_SIGNAL void client_closed (QString const& id);
|
||||
Q_SIGNAL void decode (bool is_new, QString const& id, QTime time, qint32 snr, float delta_time
|
||||
Q_SIGNAL void client_closed (ClientKey const&);
|
||||
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
|
||||
, 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
|
||||
, 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
|
||||
, QString const& report_received, QString const& tx_power, QString const& comments
|
||||
, QString const& name, QDateTime time_on, QString const& operator_call
|
||||
, QString const& my_call, QString const& my_grid
|
||||
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
|
||||
Q_SIGNAL void decodes_cleared (QString const& id);
|
||||
Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF);
|
||||
Q_SIGNAL void decodes_cleared (ClientKey const&);
|
||||
Q_SIGNAL void logged_ADIF (ClientKey const&, QByteArray const& ADIF);
|
||||
|
||||
// this signal is emitted when a network error occurs
|
||||
Q_SIGNAL void error (QString const&) const;
|
||||
@ -118,4 +122,6 @@ private:
|
||||
pimpl<impl> m_;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE (MessageServer::ClientKey);
|
||||
|
||||
#endif
|
||||
|
@ -17,9 +17,14 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCommandLineOption>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QNetworkInterface>
|
||||
#include <QDateTime>
|
||||
#include <QTime>
|
||||
#include <QHash>
|
||||
@ -38,15 +43,17 @@ class Client
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
public:
|
||||
explicit Client (QString const& id, QObject * parent = nullptr)
|
||||
explicit Client (ClientKey const& key, QObject * parent = nullptr)
|
||||
: QObject {parent}
|
||||
, id_ {id}
|
||||
, key_ {key}
|
||||
, 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*/
|
||||
, bool /*transmitting*/, bool /*decoding*/, qint32 /*rx_df*/, qint32 /*tx_df*/
|
||||
, 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*/
|
||||
, QString const& /*configuration_name*/)
|
||||
{
|
||||
if (id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
, QString const& message, bool low_confidence, bool off_air)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
||||
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
||||
<< "mode:" << mode << "Confidence:" << (low_confidence ? "low" : "high")
|
||||
<< "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
|
||||
, QString const& grid, qint32 power, bool off_air)
|
||||
{
|
||||
if (client_id == id_)
|
||||
if (key == key_)
|
||||
{
|
||||
qDebug () << "new:" << is_new << "t:" << time << "snr:" << snr
|
||||
<< "Dt:" << delta_time << "Df:" << delta_frequency
|
||||
<< "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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
, QString const& report_received, QString const& tx_power
|
||||
, QString const& comments, QString const& name, QDateTime time_on
|
||||
, QString const& operator_call, QString const& my_call, QString const& my_grid
|
||||
, 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
|
||||
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
|
||||
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
|
||||
<< "my_grid:" << my_grid << "exchange_sent:" << exchange_sent
|
||||
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
|
||||
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")
|
||||
.arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received)
|
||||
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
|
||||
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
|
||||
.arg (comments).arg (prop_mode).toStdString ()
|
||||
std::cout << QString {"%1(%2): "}.arg (key_.second).arg (key_.first.toString ()).toStdString ()
|
||||
<< 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 (dx_call).arg (dx_grid).arg (tx_power)
|
||||
.arg (report_sent).arg (report_received)
|
||||
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
|
||||
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
|
||||
.arg (comments).arg (prop_mode).toStdString ()
|
||||
<< 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;
|
||||
std::cout << QByteArray {80, '-'}.data () << '\n';
|
||||
@ -133,7 +148,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
QString id_;
|
||||
ClientKey key_;
|
||||
Frequency dial_frequency_;
|
||||
QString mode_;
|
||||
};
|
||||
@ -143,8 +158,10 @@ class Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
using ClientKey = MessageServer::ClientKey;
|
||||
|
||||
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}}
|
||||
{
|
||||
// connect up server
|
||||
@ -154,21 +171,26 @@ public:
|
||||
connect (server_, &MessageServer::client_opened, this, &Server::add_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:
|
||||
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::decode, client, &Client::decode_added);
|
||||
connect (server_, &MessageServer::WSPR_decode, client, &Client::beacon_spot_added);
|
||||
connect (server_, &MessageServer::qso_logged, client, &Client::qso_logged);
|
||||
connect (server_, &MessageServer::logged_ADIF, client, &Client::logged_ADIF);
|
||||
clients_[id] = client;
|
||||
server_->replay (id);
|
||||
std::cout << "Discovered WSJT-X instance: " << id.toStdString ();
|
||||
clients_[key] = client;
|
||||
server_->replay (key);
|
||||
std::cout << "Discovered WSJT-X instance: " << key.second.toStdString ()
|
||||
<< '(' << key.first.toString ().toStdString () << ')';
|
||||
if (version.size ())
|
||||
{
|
||||
std::cout << " v" << version.toStdString ();
|
||||
@ -180,23 +202,60 @@ private:
|
||||
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_))
|
||||
{
|
||||
clients_.erase (iter);
|
||||
(*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_;
|
||||
|
||||
// maps client id to clients
|
||||
QHash<QString, Client *> clients_;
|
||||
// maps client key to 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"
|
||||
|
||||
int main (int argc, char * argv[])
|
||||
@ -217,6 +276,11 @@ int main (int argc, char * argv[])
|
||||
auto help_option = parser.addHelpOption ();
|
||||
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"},
|
||||
app.translate ("UDPDaemon",
|
||||
"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"));
|
||||
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);
|
||||
|
||||
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 ();
|
||||
}
|
||||
|
169
doc/building on MS Windows.txt
Normal file
169
doc/building on MS Windows.txt
Normal 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\.
|
||||
|
@ -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]
|
||||
: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]
|
||||
:win32_openssl: https://slproweb.com/download/Win32OpenSSL_Light-1_1_1g.msi[Win32 OpenSSL Light Package]
|
||||
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1g.msi[Win64 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_1h.msi[Win64 OpenSSL Light Package]
|
||||
:writelog: https://writelog.com/[Writelog]
|
||||
:wsjtx_group: https://groups.io/g/WSJTX[WSJTX Group]
|
||||
:wsjtx: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]
|
||||
|
BIN
lib/fsk4hf/.DS_Store
vendored
BIN
lib/fsk4hf/.DS_Store
vendored
Binary file not shown.
32
lib/jt9.f90
32
lib/jt9.f90
@ -25,11 +25,11 @@ program jt9
|
||||
integer :: arglen,stat,offset,remain,mode=0,flow=200,fsplit=2700, &
|
||||
fhigh=4000,nrxfreq=1500,ndepth=1,nexp_decode=0,nQSOProg=0
|
||||
logical :: read_files = .true., tx9 = .false., display_help = .false., &
|
||||
bLowSidelobes = .false.
|
||||
type (option) :: long_options(29) = [ &
|
||||
bLowSidelobes = .false., nexp_decode_set = .false.
|
||||
type (option) :: long_options(30) = [ &
|
||||
option ('help', .false., 'h', 'Display this help message', ''), &
|
||||
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'), &
|
||||
option ('executable-path', .true., 'e', &
|
||||
'Location of subordinate executables (KVASD) default PATH="."', &
|
||||
@ -46,6 +46,8 @@ program jt9
|
||||
'Lowest JT9 frequency decoded, default HERTZ=2700', 'HERTZ'), &
|
||||
option ('rx-frequency', .true., 'f', &
|
||||
'Receive frequency offset, default HERTZ=1500', 'HERTZ'), &
|
||||
option ('freq-tolerance', .true., 'F', &
|
||||
'Receive frequency tolerance, default HERTZ=20', 'HERTZ'), &
|
||||
option ('patience', .true., 'w', &
|
||||
'FFTW3 planing patience (0-4), default PATIENCE=1', 'PATIENCE'), &
|
||||
option ('fft-threads', .true., 'm', &
|
||||
@ -54,8 +56,8 @@ program jt9
|
||||
option ('jt4', .false., '4', 'JT4 mode', ''), &
|
||||
option ('ft4', .false., '5', 'FT4 mode', ''), &
|
||||
option ('jt65', .false.,'6', 'JT65 mode', ''), &
|
||||
option ('fst4', .false., '7', 'FST4 mode', ''), &
|
||||
option ('fst4w', .false., 'W', 'FST4W mode', ''), &
|
||||
option ('fst4', .false., '7', 'FST4 mode', ''), &
|
||||
option ('fst4w', .false., 'W', 'FST4W mode', ''), &
|
||||
option ('ft8', .false., '8', 'FT8 mode', ''), &
|
||||
option ('jt9', .false., '9', 'JT9 mode', ''), &
|
||||
option ('qra64', .false., 'q', 'QRA64 mode', ''), &
|
||||
@ -83,10 +85,11 @@ program jt9
|
||||
|
||||
iwspr=0
|
||||
nsubmode = 0
|
||||
ntol = 20
|
||||
TRperiod=60.d0
|
||||
|
||||
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.)
|
||||
if (stat .ne. 0) then
|
||||
exit
|
||||
@ -113,6 +116,8 @@ program jt9
|
||||
read (optarg(:arglen), *) ndepth
|
||||
case ('f')
|
||||
read (optarg(:arglen), *) nrxfreq
|
||||
case ('F')
|
||||
read (optarg(:arglen), *) ntol
|
||||
case ('L')
|
||||
read (optarg(:arglen), *) flow
|
||||
case ('S')
|
||||
@ -153,6 +158,7 @@ program jt9
|
||||
read (optarg(:arglen), *) hisgrid
|
||||
case ('X')
|
||||
read (optarg(:arglen), *) nexp_decode
|
||||
nexp_decode_set = .true.
|
||||
end select
|
||||
end do
|
||||
|
||||
@ -195,6 +201,18 @@ program jt9
|
||||
go to 999
|
||||
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)
|
||||
nflatten=0
|
||||
do iarg = offset + 1, offset + remain
|
||||
@ -258,7 +276,7 @@ program jt9
|
||||
shared_data%params%nfa=flow
|
||||
shared_data%params%nfsplit=fsplit
|
||||
shared_data%params%nfb=fhigh
|
||||
shared_data%params%ntol=20
|
||||
shared_data%params%ntol=ntol
|
||||
shared_data%params%kin=64800
|
||||
if(mode.eq.240) shared_data%params%kin=720000 !### 60 s periods ###
|
||||
shared_data%params%nzhsym=nhsym
|
||||
|
20
lib/wsprd/wspr_params.f90
Normal file
20
lib/wsprd/wspr_params.f90
Normal 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
49
lib/wsprd/wspr_wav.f90
Normal 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
113
lib/wsprd/wsprsimf.f90
Normal 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
|
5
main.cpp
5
main.cpp
@ -9,6 +9,7 @@
|
||||
#include <fftw3.h>
|
||||
|
||||
#include <QSharedMemory>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QTemporaryFile>
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
@ -107,6 +108,8 @@ int main(int argc, char *argv[])
|
||||
// Multiple instances communicate with jt9 via this
|
||||
QSharedMemory mem_jt9;
|
||||
|
||||
auto const env = QProcessEnvironment::systemEnvironment ();
|
||||
|
||||
QApplication a(argc, argv);
|
||||
try
|
||||
{
|
||||
@ -421,7 +424,7 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
// 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();
|
||||
splash.raise ();
|
||||
QObject::connect (&a, SIGNAL (lastWindowClosed()), &a, SLOT (quit()));
|
||||
|
@ -130,6 +130,42 @@ namespace std
|
||||
}
|
||||
#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
|
||||
Q_DECLARE_METATYPE (QHostAddress);
|
||||
|
||||
|
@ -131,6 +131,80 @@ private:
|
||||
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)));
|
||||
}
|
||||
|
||||
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);
|
||||
|
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
93
widgets/CheckableItemComboBox.cpp
Normal file
93
widgets/CheckableItemComboBox.cpp
Normal 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"
|
37
widgets/CheckableItemComboBox.hpp
Normal file
37
widgets/CheckableItemComboBox.hpp
Normal 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
|
@ -10,7 +10,7 @@ class QWidget;
|
||||
//
|
||||
// QComboBox derivative that signals show and hide of the pop up list.
|
||||
//
|
||||
class LazyFillComboBox final
|
||||
class LazyFillComboBox
|
||||
: public QComboBox
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -24,6 +24,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK (5, 12, 0)
|
||||
void showPopup () override
|
||||
{
|
||||
Q_EMIT about_to_show_popup ();
|
||||
@ -35,6 +36,19 @@ public:
|
||||
QComboBox::hidePopup ();
|
||||
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
|
||||
|
@ -2,6 +2,8 @@
|
||||
#ifndef ASTRO_H
|
||||
#define ASTRO_H
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QScopedPointer>
|
||||
|
||||
@ -34,9 +36,17 @@ public:
|
||||
Correction (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 tx;
|
||||
};
|
||||
|
||||
Correction astroUpdate(QDateTime const& t,
|
||||
QString const& mygrid,
|
||||
QString const& hisgrid,
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <QStringListModel>
|
||||
#include <QSettings>
|
||||
#include <QKeyEvent>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QSharedMemory>
|
||||
#include <QFileDialog>
|
||||
#include <QTextBlock>
|
||||
@ -234,8 +235,9 @@ namespace
|
||||
MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
MultiSettings * multi_settings, QSharedMemory *shdmem,
|
||||
unsigned downSampleFactor,
|
||||
QSplashScreen * splash, QWidget *parent) :
|
||||
QSplashScreen * splash, QProcessEnvironment const& env, QWidget *parent) :
|
||||
QMainWindow(parent),
|
||||
m_env {env},
|
||||
m_network_manager {this},
|
||||
m_valid {true},
|
||||
m_splash {splash},
|
||||
@ -267,6 +269,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
m_secBandChanged {0},
|
||||
m_freqNominal {0},
|
||||
m_freqTxNominal {0},
|
||||
m_reverse_Doppler {"1" == env.value ("WSJT_REVERSE_DOPPLER", "0")},
|
||||
m_s6 {0.},
|
||||
m_tRemaining {0.},
|
||||
m_TRperiod {60.0},
|
||||
@ -415,6 +418,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
|
||||
m_messageClient {new MessageClient {QApplication::applicationName (),
|
||||
version (), revision (),
|
||||
m_config.udp_server_name (), m_config.udp_server_port (),
|
||||
m_config.udp_interface_names (), m_config.udp_TTL (),
|
||||
this}},
|
||||
m_psk_Reporter {&m_config, QString {"WSJT-X v" + version () + " " + m_revision}.simplified ()},
|
||||
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::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_TTL_changed, m_messageClient, &MessageClient::set_TTL);
|
||||
connect (&m_config, &Configuration::accept_udp_requests_changed, m_messageClient, &MessageClient::enable);
|
||||
connect (&m_config, &Configuration::enumerating_audio_devices, [this] () {
|
||||
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 ())
|
||||
, "-t", QDir::toNativeSeparators (m_config.temp_dir ().absolutePath ())
|
||||
};
|
||||
QProcessEnvironment env {QProcessEnvironment::systemEnvironment ()};
|
||||
env.insert ("OMP_STACKSIZE", "4M");
|
||||
proc_jt9.setProcessEnvironment (env);
|
||||
QProcessEnvironment new_env {m_env};
|
||||
new_env.insert ("OMP_STACKSIZE", "4M");
|
||||
proc_jt9.setProcessEnvironment (new_env);
|
||||
proc_jt9.start(QDir::toNativeSeparators (m_appDir) + QDir::separator () +
|
||||
"jt9", jt9_args, QIODevice::ReadWrite | QIODevice::Unbuffered);
|
||||
|
||||
@ -1813,6 +1818,7 @@ void MainWindow::on_actionSettings_triggered() //Setup Dialog
|
||||
checkMSK144ContestType();
|
||||
if (m_config.my_callsign () != 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()),
|
||||
const_cast<int *> (icw), &m_ncw, m_config.my_callsign ().length());
|
||||
}
|
||||
@ -4228,7 +4234,15 @@ void MainWindow::guiUpdate()
|
||||
transmitDisplay (true);
|
||||
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_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 ()
|
||||
{
|
||||
if(m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return;
|
||||
// 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 ());
|
||||
ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
|
||||
if (!ui->tx1->isEnabled ()) {
|
||||
// leave time for clicks to complete before setting txrb2
|
||||
QTimer::singleShot (500, ui->txrb2, SLOT (click ()));
|
||||
@ -4575,11 +4595,7 @@ void MainWindow::on_txb1_clicked()
|
||||
|
||||
void MainWindow::on_txb1_doubleClicked()
|
||||
{
|
||||
if (m_mode=="FT8" and SpecOp::HOUND == m_config.special_op_id()) return;
|
||||
// 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 ());
|
||||
ui->tx1->setEnabled (elide_tx1_not_allowed () || !ui->tx1->isEnabled ());
|
||||
}
|
||||
|
||||
void MainWindow::on_txb2_clicked()
|
||||
@ -4917,34 +4933,38 @@ void MainWindow::processMessage (DecodedText const& message, Qt::KeyboardModifie
|
||||
m_QSOProgress=ROGER_REPORT;
|
||||
}
|
||||
} else { // no grid on end of msg
|
||||
QString r=message_words.at (3);
|
||||
if(m_QSOProgress >= ROGER_REPORT && (r=="RRR" || r.toInt()==73 || "RR73" == r)) {
|
||||
if(m_mode=="FT4" and r=="RR73") 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
|
||||
auto const& word_3 = message_words.at (3);
|
||||
bool word_3_is_int;
|
||||
auto word_3_as_number = word_3.toInt (&word_3_is_int);
|
||||
if(m_QSOProgress >= ROGER_REPORT && ("RRR" == word_3
|
||||
|| (word_3_is_int && word_3_as_number == 73)
|
||||
|| "RR73" == word_3)) {
|
||||
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);
|
||||
ui->tx3->setText(ui->tx3->text().remove("TU; "));
|
||||
useNextCall();
|
||||
QString t="TU; " + ui->tx3->text();
|
||||
ui->tx3->setText(t);
|
||||
m_bTUmsg=true;
|
||||
m_ntx=6;
|
||||
ui->txrb6->setChecked(true);
|
||||
} else {
|
||||
if(SpecOp::RTTY == m_config.special_op_id()) {
|
||||
logQSOTimer.start(0);
|
||||
m_ntx=6;
|
||||
ui->txrb6->setChecked(true);
|
||||
} else {
|
||||
m_ntx=5;
|
||||
ui->txrb5->setChecked(true);
|
||||
}
|
||||
m_ntx=5;
|
||||
ui->txrb5->setChecked(true);
|
||||
}
|
||||
}
|
||||
m_QSOProgress = SIGNOFF;
|
||||
} else if((m_QSOProgress >= REPORT
|
||||
|| (m_QSOProgress >= REPLYING &&
|
||||
(m_mode=="MSK144" or m_mode=="FT8" or m_mode=="FT4")))
|
||||
&& r.mid(0,1)=="R") {
|
||||
&& word_3.startsWith ('R')) {
|
||||
m_ntx=4;
|
||||
m_QSOProgress = ROGERS;
|
||||
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];
|
||||
}
|
||||
ui->txrb4->setChecked(true);
|
||||
} else if(m_QSOProgress>=CALLING and
|
||||
((r.toInt()>=-50 && r.toInt()<=49) or (r.toInt()>=529 && r.toInt()<=599))) {
|
||||
if(SpecOp::EU_VHF==m_config.special_op_id() or
|
||||
SpecOp::FIELD_DAY==m_config.special_op_id() or
|
||||
SpecOp::RTTY==m_config.special_op_id()) {
|
||||
setTxMsg(2);
|
||||
m_QSOProgress=REPORT;
|
||||
} else {
|
||||
if(r.left(2)=="R-" or r.left(2)=="R+") {
|
||||
setTxMsg(4);
|
||||
m_QSOProgress=ROGERS;
|
||||
} else if (m_QSOProgress >= CALLING)
|
||||
{
|
||||
if (word_3_is_int
|
||||
&& ((word_3_as_number >= -50 && word_3_as_number <= 49)
|
||||
|| (word_3_as_number >= 529 && word_3_as_number <= 599))) {
|
||||
if(SpecOp::EU_VHF==m_config.special_op_id() or
|
||||
SpecOp::FIELD_DAY==m_config.special_op_id() or
|
||||
SpecOp::RTTY==m_config.special_op_id()) {
|
||||
setTxMsg(2);
|
||||
m_QSOProgress=REPORT;
|
||||
}
|
||||
else {
|
||||
setTxMsg (3);
|
||||
m_QSOProgress = ROGER_REPORT;
|
||||
}
|
||||
} else {
|
||||
setTxMsg(3);
|
||||
m_QSOProgress=ROGER_REPORT;
|
||||
if (word_3.startsWith ("R-") || word_3.startsWith ("R+")) {
|
||||
setTxMsg(4);
|
||||
m_QSOProgress=ROGERS;
|
||||
}
|
||||
}
|
||||
} else { // nothing for us
|
||||
}
|
||||
else
|
||||
{ // nothing for us
|
||||
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 ();
|
||||
}
|
||||
|
||||
bool MainWindow::shortList(QString callsign)
|
||||
bool MainWindow::shortList(QString callsign) const
|
||||
{
|
||||
int n=callsign.length();
|
||||
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"};
|
||||
auto const& time_string = time.toString ("~" == mode || "&" == mode
|
||||
|| "+" == 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
|
||||
.arg (time_string)
|
||||
.arg (snr, 3)
|
||||
.arg (delta_time, 4, 'f', 1)
|
||||
.arg (delta_frequency, 4)
|
||||
.arg (mode, -2)
|
||||
.arg (message_text);
|
||||
.arg (text);
|
||||
QTextCursor start {ui->decodedTextBrowser->document ()};
|
||||
start.movePosition (QTextCursor::End);
|
||||
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 (delta_frequency, 4)
|
||||
.arg (mode, -2)
|
||||
.arg (message_text), start, QTextDocument::FindBackward);
|
||||
.arg (text), start, QTextDocument::FindBackward);
|
||||
}
|
||||
if (!cursor.isNull ())
|
||||
{
|
||||
@ -7703,7 +7738,7 @@ void MainWindow::replyToCQ (QTime time, qint32 snr, float delta_time, quint32 de
|
||||
showNormal ();
|
||||
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
|
||||
m_bDoubleClicked = true;
|
||||
}
|
||||
@ -7817,7 +7852,7 @@ void MainWindow::networkError (QString const& e)
|
||||
, MessageBox::Cancel))
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
m_astroCorrection = correction;
|
||||
if (m_reverse_Doppler)
|
||||
{
|
||||
m_astroCorrection.reverse ();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -9110,15 +9149,15 @@ void MainWindow::write_all(QString txRx, QString message)
|
||||
QString msg;
|
||||
QString mode_string;
|
||||
|
||||
if (message[4]==' ') {
|
||||
if (message.size () > 5 && message[4]==' ') {
|
||||
msg=message.mid(4,-1);
|
||||
} else {
|
||||
msg=message.mid(6,-1);
|
||||
}
|
||||
|
||||
if (message[19]=='#') {
|
||||
if (message.size () > 19 && message[19]=='#') {
|
||||
mode_string="JT65 ";
|
||||
} else if (message[19]=='@') {
|
||||
} else if (message.size () > 19 && message[19]=='@') {
|
||||
mode_string="JT9 ";
|
||||
} else {
|
||||
mode_string=m_mode.leftJustified(6,' ');
|
||||
|
@ -64,6 +64,7 @@ namespace Ui {
|
||||
class MainWindow;
|
||||
}
|
||||
|
||||
class QProcessEnvironment;
|
||||
class QSharedMemory;
|
||||
class QSplashScreen;
|
||||
class QSettings;
|
||||
@ -104,7 +105,7 @@ public:
|
||||
|
||||
explicit MainWindow(QDir const& temp_directory, bool multiple, MultiSettings *,
|
||||
QSharedMemory *shdmem, unsigned downSampleFactor,
|
||||
QSplashScreen *,
|
||||
QSplashScreen *, QProcessEnvironment const&,
|
||||
QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
@ -356,7 +357,9 @@ private:
|
||||
void foxTest();
|
||||
void setColorHighlighting();
|
||||
void chkFT4();
|
||||
bool elide_tx1_not_allowed () const;
|
||||
|
||||
QProcessEnvironment const& m_env;
|
||||
NetworkAccessManager m_network_manager;
|
||||
bool m_valid;
|
||||
QSplashScreen * m_splash;
|
||||
@ -408,6 +411,7 @@ private:
|
||||
Frequency m_freqNominal;
|
||||
Frequency m_freqTxNominal;
|
||||
Astro::Correction m_astroCorrection;
|
||||
bool m_reverse_Doppler;
|
||||
|
||||
double m_s6;
|
||||
double m_tRemaining;
|
||||
@ -707,7 +711,7 @@ private:
|
||||
void stub();
|
||||
void statusChanged();
|
||||
void fixStop();
|
||||
bool shortList(QString callsign);
|
||||
bool shortList(QString callsign) const;
|
||||
void transmit (double snr = 99.);
|
||||
void rigFailure (QString const& reason);
|
||||
void pskSetLocal ();
|
||||
|
@ -10,7 +10,9 @@ SOURCES += \
|
||||
widgets/AbstractLogWindow.cpp \
|
||||
widgets/FrequencyLineEdit.cpp widgets/FrequencyDeltaLineEdit.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 += \
|
||||
widgets/mainwindow.h widgets/plotter.h \
|
||||
widgets/about.h widgets/widegraph.h \
|
||||
@ -22,7 +24,8 @@ HEADERS += \
|
||||
widgets/ExportCabrillo.h widgets/AbstractLogWindow.hpp \
|
||||
widgets/FoxLogWindow.hpp widgets/CabrilloLogWindow.hpp \
|
||||
widgets/DateTimeEdit.hpp widgets/HelpTextWindow.hpp \
|
||||
widgets/RestrictedSpinBox.hpp
|
||||
widgets/RestrictedSpinBox.hpp \
|
||||
widgets/LazyFillComboBox.hpp widgets/CheckableItemComboBox.hpp
|
||||
|
||||
FORMS += \
|
||||
widgets/mainwindow.ui widgets/about.ui \
|
||||
|
Loading…
Reference in New Issue
Block a user