mirror of https://github.com/saitohirga/WSJT-X.git
Merge branch 'release-2.2.0'
This commit is contained in:
commit
83b8caacd3
|
@ -3,3 +3,4 @@
|
|||
/lib/fsk4hf export-ignore
|
||||
/lib/fsk4hf export-ignore
|
||||
/robots export-ignore
|
||||
/plots export-ignore
|
||||
|
|
|
@ -8,3 +8,6 @@ jnq*
|
|||
*.o
|
||||
*.mod
|
||||
*.pro.user
|
||||
*.txt
|
||||
cmake-build-debug
|
||||
cmake-build-release
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
#include "AudioDevice.hpp"
|
||||
|
||||
bool AudioDevice::initialize (OpenMode mode, Channel channel)
|
||||
{
|
||||
m_channel = channel;
|
||||
|
||||
// open and ensure we are unbuffered if possible
|
||||
return QIODevice::open (mode | QIODevice::Unbuffered);
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ namespace
|
|||
{
|
||||
auto end = reinterpret_cast<char const *> (::memchr (id, '\0', 4u));
|
||||
auto len = end ? end - id : 4u;
|
||||
memcpy (id_.data (), id, len);
|
||||
::memcpy (id_.data (), id, len);
|
||||
if (len < 4u)
|
||||
{
|
||||
memset (id_.data () + len, ' ', 4u - len);
|
||||
|
@ -46,7 +46,7 @@ namespace
|
|||
}
|
||||
else
|
||||
{
|
||||
memcpy (id_.data (), "JUNK", 4u);
|
||||
::memcpy (id_.data (), "JUNK", 4u);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,10 +86,10 @@ namespace
|
|||
{
|
||||
// set some sensible defaults for the "bext" fields
|
||||
auto now = QDateTime::currentDateTimeUtc ();
|
||||
std::strncpy (origination_date_,
|
||||
::memcpy (origination_date_,
|
||||
now.date ().toString ("yyyy-MM-dd").toLocal8Bit ().constData (),
|
||||
sizeof origination_date_);
|
||||
std::strncpy (origination_time_,
|
||||
::memcpy (origination_time_,
|
||||
now.time ().toString ("hh-mm-ss").toLocal8Bit ().constData (),
|
||||
sizeof origination_time_);
|
||||
auto uuid = QUuid::createUuid ().toRfc4122 ();
|
||||
|
@ -354,6 +354,7 @@ bool BWFFile::impl::update_header ()
|
|||
{
|
||||
case BextVersion::v_0:
|
||||
data->version_ = qToBigEndian<quint32> (data->version_);
|
||||
// fall through
|
||||
default:
|
||||
data->loudness_value_ = qToBigEndian<quint16> (data->loudness_value_);
|
||||
data->loudness_range_ = qToBigEndian<quint16> (data->loudness_range_);
|
||||
|
@ -370,6 +371,7 @@ bool BWFFile::impl::update_header ()
|
|||
{
|
||||
case BextVersion::v_0:
|
||||
data->version_ = qToLittleEndian<quint32> (data->version_);
|
||||
// fall through
|
||||
default:
|
||||
data->loudness_value_ = qToLittleEndian<quint16> (data->loudness_value_);
|
||||
data->loudness_range_ = qToLittleEndian<quint16> (data->loudness_range_);
|
||||
|
@ -686,7 +688,7 @@ QByteArray BWFFile::bext_description () const
|
|||
void BWFFile::bext_description (QByteArray const& description)
|
||||
{
|
||||
m_->header_dirty_ = true;
|
||||
std::strncpy (m_->bext ()->description_, description.constData (), sizeof (BroadcastAudioExtension::description_));
|
||||
::memcpy (m_->bext ()->description_, description.constData (), sizeof BroadcastAudioExtension::description_);
|
||||
}
|
||||
|
||||
QByteArray BWFFile::bext_originator () const
|
||||
|
@ -698,7 +700,7 @@ QByteArray BWFFile::bext_originator () const
|
|||
void BWFFile::bext_originator (QByteArray const& originator)
|
||||
{
|
||||
m_->header_dirty_ = true;
|
||||
std::strncpy (m_->bext ()->originator_, originator.constData (), sizeof (BroadcastAudioExtension::originator_));
|
||||
::memcpy (m_->bext ()->originator_, originator.constData (), sizeof BroadcastAudioExtension::originator_);
|
||||
}
|
||||
|
||||
QByteArray BWFFile::bext_originator_reference () const
|
||||
|
@ -710,7 +712,7 @@ QByteArray BWFFile::bext_originator_reference () const
|
|||
void BWFFile::bext_originator_reference (QByteArray const& reference)
|
||||
{
|
||||
m_->header_dirty_ = true;
|
||||
std::strncpy (m_->bext ()->originator_reference_, reference.constData (), sizeof (BroadcastAudioExtension::originator_reference_));
|
||||
::memcpy (m_->bext ()->originator_reference_, reference.constData (), sizeof BroadcastAudioExtension::originator_reference_);
|
||||
}
|
||||
|
||||
QDateTime BWFFile::bext_origination_date_time () const
|
||||
|
@ -723,12 +725,12 @@ QDateTime BWFFile::bext_origination_date_time () const
|
|||
void BWFFile::bext_origination_date_time (QDateTime const& dt)
|
||||
{
|
||||
m_->header_dirty_ = true;
|
||||
std::strncpy (m_->bext ()->origination_date_,
|
||||
::memcpy (m_->bext ()->origination_date_,
|
||||
dt.date ().toString ("yyyy-MM-dd").toLocal8Bit ().constData (),
|
||||
sizeof (BroadcastAudioExtension::origination_date_));
|
||||
std::strncpy (m_->bext ()->origination_time_,
|
||||
sizeof BroadcastAudioExtension::origination_date_);
|
||||
::memcpy (m_->bext ()->origination_time_,
|
||||
dt.time ().toString ("hh-mm-ss").toLocal8Bit ().constData (),
|
||||
sizeof (BroadcastAudioExtension::origination_time_));
|
||||
sizeof BroadcastAudioExtension::origination_time_);
|
||||
}
|
||||
|
||||
quint64 BWFFile::bext_time_reference () const
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
// -*- Mode: C++ -*-
|
||||
#ifndef SOUNDIN_H__
|
||||
#define SOUNDIN_H__
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QDateTime>
|
||||
#include <QScopedPointer>
|
||||
#include <QPointer>
|
||||
#include <QAudioInput>
|
||||
|
||||
#include "Audio/AudioDevice.hpp"
|
||||
|
||||
class QAudioDeviceInfo;
|
||||
class QAudioInput;
|
||||
|
||||
// Gets audio data from sound sample source and passes it to a sink device
|
||||
class SoundInput
|
||||
: public QObject
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
SoundInput (QObject * parent = nullptr)
|
||||
: QObject {parent}
|
||||
, m_sink {nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
~SoundInput ();
|
||||
|
||||
// sink must exist from the start call until the next start call or
|
||||
// stop call
|
||||
Q_SLOT void start(QAudioDeviceInfo const&, int framesPerBuffer, AudioDevice * sink, unsigned downSampleFactor, AudioDevice::Channel = AudioDevice::Mono);
|
||||
Q_SLOT void suspend ();
|
||||
Q_SLOT void resume ();
|
||||
Q_SLOT void stop ();
|
||||
|
||||
Q_SIGNAL void error (QString message) const;
|
||||
Q_SIGNAL void status (QString message) const;
|
||||
|
||||
private:
|
||||
// used internally
|
||||
Q_SLOT void handleStateChanged (QAudio::State) const;
|
||||
|
||||
bool audioError () const;
|
||||
|
||||
QScopedPointer<QAudioInput> m_stream;
|
||||
QPointer<AudioDevice> m_sink;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,10 +0,0 @@
|
|||
#include "AudioDevice.hpp"
|
||||
|
||||
bool AudioDevice::initialize (OpenMode mode, Channel channel)
|
||||
{
|
||||
m_channel = channel;
|
||||
|
||||
// open and ensure we are unbuffered if possible
|
||||
return QIODevice::open (mode | QIODevice::Unbuffered);
|
||||
}
|
||||
|
109
CMakeLists.txt
109
CMakeLists.txt
|
@ -10,15 +10,15 @@ if (APPLE)
|
|||
#
|
||||
# otool -l <binary> | grep -A3 LC_VERSION_MIN_MACOSX
|
||||
#
|
||||
set (CMAKE_OSX_DEPLOYMENT_TARGET 10.10
|
||||
set (CMAKE_OSX_DEPLOYMENT_TARGET 10.13
|
||||
CACHE STRING "Earliest version of OS X supported
|
||||
|
||||
Earliest version we can support with Qt 5.8, C++11 & libc++ is 10.10.
|
||||
Earliest version we can support with Qt 5.14, C++11 & libc++ is 10.13.
|
||||
Do not override this if you intend to build an official deployable installer.")
|
||||
set (CMAKE_OSX_SYSROOT /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk
|
||||
CACHE STRING "Mac OS X SDK to build with
|
||||
|
||||
Change this to the newest SDK available that you can install on your system (10.11 preferred).
|
||||
Change this to the newest SDK available that you can install on your system (10.14 preferred).
|
||||
Do not override this if you intend to build an official deployable installer.")
|
||||
endif (APPLE)
|
||||
|
||||
|
@ -224,9 +224,10 @@ set (WSJT_QT_CONF_DESTINATION ${QT_CONF_DESTINATION} CACHE PATH "Path for the qt
|
|||
#
|
||||
set (wsjt_qt_CXXSRCS
|
||||
qt_helpers.cpp
|
||||
lib/shmem.cpp
|
||||
widgets/MessageBox.cpp
|
||||
MetaDataRegistry.cpp
|
||||
NetworkServerLookup.cpp
|
||||
Network/NetworkServerLookup.cpp
|
||||
revision_utils.cpp
|
||||
WFPalette.cpp
|
||||
Radio.cpp
|
||||
|
@ -243,17 +244,17 @@ set (wsjt_qt_CXXSRCS
|
|||
validators/LiveFrequencyValidator.cpp
|
||||
GetUserId.cpp
|
||||
TraceFile.cpp
|
||||
AudioDevice.cpp
|
||||
Transceiver.cpp
|
||||
TransceiverBase.cpp
|
||||
EmulateSplitTransceiver.cpp
|
||||
TransceiverFactory.cpp
|
||||
PollingTransceiver.cpp
|
||||
HamlibTransceiver.cpp
|
||||
HRDTransceiver.cpp
|
||||
DXLabSuiteCommanderTransceiver.cpp
|
||||
NetworkMessage.cpp
|
||||
MessageClient.cpp
|
||||
Audio/AudioDevice.cpp
|
||||
Transceiver/Transceiver.cpp
|
||||
Transceiver/TransceiverBase.cpp
|
||||
Transceiver/EmulateSplitTransceiver.cpp
|
||||
Transceiver/TransceiverFactory.cpp
|
||||
Transceiver/PollingTransceiver.cpp
|
||||
Transceiver/HamlibTransceiver.cpp
|
||||
Transceiver/HRDTransceiver.cpp
|
||||
Transceiver/DXLabSuiteCommanderTransceiver.cpp
|
||||
Network/NetworkMessage.cpp
|
||||
Network/MessageClient.cpp
|
||||
widgets/LettersSpinBox.cpp
|
||||
widgets/HintedSpinBox.cpp
|
||||
widgets/RestrictedSpinBox.cpp
|
||||
|
@ -271,7 +272,7 @@ set (wsjt_qt_CXXSRCS
|
|||
EqualizationToolsDialog.cpp
|
||||
widgets/DoubleClickablePushButton.cpp
|
||||
widgets/DoubleClickableRadioButton.cpp
|
||||
LotWUsers.cpp
|
||||
Network/LotWUsers.cpp
|
||||
models/DecodeHighlightingModel.cpp
|
||||
widgets/DecodeHighlightingListView.cpp
|
||||
models/FoxLog.cpp
|
||||
|
@ -282,6 +283,7 @@ set (wsjt_qt_CXXSRCS
|
|||
item_delegates/MaidenheadLocatorDelegate.cpp
|
||||
item_delegates/FrequencyDelegate.cpp
|
||||
item_delegates/FrequencyDeltaDelegate.cpp
|
||||
item_delegates/SQLiteDateTimeDelegate.cpp
|
||||
models/CabrilloLog.cpp
|
||||
logbook/AD1CCty.cpp
|
||||
logbook/WorkedBefore.cpp
|
||||
|
@ -297,21 +299,17 @@ set (jt9_FSRCS
|
|||
lib/jt9a.f90
|
||||
)
|
||||
|
||||
set (jt9_CXXSRCS
|
||||
lib/ipcomm.cpp
|
||||
)
|
||||
|
||||
set (wsjtx_CXXSRCS
|
||||
logbook/logbook.cpp
|
||||
psk_reporter.cpp
|
||||
Modulator.cpp
|
||||
Detector.cpp
|
||||
Network/psk_reporter.cpp
|
||||
Modulator/Modulator.cpp
|
||||
Detector/Detector.cpp
|
||||
widgets/logqso.cpp
|
||||
widgets/displaytext.cpp
|
||||
decodedtext.cpp
|
||||
Decoder/decodedtext.cpp
|
||||
getfile.cpp
|
||||
soundout.cpp
|
||||
soundin.cpp
|
||||
Audio/soundout.cpp
|
||||
Audio/soundin.cpp
|
||||
widgets/meterwidget.cpp
|
||||
widgets/signalmeter.cpp
|
||||
widgets/plotter.cpp
|
||||
|
@ -324,12 +322,12 @@ set (wsjtx_CXXSRCS
|
|||
widgets/astro.cpp
|
||||
widgets/messageaveraging.cpp
|
||||
widgets/colorhighlighting.cpp
|
||||
WsprTxScheduler.cpp
|
||||
WSPR/WsprTxScheduler.cpp
|
||||
widgets/mainwindow.cpp
|
||||
Configuration.cpp
|
||||
main.cpp
|
||||
wsprnet.cpp
|
||||
WSPRBandHopping.cpp
|
||||
Network/wsprnet.cpp
|
||||
WSPR/WSPRBandHopping.cpp
|
||||
widgets/ExportCabrillo.cpp
|
||||
)
|
||||
|
||||
|
@ -353,12 +351,13 @@ if (WIN32)
|
|||
|
||||
set (wsjt_qt_CXXSRCS
|
||||
${wsjt_qt_CXXSRCS}
|
||||
OmniRigTransceiver.cpp
|
||||
Transceiver/OmniRigTransceiver.cpp
|
||||
)
|
||||
endif (WIN32)
|
||||
|
||||
set (wsjt_FSRCS
|
||||
# put module sources first in the hope that they get rebuilt before use
|
||||
lib/shmem.f90
|
||||
lib/crc.f90
|
||||
lib/fftw3mod.f90
|
||||
lib/hashing.f90
|
||||
|
@ -416,6 +415,7 @@ set (wsjt_FSRCS
|
|||
lib/decode65a.f90
|
||||
lib/decode65b.f90
|
||||
lib/decode9w.f90
|
||||
lib/ft8/decode174_91.f90
|
||||
lib/decoder.f90
|
||||
lib/deep4.f90
|
||||
lib/deg2grid.f90
|
||||
|
@ -427,15 +427,13 @@ set (wsjt_FSRCS
|
|||
lib/encode4.f90
|
||||
lib/encode_msk40.f90
|
||||
lib/encode_128_90.f90
|
||||
lib/ft8/encode174.f90
|
||||
lib/ft8/encode174_91.f90
|
||||
lib/ft8/encode174_91_nocrc.f90
|
||||
lib/entail.f90
|
||||
lib/ephem.f90
|
||||
lib/extract.f90
|
||||
lib/extract4.f90
|
||||
lib/extractmessage77.f90
|
||||
lib/ft8/extractmessage174.f90
|
||||
lib/ft8/extractmessage174_91.f90
|
||||
lib/fano232.f90
|
||||
lib/fast9.f90
|
||||
lib/fast_decode.f90
|
||||
|
@ -461,16 +459,12 @@ set (wsjt_FSRCS
|
|||
lib/ft8/foxfilt.f90
|
||||
lib/ft8/foxgen.f90
|
||||
lib/ft8/foxgen_wrap.f90
|
||||
lib/fqso_first.f90
|
||||
lib/freqcal.f90
|
||||
lib/ft8/ft8apset.f90
|
||||
lib/ft8/ft8b.f90
|
||||
lib/ft8/ft8code.f90
|
||||
lib/ft8/ft8_downsample.f90
|
||||
lib/ft8.f90
|
||||
lib/ft8dec.f90
|
||||
lib/ft8/ft8sim.f90
|
||||
lib/ft8/ft8sim_gfsk.f90
|
||||
lib/gen4.f90
|
||||
lib/gen65.f90
|
||||
lib/gen9.f90
|
||||
|
@ -478,6 +472,7 @@ set (wsjt_FSRCS
|
|||
lib/ft8/genft8.f90
|
||||
lib/genmsk_128_90.f90
|
||||
lib/genmsk40.f90
|
||||
lib/ft4/ft4code.f90
|
||||
lib/ft4/genft4.f90
|
||||
lib/ft4/gen_ft4wave.f90
|
||||
lib/ft8/gen_ft8wave.f90
|
||||
|
@ -485,8 +480,10 @@ set (wsjt_FSRCS
|
|||
lib/ft8/genft8refsig.f90
|
||||
lib/genwspr.f90
|
||||
lib/geodist.f90
|
||||
lib/ft8/get_crc14.f90
|
||||
lib/getlags.f90
|
||||
lib/getmet4.f90
|
||||
lib/ft8/get_spectrum_baseline.f90
|
||||
lib/ft2/gfsk_pulse.f90
|
||||
lib/graycode.f90
|
||||
lib/graycode65.f90
|
||||
|
@ -533,6 +530,7 @@ set (wsjt_FSRCS
|
|||
lib/77bit/my_hash.f90
|
||||
lib/wsprd/osdwspr.f90
|
||||
lib/ft8/osd174_91.f90
|
||||
lib/osd128_90.f90
|
||||
lib/pctile.f90
|
||||
lib/peakdt9.f90
|
||||
lib/peakup.f90
|
||||
|
@ -682,13 +680,13 @@ set (wsjtx_UISRCS
|
|||
set (UDP_library_CXXSRCS
|
||||
Radio.cpp
|
||||
RadioMetaType.cpp
|
||||
NetworkMessage.cpp
|
||||
MessageServer.cpp
|
||||
Network/NetworkMessage.cpp
|
||||
UDPExamples/MessageServer.cpp
|
||||
)
|
||||
|
||||
set (UDP_library_HEADERS
|
||||
Radio.hpp
|
||||
MessageServer.hpp
|
||||
UDPExamples/MessageServer.hpp
|
||||
${PROJECT_BINARY_DIR}/udp_export.h
|
||||
)
|
||||
|
||||
|
@ -713,7 +711,6 @@ set (all_CXXSRCS
|
|||
${wsjt_CXXSRCS}
|
||||
${wsjt_qt_CXXSRCS}
|
||||
${wsjt_qtmm_CXXSRCS}
|
||||
${jt9_CXXSRCS}
|
||||
${wsjtx_CXXSRCS}
|
||||
${qcp_CXXSRCS}
|
||||
)
|
||||
|
@ -790,6 +787,10 @@ endif (APPLE)
|
|||
|
||||
set_source_files_properties (${WSJTX_ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
|
||||
# suppress intransigent compiler diagnostics
|
||||
set_source_files_properties (lib/decoder.f90 PROPERTIES COMPILE_FLAGS "-Wno-unused-dummy-argument")
|
||||
set_source_files_properties (lib/filbig.f90 PROPERTIES COMPILE_FLAGS "-Wno-aliasing")
|
||||
|
||||
if (WSJT_QDEBUG_IN_RELEASE)
|
||||
# context info in Qt message handler in release configuration
|
||||
set_property (DIRECTORY APPEND PROPERTY
|
||||
|
@ -919,7 +920,7 @@ set (CMAKE_VISIBILITY_INLINES_HIDDEN ON)
|
|||
#
|
||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra")
|
||||
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fexceptions -frtti")
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror -Wall -Wextra -fexceptions -frtti")
|
||||
|
||||
if (NOT APPLE)
|
||||
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-pragmas")
|
||||
|
@ -1219,7 +1220,7 @@ add_executable (fcal lib/fcal.f90 wsjtx.rc)
|
|||
|
||||
add_executable (fmeasure lib/fmeasure.f90 wsjtx.rc)
|
||||
|
||||
add_executable (jt9 ${jt9_FSRCS} ${jt9_CXXSRCS} wsjtx.rc)
|
||||
add_executable (jt9 ${jt9_FSRCS} wsjtx.rc)
|
||||
if (${OPENMP_FOUND} OR APPLE)
|
||||
if (APPLE)
|
||||
# On Mac we don't have working OpenMP support in the C/C++
|
||||
|
@ -1248,11 +1249,11 @@ if (${OPENMP_FOUND} OR APPLE)
|
|||
)
|
||||
endif (APPLE)
|
||||
if (WIN32)
|
||||
# set_target_properties (jt9 PROPERTIES
|
||||
# LINK_FLAGS -Wl,--stack,16777216
|
||||
# )
|
||||
set_target_properties (jt9 PROPERTIES
|
||||
LINK_FLAGS -Wl,--stack,16777216
|
||||
)
|
||||
endif ()
|
||||
target_link_libraries (jt9 wsjt_fort_omp wsjt_cxx Qt5::Core)
|
||||
target_link_libraries (jt9 wsjt_fort_omp wsjt_cxx wsjt_qt)
|
||||
else (${OPENMP_FOUND} OR APPLE)
|
||||
target_link_libraries (jt9 wsjt_fort wsjt_cxx Qt5::Core)
|
||||
endif (${OPENMP_FOUND} OR APPLE)
|
||||
|
@ -1264,6 +1265,9 @@ target_link_libraries (jt4sim wsjt_fort wsjt_cxx)
|
|||
add_executable (jt65sim lib/jt65sim.f90 wsjtx.rc)
|
||||
target_link_libraries (jt65sim wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (sumsim lib/sumsim.f90 wsjtx.rc)
|
||||
target_link_libraries (sumsim wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (qra64sim lib/qra/qra64/qra64sim.f90 wsjtx.rc)
|
||||
target_link_libraries (qra64sim wsjt_fort wsjt_cxx)
|
||||
|
||||
|
@ -1289,6 +1293,9 @@ add_executable (wsprcode lib/wsprcode/wsprcode.f90 lib/wsprcode/nhash.c
|
|||
wsjtx.rc)
|
||||
target_link_libraries (wsprcode wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (encode77 lib/77bit/encode77.f90 wsjtx.rc)
|
||||
target_link_libraries (encode77 wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (wsprsim ${wsprsim_CSRCS})
|
||||
|
||||
add_executable (jt4code lib/jt4code.f90 wsjtx.rc)
|
||||
|
@ -1303,15 +1310,12 @@ target_link_libraries (jt65 wsjt_fort wsjt_cxx)
|
|||
add_executable (ft8code lib/ft8/ft8code.f90 wsjtx.rc)
|
||||
target_link_libraries (ft8code wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (ft8 lib/ft8.f90 wsjtx.rc)
|
||||
target_link_libraries (ft8 wsjt_fort wsjt_cxx)
|
||||
add_executable (ft4code lib/ft4/ft4code.f90 wsjtx.rc)
|
||||
target_link_libraries (ft4code wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (ft8sim lib/ft8/ft8sim.f90 wsjtx.rc)
|
||||
target_link_libraries (ft8sim wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (ft8sim_gfsk lib/ft8/ft8sim_gfsk.f90 wsjtx.rc)
|
||||
target_link_libraries (ft8sim_gfsk wsjt_fort wsjt_cxx)
|
||||
|
||||
add_executable (msk144sim lib/msk144sim.f90 wsjtx.rc)
|
||||
target_link_libraries (msk144sim wsjt_fort wsjt_cxx)
|
||||
|
||||
|
@ -1628,6 +1632,7 @@ if (NOT is_debug_build)
|
|||
PATTERN "*minimal*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE
|
||||
PATTERN "*offscreen*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE
|
||||
PATTERN "*quick*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE
|
||||
PATTERN "*webgl*${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE
|
||||
PATTERN "*_debug${CMAKE_SHARED_LIBRARY_SUFFIX}" EXCLUDE
|
||||
)
|
||||
install (
|
||||
|
|
|
@ -172,18 +172,18 @@
|
|||
#include "item_delegates/ForeignKeyDelegate.hpp"
|
||||
#include "item_delegates/FrequencyDelegate.hpp"
|
||||
#include "item_delegates/FrequencyDeltaDelegate.hpp"
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "Transceiver.hpp"
|
||||
#include "Transceiver/TransceiverFactory.hpp"
|
||||
#include "Transceiver/Transceiver.hpp"
|
||||
#include "models/Bands.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
#include "models/Modes.hpp"
|
||||
#include "models/FrequencyList.hpp"
|
||||
#include "models/StationList.hpp"
|
||||
#include "NetworkServerLookup.hpp"
|
||||
#include "Network/NetworkServerLookup.hpp"
|
||||
#include "widgets/MessageBox.hpp"
|
||||
#include "validators/MaidenheadLocatorValidator.hpp"
|
||||
#include "validators/CallsignValidator.hpp"
|
||||
#include "LotWUsers.hpp"
|
||||
#include "Network/LotWUsers.hpp"
|
||||
#include "models/DecodeHighlightingModel.hpp"
|
||||
#include "logbook/logbook.h"
|
||||
|
||||
|
@ -1084,6 +1084,7 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
|
|||
ui_->special_op_activity_button_group->setId (ui_->rbEU_VHF_Contest, static_cast<int> (SpecialOperatingActivity::EU_VHF));
|
||||
ui_->special_op_activity_button_group->setId (ui_->rbField_Day, static_cast<int> (SpecialOperatingActivity::FIELD_DAY));
|
||||
ui_->special_op_activity_button_group->setId (ui_->rbRTTY_Roundup, static_cast<int> (SpecialOperatingActivity::RTTY));
|
||||
ui_->special_op_activity_button_group->setId (ui_->rbWW_DIGI, static_cast<int> (SpecialOperatingActivity::WW_DIGI));
|
||||
ui_->special_op_activity_button_group->setId (ui_->rbFox, static_cast<int> (SpecialOperatingActivity::FOX));
|
||||
ui_->special_op_activity_button_group->setId (ui_->rbHound, static_cast<int> (SpecialOperatingActivity::HOUND));
|
||||
|
||||
|
@ -1383,8 +1384,8 @@ void Configuration::impl::read_settings ()
|
|||
txDelay_ = settings_->value ("TxDelay",0.2).toDouble();
|
||||
aggressive_ = settings_->value ("Aggressive", 0).toInt ();
|
||||
RxBandwidth_ = settings_->value ("RxBandwidth", 2500).toInt ();
|
||||
save_directory_ = settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ();
|
||||
azel_directory_ = settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ();
|
||||
save_directory_.setPath (settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ());
|
||||
azel_directory_.setPath (settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ());
|
||||
|
||||
{
|
||||
//
|
||||
|
@ -2069,8 +2070,8 @@ void Configuration::impl::accept ()
|
|||
TX_messages_ = ui_->TX_messages_check_box->isChecked ();
|
||||
data_mode_ = static_cast<DataMode> (ui_->TX_mode_button_group->checkedId ());
|
||||
bLowSidelobes_ = ui_->rbLowSidelobes->isChecked();
|
||||
save_directory_ = ui_->save_path_display_label->text ();
|
||||
azel_directory_ = ui_->azel_path_display_label->text ();
|
||||
save_directory_.setPath (ui_->save_path_display_label->text ());
|
||||
azel_directory_.setPath (ui_->azel_path_display_label->text ());
|
||||
enable_VHF_features_ = ui_->enable_VHF_features_check_box->isChecked ();
|
||||
decode_at_52s_ = ui_->decode_at_52s_check_box->isChecked ();
|
||||
single_decode_ = ui_->single_decode_check_box->isChecked ();
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
|
||||
#include "Radio.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
#include "AudioDevice.hpp"
|
||||
#include "Transceiver.hpp"
|
||||
#include "Audio/AudioDevice.hpp"
|
||||
#include "Transceiver/Transceiver.hpp"
|
||||
|
||||
#include "pimpl_h.hpp"
|
||||
|
||||
|
@ -180,7 +180,7 @@ public:
|
|||
bool highlight_only_fields () const;
|
||||
bool include_WAE_entities () const;
|
||||
|
||||
enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, FOX, HOUND};
|
||||
enum class SpecialOperatingActivity {NONE, NA_VHF, EU_VHF, FIELD_DAY, RTTY, WW_DIGI, FOX, HOUND};
|
||||
SpecialOperatingActivity special_op_id () const;
|
||||
|
||||
struct CalibrationParams
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>554</width>
|
||||
<height>563</height>
|
||||
<width>670</width>
|
||||
<height>617</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -559,7 +559,7 @@ quiet period when decoding is done.</string>
|
|||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<property name="currentText" stdset="0">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="insertPolicy">
|
||||
|
@ -951,7 +951,7 @@ a few, particularly some Kenwood rigs, require it).</string>
|
|||
<item row="0" column="1">
|
||||
<widget class="QRadioButton" name="PTT_DTR_radio_button">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Use the RS-232 DTR control line to toggle your radio's PTT, requires hardware to inteface the line.</p><p>Some commercial interface units also use this method.</p><p>The DTR control line of the CAT serial port may be used for this or a DTR control line on a different serial port may be used.</p></body></html></string>
|
||||
<string><html><head/><body><p>Use the RS-232 DTR control line to toggle your radio's PTT, requires hardware to interface the line.</p><p>Some commercial interface units also use this method.</p><p>The DTR control line of the CAT serial port may be used for this or a DTR control line on a different serial port may be used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&DTR</string>
|
||||
|
@ -982,7 +982,7 @@ other hardware interface for PTT.</string>
|
|||
<item row="1" column="1">
|
||||
<widget class="QRadioButton" name="PTT_RTS_radio_button">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Use the RS-232 RTS control line to toggle your radio's PTT, requires hardware to inteface the line.</p><p>Some commercial interface units also use this method.</p><p>The RTS control line of the CAT serial port may be used for this or a RTS control line on a different serial port may be used. Note that this option is not available on the CAT serial port when hardware flow control is used.</p></body></html></string>
|
||||
<string><html><head/><body><p>Use the RS-232 RTS control line to toggle your radio's PTT, requires hardware to interface the line.</p><p>Some commercial interface units also use this method.</p><p>The RTS control line of the CAT serial port may be used for this or a RTS control line on a different serial port may be used. Note that this option is not available on the CAT serial port when hardware flow control is used.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>R&TS</string>
|
||||
|
@ -1021,7 +1021,7 @@ other hardware interface for PTT.</string>
|
|||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="currentText">
|
||||
<property name="currentText" stdset="0">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
|
@ -1080,7 +1080,7 @@ or bandwidth is selected).</string>
|
|||
<item row="0" column="2">
|
||||
<widget class="QRadioButton" name="mode_data_radio_button">
|
||||
<property name="toolTip">
|
||||
<string>If this is availabe then it is usually the correct mode for this program.</string>
|
||||
<string>If this is available then it is usually the correct mode for this program.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Data/P&kt</string>
|
||||
|
@ -2261,9 +2261,6 @@ Right click for insert and delete options.</string>
|
|||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable or disable using the check boxes and right-click an item to change or unset the foreground color, background color, or reset the item to default values. Drag and drop the items to change their priority, higher in the list is higher in priority.</p><p>Note that each foreground or background color may be either set or unset, unset means that it is not allocated for that item's type and lower priority items may apply.</p></body></html></string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
|
@ -2572,6 +2569,19 @@ Right click for insert and delete options.</string>
|
|||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="3">
|
||||
<spacer name="horizontalSpacer_11">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_18" stretch="2,1,1">
|
||||
<item>
|
||||
|
@ -2635,19 +2645,6 @@ Right click for insert and delete options.</string>
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="3">
|
||||
<spacer name="horizontalSpacer_11">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_17" stretch="2,1,1">
|
||||
<item>
|
||||
|
@ -2711,6 +2708,25 @@ Right click for insert and delete options.</string>
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QRadioButton" name="rbWW_DIGI">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>18</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>World-Wide Digi-mode contest</p><p><br/></p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>WW Digi Contest</string>
|
||||
</property>
|
||||
<attribute name="buttonGroup">
|
||||
<string notr="true">special_op_activity_button_group</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -2856,15 +2872,9 @@ Right click for insert and delete options.</string>
|
|||
<property name="title">
|
||||
<string>Waterfall spectra</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_16">
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbLowSidelobes">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>20</y>
|
||||
<width>91</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Low sidelobes</string>
|
||||
</property>
|
||||
|
@ -2872,19 +2882,15 @@ Right click for insert and delete options.</string>
|
|||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="rbMaxSensitivity">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>120</x>
|
||||
<y>20</y>
|
||||
<width>92</width>
|
||||
<height>17</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Most sensitive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -3103,12 +3109,12 @@ Right click for insert and delete options.</string>
|
|||
</connections>
|
||||
<buttongroups>
|
||||
<buttongroup name="split_mode_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="special_op_activity_button_group"/>
|
||||
<buttongroup name="CAT_handshake_button_group"/>
|
||||
<buttongroup name="PTT_method_button_group"/>
|
||||
<buttongroup name="CAT_data_bits_button_group"/>
|
||||
<buttongroup name="CAT_stop_bits_button_group"/>
|
||||
<buttongroup name="TX_mode_button_group"/>
|
||||
<buttongroup name="TX_audio_source_button_group"/>
|
||||
</buttongroups>
|
||||
</ui>
|
||||
|
|
|
@ -1,506 +0,0 @@
|
|||
#include "DXLabSuiteCommanderTransceiver.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QRegularExpression>
|
||||
#include <QLocale>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "NetworkServerLookup.hpp"
|
||||
|
||||
#include "moc_DXLabSuiteCommanderTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
char const * const commander_transceiver_name {"DX Lab Suite Commander"};
|
||||
|
||||
QString map_mode (Transceiver::MODE mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Transceiver::AM: return "AM";
|
||||
case Transceiver::CW: return "CW";
|
||||
case Transceiver::CW_R: return "CW-R";
|
||||
case Transceiver::USB: return "USB";
|
||||
case Transceiver::LSB: return "LSB";
|
||||
case Transceiver::FSK: return "RTTY";
|
||||
case Transceiver::FSK_R: return "RTTY-R";
|
||||
case Transceiver::DIG_L: return "DATA-L";
|
||||
case Transceiver::DIG_U: return "DATA-U";
|
||||
case Transceiver::FM:
|
||||
case Transceiver::DIG_FM:
|
||||
return "FM";
|
||||
default: break;
|
||||
}
|
||||
return "USB";
|
||||
}
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
|
||||
{
|
||||
(*registry)[commander_transceiver_name] = TransceiverFactory::Capabilities {id, TransceiverFactory::Capabilities::network, true};
|
||||
}
|
||||
|
||||
DXLabSuiteCommanderTransceiver::DXLabSuiteCommanderTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
QString const& address, bool use_for_ptt,
|
||||
int poll_interval, QObject * parent)
|
||||
: PollingTransceiver {poll_interval, parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, use_for_ptt_ {use_for_ptt}
|
||||
, server_ {address}
|
||||
, commander_ {nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
int DXLabSuiteCommanderTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "starting");
|
||||
if (wrapped_) wrapped_->start (0);
|
||||
|
||||
auto server_details = network_server_lookup (server_, 52002u, QHostAddress::LocalHost, QAbstractSocket::IPv4Protocol);
|
||||
|
||||
if (!commander_)
|
||||
{
|
||||
commander_ = new QTcpSocket {this}; // QObject takes ownership
|
||||
}
|
||||
|
||||
commander_->connectToHost (std::get<0> (server_details), std::get<1> (server_details));
|
||||
if (!commander_->waitForConnected ())
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to connect" << commander_->errorString ());
|
||||
throw error {tr ("Failed to connect to DX Lab Suite Commander\n") + commander_->errorString ()};
|
||||
}
|
||||
|
||||
// sleeps here are to ensure Commander has actually queried the rig
|
||||
// rather than returning cached data which maybe stale or simply
|
||||
// read backs of not yet committed values, the 2s delays are
|
||||
// arbitrary but hopefully enough as the actual time required is rig
|
||||
// and Commander setting dependent
|
||||
int resolution {0};
|
||||
QThread::msleep (2000);
|
||||
auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f && !(f % 10))
|
||||
{
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
auto f_string = frequency_to_string (test_frequency);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
QThread::msleep (2000);
|
||||
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
switch (static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
|
||||
{
|
||||
case -5: resolution = -1; break; // 10Hz truncated
|
||||
case 5: resolution = 1; break; // 10Hz rounded
|
||||
case -15: resolution = -2; break; // 20Hz truncated
|
||||
case -55: resolution = -2; break; // 100Hz truncated
|
||||
case 45: resolution = 2; break; // 100Hz rounded
|
||||
}
|
||||
if (1 == resolution) // may be 20Hz rounded
|
||||
{
|
||||
test_frequency = f - f % 100 + 51;
|
||||
f_string = frequency_to_string (test_frequency);
|
||||
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
QThread::msleep (2000);
|
||||
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
|
||||
{
|
||||
resolution = 2; // 20Hz rounded
|
||||
}
|
||||
}
|
||||
f_string = frequency_to_string (f);
|
||||
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
|
||||
}
|
||||
|
||||
do_poll ();
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_stop ()
|
||||
{
|
||||
if (commander_)
|
||||
{
|
||||
commander_->close ();
|
||||
delete commander_, commander_ = nullptr;
|
||||
}
|
||||
|
||||
if (wrapped_) wrapped_->stop ();
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_ptt (bool on)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", on << state ());
|
||||
if (use_for_ptt_)
|
||||
{
|
||||
simple_command (on ? "<command:5>CmdTX<parameters:0>" : "<command:5>CmdRX<parameters:0>");
|
||||
|
||||
bool tx {!on};
|
||||
auto start = QDateTime::currentMSecsSinceEpoch ();
|
||||
// we must now wait for Tx on the rig, we will wait a short while
|
||||
// before bailing out
|
||||
while (tx != on && QDateTime::currentMSecsSinceEpoch () - start < 1000)
|
||||
{
|
||||
auto reply = command_with_reply ("<command:9>CmdSendTx<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdTX:"))
|
||||
{
|
||||
auto state = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("ON" == state)
|
||||
{
|
||||
tx = true;
|
||||
}
|
||||
else if ("OFF" == state)
|
||||
{
|
||||
tx = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "unexpected TX state" << state);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised TX state: ") + state};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get TX unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX status: ") + reply};
|
||||
}
|
||||
if (tx != on) QThread::msleep (10); // don't thrash Commander
|
||||
}
|
||||
update_PTT (tx);
|
||||
if (tx != on)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "rig failed to respond to PTT: " << on);
|
||||
throw error {tr ("DX Lab Suite Commander rig did not respond to PTT: ") + (on ? "ON" : "OFF")};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_ASSERT (wrapped_);
|
||||
TransceiverState new_state {wrapped_->state ()};
|
||||
new_state.ptt (on);
|
||||
wrapped_->set (new_state, 0);
|
||||
update_PTT (on);
|
||||
}
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", f << state ());
|
||||
auto f_string = frequency_to_string (f);
|
||||
if (UNK != m && m != get_mode ())
|
||||
{
|
||||
auto m_string = map_mode (m);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string + "<xcvrmode:%2>" + m_string + "<preservesplitanddual:1>Y").arg (f_string.size ()).arg (m_string.size ());
|
||||
simple_command (("<command:14>CmdSetFreqMode<parameters:%1>" + params).arg (params.size ()));
|
||||
update_mode (m);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_tx_frequency (Frequency tx, MODE mode, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", tx << state ());
|
||||
if (tx)
|
||||
{
|
||||
auto f_string = frequency_to_string (tx);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string + "<SuppressDual:1>Y").arg (f_string.size ());
|
||||
if (UNK == mode)
|
||||
{
|
||||
params += "<SuppressModeChange:1>Y";
|
||||
}
|
||||
simple_command (("<command:11>CmdQSXSplit<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
simple_command ("<command:8>CmdSplit<parameters:8><1:3>off");
|
||||
}
|
||||
update_split (tx);
|
||||
update_other_frequency (tx);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", m << state ());
|
||||
auto m_string = map_mode (m);
|
||||
auto params = ("<1:%1>" + m_string).arg (m_string.size ());
|
||||
simple_command (("<command:10>CmdSetMode<parameters:%1>" + params).arg (params.size ()));
|
||||
update_mode (m);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_poll ()
|
||||
{
|
||||
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
|
||||
bool quiet {false};
|
||||
#else
|
||||
bool quiet {true};
|
||||
#endif
|
||||
|
||||
auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f)
|
||||
{
|
||||
if (!state ().ptt ()) // Commander is not reliable on frequency
|
||||
// polls while transmitting
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling frequency: ") + reply};
|
||||
}
|
||||
|
||||
if (state ().split ())
|
||||
{
|
||||
reply = command_with_reply ("<command:12>CmdGetTXFreq<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdTXFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f)
|
||||
{
|
||||
if (!state ().ptt ()) // Commander is not reliable on frequency
|
||||
// polls while transmitting
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get tx frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX frequency: ") + reply};
|
||||
}
|
||||
}
|
||||
|
||||
reply = command_with_reply ("<command:12>CmdSendSplit<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdSplit:"))
|
||||
{
|
||||
auto split = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("ON" == split)
|
||||
{
|
||||
update_split (true);
|
||||
}
|
||||
else if ("OFF" == split)
|
||||
{
|
||||
update_split (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected split state" << split);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised split state: ") + split};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get split mode unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling split status: ") + reply};
|
||||
}
|
||||
|
||||
get_mode (quiet);
|
||||
}
|
||||
|
||||
auto DXLabSuiteCommanderTransceiver::get_mode (bool no_debug) -> MODE
|
||||
{
|
||||
MODE m {UNK};
|
||||
auto reply = command_with_reply ("<command:11>CmdSendMode<parameters:0>", no_debug);
|
||||
if (0 == reply.indexOf ("<CmdMode:"))
|
||||
{
|
||||
auto mode = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("AM" == mode)
|
||||
{
|
||||
m = AM;
|
||||
}
|
||||
else if ("CW" == mode)
|
||||
{
|
||||
m = CW;
|
||||
}
|
||||
else if ("CW-R" == mode)
|
||||
{
|
||||
m = CW_R;
|
||||
}
|
||||
else if ("FM" == mode || "WBFM" == mode)
|
||||
{
|
||||
m = FM;
|
||||
}
|
||||
else if ("LSB" == mode)
|
||||
{
|
||||
m = LSB;
|
||||
}
|
||||
else if ("USB" == mode)
|
||||
{
|
||||
m = USB;
|
||||
}
|
||||
else if ("RTTY" == mode)
|
||||
{
|
||||
m = FSK;
|
||||
}
|
||||
else if ("RTTY-R" == mode)
|
||||
{
|
||||
m = FSK_R;
|
||||
}
|
||||
else if ("PKT" == mode || "DATA-L" == mode || "Data-L" == mode || "DIGL" == mode)
|
||||
{
|
||||
m = DIG_L;
|
||||
}
|
||||
else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode || "DIGU" == mode)
|
||||
{
|
||||
m = DIG_U;
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected mode name" << mode);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised mode: \"") + mode + '"'};
|
||||
}
|
||||
update_mode (m);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling mode: ") + reply};
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::simple_command (QString const& cmd, bool no_debug)
|
||||
{
|
||||
Q_ASSERT (commander_);
|
||||
|
||||
if (!no_debug)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd);
|
||||
}
|
||||
|
||||
if (!write_to_port (cmd))
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed:" << commander_->errorString ());
|
||||
throw error {tr ("DX Lab Suite Commander send command failed\n") + commander_->errorString ()};
|
||||
}
|
||||
}
|
||||
|
||||
QString DXLabSuiteCommanderTransceiver::command_with_reply (QString const& cmd, bool no_debug)
|
||||
{
|
||||
Q_ASSERT (commander_);
|
||||
|
||||
if (!write_to_port (cmd))
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to send command:" << commander_->errorString ());
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander failed to send command \"%1\": %2\n")
|
||||
.arg (cmd)
|
||||
.arg (commander_->errorString ())
|
||||
};
|
||||
}
|
||||
|
||||
// waitForReadReady appears to be unreliable on Windows timing out
|
||||
// when data is waiting so retry a few times
|
||||
unsigned retries {5};
|
||||
bool replied {false};
|
||||
while (!replied && --retries)
|
||||
{
|
||||
replied = commander_->waitForReadyRead ();
|
||||
if (!replied && commander_->error () != commander_->SocketTimeoutError)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "failed to read reply:" << commander_->errorString ());
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander send command \"%1\" read reply failed: %2\n")
|
||||
.arg (cmd)
|
||||
.arg (commander_->errorString ())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!replied)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "retries exhausted");
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander retries exhausted sending command \"%1\"")
|
||||
.arg (cmd)
|
||||
};
|
||||
}
|
||||
|
||||
auto result = commander_->readAll ();
|
||||
// qDebug () << "result: " << result;
|
||||
// for (int i = 0; i < result.size (); ++i)
|
||||
// {
|
||||
// qDebug () << i << ":" << hex << int (result[i]);
|
||||
// }
|
||||
|
||||
if (!no_debug)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "->" << result);
|
||||
}
|
||||
|
||||
return result; // converting raw UTF-8 bytes to QString
|
||||
}
|
||||
|
||||
bool DXLabSuiteCommanderTransceiver::write_to_port (QString const& s)
|
||||
{
|
||||
auto data = s.toLocal8Bit ();
|
||||
auto to_send = data.constData ();
|
||||
auto length = data.size ();
|
||||
|
||||
qint64 total_bytes_sent {0};
|
||||
while (total_bytes_sent < length)
|
||||
{
|
||||
auto bytes_sent = commander_->write (to_send + total_bytes_sent, length - total_bytes_sent);
|
||||
if (bytes_sent < 0 || !commander_->waitForBytesWritten ())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
total_bytes_sent += bytes_sent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DXLabSuiteCommanderTransceiver::frequency_to_string (Frequency f) const
|
||||
{
|
||||
// number is localized and in kHz, avoid floating point translation
|
||||
// errors by adding a small number (0.1Hz)
|
||||
return QString {"%L1"}.arg (f / 1e3 + 1e-4, 10, 'f', 3);
|
||||
}
|
||||
|
||||
auto DXLabSuiteCommanderTransceiver::string_to_frequency (QString s) const -> Frequency
|
||||
{
|
||||
// temporary hack because Commander is returning invalid UTF-8 bytes
|
||||
s.replace (QChar {QChar::ReplacementCharacter}, locale_.groupSeparator ());
|
||||
|
||||
// remove DP - relies on n.nnn kHz format so we can do ulonglong
|
||||
// conversion to Hz
|
||||
bool ok;
|
||||
|
||||
// auto f = locale_.toDouble (s, &ok); // use when CmdSendFreq and
|
||||
// CmdSendTxFreq reinstated
|
||||
|
||||
auto f = QLocale::c ().toDouble (s, &ok); // temporary fix
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognized frequency")};
|
||||
}
|
||||
return (f + 1e-4) * 1e3;
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
#include "decodedtext.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QRegularExpression>
|
||||
#include <QDebug>
|
||||
|
||||
extern "C" {
|
||||
bool stdmsg_(char const * msg, fortran_charlen_t);
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
QRegularExpression words_re {R"(^(?:(?<word1>(?:CQ|DE|QRZ)(?:\s?DX|\s(?:[A-Z]{1,4}|\d{3}))|[A-Z0-9/]+|\.{3})\s)(?:(?<word2>[A-Z0-9/]+)(?:\s(?<word3>[-+A-Z0-9]+)(?:\s(?<word4>(?:OOO|(?!RR73)[A-R]{2}[0-9]{2})))?)?)?)"};
|
||||
}
|
||||
|
||||
DecodedText::DecodedText (QString const& the_string)
|
||||
: string_ {the_string.left (the_string.indexOf (QChar::Nbsp))} // discard appended info
|
||||
, padding_ {string_.indexOf (" ") > 4 ? 2 : 0} // allow for
|
||||
// seconds
|
||||
, message_ {string_.mid (column_qsoText + padding_).trimmed ()}
|
||||
, is_standard_ {false}
|
||||
{
|
||||
// qDebug () << "DecodedText: the_string:" << the_string << "Nbsp pos:" << the_string.indexOf (QChar::Nbsp);
|
||||
if (message_.length() >= 1)
|
||||
{
|
||||
message0_ = message_.left(36);
|
||||
message_ = message_.left(36).remove (QRegularExpression {"[<>]"});
|
||||
int i1 = message_.indexOf ('\r');
|
||||
if (i1 > 0)
|
||||
{
|
||||
message_ = message_.left (i1 - 1);
|
||||
}
|
||||
if (message_.contains (QRegularExpression {"^(CQ|QRZ)\\s"}))
|
||||
{
|
||||
// TODO this magic position 16 is guaranteed to be after the
|
||||
// last space in a decoded CQ or QRZ message but before any
|
||||
// appended DXCC entity name or worked before information
|
||||
auto eom_pos = message_.indexOf (' ', 16);
|
||||
// we always want at least the characters to position 16
|
||||
if (eom_pos < 16) eom_pos = message_.size () - 1;
|
||||
// remove DXCC entity and worked B4 status. TODO need a better way to do this
|
||||
message_ = message_.left (eom_pos + 1);
|
||||
}
|
||||
// stdmsg is a Fortran routine that packs the text, unpacks it
|
||||
// and compares the result
|
||||
auto message_c_string = message0_.toLocal8Bit ();
|
||||
message_c_string += QByteArray {37 - message_c_string.size (), ' '};
|
||||
is_standard_ = stdmsg_(message_c_string.constData(),37);
|
||||
}
|
||||
};
|
||||
|
||||
QStringList DecodedText::messageWords () const
|
||||
{
|
||||
if(is_standard_) {
|
||||
// extract up to the first four message words
|
||||
QString t=message_;
|
||||
if(t.left(4)=="TU; ") t=message_.mid(4,-1);
|
||||
return words_re.match(t).capturedTexts();
|
||||
}
|
||||
// simple word split for free text messages
|
||||
auto words = message_.split (' ', QString::SkipEmptyParts);
|
||||
// add whole message as item 0 to mimic RE capture list
|
||||
words.prepend (message_);
|
||||
return words;
|
||||
}
|
||||
|
||||
QString DecodedText::CQersCall() const
|
||||
{
|
||||
QRegularExpression callsign_re {R"(^(CQ|DE|QRZ)(\s?DX|\s([A-Z]{1,4}|\d{3}))?\s(?<callsign>[A-Z0-9/]{2,})(\s[A-R]{2}[0-9]{2})?)"};
|
||||
return callsign_re.match (message_).captured ("callsign");
|
||||
}
|
||||
|
||||
|
||||
bool DecodedText::isJT65() const
|
||||
{
|
||||
return string_.indexOf("#") == column_mode + padding_;
|
||||
}
|
||||
|
||||
bool DecodedText::isJT9() const
|
||||
{
|
||||
return string_.indexOf("@") == column_mode + padding_;
|
||||
}
|
||||
|
||||
bool DecodedText::isTX() const
|
||||
{
|
||||
int i = string_.indexOf("Tx");
|
||||
return (i >= 0 && i < 15); // TODO guessing those numbers. Does Tx ever move?
|
||||
}
|
||||
|
||||
bool DecodedText::isLowConfidence () const
|
||||
{
|
||||
return QChar {'?'} == string_.mid (padding_ + column_qsoText + 21, 1);
|
||||
}
|
||||
|
||||
int DecodedText::frequencyOffset() const
|
||||
{
|
||||
return string_.mid(column_freq + padding_,4).toInt();
|
||||
}
|
||||
|
||||
int DecodedText::snr() const
|
||||
{
|
||||
int i1=string_.indexOf(" ")+1;
|
||||
return string_.mid(i1,3).toInt();
|
||||
}
|
||||
|
||||
float DecodedText::dt() const
|
||||
{
|
||||
return string_.mid(column_dt + padding_,5).toFloat();
|
||||
}
|
||||
|
||||
/*
|
||||
2343 -11 0.8 1259 # YV6BFE F6GUU R-08
|
||||
2343 -19 0.3 718 # VE6WQ SQ2NIJ -14
|
||||
2343 -7 0.3 815 # KK4DSD W7VP -16
|
||||
2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02
|
||||
|
||||
0605 Tx 1259 # CQ VK3ACF QF22
|
||||
*/
|
||||
|
||||
// find and extract any report. Returns true if this is a standard message
|
||||
bool DecodedText::report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report) const
|
||||
{
|
||||
if (message_.size () < 1) return false;
|
||||
|
||||
QStringList const& w = message_.split(" ",QString::SkipEmptyParts);
|
||||
if (w.size ()
|
||||
&& is_standard_ && (w[0] == myBaseCall
|
||||
|| w[0].endsWith ("/" + myBaseCall)
|
||||
|| w[0].startsWith (myBaseCall + "/")
|
||||
|| (w.size () > 1 && !dxBaseCall.isEmpty ()
|
||||
&& (w[1] == dxBaseCall
|
||||
|| w[1].endsWith ("/" + dxBaseCall)
|
||||
|| w[1].startsWith (dxBaseCall + "/")))))
|
||||
{
|
||||
QString tt="";
|
||||
if(w.size() > 2) tt=w[2];
|
||||
bool ok;
|
||||
auto i1=tt.toInt(&ok);
|
||||
if (ok and i1>=-50 and i1<50)
|
||||
{
|
||||
report = tt;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (tt.mid(0,1)=="R")
|
||||
{
|
||||
i1=tt.mid(1).toInt(&ok);
|
||||
if(ok and i1>=-50 and i1<50)
|
||||
{
|
||||
report = tt.mid(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return is_standard_;
|
||||
}
|
||||
|
||||
// get the first text word, usually the call
|
||||
QString DecodedText::call() const
|
||||
{
|
||||
return words_re.match (message_).captured ("word1");
|
||||
}
|
||||
|
||||
// get the second word, most likely the de call and the third word, most likely grid
|
||||
void DecodedText::deCallAndGrid(/*out*/QString& call, QString& grid) const
|
||||
{
|
||||
auto const& match = words_re.match (message_);
|
||||
call = match.captured ("word2");
|
||||
grid = match.captured ("word3");
|
||||
if ("R" == grid) grid = match.captured ("word4");
|
||||
}
|
||||
|
||||
unsigned DecodedText::timeInSeconds() const
|
||||
{
|
||||
return 3600 * string_.mid (column_time, 2).toUInt ()
|
||||
+ 60 * string_.mid (column_time + 2, 2).toUInt()
|
||||
+ (padding_ ? string_.mid (column_time + 2 + padding_, 2).toUInt () : 0U);
|
||||
}
|
||||
|
||||
QString DecodedText::report() const // returns a string of the SNR field with a leading + or - followed by two digits
|
||||
{
|
||||
int sr = snr();
|
||||
if (sr<-50)
|
||||
sr = -50;
|
||||
else
|
||||
if (sr > 49)
|
||||
sr = 49;
|
||||
|
||||
QString rpt;
|
||||
rpt = rpt.asprintf("%d",abs(sr));
|
||||
if (sr > 9)
|
||||
rpt = "+" + rpt;
|
||||
else
|
||||
if (sr >= 0)
|
||||
rpt = "+0" + rpt;
|
||||
else
|
||||
if (sr >= -9)
|
||||
rpt = "-0" + rpt;
|
||||
else
|
||||
rpt = "-" + rpt;
|
||||
return rpt;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
// -*- Mode: C++ -*-
|
||||
/*
|
||||
* Class to handle the formatted string as returned from the fortran decoder
|
||||
*
|
||||
* VK3ACF August 2013
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DECODEDTEXT_H
|
||||
#define DECODEDTEXT_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
|
||||
|
||||
/*
|
||||
012345678901234567890123456789012345678901
|
||||
^ ^ ^ ^ ^ ^
|
||||
2343 -11 0.8 1259 # CQ VP2X/GM4WJS GL33
|
||||
2343 -11 0.8 1259 # CQ 999 VP2V/GM4WJS
|
||||
2343 -11 0.8 1259 # YV6BFE F6GUU R-08
|
||||
2343 -19 0.3 718 # VE6WQ SQ2NIJ -14
|
||||
2343 -7 0.3 815 # KK4DSD W7VP -16
|
||||
2343 -13 0.1 3627 @ CT1FBK IK5YZT R+02
|
||||
|
||||
0605 Tx 1259 # CQ VK3ACF QF22
|
||||
*/
|
||||
|
||||
class DecodedText
|
||||
{
|
||||
public:
|
||||
explicit DecodedText (QString const& message);
|
||||
|
||||
QString string() const { return string_; };
|
||||
QStringList messageWords () const;
|
||||
int indexOf(QString s) const { return string_.indexOf(s); };
|
||||
int indexOf(QString s, int i) const { return string_.indexOf(s,i); };
|
||||
QString mid(int f, int t) const { return string_.mid(f,t); };
|
||||
QString left(int i) const { return string_.left(i); };
|
||||
|
||||
void clear() { string_.clear(); };
|
||||
|
||||
QString CQersCall() const;
|
||||
|
||||
bool isJT65() const;
|
||||
bool isJT9() const;
|
||||
bool isTX() const;
|
||||
bool isStandardMessage () const {return is_standard_;}
|
||||
bool isLowConfidence () const;
|
||||
int frequencyOffset() const; // hertz offset from the tuned dial or rx frequency, aka audio frequency
|
||||
int snr() const;
|
||||
float dt() const;
|
||||
|
||||
// find and extract any report. Returns true if this is a standard message
|
||||
bool report(QString const& myBaseCall, QString const& dxBaseCall, /*mod*/QString& report) const;
|
||||
|
||||
// get the first message text word, usually the call
|
||||
QString call() const;
|
||||
|
||||
// get the second word, most likely the de call and the third word, most likely grid
|
||||
void deCallAndGrid(/*out*/QString& call, QString& grid) const;
|
||||
|
||||
unsigned timeInSeconds() const;
|
||||
|
||||
// returns a string of the SNR field with a leading + or - followed by two digits
|
||||
QString report() const;
|
||||
|
||||
private:
|
||||
// These define the columns in the decoded text where fields are to be found.
|
||||
// We rely on these columns being the same in the fortran code (lib/decoder.f90) that formats the decoded text
|
||||
enum Columns {column_time = 0,
|
||||
column_snr = 5,
|
||||
column_dt = 9,
|
||||
column_freq = 14,
|
||||
column_mode = 19,
|
||||
column_qsoText = 22 };
|
||||
|
||||
QString string_;
|
||||
int padding_;
|
||||
QString message_;
|
||||
QString message0_;
|
||||
bool is_standard_;
|
||||
};
|
||||
|
||||
#endif // DECODEDTEXT_H
|
125
Detector.cpp
125
Detector.cpp
|
@ -1,125 +0,0 @@
|
|||
#include "Detector.hpp"
|
||||
#include <QDateTime>
|
||||
#include <QtAlgorithms>
|
||||
#include <QDebug>
|
||||
#include <math.h>
|
||||
#include "commons.h"
|
||||
|
||||
#include "moc_Detector.cpp"
|
||||
|
||||
extern "C" {
|
||||
void fil4_(qint16*, qint32*, qint16*, qint32*);
|
||||
}
|
||||
|
||||
Detector::Detector (unsigned frameRate, double periodLengthInSeconds,
|
||||
unsigned downSampleFactor, QObject * parent)
|
||||
: AudioDevice (parent)
|
||||
, m_frameRate (frameRate)
|
||||
, m_period (periodLengthInSeconds)
|
||||
, m_downSampleFactor (downSampleFactor)
|
||||
, m_samplesPerFFT {max_buffer_size}
|
||||
, m_ns (999)
|
||||
, m_buffer ((downSampleFactor > 1) ?
|
||||
new short [max_buffer_size * downSampleFactor] : nullptr)
|
||||
, m_bufferPos (0)
|
||||
{
|
||||
(void)m_frameRate; // quell compiler warning
|
||||
clear ();
|
||||
}
|
||||
|
||||
void Detector::setBlockSize (unsigned n)
|
||||
{
|
||||
m_samplesPerFFT = n;
|
||||
}
|
||||
|
||||
bool Detector::reset ()
|
||||
{
|
||||
clear ();
|
||||
// don't call base call reset because it calls seek(0) which causes
|
||||
// a warning
|
||||
return isOpen ();
|
||||
}
|
||||
|
||||
void Detector::clear ()
|
||||
{
|
||||
// set index to roughly where we are in time (1ms resolution)
|
||||
// qint64 now (QDateTime::currentMSecsSinceEpoch ());
|
||||
// unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000));
|
||||
// dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast<unsigned> (sizeof (dec_data.d2) / sizeof (dec_data.d2[0])));
|
||||
dec_data.params.kin = 0;
|
||||
m_bufferPos = 0;
|
||||
|
||||
// fill buffer with zeros (G4WJS commented out because it might cause decoder hangs)
|
||||
// qFill (dec_data.d2, dec_data.d2 + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]), 0);
|
||||
}
|
||||
|
||||
qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||
{
|
||||
static unsigned mstr0=999999;
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
if(mstr < mstr0) { //When mstr has wrapped around to 0, restart the buffer
|
||||
dec_data.params.kin = 0;
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
mstr0=mstr;
|
||||
|
||||
// no torn frames
|
||||
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ())));
|
||||
// these are in terms of input frames (not down sampled)
|
||||
size_t framesAcceptable ((sizeof (dec_data.d2) /
|
||||
sizeof (dec_data.d2[0]) - dec_data.params.kin) * m_downSampleFactor);
|
||||
size_t framesAccepted (qMin (static_cast<size_t> (maxSize /
|
||||
bytesPerFrame ()), framesAcceptable));
|
||||
|
||||
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
|
||||
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
|
||||
<< " frames of data on the floor!"
|
||||
<< dec_data.params.kin << mstr;
|
||||
}
|
||||
|
||||
for (unsigned remaining = framesAccepted; remaining; ) {
|
||||
size_t numFramesProcessed (qMin (m_samplesPerFFT *
|
||||
m_downSampleFactor - m_bufferPos, remaining));
|
||||
|
||||
if(m_downSampleFactor > 1) {
|
||||
store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
|
||||
numFramesProcessed, &m_buffer[m_bufferPos]);
|
||||
m_bufferPos += numFramesProcessed;
|
||||
|
||||
if(m_bufferPos==m_samplesPerFFT*m_downSampleFactor) {
|
||||
qint32 framesToProcess (m_samplesPerFFT * m_downSampleFactor);
|
||||
qint32 framesAfterDownSample (m_samplesPerFFT);
|
||||
if(m_downSampleFactor > 1 && dec_data.params.kin>=0 &&
|
||||
dec_data.params.kin < (NTMAX*12000 - framesAfterDownSample)) {
|
||||
fil4_(&m_buffer[0], &framesToProcess, &dec_data.d2[dec_data.params.kin],
|
||||
&framesAfterDownSample);
|
||||
dec_data.params.kin += framesAfterDownSample;
|
||||
} else {
|
||||
// qDebug() << "framesToProcess = " << framesToProcess;
|
||||
// qDebug() << "dec_data.params.kin = " << dec_data.params.kin;
|
||||
// qDebug() << "secondInPeriod = " << secondInPeriod();
|
||||
// qDebug() << "framesAfterDownSample" << framesAfterDownSample;
|
||||
}
|
||||
Q_EMIT framesWritten (dec_data.params.kin);
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
|
||||
numFramesProcessed, &dec_data.d2[dec_data.params.kin]);
|
||||
m_bufferPos += numFramesProcessed;
|
||||
dec_data.params.kin += numFramesProcessed;
|
||||
if (m_bufferPos == static_cast<unsigned> (m_samplesPerFFT)) {
|
||||
Q_EMIT framesWritten (dec_data.params.kin);
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
}
|
||||
remaining -= numFramesProcessed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return maxSize; // we drop any data past the end of the buffer on
|
||||
// the floor until the next period starts
|
||||
}
|
59
Detector.hpp
59
Detector.hpp
|
@ -1,59 +0,0 @@
|
|||
#ifndef DETECTOR_HPP__
|
||||
#define DETECTOR_HPP__
|
||||
#include "AudioDevice.hpp"
|
||||
#include <QScopedArrayPointer>
|
||||
|
||||
//
|
||||
// output device that distributes data in predefined chunks via a signal
|
||||
//
|
||||
// the underlying device for this abstraction is just the buffer that
|
||||
// stores samples throughout a receiving period
|
||||
//
|
||||
class Detector : public AudioDevice
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
//
|
||||
// if the data buffer were not global storage and fixed size then we
|
||||
// might want maximum size passed as constructor arguments
|
||||
//
|
||||
// we down sample by a factor of 4
|
||||
//
|
||||
// the samplesPerFFT argument is the number after down sampling
|
||||
//
|
||||
Detector (unsigned frameRate, double periodLengthInSeconds, unsigned downSampleFactor = 4u,
|
||||
QObject * parent = 0);
|
||||
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
bool reset () override;
|
||||
|
||||
Q_SIGNAL void framesWritten (qint64) const;
|
||||
Q_SLOT void setBlockSize (unsigned);
|
||||
|
||||
protected:
|
||||
qint64 readData (char * /* data */, qint64 /* maxSize */) override
|
||||
{
|
||||
return -1; // we don't produce data
|
||||
}
|
||||
|
||||
qint64 writeData (char const * data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
void clear (); // discard buffer contents
|
||||
|
||||
unsigned m_frameRate;
|
||||
double m_period;
|
||||
unsigned m_downSampleFactor;
|
||||
qint32 m_samplesPerFFT; // after any down sampling
|
||||
qint32 m_ns;
|
||||
static size_t const max_buffer_size {7 * 512};
|
||||
QScopedArrayPointer<short> m_buffer; // de-interleaved sample buffer
|
||||
// big enough for all the
|
||||
// samples for one increment of
|
||||
// data (a signals worth) at
|
||||
// the input sample rate
|
||||
unsigned m_bufferPos;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,126 @@
|
|||
#include "Detector.hpp"
|
||||
#include <QDateTime>
|
||||
#include <QtAlgorithms>
|
||||
#include <QDebug>
|
||||
#include <math.h>
|
||||
#include "commons.h"
|
||||
|
||||
#include "moc_Detector.cpp"
|
||||
|
||||
extern "C" {
|
||||
void fil4_(qint16*, qint32*, qint16*, qint32*);
|
||||
}
|
||||
|
||||
extern dec_data_t dec_data;
|
||||
|
||||
Detector::Detector (unsigned frameRate, double periodLengthInSeconds,
|
||||
unsigned downSampleFactor, QObject * parent)
|
||||
: AudioDevice (parent)
|
||||
, m_frameRate (frameRate)
|
||||
, m_period (periodLengthInSeconds)
|
||||
, m_downSampleFactor (downSampleFactor)
|
||||
, m_samplesPerFFT {max_buffer_size}
|
||||
, m_buffer ((downSampleFactor > 1) ?
|
||||
new short [max_buffer_size * downSampleFactor] : nullptr)
|
||||
, m_bufferPos (0)
|
||||
{
|
||||
(void)m_frameRate; // quell compiler warning
|
||||
clear ();
|
||||
}
|
||||
|
||||
void Detector::setBlockSize (unsigned n)
|
||||
{
|
||||
m_samplesPerFFT = n;
|
||||
}
|
||||
|
||||
bool Detector::reset ()
|
||||
{
|
||||
clear ();
|
||||
// don't call base call reset because it calls seek(0) which causes
|
||||
// a warning
|
||||
return isOpen ();
|
||||
}
|
||||
|
||||
void Detector::clear ()
|
||||
{
|
||||
// set index to roughly where we are in time (1ms resolution)
|
||||
// qint64 now (QDateTime::currentMSecsSinceEpoch ());
|
||||
// unsigned msInPeriod ((now % 86400000LL) % (m_period * 1000));
|
||||
// dec_data.params.kin = qMin ((msInPeriod * m_frameRate) / 1000, static_cast<unsigned> (sizeof (dec_data.d2) / sizeof (dec_data.d2[0])));
|
||||
dec_data.params.kin = 0;
|
||||
m_bufferPos = 0;
|
||||
|
||||
// fill buffer with zeros (G4WJS commented out because it might cause decoder hangs)
|
||||
// qFill (dec_data.d2, dec_data.d2 + sizeof (dec_data.d2) / sizeof (dec_data.d2[0]), 0);
|
||||
}
|
||||
|
||||
qint64 Detector::writeData (char const * data, qint64 maxSize)
|
||||
{
|
||||
static unsigned mstr0=999999;
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
if(mstr < mstr0) { //When mstr has wrapped around to 0, restart the buffer
|
||||
dec_data.params.kin = 0;
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
mstr0=mstr;
|
||||
|
||||
// no torn frames
|
||||
Q_ASSERT (!(maxSize % static_cast<qint64> (bytesPerFrame ())));
|
||||
// these are in terms of input frames (not down sampled)
|
||||
size_t framesAcceptable ((sizeof (dec_data.d2) /
|
||||
sizeof (dec_data.d2[0]) - dec_data.params.kin) * m_downSampleFactor);
|
||||
size_t framesAccepted (qMin (static_cast<size_t> (maxSize /
|
||||
bytesPerFrame ()), framesAcceptable));
|
||||
|
||||
if (framesAccepted < static_cast<size_t> (maxSize / bytesPerFrame ())) {
|
||||
qDebug () << "dropped " << maxSize / bytesPerFrame () - framesAccepted
|
||||
<< " frames of data on the floor!"
|
||||
<< dec_data.params.kin << mstr;
|
||||
}
|
||||
|
||||
for (unsigned remaining = framesAccepted; remaining; ) {
|
||||
size_t numFramesProcessed (qMin (m_samplesPerFFT *
|
||||
m_downSampleFactor - m_bufferPos, remaining));
|
||||
|
||||
if(m_downSampleFactor > 1) {
|
||||
store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
|
||||
numFramesProcessed, &m_buffer[m_bufferPos]);
|
||||
m_bufferPos += numFramesProcessed;
|
||||
|
||||
if(m_bufferPos==m_samplesPerFFT*m_downSampleFactor) {
|
||||
qint32 framesToProcess (m_samplesPerFFT * m_downSampleFactor);
|
||||
qint32 framesAfterDownSample (m_samplesPerFFT);
|
||||
if(m_downSampleFactor > 1 && dec_data.params.kin>=0 &&
|
||||
dec_data.params.kin < (NTMAX*12000 - framesAfterDownSample)) {
|
||||
fil4_(&m_buffer[0], &framesToProcess, &dec_data.d2[dec_data.params.kin],
|
||||
&framesAfterDownSample);
|
||||
dec_data.params.kin += framesAfterDownSample;
|
||||
} else {
|
||||
// qDebug() << "framesToProcess = " << framesToProcess;
|
||||
// qDebug() << "dec_data.params.kin = " << dec_data.params.kin;
|
||||
// qDebug() << "secondInPeriod = " << secondInPeriod();
|
||||
// qDebug() << "framesAfterDownSample" << framesAfterDownSample;
|
||||
}
|
||||
Q_EMIT framesWritten (dec_data.params.kin);
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
|
||||
} else {
|
||||
store (&data[(framesAccepted - remaining) * bytesPerFrame ()],
|
||||
numFramesProcessed, &dec_data.d2[dec_data.params.kin]);
|
||||
m_bufferPos += numFramesProcessed;
|
||||
dec_data.params.kin += numFramesProcessed;
|
||||
if (m_bufferPos == static_cast<unsigned> (m_samplesPerFFT)) {
|
||||
Q_EMIT framesWritten (dec_data.params.kin);
|
||||
m_bufferPos = 0;
|
||||
}
|
||||
}
|
||||
remaining -= numFramesProcessed;
|
||||
}
|
||||
|
||||
|
||||
|
||||
return maxSize; // we drop any data past the end of the buffer on
|
||||
// the floor until the next period starts
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef DETECTOR_HPP__
|
||||
#define DETECTOR_HPP__
|
||||
#include "Audio/AudioDevice.hpp"
|
||||
#include <QScopedArrayPointer>
|
||||
|
||||
//
|
||||
// output device that distributes data in predefined chunks via a signal
|
||||
//
|
||||
// the underlying device for this abstraction is just the buffer that
|
||||
// stores samples throughout a receiving period
|
||||
//
|
||||
class Detector : public AudioDevice
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
//
|
||||
// if the data buffer were not global storage and fixed size then we
|
||||
// might want maximum size passed as constructor arguments
|
||||
//
|
||||
// we down sample by a factor of 4
|
||||
//
|
||||
// the samplesPerFFT argument is the number after down sampling
|
||||
//
|
||||
Detector (unsigned frameRate, double periodLengthInSeconds, unsigned downSampleFactor = 4u,
|
||||
QObject * parent = 0);
|
||||
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
bool reset () override;
|
||||
|
||||
Q_SIGNAL void framesWritten (qint64) const;
|
||||
Q_SLOT void setBlockSize (unsigned);
|
||||
|
||||
protected:
|
||||
qint64 readData (char * /* data */, qint64 /* maxSize */) override
|
||||
{
|
||||
return -1; // we don't produce data
|
||||
}
|
||||
|
||||
qint64 writeData (char const * data, qint64 maxSize) override;
|
||||
|
||||
private:
|
||||
void clear (); // discard buffer contents
|
||||
|
||||
unsigned m_frameRate;
|
||||
double m_period;
|
||||
unsigned m_downSampleFactor;
|
||||
qint32 m_samplesPerFFT; // after any down sampling
|
||||
static size_t const max_buffer_size {7 * 512};
|
||||
QScopedArrayPointer<short> m_buffer; // de-interleaved sample buffer
|
||||
// big enough for all the
|
||||
// samples for one increment of
|
||||
// data (a signals worth) at
|
||||
// the input sample rate
|
||||
unsigned m_bufferPos;
|
||||
};
|
||||
|
||||
#endif
|
1173
HRDTransceiver.cpp
1173
HRDTransceiver.cpp
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,76 +0,0 @@
|
|||
#ifndef HAMLIB_TRANSCEIVER_HPP_
|
||||
#define HAMLIB_TRANSCEIVER_HPP_
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <hamlib/rig.h>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
typedef struct rig RIG;
|
||||
struct rig_caps;
|
||||
typedef int vfo_t;
|
||||
}
|
||||
|
||||
// hamlib transceiver and PTT mostly delegated directly to hamlib Rig class
|
||||
class HamlibTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT // for translation context
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *);
|
||||
static void unregister_transceivers ();
|
||||
|
||||
explicit HamlibTransceiver (int model_number, TransceiverFactory::ParameterPack const&,
|
||||
QObject * parent = nullptr);
|
||||
explicit HamlibTransceiver (TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port,
|
||||
QObject * parent = nullptr);
|
||||
|
||||
private:
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool) override;
|
||||
|
||||
void do_poll () override;
|
||||
|
||||
void error_check (int ret_code, QString const& doing) const;
|
||||
void set_conf (char const * item, char const * value);
|
||||
QByteArray get_conf (char const * item);
|
||||
Transceiver::MODE map_mode (rmode_t) const;
|
||||
rmode_t map_mode (Transceiver::MODE mode) const;
|
||||
std::tuple<vfo_t, vfo_t> get_vfos (bool for_split) const;
|
||||
|
||||
struct RIGDeleter {static void cleanup (RIG *);};
|
||||
QScopedPointer<RIG, RIGDeleter> rig_;
|
||||
|
||||
bool back_ptt_port_;
|
||||
bool one_VFO_;
|
||||
bool is_dummy_;
|
||||
|
||||
// these are saved on destruction so we can start new instances
|
||||
// where the last one left off
|
||||
static freq_t dummy_frequency_;
|
||||
static rmode_t dummy_mode_;
|
||||
|
||||
bool mutable reversed_;
|
||||
|
||||
bool freq_query_works_;
|
||||
bool mode_query_works_;
|
||||
bool split_query_works_;
|
||||
bool tickle_hamlib_; // Hamlib requires a
|
||||
// rig_set_split_vfo() call to
|
||||
// establish the Tx VFO
|
||||
bool get_vfo_works_; // Net rigctl promises what it can't deliver
|
||||
bool set_vfo_works_; // More rigctl promises which it can't deliver
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,591 +0,0 @@
|
|||
#include "MessageServer.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "NetworkMessage.hpp"
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageServer.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto quint32_max = std::numeric_limits<quint32>::max ();
|
||||
}
|
||||
|
||||
class MessageServer::impl
|
||||
: public QUdpSocket
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
impl (MessageServer * self, QString const& version, QString const& revision)
|
||||
: self_ {self}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, port_ {0u}
|
||||
, clock_ {new QTimer {this}}
|
||||
{
|
||||
// register the required types with Qt
|
||||
Radio::register_types ();
|
||||
|
||||
connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
|
||||
connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
|
||||
, [this] (SocketError /* e */)
|
||||
{
|
||||
Q_EMIT self_->error (errorString ());
|
||||
});
|
||||
connect (clock_, &QTimer::timeout, this, &impl::tick);
|
||||
clock_->start (NetworkMessage::pulse * 1000);
|
||||
}
|
||||
|
||||
enum StreamStatus {Fail, Short, OK};
|
||||
|
||||
void leave_multicast_group ();
|
||||
void join_multicast_group ();
|
||||
void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg);
|
||||
void tick ();
|
||||
void pending_datagrams ();
|
||||
StreamStatus check_status (QDataStream const&) const;
|
||||
void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
writeDatagram (message, address, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
MessageServer * self_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
port_type port_;
|
||||
QHostAddress multicast_group_address_;
|
||||
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}
|
||||
, negotiated_schema_number_ {2} // not 1 because it's broken
|
||||
, last_activity_ {QDateTime::currentDateTime ()}
|
||||
{
|
||||
}
|
||||
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
|
||||
QTimer * clock_;
|
||||
};
|
||||
|
||||
MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
|
||||
|
||||
#include "MessageServer.moc"
|
||||
|
||||
void MessageServer::impl::leave_multicast_group ()
|
||||
{
|
||||
if (!multicast_group_address_.isNull () && BoundState == state ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
leaveMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::join_multicast_group ()
|
||||
{
|
||||
if (BoundState == state ()
|
||||
&& !multicast_group_address_.isNull ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
auto mcast_iface = multicastInterface ();
|
||||
if (IPv4Protocol == multicast_group_address_.protocol ()
|
||||
&& IPv4Protocol != localAddress ().protocol ())
|
||||
{
|
||||
close ();
|
||||
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
|
||||
}
|
||||
bool joined {false};
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
// Windows requires outgoing interface to match
|
||||
// interface to be joined while joining, at least for
|
||||
// IPv4 it seems to
|
||||
setMulticastInterface (interface);
|
||||
|
||||
joined |= joinMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
}
|
||||
if (!joined)
|
||||
{
|
||||
multicast_group_address_.clear ();
|
||||
}
|
||||
setMulticastInterface (mcast_iface);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::pending_datagrams ()
|
||||
{
|
||||
while (hasPendingDatagrams ())
|
||||
{
|
||||
QByteArray datagram;
|
||||
datagram.resize (pendingDatagramSize ());
|
||||
QHostAddress sender_address;
|
||||
port_type sender_port;
|
||||
if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
|
||||
{
|
||||
parse_message (sender_address, sender_port, datagram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
NetworkMessage::Reader in {msg};
|
||||
|
||||
auto id = in.id ();
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
if (!clients_.contains (id))
|
||||
{
|
||||
auto& client = (clients_[id] = {sender, sender_port});
|
||||
QByteArray client_version;
|
||||
QByteArray client_revision;
|
||||
|
||||
if (NetworkMessage::Heartbeat == in.type ())
|
||||
{
|
||||
// negotiate a working schema number
|
||||
in >> client.negotiated_schema_number_;
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
auto sn = NetworkMessage::Builder::schema_number;
|
||||
client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_);
|
||||
|
||||
// reply to the new client informing it of the
|
||||
// negotiated schema number
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_};
|
||||
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (impl::OK == check_status (hb))
|
||||
{
|
||||
writeDatagram (message, client.sender_address_, client.sender_port_);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
// we don't care if this fails to read
|
||||
in >> client_version >> client_revision;
|
||||
}
|
||||
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version),
|
||||
QString::fromUtf8 (client_revision));
|
||||
}
|
||||
clients_[id].last_activity_ = QDateTime::currentDateTime ();
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
switch (in.type ())
|
||||
{
|
||||
case NetworkMessage::Heartbeat:
|
||||
//nothing to do here as time out handling deals with lifetime
|
||||
break;
|
||||
|
||||
case NetworkMessage::Clear:
|
||||
Q_EMIT self_->decodes_cleared (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::Status:
|
||||
{
|
||||
// unpack message
|
||||
Frequency f;
|
||||
QByteArray mode;
|
||||
QByteArray dx_call;
|
||||
QByteArray report;
|
||||
QByteArray tx_mode;
|
||||
bool tx_enabled {false};
|
||||
bool transmitting {false};
|
||||
bool decoding {false};
|
||||
quint32 rx_df {quint32_max};
|
||||
quint32 tx_df {quint32_max};
|
||||
QByteArray de_call;
|
||||
QByteArray de_grid;
|
||||
QByteArray dx_grid;
|
||||
bool watchdog_timeout {false};
|
||||
QByteArray sub_mode;
|
||||
bool fast_mode {false};
|
||||
quint8 special_op_mode {0};
|
||||
quint32 frequency_tolerance {quint32_max};
|
||||
quint32 tr_period {quint32_max};
|
||||
QByteArray configuration_name;
|
||||
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
|
||||
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
|
||||
>> 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)
|
||||
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
|
||||
, tx_enabled, transmitting, decoding, rx_df, tx_df
|
||||
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
|
||||
, QString::fromUtf8 (dx_grid), watchdog_timeout
|
||||
, QString::fromUtf8 (sub_mode), fast_mode
|
||||
, special_op_mode, frequency_tolerance, tr_period
|
||||
, QString::fromUtf8 (configuration_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Decode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
quint32 delta_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray message;
|
||||
bool low_confidence {false};
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode
|
||||
>> message >> low_confidence >> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency
|
||||
, QString::fromUtf8 (mode), QString::fromUtf8 (message)
|
||||
, low_confidence, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::WSPRDecode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
Frequency frequency;
|
||||
qint32 drift;
|
||||
QByteArray callsign;
|
||||
QByteArray grid;
|
||||
qint32 power;
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power
|
||||
>> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift
|
||||
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
|
||||
, power, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::QSOLogged:
|
||||
{
|
||||
QDateTime time_off;
|
||||
QByteArray dx_call;
|
||||
QByteArray dx_grid;
|
||||
Frequency dial_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray report_sent;
|
||||
QByteArray report_received;
|
||||
QByteArray tx_power;
|
||||
QByteArray comments;
|
||||
QByteArray name;
|
||||
QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window
|
||||
QByteArray operator_call;
|
||||
QByteArray my_call;
|
||||
QByteArray my_grid;
|
||||
QByteArray exchange_sent;
|
||||
QByteArray exchange_rcvd;
|
||||
in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received
|
||||
>> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid
|
||||
>> exchange_sent >> exchange_rcvd;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
|
||||
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
|
||||
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
|
||||
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
|
||||
, QString::fromUtf8 (my_grid), QString::fromUtf8 (exchange_sent)
|
||||
, QString::fromUtf8 (exchange_rcvd));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
Q_EMIT self_->client_closed (id);
|
||||
clients_.remove (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::LoggedADIF:
|
||||
{
|
||||
QByteArray ADIF;
|
||||
in >> ADIF;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->logged_ADIF (id, ADIF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("MessageServer warning: invalid UDP message received");
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Q_EMIT self_->error ("Unexpected exception in MessageServer");
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::tick ()
|
||||
{
|
||||
auto now = QDateTime::currentDateTime ();
|
||||
auto iter = std::begin (clients_);
|
||||
while (iter != std::end (clients_))
|
||||
{
|
||||
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
|
||||
{
|
||||
Q_EMIT self_->clear_decodes (iter.key ());
|
||||
Q_EMIT self_->client_closed (iter.key ());
|
||||
iter = clients_.erase (iter); // safe while iterating as doesn't rehash
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto MessageServer::impl::check_status (QDataStream const& stream) const -> StreamStatus
|
||||
{
|
||||
auto stat = stream.status ();
|
||||
StreamStatus result {Fail};
|
||||
switch (stat)
|
||||
{
|
||||
case QDataStream::ReadPastEnd:
|
||||
result = Short;
|
||||
break;
|
||||
|
||||
case QDataStream::ReadCorruptData:
|
||||
Q_EMIT self_->error ("Message serialization error: read corrupt data");
|
||||
break;
|
||||
|
||||
case QDataStream::WriteFailed:
|
||||
Q_EMIT self_->error ("Message serialization error: write error");
|
||||
break;
|
||||
|
||||
default:
|
||||
result = OK;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision)
|
||||
: QObject {parent}
|
||||
, m_ {this, version, revision}
|
||||
{
|
||||
}
|
||||
|
||||
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
|
||||
{
|
||||
if (port != m_->port_
|
||||
|| multicast_group_address != m_->multicast_group_address_)
|
||||
{
|
||||
m_->leave_multicast_group ();
|
||||
if (impl::BoundState == 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_))
|
||||
{
|
||||
m_->port_ = port;
|
||||
m_->join_multicast_group ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_->port_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::clear_decodes (QString const& id, quint8 window)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_};
|
||||
out << window;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message_text, bool low_confidence, quint8 modifiers)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_};
|
||||
out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
|
||||
<< message_text.toUtf8 () << low_confidence << modifiers;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::replay (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::close (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::halt_tx (QString const& id, bool auto_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_};
|
||||
out << auto_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::free_text (QString const& id, QString const& text, bool send)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_};
|
||||
out << text.toUtf8 () << send;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::location (QString const& id, QString const& loc)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_};
|
||||
out << loc.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::highlight_callsign (QString const& id, QString const& callsign
|
||||
, QColor const& bg, QColor const& fg, bool last_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_};
|
||||
out << callsign.toUtf8 () << bg << fg << last_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
|
||||
out << configuration_name.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::configure (QString const& id, 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);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*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_);
|
||||
}
|
||||
}
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
#include "Radio.hpp"
|
||||
#include "models/FrequencyList.hpp"
|
||||
#include "AudioDevice.hpp"
|
||||
#include "Audio/AudioDevice.hpp"
|
||||
#include "Configuration.hpp"
|
||||
#include "models/StationList.hpp"
|
||||
#include "Transceiver.hpp"
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "Transceiver/Transceiver.hpp"
|
||||
#include "Transceiver/TransceiverFactory.hpp"
|
||||
#include "WFPalette.hpp"
|
||||
#include "models/IARURegions.hpp"
|
||||
#include "models/DecodeHighlightingModel.hpp"
|
||||
|
|
350
Modulator.cpp
350
Modulator.cpp
|
@ -1,350 +0,0 @@
|
|||
#include "Modulator.hpp"
|
||||
#include <limits>
|
||||
#include <qmath.h>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include "widgets/mainwindow.h" // TODO: G4WJS - break this dependency
|
||||
#include "soundout.h"
|
||||
#include "commons.h"
|
||||
|
||||
#include "moc_Modulator.cpp"
|
||||
|
||||
extern float gran(); // Noise generator (for tests only)
|
||||
|
||||
#define RAMP_INCREMENT 64 // MUST be an integral factor of 2^16
|
||||
|
||||
#if defined (WSJT_SOFT_KEYING)
|
||||
# define SOFT_KEYING WSJT_SOFT_KEYING
|
||||
#else
|
||||
# define SOFT_KEYING 1
|
||||
#endif
|
||||
|
||||
double constexpr Modulator::m_twoPi;
|
||||
|
||||
// float wpm=20.0;
|
||||
// unsigned m_nspd=1.2*48000.0/wpm;
|
||||
// m_nspd=3072; //18.75 WPM
|
||||
|
||||
Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
|
||||
QObject * parent)
|
||||
: AudioDevice {parent}
|
||||
, m_quickClose {false}
|
||||
, m_phi {0.0}
|
||||
, m_toneSpacing {0.0}
|
||||
, m_fSpread {0.0}
|
||||
, m_period {periodLengthInSeconds}
|
||||
, m_frameRate {frameRate}
|
||||
, m_state {Idle}
|
||||
, m_tuning {false}
|
||||
, m_cwLevel {false}
|
||||
, m_j0 {-1}
|
||||
, m_toneFrequency0 {1500.0}
|
||||
{
|
||||
}
|
||||
|
||||
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
double frequency, double toneSpacing,
|
||||
SoundOutput * stream, Channel channel,
|
||||
bool synchronize, bool fastMode, double dBSNR, double TRperiod)
|
||||
{
|
||||
Q_ASSERT (stream);
|
||||
// Time according to this computer which becomes our base time
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
|
||||
if(m_state != Idle) stop();
|
||||
m_quickClose = false;
|
||||
m_symbolsLength = symbolsLength;
|
||||
m_isym0 = std::numeric_limits<unsigned>::max (); // big number
|
||||
m_frequency0 = 0.;
|
||||
m_phi = 0.;
|
||||
m_addNoise = dBSNR < 0.;
|
||||
m_nsps = framesPerSymbol;
|
||||
m_frequency = frequency;
|
||||
m_amp = std::numeric_limits<qint16>::max ();
|
||||
m_toneSpacing = toneSpacing;
|
||||
m_bFastMode=fastMode;
|
||||
m_TRperiod=TRperiod;
|
||||
unsigned delay_ms=1000;
|
||||
if(m_nsps==1920) delay_ms=500; //FT8
|
||||
if(m_nsps==576) delay_ms=300; //FT4
|
||||
|
||||
// noise generator parameters
|
||||
if (m_addNoise) {
|
||||
m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0));
|
||||
m_fac = 3000.0;
|
||||
if (m_snr > 1.0) m_fac = 3000.0 / m_snr;
|
||||
}
|
||||
|
||||
// round up to an exact portion of a second that allows for startup delays
|
||||
m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000;
|
||||
|
||||
if(m_bFastMode) m_ic=0;
|
||||
|
||||
m_silentFrames = 0;
|
||||
// calculate number of silent frames to send, so that audio will start at
|
||||
// the nominal time "delay_ms" into the Tx sequence.
|
||||
if (synchronize && !m_tuning && !m_bFastMode) {
|
||||
m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000));
|
||||
}
|
||||
|
||||
// qDebug() << "aa" << QDateTime::currentDateTimeUtc().toString("hh:mm:ss.zzz")
|
||||
// << m_ic << m_silentFrames << m_silentFrames/48000.0
|
||||
// << mstr << fmod(double(ms0),1000.0*m_period);
|
||||
|
||||
|
||||
initialize (QIODevice::ReadOnly, channel);
|
||||
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
|
||||
Synchronizing : Active));
|
||||
m_stream = stream;
|
||||
if (m_stream) m_stream->restart (this);
|
||||
}
|
||||
|
||||
void Modulator::tune (bool newState)
|
||||
{
|
||||
m_tuning = newState;
|
||||
if (!m_tuning) stop (true);
|
||||
}
|
||||
|
||||
void Modulator::stop (bool quick)
|
||||
{
|
||||
m_quickClose = quick;
|
||||
close ();
|
||||
}
|
||||
|
||||
void Modulator::close ()
|
||||
{
|
||||
if (m_stream)
|
||||
{
|
||||
if (m_quickClose)
|
||||
{
|
||||
m_stream->reset ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stream->stop ();
|
||||
}
|
||||
}
|
||||
if (m_state != Idle)
|
||||
{
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
}
|
||||
AudioDevice::close ();
|
||||
}
|
||||
|
||||
qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
{
|
||||
double toneFrequency=1500.0;
|
||||
if(m_nsps==6) {
|
||||
toneFrequency=1000.0;
|
||||
m_frequency=1000.0;
|
||||
m_frequency0=1000.0;
|
||||
}
|
||||
if(maxSize==0) return 0;
|
||||
Q_ASSERT (!(maxSize % qint64 (bytesPerFrame ()))); // no torn frames
|
||||
Q_ASSERT (isOpen ());
|
||||
|
||||
qint64 numFrames (maxSize / bytesPerFrame ());
|
||||
qint16 * samples (reinterpret_cast<qint16 *> (data));
|
||||
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
|
||||
qint64 framesGenerated (0);
|
||||
|
||||
// if(m_ic==0) qDebug() << "Modulator::readData" << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case Synchronizing:
|
||||
{
|
||||
if (m_silentFrames) { // send silence up to first second
|
||||
framesGenerated = qMin (m_silentFrames, numFrames);
|
||||
for ( ; samples != end; samples = load (0, samples)) { // silence
|
||||
}
|
||||
m_silentFrames -= framesGenerated;
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
|
||||
Q_EMIT stateChanged ((m_state = Active));
|
||||
m_cwLevel = false;
|
||||
m_ramp = 0; // prepare for CW wave shaping
|
||||
}
|
||||
// fall through
|
||||
|
||||
case Active:
|
||||
{
|
||||
unsigned int isym=0;
|
||||
|
||||
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
|
||||
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode);
|
||||
if(m_TRperiod==3.0) slowCwId=false;
|
||||
bool fastCwId=false;
|
||||
static bool bCwId=false;
|
||||
qint64 ms = QDateTime::currentMSecsSinceEpoch();
|
||||
float tsec=0.001*(ms % int(1000*m_TRperiod));
|
||||
if(m_bFastMode and (icw[0]>0) and (tsec > (m_TRperiod-5.0))) fastCwId=true;
|
||||
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
|
||||
|
||||
// qDebug() << "Mod A" << m_ic << isym << tsec;
|
||||
|
||||
if(slowCwId or fastCwId) { // Transmit CW ID?
|
||||
m_dphi = m_twoPi*m_frequency/m_frameRate;
|
||||
if(m_bFastMode and !bCwId) {
|
||||
m_frequency=1500; // Set params for CW ID
|
||||
m_dphi = m_twoPi*m_frequency/m_frameRate;
|
||||
m_symbolsLength=126;
|
||||
m_nsps=4096.0*12000.0/11025.0;
|
||||
m_ic=2246949;
|
||||
m_nspd=2560; // 22.5 WPM
|
||||
if(icw[0]*m_nspd/48000.0 > 4.0) m_nspd=4.0*48000.0/icw[0]; //Faster CW for long calls
|
||||
}
|
||||
bCwId=true;
|
||||
unsigned ic0 = m_symbolsLength * 4 * m_nsps;
|
||||
unsigned j(0);
|
||||
|
||||
while (samples != end) {
|
||||
j = (m_ic - ic0)/m_nspd + 1; // symbol of this sample
|
||||
bool level {bool (icw[j])};
|
||||
m_phi += m_dphi;
|
||||
if (m_phi > m_twoPi) m_phi -= m_twoPi;
|
||||
qint16 sample=0;
|
||||
float amp=32767.0;
|
||||
float x=0;
|
||||
if(m_ramp!=0) {
|
||||
x=qSin(float(m_phi));
|
||||
if(SOFT_KEYING) {
|
||||
amp=qAbs(qint32(m_ramp));
|
||||
if(amp>32767.0) amp=32767.0;
|
||||
}
|
||||
sample=round(amp*x);
|
||||
}
|
||||
if(m_bFastMode) {
|
||||
sample=0;
|
||||
if(level) sample=32767.0*x;
|
||||
}
|
||||
if (int (j) <= icw[0] && j < NUM_CW_SYMBOLS) { // stop condition
|
||||
samples = load (postProcessSample (sample), samples);
|
||||
++framesGenerated;
|
||||
++m_ic;
|
||||
} else {
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
|
||||
// adjust ramp
|
||||
if ((m_ramp != 0 && m_ramp != std::numeric_limits<qint16>::min ()) || level != m_cwLevel) {
|
||||
// either ramp has terminated at max/min or direction has changed
|
||||
m_ramp += RAMP_INCREMENT; // ramp
|
||||
}
|
||||
m_cwLevel = level;
|
||||
}
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
} else {
|
||||
bCwId=false;
|
||||
} //End of code for CW ID
|
||||
|
||||
double const baud (12000.0 / m_nsps);
|
||||
// fade out parameters (no fade out for tuning)
|
||||
unsigned int i0,i1;
|
||||
if(m_tuning) {
|
||||
i1 = i0 = (m_bFastMode ? 999999 : 9999) * m_nsps;
|
||||
} else {
|
||||
i0=(m_symbolsLength - 0.017) * 4.0 * m_nsps;
|
||||
i1= m_symbolsLength * 4.0 * m_nsps;
|
||||
}
|
||||
if(m_bFastMode and !m_tuning) {
|
||||
i1=m_TRperiod*48000.0 - 24000.0;
|
||||
i0=i1-816;
|
||||
}
|
||||
|
||||
qint16 sample;
|
||||
|
||||
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
|
||||
isym=0;
|
||||
if(!m_tuning and m_TRperiod!=3.0) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
|
||||
if(m_bFastMode) isym=isym%m_symbolsLength;
|
||||
if (isym != m_isym0 || m_frequency != m_frequency0) {
|
||||
if(itone[0]>=100) {
|
||||
m_toneFrequency0=itone[0];
|
||||
} else {
|
||||
if(m_toneSpacing==0.0) {
|
||||
m_toneFrequency0=m_frequency + itone[isym]*baud;
|
||||
} else {
|
||||
m_toneFrequency0=m_frequency + itone[isym]*m_toneSpacing;
|
||||
}
|
||||
}
|
||||
m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate;
|
||||
m_isym0 = isym;
|
||||
m_frequency0 = m_frequency; //???
|
||||
}
|
||||
|
||||
int j=m_ic/480;
|
||||
if(m_fSpread>0.0 and j!=m_j0) {
|
||||
float x1=(float)qrand()/RAND_MAX;
|
||||
float x2=(float)qrand()/RAND_MAX;
|
||||
toneFrequency = m_toneFrequency0 + 0.5*m_fSpread*(x1+x2-1.0);
|
||||
m_dphi = m_twoPi * toneFrequency / m_frameRate;
|
||||
m_j0=j;
|
||||
}
|
||||
|
||||
m_phi += m_dphi;
|
||||
if (m_phi > m_twoPi) m_phi -= m_twoPi;
|
||||
if (m_ic > i0) m_amp = 0.98 * m_amp;
|
||||
if (m_ic > i1) m_amp = 0.0;
|
||||
|
||||
sample=qRound(m_amp*qSin(m_phi));
|
||||
|
||||
//Here's where we transmit from a precomputed wave[] array:
|
||||
if(!m_tuning and (m_toneSpacing < 0)) {
|
||||
m_amp=32767.0;
|
||||
sample=qRound(m_amp*foxcom_.wave[m_ic]);
|
||||
}
|
||||
|
||||
samples = load(postProcessSample(sample), samples);
|
||||
++framesGenerated;
|
||||
++m_ic;
|
||||
}
|
||||
|
||||
if (m_amp == 0.0) { // TODO G4WJS: compare double with zero might not be wise
|
||||
if (icw[0] == 0) {
|
||||
// no CW ID to send
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
m_phi = 0.0;
|
||||
}
|
||||
|
||||
m_frequency0 = m_frequency;
|
||||
// done for this chunk - continue on next call
|
||||
|
||||
// qDebug() << "Mod B" << m_ic << i1 << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
|
||||
|
||||
while (samples != end) // pad block with silence
|
||||
{
|
||||
samples = load (0, samples);
|
||||
++framesGenerated;
|
||||
}
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
// fall through
|
||||
|
||||
case Idle:
|
||||
break;
|
||||
}
|
||||
|
||||
Q_ASSERT (Idle == m_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint16 Modulator::postProcessSample (qint16 sample) const
|
||||
{
|
||||
if (m_addNoise) { // Test frame, we'll add noise
|
||||
qint32 s = m_fac * (gran () + sample * m_snr / 32768.0);
|
||||
if (s > std::numeric_limits<qint16>::max ()) {
|
||||
s = std::numeric_limits<qint16>::max ();
|
||||
}
|
||||
if (s < std::numeric_limits<qint16>::min ()) {
|
||||
s = std::numeric_limits<qint16>::min ();
|
||||
}
|
||||
sample = s;
|
||||
}
|
||||
return sample;
|
||||
}
|
|
@ -1,96 +0,0 @@
|
|||
#ifndef MODULATOR_HPP__
|
||||
#define MODULATOR_HPP__
|
||||
|
||||
#include <QAudio>
|
||||
#include <QPointer>
|
||||
|
||||
#include "AudioDevice.hpp"
|
||||
|
||||
class SoundOutput;
|
||||
|
||||
//
|
||||
// Input device that generates PCM audio frames that encode a message
|
||||
// and an optional CW ID.
|
||||
//
|
||||
// Output can be muted while underway, preserving waveform timing when
|
||||
// transmission is resumed.
|
||||
//
|
||||
class Modulator
|
||||
: public AudioDevice
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum ModulatorState {Synchronizing, Active, Idle};
|
||||
|
||||
Modulator (unsigned frameRate, double periodLengthInSeconds, QObject * parent = nullptr);
|
||||
|
||||
void close () override;
|
||||
|
||||
bool isTuning () const {return m_tuning;}
|
||||
double frequency () const {return m_frequency;}
|
||||
bool isActive () const {return m_state != Idle;}
|
||||
void setSpread(double s) {m_fSpread=s;}
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
void set_nsym(int n) {m_symbolsLength=n;}
|
||||
void set_ms0(qint64 ms) {m_ms0=ms;}
|
||||
|
||||
Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, double frequency,
|
||||
double toneSpacing, SoundOutput *, Channel = Mono,
|
||||
bool synchronize = true, bool fastMode = false,
|
||||
double dBSNR = 99., double TRperiod=60.0);
|
||||
Q_SLOT void stop (bool quick = false);
|
||||
Q_SLOT void tune (bool newState = true);
|
||||
Q_SLOT void setFrequency (double newFrequency) {m_frequency = newFrequency;}
|
||||
Q_SIGNAL void stateChanged (ModulatorState) const;
|
||||
|
||||
protected:
|
||||
qint64 readData (char * data, qint64 maxSize) override;
|
||||
qint64 writeData (char const * /* data */, qint64 /* maxSize */) override
|
||||
{
|
||||
return -1; // we don't consume data
|
||||
}
|
||||
|
||||
private:
|
||||
qint16 postProcessSample (qint16 sample) const;
|
||||
|
||||
QPointer<SoundOutput> m_stream;
|
||||
bool m_quickClose;
|
||||
|
||||
unsigned m_symbolsLength;
|
||||
|
||||
static double constexpr m_twoPi = 2.0 * 3.141592653589793238462;
|
||||
unsigned m_nspd = 2048 + 512; // CW ID WPM factor = 22.5 WPM
|
||||
|
||||
double m_phi;
|
||||
double m_dphi;
|
||||
double m_amp;
|
||||
double m_nsps;
|
||||
double volatile m_frequency;
|
||||
double m_frequency0;
|
||||
double m_snr;
|
||||
double m_fac;
|
||||
double m_toneSpacing;
|
||||
double m_fSpread;
|
||||
double m_TRperiod;
|
||||
double m_period;
|
||||
|
||||
qint64 m_silentFrames;
|
||||
qint64 m_ms0;
|
||||
qint16 m_ramp;
|
||||
|
||||
unsigned m_frameRate;
|
||||
ModulatorState volatile m_state;
|
||||
|
||||
bool volatile m_tuning;
|
||||
bool m_addNoise;
|
||||
bool m_bFastMode;
|
||||
|
||||
bool m_cwLevel;
|
||||
unsigned m_ic;
|
||||
unsigned m_isym0;
|
||||
int m_j0;
|
||||
double m_toneFrequency0;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,350 @@
|
|||
#include "Modulator.hpp"
|
||||
#include <limits>
|
||||
#include <qmath.h>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include "widgets/mainwindow.h" // TODO: G4WJS - break this dependency
|
||||
#include "Audio/soundout.h"
|
||||
#include "commons.h"
|
||||
|
||||
#include "moc_Modulator.cpp"
|
||||
|
||||
extern float gran(); // Noise generator (for tests only)
|
||||
|
||||
#define RAMP_INCREMENT 64 // MUST be an integral factor of 2^16
|
||||
|
||||
#if defined (WSJT_SOFT_KEYING)
|
||||
# define SOFT_KEYING WSJT_SOFT_KEYING
|
||||
#else
|
||||
# define SOFT_KEYING 1
|
||||
#endif
|
||||
|
||||
double constexpr Modulator::m_twoPi;
|
||||
|
||||
// float wpm=20.0;
|
||||
// unsigned m_nspd=1.2*48000.0/wpm;
|
||||
// m_nspd=3072; //18.75 WPM
|
||||
|
||||
Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
|
||||
QObject * parent)
|
||||
: AudioDevice {parent}
|
||||
, m_quickClose {false}
|
||||
, m_phi {0.0}
|
||||
, m_toneSpacing {0.0}
|
||||
, m_fSpread {0.0}
|
||||
, m_period {periodLengthInSeconds}
|
||||
, m_frameRate {frameRate}
|
||||
, m_state {Idle}
|
||||
, m_tuning {false}
|
||||
, m_cwLevel {false}
|
||||
, m_j0 {-1}
|
||||
, m_toneFrequency0 {1500.0}
|
||||
{
|
||||
}
|
||||
|
||||
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
|
||||
double frequency, double toneSpacing,
|
||||
SoundOutput * stream, Channel channel,
|
||||
bool synchronize, bool fastMode, double dBSNR, double TRperiod)
|
||||
{
|
||||
Q_ASSERT (stream);
|
||||
// Time according to this computer which becomes our base time
|
||||
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
|
||||
unsigned mstr = ms0 % int(1000.0*m_period); // ms into the nominal Tx start time
|
||||
|
||||
if(m_state != Idle) stop();
|
||||
m_quickClose = false;
|
||||
m_symbolsLength = symbolsLength;
|
||||
m_isym0 = std::numeric_limits<unsigned>::max (); // big number
|
||||
m_frequency0 = 0.;
|
||||
m_phi = 0.;
|
||||
m_addNoise = dBSNR < 0.;
|
||||
m_nsps = framesPerSymbol;
|
||||
m_frequency = frequency;
|
||||
m_amp = std::numeric_limits<qint16>::max ();
|
||||
m_toneSpacing = toneSpacing;
|
||||
m_bFastMode=fastMode;
|
||||
m_TRperiod=TRperiod;
|
||||
unsigned delay_ms=1000;
|
||||
if(m_nsps==1920) delay_ms=500; //FT8
|
||||
if(m_nsps==576) delay_ms=300; //FT4
|
||||
|
||||
// noise generator parameters
|
||||
if (m_addNoise) {
|
||||
m_snr = qPow (10.0, 0.05 * (dBSNR - 6.0));
|
||||
m_fac = 3000.0;
|
||||
if (m_snr > 1.0) m_fac = 3000.0 / m_snr;
|
||||
}
|
||||
|
||||
// round up to an exact portion of a second that allows for startup delays
|
||||
m_ic = (mstr / delay_ms) * m_frameRate * delay_ms / 1000;
|
||||
|
||||
if(m_bFastMode) m_ic=0;
|
||||
|
||||
m_silentFrames = 0;
|
||||
// calculate number of silent frames to send, so that audio will start at
|
||||
// the nominal time "delay_ms" into the Tx sequence.
|
||||
if (synchronize && !m_tuning && !m_bFastMode) {
|
||||
m_silentFrames = m_ic + m_frameRate / (1000 / delay_ms) - (mstr * (m_frameRate / 1000));
|
||||
}
|
||||
|
||||
// qDebug() << "aa" << QDateTime::currentDateTimeUtc().toString("hh:mm:ss.zzz")
|
||||
// << m_ic << m_silentFrames << m_silentFrames/48000.0
|
||||
// << mstr << fmod(double(ms0),1000.0*m_period);
|
||||
|
||||
|
||||
initialize (QIODevice::ReadOnly, channel);
|
||||
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
|
||||
Synchronizing : Active));
|
||||
m_stream = stream;
|
||||
if (m_stream) m_stream->restart (this);
|
||||
}
|
||||
|
||||
void Modulator::tune (bool newState)
|
||||
{
|
||||
m_tuning = newState;
|
||||
if (!m_tuning) stop (true);
|
||||
}
|
||||
|
||||
void Modulator::stop (bool quick)
|
||||
{
|
||||
m_quickClose = quick;
|
||||
close ();
|
||||
}
|
||||
|
||||
void Modulator::close ()
|
||||
{
|
||||
if (m_stream)
|
||||
{
|
||||
if (m_quickClose)
|
||||
{
|
||||
m_stream->reset ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stream->stop ();
|
||||
}
|
||||
}
|
||||
if (m_state != Idle)
|
||||
{
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
}
|
||||
AudioDevice::close ();
|
||||
}
|
||||
|
||||
qint64 Modulator::readData (char * data, qint64 maxSize)
|
||||
{
|
||||
double toneFrequency=1500.0;
|
||||
if(m_nsps==6) {
|
||||
toneFrequency=1000.0;
|
||||
m_frequency=1000.0;
|
||||
m_frequency0=1000.0;
|
||||
}
|
||||
if(maxSize==0) return 0;
|
||||
Q_ASSERT (!(maxSize % qint64 (bytesPerFrame ()))); // no torn frames
|
||||
Q_ASSERT (isOpen ());
|
||||
|
||||
qint64 numFrames (maxSize / bytesPerFrame ());
|
||||
qint16 * samples (reinterpret_cast<qint16 *> (data));
|
||||
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
|
||||
qint64 framesGenerated (0);
|
||||
|
||||
// if(m_ic==0) qDebug() << "Modulator::readData" << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
|
||||
|
||||
switch (m_state)
|
||||
{
|
||||
case Synchronizing:
|
||||
{
|
||||
if (m_silentFrames) { // send silence up to first second
|
||||
framesGenerated = qMin (m_silentFrames, numFrames);
|
||||
for ( ; samples != end; samples = load (0, samples)) { // silence
|
||||
}
|
||||
m_silentFrames -= framesGenerated;
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
|
||||
Q_EMIT stateChanged ((m_state = Active));
|
||||
m_cwLevel = false;
|
||||
m_ramp = 0; // prepare for CW wave shaping
|
||||
}
|
||||
// fall through
|
||||
|
||||
case Active:
|
||||
{
|
||||
unsigned int isym=0;
|
||||
|
||||
if(!m_tuning) isym=m_ic/(4.0*m_nsps); // Actual fsample=48000
|
||||
bool slowCwId=((isym >= m_symbolsLength) && (icw[0] > 0)) && (!m_bFastMode);
|
||||
if(m_TRperiod==3.0) slowCwId=false;
|
||||
bool fastCwId=false;
|
||||
static bool bCwId=false;
|
||||
qint64 ms = QDateTime::currentMSecsSinceEpoch();
|
||||
float tsec=0.001*(ms % int(1000*m_TRperiod));
|
||||
if(m_bFastMode and (icw[0]>0) and (tsec > (m_TRperiod-5.0))) fastCwId=true;
|
||||
if(!m_bFastMode) m_nspd=2560; // 22.5 WPM
|
||||
|
||||
// qDebug() << "Mod A" << m_ic << isym << tsec;
|
||||
|
||||
if(slowCwId or fastCwId) { // Transmit CW ID?
|
||||
m_dphi = m_twoPi*m_frequency/m_frameRate;
|
||||
if(m_bFastMode and !bCwId) {
|
||||
m_frequency=1500; // Set params for CW ID
|
||||
m_dphi = m_twoPi*m_frequency/m_frameRate;
|
||||
m_symbolsLength=126;
|
||||
m_nsps=4096.0*12000.0/11025.0;
|
||||
m_ic=2246949;
|
||||
m_nspd=2560; // 22.5 WPM
|
||||
if(icw[0]*m_nspd/48000.0 > 4.0) m_nspd=4.0*48000.0/icw[0]; //Faster CW for long calls
|
||||
}
|
||||
bCwId=true;
|
||||
unsigned ic0 = m_symbolsLength * 4 * m_nsps;
|
||||
unsigned j(0);
|
||||
|
||||
while (samples != end) {
|
||||
j = (m_ic - ic0)/m_nspd + 1; // symbol of this sample
|
||||
bool level {bool (icw[j])};
|
||||
m_phi += m_dphi;
|
||||
if (m_phi > m_twoPi) m_phi -= m_twoPi;
|
||||
qint16 sample=0;
|
||||
float amp=32767.0;
|
||||
float x=0;
|
||||
if(m_ramp!=0) {
|
||||
x=qSin(float(m_phi));
|
||||
if(SOFT_KEYING) {
|
||||
amp=qAbs(qint32(m_ramp));
|
||||
if(amp>32767.0) amp=32767.0;
|
||||
}
|
||||
sample=round(amp*x);
|
||||
}
|
||||
if(m_bFastMode) {
|
||||
sample=0;
|
||||
if(level) sample=32767.0*x;
|
||||
}
|
||||
if (int (j) <= icw[0] && j < NUM_CW_SYMBOLS) { // stop condition
|
||||
samples = load (postProcessSample (sample), samples);
|
||||
++framesGenerated;
|
||||
++m_ic;
|
||||
} else {
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
|
||||
// adjust ramp
|
||||
if ((m_ramp != 0 && m_ramp != std::numeric_limits<qint16>::min ()) || level != m_cwLevel) {
|
||||
// either ramp has terminated at max/min or direction has changed
|
||||
m_ramp += RAMP_INCREMENT; // ramp
|
||||
}
|
||||
m_cwLevel = level;
|
||||
}
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
} else {
|
||||
bCwId=false;
|
||||
} //End of code for CW ID
|
||||
|
||||
double const baud (12000.0 / m_nsps);
|
||||
// fade out parameters (no fade out for tuning)
|
||||
unsigned int i0,i1;
|
||||
if(m_tuning) {
|
||||
i1 = i0 = (m_bFastMode ? 999999 : 9999) * m_nsps;
|
||||
} else {
|
||||
i0=(m_symbolsLength - 0.017) * 4.0 * m_nsps;
|
||||
i1= m_symbolsLength * 4.0 * m_nsps;
|
||||
}
|
||||
if(m_bFastMode and !m_tuning) {
|
||||
i1=m_TRperiod*48000.0 - 24000.0;
|
||||
i0=i1-816;
|
||||
}
|
||||
|
||||
qint16 sample;
|
||||
|
||||
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
|
||||
isym=0;
|
||||
if(!m_tuning and m_TRperiod!=3.0) isym=m_ic/(4.0*m_nsps); //Actual fsample=48000
|
||||
if(m_bFastMode) isym=isym%m_symbolsLength;
|
||||
if (isym != m_isym0 || m_frequency != m_frequency0) {
|
||||
if(itone[0]>=100) {
|
||||
m_toneFrequency0=itone[0];
|
||||
} else {
|
||||
if(m_toneSpacing==0.0) {
|
||||
m_toneFrequency0=m_frequency + itone[isym]*baud;
|
||||
} else {
|
||||
m_toneFrequency0=m_frequency + itone[isym]*m_toneSpacing;
|
||||
}
|
||||
}
|
||||
m_dphi = m_twoPi * m_toneFrequency0 / m_frameRate;
|
||||
m_isym0 = isym;
|
||||
m_frequency0 = m_frequency; //???
|
||||
}
|
||||
|
||||
int j=m_ic/480;
|
||||
if(m_fSpread>0.0 and j!=m_j0) {
|
||||
float x1=(float)qrand()/RAND_MAX;
|
||||
float x2=(float)qrand()/RAND_MAX;
|
||||
toneFrequency = m_toneFrequency0 + 0.5*m_fSpread*(x1+x2-1.0);
|
||||
m_dphi = m_twoPi * toneFrequency / m_frameRate;
|
||||
m_j0=j;
|
||||
}
|
||||
|
||||
m_phi += m_dphi;
|
||||
if (m_phi > m_twoPi) m_phi -= m_twoPi;
|
||||
if (m_ic > i0) m_amp = 0.98 * m_amp;
|
||||
if (m_ic > i1) m_amp = 0.0;
|
||||
|
||||
sample=qRound(m_amp*qSin(m_phi));
|
||||
|
||||
//Here's where we transmit from a precomputed wave[] array:
|
||||
if(!m_tuning and (m_toneSpacing < 0)) {
|
||||
m_amp=32767.0;
|
||||
sample=qRound(m_amp*foxcom_.wave[m_ic]);
|
||||
}
|
||||
|
||||
samples = load(postProcessSample(sample), samples);
|
||||
++framesGenerated;
|
||||
++m_ic;
|
||||
}
|
||||
|
||||
if (m_amp == 0.0) { // TODO G4WJS: compare double with zero might not be wise
|
||||
if (icw[0] == 0) {
|
||||
// no CW ID to send
|
||||
Q_EMIT stateChanged ((m_state = Idle));
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
m_phi = 0.0;
|
||||
}
|
||||
|
||||
m_frequency0 = m_frequency;
|
||||
// done for this chunk - continue on next call
|
||||
|
||||
// qDebug() << "Mod B" << m_ic << i1 << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
|
||||
|
||||
while (samples != end) // pad block with silence
|
||||
{
|
||||
samples = load (0, samples);
|
||||
++framesGenerated;
|
||||
}
|
||||
return framesGenerated * bytesPerFrame ();
|
||||
}
|
||||
// fall through
|
||||
|
||||
case Idle:
|
||||
break;
|
||||
}
|
||||
|
||||
Q_ASSERT (Idle == m_state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint16 Modulator::postProcessSample (qint16 sample) const
|
||||
{
|
||||
if (m_addNoise) { // Test frame, we'll add noise
|
||||
qint32 s = m_fac * (gran () + sample * m_snr / 32768.0);
|
||||
if (s > std::numeric_limits<qint16>::max ()) {
|
||||
s = std::numeric_limits<qint16>::max ();
|
||||
}
|
||||
if (s < std::numeric_limits<qint16>::min ()) {
|
||||
s = std::numeric_limits<qint16>::min ();
|
||||
}
|
||||
sample = s;
|
||||
}
|
||||
return sample;
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
#ifndef MODULATOR_HPP__
|
||||
#define MODULATOR_HPP__
|
||||
|
||||
#include <QAudio>
|
||||
#include <QPointer>
|
||||
|
||||
#include "Audio/AudioDevice.hpp"
|
||||
|
||||
class SoundOutput;
|
||||
|
||||
//
|
||||
// Input device that generates PCM audio frames that encode a message
|
||||
// and an optional CW ID.
|
||||
//
|
||||
// Output can be muted while underway, preserving waveform timing when
|
||||
// transmission is resumed.
|
||||
//
|
||||
class Modulator
|
||||
: public AudioDevice
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
enum ModulatorState {Synchronizing, Active, Idle};
|
||||
|
||||
Modulator (unsigned frameRate, double periodLengthInSeconds, QObject * parent = nullptr);
|
||||
|
||||
void close () override;
|
||||
|
||||
bool isTuning () const {return m_tuning;}
|
||||
double frequency () const {return m_frequency;}
|
||||
bool isActive () const {return m_state != Idle;}
|
||||
void setSpread(double s) {m_fSpread=s;}
|
||||
void setTRPeriod(double p) {m_period=p;}
|
||||
void set_nsym(int n) {m_symbolsLength=n;}
|
||||
void set_ms0(qint64 ms) {m_ms0=ms;}
|
||||
|
||||
Q_SLOT void start (unsigned symbolsLength, double framesPerSymbol, double frequency,
|
||||
double toneSpacing, SoundOutput *, Channel = Mono,
|
||||
bool synchronize = true, bool fastMode = false,
|
||||
double dBSNR = 99., double TRperiod=60.0);
|
||||
Q_SLOT void stop (bool quick = false);
|
||||
Q_SLOT void tune (bool newState = true);
|
||||
Q_SLOT void setFrequency (double newFrequency) {m_frequency = newFrequency;}
|
||||
Q_SIGNAL void stateChanged (ModulatorState) const;
|
||||
|
||||
protected:
|
||||
qint64 readData (char * data, qint64 maxSize) override;
|
||||
qint64 writeData (char const * /* data */, qint64 /* maxSize */) override
|
||||
{
|
||||
return -1; // we don't consume data
|
||||
}
|
||||
|
||||
private:
|
||||
qint16 postProcessSample (qint16 sample) const;
|
||||
|
||||
QPointer<SoundOutput> m_stream;
|
||||
bool m_quickClose;
|
||||
|
||||
unsigned m_symbolsLength;
|
||||
|
||||
static double constexpr m_twoPi = 2.0 * 3.141592653589793238462;
|
||||
unsigned m_nspd = 2048 + 512; // CW ID WPM factor = 22.5 WPM
|
||||
|
||||
double m_phi;
|
||||
double m_dphi;
|
||||
double m_amp;
|
||||
double m_nsps;
|
||||
double volatile m_frequency;
|
||||
double m_frequency0;
|
||||
double m_snr;
|
||||
double m_fac;
|
||||
double m_toneSpacing;
|
||||
double m_fSpread;
|
||||
double m_TRperiod;
|
||||
double m_period;
|
||||
|
||||
qint64 m_silentFrames;
|
||||
qint64 m_ms0;
|
||||
qint16 m_ramp;
|
||||
|
||||
unsigned m_frameRate;
|
||||
ModulatorState volatile m_state;
|
||||
|
||||
bool volatile m_tuning;
|
||||
bool m_addNoise;
|
||||
bool m_bFastMode;
|
||||
|
||||
bool m_cwLevel;
|
||||
unsigned m_ic;
|
||||
unsigned m_isym0;
|
||||
int m_j0;
|
||||
double m_toneFrequency0;
|
||||
};
|
||||
|
||||
#endif
|
92
NEWS
92
NEWS
|
@ -13,6 +13,98 @@
|
|||
Copyright 2001 - 2019 by Joe Taylor, K1JT.
|
||||
|
||||
|
||||
Release: WSJT-X 2.2.0-rc1
|
||||
May 10, 2020
|
||||
-------------------------
|
||||
|
||||
WSJT-X 2.2.0-rc1 is a beta-quality release candidate for a program
|
||||
upgrade that provides a number of new features and capabilities. These
|
||||
include:
|
||||
|
||||
- Improvements to the decoders for five modes:
|
||||
|
||||
FT4: Corrected bugs that prevented AP decoding and/or multi-pass
|
||||
decoding in some circumstances. The algorithm for AP
|
||||
decoding has been improved and extended.
|
||||
|
||||
FT8: Decoding is now spread over three intervals. The first
|
||||
starts at t = 11.8 s into an Rx sequence and typically yields
|
||||
around 85% of the possible decodes for the sequence. You
|
||||
therefore see most decodes much earlier than before. A second
|
||||
processing step starts at 13.5 s, and the final one at 14.7 s.
|
||||
Overall decoding yield on crowded bands is improved by 10% or
|
||||
more. (Systems with receive latency greater than 0.2 s will see
|
||||
smaller improvements, but will still see many decodes earlier
|
||||
than before.)
|
||||
|
||||
JT4: Formatting and display of Averaged and Deep Search decodes
|
||||
has been cleaned up and made consistent with other modes.
|
||||
Together with JT65 and QRA64, JT4 remains one of the digital
|
||||
modes widely for EME and other extreme weak-signal work on
|
||||
microwave bands.
|
||||
|
||||
JT65: Many improvements for Averaged and Deep Search decodes and
|
||||
their display to the user. These improvements are particularly
|
||||
important for EME on VHF and UHF bands.
|
||||
|
||||
WSPR: Significant improvements have been made to the WSPR
|
||||
decoder's sensitivity, its ability to cope with many signals in
|
||||
a crowded sub-band, and its rate of undetected false decodes.
|
||||
We now use up to three decoding passes. Passes 1 and 2 use
|
||||
noncoherent demodulation of single symbols and allow for
|
||||
frequency drifts up to ±4 Hz in a transmission. Pass 3 assumes
|
||||
no drift and does coherent block detection of up to three
|
||||
symbols. It also applies bit-by-bit normalization of the
|
||||
single-symbol bit metrics, a technique that has proven helpful
|
||||
for signals corrupted by artifacts of the subtraction of
|
||||
stronger signals and also for LF/MF signals heavily contaminated
|
||||
by lightning transients. With these improvements the number of
|
||||
decodes in a crowded WSPR sub-band typically increases by 10 to
|
||||
15%.
|
||||
|
||||
- New format for "EU VHF Contest" Tx2 and Tx3 messages
|
||||
|
||||
When "EU VHF Contest" is selected, the Tx2 and Tx3 messages
|
||||
(those conveying signal report, serial number, and 6-character
|
||||
locator) now use hashcodes for both callsigns. This change is
|
||||
NOT backward compatible with earlier versions of _WSJT-X_, so
|
||||
all users of EU VHF Contest messages should be sure to upgrade
|
||||
to versiion 2.2.0.
|
||||
|
||||
- Accessibility
|
||||
|
||||
Keyboard shortcuts have been added as an aid to accessibility:
|
||||
Alt+R sets Tx4 message to RR73, Ctrl+R sets it to RRR.
|
||||
|
||||
As an aid for partial color-blindness, the "inverted goal posts"
|
||||
marking Rx frequency on the Wide Graph's frequency scale are now
|
||||
rendered in a darker shade of green.
|
||||
|
||||
- Minor enhancements and bug fixes
|
||||
|
||||
"Save None" now writes no .wav files to disk, even temporarily.
|
||||
|
||||
An explicit entry for "WW Digi Contest" has been added to
|
||||
"Special operating activities" on the "Settings | Advanced" tab.
|
||||
|
||||
Contest mode FT4 now always uses RR73 for the Tx4 message.
|
||||
|
||||
The Status bar now displays the number of decodes found in the
|
||||
most recent Rx sequence.
|
||||
|
||||
Release candidate WSJT-X 2.2.0-rc1 will be available for beta-testing
|
||||
for one month starting on May 10, 2020. We currently plan a General
|
||||
Availability (GA) release of WSJT-X 2.2.0 on June 1, 2020.
|
||||
|
||||
For those looking even farther ahead: We are well along in the
|
||||
development of two new modes designed for the LF and MF bands. One
|
||||
mode is for WSPR-like activity and one for making 2-way QSOs. Both
|
||||
use Low-density Parity Check (LDPC) codes, 4-GFSK modulation, and
|
||||
two-minute T/R sequences. The QSO mode reaches threshold SNR
|
||||
sensitivity around -31 dB on the AWGN channel, and the WSPR-like mode
|
||||
better than -32 dB.
|
||||
|
||||
|
||||
Release: WSJT-X 2.1.1
|
||||
November 25, 2019
|
||||
---------------------
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
#include "NetworkServerLookup.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QString>
|
||||
|
||||
std::tuple<QHostAddress, quint16>
|
||||
network_server_lookup (QString query
|
||||
, quint16 default_service_port
|
||||
, QHostAddress default_host_address
|
||||
, QAbstractSocket::NetworkLayerProtocol required_protocol)
|
||||
{
|
||||
query = query.trimmed ();
|
||||
|
||||
QHostAddress host_address {default_host_address};
|
||||
quint16 service_port {default_service_port};
|
||||
|
||||
QString host_name;
|
||||
if (!query.isEmpty ())
|
||||
{
|
||||
int port_colon_index {-1};
|
||||
|
||||
if ('[' == query[0])
|
||||
{
|
||||
// assume IPv6 combined address/port syntax [<address>]:<port>
|
||||
auto close_bracket_index = query.lastIndexOf (']');
|
||||
host_name = query.mid (1, close_bracket_index - 1);
|
||||
port_colon_index = query.indexOf (':', close_bracket_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
port_colon_index = query.lastIndexOf (':');
|
||||
host_name = query.left (port_colon_index);
|
||||
}
|
||||
host_name = host_name.trimmed ();
|
||||
|
||||
if (port_colon_index >= 0)
|
||||
{
|
||||
bool ok;
|
||||
service_port = query.mid (port_colon_index + 1).trimmed ().toUShort (&ok);
|
||||
if (!ok)
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: invalid port"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!host_name.isEmpty ())
|
||||
{
|
||||
auto host_info = QHostInfo::fromName (host_name);
|
||||
if (host_info.addresses ().isEmpty ())
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: host name lookup failed"};
|
||||
}
|
||||
|
||||
bool found {false};
|
||||
for (int i {0}; i < host_info.addresses ().size () && !found; ++i)
|
||||
{
|
||||
host_address = host_info.addresses ().at (i);
|
||||
switch (required_protocol)
|
||||
{
|
||||
case QAbstractSocket::IPv4Protocol:
|
||||
case QAbstractSocket::IPv6Protocol:
|
||||
if (required_protocol != host_address.protocol ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
// fall through
|
||||
case QAbstractSocket::AnyIPProtocol:
|
||||
found = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error {"network server lookup error: invalid required protocol"};
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: no suitable host address found"};
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple (host_address, service_port);
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
// KISS Interface for posting spots to PSK Reporter web site
|
||||
// Implemented by Edson Pereira PY2SDR
|
||||
//
|
||||
// Reports will be sent in batch mode every 5 minutes.
|
||||
|
||||
#include "psk_reporter.h"
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Network/MessageClient.hpp"
|
||||
|
||||
#include "moc_psk_reporter.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
int constexpr MAX_PAYLOAD_LENGTH {1400};
|
||||
}
|
||||
|
||||
PSK_Reporter::PSK_Reporter(MessageClient * message_client, QObject *parent) :
|
||||
QObject {parent},
|
||||
m_messageClient {message_client},
|
||||
reportTimer {new QTimer {this}},
|
||||
m_sequenceNumber {0}
|
||||
{
|
||||
m_header_h = "000Allllttttttttssssssssiiiiiiii";
|
||||
|
||||
// We use 50E2 and 50E3 for link Id
|
||||
m_rxInfoDescriptor_h = "0003002C50E200040000"
|
||||
"8002FFFF0000768F" // 2. Rx Call
|
||||
"8004FFFF0000768F" // 4. Rx Grid
|
||||
"8008FFFF0000768F" // 8. Rx Soft
|
||||
"8009FFFF0000768F" // 9. Rx Antenna
|
||||
"0000";
|
||||
|
||||
m_txInfoDescriptor_h = "0002003C50E30007"
|
||||
"8001FFFF0000768F" // 1. Tx Call
|
||||
"800500040000768F" // 5. Tx Freq
|
||||
"800600010000768F" // 6. Tx snr
|
||||
"800AFFFF0000768F" // 10. Tx Mode
|
||||
"8003FFFF0000768F" // 3. Tx Grid
|
||||
"800B00010000768F" // 11. Tx info src
|
||||
"00960004"; // Report time
|
||||
|
||||
|
||||
m_randomId_h = QString("%1").arg(qrand(),8,16,QChar('0'));
|
||||
|
||||
QHostInfo::lookupHost("report.pskreporter.info", this, SLOT(dnsLookupResult(QHostInfo)));
|
||||
|
||||
connect(reportTimer, SIGNAL(timeout()), this, SLOT(sendReport()));
|
||||
reportTimer->start(5*60*1000); // 5 minutes;
|
||||
}
|
||||
|
||||
void PSK_Reporter::setLocalStation(QString call, QString gridSquare, QString antenna, QString programInfo)
|
||||
{
|
||||
m_rxCall = call;
|
||||
m_rxGrid = gridSquare;
|
||||
m_rxAnt = antenna;
|
||||
m_progId = programInfo;
|
||||
}
|
||||
|
||||
void PSK_Reporter::addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time )
|
||||
{
|
||||
QHash<QString,QString> spot;
|
||||
spot["call"] = call;
|
||||
spot["grid"] = grid;
|
||||
spot["snr"] = snr;
|
||||
spot["freq"] = freq;
|
||||
spot["mode"] = mode;
|
||||
spot["time"] = time;
|
||||
m_spotQueue.enqueue(spot);
|
||||
}
|
||||
|
||||
void PSK_Reporter::sendReport()
|
||||
{
|
||||
while (!m_spotQueue.isEmpty()) {
|
||||
QString report_h;
|
||||
|
||||
// Header
|
||||
QString header_h = m_header_h;
|
||||
header_h.replace("tttttttt", QString("%1").arg(QDateTime::currentDateTime().toTime_t(),8,16,QChar('0')));
|
||||
header_h.replace("ssssssss", QString("%1").arg(++m_sequenceNumber,8,16,QChar('0')));
|
||||
header_h.replace("iiiiiiii", m_randomId_h);
|
||||
|
||||
// Receiver information
|
||||
QString rxInfoData_h = "50E2llll";
|
||||
rxInfoData_h += QString("%1").arg(m_rxCall.length(),2,16,QChar('0')) + m_rxCall.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_rxGrid.length(),2,16,QChar('0')) + m_rxGrid.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_progId.length(),2,16,QChar('0')) + m_progId.toUtf8().toHex();
|
||||
rxInfoData_h += QString("%1").arg(m_rxAnt.length(),2,16,QChar('0')) + m_rxAnt.toUtf8().toHex();
|
||||
rxInfoData_h += "0000";
|
||||
rxInfoData_h.replace("50E2llll", "50E2" + QString("%1").arg(rxInfoData_h.length()/2,4,16,QChar('0')));
|
||||
|
||||
// Sender information
|
||||
QString txInfoData_h = "50E3llll";
|
||||
while (!m_spotQueue.isEmpty()
|
||||
&& (header_h.size () + m_rxInfoDescriptor_h.size () + m_txInfoDescriptor_h.size () + rxInfoData_h.size () + txInfoData_h.size ()) / 2 < MAX_PAYLOAD_LENGTH) {
|
||||
QHash<QString,QString> spot = m_spotQueue.dequeue();
|
||||
txInfoData_h += QString("%1").arg(spot["call"].length(),2,16,QChar('0')) + spot["call"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(spot["freq"].toLongLong(),8,16,QChar('0'));
|
||||
txInfoData_h += QString("%1").arg(spot["snr"].toInt(),8,16,QChar('0')).right(2);
|
||||
txInfoData_h += QString("%1").arg(spot["mode"].length(),2,16,QChar('0')) + spot["mode"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(spot["grid"].length(),2,16,QChar('0')) + spot["grid"].toUtf8().toHex();
|
||||
txInfoData_h += QString("%1").arg(1,2,16,QChar('0')); // REPORTER_SOURCE_AUTOMATIC
|
||||
txInfoData_h += QString("%1").arg(spot["time"].toInt(),8,16,QChar('0'));
|
||||
}
|
||||
txInfoData_h += "0000";
|
||||
txInfoData_h.replace("50E3llll", "50E3" + QString("%1").arg(txInfoData_h.length()/2,4,16,QChar('0')));
|
||||
report_h = header_h + m_rxInfoDescriptor_h + m_txInfoDescriptor_h + rxInfoData_h + txInfoData_h;
|
||||
//qDebug() << "Sending Report TX: ";
|
||||
|
||||
report_h.replace("000Allll", "000A" + QString("%1").arg(report_h.length()/2,4,16,QChar('0')));
|
||||
QByteArray report = QByteArray::fromHex(report_h.toUtf8());
|
||||
|
||||
// Send data to PSK Reporter site
|
||||
if (!m_pskReporterAddress.isNull()) {
|
||||
m_messageClient->send_raw_datagram (report, m_pskReporterAddress, 4739);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PSK_Reporter::dnsLookupResult(QHostInfo info)
|
||||
{
|
||||
if (!info.addresses().isEmpty()) {
|
||||
m_pskReporterAddress = info.addresses().at(0);
|
||||
// qDebug() << "PSK Reporter IP: " << m_pskReporterAddress;
|
||||
|
||||
// deal with miss-configured settings that attempt to set a
|
||||
// Pskreporter Internet address for the WSJT-X UDP protocol
|
||||
// server address
|
||||
m_messageClient->add_blocked_destination (m_pskReporterAddress);
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
#include "NetworkServerLookup.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <QHostInfo>
|
||||
#include <QString>
|
||||
|
||||
std::tuple<QHostAddress, quint16>
|
||||
network_server_lookup (QString query
|
||||
, quint16 default_service_port
|
||||
, QHostAddress default_host_address
|
||||
, QAbstractSocket::NetworkLayerProtocol required_protocol)
|
||||
{
|
||||
query = query.trimmed ();
|
||||
|
||||
QHostAddress host_address {default_host_address};
|
||||
quint16 service_port {default_service_port};
|
||||
|
||||
QString host_name;
|
||||
if (!query.isEmpty ())
|
||||
{
|
||||
int port_colon_index {-1};
|
||||
|
||||
if ('[' == query[0])
|
||||
{
|
||||
// assume IPv6 combined address/port syntax [<address>]:<port>
|
||||
auto close_bracket_index = query.lastIndexOf (']');
|
||||
host_name = query.mid (1, close_bracket_index - 1);
|
||||
port_colon_index = query.indexOf (':', close_bracket_index);
|
||||
}
|
||||
else
|
||||
{
|
||||
port_colon_index = query.lastIndexOf (':');
|
||||
host_name = query.left (port_colon_index);
|
||||
}
|
||||
host_name = host_name.trimmed ();
|
||||
|
||||
if (port_colon_index >= 0)
|
||||
{
|
||||
bool ok;
|
||||
service_port = query.mid (port_colon_index + 1).trimmed ().toUShort (&ok);
|
||||
if (!ok)
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: invalid port"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!host_name.isEmpty ())
|
||||
{
|
||||
auto host_info = QHostInfo::fromName (host_name);
|
||||
if (host_info.addresses ().isEmpty ())
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: host name lookup failed"};
|
||||
}
|
||||
|
||||
bool found {false};
|
||||
for (int i {0}; i < host_info.addresses ().size () && !found; ++i)
|
||||
{
|
||||
host_address = host_info.addresses ().at (i);
|
||||
switch (required_protocol)
|
||||
{
|
||||
case QAbstractSocket::IPv4Protocol:
|
||||
case QAbstractSocket::IPv6Protocol:
|
||||
if (required_protocol != host_address.protocol ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
// drop through
|
||||
|
||||
case QAbstractSocket::AnyIPProtocol:
|
||||
found = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error {"network server lookup error: invalid required protocol"};
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw std::runtime_error {"network server lookup error: no suitable host address found"};
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_tuple (host_address, service_port);
|
||||
}
|
|
@ -1,801 +0,0 @@
|
|||
#include "OmniRigTransceiver.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <objbase.h>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "qt_helpers.hpP"
|
||||
|
||||
#include "moc_OmniRigTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto constexpr OmniRig_transceiver_one_name = "OmniRig Rig 1";
|
||||
auto constexpr OmniRig_transceiver_two_name = "OmniRig Rig 2";
|
||||
}
|
||||
|
||||
auto OmniRigTransceiver::map_mode (OmniRig::RigParamX param) -> MODE
|
||||
{
|
||||
if (param & OmniRig::PM_CW_U)
|
||||
{
|
||||
return CW_R;
|
||||
}
|
||||
else if (param & OmniRig::PM_CW_L)
|
||||
{
|
||||
return CW;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_U)
|
||||
{
|
||||
return USB;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_L)
|
||||
{
|
||||
return LSB;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_U)
|
||||
{
|
||||
return DIG_U;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_L)
|
||||
{
|
||||
return DIG_L;
|
||||
}
|
||||
else if (param & OmniRig::PM_AM)
|
||||
{
|
||||
return AM;
|
||||
}
|
||||
else if (param & OmniRig::PM_FM)
|
||||
{
|
||||
return FM;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "unrecognized mode");
|
||||
throw_qstring (tr ("OmniRig: unrecognized mode"));
|
||||
return UNK;
|
||||
}
|
||||
|
||||
OmniRig::RigParamX OmniRigTransceiver::map_mode (MODE mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AM: return OmniRig::PM_AM;
|
||||
case CW: return OmniRig::PM_CW_L;
|
||||
case CW_R: return OmniRig::PM_CW_U;
|
||||
case USB: return OmniRig::PM_SSB_U;
|
||||
case LSB: return OmniRig::PM_SSB_L;
|
||||
case FSK: return OmniRig::PM_DIG_L;
|
||||
case FSK_R: return OmniRig::PM_DIG_U;
|
||||
case DIG_L: return OmniRig::PM_DIG_L;
|
||||
case DIG_U: return OmniRig::PM_DIG_U;
|
||||
case FM: return OmniRig::PM_FM;
|
||||
case DIG_FM: return OmniRig::PM_FM;
|
||||
default: break;
|
||||
}
|
||||
return OmniRig::PM_SSB_U; // quieten compiler grumble
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id1, int id2)
|
||||
{
|
||||
(*registry)[OmniRig_transceiver_one_name] = TransceiverFactory::Capabilities {
|
||||
id1
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
(*registry)[OmniRig_transceiver_two_name] = TransceiverFactory::Capabilities {
|
||||
id2
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
}
|
||||
|
||||
OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
RigNumber n, TransceiverFactory::PTTMethod ptt_type,
|
||||
QString const& ptt_port, QObject * parent)
|
||||
: TransceiverBase {parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, use_for_ptt_ {TransceiverFactory::PTT_method_CAT == ptt_type || ("CAT" == ptt_port && (TransceiverFactory::PTT_method_RTS == ptt_type || TransceiverFactory::PTT_method_DTR == ptt_type))}
|
||||
, ptt_type_ {ptt_type}
|
||||
, rig_number_ {n}
|
||||
, readable_params_ {0}
|
||||
, writable_params_ {0}
|
||||
, send_update_signal_ {false}
|
||||
, reversed_ {false}
|
||||
{
|
||||
}
|
||||
|
||||
// returns false on time out
|
||||
bool OmniRigTransceiver::await_notification_with_timeout (int timeout)
|
||||
{
|
||||
QEventLoop el;
|
||||
connect (this, &OmniRigTransceiver::notified, &el, [&el] () {el.exit (1);});
|
||||
QTimer::singleShot (timeout, Qt::CoarseTimer, &el, [&el] () {el.exit (0);});
|
||||
return 1 == el.exec (); // wait for notify or timer
|
||||
}
|
||||
|
||||
int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "starting");
|
||||
if (wrapped_) wrapped_->start (0);
|
||||
|
||||
CoInitializeEx (nullptr, 0 /*COINIT_APARTMENTTHREADED*/); // required because Qt only does this for GUI thread
|
||||
|
||||
omni_rig_.reset (new OmniRig::OmniRigX {this});
|
||||
if (omni_rig_->isNull ())
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "failed to start COM server");
|
||||
throw_qstring (tr ("Failed to start OmniRig COM server"));
|
||||
}
|
||||
|
||||
// COM/OLE exceptions get signaled
|
||||
connect (&*omni_rig_, SIGNAL (exception (int, QString, QString, QString)), this, SLOT (handle_COM_exception (int, QString, QString, QString)));
|
||||
|
||||
// IOmniRigXEvent interface signals
|
||||
connect (&*omni_rig_, SIGNAL (VisibleChange ()), this, SLOT (handle_visible_change ()));
|
||||
connect (&*omni_rig_, SIGNAL (RigTypeChange (int)), this, SLOT (handle_rig_type_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (StatusChange (int)), this, SLOT (handle_status_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (ParamsChange (int, int)), this, SLOT (handle_params_change (int, int)));
|
||||
connect (&*omni_rig_
|
||||
, SIGNAL (CustomReply (int, QVariant const&, QVariant const&))
|
||||
, this, SLOT (handle_custom_reply (int, QVariant const&, QVariant const&)));
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig s/w version:" << QString::number (omni_rig_->SoftwareVersion ()).toLocal8Bit ()
|
||||
<< "i/f version:" << QString::number (omni_rig_->InterfaceVersion ()).toLocal8Bit ());
|
||||
|
||||
// fetch the interface of the RigX CoClass and instantiate a proxy object
|
||||
switch (rig_number_)
|
||||
{
|
||||
case One: rig_.reset (new OmniRig::RigX (omni_rig_->Rig1 ())); break;
|
||||
case Two: rig_.reset (new OmniRig::RigX (omni_rig_->Rig2 ())); break;
|
||||
}
|
||||
|
||||
Q_ASSERT (rig_);
|
||||
Q_ASSERT (!rig_->isNull ());
|
||||
|
||||
offline_timer_.reset (new QTimer); // instantiate here as
|
||||
// constructor runs in wrong
|
||||
// thread
|
||||
offline_timer_->setSingleShot (true);
|
||||
connect (offline_timer_.data (), &QTimer::timeout, [this] () {offline ("Rig went offline");});
|
||||
|
||||
if (use_for_ptt_ && (TransceiverFactory::PTT_method_DTR == ptt_type_ || TransceiverFactory::PTT_method_RTS == ptt_type_))
|
||||
{
|
||||
// fetch the interface for the serial port if we need it for PTT
|
||||
port_.reset (new OmniRig::PortBits (rig_->PortBits ()));
|
||||
|
||||
Q_ASSERT (port_);
|
||||
Q_ASSERT (!port_->isNull ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig RTS state:" << port_->Rts ());
|
||||
|
||||
if (!port_->Lock ()) // try to take exclusive use of the OmniRig serial port for PTT
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "Failed to get exclusive use of serial port for PTT from OmniRig");
|
||||
}
|
||||
|
||||
// start off so we don't accidentally key the radio
|
||||
if (TransceiverFactory::PTT_method_DTR == ptt_type_)
|
||||
{
|
||||
port_->SetDtr (false);
|
||||
}
|
||||
else // RTS
|
||||
{
|
||||
port_->SetRts (false);
|
||||
}
|
||||
}
|
||||
|
||||
rig_type_ = rig_->RigType ();
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig initial rig type: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_type_)
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number_).toLocal8Bit ());
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (OmniRig::ST_ONLINE == rig_->Status ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
await_notification_with_timeout (1000);
|
||||
}
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
throw_qstring ("OmniRig: " + rig_->StatusStr ());
|
||||
}
|
||||
QThread::msleep (500); // leave some time for Omni-Rig to get
|
||||
// the rig status for the 1st. time
|
||||
auto f = rig_->GetRxFrequency ();
|
||||
for (int i = 0; (f == 0) && (i < 5); ++i)
|
||||
{
|
||||
await_notification_with_timeout (1000);
|
||||
f = rig_->GetRxFrequency ();
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
int resolution {0};
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
|
||||
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
// start with VFO A (probably MAIN) on rigs that we
|
||||
// can't query VFO but can set explicitly
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
f = state ().frequency ();
|
||||
if (f % 10) return resolution; // 1Hz resolution
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
if (!await_notification_with_timeout (1000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
switch (rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
case -5: resolution = -1; break; // 10Hz truncated
|
||||
case 5: resolution = 1; break; // 10Hz rounded
|
||||
case -15: resolution = -2; break; // 20Hz truncated
|
||||
case -55: resolution = -2; break; // 100Hz truncated
|
||||
case 45: resolution = 2; break; // 100Hz rounded
|
||||
}
|
||||
if (1 == resolution) // may be 20Hz rounded
|
||||
{
|
||||
test_frequency = f - f % 100 + 51;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
if (!await_notification_with_timeout (2000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 2: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
if (9 == rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
resolution = 2; // 20Hz rounded
|
||||
}
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_stop ()
|
||||
{
|
||||
QThread::msleep (200); // leave some time for pending
|
||||
// commands at the server end
|
||||
|
||||
offline_timer_.reset (); // destroy here rather than in
|
||||
// destructor as destructor runs in
|
||||
// wrong thread
|
||||
|
||||
if (port_)
|
||||
{
|
||||
port_->Unlock (); // release serial port
|
||||
port_->clear ();
|
||||
port_.reset ();
|
||||
}
|
||||
if (omni_rig_)
|
||||
{
|
||||
if (rig_)
|
||||
{
|
||||
rig_->clear ();
|
||||
rig_.reset ();
|
||||
}
|
||||
omni_rig_->clear ();
|
||||
omni_rig_.reset ();
|
||||
CoUninitialize ();
|
||||
}
|
||||
if (wrapped_) wrapped_->stop ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + help + ')');
|
||||
throw_qstring (tr ("OmniRig COM/OLE error: %1 at %2: %3 (%4)").arg (QString::number (code)).arg (source). arg (desc). arg (help));
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_visible_change ()
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_rig_type_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig type change: rig =" << rig_number);
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_->RigType ())
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_status_change (int rig_number)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"status change for rig %1"}.arg (rig_number).toLocal8Bit ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
auto const& status = rig_->StatusStr ().toLocal8Bit ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig status change: new status = " << status);
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
if (!offline_timer_->isActive ())
|
||||
{
|
||||
// Omni-Rig is prone to reporting the rig offline and
|
||||
// then recovering autonomously, so we will give it a
|
||||
// few seconds to make its mind up
|
||||
offline_timer_->start (10000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
offline_timer_->stop (); // good to go again
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// update_rx_frequency (rig_->GetRxFrequency ());
|
||||
// update_complete ();
|
||||
// TRACE_CAT ("OmniRigTransceiver", "frequency:" << state ().frequency ());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
|
||||
.arg (params, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ()
|
||||
<< "state before:" << state ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
// starting_ = false;
|
||||
TransceiverState old_state {state ()};
|
||||
auto need_frequency = false;
|
||||
|
||||
if (params & OmniRig::PM_VFOAA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
|
||||
update_split (false);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOAB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
|
||||
update_split (true);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
|
||||
update_split (true);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
|
||||
update_split (false);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOA");
|
||||
reversed_ = false;
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_VFOB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOB");
|
||||
reversed_ = true;
|
||||
need_frequency = true;
|
||||
}
|
||||
|
||||
if (params & OmniRig::PM_FREQ)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ");
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
if (need_frequency)
|
||||
{
|
||||
if (readable_params_ & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQ && !state ().ptt ())
|
||||
{
|
||||
auto f = rig_->Freq ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ = " << f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_PITCH)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "PITCH");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFFSET)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
|
||||
}
|
||||
if (params & OmniRig::PM_RIT0)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RIT0");
|
||||
}
|
||||
if (params & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq ();
|
||||
auto m = map_mode (rig_->Mode ());
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"VFOEQUAL f=%1 m=%2"}.arg (f).arg (m));
|
||||
update_rx_frequency (f);
|
||||
update_other_frequency (f);
|
||||
update_mode (m);
|
||||
}
|
||||
if (params & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOSWAP");
|
||||
auto f = state ().tx_frequency ();
|
||||
update_other_frequency (state ().frequency ());
|
||||
update_rx_frequency (f);
|
||||
update_mode (map_mode (rig_->Mode ()));
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
|
||||
update_split (true);
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
|
||||
update_split (false);
|
||||
}
|
||||
if (params & OmniRig::PM_RITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITON");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_XITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITON");
|
||||
}
|
||||
if (params & OmniRig::PM_XITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_RX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RX");
|
||||
update_PTT (false);
|
||||
}
|
||||
if (params & OmniRig::PM_TX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "TX");
|
||||
update_PTT ();
|
||||
}
|
||||
if (params & OmniRig::PM_CW_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW-R");
|
||||
update_mode (CW_R);
|
||||
}
|
||||
if (params & OmniRig::PM_CW_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW");
|
||||
update_mode (CW);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "USB");
|
||||
update_mode (USB);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "LSB");
|
||||
update_mode (LSB);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
|
||||
update_mode (DIG_U);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
|
||||
update_mode (DIG_L);
|
||||
}
|
||||
if (params & OmniRig::PM_AM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "AM");
|
||||
update_mode (AM);
|
||||
}
|
||||
if (params & OmniRig::PM_FM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FM");
|
||||
update_mode (FM);
|
||||
}
|
||||
|
||||
if (old_state != state () || send_update_signal_)
|
||||
{
|
||||
update_complete ();
|
||||
send_update_signal_ = false;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig params change: state after:" << state ());
|
||||
}
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply)
|
||||
{
|
||||
(void)command;
|
||||
(void)reply;
|
||||
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
|
||||
<< "with reply" << reply.toString ().toLocal8Bit ()
|
||||
<< QString ("for rig %1").arg (rig_number).toLocal8Bit ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig number:" << rig_number_ << ':' << state ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_ptt (bool on)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", on << state ());
|
||||
if (use_for_ptt_ && TransceiverFactory::PTT_method_CAT == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT");
|
||||
rig_->SetTx (on ? OmniRig::PM_TX : OmniRig::PM_RX);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (port_)
|
||||
{
|
||||
if (TransceiverFactory::PTT_method_RTS == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set RTS");
|
||||
port_->SetRts (on);
|
||||
}
|
||||
else // "DTR"
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set DTR");
|
||||
port_->SetDtr (on);
|
||||
}
|
||||
}
|
||||
else if (wrapped_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT using basic transceiver");
|
||||
TransceiverState new_state {wrapped_->state ()};
|
||||
new_state.ptt (on);
|
||||
wrapped_->set (new_state, 0);
|
||||
}
|
||||
}
|
||||
update_PTT (on);
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", f << state ());
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_tx_frequency (Frequency tx, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", tx << state ());
|
||||
bool split {tx != 0};
|
||||
if (split)
|
||||
{
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ())
|
||||
{
|
||||
if (writable_params_ & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
// nothing to do here because OmniRig will use VFO
|
||||
// equalize to set the mode of the Tx VFO for us
|
||||
}
|
||||
else if ((writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOB);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
else if (writable_params_ & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode on");
|
||||
rig_->SetSplitMode (state ().frequency (), tx);
|
||||
update_other_frequency (tx);
|
||||
update_split (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode off");
|
||||
rig_->SetSimplexMode (state ().frequency ());
|
||||
update_split (false);
|
||||
}
|
||||
bool notify {false};
|
||||
if (readable_params_ & OmniRig::PM_FREQ || !(readable_params_ & (OmniRig::PM_FREQA | OmniRig::PM_FREQB)))
|
||||
{
|
||||
update_other_frequency (tx); // async updates won't return this
|
||||
// so just store it and hope
|
||||
// operator doesn't change the
|
||||
// "back" VFO on rig
|
||||
notify = true;
|
||||
}
|
||||
if (!((OmniRig::PM_VFOAB | OmniRig::PM_VFOBA | OmniRig::PM_SPLITON) & readable_params_))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "setting SPLIT manually");
|
||||
update_split (split); // we can't read it so just set and
|
||||
// hope op doesn't change it
|
||||
notify = true;
|
||||
}
|
||||
if (notify)
|
||||
{
|
||||
update_complete ();
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_mode (MODE mode)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", mode << state ());
|
||||
// TODO: G4WJS OmniRig doesn't seem to have any capability of tracking/setting VFO B mode
|
||||
auto mapped = map_mode (mode);
|
||||
if (mapped & writable_params_)
|
||||
{
|
||||
rig_->SetMode (mapped);
|
||||
update_mode (mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
offline ("OmniRig invalid mode");
|
||||
}
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
#ifndef OMNI_RIG_TRANSCEIVER_HPP__
|
||||
#define OMNI_RIG_TRANSCEIVER_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "TransceiverBase.hpp"
|
||||
|
||||
#include "OmniRig.h"
|
||||
|
||||
//
|
||||
// OmniRig Transceiver Interface
|
||||
//
|
||||
// Implemented as a Transceiver decorator because we may want the PTT
|
||||
// services of another Transceiver type such as the HamlibTransceiver
|
||||
// which can be enabled by wrapping a HamlibTransceiver instantiated
|
||||
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
|
||||
//
|
||||
class OmniRigTransceiver final
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id1, int id2);
|
||||
|
||||
enum RigNumber {One = 1, Two};
|
||||
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped, RigNumber, TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port, QObject * parent = nullptr);
|
||||
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
private:
|
||||
bool await_notification_with_timeout (int timeout);
|
||||
Q_SIGNAL void notified () const;
|
||||
// Q_SLOT void timeout_check ();
|
||||
Q_SLOT void handle_COM_exception (int, QString, QString, QString);
|
||||
Q_SLOT void handle_visible_change ();
|
||||
Q_SLOT void handle_rig_type_change (int rig_number);
|
||||
Q_SLOT void handle_status_change (int rig_number);
|
||||
Q_SLOT void handle_params_change (int rig_number, int params);
|
||||
Q_SLOT void handle_custom_reply (int, QVariant const& command, QVariant const& reply);
|
||||
|
||||
static MODE map_mode (OmniRig::RigParamX param);
|
||||
static OmniRig::RigParamX map_mode (MODE mode);
|
||||
|
||||
std::unique_ptr<TransceiverBase> wrapped_; // may be null
|
||||
bool use_for_ptt_;
|
||||
TransceiverFactory::PTTMethod ptt_type_;
|
||||
QScopedPointer<OmniRig::OmniRigX> omni_rig_;
|
||||
RigNumber rig_number_;
|
||||
QScopedPointer<OmniRig::RigX> rig_;
|
||||
QScopedPointer<OmniRig::PortBits> port_;
|
||||
QString rig_type_;
|
||||
int readable_params_;
|
||||
int writable_params_;
|
||||
QScopedPointer<QTimer> offline_timer_;
|
||||
bool send_update_signal_;
|
||||
bool reversed_; // some rigs can reverse VFOs
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,74 +0,0 @@
|
|||
#ifndef POLLING_TRANSCEIVER_HPP__
|
||||
#define POLLING_TRANSCEIVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "TransceiverBase.hpp"
|
||||
|
||||
class QTimer;
|
||||
|
||||
//
|
||||
// Polling Transceiver
|
||||
//
|
||||
// Helper base class that encapsulates the emulation of continuous
|
||||
// update and caching of a transceiver state.
|
||||
//
|
||||
// Collaborations
|
||||
//
|
||||
// Implements the TransceiverBase post action interface and provides
|
||||
// the abstract poll() operation for sub-classes to implement. The
|
||||
// poll operation is invoked every poll_interval seconds.
|
||||
//
|
||||
// Responsibilities
|
||||
//
|
||||
// Because some rig interfaces don't immediately update after a state
|
||||
// change request; this class allows a rig a few polls to stabilise
|
||||
// to the requested state before signalling the change. This means
|
||||
// that clients don't see intermediate states that are sometimes
|
||||
// inaccurate, e.g. changing the split TX frequency on Icom rigs
|
||||
// requires a VFO switch and polls while switched will return the
|
||||
// wrong current frequency.
|
||||
//
|
||||
class PollingTransceiver
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
|
||||
protected:
|
||||
explicit PollingTransceiver (int poll_interval, // in seconds
|
||||
QObject * parent);
|
||||
|
||||
protected:
|
||||
// Sub-classes implement this and fetch what they can from the rig
|
||||
// in a non-intrusive manner.
|
||||
virtual void do_poll () = 0;
|
||||
|
||||
void do_post_start () override final;
|
||||
void do_post_stop () override final;
|
||||
void do_post_frequency (Frequency, MODE) override final;
|
||||
void do_post_tx_frequency (Frequency, MODE) override final;
|
||||
void do_post_mode (MODE) override final;
|
||||
void do_post_ptt (bool = true) override final;
|
||||
bool do_pre_update () override final;
|
||||
|
||||
private:
|
||||
void start_timer ();
|
||||
void stop_timer ();
|
||||
|
||||
Q_SLOT void handle_timeout ();
|
||||
|
||||
int interval_; // polling interval in milliseconds
|
||||
QTimer * poll_timer_;
|
||||
|
||||
// keep a record of the last state signalled so we can elide
|
||||
// duplicate updates
|
||||
Transceiver::TransceiverState last_signalled_state_;
|
||||
|
||||
// keep a record of expected state so we can compare with actual
|
||||
// updates to determine when state changes have bubbled through
|
||||
Transceiver::TransceiverState next_state_;
|
||||
|
||||
unsigned retries_; // number of incorrect polls left
|
||||
};
|
||||
|
||||
#endif
|
|
@ -10,7 +10,100 @@
|
|||
|
||||
|
||||
|
||||
Copyright 2001 - 2019 by Joe Taylor, K1JT.
|
||||
Copyright 2001 - 2020 by Joe Taylor, K1JT.
|
||||
|
||||
|
||||
Release: WSJT-X 2.2.0-rc1
|
||||
May 10, 2020
|
||||
-------------------------
|
||||
|
||||
WSJT-X 2.2.0-rc1 is a beta-quality release candidate for a program
|
||||
upgrade that provides a number of new features and capabilities. These
|
||||
include:
|
||||
|
||||
- Improvements to the decoders for five modes:
|
||||
|
||||
FT4: Corrected bugs that prevented AP decoding and/or multi-pass
|
||||
decoding in some circumstances. The algorithm for AP
|
||||
decoding has been improved and extended.
|
||||
|
||||
FT8: Decoding is now spread over three intervals. The first
|
||||
starts at t = 11.8 s into an Rx sequence and typically yields
|
||||
around 85% of the possible decodes for the sequence. You
|
||||
therefore see most decodes much earlier than before. A second
|
||||
processing step starts at 13.5 s, and the final one at 14.7 s.
|
||||
Overall decoding yield on crowded bands is improved by 10% or
|
||||
more. (Systems with receive latency greater than 0.2 s will see
|
||||
smaller improvements, but will still see many decodes earlier
|
||||
than before.)
|
||||
|
||||
JT4: Formatting and display of Averaged and Deep Search decodes
|
||||
has been cleaned up and made consistent with other modes.
|
||||
Together with JT65 and QRA64, JT4 remains one of the digital
|
||||
modes widely for EME and other extreme weak-signal work on
|
||||
microwave bands.
|
||||
|
||||
JT65: Many improvements for Averaged and Deep Search decodes and
|
||||
their display to the user. These improvements are particularly
|
||||
important for EME on VHF and UHF bands.
|
||||
|
||||
WSPR: Significant improvements have been made to the WSPR
|
||||
decoder's sensitivity, its ability to cope with many signals in
|
||||
a crowded sub-band, and its rate of undetected false decodes.
|
||||
We now use up to three decoding passes. Passes 1 and 2 use
|
||||
noncoherent demodulation of single symbols and allow for
|
||||
frequency drifts up to ±4 Hz in a transmission. Pass 3 assumes
|
||||
no drift and does coherent block detection of up to three
|
||||
symbols. It also applies bit-by-bit normalization of the
|
||||
single-symbol bit metrics, a technique that has proven helpful
|
||||
for signals corrupted by artifacts of the subtraction of
|
||||
stronger signals and also for LF/MF signals heavily contaminated
|
||||
by lightning transients. With these improvements the number of
|
||||
decodes in a crowded WSPR sub-band typically increases by 10 to
|
||||
15%.
|
||||
|
||||
- New format for "EU VHF Contest" Tx2 and Tx3 messages
|
||||
|
||||
When "EU VHF Contest" is selected, the Tx2 and Tx3 messages
|
||||
(those conveying signal report, serial number, and 6-character
|
||||
locator) now use hashcodes for both callsigns. This change is
|
||||
NOT backward compatible with earlier versions of _WSJT-X_, so
|
||||
all users of EU VHF Contest messages should be sure to upgrade
|
||||
to versiion 2.2.0.
|
||||
|
||||
- Accessibility
|
||||
|
||||
Keyboard shortcuts have been added as an aid to accessibility:
|
||||
Alt+R sets Tx4 message to RR73, Ctrl+R sets it to RRR.
|
||||
|
||||
As an aid for partial color-blindness, the "inverted goal posts"
|
||||
marking Rx frequency on the Wide Graph's frequency scale are now
|
||||
rendered in a darker shade of green.
|
||||
|
||||
- Minor enhancements and bug fixes
|
||||
|
||||
"Save None" now writes no .wav files to disk, even temporarily.
|
||||
|
||||
An explicit entry for "WW Digi Contest" has been added to
|
||||
"Special operating activities" on the "Settings | Advanced" tab.
|
||||
|
||||
Contest mode FT4 now always uses RR73 for the Tx4 message.
|
||||
|
||||
The Status bar now displays the number of decodes found in the
|
||||
most recent Rx sequence.
|
||||
|
||||
Release candidate WSJT-X 2.2.0-rc1 will be available for beta-testing
|
||||
for one month starting on May 10, 2020. We currently plan a General
|
||||
Availability (GA) release of WSJT-X 2.2.0 on June 1, 2020.
|
||||
|
||||
For those looking even farther ahead: We are well along in the
|
||||
development of two new modes designed for the LF and MF bands. One
|
||||
mode is for WSPR-like activity and one for making 2-way QSOs. Both
|
||||
use Low-density Parity Check (LDPC) codes, 4-GFSK modulation, and
|
||||
two-minute T/R sequences. The QSO mode reaches threshold SNR
|
||||
sensitivity around -31 dB on the AWGN channel, and the WSPR-like mode
|
||||
better than -32 dB.
|
||||
|
||||
|
||||
Release: WSJT-X 2.1.1
|
||||
November 25, 2019
|
||||
|
@ -836,5 +929,5 @@ activated in v1.8.0.
|
|||
We haven't yet finalized what the three extra bits in the message
|
||||
payload will be used for. Suggestions are welcome!
|
||||
|
||||
-- Joe, K1JT, for the WSJT Development Team
|
||||
++++++ -- Joe, K1JT, for the WSJT Development Team
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ Directory::Directory (Configuration const * configuration
|
|||
|
||||
connect (network_manager_, &QNetworkAccessManager::authenticationRequired
|
||||
, this, &Directory::authentication);
|
||||
connect (this, &Directory::itemChanged, [this] (QTreeWidgetItem * item) {
|
||||
connect (this, &Directory::itemChanged, [] (QTreeWidgetItem * item) {
|
||||
switch (item->type ())
|
||||
{
|
||||
case FileNode::Type:
|
||||
|
|
|
@ -0,0 +1,506 @@
|
|||
#include "DXLabSuiteCommanderTransceiver.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QRegularExpression>
|
||||
#include <QLocale>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
|
||||
#include "Network/NetworkServerLookup.hpp"
|
||||
|
||||
#include "moc_DXLabSuiteCommanderTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
char const * const commander_transceiver_name {"DX Lab Suite Commander"};
|
||||
|
||||
QString map_mode (Transceiver::MODE mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Transceiver::AM: return "AM";
|
||||
case Transceiver::CW: return "CW";
|
||||
case Transceiver::CW_R: return "CW-R";
|
||||
case Transceiver::USB: return "USB";
|
||||
case Transceiver::LSB: return "LSB";
|
||||
case Transceiver::FSK: return "RTTY";
|
||||
case Transceiver::FSK_R: return "RTTY-R";
|
||||
case Transceiver::DIG_L: return "DATA-L";
|
||||
case Transceiver::DIG_U: return "DATA-U";
|
||||
case Transceiver::FM:
|
||||
case Transceiver::DIG_FM:
|
||||
return "FM";
|
||||
default: break;
|
||||
}
|
||||
return "USB";
|
||||
}
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id)
|
||||
{
|
||||
(*registry)[commander_transceiver_name] = TransceiverFactory::Capabilities {id, TransceiverFactory::Capabilities::network, true};
|
||||
}
|
||||
|
||||
DXLabSuiteCommanderTransceiver::DXLabSuiteCommanderTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
QString const& address, bool use_for_ptt,
|
||||
int poll_interval, QObject * parent)
|
||||
: PollingTransceiver {poll_interval, parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, use_for_ptt_ {use_for_ptt}
|
||||
, server_ {address}
|
||||
, commander_ {nullptr}
|
||||
{
|
||||
}
|
||||
|
||||
int DXLabSuiteCommanderTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "starting");
|
||||
if (wrapped_) wrapped_->start (0);
|
||||
|
||||
auto server_details = network_server_lookup (server_, 52002u, QHostAddress::LocalHost, QAbstractSocket::IPv4Protocol);
|
||||
|
||||
if (!commander_)
|
||||
{
|
||||
commander_ = new QTcpSocket {this}; // QObject takes ownership
|
||||
}
|
||||
|
||||
commander_->connectToHost (std::get<0> (server_details), std::get<1> (server_details));
|
||||
if (!commander_->waitForConnected ())
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to connect" << commander_->errorString ());
|
||||
throw error {tr ("Failed to connect to DX Lab Suite Commander\n") + commander_->errorString ()};
|
||||
}
|
||||
|
||||
// sleeps here are to ensure Commander has actually queried the rig
|
||||
// rather than returning cached data which maybe stale or simply
|
||||
// read backs of not yet committed values, the 2s delays are
|
||||
// arbitrary but hopefully enough as the actual time required is rig
|
||||
// and Commander setting dependent
|
||||
int resolution {0};
|
||||
QThread::msleep (2000);
|
||||
auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f && !(f % 10))
|
||||
{
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
auto f_string = frequency_to_string (test_frequency);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
QThread::msleep (2000);
|
||||
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
switch (static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
|
||||
{
|
||||
case -5: resolution = -1; break; // 10Hz truncated
|
||||
case 5: resolution = 1; break; // 10Hz rounded
|
||||
case -15: resolution = -2; break; // 20Hz truncated
|
||||
case -55: resolution = -2; break; // 100Hz truncated
|
||||
case 45: resolution = 2; break; // 100Hz rounded
|
||||
}
|
||||
if (1 == resolution) // may be 20Hz rounded
|
||||
{
|
||||
test_frequency = f - f % 100 + 51;
|
||||
f_string = frequency_to_string (test_frequency);
|
||||
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
QThread::msleep (2000);
|
||||
reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>");
|
||||
new_frequency = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (9 == static_cast<Radio::FrequencyDelta> (new_frequency - test_frequency))
|
||||
{
|
||||
resolution = 2; // 20Hz rounded
|
||||
}
|
||||
}
|
||||
f_string = frequency_to_string (f);
|
||||
params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly reading frequency: ") + reply};
|
||||
}
|
||||
|
||||
do_poll ();
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_stop ()
|
||||
{
|
||||
if (commander_)
|
||||
{
|
||||
commander_->close ();
|
||||
delete commander_, commander_ = nullptr;
|
||||
}
|
||||
|
||||
if (wrapped_) wrapped_->stop ();
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_ptt (bool on)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", on << state ());
|
||||
if (use_for_ptt_)
|
||||
{
|
||||
simple_command (on ? "<command:5>CmdTX<parameters:0>" : "<command:5>CmdRX<parameters:0>");
|
||||
|
||||
bool tx {!on};
|
||||
auto start = QDateTime::currentMSecsSinceEpoch ();
|
||||
// we must now wait for Tx on the rig, we will wait a short while
|
||||
// before bailing out
|
||||
while (tx != on && QDateTime::currentMSecsSinceEpoch () - start < 1000)
|
||||
{
|
||||
auto reply = command_with_reply ("<command:9>CmdSendTx<parameters:0>");
|
||||
if (0 == reply.indexOf ("<CmdTX:"))
|
||||
{
|
||||
auto state = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("ON" == state)
|
||||
{
|
||||
tx = true;
|
||||
}
|
||||
else if ("OFF" == state)
|
||||
{
|
||||
tx = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "unexpected TX state" << state);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised TX state: ") + state};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "get TX unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX status: ") + reply};
|
||||
}
|
||||
if (tx != on) QThread::msleep (10); // don't thrash Commander
|
||||
}
|
||||
update_PTT (tx);
|
||||
if (tx != on)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "rig failed to respond to PTT: " << on);
|
||||
throw error {tr ("DX Lab Suite Commander rig did not respond to PTT: ") + (on ? "ON" : "OFF")};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_ASSERT (wrapped_);
|
||||
TransceiverState new_state {wrapped_->state ()};
|
||||
new_state.ptt (on);
|
||||
wrapped_->set (new_state, 0);
|
||||
update_PTT (on);
|
||||
}
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", f << state ());
|
||||
auto f_string = frequency_to_string (f);
|
||||
if (UNK != m && m != get_mode ())
|
||||
{
|
||||
auto m_string = map_mode (m);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string + "<xcvrmode:%2>" + m_string + "<preservesplitanddual:1>Y").arg (f_string.size ()).arg (m_string.size ());
|
||||
simple_command (("<command:14>CmdSetFreqMode<parameters:%1>" + params).arg (params.size ()));
|
||||
update_mode (m);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto params = ("<xcvrfreq:%1>" + f_string).arg (f_string.size ());
|
||||
simple_command (("<command:10>CmdSetFreq<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_tx_frequency (Frequency tx, MODE mode, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", tx << state ());
|
||||
if (tx)
|
||||
{
|
||||
auto f_string = frequency_to_string (tx);
|
||||
auto params = ("<xcvrfreq:%1>" + f_string + "<SuppressDual:1>Y").arg (f_string.size ());
|
||||
if (UNK == mode)
|
||||
{
|
||||
params += "<SuppressModeChange:1>Y";
|
||||
}
|
||||
simple_command (("<command:11>CmdQSXSplit<parameters:%1>" + params).arg (params.size ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
simple_command ("<command:8>CmdSplit<parameters:8><1:3>off");
|
||||
}
|
||||
update_split (tx);
|
||||
update_other_frequency (tx);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_mode (MODE m)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", m << state ());
|
||||
auto m_string = map_mode (m);
|
||||
auto params = ("<1:%1>" + m_string).arg (m_string.size ());
|
||||
simple_command (("<command:10>CmdSetMode<parameters:%1>" + params).arg (params.size ()));
|
||||
update_mode (m);
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::do_poll ()
|
||||
{
|
||||
#if WSJT_TRACE_CAT && WSJT_TRACE_CAT_POLLS
|
||||
bool quiet {false};
|
||||
#else
|
||||
bool quiet {true};
|
||||
#endif
|
||||
|
||||
auto reply = command_with_reply ("<command:10>CmdGetFreq<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f)
|
||||
{
|
||||
if (!state ().ptt ()) // Commander is not reliable on frequency
|
||||
// polls while transmitting
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling frequency: ") + reply};
|
||||
}
|
||||
|
||||
if (state ().split ())
|
||||
{
|
||||
reply = command_with_reply ("<command:12>CmdGetTXFreq<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdTXFreq:"))
|
||||
{
|
||||
auto f = string_to_frequency (reply.mid (reply.indexOf ('>') + 1));
|
||||
if (f)
|
||||
{
|
||||
if (!state ().ptt ()) // Commander is not reliable on frequency
|
||||
// polls while transmitting
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get tx frequency unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling TX frequency: ") + reply};
|
||||
}
|
||||
}
|
||||
|
||||
reply = command_with_reply ("<command:12>CmdSendSplit<parameters:0>", quiet);
|
||||
if (0 == reply.indexOf ("<CmdSplit:"))
|
||||
{
|
||||
auto split = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("ON" == split)
|
||||
{
|
||||
update_split (true);
|
||||
}
|
||||
else if ("OFF" == split)
|
||||
{
|
||||
update_split (false);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected split state" << split);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised split state: ") + split};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "get split mode unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling split status: ") + reply};
|
||||
}
|
||||
|
||||
get_mode (quiet);
|
||||
}
|
||||
|
||||
auto DXLabSuiteCommanderTransceiver::get_mode (bool no_debug) -> MODE
|
||||
{
|
||||
MODE m {UNK};
|
||||
auto reply = command_with_reply ("<command:11>CmdSendMode<parameters:0>", no_debug);
|
||||
if (0 == reply.indexOf ("<CmdMode:"))
|
||||
{
|
||||
auto mode = reply.mid (reply.indexOf ('>') + 1);
|
||||
if ("AM" == mode)
|
||||
{
|
||||
m = AM;
|
||||
}
|
||||
else if ("CW" == mode)
|
||||
{
|
||||
m = CW;
|
||||
}
|
||||
else if ("CW-R" == mode)
|
||||
{
|
||||
m = CW_R;
|
||||
}
|
||||
else if ("FM" == mode || "WBFM" == mode)
|
||||
{
|
||||
m = FM;
|
||||
}
|
||||
else if ("LSB" == mode)
|
||||
{
|
||||
m = LSB;
|
||||
}
|
||||
else if ("USB" == mode)
|
||||
{
|
||||
m = USB;
|
||||
}
|
||||
else if ("RTTY" == mode)
|
||||
{
|
||||
m = FSK;
|
||||
}
|
||||
else if ("RTTY-R" == mode)
|
||||
{
|
||||
m = FSK_R;
|
||||
}
|
||||
else if ("PKT" == mode || "DATA-L" == mode || "Data-L" == mode || "DIGL" == mode)
|
||||
{
|
||||
m = DIG_L;
|
||||
}
|
||||
else if ("PKT-R" == mode || "DATA-U" == mode || "Data-U" == mode || "DIGU" == mode)
|
||||
{
|
||||
m = DIG_U;
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected mode name" << mode);
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognised mode: \"") + mode + '"'};
|
||||
}
|
||||
update_mode (m);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT_POLL ("DXLabSuiteCommanderTransceiver", "unexpected response" << reply);
|
||||
throw error {tr ("DX Lab Suite Commander didn't respond correctly polling mode: ") + reply};
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
void DXLabSuiteCommanderTransceiver::simple_command (QString const& cmd, bool no_debug)
|
||||
{
|
||||
Q_ASSERT (commander_);
|
||||
|
||||
if (!no_debug)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd);
|
||||
}
|
||||
|
||||
if (!write_to_port (cmd))
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed:" << commander_->errorString ());
|
||||
throw error {tr ("DX Lab Suite Commander send command failed\n") + commander_->errorString ()};
|
||||
}
|
||||
}
|
||||
|
||||
QString DXLabSuiteCommanderTransceiver::command_with_reply (QString const& cmd, bool no_debug)
|
||||
{
|
||||
Q_ASSERT (commander_);
|
||||
|
||||
if (!write_to_port (cmd))
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to send command:" << commander_->errorString ());
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander failed to send command \"%1\": %2\n")
|
||||
.arg (cmd)
|
||||
.arg (commander_->errorString ())
|
||||
};
|
||||
}
|
||||
|
||||
// waitForReadReady appears to be unreliable on Windows timing out
|
||||
// when data is waiting so retry a few times
|
||||
unsigned retries {5};
|
||||
bool replied {false};
|
||||
while (!replied && --retries)
|
||||
{
|
||||
replied = commander_->waitForReadyRead ();
|
||||
if (!replied && commander_->error () != commander_->SocketTimeoutError)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "failed to read reply:" << commander_->errorString ());
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander send command \"%1\" read reply failed: %2\n")
|
||||
.arg (cmd)
|
||||
.arg (commander_->errorString ())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!replied)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "retries exhausted");
|
||||
throw error {
|
||||
tr ("DX Lab Suite Commander retries exhausted sending command \"%1\"")
|
||||
.arg (cmd)
|
||||
};
|
||||
}
|
||||
|
||||
auto result = commander_->readAll ();
|
||||
// qDebug () << "result: " << result;
|
||||
// for (int i = 0; i < result.size (); ++i)
|
||||
// {
|
||||
// qDebug () << i << ":" << hex << int (result[i]);
|
||||
// }
|
||||
|
||||
if (!no_debug)
|
||||
{
|
||||
TRACE_CAT ("DXLabSuiteCommanderTransceiver", cmd << "->" << result);
|
||||
}
|
||||
|
||||
return result; // converting raw UTF-8 bytes to QString
|
||||
}
|
||||
|
||||
bool DXLabSuiteCommanderTransceiver::write_to_port (QString const& s)
|
||||
{
|
||||
auto data = s.toLocal8Bit ();
|
||||
auto to_send = data.constData ();
|
||||
auto length = data.size ();
|
||||
|
||||
qint64 total_bytes_sent {0};
|
||||
while (total_bytes_sent < length)
|
||||
{
|
||||
auto bytes_sent = commander_->write (to_send + total_bytes_sent, length - total_bytes_sent);
|
||||
if (bytes_sent < 0 || !commander_->waitForBytesWritten ())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
total_bytes_sent += bytes_sent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DXLabSuiteCommanderTransceiver::frequency_to_string (Frequency f) const
|
||||
{
|
||||
// number is localized and in kHz, avoid floating point translation
|
||||
// errors by adding a small number (0.1Hz)
|
||||
return QString {"%L1"}.arg (f / 1e3 + 1e-4, 10, 'f', 3);
|
||||
}
|
||||
|
||||
auto DXLabSuiteCommanderTransceiver::string_to_frequency (QString s) const -> Frequency
|
||||
{
|
||||
// temporary hack because Commander is returning invalid UTF-8 bytes
|
||||
s.replace (QChar {QChar::ReplacementCharacter}, locale_.groupSeparator ());
|
||||
|
||||
// remove DP - relies on n.nnn kHz format so we can do ulonglong
|
||||
// conversion to Hz
|
||||
bool ok;
|
||||
|
||||
// auto f = locale_.toDouble (s, &ok); // use when CmdSendFreq and
|
||||
// CmdSendTxFreq reinstated
|
||||
|
||||
auto f = QLocale::c ().toDouble (s, &ok); // temporary fix
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
throw error {tr ("DX Lab Suite Commander sent an unrecognized frequency")};
|
||||
}
|
||||
return (f + 1e-4) * 1e3;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,69 @@
|
|||
#ifndef HAMLIB_TRANSCEIVER_HPP_
|
||||
#define HAMLIB_TRANSCEIVER_HPP_
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <hamlib/rig.h>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "PollingTransceiver.hpp"
|
||||
|
||||
// hamlib transceiver and PTT mostly delegated directly to hamlib Rig class
|
||||
class HamlibTransceiver final
|
||||
: public PollingTransceiver
|
||||
{
|
||||
Q_OBJECT // for translation context
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *);
|
||||
static void unregister_transceivers ();
|
||||
|
||||
explicit HamlibTransceiver (int model_number, TransceiverFactory::ParameterPack const&,
|
||||
QObject * parent = nullptr);
|
||||
explicit HamlibTransceiver (TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port,
|
||||
QObject * parent = nullptr);
|
||||
|
||||
private:
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool) override;
|
||||
|
||||
void do_poll () override;
|
||||
|
||||
void error_check (int ret_code, QString const& doing) const;
|
||||
void set_conf (char const * item, char const * value);
|
||||
QByteArray get_conf (char const * item);
|
||||
Transceiver::MODE map_mode (rmode_t) const;
|
||||
rmode_t map_mode (Transceiver::MODE mode) const;
|
||||
std::tuple<vfo_t, vfo_t> get_vfos (bool for_split) const;
|
||||
|
||||
struct RIGDeleter {static void cleanup (RIG *);};
|
||||
QScopedPointer<RIG, RIGDeleter> rig_;
|
||||
|
||||
bool back_ptt_port_;
|
||||
bool one_VFO_;
|
||||
bool is_dummy_;
|
||||
|
||||
// these are saved on destruction so we can start new instances
|
||||
// where the last one left off
|
||||
static freq_t dummy_frequency_;
|
||||
static rmode_t dummy_mode_;
|
||||
|
||||
bool mutable reversed_;
|
||||
|
||||
bool freq_query_works_;
|
||||
bool mode_query_works_;
|
||||
bool split_query_works_;
|
||||
bool tickle_hamlib_; // Hamlib requires a
|
||||
// rig_set_split_vfo() call to
|
||||
// establish the Tx VFO
|
||||
bool get_vfo_works_; // Net rigctl promises what it can't deliver
|
||||
bool set_vfo_works_; // More rigctl promises which it can't deliver
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,809 @@
|
|||
#include "OmniRigTransceiver.hpp"
|
||||
|
||||
#include <QDebug>
|
||||
#include <objbase.h>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "moc_OmniRigTransceiver.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto constexpr OmniRig_transceiver_one_name = "OmniRig Rig 1";
|
||||
auto constexpr OmniRig_transceiver_two_name = "OmniRig Rig 2";
|
||||
}
|
||||
|
||||
auto OmniRigTransceiver::map_mode (OmniRig::RigParamX param) -> MODE
|
||||
{
|
||||
if (param & OmniRig::PM_CW_U)
|
||||
{
|
||||
return CW_R;
|
||||
}
|
||||
else if (param & OmniRig::PM_CW_L)
|
||||
{
|
||||
return CW;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_U)
|
||||
{
|
||||
return USB;
|
||||
}
|
||||
else if (param & OmniRig::PM_SSB_L)
|
||||
{
|
||||
return LSB;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_U)
|
||||
{
|
||||
return DIG_U;
|
||||
}
|
||||
else if (param & OmniRig::PM_DIG_L)
|
||||
{
|
||||
return DIG_L;
|
||||
}
|
||||
else if (param & OmniRig::PM_AM)
|
||||
{
|
||||
return AM;
|
||||
}
|
||||
else if (param & OmniRig::PM_FM)
|
||||
{
|
||||
return FM;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "unrecognized mode");
|
||||
throw_qstring (tr ("OmniRig: unrecognized mode"));
|
||||
return UNK;
|
||||
}
|
||||
|
||||
OmniRig::RigParamX OmniRigTransceiver::map_mode (MODE mode)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case AM: return OmniRig::PM_AM;
|
||||
case CW: return OmniRig::PM_CW_L;
|
||||
case CW_R: return OmniRig::PM_CW_U;
|
||||
case USB: return OmniRig::PM_SSB_U;
|
||||
case LSB: return OmniRig::PM_SSB_L;
|
||||
case FSK: return OmniRig::PM_DIG_L;
|
||||
case FSK_R: return OmniRig::PM_DIG_U;
|
||||
case DIG_L: return OmniRig::PM_DIG_L;
|
||||
case DIG_U: return OmniRig::PM_DIG_U;
|
||||
case FM: return OmniRig::PM_FM;
|
||||
case DIG_FM: return OmniRig::PM_FM;
|
||||
default: break;
|
||||
}
|
||||
return OmniRig::PM_SSB_U; // quieten compiler grumble
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::register_transceivers (TransceiverFactory::Transceivers * registry, int id1, int id2)
|
||||
{
|
||||
(*registry)[OmniRig_transceiver_one_name] = TransceiverFactory::Capabilities {
|
||||
id1
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
(*registry)[OmniRig_transceiver_two_name] = TransceiverFactory::Capabilities {
|
||||
id2
|
||||
, TransceiverFactory::Capabilities::none // COM isn't serial or network
|
||||
, true // does PTT
|
||||
, false // doesn't select mic/data (use OmniRig config file)
|
||||
, true // can remote control RTS nd DTR
|
||||
, true // asynchronous interface
|
||||
};
|
||||
}
|
||||
|
||||
OmniRigTransceiver::OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped,
|
||||
RigNumber n, TransceiverFactory::PTTMethod ptt_type,
|
||||
QString const& ptt_port, QObject * parent)
|
||||
: TransceiverBase {parent}
|
||||
, wrapped_ {std::move (wrapped)}
|
||||
, use_for_ptt_ {TransceiverFactory::PTT_method_CAT == ptt_type || ("CAT" == ptt_port && (TransceiverFactory::PTT_method_RTS == ptt_type || TransceiverFactory::PTT_method_DTR == ptt_type))}
|
||||
, ptt_type_ {ptt_type}
|
||||
, rig_number_ {n}
|
||||
, readable_params_ {0}
|
||||
, writable_params_ {0}
|
||||
, send_update_signal_ {false}
|
||||
, reversed_ {false}
|
||||
{
|
||||
CoInitializeEx (nullptr, 0 /*COINIT_APARTMENTTHREADED*/); // required because Qt only does this for GUI thread
|
||||
}
|
||||
|
||||
OmniRigTransceiver::~OmniRigTransceiver ()
|
||||
{
|
||||
CoUninitialize ();
|
||||
}
|
||||
|
||||
// returns false on time out
|
||||
bool OmniRigTransceiver::await_notification_with_timeout (int timeout)
|
||||
{
|
||||
QEventLoop el;
|
||||
connect (this, &OmniRigTransceiver::notified, &el, [&el] () {el.exit (1);});
|
||||
QTimer::singleShot (timeout, Qt::CoarseTimer, &el, [&el] () {el.exit (0);});
|
||||
return 1 == el.exec (); // wait for notify or timer
|
||||
}
|
||||
|
||||
int OmniRigTransceiver::do_start ()
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "starting");
|
||||
|
||||
if (wrapped_) wrapped_->start (0);
|
||||
|
||||
omni_rig_.reset (new OmniRig::OmniRigX {this});
|
||||
if (omni_rig_->isNull ())
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "failed to start COM server");
|
||||
throw_qstring (tr ("Failed to start OmniRig COM server"));
|
||||
}
|
||||
|
||||
// COM/OLE exceptions get signaled
|
||||
connect (&*omni_rig_, SIGNAL (exception (int, QString, QString, QString)), this, SLOT (handle_COM_exception (int, QString, QString, QString)));
|
||||
|
||||
// IOmniRigXEvent interface signals
|
||||
connect (&*omni_rig_, SIGNAL (VisibleChange ()), this, SLOT (handle_visible_change ()));
|
||||
connect (&*omni_rig_, SIGNAL (RigTypeChange (int)), this, SLOT (handle_rig_type_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (StatusChange (int)), this, SLOT (handle_status_change (int)));
|
||||
connect (&*omni_rig_, SIGNAL (ParamsChange (int, int)), this, SLOT (handle_params_change (int, int)));
|
||||
connect (&*omni_rig_
|
||||
, SIGNAL (CustomReply (int, QVariant const&, QVariant const&))
|
||||
, this, SLOT (handle_custom_reply (int, QVariant const&, QVariant const&)));
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig s/w version:" << QString::number (omni_rig_->SoftwareVersion ()).toLocal8Bit ()
|
||||
<< "i/f version:" << QString::number (omni_rig_->InterfaceVersion ()).toLocal8Bit ());
|
||||
|
||||
// fetch the interface of the RigX CoClass and instantiate a proxy object
|
||||
switch (rig_number_)
|
||||
{
|
||||
case One: rig_.reset (new OmniRig::RigX (omni_rig_->Rig1 ())); break;
|
||||
case Two: rig_.reset (new OmniRig::RigX (omni_rig_->Rig2 ())); break;
|
||||
}
|
||||
|
||||
Q_ASSERT (rig_);
|
||||
Q_ASSERT (!rig_->isNull ());
|
||||
|
||||
offline_timer_.reset (new QTimer); // instantiate here as
|
||||
// constructor runs in wrong
|
||||
// thread
|
||||
offline_timer_->setSingleShot (true);
|
||||
connect (offline_timer_.data (), &QTimer::timeout, [this] () {offline ("Rig went offline");});
|
||||
|
||||
if (use_for_ptt_ && (TransceiverFactory::PTT_method_DTR == ptt_type_ || TransceiverFactory::PTT_method_RTS == ptt_type_))
|
||||
{
|
||||
// fetch the interface for the serial port if we need it for PTT
|
||||
port_.reset (new OmniRig::PortBits (rig_->PortBits ()));
|
||||
|
||||
Q_ASSERT (port_);
|
||||
Q_ASSERT (!port_->isNull ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig RTS state:" << port_->Rts ());
|
||||
|
||||
if (!port_->Lock ()) // try to take exclusive use of the OmniRig serial port for PTT
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "Failed to get exclusive use of serial port for PTT from OmniRig");
|
||||
}
|
||||
|
||||
// start off so we don't accidentally key the radio
|
||||
if (TransceiverFactory::PTT_method_DTR == ptt_type_)
|
||||
{
|
||||
port_->SetDtr (false);
|
||||
}
|
||||
else // RTS
|
||||
{
|
||||
port_->SetRts (false);
|
||||
}
|
||||
}
|
||||
|
||||
rig_type_ = rig_->RigType ();
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"OmniRig initial rig type: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_type_)
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number_).toLocal8Bit ());
|
||||
for (int i = 0; i < 5; ++i)
|
||||
{
|
||||
if (OmniRig::ST_ONLINE == rig_->Status ())
|
||||
{
|
||||
break;
|
||||
}
|
||||
await_notification_with_timeout (1000);
|
||||
}
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
throw_qstring ("OmniRig: " + rig_->StatusStr ());
|
||||
}
|
||||
QThread::msleep (500); // leave some time for Omni-Rig to get
|
||||
// the rig status for the 1st. time
|
||||
auto f = rig_->GetRxFrequency ();
|
||||
for (int i = 0; (f == 0) && (i < 5); ++i)
|
||||
{
|
||||
await_notification_with_timeout (1000);
|
||||
f = rig_->GetRxFrequency ();
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
int resolution {0};
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ()
|
||||
&& (writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
// start with VFO A (probably MAIN) on rigs that we
|
||||
// can't query VFO but can set explicitly
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
f = state ().frequency ();
|
||||
if (f % 10) return resolution; // 1Hz resolution
|
||||
auto test_frequency = f - f % 100 + 55;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
if (!await_notification_with_timeout (1000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 1: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
switch (rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
case -5: resolution = -1; break; // 10Hz truncated
|
||||
case 5: resolution = 1; break; // 10Hz rounded
|
||||
case -15: resolution = -2; break; // 20Hz truncated
|
||||
case -55: resolution = -2; break; // 100Hz truncated
|
||||
case 45: resolution = 2; break; // 100Hz rounded
|
||||
}
|
||||
if (1 == resolution) // may be 20Hz rounded
|
||||
{
|
||||
test_frequency = f - f % 100 + 51;
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (test_frequency);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (test_frequency);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (test_frequency);
|
||||
}
|
||||
if (!await_notification_with_timeout (2000))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "do_start 2: wait timed out");
|
||||
throw_qstring (tr ("OmniRig: timeout waiting for update from rig"));
|
||||
}
|
||||
if (9 == rig_->GetRxFrequency () - test_frequency)
|
||||
{
|
||||
resolution = 2; // 20Hz rounded
|
||||
}
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
}
|
||||
update_rx_frequency (f);
|
||||
return resolution;
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_stop ()
|
||||
{
|
||||
QThread::msleep (200); // leave some time for pending
|
||||
// commands at the server end
|
||||
|
||||
offline_timer_.reset (); // destroy here rather than in
|
||||
// destructor as destructor runs in
|
||||
// wrong thread
|
||||
|
||||
if (port_)
|
||||
{
|
||||
port_->Unlock (); // release serial port
|
||||
port_->clear ();
|
||||
port_.reset ();
|
||||
}
|
||||
if (omni_rig_ && !omni_rig_->isNull ())
|
||||
{
|
||||
if (rig_ && !rig_->isNull ())
|
||||
{
|
||||
rig_->clear ();
|
||||
rig_.reset ();
|
||||
}
|
||||
omni_rig_->clear ();
|
||||
omni_rig_.reset ();
|
||||
}
|
||||
|
||||
if (wrapped_) wrapped_->stop ();
|
||||
|
||||
TRACE_CAT ("OmniRigTransceiver", "stopped");
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_COM_exception (int code, QString source, QString desc, QString help)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString::number (code) + " at " + source + ": " + desc + " (" + help + ')');
|
||||
throw_qstring (tr ("OmniRig COM/OLE error: %1 at %2: %3 (%4)").arg (QString::number (code)).arg (source). arg (desc). arg (help));
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_visible_change ()
|
||||
{
|
||||
if (!omni_rig_ || omni_rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "visibility change: visibility =" << omni_rig_->DialogVisible ());
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_rig_type_change (int rig_number)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig type change: rig =" << rig_number);
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
readable_params_ = rig_->ReadableParams ();
|
||||
writable_params_ = rig_->WriteableParams ();
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"rig type change to: %1 readable params = 0x%2 writable params = 0x%3 for rig %4"}
|
||||
.arg (rig_->RigType ())
|
||||
.arg (readable_params_, 8, 16, QChar ('0'))
|
||||
.arg (writable_params_, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_status_change (int rig_number)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"status change for rig %1"}.arg (rig_number).toLocal8Bit ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
auto const& status = rig_->StatusStr ().toLocal8Bit ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig status change: new status = " << status);
|
||||
if (OmniRig::ST_ONLINE != rig_->Status ())
|
||||
{
|
||||
if (!offline_timer_->isActive ())
|
||||
{
|
||||
// Omni-Rig is prone to reporting the rig offline and
|
||||
// then recovering autonomously, so we will give it a
|
||||
// few seconds to make its mind up
|
||||
offline_timer_->start (10000);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
offline_timer_->stop (); // good to go again
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// update_rx_frequency (rig_->GetRxFrequency ());
|
||||
// update_complete ();
|
||||
// TRACE_CAT ("OmniRigTransceiver", "frequency:" << state ().frequency ());
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_params_change (int rig_number, int params)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"params change: params = 0x%1 for rig %2"}
|
||||
.arg (params, 8, 16, QChar ('0'))
|
||||
.arg (rig_number).toLocal8Bit ()
|
||||
<< "state before:" << state ());
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
// starting_ = false;
|
||||
TransceiverState old_state {state ()};
|
||||
auto need_frequency = false;
|
||||
|
||||
if (params & OmniRig::PM_VFOAA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAA");
|
||||
update_split (false);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOAB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOAB");
|
||||
update_split (true);
|
||||
reversed_ = false;
|
||||
update_rx_frequency (rig_->FreqA ());
|
||||
update_other_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBA");
|
||||
update_split (true);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOBB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOBB");
|
||||
update_split (false);
|
||||
reversed_ = true;
|
||||
update_other_frequency (rig_->FreqA ());
|
||||
update_rx_frequency (rig_->FreqB ());
|
||||
}
|
||||
if (params & OmniRig::PM_VFOA)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOA");
|
||||
reversed_ = false;
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_VFOB)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOB");
|
||||
reversed_ = true;
|
||||
need_frequency = true;
|
||||
}
|
||||
|
||||
if (params & OmniRig::PM_FREQ)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ");
|
||||
need_frequency = true;
|
||||
}
|
||||
if (params & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
if (need_frequency)
|
||||
{
|
||||
if (readable_params_ & OmniRig::PM_FREQA)
|
||||
{
|
||||
auto f = rig_->FreqA ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQA = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQB)
|
||||
{
|
||||
auto f = rig_->FreqB ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQB = " << f);
|
||||
if (reversed_)
|
||||
{
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
update_other_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (readable_params_ & OmniRig::PM_FREQ && !state ().ptt ())
|
||||
{
|
||||
auto f = rig_->Freq ();
|
||||
if (f)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FREQ = " << f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params & OmniRig::PM_PITCH)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "PITCH");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFFSET)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFFSET");
|
||||
}
|
||||
if (params & OmniRig::PM_RIT0)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RIT0");
|
||||
}
|
||||
if (params & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
auto f = readable_params_ & OmniRig::PM_FREQA ? rig_->FreqA () : rig_->Freq ();
|
||||
auto m = map_mode (rig_->Mode ());
|
||||
TRACE_CAT ("OmniRigTransceiver", QString {"VFOEQUAL f=%1 m=%2"}.arg (f).arg (m));
|
||||
update_rx_frequency (f);
|
||||
update_other_frequency (f);
|
||||
update_mode (m);
|
||||
}
|
||||
if (params & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "VFOSWAP");
|
||||
auto f = state ().tx_frequency ();
|
||||
update_other_frequency (state ().frequency ());
|
||||
update_rx_frequency (f);
|
||||
update_mode (map_mode (rig_->Mode ()));
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITON");
|
||||
update_split (true);
|
||||
}
|
||||
if (params & OmniRig::PM_SPLITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "SPLITOFF");
|
||||
update_split (false);
|
||||
}
|
||||
if (params & OmniRig::PM_RITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITON");
|
||||
}
|
||||
if (params & OmniRig::PM_RITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_XITON)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITON");
|
||||
}
|
||||
if (params & OmniRig::PM_XITOFF)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "XITOFF");
|
||||
}
|
||||
if (params & OmniRig::PM_RX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "RX");
|
||||
update_PTT (false);
|
||||
}
|
||||
if (params & OmniRig::PM_TX)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "TX");
|
||||
update_PTT ();
|
||||
}
|
||||
if (params & OmniRig::PM_CW_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW-R");
|
||||
update_mode (CW_R);
|
||||
}
|
||||
if (params & OmniRig::PM_CW_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "CW");
|
||||
update_mode (CW);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "USB");
|
||||
update_mode (USB);
|
||||
}
|
||||
if (params & OmniRig::PM_SSB_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "LSB");
|
||||
update_mode (LSB);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_U)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-U");
|
||||
update_mode (DIG_U);
|
||||
}
|
||||
if (params & OmniRig::PM_DIG_L)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "DATA-L");
|
||||
update_mode (DIG_L);
|
||||
}
|
||||
if (params & OmniRig::PM_AM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "AM");
|
||||
update_mode (AM);
|
||||
}
|
||||
if (params & OmniRig::PM_FM)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "FM");
|
||||
update_mode (FM);
|
||||
}
|
||||
|
||||
if (old_state != state () || send_update_signal_)
|
||||
{
|
||||
update_complete ();
|
||||
send_update_signal_ = false;
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "OmniRig params change: state after:" << state ());
|
||||
}
|
||||
Q_EMIT notified ();
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::handle_custom_reply (int rig_number, QVariant const& command, QVariant const& reply)
|
||||
{
|
||||
(void)command;
|
||||
(void)reply;
|
||||
|
||||
if (rig_number_ == rig_number)
|
||||
{
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
TRACE_CAT ("OmniRigTransceiver", "custom command" << command.toString ().toLocal8Bit ()
|
||||
<< "with reply" << reply.toString ().toLocal8Bit ()
|
||||
<< QString ("for rig %1").arg (rig_number).toLocal8Bit ());
|
||||
TRACE_CAT ("OmniRigTransceiver", "rig number:" << rig_number_ << ':' << state ());
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_ptt (bool on)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", on << state ());
|
||||
if (use_for_ptt_ && TransceiverFactory::PTT_method_CAT == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT");
|
||||
if (rig_ && !rig_->isNull ())
|
||||
{
|
||||
rig_->SetTx (on ? OmniRig::PM_TX : OmniRig::PM_RX);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (port_ && !port_->isNull ())
|
||||
{
|
||||
if (TransceiverFactory::PTT_method_RTS == ptt_type_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set RTS");
|
||||
port_->SetRts (on);
|
||||
}
|
||||
else // "DTR"
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set DTR");
|
||||
port_->SetDtr (on);
|
||||
}
|
||||
}
|
||||
else if (wrapped_)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set PTT using basic transceiver");
|
||||
TransceiverState new_state {wrapped_->state ()};
|
||||
new_state.ptt (on);
|
||||
wrapped_->set (new_state, 0);
|
||||
}
|
||||
}
|
||||
update_PTT (on);
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_frequency (Frequency f, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", f << state ());
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
}
|
||||
if (OmniRig::PM_FREQ & writable_params_)
|
||||
{
|
||||
rig_->SetFreq (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (reversed_ && (OmniRig::PM_FREQB & writable_params_))
|
||||
{
|
||||
rig_->SetFreqB (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else if (!reversed_ && (OmniRig::PM_FREQA & writable_params_))
|
||||
{
|
||||
rig_->SetFreqA (f);
|
||||
update_rx_frequency (f);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw_qstring (tr ("OmniRig: don't know how to set rig frequency"));
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_tx_frequency (Frequency tx, MODE m, bool /*no_ignore*/)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", tx << state ());
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
bool split {tx != 0};
|
||||
if (split)
|
||||
{
|
||||
if (UNK != m)
|
||||
{
|
||||
do_mode (m);
|
||||
if (OmniRig::PM_UNKNOWN == rig_->Vfo ())
|
||||
{
|
||||
if (writable_params_ & OmniRig::PM_VFOEQUAL)
|
||||
{
|
||||
// nothing to do here because OmniRig will use VFO
|
||||
// equalize to set the mode of the Tx VFO for us
|
||||
}
|
||||
else if ((writable_params_ & (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
== (OmniRig::PM_VFOA | OmniRig::PM_VFOB))
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOB);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOA);
|
||||
}
|
||||
else if (writable_params_ & OmniRig::PM_VFOSWAP)
|
||||
{
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
do_mode (m);
|
||||
rig_->SetVfo (OmniRig::PM_VFOSWAP);
|
||||
}
|
||||
}
|
||||
}
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode on");
|
||||
rig_->SetSplitMode (state ().frequency (), tx);
|
||||
update_other_frequency (tx);
|
||||
update_split (true);
|
||||
}
|
||||
else
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "set SPLIT mode off");
|
||||
rig_->SetSimplexMode (state ().frequency ());
|
||||
update_split (false);
|
||||
}
|
||||
bool notify {false};
|
||||
if (readable_params_ & OmniRig::PM_FREQ || !(readable_params_ & (OmniRig::PM_FREQA | OmniRig::PM_FREQB)))
|
||||
{
|
||||
update_other_frequency (tx); // async updates won't return this
|
||||
// so just store it and hope
|
||||
// operator doesn't change the
|
||||
// "back" VFO on rig
|
||||
notify = true;
|
||||
}
|
||||
if (!((OmniRig::PM_VFOAB | OmniRig::PM_VFOBA | OmniRig::PM_SPLITON) & readable_params_))
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", "setting SPLIT manually");
|
||||
update_split (split); // we can't read it so just set and
|
||||
// hope op doesn't change it
|
||||
notify = true;
|
||||
}
|
||||
if (notify)
|
||||
{
|
||||
update_complete ();
|
||||
}
|
||||
}
|
||||
|
||||
void OmniRigTransceiver::do_mode (MODE mode)
|
||||
{
|
||||
TRACE_CAT ("OmniRigTransceiver", mode << state ());
|
||||
if (!rig_ || rig_->isNull ()) return;
|
||||
// TODO: G4WJS OmniRig doesn't seem to have any capability of tracking/setting VFO B mode
|
||||
auto mapped = map_mode (mode);
|
||||
if (mapped & writable_params_)
|
||||
{
|
||||
rig_->SetMode (mapped);
|
||||
update_mode (mode);
|
||||
}
|
||||
else
|
||||
{
|
||||
offline ("OmniRig invalid mode");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
#ifndef OMNI_RIG_TRANSCEIVER_HPP__
|
||||
#define OMNI_RIG_TRANSCEIVER_HPP__
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "TransceiverFactory.hpp"
|
||||
#include "TransceiverBase.hpp"
|
||||
|
||||
#include "OmniRig.h"
|
||||
|
||||
//
|
||||
// OmniRig Transceiver Interface
|
||||
//
|
||||
// Implemented as a Transceiver decorator because we may want the PTT
|
||||
// services of another Transceiver type such as the HamlibTransceiver
|
||||
// which can be enabled by wrapping a HamlibTransceiver instantiated
|
||||
// as a "Hamlib Dummy" transceiver in the Transceiver factory method.
|
||||
//
|
||||
class OmniRigTransceiver final
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
static void register_transceivers (TransceiverFactory::Transceivers *, int id1, int id2);
|
||||
|
||||
enum RigNumber {One = 1, Two};
|
||||
|
||||
// takes ownership of wrapped Transceiver
|
||||
explicit OmniRigTransceiver (std::unique_ptr<TransceiverBase> wrapped, RigNumber, TransceiverFactory::PTTMethod ptt_type, QString const& ptt_port, QObject * parent = nullptr);
|
||||
~OmniRigTransceiver ();
|
||||
|
||||
int do_start () override;
|
||||
void do_stop () override;
|
||||
void do_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_tx_frequency (Frequency, MODE, bool no_ignore) override;
|
||||
void do_mode (MODE) override;
|
||||
void do_ptt (bool on) override;
|
||||
|
||||
private:
|
||||
bool await_notification_with_timeout (int timeout);
|
||||
Q_SIGNAL void notified () const;
|
||||
// Q_SLOT void timeout_check ();
|
||||
Q_SLOT void handle_COM_exception (int, QString, QString, QString);
|
||||
Q_SLOT void handle_visible_change ();
|
||||
Q_SLOT void handle_rig_type_change (int rig_number);
|
||||
Q_SLOT void handle_status_change (int rig_number);
|
||||
Q_SLOT void handle_params_change (int rig_number, int params);
|
||||
Q_SLOT void handle_custom_reply (int, QVariant const& command, QVariant const& reply);
|
||||
|
||||
static MODE map_mode (OmniRig::RigParamX param);
|
||||
static OmniRig::RigParamX map_mode (MODE mode);
|
||||
|
||||
std::unique_ptr<TransceiverBase> wrapped_; // may be null
|
||||
bool use_for_ptt_;
|
||||
TransceiverFactory::PTTMethod ptt_type_;
|
||||
QScopedPointer<OmniRig::OmniRigX> omni_rig_;
|
||||
RigNumber rig_number_;
|
||||
QScopedPointer<OmniRig::RigX> rig_;
|
||||
QScopedPointer<OmniRig::PortBits> port_;
|
||||
QString rig_type_;
|
||||
int readable_params_;
|
||||
int writable_params_;
|
||||
QScopedPointer<QTimer> offline_timer_;
|
||||
bool send_update_signal_;
|
||||
bool reversed_; // some rigs can reverse VFOs
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,74 @@
|
|||
#ifndef POLLING_TRANSCEIVER_HPP__
|
||||
#define POLLING_TRANSCEIVER_HPP__
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "Transceiver/TransceiverBase.hpp"
|
||||
|
||||
class QTimer;
|
||||
|
||||
//
|
||||
// Polling Transceiver
|
||||
//
|
||||
// Helper base class that encapsulates the emulation of continuous
|
||||
// update and caching of a transceiver state.
|
||||
//
|
||||
// Collaborations
|
||||
//
|
||||
// Implements the TransceiverBase post action interface and provides
|
||||
// the abstract poll() operation for sub-classes to implement. The
|
||||
// poll operation is invoked every poll_interval seconds.
|
||||
//
|
||||
// Responsibilities
|
||||
//
|
||||
// Because some rig interfaces don't immediately update after a state
|
||||
// change request; this class allows a rig a few polls to stabilise
|
||||
// to the requested state before signalling the change. This means
|
||||
// that clients don't see intermediate states that are sometimes
|
||||
// inaccurate, e.g. changing the split TX frequency on Icom rigs
|
||||
// requires a VFO switch and polls while switched will return the
|
||||
// wrong current frequency.
|
||||
//
|
||||
class PollingTransceiver
|
||||
: public TransceiverBase
|
||||
{
|
||||
Q_OBJECT; // for translation context
|
||||
|
||||
protected:
|
||||
explicit PollingTransceiver (int poll_interval, // in seconds
|
||||
QObject * parent);
|
||||
|
||||
protected:
|
||||
// Sub-classes implement this and fetch what they can from the rig
|
||||
// in a non-intrusive manner.
|
||||
virtual void do_poll () = 0;
|
||||
|
||||
void do_post_start () override final;
|
||||
void do_post_stop () override final;
|
||||
void do_post_frequency (Frequency, MODE) override final;
|
||||
void do_post_tx_frequency (Frequency, MODE) override final;
|
||||
void do_post_mode (MODE) override final;
|
||||
void do_post_ptt (bool = true) override final;
|
||||
bool do_pre_update () override final;
|
||||
|
||||
private:
|
||||
void start_timer ();
|
||||
void stop_timer ();
|
||||
|
||||
Q_SLOT void handle_timeout ();
|
||||
|
||||
int interval_; // polling interval in milliseconds
|
||||
QTimer * poll_timer_;
|
||||
|
||||
// keep a record of the last state signalled so we can elide
|
||||
// duplicate updates
|
||||
Transceiver::TransceiverState last_signalled_state_;
|
||||
|
||||
// keep a record of expected state so we can compare with actual
|
||||
// updates to determine when state changes have bubbled through
|
||||
Transceiver::TransceiverState next_state_;
|
||||
|
||||
unsigned retries_; // number of incorrect polls left
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,591 @@
|
|||
#include "MessageServer.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
#include <QNetworkInterface>
|
||||
#include <QUdpSocket>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QHash>
|
||||
|
||||
#include "Radio.hpp"
|
||||
#include "Network/NetworkMessage.hpp"
|
||||
#include "qt_helpers.hpp"
|
||||
|
||||
#include "pimpl_impl.hpp"
|
||||
|
||||
#include "moc_MessageServer.cpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
auto quint32_max = std::numeric_limits<quint32>::max ();
|
||||
}
|
||||
|
||||
class MessageServer::impl
|
||||
: public QUdpSocket
|
||||
{
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
impl (MessageServer * self, QString const& version, QString const& revision)
|
||||
: self_ {self}
|
||||
, version_ {version}
|
||||
, revision_ {revision}
|
||||
, port_ {0u}
|
||||
, clock_ {new QTimer {this}}
|
||||
{
|
||||
// register the required types with Qt
|
||||
Radio::register_types ();
|
||||
|
||||
connect (this, &QIODevice::readyRead, this, &MessageServer::impl::pending_datagrams);
|
||||
connect (this, static_cast<void (impl::*) (SocketError)> (&impl::error)
|
||||
, [this] (SocketError /* e */)
|
||||
{
|
||||
Q_EMIT self_->error (errorString ());
|
||||
});
|
||||
connect (clock_, &QTimer::timeout, this, &impl::tick);
|
||||
clock_->start (NetworkMessage::pulse * 1000);
|
||||
}
|
||||
|
||||
enum StreamStatus {Fail, Short, OK};
|
||||
|
||||
void leave_multicast_group ();
|
||||
void join_multicast_group ();
|
||||
void parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg);
|
||||
void tick ();
|
||||
void pending_datagrams ();
|
||||
StreamStatus check_status (QDataStream const&) const;
|
||||
void send_message (QDataStream const& out, QByteArray const& message, QHostAddress const& address, port_type port)
|
||||
{
|
||||
if (OK == check_status (out))
|
||||
{
|
||||
writeDatagram (message, address, port);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
|
||||
MessageServer * self_;
|
||||
QString version_;
|
||||
QString revision_;
|
||||
port_type port_;
|
||||
QHostAddress multicast_group_address_;
|
||||
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}
|
||||
, negotiated_schema_number_ {2} // not 1 because it's broken
|
||||
, last_activity_ {QDateTime::currentDateTime ()}
|
||||
{
|
||||
}
|
||||
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
|
||||
QTimer * clock_;
|
||||
};
|
||||
|
||||
MessageServer::impl::BindMode constexpr MessageServer::impl::bind_mode_;
|
||||
|
||||
#include "MessageServer.moc"
|
||||
|
||||
void MessageServer::impl::leave_multicast_group ()
|
||||
{
|
||||
if (!multicast_group_address_.isNull () && BoundState == state ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
leaveMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::join_multicast_group ()
|
||||
{
|
||||
if (BoundState == state ()
|
||||
&& !multicast_group_address_.isNull ()
|
||||
#if QT_VERSION >= 0x050600
|
||||
&& multicast_group_address_.isMulticast ()
|
||||
#endif
|
||||
)
|
||||
{
|
||||
auto mcast_iface = multicastInterface ();
|
||||
if (IPv4Protocol == multicast_group_address_.protocol ()
|
||||
&& IPv4Protocol != localAddress ().protocol ())
|
||||
{
|
||||
close ();
|
||||
bind (QHostAddress::AnyIPv4, port_, bind_mode_);
|
||||
}
|
||||
bool joined {false};
|
||||
for (auto const& interface : QNetworkInterface::allInterfaces ())
|
||||
{
|
||||
if (QNetworkInterface::CanMulticast & interface.flags ())
|
||||
{
|
||||
// Windows requires outgoing interface to match
|
||||
// interface to be joined while joining, at least for
|
||||
// IPv4 it seems to
|
||||
setMulticastInterface (interface);
|
||||
|
||||
joined |= joinMulticastGroup (multicast_group_address_, interface);
|
||||
}
|
||||
}
|
||||
if (!joined)
|
||||
{
|
||||
multicast_group_address_.clear ();
|
||||
}
|
||||
setMulticastInterface (mcast_iface);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::pending_datagrams ()
|
||||
{
|
||||
while (hasPendingDatagrams ())
|
||||
{
|
||||
QByteArray datagram;
|
||||
datagram.resize (pendingDatagramSize ());
|
||||
QHostAddress sender_address;
|
||||
port_type sender_port;
|
||||
if (0 <= readDatagram (datagram.data (), datagram.size (), &sender_address, &sender_port))
|
||||
{
|
||||
parse_message (sender_address, sender_port, datagram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::parse_message (QHostAddress const& sender, port_type sender_port, QByteArray const& msg)
|
||||
{
|
||||
try
|
||||
{
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
NetworkMessage::Reader in {msg};
|
||||
|
||||
auto id = in.id ();
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
if (!clients_.contains (id))
|
||||
{
|
||||
auto& client = (clients_[id] = {sender, sender_port});
|
||||
QByteArray client_version;
|
||||
QByteArray client_revision;
|
||||
|
||||
if (NetworkMessage::Heartbeat == in.type ())
|
||||
{
|
||||
// negotiate a working schema number
|
||||
in >> client.negotiated_schema_number_;
|
||||
if (OK == check_status (in))
|
||||
{
|
||||
auto sn = NetworkMessage::Builder::schema_number;
|
||||
client.negotiated_schema_number_ = std::min (sn, client.negotiated_schema_number_);
|
||||
|
||||
// reply to the new client informing it of the
|
||||
// negotiated schema number
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder hb {&message, NetworkMessage::Heartbeat, id, client.negotiated_schema_number_};
|
||||
hb << NetworkMessage::Builder::schema_number // maximum schema number accepted
|
||||
<< version_.toUtf8 () << revision_.toUtf8 ();
|
||||
if (impl::OK == check_status (hb))
|
||||
{
|
||||
writeDatagram (message, client.sender_address_, client.sender_port_);
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("Error creating UDP message");
|
||||
}
|
||||
}
|
||||
// we don't care if this fails to read
|
||||
in >> client_version >> client_revision;
|
||||
}
|
||||
Q_EMIT self_->client_opened (id, QString::fromUtf8 (client_version),
|
||||
QString::fromUtf8 (client_revision));
|
||||
}
|
||||
clients_[id].last_activity_ = QDateTime::currentDateTime ();
|
||||
|
||||
//
|
||||
// message format is described in NetworkMessage.hpp
|
||||
//
|
||||
switch (in.type ())
|
||||
{
|
||||
case NetworkMessage::Heartbeat:
|
||||
//nothing to do here as time out handling deals with lifetime
|
||||
break;
|
||||
|
||||
case NetworkMessage::Clear:
|
||||
Q_EMIT self_->decodes_cleared (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::Status:
|
||||
{
|
||||
// unpack message
|
||||
Frequency f;
|
||||
QByteArray mode;
|
||||
QByteArray dx_call;
|
||||
QByteArray report;
|
||||
QByteArray tx_mode;
|
||||
bool tx_enabled {false};
|
||||
bool transmitting {false};
|
||||
bool decoding {false};
|
||||
quint32 rx_df {quint32_max};
|
||||
quint32 tx_df {quint32_max};
|
||||
QByteArray de_call;
|
||||
QByteArray de_grid;
|
||||
QByteArray dx_grid;
|
||||
bool watchdog_timeout {false};
|
||||
QByteArray sub_mode;
|
||||
bool fast_mode {false};
|
||||
quint8 special_op_mode {0};
|
||||
quint32 frequency_tolerance {quint32_max};
|
||||
quint32 tr_period {quint32_max};
|
||||
QByteArray configuration_name;
|
||||
in >> f >> mode >> dx_call >> report >> tx_mode >> tx_enabled >> transmitting >> decoding
|
||||
>> rx_df >> tx_df >> de_call >> de_grid >> dx_grid >> watchdog_timeout >> sub_mode
|
||||
>> 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)
|
||||
, QString::fromUtf8 (report), QString::fromUtf8 (tx_mode)
|
||||
, tx_enabled, transmitting, decoding, rx_df, tx_df
|
||||
, QString::fromUtf8 (de_call), QString::fromUtf8 (de_grid)
|
||||
, QString::fromUtf8 (dx_grid), watchdog_timeout
|
||||
, QString::fromUtf8 (sub_mode), fast_mode
|
||||
, special_op_mode, frequency_tolerance, tr_period
|
||||
, QString::fromUtf8 (configuration_name));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Decode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
quint32 delta_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray message;
|
||||
bool low_confidence {false};
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> delta_frequency >> mode
|
||||
>> message >> low_confidence >> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->decode (is_new, id, time, snr, delta_time, delta_frequency
|
||||
, QString::fromUtf8 (mode), QString::fromUtf8 (message)
|
||||
, low_confidence, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::WSPRDecode:
|
||||
{
|
||||
// unpack message
|
||||
bool is_new {true};
|
||||
QTime time;
|
||||
qint32 snr;
|
||||
float delta_time;
|
||||
Frequency frequency;
|
||||
qint32 drift;
|
||||
QByteArray callsign;
|
||||
QByteArray grid;
|
||||
qint32 power;
|
||||
bool off_air {false};
|
||||
in >> is_new >> time >> snr >> delta_time >> frequency >> drift >> callsign >> grid >> power
|
||||
>> off_air;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->WSPR_decode (is_new, id, time, snr, delta_time, frequency, drift
|
||||
, QString::fromUtf8 (callsign), QString::fromUtf8 (grid)
|
||||
, power, off_air);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::QSOLogged:
|
||||
{
|
||||
QDateTime time_off;
|
||||
QByteArray dx_call;
|
||||
QByteArray dx_grid;
|
||||
Frequency dial_frequency;
|
||||
QByteArray mode;
|
||||
QByteArray report_sent;
|
||||
QByteArray report_received;
|
||||
QByteArray tx_power;
|
||||
QByteArray comments;
|
||||
QByteArray name;
|
||||
QDateTime time_on; // Note: LOTW uses TIME_ON for their +/- 30-minute time window
|
||||
QByteArray operator_call;
|
||||
QByteArray my_call;
|
||||
QByteArray my_grid;
|
||||
QByteArray exchange_sent;
|
||||
QByteArray exchange_rcvd;
|
||||
in >> time_off >> dx_call >> dx_grid >> dial_frequency >> mode >> report_sent >> report_received
|
||||
>> tx_power >> comments >> name >> time_on >> operator_call >> my_call >> my_grid
|
||||
>> exchange_sent >> exchange_rcvd;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
|
||||
, dial_frequency, QString::fromUtf8 (mode), QString::fromUtf8 (report_sent)
|
||||
, QString::fromUtf8 (report_received), QString::fromUtf8 (tx_power)
|
||||
, QString::fromUtf8 (comments), QString::fromUtf8 (name), time_on
|
||||
, QString::fromUtf8 (operator_call), QString::fromUtf8 (my_call)
|
||||
, QString::fromUtf8 (my_grid), QString::fromUtf8 (exchange_sent)
|
||||
, QString::fromUtf8 (exchange_rcvd));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NetworkMessage::Close:
|
||||
Q_EMIT self_->client_closed (id);
|
||||
clients_.remove (id);
|
||||
break;
|
||||
|
||||
case NetworkMessage::LoggedADIF:
|
||||
{
|
||||
QByteArray ADIF;
|
||||
in >> ADIF;
|
||||
if (check_status (in) != Fail)
|
||||
{
|
||||
Q_EMIT self_->logged_ADIF (id, ADIF);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_EMIT self_->error ("MessageServer warning: invalid UDP message received");
|
||||
}
|
||||
}
|
||||
catch (std::exception const& e)
|
||||
{
|
||||
Q_EMIT self_->error (QString {"MessageServer exception: %1"}.arg (e.what ()));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Q_EMIT self_->error ("Unexpected exception in MessageServer");
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::impl::tick ()
|
||||
{
|
||||
auto now = QDateTime::currentDateTime ();
|
||||
auto iter = std::begin (clients_);
|
||||
while (iter != std::end (clients_))
|
||||
{
|
||||
if (now > (*iter).last_activity_.addSecs (NetworkMessage::pulse))
|
||||
{
|
||||
Q_EMIT self_->clear_decodes (iter.key ());
|
||||
Q_EMIT self_->client_closed (iter.key ());
|
||||
iter = clients_.erase (iter); // safe while iterating as doesn't rehash
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto MessageServer::impl::check_status (QDataStream const& stream) const -> StreamStatus
|
||||
{
|
||||
auto stat = stream.status ();
|
||||
StreamStatus result {Fail};
|
||||
switch (stat)
|
||||
{
|
||||
case QDataStream::ReadPastEnd:
|
||||
result = Short;
|
||||
break;
|
||||
|
||||
case QDataStream::ReadCorruptData:
|
||||
Q_EMIT self_->error ("Message serialization error: read corrupt data");
|
||||
break;
|
||||
|
||||
case QDataStream::WriteFailed:
|
||||
Q_EMIT self_->error ("Message serialization error: write error");
|
||||
break;
|
||||
|
||||
default:
|
||||
result = OK;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
MessageServer::MessageServer (QObject * parent, QString const& version, QString const& revision)
|
||||
: QObject {parent}
|
||||
, m_ {this, version, revision}
|
||||
{
|
||||
}
|
||||
|
||||
void MessageServer::start (port_type port, QHostAddress const& multicast_group_address)
|
||||
{
|
||||
if (port != m_->port_
|
||||
|| multicast_group_address != m_->multicast_group_address_)
|
||||
{
|
||||
m_->leave_multicast_group ();
|
||||
if (impl::BoundState == 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_))
|
||||
{
|
||||
m_->port_ = port;
|
||||
m_->join_multicast_group ();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_->port_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::clear_decodes (QString const& id, quint8 window)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Clear, id, (*iter).negotiated_schema_number_};
|
||||
out << window;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::reply (QString const& id, QTime time, qint32 snr, float delta_time
|
||||
, quint32 delta_frequency, QString const& mode
|
||||
, QString const& message_text, bool low_confidence, quint8 modifiers)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Reply, id, (*iter).negotiated_schema_number_};
|
||||
out << time << snr << delta_time << delta_frequency << mode.toUtf8 ()
|
||||
<< message_text.toUtf8 () << low_confidence << modifiers;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::replay (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::close (QString const& id)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::halt_tx (QString const& id, bool auto_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HaltTx, id, (*iter).negotiated_schema_number_};
|
||||
out << auto_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::free_text (QString const& id, QString const& text, bool send)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::FreeText, id, (*iter).negotiated_schema_number_};
|
||||
out << text.toUtf8 () << send;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::location (QString const& id, QString const& loc)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Location, id, (*iter).negotiated_schema_number_};
|
||||
out << loc.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::highlight_callsign (QString const& id, QString const& callsign
|
||||
, QColor const& bg, QColor const& fg, bool last_only)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::HighlightCallsign, id, (*iter).negotiated_schema_number_};
|
||||
out << callsign.toUtf8 () << bg << fg << last_only;
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::switch_configuration (QString const& id, QString const& configuration_name)
|
||||
{
|
||||
auto iter = m_->clients_.find (id);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::SwitchConfiguration, id, (*iter).negotiated_schema_number_};
|
||||
out << configuration_name.toUtf8 ();
|
||||
m_->send_message (out, message, iter.value ().sender_address_, (*iter).sender_port_);
|
||||
}
|
||||
}
|
||||
|
||||
void MessageServer::configure (QString const& id, 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);
|
||||
if (iter != std::end (m_->clients_))
|
||||
{
|
||||
QByteArray message;
|
||||
NetworkMessage::Builder out {&message, NetworkMessage::Configure, id, (*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_);
|
||||
}
|
||||
}
|
|
@ -147,7 +147,7 @@ public:
|
|||
: server_ {new MessageServer {this}}
|
||||
{
|
||||
// connect up server
|
||||
connect (server_, &MessageServer::error, [this] (QString const& message) {
|
||||
connect (server_, &MessageServer::error, [] (QString const& message) {
|
||||
std::cerr << tr ("Network Error: %1").arg ( message).toStdString () << std::endl;
|
||||
});
|
||||
connect (server_, &MessageServer::client_opened, this, &Server::add_client);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Version number components
|
||||
set (WSJTX_VERSION_MAJOR 2)
|
||||
set (WSJTX_VERSION_MINOR 1)
|
||||
set (WSJTX_VERSION_PATCH 2)
|
||||
set (WSJTX_RC 0) # release candidate number, comment out or zero for development versions
|
||||
set (WSJTX_VERSION_IS_RELEASE 1) # set to 1 for final release build
|
||||
set (WSJTX_VERSION_MINOR 2)
|
||||
set (WSJTX_VERSION_PATCH 0)
|
||||
set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions
|
||||
set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build
|
||||
|
|
|
@ -0,0 +1,501 @@
|
|||
#include "WSPRBandHopping.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QSettings>
|
||||
#include <QBitArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "SettingsGroup.hpp"
|
||||
#include "Configuration.hpp"
|
||||
#include "models/Bands.hpp"
|
||||
#include "models/FrequencyList.hpp"
|
||||
#include "WsprTxScheduler.h"
|
||||
#include "pimpl_impl.hpp"
|
||||
#include "moc_WSPRBandHopping.cpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#ifndef CMAKE_BUILD
|
||||
#define FC_grayline grayline_
|
||||
#else
|
||||
#include "FC.h"
|
||||
void FC_grayline (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid
|
||||
, int const * nduration, int * isun
|
||||
, int my_grid_len);
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
char const * const title = "WSPR Band Hopping";
|
||||
char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"};
|
||||
size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])};
|
||||
// These 10 bands are globally coordinated
|
||||
QList<QString> const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"};
|
||||
}
|
||||
|
||||
//
|
||||
// Dialog - maintenance of band hopping options
|
||||
//
|
||||
class Dialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using BandList = QList<QString>;
|
||||
|
||||
Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands
|
||||
, int * gray_line_duration, QWidget * parent = nullptr);
|
||||
~Dialog ();
|
||||
|
||||
Q_SLOT void frequencies_changed ();
|
||||
void resize_to_maximum ();
|
||||
|
||||
private:
|
||||
void closeEvent (QCloseEvent *) override;
|
||||
void save_window_state ();
|
||||
|
||||
QSettings * settings_;
|
||||
Configuration const * configuration_;
|
||||
BandList const * WSPR_bands_;
|
||||
QBitArray * bands_;
|
||||
int * gray_line_duration_;
|
||||
QPointer<QTableWidget> bands_table_;
|
||||
QBrush coord_background_brush_;
|
||||
QPointer<QSpinBox> gray_line_width_spin_box_;
|
||||
static int constexpr band_index_role {Qt::UserRole};
|
||||
};
|
||||
|
||||
#include "WSPRBandHopping.moc"
|
||||
|
||||
Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
|
||||
, QBitArray * bands, int * gray_line_duration, QWidget * parent)
|
||||
: QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}
|
||||
, settings_ {settings}
|
||||
, configuration_ {configuration}
|
||||
, WSPR_bands_ {WSPR_bands}
|
||||
, bands_ {bands}
|
||||
, gray_line_duration_ {gray_line_duration}
|
||||
, bands_table_ {new QTableWidget {this}}
|
||||
, coord_background_brush_ {Qt::yellow}
|
||||
, gray_line_width_spin_box_ {new QSpinBox {this}}
|
||||
{
|
||||
setWindowTitle (windowTitle () + ' ' + tr (title));
|
||||
{
|
||||
SettingsGroup g {settings_, title};
|
||||
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
|
||||
}
|
||||
|
||||
QVBoxLayout * main_layout {new QVBoxLayout};
|
||||
|
||||
bands_table_->setRowCount (num_periods);
|
||||
bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
|
||||
bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
|
||||
bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
frequencies_changed ();
|
||||
main_layout->addWidget (bands_table_);
|
||||
// recalculate table when frequencies change
|
||||
connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged
|
||||
, this, &Dialog::frequencies_changed);
|
||||
// handle changes by updating the underlying flags
|
||||
connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) {
|
||||
auto band_number = item->data (band_index_role).toInt ();
|
||||
bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ());
|
||||
});
|
||||
|
||||
// set up the gray line duration spin box
|
||||
gray_line_width_spin_box_->setRange (1, 60 * 2);
|
||||
gray_line_width_spin_box_->setSuffix ("min");
|
||||
gray_line_width_spin_box_->setValue (*gray_line_duration_);
|
||||
QFormLayout * form_layout = new QFormLayout;
|
||||
form_layout->addRow (tr ("Gray time:"), gray_line_width_spin_box_);
|
||||
connect (gray_line_width_spin_box_.data ()
|
||||
, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged)
|
||||
, [this] (int new_value) {*gray_line_duration_ = new_value;});
|
||||
|
||||
QHBoxLayout * bottom_layout = new QHBoxLayout;
|
||||
bottom_layout->addStretch ();
|
||||
bottom_layout->addLayout (form_layout);
|
||||
main_layout->addLayout (bottom_layout);
|
||||
|
||||
setLayout (main_layout);
|
||||
}
|
||||
|
||||
Dialog::~Dialog ()
|
||||
{
|
||||
// do this here too because ESC or parent shutdown closing this
|
||||
// window doesn't queue a close event
|
||||
save_window_state ();
|
||||
}
|
||||
|
||||
void Dialog::closeEvent (QCloseEvent * e)
|
||||
{
|
||||
save_window_state ();
|
||||
QDialog::closeEvent (e);
|
||||
}
|
||||
|
||||
void Dialog::save_window_state ()
|
||||
{
|
||||
SettingsGroup g {settings_, title};
|
||||
settings_->setValue ("geometry", saveGeometry ());
|
||||
}
|
||||
|
||||
void Dialog::frequencies_changed ()
|
||||
{
|
||||
bands_table_->setColumnCount (WSPR_bands_->size ());
|
||||
// set up and load the table of check boxes
|
||||
for (auto row = 0u; row < num_periods; ++row)
|
||||
{
|
||||
auto vertical_header = new QTableWidgetItem {periods[row]};
|
||||
vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter);
|
||||
bands_table_->setVerticalHeaderItem (row, vertical_header);
|
||||
int column {0};
|
||||
int band_number {0};
|
||||
for (auto const& band : *configuration_->bands ())
|
||||
{
|
||||
if (WSPR_bands_->contains (band))
|
||||
{
|
||||
if (0 == row)
|
||||
{
|
||||
auto horizontal_header = new QTableWidgetItem {band};
|
||||
bands_table_->setHorizontalHeaderItem (column, horizontal_header);
|
||||
}
|
||||
auto item = new QTableWidgetItem;
|
||||
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
item->setCheckState (bands_[row].testBit (band_number) ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData (band_index_role, band_number);
|
||||
if (coordinated_bands.contains (band))
|
||||
{
|
||||
item->setBackground (coord_background_brush_);
|
||||
}
|
||||
bands_table_->setItem (row, column, item);
|
||||
++column;
|
||||
}
|
||||
++band_number;
|
||||
}
|
||||
}
|
||||
bands_table_->resizeColumnsToContents ();
|
||||
auto is_visible = isVisible ();
|
||||
show ();
|
||||
resize_to_maximum ();
|
||||
adjustSize (); // fix the size
|
||||
if (!is_visible)
|
||||
{
|
||||
hide ();
|
||||
}
|
||||
}
|
||||
|
||||
// to get the dialog window exactly the right size to contain the
|
||||
// widgets without needing scroll bars we need to measure the size of
|
||||
// the table widget and set its minimum size to the measured size
|
||||
void Dialog::resize_to_maximum ()
|
||||
{
|
||||
bands_table_->setMinimumSize ({
|
||||
bands_table_->horizontalHeader ()->length ()
|
||||
+ bands_table_->verticalHeader ()->width ()
|
||||
+ 2 * bands_table_->frameWidth ()
|
||||
, bands_table_->verticalHeader ()->length ()
|
||||
+ bands_table_->horizontalHeader ()->height ()
|
||||
+ 2 * bands_table_->frameWidth ()
|
||||
});
|
||||
bands_table_->setMaximumSize (bands_table_->minimumSize ());
|
||||
}
|
||||
|
||||
class WSPRBandHopping::impl
|
||||
{
|
||||
public:
|
||||
using BandList = Dialog::BandList;
|
||||
|
||||
impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
|
||||
: settings_ {settings}
|
||||
, configuration_ {configuration}
|
||||
, tx_percent_ {0}
|
||||
, parent_widget_ {parent_widget}
|
||||
, last_was_tx_ {false}
|
||||
, carry_ {false}
|
||||
, seed_ {{rand (), rand (), rand (), rand (), rand (), rand (), rand (), rand ()}}
|
||||
, gen_ {seed_}
|
||||
, dist_ {1, 100}
|
||||
{
|
||||
auto num_bands = configuration_->bands ()->rowCount ();
|
||||
for (auto& flags : bands_)
|
||||
{
|
||||
flags.resize (num_bands);
|
||||
}
|
||||
}
|
||||
|
||||
bool simple_scheduler ();
|
||||
|
||||
QSettings * settings_;
|
||||
Configuration const * configuration_;
|
||||
int tx_percent_;
|
||||
BandList WSPR_bands_;
|
||||
BandList rx_permutation_;
|
||||
BandList tx_permutation_;
|
||||
QWidget * parent_widget_;
|
||||
|
||||
// 5 x 10 bit flags representing each hopping band in each period
|
||||
// and tune
|
||||
QBitArray bands_[num_periods];
|
||||
|
||||
int gray_line_duration_;
|
||||
QPointer<Dialog> dialog_;
|
||||
bool last_was_tx_;
|
||||
bool carry_;
|
||||
std::seed_seq seed_;
|
||||
std::mt19937 gen_;
|
||||
std::uniform_int_distribution<int> dist_;
|
||||
};
|
||||
|
||||
bool WSPRBandHopping::impl::simple_scheduler ()
|
||||
{
|
||||
auto tx = carry_ || tx_percent_ > dist_ (gen_);
|
||||
if (carry_)
|
||||
{
|
||||
carry_ = false;
|
||||
}
|
||||
else if (tx_percent_ < 40 && last_was_tx_ && tx)
|
||||
{
|
||||
// if percentage is less than 40 then avoid consecutive tx but
|
||||
// always catch up on the next round
|
||||
tx = false;
|
||||
carry_ = true;
|
||||
}
|
||||
last_was_tx_ = tx;
|
||||
return tx;
|
||||
}
|
||||
|
||||
WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
|
||||
: m_ {settings, configuration, parent_widget}
|
||||
{
|
||||
// detect changes to the working frequencies model
|
||||
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).values ();
|
||||
connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged
|
||||
, [this] () {
|
||||
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).values ();
|
||||
});
|
||||
|
||||
// load settings
|
||||
SettingsGroup g {m_->settings_, title};
|
||||
size_t size = m_->settings_->beginReadArray ("phases");
|
||||
for (auto i = 0u; i < size; ++i)
|
||||
{
|
||||
if (i < num_periods)
|
||||
{
|
||||
m_->settings_->setArrayIndex (i);
|
||||
m_->bands_[i] = m_->settings_->value ("bands").toBitArray ();
|
||||
}
|
||||
}
|
||||
m_->settings_->endArray ();
|
||||
m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt ();
|
||||
}
|
||||
|
||||
WSPRBandHopping::~WSPRBandHopping ()
|
||||
{
|
||||
// save settings
|
||||
SettingsGroup g {m_->settings_, title};
|
||||
m_->settings_->beginWriteArray ("phases");
|
||||
for (auto i = 0u; i < num_periods; ++i)
|
||||
{
|
||||
m_->settings_->setArrayIndex (i);
|
||||
m_->settings_->setValue ("bands", m_->bands_[i]);
|
||||
}
|
||||
m_->settings_->endArray ();
|
||||
m_->settings_->setValue ("GrayLineDuration", m_->gray_line_duration_);
|
||||
}
|
||||
|
||||
// pop up the maintenance dialog window
|
||||
void WSPRBandHopping::show_dialog (bool /* checked */)
|
||||
{
|
||||
if (!m_->dialog_)
|
||||
{
|
||||
m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_
|
||||
, &m_->gray_line_duration_, m_->parent_widget_};
|
||||
}
|
||||
m_->dialog_->show ();
|
||||
m_->dialog_->raise ();
|
||||
m_->dialog_->activateWindow ();
|
||||
}
|
||||
|
||||
int WSPRBandHopping::tx_percent () const
|
||||
{
|
||||
return m_->tx_percent_;
|
||||
}
|
||||
|
||||
void WSPRBandHopping::set_tx_percent (int new_value)
|
||||
{
|
||||
m_->tx_percent_ = new_value;
|
||||
}
|
||||
|
||||
// determine the parameters of the hop, if any
|
||||
auto WSPRBandHopping::next_hop (bool tx_enabled) -> Hop
|
||||
{
|
||||
auto const& now = QDateTime::currentDateTimeUtc ();
|
||||
auto const& date = now.date ();
|
||||
auto year = date.year ();
|
||||
auto month = date.month ();
|
||||
auto day = date.day ();
|
||||
auto const& time = now.time ();
|
||||
float uth = time.hour () + time.minute () / 60.
|
||||
+ (time.second () + .001 * time.msec ()) / 3600.;
|
||||
auto my_grid = m_->configuration_->my_grid ();
|
||||
int period_index;
|
||||
int band_index;
|
||||
int tx_next;
|
||||
|
||||
my_grid = (my_grid + " ").left (6); // hopping doesn't like
|
||||
// short grids
|
||||
|
||||
// look up the period for this time
|
||||
FC_grayline (&year, &month, &day, &uth, my_grid.toLatin1 ().constData ()
|
||||
, &m_->gray_line_duration_, &period_index
|
||||
, my_grid.size ());
|
||||
|
||||
band_index = next_hopping_band();
|
||||
|
||||
tx_next = next_is_tx () && tx_enabled;
|
||||
|
||||
int frequencies_index {-1};
|
||||
auto const& frequencies = m_->configuration_->frequencies ();
|
||||
auto const& bands = m_->configuration_->bands ();
|
||||
auto band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
|
||||
if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
|
||||
// coordinated bands
|
||||
&& m_->WSPR_bands_.contains (band_name))
|
||||
{
|
||||
// here we have a band that has been enabled in the hopping
|
||||
// matrix so check it it has a configured working frequency
|
||||
frequencies_index = frequencies->best_working_frequency (band_name);
|
||||
}
|
||||
|
||||
// if we do not have a configured working frequency on the selected
|
||||
// coordinated hopping band we next pick from a random permutation
|
||||
// of the other enabled bands in the hopping bands matrix
|
||||
if (frequencies_index < 0)
|
||||
{
|
||||
// build sets of available rx and tx bands
|
||||
#if QT_VERSION < QT_VERSION_CHECK (5, 14, 0)
|
||||
auto target_rx_bands = m_->WSPR_bands_.toSet ();
|
||||
#else
|
||||
QSet<QString> target_rx_bands {m_->WSPR_bands_.begin (), m_->WSPR_bands_.end ()};
|
||||
#endif
|
||||
auto target_tx_bands = target_rx_bands;
|
||||
for (auto i = 0; i < m_->bands_[period_index].size (); ++i)
|
||||
{
|
||||
auto const& band = bands->data (bands->index (i, 0)).toString ();
|
||||
// remove bands that are not enabled for hopping in this phase
|
||||
if (!m_->bands_[period_index].testBit (i))
|
||||
{
|
||||
target_rx_bands.remove (band);
|
||||
target_tx_bands.remove (band);
|
||||
}
|
||||
// remove rx only bands from transmit list and vice versa
|
||||
if (m_->bands_[5].testBit (i))
|
||||
{
|
||||
target_tx_bands.remove (band);
|
||||
}
|
||||
else
|
||||
{
|
||||
target_rx_bands.remove (band);
|
||||
}
|
||||
}
|
||||
// if we have some bands to permute
|
||||
if (target_rx_bands.size () + target_tx_bands.size ())
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK (5, 14, 0)
|
||||
auto rx_permutations = m_->rx_permutation_.toSet ();
|
||||
auto tx_permutations = m_->tx_permutation_.toSet ();
|
||||
#else
|
||||
QSet<QString> rx_permutations {m_->rx_permutation_.begin (), m_->rx_permutation_.end ()};
|
||||
QSet<QString> tx_permutations {m_->tx_permutation_.begin (), m_->tx_permutation_.end ()};
|
||||
#endif
|
||||
if (!(m_->rx_permutation_.size () + m_->tx_permutation_.size ()) // all used up
|
||||
// or rx list contains a band no longer scheduled
|
||||
|| !target_rx_bands.contains (rx_permutations)
|
||||
// or tx list contains a band no longer scheduled for tx
|
||||
|| !target_tx_bands.contains (tx_permutations))
|
||||
{
|
||||
// build new random permutations
|
||||
m_->rx_permutation_ = target_rx_bands.values ();
|
||||
std::random_shuffle (std::begin (m_->rx_permutation_), std::end (m_->rx_permutation_));
|
||||
m_->tx_permutation_ = target_tx_bands.values ();
|
||||
std::random_shuffle (std::begin (m_->tx_permutation_), std::end (m_->tx_permutation_));
|
||||
// qDebug () << "New random Rx permutation:" << m_->rx_permutation_
|
||||
// << "random Tx permutation:" << m_->tx_permutation_;
|
||||
}
|
||||
if ((tx_next && m_->tx_permutation_.size ()) || !m_->rx_permutation_.size ())
|
||||
{
|
||||
Q_ASSERT (m_->tx_permutation_.size ());
|
||||
// use one from the current random tx permutation
|
||||
band_name = m_->tx_permutation_.takeFirst ();
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_ASSERT (m_->rx_permutation_.size ());
|
||||
// use one from the current random rx permutation
|
||||
band_name = m_->rx_permutation_.takeFirst ();
|
||||
}
|
||||
// find the first WSPR working frequency for the chosen band
|
||||
frequencies_index = frequencies->best_working_frequency (band_name);
|
||||
if (frequencies_index >= 0) // should be a redundant check,
|
||||
// but to be safe
|
||||
{
|
||||
// we can use the random choice
|
||||
// qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
|
||||
band_index = bands->find (band_name);
|
||||
if (band_index < 0) // this shouldn't happen
|
||||
{
|
||||
Q_ASSERT (band_index >= 0);
|
||||
frequencies_index = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
band_index += 3;
|
||||
// qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
|
||||
// remove from random permutations to stop the coordinated bands
|
||||
// getting too high a weighting - not perfect but surely helps
|
||||
m_->rx_permutation_.removeOne (band_name);
|
||||
m_->tx_permutation_.removeOne (band_name);
|
||||
}
|
||||
|
||||
return {
|
||||
periods[period_index]
|
||||
|
||||
, frequencies_index
|
||||
|
||||
, frequencies_index >= 0 // new band
|
||||
&& tx_enabled // transmit is allowed
|
||||
&& !tx_next // not going to Tx anyway
|
||||
&& m_->bands_[4].testBit (band_index) // tune up required
|
||||
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
|
||||
|
||||
, frequencies_index >= 0 // new band
|
||||
&& tx_next // Tx scheduled
|
||||
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
|
||||
};
|
||||
}
|
||||
|
||||
bool WSPRBandHopping::next_is_tx (bool simple_schedule)
|
||||
{
|
||||
if (simple_schedule)
|
||||
{
|
||||
return m_->simple_scheduler ();
|
||||
}
|
||||
if (100 == m_->tx_percent_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// consult scheduler to determine if next period should be a tx interval
|
||||
return next_tx_state(m_->tx_percent_);
|
||||
}
|
||||
}
|
|
@ -1,490 +0,0 @@
|
|||
#include "WSPRBandHopping.hpp"
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <QPointer>
|
||||
#include <QSettings>
|
||||
#include <QBitArray>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QtWidgets>
|
||||
|
||||
#include "SettingsGroup.hpp"
|
||||
#include "Configuration.hpp"
|
||||
#include "models/Bands.hpp"
|
||||
#include "models/FrequencyList.hpp"
|
||||
#include "WsprTxScheduler.h"
|
||||
#include "pimpl_impl.hpp"
|
||||
#include "moc_WSPRBandHopping.cpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#ifndef CMAKE_BUILD
|
||||
#define FC_grayline grayline_
|
||||
#else
|
||||
#include "FC.h"
|
||||
void FC_grayline (int const * year, int const * month, int const * nday, float const * uth, char const * my_grid
|
||||
, int const * nduration, int * isun
|
||||
, int my_grid_len);
|
||||
#endif
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
char const * const title = "WSPR Band Hopping";
|
||||
char const * const periods[] = {"Sunrise grayline", "Day", "Sunset grayline", "Night", "Tune", "Rx only"};
|
||||
size_t constexpr num_periods {sizeof (periods) / sizeof (periods[0])};
|
||||
// These 10 bands are globally coordinated
|
||||
QList<QString> const coordinated_bands = {"160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m"};
|
||||
}
|
||||
|
||||
//
|
||||
// Dialog - maintenance of band hopping options
|
||||
//
|
||||
class Dialog
|
||||
: public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using BandList = QList<QString>;
|
||||
|
||||
Dialog (QSettings *, Configuration const *, BandList const * WSPT_bands, QBitArray * bands
|
||||
, int * gray_line_duration, QWidget * parent = nullptr);
|
||||
~Dialog ();
|
||||
|
||||
Q_SLOT void frequencies_changed ();
|
||||
void resize_to_maximum ();
|
||||
|
||||
private:
|
||||
void closeEvent (QCloseEvent *) override;
|
||||
void save_window_state ();
|
||||
|
||||
QSettings * settings_;
|
||||
Configuration const * configuration_;
|
||||
BandList const * WSPR_bands_;
|
||||
QBitArray * bands_;
|
||||
int * gray_line_duration_;
|
||||
QPointer<QTableWidget> bands_table_;
|
||||
QBrush coord_background_brush_;
|
||||
QPointer<QSpinBox> gray_line_width_spin_box_;
|
||||
static int constexpr band_index_role {Qt::UserRole};
|
||||
};
|
||||
|
||||
#include "WSPRBandHopping.moc"
|
||||
|
||||
Dialog::Dialog (QSettings * settings, Configuration const * configuration, BandList const * WSPR_bands
|
||||
, QBitArray * bands, int * gray_line_duration, QWidget * parent)
|
||||
: QDialog {parent, Qt::Window | Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint}
|
||||
, settings_ {settings}
|
||||
, configuration_ {configuration}
|
||||
, WSPR_bands_ {WSPR_bands}
|
||||
, bands_ {bands}
|
||||
, gray_line_duration_ {gray_line_duration}
|
||||
, bands_table_ {new QTableWidget {this}}
|
||||
, coord_background_brush_ {Qt::yellow}
|
||||
, gray_line_width_spin_box_ {new QSpinBox {this}}
|
||||
{
|
||||
setWindowTitle (windowTitle () + ' ' + tr (title));
|
||||
{
|
||||
SettingsGroup g {settings_, title};
|
||||
restoreGeometry (settings_->value ("geometry", saveGeometry ()).toByteArray ());
|
||||
}
|
||||
|
||||
QVBoxLayout * main_layout {new QVBoxLayout};
|
||||
|
||||
bands_table_->setRowCount (num_periods);
|
||||
bands_table_->setVerticalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
|
||||
bands_table_->setHorizontalScrollBarPolicy (Qt::ScrollBarAlwaysOff);
|
||||
bands_table_->setSizePolicy (QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
frequencies_changed ();
|
||||
main_layout->addWidget (bands_table_);
|
||||
// recalculate table when frequencies change
|
||||
connect (configuration_->frequencies (), &QAbstractItemModel::layoutChanged
|
||||
, this, &Dialog::frequencies_changed);
|
||||
// handle changes by updating the underlying flags
|
||||
connect (bands_table_.data (), &QTableWidget::itemChanged, [this] (QTableWidgetItem * item) {
|
||||
auto band_number = item->data (band_index_role).toInt ();
|
||||
bands_[item->row ()].setBit (band_number, Qt::Checked == item->checkState ());
|
||||
});
|
||||
|
||||
// set up the gray line duration spin box
|
||||
gray_line_width_spin_box_->setRange (1, 60 * 2);
|
||||
gray_line_width_spin_box_->setSuffix ("min");
|
||||
gray_line_width_spin_box_->setValue (*gray_line_duration_);
|
||||
QFormLayout * form_layout = new QFormLayout;
|
||||
form_layout->addRow (tr ("Gray time:"), gray_line_width_spin_box_);
|
||||
connect (gray_line_width_spin_box_.data ()
|
||||
, static_cast<void (QSpinBox::*) (int)> (&QSpinBox::valueChanged)
|
||||
, [this] (int new_value) {*gray_line_duration_ = new_value;});
|
||||
|
||||
QHBoxLayout * bottom_layout = new QHBoxLayout;
|
||||
bottom_layout->addStretch ();
|
||||
bottom_layout->addLayout (form_layout);
|
||||
main_layout->addLayout (bottom_layout);
|
||||
|
||||
setLayout (main_layout);
|
||||
}
|
||||
|
||||
Dialog::~Dialog ()
|
||||
{
|
||||
// do this here too because ESC or parent shutdown closing this
|
||||
// window doesn't queue a close event
|
||||
save_window_state ();
|
||||
}
|
||||
|
||||
void Dialog::closeEvent (QCloseEvent * e)
|
||||
{
|
||||
save_window_state ();
|
||||
QDialog::closeEvent (e);
|
||||
}
|
||||
|
||||
void Dialog::save_window_state ()
|
||||
{
|
||||
SettingsGroup g {settings_, title};
|
||||
settings_->setValue ("geometry", saveGeometry ());
|
||||
}
|
||||
|
||||
void Dialog::frequencies_changed ()
|
||||
{
|
||||
bands_table_->setColumnCount (WSPR_bands_->size ());
|
||||
// set up and load the table of check boxes
|
||||
for (auto row = 0u; row < num_periods; ++row)
|
||||
{
|
||||
auto vertical_header = new QTableWidgetItem {periods[row]};
|
||||
vertical_header->setTextAlignment (Qt::AlignRight | Qt::AlignVCenter);
|
||||
bands_table_->setVerticalHeaderItem (row, vertical_header);
|
||||
int column {0};
|
||||
int band_number {0};
|
||||
for (auto const& band : *configuration_->bands ())
|
||||
{
|
||||
if (WSPR_bands_->contains (band))
|
||||
{
|
||||
if (0 == row)
|
||||
{
|
||||
auto horizontal_header = new QTableWidgetItem {band};
|
||||
bands_table_->setHorizontalHeaderItem (column, horizontal_header);
|
||||
}
|
||||
auto item = new QTableWidgetItem;
|
||||
item->setFlags (Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
|
||||
item->setCheckState (bands_[row].testBit (band_number) ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData (band_index_role, band_number);
|
||||
if (coordinated_bands.contains (band))
|
||||
{
|
||||
item->setBackground (coord_background_brush_);
|
||||
}
|
||||
bands_table_->setItem (row, column, item);
|
||||
++column;
|
||||
}
|
||||
++band_number;
|
||||
}
|
||||
}
|
||||
bands_table_->resizeColumnsToContents ();
|
||||
auto is_visible = isVisible ();
|
||||
show ();
|
||||
resize_to_maximum ();
|
||||
adjustSize (); // fix the size
|
||||
if (!is_visible)
|
||||
{
|
||||
hide ();
|
||||
}
|
||||
}
|
||||
|
||||
// to get the dialog window exactly the right size to contain the
|
||||
// widgets without needing scroll bars we need to measure the size of
|
||||
// the table widget and set its minimum size to the measured size
|
||||
void Dialog::resize_to_maximum ()
|
||||
{
|
||||
bands_table_->setMinimumSize ({
|
||||
bands_table_->horizontalHeader ()->length ()
|
||||
+ bands_table_->verticalHeader ()->width ()
|
||||
+ 2 * bands_table_->frameWidth ()
|
||||
, bands_table_->verticalHeader ()->length ()
|
||||
+ bands_table_->horizontalHeader ()->height ()
|
||||
+ 2 * bands_table_->frameWidth ()
|
||||
});
|
||||
bands_table_->setMaximumSize (bands_table_->minimumSize ());
|
||||
}
|
||||
|
||||
class WSPRBandHopping::impl
|
||||
{
|
||||
public:
|
||||
using BandList = Dialog::BandList;
|
||||
|
||||
impl (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
|
||||
: settings_ {settings}
|
||||
, configuration_ {configuration}
|
||||
, tx_percent_ {0}
|
||||
, parent_widget_ {parent_widget}
|
||||
, last_was_tx_ {false}
|
||||
, carry_ {false}
|
||||
, seed_ {{rand (), rand (), rand (), rand (), rand (), rand (), rand (), rand ()}}
|
||||
, gen_ {seed_}
|
||||
, dist_ {1, 100}
|
||||
{
|
||||
auto num_bands = configuration_->bands ()->rowCount ();
|
||||
for (auto& flags : bands_)
|
||||
{
|
||||
flags.resize (num_bands);
|
||||
}
|
||||
}
|
||||
|
||||
bool simple_scheduler ();
|
||||
|
||||
QSettings * settings_;
|
||||
Configuration const * configuration_;
|
||||
int tx_percent_;
|
||||
BandList WSPR_bands_;
|
||||
BandList rx_permutation_;
|
||||
BandList tx_permutation_;
|
||||
QWidget * parent_widget_;
|
||||
|
||||
// 5 x 10 bit flags representing each hopping band in each period
|
||||
// and tune
|
||||
QBitArray bands_[num_periods];
|
||||
|
||||
int gray_line_duration_;
|
||||
QPointer<Dialog> dialog_;
|
||||
bool last_was_tx_;
|
||||
bool carry_;
|
||||
std::seed_seq seed_;
|
||||
std::mt19937 gen_;
|
||||
std::uniform_int_distribution<int> dist_;
|
||||
};
|
||||
|
||||
bool WSPRBandHopping::impl::simple_scheduler ()
|
||||
{
|
||||
auto tx = carry_ || tx_percent_ > dist_ (gen_);
|
||||
if (carry_)
|
||||
{
|
||||
carry_ = false;
|
||||
}
|
||||
else if (tx_percent_ < 40 && last_was_tx_ && tx)
|
||||
{
|
||||
// if percentage is less than 40 then avoid consecutive tx but
|
||||
// always catch up on the next round
|
||||
tx = false;
|
||||
carry_ = true;
|
||||
}
|
||||
last_was_tx_ = tx;
|
||||
return tx;
|
||||
}
|
||||
|
||||
WSPRBandHopping::WSPRBandHopping (QSettings * settings, Configuration const * configuration, QWidget * parent_widget)
|
||||
: m_ {settings, configuration, parent_widget}
|
||||
{
|
||||
// detect changes to the working frequencies model
|
||||
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList ();
|
||||
connect (m_->configuration_->frequencies (), &QAbstractItemModel::layoutChanged
|
||||
, [this] () {
|
||||
m_->WSPR_bands_ = m_->configuration_->frequencies ()->all_bands (m_->configuration_->region (), Modes::WSPR).toList ();
|
||||
});
|
||||
|
||||
// load settings
|
||||
SettingsGroup g {m_->settings_, title};
|
||||
size_t size = m_->settings_->beginReadArray ("phases");
|
||||
for (auto i = 0u; i < size; ++i)
|
||||
{
|
||||
if (i < num_periods)
|
||||
{
|
||||
m_->settings_->setArrayIndex (i);
|
||||
m_->bands_[i] = m_->settings_->value ("bands").toBitArray ();
|
||||
}
|
||||
}
|
||||
m_->settings_->endArray ();
|
||||
m_->gray_line_duration_ = m_->settings_->value ("GrayLineDuration", 60).toUInt ();
|
||||
}
|
||||
|
||||
WSPRBandHopping::~WSPRBandHopping ()
|
||||
{
|
||||
// save settings
|
||||
SettingsGroup g {m_->settings_, title};
|
||||
m_->settings_->beginWriteArray ("phases");
|
||||
for (auto i = 0u; i < num_periods; ++i)
|
||||
{
|
||||
m_->settings_->setArrayIndex (i);
|
||||
m_->settings_->setValue ("bands", m_->bands_[i]);
|
||||
}
|
||||
m_->settings_->endArray ();
|
||||
m_->settings_->setValue ("GrayLineDuration", m_->gray_line_duration_);
|
||||
}
|
||||
|
||||
// pop up the maintenance dialog window
|
||||
void WSPRBandHopping::show_dialog (bool /* checked */)
|
||||
{
|
||||
if (!m_->dialog_)
|
||||
{
|
||||
m_->dialog_ = new Dialog {m_->settings_, m_->configuration_, &m_->WSPR_bands_, m_->bands_
|
||||
, &m_->gray_line_duration_, m_->parent_widget_};
|
||||
}
|
||||
m_->dialog_->show ();
|
||||
m_->dialog_->raise ();
|
||||
m_->dialog_->activateWindow ();
|
||||
}
|
||||
|
||||
int WSPRBandHopping::tx_percent () const
|
||||
{
|
||||
return m_->tx_percent_;
|
||||
}
|
||||
|
||||
void WSPRBandHopping::set_tx_percent (int new_value)
|
||||
{
|
||||
m_->tx_percent_ = new_value;
|
||||
}
|
||||
|
||||
// determine the parameters of the hop, if any
|
||||
auto WSPRBandHopping::next_hop (bool tx_enabled) -> Hop
|
||||
{
|
||||
auto const& now = QDateTime::currentDateTimeUtc ();
|
||||
auto const& date = now.date ();
|
||||
auto year = date.year ();
|
||||
auto month = date.month ();
|
||||
auto day = date.day ();
|
||||
auto const& time = now.time ();
|
||||
float uth = time.hour () + time.minute () / 60.
|
||||
+ (time.second () + .001 * time.msec ()) / 3600.;
|
||||
auto my_grid = m_->configuration_->my_grid ();
|
||||
int period_index;
|
||||
int band_index;
|
||||
int tx_next;
|
||||
|
||||
my_grid = (my_grid + " ").left (6); // hopping doesn't like
|
||||
// short grids
|
||||
|
||||
// look up the period for this time
|
||||
FC_grayline (&year, &month, &day, &uth, my_grid.toLatin1 ().constData ()
|
||||
, &m_->gray_line_duration_, &period_index
|
||||
, my_grid.size ());
|
||||
|
||||
band_index = next_hopping_band();
|
||||
|
||||
tx_next = next_is_tx () && tx_enabled;
|
||||
|
||||
int frequencies_index {-1};
|
||||
auto const& frequencies = m_->configuration_->frequencies ();
|
||||
auto const& bands = m_->configuration_->bands ();
|
||||
auto band_name = bands->data (bands->index (band_index + 3, 0)).toString ();
|
||||
if (m_->bands_[period_index].testBit (band_index + 3) // +3 for
|
||||
// coordinated bands
|
||||
&& m_->WSPR_bands_.contains (band_name))
|
||||
{
|
||||
// here we have a band that has been enabled in the hopping
|
||||
// matrix so check it it has a configured working frequency
|
||||
frequencies_index = frequencies->best_working_frequency (band_name);
|
||||
}
|
||||
|
||||
// if we do not have a configured working frequency on the selected
|
||||
// coordinated hopping band we next pick from a random permutation
|
||||
// of the other enabled bands in the hopping bands matrix
|
||||
if (frequencies_index < 0)
|
||||
{
|
||||
// build sets of available rx and tx bands
|
||||
auto target_rx_bands = m_->WSPR_bands_.toSet ();
|
||||
auto target_tx_bands = target_rx_bands;
|
||||
for (auto i = 0; i < m_->bands_[period_index].size (); ++i)
|
||||
{
|
||||
auto const& band = bands->data (bands->index (i, 0)).toString ();
|
||||
// remove bands that are not enabled for hopping in this phase
|
||||
if (!m_->bands_[period_index].testBit (i))
|
||||
{
|
||||
target_rx_bands.remove (band);
|
||||
target_tx_bands.remove (band);
|
||||
}
|
||||
// remove rx only bands from transmit list and vice versa
|
||||
if (m_->bands_[5].testBit (i))
|
||||
{
|
||||
target_tx_bands.remove (band);
|
||||
}
|
||||
else
|
||||
{
|
||||
target_rx_bands.remove (band);
|
||||
}
|
||||
}
|
||||
// if we have some bands to permute
|
||||
if (target_rx_bands.size () + target_tx_bands.size ())
|
||||
{
|
||||
if (!(m_->rx_permutation_.size () + m_->tx_permutation_.size ()) // all used up
|
||||
// or rx list contains a band no longer scheduled
|
||||
|| !target_rx_bands.contains (m_->rx_permutation_.toSet ())
|
||||
// or tx list contains a band no longer scheduled for tx
|
||||
|| !target_tx_bands.contains (m_->tx_permutation_.toSet ()))
|
||||
{
|
||||
// build new random permutations
|
||||
m_->rx_permutation_ = target_rx_bands.toList ();
|
||||
std::random_shuffle (std::begin (m_->rx_permutation_), std::end (m_->rx_permutation_));
|
||||
m_->tx_permutation_ = target_tx_bands.toList ();
|
||||
std::random_shuffle (std::begin (m_->tx_permutation_), std::end (m_->tx_permutation_));
|
||||
// qDebug () << "New random Rx permutation:" << m_->rx_permutation_
|
||||
// << "random Tx permutation:" << m_->tx_permutation_;
|
||||
}
|
||||
if ((tx_next && m_->tx_permutation_.size ()) || !m_->rx_permutation_.size ())
|
||||
{
|
||||
Q_ASSERT (m_->tx_permutation_.size ());
|
||||
// use one from the current random tx permutation
|
||||
band_name = m_->tx_permutation_.takeFirst ();
|
||||
}
|
||||
else
|
||||
{
|
||||
Q_ASSERT (m_->rx_permutation_.size ());
|
||||
// use one from the current random rx permutation
|
||||
band_name = m_->rx_permutation_.takeFirst ();
|
||||
}
|
||||
// find the first WSPR working frequency for the chosen band
|
||||
frequencies_index = frequencies->best_working_frequency (band_name);
|
||||
if (frequencies_index >= 0) // should be a redundant check,
|
||||
// but to be safe
|
||||
{
|
||||
// we can use the random choice
|
||||
// qDebug () << "random:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
|
||||
band_index = bands->find (band_name);
|
||||
if (band_index < 0) // this shouldn't happen
|
||||
{
|
||||
Q_ASSERT (band_index >= 0);
|
||||
frequencies_index = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
band_index += 3;
|
||||
// qDebug () << "scheduled:" << frequencies->data (frequencies->index (frequencies_index, FrequencyList_v2::frequency_column)).toString ();
|
||||
// remove from random permutations to stop the coordinated bands
|
||||
// getting too high a weighting - not perfect but surely helps
|
||||
m_->rx_permutation_.removeOne (band_name);
|
||||
m_->tx_permutation_.removeOne (band_name);
|
||||
}
|
||||
|
||||
return {
|
||||
periods[period_index]
|
||||
|
||||
, frequencies_index
|
||||
|
||||
, frequencies_index >= 0 // new band
|
||||
&& tx_enabled // transmit is allowed
|
||||
&& !tx_next // not going to Tx anyway
|
||||
&& m_->bands_[4].testBit (band_index) // tune up required
|
||||
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
|
||||
|
||||
, frequencies_index >= 0 // new band
|
||||
&& tx_next // Tx scheduled
|
||||
&& !m_->bands_[5].testBit (band_index) // not an Rx only band
|
||||
};
|
||||
}
|
||||
|
||||
bool WSPRBandHopping::next_is_tx (bool simple_schedule)
|
||||
{
|
||||
if (simple_schedule)
|
||||
{
|
||||
return m_->simple_scheduler ();
|
||||
}
|
||||
if (100 == m_->tx_percent_)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// consult scheduler to determine if next period should be a tx interval
|
||||
return next_tx_state(m_->tx_percent_);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
import sys
|
||||
from datetime import datetime
|
||||
mycall="K1JT"
|
||||
mygrid="FN20"
|
||||
|
||||
# Keyed with hiscall:
|
||||
Freq={}
|
||||
T0={}
|
||||
RcvdExch={}
|
||||
Grid={}
|
||||
|
||||
# Keyed with hiscall_band:
|
||||
Staged={}
|
||||
TimeLogged={}
|
||||
QSOinProgress={}
|
||||
# QSOinProgress bit values:
|
||||
# 1 He called me
|
||||
# 2 I called him
|
||||
# 4 Received his exchange
|
||||
# 8 Received his Roger
|
||||
# 16 Sent Roger
|
||||
# 32 Staged for logging
|
||||
|
||||
def isGrid(g):
|
||||
"""Return True if g is a valid grid4 and not RR73"""
|
||||
if len(g)!=4 or g=="RR73": return False
|
||||
if ord(g[0:1])<ord('A') or ord(g[0:1])>ord('R'): return False
|
||||
if ord(g[1:2])<ord('A') or ord(g[1:2])>ord('R'): return False
|
||||
if ord(g[2:3])<ord('0') or ord(g[2:3])>ord('9'): return False
|
||||
if ord(g[3:4])<ord('0') or ord(g[3:4])>ord('9'): return False
|
||||
return True
|
||||
|
||||
if len(sys.argv)!=4:
|
||||
print "Usage: python all2cab.py <mycall> <mygrid> <infile>"
|
||||
print "Example: python all2cab.py K1JT FN20 all_wwdigi_2019.txt"
|
||||
exit()
|
||||
|
||||
f=open(sys.argv[3],mode='r')
|
||||
ss=f.readlines()
|
||||
f.close
|
||||
dt0=datetime.strptime(ss[0][0:13],"%y%m%d_%H%M%S")
|
||||
iz=len(ss)
|
||||
nqso=0
|
||||
hiscall_band=""
|
||||
|
||||
for i in range(iz):
|
||||
s=ss[i][0:80].strip()
|
||||
dt=datetime.strptime(s[0:13],"%y%m%d_%H%M%S")
|
||||
tx=" Tx " in s #True if this is my transmission
|
||||
w=s.split()
|
||||
if len(w)<10: continue
|
||||
if w[7]=="CQ":
|
||||
cq=True
|
||||
if w[8].isalpha():
|
||||
s=s.replace(" CQ "," CQ_")
|
||||
w=s.split()
|
||||
if len(w)<10: continue
|
||||
c1=w[7]
|
||||
c2=w[8]
|
||||
c3=w[9]
|
||||
roger = c3=="R"
|
||||
if roger: c3=w[10]
|
||||
cq = (c1=="CQ" or c1[0:3]=="CQ_")
|
||||
if cq:
|
||||
Grid[c2]=c3
|
||||
|
||||
hiscall=""
|
||||
if tx and not cq:
|
||||
hiscall=c1
|
||||
if c1==mycall:
|
||||
freq=int(1000.0*float(s[13:23])) + int((int(s[42:47]))/1000)
|
||||
hiscall=c2
|
||||
Freq[hiscall]=freq
|
||||
MHz="%3d" % int(float(s[13:23]))
|
||||
hiscall_band=hiscall+MHz
|
||||
n=QSOinProgress.get(hiscall_band,0)
|
||||
n = n | 1 #He called me
|
||||
if roger or c3=="RR73" or c3=="RRR" or c3=="73":
|
||||
n = n | 8 # Rcvd Roger
|
||||
if isGrid(c3):
|
||||
hisgrid=c3
|
||||
RcvdExch[hiscall]=hisgrid
|
||||
n = n | 4 #Received his exchange
|
||||
else:
|
||||
g=Grid.get(hiscall,"")
|
||||
if isGrid(g):
|
||||
RcvdExch[hiscall]=g
|
||||
n = n | 4 #Received his exchange
|
||||
QSOinProgress[hiscall_band]=n
|
||||
|
||||
if len(hiscall)>=3:
|
||||
MHz="%3d" % int(float(s[13:23]))
|
||||
hiscall_band=hiscall+MHz
|
||||
if tx:
|
||||
n=QSOinProgress.get(hiscall_band,0)
|
||||
n = n | 2 #I called him
|
||||
if roger or c3=="RR73" or c3=="RRR" or c3=="73":
|
||||
n = n | 4 | 16 #Rcvd Exch, Sent Roger
|
||||
if c3=="RR73" or c3=="RRR" or c3=="73":
|
||||
n = n | 8 #Rcvd Exch, Sent Roger
|
||||
QSOinProgress[hiscall_band]=n
|
||||
T0[hiscall]=dt
|
||||
|
||||
if len(hiscall_band)<5 or len(hiscall)<3: continue
|
||||
|
||||
if QSOinProgress.get(hiscall_band,0)>=31:
|
||||
n=QSOinProgress.get(hiscall_band,0)
|
||||
n = n | 32
|
||||
QSOinProgress[hiscall_band]=n
|
||||
t=str(T0[hiscall])[0:16]
|
||||
t=t[0:13] + t[14:16]
|
||||
buf="QSO: %5d DG %s %-10s %s %-10s %s" % (Freq[hiscall],t,\
|
||||
mycall,mygrid,hiscall,RcvdExch.get(hiscall," "))
|
||||
MHz="%3d" % int(float(s[13:23]))
|
||||
hiscall_band=hiscall+MHz
|
||||
t=Staged.get(hiscall_band,"")
|
||||
time_diff=-1
|
||||
if TimeLogged.get(hiscall_band,0)!=0:
|
||||
time_diff=(T0[hiscall] - TimeLogged[hiscall_band]).total_seconds()
|
||||
if time_diff == -1 or time_diff > 180: #Log only once within 3 min
|
||||
Staged[hiscall_band]=buf #Staged for logging
|
||||
nqso=nqso+1
|
||||
TimeLogged[hiscall_band]=T0[hiscall]
|
||||
print buf #For now, log anything staged
|
||||
del QSOinProgress[hiscall_band]
|
|
@ -1,8 +0,0 @@
|
|||
See ./index.html for information about this release. The "Getting Started"
|
||||
section is a useful starting place.
|
||||
|
||||
---------------------------
|
||||
Copyright Beman Dawes, 2008
|
||||
|
||||
Distributed under the Boost Software License, Version 1.0.
|
||||
See ./LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt
|
312
boost/Jamroot
312
boost/Jamroot
|
@ -1,312 +0,0 @@
|
|||
# Copyright Vladimir Prus 2002-2006.
|
||||
# Copyright Dave Abrahams 2005-2006.
|
||||
# Copyright Rene Rivera 2005-2007.
|
||||
# Copyright Douglas Gregor 2005.
|
||||
#
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE_1_0.txt or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
# Usage:
|
||||
#
|
||||
# b2 [options] [properties] [install|stage]
|
||||
#
|
||||
# Builds and installs Boost.
|
||||
#
|
||||
# Targets and Related Options:
|
||||
#
|
||||
# install Install headers and compiled library files to the
|
||||
# ======= configured locations (below).
|
||||
#
|
||||
# --prefix=<PREFIX> Install architecture independent files here.
|
||||
# Default; C:\Boost on Win32
|
||||
# Default; /usr/local on Unix. Linux, etc.
|
||||
#
|
||||
# --exec-prefix=<EPREFIX> Install architecture dependent files here.
|
||||
# Default; <PREFIX>
|
||||
#
|
||||
# --libdir=<DIR> Install library files here.
|
||||
# Default; <EPREFIX>/lib
|
||||
#
|
||||
# --includedir=<HDRDIR> Install header files here.
|
||||
# Default; <PREFIX>/include
|
||||
#
|
||||
# stage Build and install only compiled library files to the
|
||||
# ===== stage directory.
|
||||
#
|
||||
# --stagedir=<STAGEDIR> Install library files here
|
||||
# Default; ./stage
|
||||
#
|
||||
# Other Options:
|
||||
#
|
||||
# --build-type=<type> Build the specified pre-defined set of variations of
|
||||
# the libraries. Note, that which variants get built
|
||||
# depends on what each library supports.
|
||||
#
|
||||
# -- minimal -- (default) Builds a minimal set of
|
||||
# variants. On Windows, these are static
|
||||
# multithreaded libraries in debug and release
|
||||
# modes, using shared runtime. On Linux, these are
|
||||
# static and shared multithreaded libraries in
|
||||
# release mode.
|
||||
#
|
||||
# -- complete -- Build all possible variations.
|
||||
#
|
||||
# --build-dir=DIR Build in this location instead of building within
|
||||
# the distribution tree. Recommended!
|
||||
#
|
||||
# --show-libraries Display the list of Boost libraries that require
|
||||
# build and installation steps, and then exit.
|
||||
#
|
||||
# --layout=<layout> Determine whether to choose library names and header
|
||||
# locations such that multiple versions of Boost or
|
||||
# multiple compilers can be used on the same system.
|
||||
#
|
||||
# -- versioned -- Names of boost binaries include
|
||||
# the Boost version number, name and version of
|
||||
# the compiler and encoded build properties. Boost
|
||||
# headers are installed in a subdirectory of
|
||||
# <HDRDIR> whose name contains the Boost version
|
||||
# number.
|
||||
#
|
||||
# -- tagged -- Names of boost binaries include the
|
||||
# encoded build properties such as variant and
|
||||
# threading, but do not including compiler name
|
||||
# and version, or Boost version. This option is
|
||||
# useful if you build several variants of Boost,
|
||||
# using the same compiler.
|
||||
#
|
||||
# -- system -- Binaries names do not include the
|
||||
# Boost version number or the name and version
|
||||
# number of the compiler. Boost headers are
|
||||
# installed directly into <HDRDIR>. This option is
|
||||
# intended for system integrators building
|
||||
# distribution packages.
|
||||
#
|
||||
# The default value is 'versioned' on Windows, and
|
||||
# 'system' on Unix.
|
||||
#
|
||||
# --buildid=ID Add the specified ID to the name of built libraries.
|
||||
# The default is to not add anything.
|
||||
#
|
||||
# --python-buildid=ID Add the specified ID to the name of built libraries
|
||||
# that depend on Python. The default is to not add
|
||||
# anything. This ID is added in addition to --buildid.
|
||||
#
|
||||
# --help This message.
|
||||
#
|
||||
# --with-<library> Build and install the specified <library>. If this
|
||||
# option is used, only libraries specified using this
|
||||
# option will be built.
|
||||
#
|
||||
# --without-<library> Do not build, stage, or install the specified
|
||||
# <library>. By default, all libraries are built.
|
||||
#
|
||||
# Properties:
|
||||
#
|
||||
# toolset=toolset Indicate the toolset to build with.
|
||||
#
|
||||
# variant=debug|release Select the build variant
|
||||
#
|
||||
# link=static|shared Whether to build static or shared libraries
|
||||
#
|
||||
# threading=single|multi Whether to build single or multithreaded binaries
|
||||
#
|
||||
# runtime-link=static|shared
|
||||
# Whether to link to static or shared C and C++
|
||||
# runtime.
|
||||
#
|
||||
|
||||
# TODO:
|
||||
# - handle boost version
|
||||
# - handle python options such as pydebug
|
||||
|
||||
import boostcpp ;
|
||||
import package ;
|
||||
|
||||
import sequence ;
|
||||
import xsltproc ;
|
||||
import set ;
|
||||
import path ;
|
||||
import link ;
|
||||
|
||||
path-constant BOOST_ROOT : . ;
|
||||
constant BOOST_VERSION : 1.63.0 ;
|
||||
constant BOOST_JAMROOT_MODULE : $(__name__) ;
|
||||
|
||||
boostcpp.set-version $(BOOST_VERSION) ;
|
||||
|
||||
use-project /boost/architecture : libs/config/checks/architecture ;
|
||||
|
||||
local all-headers =
|
||||
[ MATCH .*libs/(.*)/include/boost : [ glob libs/*/include/boost libs/*/*/include/boost ] ] ;
|
||||
|
||||
for dir in $(all-headers)
|
||||
{
|
||||
link-directory $(dir)-headers : libs/$(dir)/include/boost : <location>. ;
|
||||
explicit $(dir)-headers ;
|
||||
}
|
||||
|
||||
if $(all-headers)
|
||||
{
|
||||
constant BOOST_MODULARLAYOUT : $(all-headers) ;
|
||||
}
|
||||
|
||||
project boost
|
||||
: requirements <include>.
|
||||
|
||||
[ boostcpp.architecture ]
|
||||
[ boostcpp.address-model ]
|
||||
|
||||
# Disable auto-linking for all targets here, primarily because it caused
|
||||
# troubles with V2.
|
||||
<define>BOOST_ALL_NO_LIB=1
|
||||
# Used to encode variant in target name. See the 'tag' rule below.
|
||||
<tag>@$(__name__).tag
|
||||
<conditional>@handle-static-runtime
|
||||
# Comeau does not support shared lib
|
||||
<toolset>como:<link>static
|
||||
<toolset>como-linux:<define>_GNU_SOURCE=1
|
||||
# When building docs within Boost, we want the standard Boost style
|
||||
<xsl:param>boost.defaults=Boost
|
||||
: usage-requirements <include>.
|
||||
: build-dir bin.v2
|
||||
;
|
||||
|
||||
# This rule is called by Boost.Build to determine the name of target. We use it
|
||||
# to encode the build variant, compiler name and boost version in the target
|
||||
# name.
|
||||
#
|
||||
rule tag ( name : type ? : property-set )
|
||||
{
|
||||
return [ boostcpp.tag $(name) : $(type) : $(property-set) ] ;
|
||||
}
|
||||
|
||||
rule python-tag ( name : type ? : property-set )
|
||||
{
|
||||
return [ boostcpp.python-tag $(name) : $(type) : $(property-set) ] ;
|
||||
}
|
||||
|
||||
rule handle-static-runtime ( properties * )
|
||||
{
|
||||
# Using static runtime with shared libraries is impossible on Linux, and
|
||||
# dangerous on Windows. Therefore, we disallow it. This might be drastic,
|
||||
# but it was disabled for a while without anybody complaining.
|
||||
|
||||
# For CW, static runtime is needed so that std::locale works.
|
||||
if <link>shared in $(properties) && <runtime-link>static in $(properties) &&
|
||||
! ( <toolset>cw in $(properties) )
|
||||
{
|
||||
ECHO "error: link=shared together with runtime-link=static is not allowed" ;
|
||||
ECHO "error: such property combination is either impossible " ;
|
||||
ECHO "error: or too dangerious to be of any use" ;
|
||||
EXIT ;
|
||||
}
|
||||
}
|
||||
|
||||
all-libraries = [ MATCH .*libs/(.*)/build/.* : [ glob libs/*/build/Jamfile.v2 ]
|
||||
[ glob libs/*/build/Jamfile ] ] ;
|
||||
|
||||
all-libraries = [ sequence.unique $(all-libraries) ] ;
|
||||
# The function_types library has a Jamfile, but it's used for maintenance
|
||||
# purposes, there's no library to build and install.
|
||||
all-libraries = [ set.difference $(all-libraries) : function_types ] ;
|
||||
|
||||
# Setup convenient aliases for all libraries.
|
||||
|
||||
local rule explicit-alias ( id : targets + )
|
||||
{
|
||||
alias $(id) : $(targets) ;
|
||||
explicit $(id) ;
|
||||
}
|
||||
|
||||
# First, the complicated libraries: where the target name in Jamfile is
|
||||
# different from its directory name.
|
||||
explicit-alias prg_exec_monitor : libs/test/build//boost_prg_exec_monitor ;
|
||||
explicit-alias test_exec_monitor : libs/test/build//boost_test_exec_monitor ;
|
||||
explicit-alias unit_test_framework : libs/test/build//boost_unit_test_framework ;
|
||||
explicit-alias bgl-vis : libs/graps/build//bgl-vis ;
|
||||
explicit-alias serialization : libs/serialization/build//boost_serialization ;
|
||||
explicit-alias wserialization : libs/serialization/build//boost_wserialization ;
|
||||
for local l in $(all-libraries)
|
||||
{
|
||||
if ! $(l) in test graph serialization
|
||||
{
|
||||
explicit-alias $(l) : libs/$(l)/build//boost_$(l) ;
|
||||
}
|
||||
}
|
||||
|
||||
# Log has an additional target
|
||||
explicit-alias log_setup : libs/log/build//boost_log_setup ;
|
||||
|
||||
alias headers : $(all-headers)-headers : : : <include>. ;
|
||||
explicit headers ;
|
||||
|
||||
# Make project ids of all libraries known.
|
||||
for local l in $(all-libraries)
|
||||
{
|
||||
use-project /boost/$(l) : libs/$(l)/build ;
|
||||
}
|
||||
|
||||
if [ path.exists $(BOOST_ROOT)/tools/inspect ]
|
||||
{
|
||||
use-project /boost/tools/inspect : tools/inspect/build ;
|
||||
}
|
||||
|
||||
if [ path.exists $(BOOST_ROOT)/libs/wave/tool ]
|
||||
{
|
||||
use-project /boost/libs/wave/tool : libs/wave/tool/build ;
|
||||
}
|
||||
|
||||
# This rule should be called from libraries' Jamfiles and will create two
|
||||
# targets, "install" and "stage", that will install or stage that library. The
|
||||
# --prefix option is respected, but --with and --without options, naturally, are
|
||||
# ignored.
|
||||
#
|
||||
# - libraries -- list of library targets to install.
|
||||
#
|
||||
rule boost-install ( libraries * )
|
||||
{
|
||||
package.install install
|
||||
: <dependency>/boost//install-proper-headers $(install-requirements)
|
||||
: # No binaries
|
||||
: $(libraries)
|
||||
: # No headers, it is handled by the dependency.
|
||||
;
|
||||
|
||||
install stage : $(libraries) : <location>$(BOOST_STAGE_LOCATE) ;
|
||||
|
||||
module [ CALLER_MODULE ]
|
||||
{
|
||||
explicit stage ;
|
||||
explicit install ;
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a library target, adding autolink support and also creates
|
||||
# stage and install targets via boost-install, above.
|
||||
rule boost-lib ( name : sources * : requirements * : default-build * : usage-requirements * )
|
||||
{
|
||||
name = boost_$(name) ;
|
||||
autolink = <link>shared:<define>BOOST_$(name:U)_DYN_LINK=1 ;
|
||||
lib $(name)
|
||||
: $(sources)
|
||||
: $(requirements) $(autolink)
|
||||
: $(default-build)
|
||||
: $(usage-requirements) $(autolink)
|
||||
;
|
||||
boost-install $(name) ;
|
||||
}
|
||||
|
||||
|
||||
headers =
|
||||
# The .SUNWCCh files are present in tr1 include directory and have to be
|
||||
# installed (see http://lists.boost.org/Archives/boost/2007/05/121430.php).
|
||||
[ path.glob-tree $(BOOST_ROOT)/boost : *.hpp *.ipp *.h *.inc *.SUNWCCh : CVS .svn ]
|
||||
[ path.glob-tree $(BOOST_ROOT)/boost/compatibility/cpp_c_headers : c* : CVS .svn ]
|
||||
[ path.glob boost/tr1/tr1 : * : bcc32 sun CVS .svn ]
|
||||
;
|
||||
|
||||
# Declare special top-level targets that build and install the desired variants
|
||||
# of the libraries.
|
||||
boostcpp.declare-targets $(all-libraries) : $(headers) ;
|
|
@ -1,23 +0,0 @@
|
|||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
|
@ -7,11 +7,18 @@ upstream and master. To upgrade the content do the following:
|
|||
|
||||
```bash
|
||||
git checkout upstream
|
||||
mv README.md /tmp
|
||||
rm -r *
|
||||
# use the bcp tool to populate with the new Boost libraries
|
||||
# use git add to stage any new files and directories
|
||||
git commit -a -m "Updated Boost v1.63 libraries including ..."
|
||||
git tag boost_1_63
|
||||
mv /tmp/README.md .
|
||||
# use the bcp tool to populate with the new Boost libraries from a clean boost install.
|
||||
# Something like:
|
||||
#
|
||||
# bcp --boost=../boost_1_70_0 --unix-lines iterator range math numeric crc circular_buffer multi_index intrusive .
|
||||
#
|
||||
# Clean out any unwanted files and directories (e.g. libs and docs for a header only subset).
|
||||
# Use git add to stage any new files and directories.
|
||||
git commit -a -m "Updated Boost v1.70.0 libraries including ..."
|
||||
git tag boost_1_70_0
|
||||
git push origin
|
||||
git checkout master
|
||||
git merge upstream
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
# Copyright (C) 2002-2003 David Abrahams.
|
||||
# Copyright (C) 2002-2003 Vladimir Prus.
|
||||
# Copyright (C) 2003,2007 Rene Rivera.
|
||||
# Use, modification and distribution are subject to the
|
||||
# Boost Software License, Version 1.0. (See accompanying file
|
||||
# LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
# This is the initial file loaded by Boost Jam when run from any Boost library
|
||||
# folder. It allows us to choose which Boost Build installation to use for
|
||||
# building Boost libraries. Unless explicitly selected using a command-line
|
||||
# option, the version included with the Boost library distribution is used (as
|
||||
# opposed to any other Boost Build version installed on the user's sytem).
|
||||
|
||||
BOOST_ROOT = $(.boost-build-file:D) ;
|
||||
BOOST_BUILD = [ MATCH --boost-build=(.*) : $(ARGV) ] ;
|
||||
BOOST_BUILD ?= tools/build/src ;
|
||||
boost-build $(BOOST_BUILD) ;
|
|
@ -1,66 +0,0 @@
|
|||
/*=============================================================================
|
||||
Copyright 2002 William E. Kempf
|
||||
Distributed under the Boost Software License, Version 1.0. (See accompany-
|
||||
ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
=============================================================================*/
|
||||
|
||||
H1
|
||||
{
|
||||
FONT-SIZE: 200%;
|
||||
COLOR: #00008B;
|
||||
}
|
||||
H2
|
||||
{
|
||||
FONT-SIZE: 150%;
|
||||
}
|
||||
H3
|
||||
{
|
||||
FONT-SIZE: 125%;
|
||||
}
|
||||
H4
|
||||
{
|
||||
FONT-SIZE: 108%;
|
||||
}
|
||||
BODY
|
||||
{
|
||||
FONT-SIZE: 100%;
|
||||
BACKGROUND-COLOR: #ffffff;
|
||||
COLOR: #000000;
|
||||
}
|
||||
PRE
|
||||
{
|
||||
MARGIN-LEFT: 2em;
|
||||
FONT-FAMILY: Courier,
|
||||
monospace;
|
||||
}
|
||||
CODE
|
||||
{
|
||||
FONT-FAMILY: Courier,
|
||||
monospace;
|
||||
}
|
||||
CODE.as_pre
|
||||
{
|
||||
white-space: pre;
|
||||
}
|
||||
.index
|
||||
{
|
||||
TEXT-ALIGN: left;
|
||||
}
|
||||
.page-index
|
||||
{
|
||||
TEXT-ALIGN: left;
|
||||
}
|
||||
.definition
|
||||
{
|
||||
TEXT-ALIGN: left;
|
||||
}
|
||||
.footnote
|
||||
{
|
||||
FONT-SIZE: 66%;
|
||||
VERTICAL-ALIGN: super;
|
||||
TEXT-DECORATION: none;
|
||||
}
|
||||
.function-semantics
|
||||
{
|
||||
CLEAR: left;
|
||||
}
|
BIN
boost/boost.png
BIN
boost/boost.png
Binary file not shown.
Before Width: | Height: | Size: 6.2 KiB |
|
@ -27,7 +27,7 @@ namespace boost { namespace algorithm {
|
|||
///
|
||||
/// \note This function is part of the C++2011 standard library.
|
||||
template<typename InputIterator, typename Predicate>
|
||||
bool all_of ( InputIterator first, InputIterator last, Predicate p )
|
||||
BOOST_CXX14_CONSTEXPR bool all_of ( InputIterator first, InputIterator last, Predicate p )
|
||||
{
|
||||
for ( ; first != last; ++first )
|
||||
if ( !p(*first))
|
||||
|
@ -43,7 +43,7 @@ bool all_of ( InputIterator first, InputIterator last, Predicate p )
|
|||
/// \param p A predicate for testing the elements of the range
|
||||
///
|
||||
template<typename Range, typename Predicate>
|
||||
bool all_of ( const Range &r, Predicate p )
|
||||
BOOST_CXX14_CONSTEXPR bool all_of ( const Range &r, Predicate p )
|
||||
{
|
||||
return boost::algorithm::all_of ( boost::begin (r), boost::end (r), p );
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ bool all_of ( const Range &r, Predicate p )
|
|||
/// \param val A value to compare against
|
||||
///
|
||||
template<typename InputIterator, typename T>
|
||||
bool all_of_equal ( InputIterator first, InputIterator last, const T &val )
|
||||
BOOST_CXX14_CONSTEXPR bool all_of_equal ( InputIterator first, InputIterator last, const T &val )
|
||||
{
|
||||
for ( ; first != last; ++first )
|
||||
if ( val != *first )
|
||||
|
@ -73,7 +73,7 @@ bool all_of_equal ( InputIterator first, InputIterator last, const T &val )
|
|||
/// \param val A value to compare against
|
||||
///
|
||||
template<typename Range, typename T>
|
||||
bool all_of_equal ( const Range &r, const T &val )
|
||||
BOOST_CXX14_CONSTEXPR bool all_of_equal ( const Range &r, const T &val )
|
||||
{
|
||||
return boost::algorithm::all_of_equal ( boost::begin (r), boost::end (r), val );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,553 @@
|
|||
// (C) Copyright Herve Bronnimann 2004.
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See accompanying
|
||||
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
/*
|
||||
Revision history:
|
||||
1 July 2004
|
||||
Split the code into two headers to lessen dependence on
|
||||
Boost.tuple. (Herve)
|
||||
26 June 2004
|
||||
Added the code for the boost minmax library. (Herve)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_ALGORITHM_MINMAX_ELEMENT_HPP
|
||||
#define BOOST_ALGORITHM_MINMAX_ELEMENT_HPP
|
||||
|
||||
/* PROPOSED STANDARD EXTENSIONS:
|
||||
*
|
||||
* minmax_element(first, last)
|
||||
* Effect: std::make_pair( std::min_element(first, last),
|
||||
* std::max_element(first, last) );
|
||||
*
|
||||
* minmax_element(first, last, comp)
|
||||
* Effect: std::make_pair( std::min_element(first, last, comp),
|
||||
* std::max_element(first, last, comp) );
|
||||
*/
|
||||
|
||||
#include <utility> // for std::pair and std::make_pair
|
||||
|
||||
namespace boost {
|
||||
|
||||
namespace detail { // for obtaining a uniform version of minmax_element
|
||||
// that compiles with VC++ 6.0 -- avoid the iterator_traits by
|
||||
// having comparison object over iterator, not over dereferenced value
|
||||
|
||||
template <typename Iterator>
|
||||
struct less_over_iter {
|
||||
bool operator()(Iterator const& it1,
|
||||
Iterator const& it2) const { return *it1 < *it2; }
|
||||
};
|
||||
|
||||
template <typename Iterator, class BinaryPredicate>
|
||||
struct binary_pred_over_iter {
|
||||
explicit binary_pred_over_iter(BinaryPredicate const& p ) : m_p( p ) {}
|
||||
bool operator()(Iterator const& it1,
|
||||
Iterator const& it2) const { return m_p(*it1, *it2); }
|
||||
private:
|
||||
BinaryPredicate m_p;
|
||||
};
|
||||
|
||||
// common base for the two minmax_element overloads
|
||||
|
||||
template <typename ForwardIter, class Compare >
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
basic_minmax_element(ForwardIter first, ForwardIter last, Compare comp)
|
||||
{
|
||||
if (first == last)
|
||||
return std::make_pair(last,last);
|
||||
|
||||
ForwardIter min_result = first;
|
||||
ForwardIter max_result = first;
|
||||
|
||||
// if only one element
|
||||
ForwardIter second = first; ++second;
|
||||
if (second == last)
|
||||
return std::make_pair(min_result, max_result);
|
||||
|
||||
// treat first pair separately (only one comparison for first two elements)
|
||||
ForwardIter potential_min_result = last;
|
||||
if (comp(first, second))
|
||||
max_result = second;
|
||||
else {
|
||||
min_result = second;
|
||||
potential_min_result = first;
|
||||
}
|
||||
|
||||
// then each element by pairs, with at most 3 comparisons per pair
|
||||
first = ++second; if (first != last) ++second;
|
||||
while (second != last) {
|
||||
if (comp(first, second)) {
|
||||
if (comp(first, min_result)) {
|
||||
min_result = first;
|
||||
potential_min_result = last;
|
||||
}
|
||||
if (comp(max_result, second))
|
||||
max_result = second;
|
||||
} else {
|
||||
if (comp(second, min_result)) {
|
||||
min_result = second;
|
||||
potential_min_result = first;
|
||||
}
|
||||
if (comp(max_result, first))
|
||||
max_result = first;
|
||||
}
|
||||
first = ++second;
|
||||
if (first != last) ++second;
|
||||
}
|
||||
|
||||
// if odd number of elements, treat last element
|
||||
if (first != last) { // odd number of elements
|
||||
if (comp(first, min_result)) {
|
||||
min_result = first;
|
||||
potential_min_result = last;
|
||||
}
|
||||
else if (comp(max_result, first))
|
||||
max_result = first;
|
||||
}
|
||||
|
||||
// resolve min_result being incorrect with one extra comparison
|
||||
// (in which case potential_min_result is necessarily the correct result)
|
||||
if (potential_min_result != last
|
||||
&& !comp(min_result, potential_min_result))
|
||||
min_result = potential_min_result;
|
||||
|
||||
return std::make_pair(min_result,max_result);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename ForwardIter>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
minmax_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_minmax_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
minmax_element(ForwardIter first, ForwardIter last, BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_minmax_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* PROPOSED BOOST EXTENSIONS
|
||||
* In the description below, [rfirst,rlast) denotes the reversed range
|
||||
* of [first,last). Even though the iterator type of first and last may
|
||||
* be only a Forward Iterator, it is possible to explain the semantics
|
||||
* by assuming that it is a Bidirectional Iterator. In the sequel,
|
||||
* reverse(ForwardIterator&) returns the reverse_iterator adaptor.
|
||||
* This is not how the functions would be implemented!
|
||||
*
|
||||
* first_min_element(first, last)
|
||||
* Effect: std::min_element(first, last);
|
||||
*
|
||||
* first_min_element(first, last, comp)
|
||||
* Effect: std::min_element(first, last, comp);
|
||||
*
|
||||
* last_min_element(first, last)
|
||||
* Effect: reverse( std::min_element(reverse(last), reverse(first)) );
|
||||
*
|
||||
* last_min_element(first, last, comp)
|
||||
* Effect: reverse( std::min_element(reverse(last), reverse(first), comp) );
|
||||
*
|
||||
* first_max_element(first, last)
|
||||
* Effect: std::max_element(first, last);
|
||||
*
|
||||
* first_max_element(first, last, comp)
|
||||
* Effect: max_element(first, last);
|
||||
*
|
||||
* last_max_element(first, last)
|
||||
* Effect: reverse( std::max_element(reverse(last), reverse(first)) );
|
||||
*
|
||||
* last_max_element(first, last, comp)
|
||||
* Effect: reverse( std::max_element(reverse(last), reverse(first), comp) );
|
||||
*
|
||||
* first_min_first_max_element(first, last)
|
||||
* Effect: std::make_pair( first_min_element(first, last),
|
||||
* first_max_element(first, last) );
|
||||
*
|
||||
* first_min_first_max_element(first, last, comp)
|
||||
* Effect: std::make_pair( first_min_element(first, last, comp),
|
||||
* first_max_element(first, last, comp) );
|
||||
*
|
||||
* first_min_last_max_element(first, last)
|
||||
* Effect: std::make_pair( first_min_element(first, last),
|
||||
* last_max_element(first, last) );
|
||||
*
|
||||
* first_min_last_max_element(first, last, comp)
|
||||
* Effect: std::make_pair( first_min_element(first, last, comp),
|
||||
* last_max_element(first, last, comp) );
|
||||
*
|
||||
* last_min_first_max_element(first, last)
|
||||
* Effect: std::make_pair( last_min_element(first, last),
|
||||
* first_max_element(first, last) );
|
||||
*
|
||||
* last_min_first_max_element(first, last, comp)
|
||||
* Effect: std::make_pair( last_min_element(first, last, comp),
|
||||
* first_max_element(first, last, comp) );
|
||||
*
|
||||
* last_min_last_max_element(first, last)
|
||||
* Effect: std::make_pair( last_min_element(first, last),
|
||||
* last_max_element(first, last) );
|
||||
*
|
||||
* last_min_last_max_element(first, last, comp)
|
||||
* Effect: std::make_pair( last_min_element(first, last, comp),
|
||||
* last_max_element(first, last, comp) );
|
||||
*/
|
||||
|
||||
namespace boost {
|
||||
|
||||
// Min_element and max_element variants
|
||||
|
||||
namespace detail { // common base for the overloads
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
basic_first_min_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return last;
|
||||
ForwardIter min_result = first;
|
||||
while (++first != last)
|
||||
if (comp(first, min_result))
|
||||
min_result = first;
|
||||
return min_result;
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
basic_last_min_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return last;
|
||||
ForwardIter min_result = first;
|
||||
while (++first != last)
|
||||
if (!comp(min_result, first))
|
||||
min_result = first;
|
||||
return min_result;
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
basic_first_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return last;
|
||||
ForwardIter max_result = first;
|
||||
while (++first != last)
|
||||
if (comp(max_result, first))
|
||||
max_result = first;
|
||||
return max_result;
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
basic_last_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return last;
|
||||
ForwardIter max_result = first;
|
||||
while (++first != last)
|
||||
if (!comp(first, max_result))
|
||||
max_result = first;
|
||||
return max_result;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename ForwardIter>
|
||||
ForwardIter
|
||||
first_min_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_first_min_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
first_min_element(ForwardIter first, ForwardIter last, BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_first_min_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
ForwardIter
|
||||
last_min_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_last_min_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
last_min_element(ForwardIter first, ForwardIter last, BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_last_min_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
ForwardIter
|
||||
first_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_first_max_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
first_max_element(ForwardIter first, ForwardIter last, BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_first_max_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
ForwardIter
|
||||
last_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_last_max_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
ForwardIter
|
||||
last_max_element(ForwardIter first, ForwardIter last, BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_last_max_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
|
||||
// Minmax_element variants -- comments removed
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
basic_first_min_last_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last)
|
||||
return std::make_pair(last,last);
|
||||
|
||||
ForwardIter min_result = first;
|
||||
ForwardIter max_result = first;
|
||||
|
||||
ForwardIter second = ++first;
|
||||
if (second == last)
|
||||
return std::make_pair(min_result, max_result);
|
||||
|
||||
if (comp(second, min_result))
|
||||
min_result = second;
|
||||
else
|
||||
max_result = second;
|
||||
|
||||
first = ++second; if (first != last) ++second;
|
||||
while (second != last) {
|
||||
if (!comp(second, first)) {
|
||||
if (comp(first, min_result))
|
||||
min_result = first;
|
||||
if (!comp(second, max_result))
|
||||
max_result = second;
|
||||
} else {
|
||||
if (comp(second, min_result))
|
||||
min_result = second;
|
||||
if (!comp(first, max_result))
|
||||
max_result = first;
|
||||
}
|
||||
first = ++second; if (first != last) ++second;
|
||||
}
|
||||
|
||||
if (first != last) {
|
||||
if (comp(first, min_result))
|
||||
min_result = first;
|
||||
else if (!comp(first, max_result))
|
||||
max_result = first;
|
||||
}
|
||||
|
||||
return std::make_pair(min_result, max_result);
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
basic_last_min_first_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return std::make_pair(last,last);
|
||||
|
||||
ForwardIter min_result = first;
|
||||
ForwardIter max_result = first;
|
||||
|
||||
ForwardIter second = ++first;
|
||||
if (second == last)
|
||||
return std::make_pair(min_result, max_result);
|
||||
|
||||
if (comp(max_result, second))
|
||||
max_result = second;
|
||||
else
|
||||
min_result = second;
|
||||
|
||||
first = ++second; if (first != last) ++second;
|
||||
while (second != last) {
|
||||
if (comp(first, second)) {
|
||||
if (!comp(min_result, first))
|
||||
min_result = first;
|
||||
if (comp(max_result, second))
|
||||
max_result = second;
|
||||
} else {
|
||||
if (!comp(min_result, second))
|
||||
min_result = second;
|
||||
if (comp(max_result, first))
|
||||
max_result = first;
|
||||
}
|
||||
first = ++second; if (first != last) ++second;
|
||||
}
|
||||
|
||||
if (first != last) {
|
||||
if (!comp(min_result, first))
|
||||
min_result = first;
|
||||
else if (comp(max_result, first))
|
||||
max_result = first;
|
||||
}
|
||||
|
||||
return std::make_pair(min_result, max_result);
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
basic_last_min_last_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
if (first == last) return std::make_pair(last,last);
|
||||
|
||||
ForwardIter min_result = first;
|
||||
ForwardIter max_result = first;
|
||||
|
||||
ForwardIter second = first; ++second;
|
||||
if (second == last)
|
||||
return std::make_pair(min_result,max_result);
|
||||
|
||||
ForwardIter potential_max_result = last;
|
||||
if (comp(first, second))
|
||||
max_result = second;
|
||||
else {
|
||||
min_result = second;
|
||||
potential_max_result = second;
|
||||
}
|
||||
|
||||
first = ++second; if (first != last) ++second;
|
||||
while (second != last) {
|
||||
if (comp(first, second)) {
|
||||
if (!comp(min_result, first))
|
||||
min_result = first;
|
||||
if (!comp(second, max_result)) {
|
||||
max_result = second;
|
||||
potential_max_result = last;
|
||||
}
|
||||
} else {
|
||||
if (!comp(min_result, second))
|
||||
min_result = second;
|
||||
if (!comp(first, max_result)) {
|
||||
max_result = first;
|
||||
potential_max_result = second;
|
||||
}
|
||||
}
|
||||
first = ++second;
|
||||
if (first != last) ++second;
|
||||
}
|
||||
|
||||
if (first != last) {
|
||||
if (!comp(min_result, first))
|
||||
min_result = first;
|
||||
if (!comp(first, max_result)) {
|
||||
max_result = first;
|
||||
potential_max_result = last;
|
||||
}
|
||||
}
|
||||
|
||||
if (potential_max_result != last
|
||||
&& !comp(potential_max_result, max_result))
|
||||
max_result = potential_max_result;
|
||||
|
||||
return std::make_pair(min_result,max_result);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename ForwardIter>
|
||||
inline std::pair<ForwardIter,ForwardIter>
|
||||
first_min_first_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return minmax_element(first, last);
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
inline std::pair<ForwardIter,ForwardIter>
|
||||
first_min_first_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
return minmax_element(first, last, comp);
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
first_min_last_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_first_min_last_max_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
inline std::pair<ForwardIter,ForwardIter>
|
||||
first_min_last_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_first_min_last_max_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
last_min_first_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_last_min_first_max_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
inline std::pair<ForwardIter,ForwardIter>
|
||||
last_min_first_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_last_min_first_max_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
template <typename ForwardIter>
|
||||
std::pair<ForwardIter,ForwardIter>
|
||||
last_min_last_max_element(ForwardIter first, ForwardIter last)
|
||||
{
|
||||
return detail::basic_last_min_last_max_element(first, last,
|
||||
detail::less_over_iter<ForwardIter>() );
|
||||
}
|
||||
|
||||
template <typename ForwardIter, class BinaryPredicate>
|
||||
inline std::pair<ForwardIter,ForwardIter>
|
||||
last_min_last_max_element(ForwardIter first, ForwardIter last,
|
||||
BinaryPredicate comp)
|
||||
{
|
||||
return detail::basic_last_min_last_max_element(first, last,
|
||||
detail::binary_pred_over_iter<ForwardIter,BinaryPredicate>(comp) );
|
||||
}
|
||||
|
||||
} // namespace boost
|
||||
|
||||
#endif // BOOST_ALGORITHM_MINMAX_ELEMENT_HPP
|
|
@ -30,8 +30,10 @@ namespace boost {
|
|||
|
||||
// a tolower functor
|
||||
template<typename CharT>
|
||||
struct to_lowerF : public std::unary_function<CharT, CharT>
|
||||
struct to_lowerF
|
||||
{
|
||||
typedef CharT argument_type;
|
||||
typedef CharT result_type;
|
||||
// Constructor
|
||||
to_lowerF( const std::locale& Loc ) : m_Loc( &Loc ) {}
|
||||
|
||||
|
@ -50,8 +52,10 @@ namespace boost {
|
|||
|
||||
// a toupper functor
|
||||
template<typename CharT>
|
||||
struct to_upperF : public std::unary_function<CharT, CharT>
|
||||
struct to_upperF
|
||||
{
|
||||
typedef CharT argument_type;
|
||||
typedef CharT result_type;
|
||||
// Constructor
|
||||
to_upperF( const std::locale& Loc ) : m_Loc( &Loc ) {}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace boost {
|
|||
// Protected construction/destruction
|
||||
|
||||
// Default constructor
|
||||
find_iterator_base() {};
|
||||
find_iterator_base() {}
|
||||
// Copy construction
|
||||
find_iterator_base( const find_iterator_base& Other ) :
|
||||
m_Finder(Other.m_Finder) {}
|
||||
|
|
|
@ -89,9 +89,10 @@ namespace boost {
|
|||
template<
|
||||
typename SeqT,
|
||||
typename IteratorT=BOOST_STRING_TYPENAME SeqT::const_iterator >
|
||||
struct copy_iterator_rangeF :
|
||||
public std::unary_function< iterator_range<IteratorT>, SeqT >
|
||||
struct copy_iterator_rangeF
|
||||
{
|
||||
typedef iterator_range<IteratorT> argument_type;
|
||||
typedef SeqT result_type;
|
||||
SeqT operator()( const iterator_range<IteratorT>& Range ) const
|
||||
{
|
||||
return copy_range<SeqT>(Range);
|
||||
|
|
|
@ -43,7 +43,6 @@ namespace boost {
|
|||
The result is given as an \c iterator_range delimiting the match.
|
||||
|
||||
\param Search A substring to be searched for.
|
||||
\param Comp An element comparison predicate
|
||||
\return An instance of the \c first_finder object
|
||||
*/
|
||||
template<typename RangeT>
|
||||
|
@ -84,7 +83,6 @@ namespace boost {
|
|||
The result is given as an \c iterator_range delimiting the match.
|
||||
|
||||
\param Search A substring to be searched for.
|
||||
\param Comp An element comparison predicate
|
||||
\return An instance of the \c last_finder object
|
||||
*/
|
||||
template<typename RangeT>
|
||||
|
@ -124,7 +122,6 @@ namespace boost {
|
|||
|
||||
\param Search A substring to be searched for.
|
||||
\param Nth An index of the match to be find
|
||||
\param Comp An element comparison predicate
|
||||
\return An instance of the \c nth_finder object
|
||||
*/
|
||||
template<typename RangeT>
|
||||
|
@ -230,7 +227,6 @@ namespace boost {
|
|||
|
||||
\param Begin Beginning of the range
|
||||
\param End End of the range
|
||||
\param Range The range.
|
||||
\return An instance of the \c range_finger object
|
||||
*/
|
||||
template< typename ForwardIteratorT >
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
(c) 2014-2015 Glen Joseph Fernandes
|
||||
<glenjofe -at- gmail.com>
|
||||
|
||||
Distributed under the Boost Software
|
||||
License, Version 1.0.
|
||||
http://boost.org/LICENSE_1_0.txt
|
||||
*/
|
||||
#ifndef BOOST_ALIGN_ALIGN_HPP
|
||||
#define BOOST_ALIGN_ALIGN_HPP
|
||||
|
||||
#include <boost/config.hpp>
|
||||
|
||||
#if !defined(BOOST_NO_CXX11_STD_ALIGN)
|
||||
#include <boost/align/detail/align_cxx11.hpp>
|
||||
#else
|
||||
#include <boost/align/detail/align.hpp>
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
(c) 2014-2016 Glen Joseph Fernandes
|
||||
<glenjofe -at- gmail.com>
|
||||
|
||||
Distributed under the Boost Software
|
||||
License, Version 1.0.
|
||||
http://boost.org/LICENSE_1_0.txt
|
||||
*/
|
||||
#ifndef BOOST_ALIGN_DETAIL_ALIGN_HPP
|
||||
#define BOOST_ALIGN_DETAIL_ALIGN_HPP
|
||||
|
||||
#include <boost/align/detail/is_alignment.hpp>
|
||||
#include <boost/assert.hpp>
|
||||
|
||||
namespace boost {
|
||||
namespace alignment {
|
||||
|
||||
inline void* align(std::size_t alignment, std::size_t size,
|
||||
void*& ptr, std::size_t& space)
|
||||
{
|
||||
BOOST_ASSERT(detail::is_alignment(alignment));
|
||||
if (size <= space) {
|
||||
char* p = (char*)(((std::size_t)ptr + alignment - 1) &
|
||||
~(alignment - 1));
|
||||
std::size_t n = space - (p - static_cast<char*>(ptr));
|
||||
if (size <= n) {
|
||||
ptr = p;
|
||||
space = n;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} /* .alignment */
|
||||
} /* .boost */
|
||||
|
||||
#endif
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
(c) 2014 Glen Joseph Fernandes
|
||||
<glenjofe -at- gmail.com>
|
||||
|
||||
Distributed under the Boost Software
|
||||
License, Version 1.0.
|
||||
http://boost.org/LICENSE_1_0.txt
|
||||
*/
|
||||
#ifndef BOOST_ALIGN_DETAIL_ALIGN_CXX11_HPP
|
||||
#define BOOST_ALIGN_DETAIL_ALIGN_CXX11_HPP
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace boost {
|
||||
namespace alignment {
|
||||
|
||||
using std::align;
|
||||
|
||||
} /* .alignment */
|
||||
} /* .boost */
|
||||
|
||||
#endif
|
|
@ -1,29 +0,0 @@
|
|||
/*
|
||||
(c) 2014 Glen Joseph Fernandes
|
||||
<glenjofe -at- gmail.com>
|
||||
|
||||
Distributed under the Boost Software
|
||||
License, Version 1.0.
|
||||
http://boost.org/LICENSE_1_0.txt
|
||||
*/
|
||||
#ifndef BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP
|
||||
#define BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP
|
||||
|
||||
#include <boost/config.hpp>
|
||||
#include <cstddef>
|
||||
|
||||
namespace boost {
|
||||
namespace alignment {
|
||||
namespace detail {
|
||||
|
||||
BOOST_CONSTEXPR inline bool is_alignment(std::size_t value)
|
||||
BOOST_NOEXCEPT
|
||||
{
|
||||
return (value > 0) && ((value & (value - 1)) == 0);
|
||||
}
|
||||
|
||||
} /* .detail */
|
||||
} /* .alignment */
|
||||
} /* .boost */
|
||||
|
||||
#endif
|
|
@ -12,11 +12,11 @@
|
|||
// with features contributed and bugs found by
|
||||
// Antony Polukhin, Ed Brey, Mark Rodgers,
|
||||
// Peter Dimov, and James Curran
|
||||
// when: July 2001, April 2013 - May 2013
|
||||
// when: July 2001, April 2013 - 2019
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "boost/config.hpp"
|
||||
#include <boost/config.hpp>
|
||||
#include <boost/type_index.hpp>
|
||||
#include <boost/type_traits/remove_reference.hpp>
|
||||
#include <boost/type_traits/decay.hpp>
|
||||
|
@ -27,9 +27,10 @@
|
|||
#include <boost/throw_exception.hpp>
|
||||
#include <boost/static_assert.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <boost/core/addressof.hpp>
|
||||
#include <boost/type_traits/is_same.hpp>
|
||||
#include <boost/type_traits/is_const.hpp>
|
||||
#include <boost/mpl/if.hpp>
|
||||
#include <boost/type_traits/conditional.hpp>
|
||||
|
||||
namespace boost
|
||||
{
|
||||
|
@ -108,7 +109,7 @@ namespace boost
|
|||
return *this;
|
||||
}
|
||||
|
||||
// move assignement
|
||||
// move assignment
|
||||
any & operator=(any&& rhs) BOOST_NOEXCEPT
|
||||
{
|
||||
rhs.swap(*this);
|
||||
|
@ -148,7 +149,7 @@ namespace boost
|
|||
public: // types (public so any_cast can be non-friend)
|
||||
#endif
|
||||
|
||||
class placeholder
|
||||
class BOOST_SYMBOL_VISIBLE placeholder
|
||||
{
|
||||
public: // structors
|
||||
|
||||
|
@ -244,7 +245,9 @@ namespace boost
|
|||
ValueType * any_cast(any * operand) BOOST_NOEXCEPT
|
||||
{
|
||||
return operand && operand->type() == boost::typeindex::type_id<ValueType>()
|
||||
? &static_cast<any::holder<BOOST_DEDUCED_TYPENAME remove_cv<ValueType>::type> *>(operand->content)->held
|
||||
? boost::addressof(
|
||||
static_cast<any::holder<BOOST_DEDUCED_TYPENAME remove_cv<ValueType>::type> *>(operand->content)->held
|
||||
)
|
||||
: 0;
|
||||
}
|
||||
|
||||
|
@ -260,7 +263,7 @@ namespace boost
|
|||
typedef BOOST_DEDUCED_TYPENAME remove_reference<ValueType>::type nonref;
|
||||
|
||||
|
||||
nonref * result = any_cast<nonref>(&operand);
|
||||
nonref * result = any_cast<nonref>(boost::addressof(operand));
|
||||
if(!result)
|
||||
boost::throw_exception(bad_any_cast());
|
||||
|
||||
|
@ -268,13 +271,20 @@ namespace boost
|
|||
// `ValueType` is not a reference. Example:
|
||||
// `static_cast<std::string>(*result);`
|
||||
// which is equal to `std::string(*result);`
|
||||
typedef BOOST_DEDUCED_TYPENAME boost::mpl::if_<
|
||||
boost::is_reference<ValueType>,
|
||||
typedef BOOST_DEDUCED_TYPENAME boost::conditional<
|
||||
boost::is_reference<ValueType>::value,
|
||||
ValueType,
|
||||
BOOST_DEDUCED_TYPENAME boost::add_reference<ValueType>::type
|
||||
>::type ref_type;
|
||||
|
||||
#ifdef BOOST_MSVC
|
||||
# pragma warning(push)
|
||||
# pragma warning(disable: 4172) // "returning address of local variable or temporary" but *result is not local!
|
||||
#endif
|
||||
return static_cast<ref_type>(*result);
|
||||
#ifdef BOOST_MSVC
|
||||
# pragma warning(pop)
|
||||
#endif
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
|
@ -306,7 +316,9 @@ namespace boost
|
|||
template<typename ValueType>
|
||||
inline ValueType * unsafe_any_cast(any * operand) BOOST_NOEXCEPT
|
||||
{
|
||||
return &static_cast<any::holder<ValueType> *>(operand->content)->held;
|
||||
return boost::addressof(
|
||||
static_cast<any::holder<ValueType> *>(operand->content)->held
|
||||
);
|
||||
}
|
||||
|
||||
template<typename ValueType>
|
||||
|
@ -317,6 +329,7 @@ namespace boost
|
|||
}
|
||||
|
||||
// Copyright Kevlin Henney, 2000, 2001, 2002. All rights reserved.
|
||||
// Copyright Antony Polukhin, 2013-2019.
|
||||
//
|
||||
// Distributed under the Boost Software License, Version 1.0. (See
|
||||
// accompanying file LICENSE_1_0.txt or copy at
|
||||
|
|
|
@ -127,11 +127,11 @@ public:
|
|||
}
|
||||
|
||||
// used for text output
|
||||
operator int () const {
|
||||
operator base_type () const {
|
||||
return t;
|
||||
}
|
||||
// used for text input
|
||||
operator int_least16_t &() {
|
||||
operator base_type &() {
|
||||
return t;
|
||||
}
|
||||
bool operator==(const class_id_type & rhs) const {
|
||||
|
@ -151,7 +151,10 @@ private:
|
|||
public:
|
||||
object_id_type(): t(0) {};
|
||||
// note: presumes that size_t >= unsigned int.
|
||||
explicit object_id_type(const std::size_t & t_) : t(t_){
|
||||
// use explicit cast to silence useless warning
|
||||
explicit object_id_type(const std::size_t & t_) : t(static_cast<base_type>(t_)){
|
||||
// make quadriple sure that we haven't lost any real integer
|
||||
// precision
|
||||
BOOST_ASSERT(t_ <= boost::integer_traits<base_type>::const_max);
|
||||
}
|
||||
object_id_type(const object_id_type & t_) :
|
||||
|
@ -162,11 +165,11 @@ public:
|
|||
return *this;
|
||||
}
|
||||
// used for text output
|
||||
operator uint_least32_t () const {
|
||||
operator base_type () const {
|
||||
return t;
|
||||
}
|
||||
// used for text input
|
||||
operator uint_least32_t & () {
|
||||
operator base_type & () {
|
||||
return t;
|
||||
}
|
||||
bool operator==(const object_id_type & rhs) const {
|
||||
|
|
|
@ -102,17 +102,29 @@ protected:
|
|||
}
|
||||
void load_override(class_id_type & t){
|
||||
library_version_type lvt = this->get_library_version();
|
||||
/*
|
||||
* library versions:
|
||||
* boost 1.39 -> 5
|
||||
* boost 1.43 -> 7
|
||||
* boost 1.47 -> 9
|
||||
*
|
||||
*
|
||||
* 1) in boost 1.43 and inferior, class_id_type is always a 16bit value, with no check on the library version
|
||||
* --> this means all archives with version v <= 7 are written with a 16bit class_id_type
|
||||
* 2) in boost 1.44 this load_override has disappeared (and thus boost 1.44 is not backward compatible at all !!)
|
||||
* 3) recent boosts reintroduced load_override with a test on the version :
|
||||
* - v > 7 : this->detail_common_iarchive::load_override(t, version)
|
||||
* - v > 6 : 16bit
|
||||
* - other : 32bit
|
||||
* --> which is obviously incorrect, see point 1
|
||||
*
|
||||
* the fix here decodes class_id_type on 16bit for all v <= 7, which seems to be the correct behaviour ...
|
||||
*/
|
||||
if(boost::archive::library_version_type(7) < lvt){
|
||||
this->detail_common_iarchive::load_override(t);
|
||||
}
|
||||
else
|
||||
if(boost::archive::library_version_type(6) < lvt){
|
||||
int_least16_t x=0;
|
||||
* this->This() >> x;
|
||||
t = boost::archive::class_id_type(x);
|
||||
}
|
||||
else{
|
||||
int x=0;
|
||||
int_least16_t x=0;
|
||||
* this->This() >> x;
|
||||
t = boost::archive::class_id_type(x);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ namespace std{
|
|||
|
||||
//#include <boost/mpl/placeholders.hpp>
|
||||
#include <boost/serialization/is_bitwise_serializable.hpp>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/array_wrapper.hpp>
|
||||
|
||||
#include <boost/archive/basic_streambuf_locale_saver.hpp>
|
||||
#include <boost/archive/codecvt_null.hpp>
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace std{
|
|||
|
||||
//#include <boost/mpl/placeholders.hpp>
|
||||
#include <boost/serialization/is_bitwise_serializable.hpp>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/array_wrapper.hpp>
|
||||
|
||||
#include <boost/archive/basic_streambuf_locale_saver.hpp>
|
||||
#include <boost/archive/codecvt_null.hpp>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue