Merge branch 'release-2.3.0'

This commit is contained in:
Bill Somerville 2020-09-26 21:51:46 +01:00
commit 450f44c9ab
No known key found for this signature in database
GPG Key ID: D864B06D1E81618F
169 changed files with 31280 additions and 13674 deletions

11
.gitignore vendored
View File

@ -1,6 +1,9 @@
~*
TAGS
tags
GPATH
GRTAGS
GTAGS
*~
junk*
jnq*
@ -9,5 +12,13 @@ jnq*
*.mod
*.pro.user
*.txt
*.bak
!**/CMakeLists.txt
__pycache__
cmake-build-debug
cmake-build-release
CMakeFiles
fnd
lib/77bit/tmp
lib/tmp
lib/ftrsd

5
Audio/Audio.pri Normal file
View File

@ -0,0 +1,5 @@
SOURCES += Audio/AudioDevice.cpp Audio/BWFFile.cpp Audio/soundin.cpp \
Audio/soundout.cpp
HEADERS += Audio/AudioDevice.hpp Audio/BWFFile.hpp Audio/soundin.h \
Audio/soundout.h

View File

@ -7,4 +7,3 @@ bool AudioDevice::initialize (OpenMode mode, Channel channel)
// open and ensure we are unbuffered if possible
return QIODevice::open (mode | QIODevice::Unbuffered);
}

View File

@ -33,8 +33,8 @@ public:
Channel channel () const {return m_channel;}
protected:
AudioDevice (QObject * parent = 0)
: QIODevice (parent)
AudioDevice (QObject * parent = nullptr)
: QIODevice {parent}
{
}
@ -43,23 +43,23 @@ protected:
qint16 const * begin (reinterpret_cast<qint16 const *> (source));
for ( qint16 const * i = begin; i != begin + numFrames * (bytesPerFrame () / sizeof (qint16)); i += bytesPerFrame () / sizeof (qint16))
{
switch (m_channel)
{
case Mono:
*dest++ = *i;
break;
switch (m_channel)
{
case Mono:
*dest++ = *i;
break;
case Right:
*dest++ = *(i + 1);
break;
case Right:
*dest++ = *(i + 1);
break;
case Both: // should be able to happen but if it
// does we'll take left
Q_ASSERT (Both == m_channel);
case Left:
*dest++ = *i;
break;
}
case Both: // should be able to happen but if it
// does we'll take left
Q_ASSERT (Both == m_channel);
case Left:
*dest++ = *i;
break;
}
}
}
@ -68,23 +68,23 @@ protected:
switch (m_channel)
{
case Mono:
*dest++ = sample;
break;
*dest++ = sample;
break;
case Left:
*dest++ = sample;
*dest++ = 0;
break;
*dest++ = sample;
*dest++ = 0;
break;
case Right:
*dest++ = 0;
*dest++ = sample;
break;
*dest++ = 0;
*dest++ = sample;
break;
case Both:
*dest++ = sample;
*dest++ = sample;
break;
*dest++ = sample;
*dest++ = sample;
break;
}
return dest;
}

View File

@ -1,5 +1,7 @@
#include "soundin.h"
#include <cstdlib>
#include <cmath>
#include <QAudioDeviceInfo>
#include <QAudioFormat>
#include <QAudioInput>
@ -8,11 +10,9 @@
#include "moc_soundin.cpp"
bool SoundInput::audioError () const
bool SoundInput::checkStream ()
{
bool result (true);
Q_ASSERT_X (m_stream, "SoundInput", "programming error");
bool result (false);
if (m_stream)
{
switch (m_stream->error ())
@ -34,14 +34,15 @@ bool SoundInput::audioError () const
break;
case QAudio::NoError:
result = false;
result = true;
break;
}
}
return result;
}
void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, AudioDevice * sink, unsigned downSampleFactor, AudioDevice::Channel channel)
void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, AudioDevice * sink
, unsigned downSampleFactor, AudioDevice::Channel channel)
{
Q_ASSERT (sink);
@ -62,28 +63,37 @@ void SoundInput::start(QAudioDeviceInfo const& device, int framesPerBuffer, Audi
Q_EMIT error (tr ("Requested input audio format is not valid."));
return;
}
if (!device.isFormatSupported (format))
else if (!device.isFormatSupported (format))
{
// qDebug () << "Nearest supported audio format:" << device.nearestFormat (format);
Q_EMIT error (tr ("Requested input audio format is not supported on device."));
return;
}
// qDebug () << "Selected audio input format:" << format;
// qDebug () << "Selected audio input format:" << format;
m_stream.reset (new QAudioInput {device, format});
if (audioError ())
if (!checkStream ())
{
return;
}
connect (m_stream.data(), &QAudioInput::stateChanged, this, &SoundInput::handleStateChanged);
connect (m_stream.data(), &QAudioInput::notify, [this] () {checkStream ();});
m_stream->setBufferSize (m_stream->format ().bytesForFrames (framesPerBuffer));
if (sink->initialize (QIODevice::WriteOnly, channel))
//qDebug () << "SoundIn default buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
// the Windows MME version of QAudioInput uses 1/5 of the buffer
// size for period size other platforms seem to optimize themselves
#if defined (Q_OS_WIN)
m_stream->setBufferSize (m_stream->format ().bytesForFrames (framesPerBuffer * 5));
#else
Q_UNUSED (framesPerBuffer);
#endif
if (m_sink->initialize (QIODevice::WriteOnly, channel))
{
m_stream->start (sink);
audioError ();
checkStream ();
cummulative_lost_usec_ = -1;
//qDebug () << "SoundIn selected buffer size (bytes):" << m_stream->bufferSize () << "peirod size:" << m_stream->periodSize ();
}
else
{
@ -96,7 +106,7 @@ void SoundInput::suspend ()
if (m_stream)
{
m_stream->suspend ();
audioError ();
checkStream ();
}
}
@ -111,14 +121,12 @@ void SoundInput::resume ()
if (m_stream)
{
m_stream->resume ();
audioError ();
checkStream ();
}
}
void SoundInput::handleStateChanged (QAudio::State newState) const
void SoundInput::handleStateChanged (QAudio::State newState)
{
// qDebug () << "SoundInput::handleStateChanged: newState:" << newState;
switch (newState)
{
case QAudio::IdleState:
@ -126,6 +134,7 @@ void SoundInput::handleStateChanged (QAudio::State newState) const
break;
case QAudio::ActiveState:
reset (false);
Q_EMIT status (tr ("Receiving"));
break;
@ -140,7 +149,7 @@ void SoundInput::handleStateChanged (QAudio::State newState) const
#endif
case QAudio::StoppedState:
if (audioError ())
if (!checkStream ())
{
Q_EMIT status (tr ("Error"));
}
@ -152,6 +161,28 @@ void SoundInput::handleStateChanged (QAudio::State newState) const
}
}
void SoundInput::reset (bool report_dropped_frames)
{
if (m_stream)
{
auto elapsed_usecs = m_stream->elapsedUSecs ();
while (std::abs (elapsed_usecs - m_stream->processedUSecs ())
> 24 * 60 * 60 * 500000ll) // half day
{
// QAudioInput::elapsedUSecs() wraps after 24 hours
elapsed_usecs += 24 * 60 * 60 * 1000000ll;
}
// don't report first time as we don't yet known latency
if (cummulative_lost_usec_ != std::numeric_limits<qint64>::min () && report_dropped_frames)
{
auto lost_usec = elapsed_usecs - m_stream->processedUSecs () - cummulative_lost_usec_;
Q_EMIT dropped_frames (m_stream->format ().framesForDuration (lost_usec), lost_usec);
//qDebug () << "SoundInput::reset: frames dropped:" << m_stream->format ().framesForDuration (lost_usec) << "sec:" << lost_usec / 1.e6;
}
cummulative_lost_usec_ = elapsed_usecs - m_stream->processedUSecs ();
}
}
void SoundInput::stop()
{
if (m_stream)
@ -159,11 +190,6 @@ void SoundInput::stop()
m_stream->stop ();
}
m_stream.reset ();
if (m_sink)
{
m_sink->close ();
}
}
SoundInput::~SoundInput ()

View File

@ -2,6 +2,7 @@
#ifndef SOUNDIN_H__
#define SOUNDIN_H__
#include <limits>
#include <QObject>
#include <QString>
#include <QDateTime>
@ -23,7 +24,7 @@ class SoundInput
public:
SoundInput (QObject * parent = nullptr)
: QObject {parent}
, m_sink {nullptr}
, cummulative_lost_usec_ {std::numeric_limits<qint64>::min ()}
{
}
@ -35,18 +36,21 @@ public:
Q_SLOT void suspend ();
Q_SLOT void resume ();
Q_SLOT void stop ();
Q_SLOT void reset (bool report_dropped_frames);
Q_SIGNAL void error (QString message) const;
Q_SIGNAL void status (QString message) const;
Q_SIGNAL void dropped_frames (qint32 dropped, qint64 usec);
private:
// used internally
Q_SLOT void handleStateChanged (QAudio::State) const;
Q_SLOT void handleStateChanged (QAudio::State);
bool audioError () const;
bool checkStream ();
QScopedPointer<QAudioInput> m_stream;
QPointer<AudioDevice> m_sink;
qint64 cummulative_lost_usec_;
};
#endif

View File

@ -7,20 +7,13 @@
#include <qmath.h>
#include <QDebug>
#include "Audio/AudioDevice.hpp"
#include "moc_soundout.cpp"
/*
#if defined (WIN32)
# define MS_BUFFERED 1000u
#else
# define MS_BUFFERED 2000u
#endif
*/
# define MS_BUFFERED 200u
bool SoundOutput::audioError () const
bool SoundOutput::checkStream () const
{
bool result (true);
bool result {false};
Q_ASSERT_X (m_stream, "SoundOutput", "programming error");
if (m_stream) {
@ -43,69 +36,83 @@ bool SoundOutput::audioError () const
break;
case QAudio::NoError:
result = false;
result = true;
break;
}
}
return result;
}
void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered)
void SoundOutput::setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered)
{
Q_ASSERT (0 < channels && channels < 3);
m_msBuffered = msBuffered;
QAudioFormat format (device.preferredFormat ());
// qDebug () << "Preferred audio output format:" << format;
format.setChannelCount (channels);
format.setCodec ("audio/pcm");
format.setSampleRate (48000);
format.setSampleType (QAudioFormat::SignedInt);
format.setSampleSize (16);
format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
if (!format.isValid ())
{
Q_EMIT error (tr ("Requested output audio format is not valid."));
}
if (!device.isFormatSupported (format))
{
Q_EMIT error (tr ("Requested output audio format is not supported on device."));
}
// qDebug () << "Selected audio output format:" << format;
m_stream.reset (new QAudioOutput (device, format));
audioError ();
m_stream->setVolume (m_volume);
m_stream->setNotifyInterval(100);
connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged);
// qDebug() << "A" << m_volume << m_stream->notifyInterval();
m_device = device;
m_channels = channels;
m_framesBuffered = frames_buffered;
}
void SoundOutput::restart (QIODevice * source)
{
Q_ASSERT (m_stream);
if (!m_device.isNull ())
{
QAudioFormat format (m_device.preferredFormat ());
// qDebug () << "Preferred audio output format:" << format;
format.setChannelCount (m_channels);
format.setCodec ("audio/pcm");
format.setSampleRate (48000);
format.setSampleType (QAudioFormat::SignedInt);
format.setSampleSize (16);
format.setByteOrder (QAudioFormat::Endian (QSysInfo::ByteOrder));
if (!format.isValid ())
{
Q_EMIT error (tr ("Requested output audio format is not valid."));
}
else if (!m_device.isFormatSupported (format))
{
Q_EMIT error (tr ("Requested output audio format is not supported on device."));
}
else
{
// qDebug () << "Selected audio output format:" << format;
m_stream.reset (new QAudioOutput (m_device, format));
checkStream ();
m_stream->setVolume (m_volume);
m_stream->setNotifyInterval(1000);
error_ = false;
connect (m_stream.data(), &QAudioOutput::stateChanged, this, &SoundOutput::handleStateChanged);
connect (m_stream.data(), &QAudioOutput::notify, [this] () {checkStream ();});
// qDebug() << "A" << m_volume << m_stream->notifyInterval();
}
}
if (!m_stream)
{
if (!error_)
{
error_ = true; // only signal error once
Q_EMIT error (tr ("No audio output device configured."));
}
return;
}
else
{
error_ = false;
}
//
// This buffer size is critical since for proper sound streaming. If
// it is too short; high activity levels on the machine can starve
// the audio buffer. On the other hand the Windows implementation
// seems to take the length of the buffer in time to stop the audio
// stream even if reset() is used.
//
// 2 seconds seems a reasonable compromise except for Windows
// where things are probably broken.
//
// we have to set this before every start on the stream because the
// Windows implementation seems to forget the buffer size after a
// stop.
m_stream->setBufferSize (m_stream->format().bytesForDuration((m_msBuffered ? m_msBuffered : MS_BUFFERED) * 1000));
// qDebug() << "B" << m_stream->bufferSize() <<
// m_stream->periodSize() << m_stream->notifyInterval();
//qDebug () << "SoundOut default buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
if (m_framesBuffered)
{
#if defined (Q_OS_WIN)
m_stream->setBufferSize (m_stream->format().bytesForFrames (m_framesBuffered));
#endif
}
m_stream->setCategory ("production");
m_stream->start (source);
// qDebug () << "SoundOut selected buffer size (bytes):" << m_stream->bufferSize () << "period size:" << m_stream->periodSize ();
}
void SoundOutput::suspend ()
@ -113,7 +120,7 @@ void SoundOutput::suspend ()
if (m_stream && QAudio::ActiveState == m_stream->state ())
{
m_stream->suspend ();
audioError ();
checkStream ();
}
}
@ -122,7 +129,7 @@ void SoundOutput::resume ()
if (m_stream && QAudio::SuspendedState == m_stream->state ())
{
m_stream->resume ();
audioError ();
checkStream ();
}
}
@ -131,7 +138,7 @@ void SoundOutput::reset ()
if (m_stream)
{
m_stream->reset ();
audioError ();
checkStream ();
}
}
@ -139,9 +146,10 @@ void SoundOutput::stop ()
{
if (m_stream)
{
m_stream->reset ();
m_stream->stop ();
audioError ();
}
m_stream.reset ();
}
qreal SoundOutput::attenuation () const
@ -171,8 +179,6 @@ void SoundOutput::resetAttenuation ()
void SoundOutput::handleStateChanged (QAudio::State newState)
{
// qDebug () << "SoundOutput::handleStateChanged: newState:" << newState;
switch (newState)
{
case QAudio::IdleState:
@ -194,7 +200,7 @@ void SoundOutput::handleStateChanged (QAudio::State newState)
#endif
case QAudio::StoppedState:
if (audioError ())
if (!checkStream ())
{
Q_EMIT status (tr ("Error"));
}

View File

@ -7,6 +7,7 @@
#include <QAudioOutput>
#include <QAudioDeviceInfo>
class QIODevice;
class QAudioDeviceInfo;
// An instance of this sends audio data to a specified soundcard.
@ -18,15 +19,16 @@ class SoundOutput
public:
SoundOutput ()
: m_msBuffered {0u}
: m_framesBuffered {0}
, m_volume {1.0}
, error_ {false}
{
}
qreal attenuation () const;
public Q_SLOTS:
void setFormat (QAudioDeviceInfo const& device, unsigned channels, unsigned msBuffered = 0u);
void setFormat (QAudioDeviceInfo const& device, unsigned channels, int frames_buffered = 0);
void restart (QIODevice *);
void suspend ();
void resume ();
@ -40,15 +42,18 @@ Q_SIGNALS:
void status (QString message) const;
private:
bool audioError () const;
bool checkStream () const;
private Q_SLOTS:
void handleStateChanged (QAudio::State);
private:
QAudioDeviceInfo m_device;
unsigned m_channels;
QScopedPointer<QAudioOutput> m_stream;
unsigned m_msBuffered;
int m_framesBuffered;
qreal m_volume;
bool error_;
};
#endif

View File

@ -34,6 +34,12 @@
#
# $Id: FindFFTW3.cmake 15918 2010-06-25 11:12:42Z loose $
# Compatibily with old style MinGW packages with no .dll.a files
# needed since CMake v3.17 because of fix for #20019
if (MINGW)
set (CMAKE_FIND_LIBRARY_SUFFIXES ".dll" ".dll.a" ".a" ".lib")
endif ()
# Use double precision by default.
if (FFTW3_FIND_COMPONENTS MATCHES "^$")
set (_components double)

View File

@ -222,9 +222,12 @@ set (WSJT_QT_CONF_DESTINATION ${QT_CONF_DESTINATION} CACHE PATH "Path for the qt
#
# Project sources
#
set (fort_qt_CXXSRCS
lib/shmem.cpp
)
set (wsjt_qt_CXXSRCS
qt_helpers.cpp
lib/shmem.cpp
widgets/MessageBox.cpp
MetaDataRegistry.cpp
Network/NetworkServerLookup.cpp
@ -289,6 +292,8 @@ set (wsjt_qt_CXXSRCS
logbook/AD1CCty.cpp
logbook/WorkedBefore.cpp
logbook/Multiplier.cpp
Network/NetworkAccessManager.cpp
widgets/LazyFillComboBox.cpp
)
set (wsjt_qtmm_CXXSRCS
@ -302,7 +307,7 @@ set (jt9_FSRCS
set (wsjtx_CXXSRCS
logbook/logbook.cpp
Network/psk_reporter.cpp
Network/PSKReporter.cpp
Modulator/Modulator.cpp
Detector/Detector.cpp
widgets/logqso.cpp
@ -358,6 +363,8 @@ endif (WIN32)
set (wsjt_FSRCS
# put module sources first in the hope that they get rebuilt before use
lib/types.f90
lib/C_interface_module.f90
lib/shmem.f90
lib/crc.f90
lib/fftw3mod.f90
@ -369,6 +376,7 @@ set (wsjt_FSRCS
lib/jt65_mod.f90
lib/ft8_decode.f90
lib/ft4_decode.f90
lib/fst4_decode.f90
lib/jt9_decode.f90
lib/options.f90
lib/packjt.f90
@ -395,6 +403,7 @@ set (wsjt_FSRCS
lib/badmsg.f90
lib/ft8/baseline.f90
lib/ft4/ft4_baseline.f90
lib/blanker.f90
lib/bpdecode40.f90
lib/bpdecode128_90.f90
lib/ft8/bpdecode174_91.f90
@ -544,6 +553,7 @@ set (wsjt_FSRCS
lib/qra64a.f90
lib/refspectrum.f90
lib/savec2.f90
lib/sec0.f90
lib/sec_midn.f90
lib/setup65.f90
lib/sh65.f90
@ -595,6 +605,21 @@ set (wsjt_FSRCS
lib/wqencode.f90
lib/wspr_downsample.f90
lib/zplot9.f90
lib/fst4/decode240_101.f90
lib/fst4/decode240_74.f90
lib/fst4/encode240_101.f90
lib/fst4/encode240_74.f90
lib/fst4/fst4sim.f90
lib/fst4/gen_fst4wave.f90
lib/fst4/genfst4.f90
lib/fst4/get_fst4_bitmetrics.f90
lib/fst4/get_fst4_bitmetrics2.f90
lib/fst4/ldpcsim240_101.f90
lib/fst4/ldpcsim240_74.f90
lib/fst4/osd240_101.f90
lib/fst4/osd240_74.f90
lib/fst4/get_crc24.f90
lib/fst4/fst4_baseline.f90
)
# temporary workaround for a gfortran v7.3 ICE on Fedora 27 64-bit
@ -710,6 +735,7 @@ set (qcp_CXXSRCS
set (all_CXXSRCS
${wsjt_CXXSRCS}
${fort_qt_CXXSRCS}
${wsjt_qt_CXXSRCS}
${wsjt_qtmm_CXXSRCS}
${wsjtx_CXXSRCS}
@ -724,10 +750,6 @@ set (all_C_and_CXXSRCS
)
set (TOP_LEVEL_RESOURCES
shortcuts.txt
mouse_commands.txt
prefixes.txt
cty.dat
icons/Darwin/wsjtx.iconset/icon_128x128.png
contrib/gpl-v3-logo.svg
artwork/splash.png
@ -846,6 +868,8 @@ endif (APPLE)
#
# find some useful tools
#
include (CheckSymbolExists)
find_program(CTAGS ctags)
find_program(ETAGS etags)
@ -866,7 +890,7 @@ find_package (OpenMP)
#
# fftw3 single precision library
#
find_package (FFTW3 COMPONENTS double single threads REQUIRED)
find_package (FFTW3 COMPONENTS single threads REQUIRED)
#
# libhamlib setup
@ -881,6 +905,10 @@ message (STATUS "hamlib_INCLUDE_DIRS: ${hamlib_INCLUDE_DIRS}")
message (STATUS "hamlib_LIBRARIES: ${hamlib_LIBRARIES}")
message (STATUS "hamlib_LIBRARY_DIRS: ${hamlib_LIBRARY_DIRS}")
set (CMAKE_REQUIRED_INCLUDES "${hamlib_INCLUDE_DIRS}")
set (CMAKE_REQUIRED_LIBRARIES "${hamlib_LIBRARIES}")
check_symbol_exists (rig_set_cache_timeout_ms "hamlib/rig.h" HAVE_HAMLIB_CACHING)
#
# Qt5 setup
@ -903,7 +931,9 @@ endif ()
if (WSJT_GENERATE_DOCS)
add_subdirectory (doc)
endif (WSJT_GENERATE_DOCS)
if (EXISTS ${CMAKE_SOURCE_DIR}/tests AND IS_DIRECTORY ${CMAKE_SOURCE_DIR}/tests)
add_subdirectory (tests)
endif ()
#
# Library building setup
@ -1097,14 +1127,19 @@ add_custom_target (etags COMMAND ${ETAGS} -o ${CMAKE_SOURCE_DIR}/TAGS -R ${sourc
# Qt i18n - always include the country generic if any regional variant is included
set (LANGUAGES
ca # Catalan
da # Danish
en # English (we need this to stop
# translation loaders loading the
# second preference UI languge, it
# doesn't need to be populated)
en_GB # English UK
es # Spanish
ca # Catalan
it # Italian
ja # Japanese
#no # Norwegian
#pt # Portuguese
#sv # Swedish
zh # Chinese
zh_HK # Chinese per Hong Kong
it # Italian
@ -1127,6 +1162,7 @@ if (UPDATE_TRANSLATIONS)
qt5_create_translation (
QM_FILES ${wsjt_qt_UISRCS} ${wsjtx_UISRCS} ${wsjt_qt_CXXSRCS} ${wsjtx_CXXSRCS}
${TS_FILES}
OPTIONS -I${CMAKE_CURRENT_SOURCE_DIR}
)
else ()
qt5_add_translation (QM_FILES ${TS_FILES})
@ -1227,6 +1263,11 @@ if (WIN32)
target_link_libraries (wsjt_qt Qt5::AxContainer Qt5::AxBase)
endif (WIN32)
# build a library of package Qt functionality used in Fortran utilities
add_library (fort_qt STATIC ${fort_qt_CXXSRCS})
target_link_libraries (fort_qt Qt5::Core)
# build a library of WSJT Qt multimedia components
add_library (wsjt_qtmm STATIC ${wsjt_qtmm_CXXSRCS} ${wsjt_qtmm_GENUISRCS})
target_link_libraries (wsjt_qtmm Qt5::Multimedia)
@ -1274,9 +1315,9 @@ if (${OPENMP_FOUND} OR APPLE)
LINK_FLAGS -Wl,--stack,16777216
)
endif ()
target_link_libraries (jt9 wsjt_fort_omp wsjt_cxx wsjt_qt)
target_link_libraries (jt9 wsjt_fort_omp wsjt_cxx fort_qt)
else (${OPENMP_FOUND} OR APPLE)
target_link_libraries (jt9 wsjt_fort wsjt_cxx Qt5::Core)
target_link_libraries (jt9 wsjt_fort wsjt_cxx fort_qt)
endif (${OPENMP_FOUND} OR APPLE)
if(WSJT_BUILD_UTILS)
@ -1346,6 +1387,15 @@ target_link_libraries (ft4sim_mult wsjt_fort wsjt_cxx)
add_executable (record_time_signal Audio/tools/record_time_signal.cpp)
target_link_libraries (record_time_signal wsjt_cxx wsjt_qtmm wsjt_qt)
add_executable (fst4sim lib/fst4/fst4sim.f90 wsjtx.rc)
target_link_libraries (fst4sim wsjt_fort wsjt_cxx)
add_executable (ldpcsim240_101 lib/fst4/ldpcsim240_101.f90 wsjtx.rc)
target_link_libraries (ldpcsim240_101 wsjt_fort wsjt_cxx)
add_executable (ldpcsim240_74 lib/fst4/ldpcsim240_74.f90 wsjtx.rc)
target_link_libraries (ldpcsim240_74 wsjt_fort wsjt_cxx)
endif(WSJT_BUILD_UTILS)
# build the main application
@ -1485,7 +1535,7 @@ install (TARGETS jt9 wsprd fmtave fcal fmeasure
if(WSJT_BUILD_UTILS)
install (TARGETS ft8code jt65code qra64code qra64sim jt9code jt4code
msk144code
msk144code fst4sim
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT runtime
)
@ -1525,6 +1575,7 @@ install (FILES
)
install (FILES
cty.dat
contrib/Ephemeris/JPLEPH
DESTINATION ${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}
#COMPONENT runtime
@ -1805,11 +1856,11 @@ endif ()
set (CPACK_DEBIAN_PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}")
set (CPACK_DEBIAN_PACKAGE_HOMEPAGE "${PROJECT_HOMEPAGE}")
set (CPACK_DEBIAN_PACKAGE_DEPENDS "libgfortran3 (>=4.8.2), libqt5serialport5 (>=5.5), libqt5multimedia5-plugins (>=5.5), libqt5widgets5 (>=5.5), libqt5sql5-sqlite (>=5.5), libusb-1.0-0, libudev1, libc6 (>=2.19)")
set (CPACK_DEBIAN_PACKAGE_DEPENDS "libgfortran5 (>=10), libfftw3-single3 (>=3.3.8), libgomp1 (>=10), libqt5serialport5 (>=5.12.8), libqt5multimedia5-plugins (>=5.12.8), libqt5widgets5 (>=5.12.8), libqt5network5 (>=5.12.8), libqt5printsupport5 (>=5.12.8), libqt5sql5-sqlite (>=5.12.8), libusb-1.0-0 (>=1.0.23)")
set (CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set (CPACK_RPM_PACKAGE_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
set (CPACK_RPM_PACKAGE_REQUIRES "qt5-qtserialport >= 5.5, qt5-qtmultimedia >= 5.5, qt5-qtsvg, libusb, systemd-udev, glibc >= 2, libgfortran >= 4.8.2")
set (CPACK_RPM_PACKAGE_REQUIRES "qt5-qtbase >= 5.13.2, qt5-qtserialport >= 5.13.2, qt5-qtmultimedia >= 5.13.2, qt5-qtsvg >= 5.13.2, libusbx >= 1.0.23, libgfortran >= 10.0.1, libgomp >= 10.0.1, fftw-libs-single >= 3.3.8")
set (CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION /usr/share/pixmaps /usr/share/applications /usr/share/man /usr/share/man1)
configure_file ("${PROJECT_SOURCE_DIR}/CMakeCPackOptions.cmake.in"

View File

@ -135,8 +135,11 @@
#include <cmath>
#include <QApplication>
#include <QCursor>
#include <QMetaType>
#include <QList>
#include <QPair>
#include <QVariant>
#include <QSettings>
#include <QAudioDeviceInfo>
#include <QAudioInput>
@ -186,6 +189,7 @@
#include "Network/LotWUsers.hpp"
#include "models/DecodeHighlightingModel.hpp"
#include "logbook/logbook.h"
#include "widgets/LazyFillComboBox.hpp"
#include "ui_Configuration.h"
#include "moc_Configuration.cpp"
@ -401,6 +405,7 @@ class Configuration::impl final
public:
using FrequencyDelta = Radio::FrequencyDelta;
using port_type = Configuration::port_type;
using audio_info_type = QPair<QAudioDeviceInfo, QList<QVariant> >;
explicit impl (Configuration * self
, QNetworkAccessManager * network_manager
@ -429,9 +434,13 @@ private:
void read_settings ();
void write_settings ();
bool load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
void find_audio_devices ();
QAudioDeviceInfo find_audio_device (QAudio::Mode, QComboBox *, QString const& device_name);
void load_audio_devices (QAudio::Mode, QComboBox *, QAudioDeviceInfo *);
void update_audio_channels (QComboBox const *, int, QComboBox *, bool);
void find_tab (QWidget *);
void initialize_models ();
bool split_mode () const
{
@ -477,8 +486,6 @@ private:
Q_SLOT void on_force_DTR_combo_box_currentIndexChanged (int);
Q_SLOT void on_force_RTS_combo_box_currentIndexChanged (int);
Q_SLOT void on_rig_combo_box_currentIndexChanged (int);
Q_SLOT void on_sound_input_combo_box_currentTextChanged (QString const&);
Q_SLOT void on_sound_output_combo_box_currentTextChanged (QString const&);
Q_SLOT void on_add_macro_push_button_clicked (bool = false);
Q_SLOT void on_delete_macro_push_button_clicked (bool = false);
Q_SLOT void on_PTT_method_button_group_buttonClicked (int);
@ -604,6 +611,7 @@ private:
bool id_after_73_;
bool tx_QSY_allowed_;
bool spot_to_psk_reporter_;
bool psk_reporter_tcpip_;
bool monitor_off_at_startup_;
bool monitor_last_used_;
bool log_as_RTTY_;
@ -646,11 +654,13 @@ private:
bool pwrBandTuneMemory_;
QAudioDeviceInfo audio_input_device_;
bool default_audio_input_device_selected_;
QAudioDeviceInfo next_audio_input_device_;
AudioDevice::Channel audio_input_channel_;
AudioDevice::Channel next_audio_input_channel_;
QAudioDeviceInfo audio_output_device_;
bool default_audio_output_device_selected_;
QAudioDeviceInfo next_audio_output_device_;
AudioDevice::Channel audio_output_channel_;
AudioDevice::Channel next_audio_output_channel_;
friend class Configuration;
};
@ -701,6 +711,7 @@ bool Configuration::spot_to_psk_reporter () const
// rig must be open and working to spot externally
return is_transceiver_online () && m_->spot_to_psk_reporter_;
}
bool Configuration::psk_reporter_tcpip () const {return m_->psk_reporter_tcpip_;}
bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;}
bool Configuration::monitor_last_used () const {return m_->rig_is_dummy_ || m_->monitor_last_used_;}
bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;}
@ -850,6 +861,16 @@ void Configuration::sync_transceiver (bool force_signal, bool enforce_mode_and_s
}
}
void Configuration::invalidate_audio_input_device (QString /* error */)
{
m_->audio_input_device_ = QAudioDeviceInfo {};
}
void Configuration::invalidate_audio_output_device (QString /* error */)
{
m_->audio_output_device_ = QAudioDeviceInfo {};
}
bool Configuration::valid_n1mm_info () const
{
// do very rudimentary checking on the n1mm server name and port number.
@ -895,7 +916,7 @@ auto Configuration::special_op_id () const -> SpecialOperatingActivity
void Configuration::set_location (QString const& grid_descriptor)
{
// change the dynamic grid
qDebug () << "Configuration::set_location - location:" << grid_descriptor;
// qDebug () << "Configuration::set_location - location:" << grid_descriptor;
m_->dynamic_grid_ = grid_descriptor.trimmed ();
}
@ -974,8 +995,6 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
, transceiver_command_number_ {0}
, degrade_ {0.} // initialize to zero each run, not
// saved in settings
, default_audio_input_device_selected_ {false}
, default_audio_output_device_selected_ {false}
{
ui_->setupUi (this);
@ -1025,6 +1044,21 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
// this must be done after the default paths above are set
read_settings ();
connect (ui_->sound_input_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &next_audio_input_device_);
update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false);
ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_);
QGuiApplication::restoreOverrideCursor ();
});
connect (ui_->sound_output_combo_box, &LazyFillComboBox::about_to_show_popup, [this] () {
QGuiApplication::setOverrideCursor (QCursor {Qt::WaitCursor});
load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &next_audio_output_device_);
update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true);
ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_);
QGuiApplication::restoreOverrideCursor ();
});
// set up LoTW users CSV file fetching
connect (&lotw_users_, &LotWUsers::load_finished, [this] () {
ui_->LotW_CSV_fetch_push_button->setEnabled (true);
@ -1128,7 +1162,9 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
ui_->frequencies_table_view->setModel (&next_frequencies_);
ui_->frequencies_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents);
ui_->frequencies_table_view->horizontalHeader ()->setResizeContentsPrecision (0);
ui_->frequencies_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents);
ui_->frequencies_table_view->verticalHeader ()->setResizeContentsPrecision (0);
ui_->frequencies_table_view->sortByColumn (FrequencyList_v2::frequency_column, Qt::AscendingOrder);
ui_->frequencies_table_view->setColumnHidden (FrequencyList_v2::frequency_mhz_column, true);
@ -1168,7 +1204,9 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
stations_.sort (StationList::band_column);
ui_->stations_table_view->setModel (&next_stations_);
ui_->stations_table_view->horizontalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents);
ui_->stations_table_view->horizontalHeader ()->setResizeContentsPrecision (0);
ui_->stations_table_view->verticalHeader ()->setSectionResizeMode (QHeaderView::ResizeToContents);
ui_->stations_table_view->verticalHeader ()->setResizeContentsPrecision (0);
ui_->stations_table_view->sortByColumn (StationList::band_column, Qt::AscendingOrder);
// stations delegates
@ -1187,21 +1225,14 @@ Configuration::impl::impl (Configuration * self, QNetworkAccessManager * network
//
ui_->highlighting_list_view->setModel (&next_decode_highlighing_model_);
//
// load combo boxes with audio setup choices
//
default_audio_input_device_selected_ = load_audio_devices (QAudio::AudioInput, ui_->sound_input_combo_box, &audio_input_device_);
default_audio_output_device_selected_ = load_audio_devices (QAudio::AudioOutput, ui_->sound_output_combo_box, &audio_output_device_);
update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false);
update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true);
ui_->sound_input_channel_combo_box->setCurrentIndex (audio_input_channel_);
ui_->sound_output_channel_combo_box->setCurrentIndex (audio_output_channel_);
enumerate_rigs ();
initialize_models ();
audio_input_device_ = next_audio_input_device_;
audio_input_channel_ = next_audio_input_channel_;
audio_output_device_ = next_audio_output_device_;
audio_output_channel_ = next_audio_output_channel_;
transceiver_thread_ = new QThread {this};
transceiver_thread_->start ();
}
@ -1215,6 +1246,16 @@ Configuration::impl::~impl ()
void Configuration::impl::initialize_models ()
{
next_audio_input_device_ = audio_input_device_;
next_audio_input_channel_ = audio_input_channel_;
next_audio_output_device_ = audio_output_device_;
next_audio_output_channel_ = audio_output_channel_;
restart_sound_input_device_ = false;
restart_sound_output_device_ = false;
{
SettingsGroup g {settings_, "Configuration"};
find_audio_devices ();
}
auto pal = ui_->callsign_line_edit->palette ();
if (my_callsign_.isEmpty ())
{
@ -1236,11 +1277,13 @@ void Configuration::impl::initialize_models ()
ui_->sbDegrade->setValue (degrade_);
ui_->sbBandwidth->setValue (RxBandwidth_);
ui_->PTT_method_button_group->button (rig_params_.ptt_type)->setChecked (true);
ui_->save_path_display_label->setText (save_directory_.absolutePath ());
ui_->azel_path_display_label->setText (azel_directory_.absolutePath ());
ui_->CW_id_after_73_check_box->setChecked (id_after_73_);
ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_);
ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_);
ui_->psk_reporter_tcpip_check_box->setChecked (psk_reporter_tcpip_);
ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_);
ui_->monitor_last_used_check_box->setChecked (monitor_last_used_);
ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_);
@ -1387,67 +1430,12 @@ void Configuration::impl::read_settings ()
save_directory_.setPath (settings_->value ("SaveDir", default_save_directory_.absolutePath ()).toString ());
azel_directory_.setPath (settings_->value ("AzElDir", default_azel_directory_.absolutePath ()).toString ());
{
//
// retrieve audio input device
//
auto saved_name = settings_->value ("SoundInName").toString ();
// deal with special Windows default audio devices
auto default_device = QAudioDeviceInfo::defaultInputDevice ();
if (saved_name == default_device.deviceName ())
{
audio_input_device_ = default_device;
default_audio_input_device_selected_ = true;
}
else
{
default_audio_input_device_selected_ = false;
Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (QAudio::AudioInput)) // available audio input devices
{
if (p.deviceName () == saved_name)
{
audio_input_device_ = p;
}
}
}
}
{
//
// retrieve audio output device
//
auto saved_name = settings_->value("SoundOutName").toString();
// deal with special Windows default audio devices
auto default_device = QAudioDeviceInfo::defaultOutputDevice ();
if (saved_name == default_device.deviceName ())
{
audio_output_device_ = default_device;
default_audio_output_device_selected_ = true;
}
else
{
default_audio_output_device_selected_ = false;
Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (QAudio::AudioOutput)) // available audio output devices
{
if (p.deviceName () == saved_name)
{
audio_output_device_ = p;
}
}
}
}
// retrieve audio channel info
audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ());
audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ());
type_2_msg_gen_ = settings_->value ("Type2MsgGen", QVariant::fromValue (Configuration::type_2_msg_3_full)).value<Configuration::Type2MsgGen> ();
monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool ();
monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool ();
spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool ();
psk_reporter_tcpip_ = settings_->value ("PSKReporterTCPIP", false).toBool ();
id_after_73_ = settings_->value ("After73", false).toBool ();
tx_QSY_allowed_ = settings_->value ("TxQSYAllowed", false).toBool ();
use_dynamic_grid_ = settings_->value ("AutoGrid", false).toBool ();
@ -1543,6 +1531,33 @@ void Configuration::impl::read_settings ()
pwrBandTuneMemory_ = settings_->value("pwrBandTuneMemory",false).toBool ();
}
void Configuration::impl::find_audio_devices ()
{
//
// retrieve audio input device
//
auto saved_name = settings_->value ("SoundInName").toString ();
if (next_audio_input_device_.deviceName () != saved_name || next_audio_input_device_.isNull ())
{
next_audio_input_device_ = find_audio_device (QAudio::AudioInput, ui_->sound_input_combo_box, saved_name);
next_audio_input_channel_ = AudioDevice::fromString (settings_->value ("AudioInputChannel", "Mono").toString ());
update_audio_channels (ui_->sound_input_combo_box, ui_->sound_input_combo_box->currentIndex (), ui_->sound_input_channel_combo_box, false);
ui_->sound_input_channel_combo_box->setCurrentIndex (next_audio_input_channel_);
}
//
// retrieve audio output device
//
saved_name = settings_->value("SoundOutName").toString();
if (next_audio_output_device_.deviceName () != saved_name || next_audio_output_device_.isNull ())
{
next_audio_output_device_ = find_audio_device (QAudio::AudioOutput, ui_->sound_output_combo_box, saved_name);
next_audio_output_channel_ = AudioDevice::fromString (settings_->value ("AudioOutputChannel", "Mono").toString ());
update_audio_channels (ui_->sound_output_combo_box, ui_->sound_output_combo_box->currentIndex (), ui_->sound_output_channel_combo_box, true);
ui_->sound_output_channel_combo_box->setCurrentIndex (next_audio_output_channel_);
}
}
void Configuration::impl::write_settings ()
{
SettingsGroup g {settings_, "Configuration"};
@ -1562,31 +1577,21 @@ void Configuration::impl::write_settings ()
settings_->setValue ("PTTport", rig_params_.ptt_port);
settings_->setValue ("SaveDir", save_directory_.absolutePath ());
settings_->setValue ("AzElDir", azel_directory_.absolutePath ());
if (default_audio_input_device_selected_)
{
settings_->setValue ("SoundInName", QAudioDeviceInfo::defaultInputDevice ().deviceName ());
}
else
if (!audio_input_device_.isNull ())
{
settings_->setValue ("SoundInName", audio_input_device_.deviceName ());
settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_));
}
if (default_audio_output_device_selected_)
{
settings_->setValue ("SoundOutName", QAudioDeviceInfo::defaultOutputDevice ().deviceName ());
}
else
if (!audio_output_device_.isNull ())
{
settings_->setValue ("SoundOutName", audio_output_device_.deviceName ());
settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_));
}
settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_));
settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_));
settings_->setValue ("Type2MsgGen", QVariant::fromValue (type_2_msg_gen_));
settings_->setValue ("MonitorOFF", monitor_off_at_startup_);
settings_->setValue ("MonitorLastUsed", monitor_last_used_);
settings_->setValue ("PSKReporter", spot_to_psk_reporter_);
settings_->setValue ("PSKReporterTCPIP", psk_reporter_tcpip_);
settings_->setValue ("After73", id_after_73_);
settings_->setValue ("TxQSYAllowed", tx_QSY_allowed_);
settings_->setValue ("Macros", macros_.stringList ());
@ -1653,6 +1658,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("pwrBandTuneMemory", pwrBandTuneMemory_);
settings_->setValue ("Region", QVariant::fromValue (region_));
settings_->setValue ("AutoGrid", use_dynamic_grid_);
settings_->sync ();
}
void Configuration::impl::set_rig_invariants ()
@ -1785,17 +1791,27 @@ void Configuration::impl::set_rig_invariants ()
bool Configuration::impl::validate ()
{
if (ui_->sound_input_combo_box->currentIndex () < 0
&& !QAudioDeviceInfo::availableDevices (QAudio::AudioInput).empty ())
&& next_audio_input_device_.isNull ())
{
find_tab (ui_->sound_input_combo_box);
MessageBox::critical_message (this, tr ("Invalid audio input device"));
return false;
}
if (ui_->sound_input_channel_combo_box->currentIndex () < 0
&& next_audio_input_device_.isNull ())
{
find_tab (ui_->sound_input_combo_box);
MessageBox::critical_message (this, tr ("Invalid audio input device"));
return false;
}
if (ui_->sound_output_combo_box->currentIndex () < 0
&& !QAudioDeviceInfo::availableDevices (QAudio::AudioOutput).empty ())
&& next_audio_output_device_.isNull ())
{
MessageBox::critical_message (this, tr ("Invalid audio out device"));
return false;
find_tab (ui_->sound_output_combo_box);
MessageBox::information_message (this, tr ("Invalid audio output device"));
// don't reject as we can work without an audio output
}
if (!ui_->PTT_method_button_group->checkedButton ()->isEnabled ())
@ -1817,16 +1833,7 @@ bool Configuration::impl::validate ()
if (ui_->rbField_Day->isEnabled () && ui_->rbField_Day->isChecked () &&
!ui_->Field_Day_Exchange->hasAcceptableInput ())
{
for (auto * parent = ui_->Field_Day_Exchange->parentWidget (); parent; parent = parent->parentWidget ())
{
auto index = ui_->configuration_tabs->indexOf (parent);
if (index != -1)
{
ui_->configuration_tabs->setCurrentIndex (index);
break;
}
}
ui_->Field_Day_Exchange->setFocus ();
find_tab (ui_->Field_Day_Exchange);
MessageBox::critical_message (this, tr ("Invalid Contest Exchange")
, tr ("You must input a valid ARRL Field Day exchange"));
return false;
@ -1835,16 +1842,7 @@ bool Configuration::impl::validate ()
if (ui_->rbRTTY_Roundup->isEnabled () && ui_->rbRTTY_Roundup->isChecked () &&
!ui_->RTTY_Exchange->hasAcceptableInput ())
{
for (auto * parent = ui_->RTTY_Exchange->parentWidget (); parent; parent = parent->parentWidget ())
{
auto index = ui_->configuration_tabs->indexOf (parent);
if (index != -1)
{
ui_->configuration_tabs->setCurrentIndex (index);
break;
}
}
ui_->RTTY_Exchange->setFocus ();
find_tab (ui_->RTTY_Exchange);
MessageBox::critical_message (this, tr ("Invalid Contest Exchange")
, tr ("You must input a valid ARRL RTTY Roundup exchange"));
return false;
@ -1865,6 +1863,7 @@ int Configuration::impl::exec ()
rig_changed_ = false;
initialize_models ();
return QDialog::exec();
}
@ -1962,85 +1961,67 @@ void Configuration::impl::accept ()
// related configuration parameters
rig_is_dummy_ = TransceiverFactory::basic_transceiver_name_ == rig_params_.rig_name;
// Check to see whether SoundInThread must be restarted,
// and save user parameters.
{
auto const& device_name = ui_->sound_input_combo_box->currentText ();
if (device_name != audio_input_device_.deviceName ())
auto const& selected_device = ui_->sound_input_combo_box->currentData ().value<audio_info_type> ().first;
if (selected_device != next_audio_input_device_)
{
auto const& default_device = QAudioDeviceInfo::defaultInputDevice ();
if (device_name == default_device.deviceName ())
{
audio_input_device_ = default_device;
}
else
{
bool found {false};
Q_FOREACH (auto const& d, QAudioDeviceInfo::availableDevices (QAudio::AudioInput))
{
if (device_name == d.deviceName ())
{
audio_input_device_ = d;
found = true;
}
}
if (!found)
{
audio_input_device_ = default_device;
}
}
restart_sound_input_device_ = true;
next_audio_input_device_ = selected_device;
}
}
{
auto const& device_name = ui_->sound_output_combo_box->currentText ();
if (device_name != audio_output_device_.deviceName ())
auto const& selected_device = ui_->sound_output_combo_box->currentData ().value<audio_info_type> ().first;
if (selected_device != next_audio_output_device_)
{
auto const& default_device = QAudioDeviceInfo::defaultOutputDevice ();
if (device_name == default_device.deviceName ())
{
audio_output_device_ = default_device;
}
else
{
bool found {false};
Q_FOREACH (auto const& d, QAudioDeviceInfo::availableDevices (QAudio::AudioOutput))
{
if (device_name == d.deviceName ())
{
audio_output_device_ = d;
found = true;
}
}
if (!found)
{
audio_output_device_ = default_device;
}
}
restart_sound_output_device_ = true;
next_audio_output_device_ = selected_device;
}
}
if (audio_input_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ()))
if (next_audio_input_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ()))
{
audio_input_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ());
next_audio_input_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_input_channel_combo_box->currentIndex ());
}
Q_ASSERT (next_audio_input_channel_ <= AudioDevice::Right);
if (next_audio_output_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ()))
{
next_audio_output_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ());
}
Q_ASSERT (next_audio_output_channel_ <= AudioDevice::Both);
if (audio_input_device_ != next_audio_input_device_ || next_audio_input_device_.isNull ())
{
audio_input_device_ = next_audio_input_device_;
restart_sound_input_device_ = true;
}
Q_ASSERT (audio_input_channel_ <= AudioDevice::Right);
if (audio_output_channel_ != static_cast<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ()))
if (audio_input_channel_ != next_audio_input_channel_)
{
audio_output_channel_ = static_cast<AudioDevice::Channel> (ui_->sound_output_channel_combo_box->currentIndex ());
audio_input_channel_ = next_audio_input_channel_;
restart_sound_input_device_ = true;
}
if (audio_output_device_ != next_audio_output_device_ || next_audio_output_device_.isNull ())
{
audio_output_device_ = next_audio_output_device_;
restart_sound_output_device_ = true;
}
Q_ASSERT (audio_output_channel_ <= AudioDevice::Both);
if (audio_output_channel_ != next_audio_output_channel_)
{
audio_output_channel_ = next_audio_output_channel_;
restart_sound_output_device_ = true;
}
// qDebug () << "Configure::accept: audio i/p:" << audio_input_device_.deviceName ()
// << "chan:" << audio_input_channel_
// << "o/p:" << audio_output_device_.deviceName ()
// << "chan:" << audio_output_channel_
// << "reset i/p:" << restart_sound_input_device_
// << "reset o/p:" << restart_sound_output_device_;
my_callsign_ = ui_->callsign_line_edit->text ();
my_grid_ = ui_->grid_line_edit->text ();
FD_exchange_= ui_->Field_Day_Exchange->text ().toUpper ();
RTTY_exchange_= ui_->RTTY_Exchange->text ().toUpper ();
spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked ();
psk_reporter_tcpip_ = ui_->psk_reporter_tcpip_check_box->isChecked ();
id_interval_ = ui_->CW_id_interval_spin_box->value ();
ntrials_ = ui_->sbNtrials->value ();
txDelay_ = ui_->sbTxDelay->value ();
@ -2172,6 +2153,13 @@ void Configuration::impl::reject ()
}
}
// qDebug () << "Configure::reject: audio i/p:" << audio_input_device_.deviceName ()
// << "chan:" << audio_input_channel_
// << "o/p:" << audio_output_device_.deviceName ()
// << "chan:" << audio_output_channel_
// << "reset i/p:" << restart_sound_input_device_
// << "reset o/p:" << restart_sound_output_device_;
QDialog::reject ();
}
@ -2300,16 +2288,6 @@ void Configuration::impl::on_PTT_method_button_group_buttonClicked (int /* id */
set_rig_invariants ();
}
void Configuration::impl::on_sound_input_combo_box_currentTextChanged (QString const& text)
{
default_audio_input_device_selected_ = QAudioDeviceInfo::defaultInputDevice ().deviceName () == text;
}
void Configuration::impl::on_sound_output_combo_box_currentTextChanged (QString const& text)
{
default_audio_output_device_selected_ = QAudioDeviceInfo::defaultOutputDevice ().deviceName () == text;
}
void Configuration::impl::on_add_macro_line_edit_editingFinished ()
{
ui_->add_macro_line_edit->setText (ui_->add_macro_line_edit->text ().toUpper ());
@ -2688,6 +2666,7 @@ void Configuration::impl::transceiver_frequency (Frequency f)
current_offset_ = stations_.offset (f);
cached_rig_state_.frequency (apply_calibration (f + current_offset_));
// qDebug () << "Configuration::impl::transceiver_frequency: n:" << transceiver_command_number_ + 1 << "f:" << f;
Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_);
}
@ -2713,6 +2692,7 @@ void Configuration::impl::transceiver_tx_frequency (Frequency f)
cached_rig_state_.tx_frequency (apply_calibration (f + current_tx_offset_));
}
// qDebug () << "Configuration::impl::transceiver_tx_frequency: n:" << transceiver_command_number_ + 1 << "f:" << f;
Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_);
}
}
@ -2721,6 +2701,7 @@ void Configuration::impl::transceiver_mode (MODE m)
{
cached_rig_state_.online (true); // we want the rig online
cached_rig_state_.mode (m);
// qDebug () << "Configuration::impl::transceiver_mode: n:" << transceiver_command_number_ + 1 << "m:" << m;
Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_);
}
@ -2729,6 +2710,7 @@ void Configuration::impl::transceiver_ptt (bool on)
cached_rig_state_.online (true); // we want the rig online
set_cached_mode ();
cached_rig_state_.ptt (on);
// qDebug () << "Configuration::impl::transceiver_ptt: n:" << transceiver_command_number_ + 1 << "on:" << on;
Q_EMIT set_transceiver (cached_rig_state_, ++transceiver_command_number_);
}
@ -2827,72 +2809,68 @@ void Configuration::impl::close_rig ()
}
}
// load the available audio devices into the selection combo box and
// select the default device if the current device isn't set or isn't
// available
bool Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * combo_box, QAudioDeviceInfo * device)
// find the audio device that matches the specified name, also
// populate into the selection combo box with any devices we find in
// the search
QAudioDeviceInfo Configuration::impl::find_audio_device (QAudio::Mode mode, QComboBox * combo_box
, QString const& device_name)
{
using std::copy;
using std::back_inserter;
bool result {false};
if (device_name.size ())
{
Q_EMIT self_->enumerating_audio_devices ();
auto const& devices = QAudioDeviceInfo::availableDevices (mode);
Q_FOREACH (auto const& p, devices)
{
// qDebug () << "Configuration::impl::find_audio_device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes ();
if (p.deviceName () == device_name)
{
// convert supported channel counts into something we can store in the item model
QList<QVariant> channel_counts;
auto scc = p.supportedChannelCounts ();
copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts));
combo_box->insertItem (0, device_name, QVariant::fromValue (audio_info_type {p, channel_counts}));
combo_box->setCurrentIndex (0);
return p;
}
}
// insert a place holder for the not found device
combo_box->insertItem (0, device_name + " (" + tr ("Not found", "audio device missing") + ")", QVariant::fromValue (audio_info_type {}));
combo_box->setCurrentIndex (0);
}
return {};
}
// load the available audio devices into the selection combo box
void Configuration::impl::load_audio_devices (QAudio::Mode mode, QComboBox * combo_box
, QAudioDeviceInfo * device)
{
using std::copy;
using std::back_inserter;
combo_box->clear ();
Q_EMIT self_->enumerating_audio_devices ();
int current_index = -1;
int default_index = -1;
int extra_items {0};
auto const& default_device = (mode == QAudio::AudioInput ? QAudioDeviceInfo::defaultInputDevice () : QAudioDeviceInfo::defaultOutputDevice ());
// deal with special default audio devices on Windows
if ("Default Input Device" == default_device.deviceName ()
|| "Default Output Device" == default_device.deviceName ())
auto const& devices = QAudioDeviceInfo::availableDevices (mode);
Q_FOREACH (auto const& p, devices)
{
default_index = 0;
QList<QVariant> channel_counts;
auto scc = default_device.supportedChannelCounts ();
copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts));
combo_box->addItem (default_device.deviceName (), channel_counts);
++extra_items;
if (default_device == *device)
{
current_index = 0;
result = true;
}
}
Q_FOREACH (auto const& p, QAudioDeviceInfo::availableDevices (mode))
{
// qDebug () << "Audio device: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes ();
// qDebug () << "Configuration::impl::load_audio_devices: input:" << (QAudio::AudioInput == mode) << "name:" << p.deviceName () << "preferred format:" << p.preferredFormat () << "endians:" << p.supportedByteOrders () << "codecs:" << p.supportedCodecs () << "channels:" << p.supportedChannelCounts () << "rates:" << p.supportedSampleRates () << "sizes:" << p.supportedSampleSizes () << "types:" << p.supportedSampleTypes ();
// convert supported channel counts into something we can store in the item model
QList<QVariant> channel_counts;
auto scc = p.supportedChannelCounts ();
copy (scc.cbegin (), scc.cend (), back_inserter (channel_counts));
combo_box->addItem (p.deviceName (), channel_counts);
combo_box->addItem (p.deviceName (), QVariant::fromValue (audio_info_type {p, channel_counts}));
if (p == *device)
{
current_index = combo_box->count () - 1;
}
else if (p == default_device)
{
default_index = combo_box->count () - 1;
}
}
if (current_index < 0) // not found - use default
{
*device = default_device;
result = true;
current_index = default_index;
}
combo_box->setCurrentIndex (current_index);
return result;
}
// enable only the channels that are supported by the selected audio device
@ -2904,7 +2882,8 @@ void Configuration::impl::update_audio_channels (QComboBox const * source_combo_
combo_box->setItemData (i, combo_box_item_disabled, Qt::UserRole - 1);
}
Q_FOREACH (QVariant const& v, source_combo_box->itemData (index).toList ())
Q_FOREACH (QVariant const& v
, (source_combo_box->itemData (index).value<audio_info_type> ().second))
{
// enable valid options
int n {v.toInt ()};
@ -2924,6 +2903,20 @@ void Configuration::impl::update_audio_channels (QComboBox const * source_combo_
}
}
void Configuration::impl::find_tab (QWidget * target)
{
for (auto * parent = target->parentWidget (); parent; parent = parent->parentWidget ())
{
auto index = ui_->configuration_tabs->indexOf (parent);
if (index != -1)
{
ui_->configuration_tabs->setCurrentIndex (index);
break;
}
}
target->setFocus ();
}
// load all the supported rig names into the selection combo box
void Configuration::impl::enumerate_rigs ()
{

View File

@ -113,6 +113,7 @@ public:
bool id_after_73 () const;
bool tx_QSY_allowed () const;
bool spot_to_psk_reporter () const;
bool psk_reporter_tcpip () const;
bool monitor_off_at_startup () const;
bool monitor_last_used () const;
bool log_as_RTTY () const;
@ -259,6 +260,8 @@ public:
// i.e. the transceiver is ready for use.
Q_SLOT void sync_transceiver (bool force_signal = false, bool enforce_mode_and_split = false);
Q_SLOT void invalidate_audio_input_device (QString error);
Q_SLOT void invalidate_audio_output_device (QString error);
//
// These signals indicate a font has been selected and accepted for
@ -292,6 +295,12 @@ public:
// the fault condition has been rectified or is transient.
Q_SIGNAL void transceiver_failure (QString const& reason) const;
// signal announces audio devices are being enumerated
//
// As this can take some time, particularly on Linux, consumers
// might like to notify the user.
Q_SIGNAL void enumerating_audio_devices ();
private:
class impl;
pimpl<impl> m_;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>557</width>
<height>561</height>
<width>554</width>
<height>556</height>
</rect>
</property>
<property name="windowTitle">
@ -357,7 +357,7 @@
<item row="0" column="1">
<widget class="QCheckBox" name="enable_VHF_features_check_box">
<property name="text">
<string>Enable VHF/UHF/Microwave features</string>
<string>Enable VHF and submode features</string>
</property>
</widget>
</item>
@ -577,6 +577,9 @@ quiet period when decoding is done.</string>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="accessibleName">
<string>Serial Port Parameters</string>
</property>
<property name="title">
<string>Serial Port Parameters</string>
</property>
@ -659,6 +662,9 @@ quiet period when decoding is done.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of data bits used to communicate with your radio's CAT interface (usually eight).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Data bits</string>
</property>
<property name="title">
<string>Data Bits</string>
</property>
@ -710,6 +716,9 @@ quiet period when decoding is done.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Number of stop bits used when communicating with your radio's CAT interface&lt;/p&gt;&lt;p&gt;(consult you radio's manual for details).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Stop bits</string>
</property>
<property name="title">
<string>Stop Bits</string>
</property>
@ -758,6 +767,9 @@ quiet period when decoding is done.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Flow control protocol used between this computer and your radio's CAT interface (usually &amp;quot;None&amp;quot; but some require &amp;quot;Hardware&amp;quot;).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Handshake</string>
</property>
<property name="title">
<string>Handshake</string>
</property>
@ -824,6 +836,9 @@ a few, particularly some Kenwood rigs, require it).</string>
<property name="toolTip">
<string>Special control of CAT port control lines.</string>
</property>
<property name="accessibleName">
<string>Force Control Lines</string>
</property>
<property name="title">
<string>Force Control Lines</string>
</property>
@ -1350,7 +1365,7 @@ radio interface behave as expected.</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="1" column="1">
<widget class="QComboBox" name="sound_output_combo_box">
<widget class="LazyFillComboBox" name="sound_output_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
@ -1366,29 +1381,6 @@ transmitting periods.</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="sound_input_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select the audio CODEC to use for receiving.</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="sound_input_label">
<property name="text">
<string>&amp;Input:</string>
</property>
<property name="buddy">
<cstring>sound_input_combo_box</cstring>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QComboBox" name="sound_input_channel_combo_box">
<property name="toolTip">
@ -1416,6 +1408,29 @@ transmitting periods.</string>
</item>
</widget>
</item>
<item row="0" column="1">
<widget class="LazyFillComboBox" name="sound_input_combo_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Select the audio CODEC to use for receiving.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sound_output_label">
<property name="text">
<string>Ou&amp;tput:</string>
</property>
<property name="buddy">
<cstring>sound_output_combo_box</cstring>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="sound_output_channel_combo_box">
<property name="toolTip">
@ -1446,13 +1461,13 @@ both here.</string>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="sound_output_label">
<item row="0" column="0">
<widget class="QLabel" name="sound_input_label">
<property name="text">
<string>Ou&amp;tput:</string>
<string>&amp;Input:</string>
</property>
<property name="buddy">
<cstring>sound_output_combo_box</cstring>
<cstring>sound_input_combo_box</cstring>
</property>
</widget>
</item>
@ -1493,7 +1508,8 @@ both here.</string>
<bool>false</bool>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
<string notr="true">background-color: rgb(255, 255, 255);
color: rgb(0, 0, 0);</string>
</property>
<property name="text">
<string>TextLabel</string>
@ -1541,7 +1557,8 @@ both here.</string>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 255);</string>
<string notr="true">background-color: rgb(255, 255, 255);
color: rgb(0, 0, 0);</string>
</property>
<property name="text">
<string>TextLabel</string>
@ -1800,20 +1817,27 @@ and DX Grid fields when a 73 or free text message is sent.</string>
<property name="title">
<string>Network Services</string>
</property>
<layout class="QGridLayout" name="gridLayout_17">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_22">
<item>
<widget class="QCheckBox" name="psk_reporter_check_box">
<property name="toolTip">
<string>The program can send your station details and all
decoded signals as spots to the http://pskreporter.info web site.
This is used for reverse beacon analysis which is very useful
for assessing propagation and system performance.</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The program can send your station details and all decoded signals with grid squares as spots to the http://pskreporter.info web site.&lt;/p&gt;&lt;p&gt;This is used for reverse beacon analysis which is very useful for assessing propagation and system performance.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable &amp;PSK Reporter Spotting</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="psk_reporter_tcpip_check_box">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this option if a reliable connection is needed&lt;/p&gt;&lt;p&gt;Most users do not need this, the default uses UDP which is more efficient. Only check this if you have evidence that UDP traffic from you to PSK Reporter is being lost.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Use TCP/IP connection</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -2337,6 +2361,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;URL of the ARRL LotW user's last upload dates and times data file which is used to highlight decodes from stations that are known to upload their log file to LotW.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>URL</string>
</property>
<property name="text">
<string>https://lotw.arrl.org/lotw-user-activity.csv</string>
</property>
@ -2369,6 +2396,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Days since last upload</string>
</property>
<property name="suffix">
<string> days</string>
</property>
@ -2504,6 +2534,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;FT8 DXpedition mode: Hound operator calling the DX.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Hound</string>
</property>
<property name="text">
<string>Hound</string>
</property>
@ -2526,6 +2559,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;North American VHF/UHF/Microwave contests and others in which a 4-character grid locator is the required exchange.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>NA VHF Contest</string>
</property>
<property name="text">
<string>NA VHF Contest</string>
</property>
@ -2539,6 +2575,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;FT8 DXpedition mode: Fox (DXpedition) operator.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>Fox</string>
</property>
<property name="text">
<string>Fox</string>
</property>
@ -2561,6 +2600,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;European VHF+ contests requiring a signal report, serial number, and 6-character locator.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>EU VHF Contest</string>
</property>
<property name="text">
<string>EU VHF Contest</string>
</property>
@ -2589,6 +2631,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &amp;quot;DX&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>R T T Y Roundup</string>
</property>
<property name="text">
<string>RTTY Roundup messages</string>
</property>
@ -2614,6 +2659,9 @@ Right click for insert and delete options.</string>
<layout class="QFormLayout" name="formLayout_17">
<item row="0" column="0">
<widget class="QLabel" name="labRTTY">
<property name="accessibleName">
<string>RTTY Roundup exchange</string>
</property>
<property name="text">
<string>RTTY RU Exch:</string>
</property>
@ -2652,6 +2700,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ARRL Field Day exchange: number of transmitters, Class, and ARRL/RAC section or &amp;quot;DX&amp;quot;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>A R R L Field Day</string>
</property>
<property name="text">
<string>ARRL Field Day</string>
</property>
@ -2677,6 +2728,9 @@ Right click for insert and delete options.</string>
<layout class="QFormLayout" name="formLayout_16">
<item row="0" column="0">
<widget class="QLabel" name="labFD">
<property name="accessibleName">
<string>Field Day exchange</string>
</property>
<property name="text">
<string>FD Exch:</string>
</property>
@ -2719,6 +2773,9 @@ Right click for insert and delete options.</string>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;World-Wide Digi-mode contest&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="accessibleName">
<string>WW Digital Contest</string>
</property>
<property name="text">
<string>WW Digi Contest</string>
</property>
@ -2831,6 +2888,9 @@ Right click for insert and delete options.</string>
<height>50</height>
</size>
</property>
<property name="accessibleName">
<string>Tone spacing</string>
</property>
<property name="title">
<string>Tone spacing</string>
</property>
@ -2869,6 +2929,9 @@ Right click for insert and delete options.</string>
<height>50</height>
</size>
</property>
<property name="accessibleName">
<string>Waterfall spectra</string>
</property>
<property name="title">
<string>Waterfall spectra</string>
</property>
@ -2934,6 +2997,11 @@ Right click for insert and delete options.</string>
<extends>QListView</extends>
<header>widgets/DecodeHighlightingListView.hpp</header>
</customwidget>
<customwidget>
<class>LazyFillComboBox</class>
<extends>QComboBox</extends>
<header>widgets/LazyFillComboBox.hpp</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>configuration_tabs</tabstop>
@ -2942,14 +3010,20 @@ Right click for insert and delete options.</string>
<tabstop>use_dynamic_grid</tabstop>
<tabstop>region_combo_box</tabstop>
<tabstop>type_2_msg_gen_combo_box</tabstop>
<tabstop>decodes_from_top_check_box</tabstop>
<tabstop>insert_blank_check_box</tabstop>
<tabstop>miles_check_box</tabstop>
<tabstop>TX_messages_check_box</tabstop>
<tabstop>DXCC_check_box</tabstop>
<tabstop>ppfx_check_box</tabstop>
<tabstop>font_push_button</tabstop>
<tabstop>decoded_text_font_push_button</tabstop>
<tabstop>monitor_off_check_box</tabstop>
<tabstop>monitor_last_used_check_box</tabstop>
<tabstop>quick_call_check_box</tabstop>
<tabstop>disable_TX_on_73_check_box</tabstop>
<tabstop>force_call_1st_check_box</tabstop>
<tabstop>alternate_bindings_check_box</tabstop>
<tabstop>CW_id_after_73_check_box</tabstop>
<tabstop>enable_VHF_features_check_box</tabstop>
<tabstop>tx_QSY_check_box</tabstop>
@ -2968,8 +3042,8 @@ Right click for insert and delete options.</string>
<tabstop>CAT_one_stop_bit_radio_button</tabstop>
<tabstop>CAT_two_stop_bit_radio_button</tabstop>
<tabstop>CAT_handshake_default_radio_button</tabstop>
<tabstop>CAT_handshake_none_radio_button</tabstop>
<tabstop>CAT_handshake_xon_radio_button</tabstop>
<tabstop>CAT_handshake_none_radio_button</tabstop>
<tabstop>CAT_handshake_hardware_radio_button</tabstop>
<tabstop>force_DTR_combo_box</tabstop>
<tabstop>force_RTS_combo_box</tabstop>
@ -3007,6 +3081,7 @@ Right click for insert and delete options.</string>
<tabstop>clear_DX_check_box</tabstop>
<tabstop>opCallEntry</tabstop>
<tabstop>psk_reporter_check_box</tabstop>
<tabstop>psk_reporter_tcpip_check_box</tabstop>
<tabstop>udp_server_line_edit</tabstop>
<tabstop>udp_server_port_spin_box</tabstop>
<tabstop>accept_udp_requests_check_box</tabstop>
@ -3021,9 +3096,13 @@ Right click for insert and delete options.</string>
<tabstop>stations_table_view</tabstop>
<tabstop>highlighting_list_view</tabstop>
<tabstop>reset_highlighting_to_defaults_push_button</tabstop>
<tabstop>highlight_by_mode_check_box</tabstop>
<tabstop>only_fields_check_box</tabstop>
<tabstop>include_WAE_check_box</tabstop>
<tabstop>rescan_log_push_button</tabstop>
<tabstop>LotW_CSV_URL_line_edit</tabstop>
<tabstop>LotW_CSV_fetch_push_button</tabstop>
<tabstop>LotW_days_since_upload_spin_box</tabstop>
<tabstop>LotW_CSV_fetch_push_button</tabstop>
<tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop>
@ -3032,13 +3111,18 @@ Right click for insert and delete options.</string>
<tabstop>sbTxDelay</tabstop>
<tabstop>cbx2ToneSpacing</tabstop>
<tabstop>cbx4ToneSpacing</tabstop>
<tabstop>rbLowSidelobes</tabstop>
<tabstop>rbMaxSensitivity</tabstop>
<tabstop>gbSpecialOpActivity</tabstop>
<tabstop>rbFox</tabstop>
<tabstop>rbHound</tabstop>
<tabstop>rbNA_VHF_Contest</tabstop>
<tabstop>rbEU_VHF_Contest</tabstop>
<tabstop>rbField_Day</tabstop>
<tabstop>Field_Day_Exchange</tabstop>
<tabstop>rbEU_VHF_Contest</tabstop>
<tabstop>rbRTTY_Roundup</tabstop>
<tabstop>RTTY_Exchange</tabstop>
<tabstop>rbWW_DIGI</tabstop>
</tabstops>
<resources/>
<connections>
@ -3108,13 +3192,13 @@ Right click for insert and delete options.</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="special_op_activity_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="split_mode_button_group"/>
</buttongroups>
</ui>

View File

@ -165,7 +165,13 @@ QString DecodedText::call() const
// 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_);
auto msg = message_;
auto p = msg.indexOf ("; ");
if (p >= 0)
{
msg = msg.mid (p + 2);
}
auto const& match = words_re.match (msg);
call = match.captured ("word2");
grid = match.captured ("word3");
if ("R" == grid) grid = match.captured ("word4");

3
Decoder/decodedtext.pri Normal file
View File

@ -0,0 +1,3 @@
SOURCES += Decoder/decodedtext.cpp
HEADERS += Decoder/decodedtext.h

View File

@ -36,7 +36,7 @@ void Detector::setBlockSize (unsigned n)
bool Detector::reset ()
{
clear ();
// don't call base call reset because it calls seek(0) which causes
// don't call base class reset because it calls seek(0) which causes
// a warning
return isOpen ();
}
@ -119,8 +119,7 @@ qint64 Detector::writeData (char const * data, qint64 maxSize)
remaining -= numFramesProcessed;
}
return maxSize; // we drop any data past the end of the buffer on
// the floor until the next period starts
// we drop any data past the end of the buffer on the floor until
// the next period starts
return maxSize;
}

3
Detector/Detector.pri Normal file
View File

@ -0,0 +1,3 @@
SOURCES += Detector/Detector.cpp
HEADERS += Detector/Detector.hpp

View File

@ -292,7 +292,7 @@ EqualizationToolsDialog::impl::impl (EqualizationToolsDialog * self
| QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Close
, Qt::Vertical}
{
setWindowTitle (windowTitle () + ' ' + tr (title));
setWindowTitle (windowTitle () + ' ' + tr ("Equalization Tools"));
resize (500, 600);
{
SettingsGroup g {settings_, title};

14
INSTALL
View File

@ -28,7 +28,7 @@ For MS Windows see the section "Building from Source on MS Windows"
below. For Apple Mac see the section "Building from Source on Apple
Mac".
Qt v5, preferably v5.5 or later is required to build WSJT-X.
Qt v5, preferably v5.9 or later is required to build WSJT-X.
Qt v5 multimedia support, serial port, and Linguist is necessary as
well as the core Qt v5 components, normally installing the Qt
@ -43,8 +43,8 @@ the libfftw library development package. Normally installing the
library development package pulls in all the FFTW v3 libraries
including the single precision variant.
The Hamlib library optionally requires the libusb-1.0 library, if the
development version (libusb-1.0-dev) is available Hamlib will
The Hamlib library optionally requires the libusb-1.0-1 library, if
the development version (libusb-1.0-0-dev) is available Hamlib will
configure its custom USB device back end drivers. Most rigs do not
require this so normally you can choose not to install libusb-1.0-dev
but if you have a SoftRock USB or similar SDR that uses a custom USB
@ -89,7 +89,8 @@ $ git clone git://git.code.sf.net/p/wsjt/wsjtx src
To build WSJT-X you will need CMake and asciidoc installed.
$ cd ~/wsjtx-prefix/build
$ cmake -D CMAKE_PREFIX_PATH=~/hamlib-prefix ../src
$ cmake -D CMAKE_PREFIX_PATH=~/hamlib-prefix -DWSJT_SKIP_MANPAGES=ON \
-DWSJT_GENERATE_DOCS=OFF ../src
$ cmake --build .
$ cmake --build . --target install
@ -99,7 +100,8 @@ configure step like:
$ cd ~/wsjtx-prefix/build
$ cmake -D CMAKE_PREFIX_PATH=~/hamlib-prefix \
-D CMAKE_INSTALL_PREFIX=~/wsjtx-prefix ../src
-DWSJT_SKIP_MANPAGES=ON -DWSJT_GENERATE_DOCS=OFF \
-D CMAKE_INSTALL_PREFIX=~/wsjtx-prefix ../src
$ cmake --build .
$ cmake --build . --target install
@ -316,7 +318,7 @@ configure:
$ cd ~/wsjtx-prefix/build
$ FC=gfortran-mp-5 \
cmake \
-D CMAKE_PREFIX_PATH="~/Qt/5.7/clang_64;~/hamlib-prefix;/opt/local" \
-D CMAKE_PREFIX_PATH="~/Qt/5.9/clang_64;~/hamlib-prefix;/opt/local" \
-D CMAKE_INSTALL_PREFIX=~/wsjtx-prefix \
-D CMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk \
~/wsjtx-prefix/src

View File

@ -45,11 +45,12 @@ Modulator::Modulator (unsigned frameRate, double periodLengthInSeconds,
{
}
void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
void Modulator::start (QString mode, unsigned symbolsLength, double framesPerSymbol,
double frequency, double toneSpacing,
SoundOutput * stream, Channel channel,
bool synchronize, bool fastMode, double dBSNR, double TRperiod)
{
// qDebug () << "mode:" << mode << "symbolsLength:" << symbolsLength << "framesPerSymbol:" << framesPerSymbol << "frequency:" << frequency << "toneSpacing:" << toneSpacing << "channel:" << channel << "synchronize:" << synchronize << "fastMode:" << fastMode << "dBSNR:" << dBSNR << "TRperiod:" << TRperiod;
Q_ASSERT (stream);
// Time according to this computer which becomes our base time
qint64 ms0 = QDateTime::currentMSecsSinceEpoch() % 86400000;
@ -69,8 +70,8 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
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
if(mode=="FT8" or (mode=="FST4" and m_nsps==720)) delay_ms=500; //FT8, FST4-15
if(mode=="FT4") delay_ms=300; //FT4
// noise generator parameters
if (m_addNoise) {
@ -79,28 +80,39 @@ void Modulator::start (unsigned symbolsLength, double framesPerSymbol,
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);
m_ic=0;
if (!m_tuning && !m_bFastMode)
{
// calculate number of silent frames to send, so that audio will
// start at the nominal time "delay_ms" into the Tx sequence.
if (synchronize)
{
if(delay_ms > mstr) m_silentFrames = (delay_ms - mstr) * m_frameRate / 1000;
}
// adjust for late starts
if(!m_silentFrames && mstr >= delay_ms)
{
m_ic = (mstr - delay_ms) * m_frameRate / 1000;
}
}
initialize (QIODevice::ReadOnly, channel);
Q_EMIT stateChanged ((m_state = (synchronize && m_silentFrames) ?
Synchronizing : Active));
// qDebug() << "delay_ms:" << delay_ms << "mstr:" << mstr << "m_silentFrames:" << m_silentFrames << "m_ic:" << m_ic << "m_state:" << m_state;
m_stream = stream;
if (m_stream) m_stream->restart (this);
if (m_stream)
{
m_stream->restart (this);
}
else
{
qDebug () << "Modulator::start: no audio output stream assigned";
}
}
void Modulator::tune (bool newState)
@ -152,21 +164,25 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
qint16 * end (samples + numFrames * (bytesPerFrame () / sizeof (qint16)));
qint64 framesGenerated (0);
// if(m_ic==0) qDebug() << "Modulator::readData" << 0.001*(QDateTime::currentMSecsSinceEpoch() % (1000*m_TRperiod));
// if(m_ic==0) qDebug() << "aa" << 0.001*(QDateTime::currentMSecsSinceEpoch() % qint64(1000*m_TRperiod))
// << m_state << m_TRperiod << m_silentFrames << m_ic << foxcom_.wave[m_ic];
switch (m_state)
{
case Synchronizing:
{
if (m_silentFrames) { // send silence up to first second
if (m_silentFrames) { // send silence up to end of start delay
framesGenerated = qMin (m_silentFrames, numFrames);
for ( ; samples != end; samples = load (0, samples)) { // silence
}
m_silentFrames -= framesGenerated;
return framesGenerated * bytesPerFrame ();
do
{
samples = load (0, samples); // silence
} while (--m_silentFrames && samples != end);
if (!m_silentFrames)
{
Q_EMIT stateChanged ((m_state = Active));
}
}
Q_EMIT stateChanged ((m_state = Active));
m_cwLevel = false;
m_ramp = 0; // prepare for CW wave shaping
}
@ -260,7 +276,7 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
qint16 sample;
for (unsigned i = 0; i < numFrames && m_ic <= i1; ++i) {
while (samples != end && m_ic <= i1) {
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;
@ -305,12 +321,19 @@ qint64 Modulator::readData (char * data, qint64 maxSize)
m_amp=32767.0;
sample=qRound(m_amp*foxcom_.wave[m_ic]);
}
/*
if((m_ic<1000 or (4*m_symbolsLength*m_nsps - m_ic) < 1000) and (m_ic%10)==0) {
qDebug() << "cc" << QDateTime::currentDateTimeUtc().toString("hh:mm:ss.zzz") << m_ic << sample;
}
*/
samples = load(postProcessSample(sample), samples);
++framesGenerated;
++m_ic;
}
// qDebug() << "dd" << QDateTime::currentDateTimeUtc().toString("hh:mm:ss.zzz")
// << m_ic << i1 << foxcom_.wave[m_ic] << framesGenerated;
if (m_amp == 0.0) { // TODO G4WJS: compare double with zero might not be wise
if (icw[0] == 0) {
// no CW ID to send

View File

@ -35,7 +35,7 @@ public:
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,
Q_SLOT void start (QString mode, unsigned symbolsLength, double framesPerSymbol, double frequency,
double toneSpacing, SoundOutput *, Channel = Mono,
bool synchronize = true, bool fastMode = false,
double dBSNR = 99., double TRperiod=60.0);

3
Modulator/Modulator.pri Normal file
View File

@ -0,0 +1,3 @@
SOURCES += Modulator/Modulator.cpp
HEADERS += Modulator/Mpdulator.hpp

26
NEWS
View File

@ -13,6 +13,32 @@
Copyright 2001 - 2020 by Joe Taylor, K1JT.
Release: WSJT-X 2.3.0-rc1
Sept 28, 2020
-------------------------
WSJT-X 2.3.0 is a program upgrade offering two new modes designed
especially for use on the LF and MF bands. FST4 is for 2-way QSOs,
and FST4W is for WSPR-like transmissions. Both modes offer a range of
options for T/R sequence lengths and threshold decoding sensitivities
extending well into the -40 dB range. Early tests have shown these
modes frequently spanning intercontinental distances on the 2200 m and
630 m bands. Further details and operating hints can be found in the
"Quick-Start Guide to FST4 and FST4W", posted on the WSJT web site:
https://physics.princeton.edu/pulsar/k1jt/FST4_Quick_Start.pdf
WSJT-X 2.3.0-rc1 is a beta-quality release candidate for a program
upgrade that provides a number of new features and capabilities.
These include:
- New modes FST4 and FST4W
- The *On Dx Echo* Doppler compensation method has been modified in
response to feedback from Users. Basic functionality is unchanged.
See the User Guide (Section 8.1) for more information.
Release: WSJT-X 2.2.2
June 22, 2020
---------------------

View File

@ -36,6 +36,7 @@ public:
impl (QString const& id, QString const& version, QString const& revision,
port_type server_port, MessageClient * self)
: self_ {self}
, dns_lookup_id_ {0}
, enabled_ {false}
, id_ {id}
, version_ {version}
@ -81,6 +82,7 @@ public:
Q_SLOT void host_info_results (QHostInfo);
MessageClient * self_;
int dns_lookup_id_;
bool enabled_;
QString id_;
QString version_;
@ -101,6 +103,7 @@ public:
void MessageClient::impl::host_info_results (QHostInfo host_info)
{
if (host_info.lookupId () != dns_lookup_id_) return;
if (QHostInfo::NoError != host_info.error ())
{
Q_EMIT self_->error ("UDP server lookup failed:\n" + host_info.errorString ());
@ -187,7 +190,13 @@ void MessageClient::impl::parse_message (QByteArray const& msg)
quint8 modifiers {0};
in >> time >> snr >> delta_time >> delta_frequency >> mode >> message
>> low_confidence >> modifiers;
TRACE_UDP ("Reply: time:" << time << "snr:" << snr << "dt:" << delta_time << "df:" << delta_frequency << "mode:" << mode << "message:" << message << "low confidence:" << low_confidence << "modifiers: 0x" << hex << modifiers);
TRACE_UDP ("Reply: time:" << time << "snr:" << snr << "dt:" << delta_time << "df:" << delta_frequency << "mode:" << mode << "message:" << message << "low confidence:" << low_confidence << "modifiers: 0x"
#if QT_VERSION >= QT_VERSION_CHECK (5, 15, 0)
<< Qt::hex
#else
<< hex
#endif
<< modifiers);
if (check_status (in) != Fail)
{
Q_EMIT self_->reply (time, snr, delta_time, delta_frequency
@ -423,30 +432,24 @@ MessageClient::MessageClient (QString const& id, QString const& version, QString
: QObject {self}
, m_ {id, version, revision, server_port, this}
{
connect (&*m_
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect (&*m_, static_cast<void (impl::*) (impl::SocketError)> (&impl::error)
, [this] (impl::SocketError e)
{
, static_cast<void (impl::*) (impl::SocketError)> (&impl::error), [this] (impl::SocketError e)
#else
, &impl::errorOccurred, [this] (impl::SocketError e)
#endif
{
#if defined (Q_OS_WIN)
if (e != impl::NetworkError // take this out when Qt 5.5
// stops doing this
// spuriously
&& e != impl::ConnectionRefusedError) // not
// interested
// in this with
// UDP socket
if (e != impl::NetworkError // take this out when Qt 5.5 stops doing this spuriously
&& e != impl::ConnectionRefusedError) // not interested in this with UDP socket
{
#else
Q_UNUSED (e);
{
Q_UNUSED (e);
#endif
{
Q_EMIT error (m_->errorString ());
}
});
#else
connect (&*m_, &impl::errorOccurred, [this] (impl::SocketError) {
Q_EMIT error (m_->errorString ());
});
#endif
}
});
set_server (server);
}
@ -464,11 +467,15 @@ void MessageClient::set_server (QString const& server)
{
m_->server_.clear ();
m_->server_string_ = server;
if (!server.isEmpty ())
if (server.size ())
{
// queue a host address lookup
TRACE_UDP ("server host DNS lookup:" << server);
QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, &MessageClient::impl::host_info_results);
#else
m_->dns_lookup_id_ = QHostInfo::lookupHost (server, &*m_, SLOT (host_info_results (QHostInfo)));
#endif
}
}
@ -477,27 +484,6 @@ void MessageClient::set_server_port (port_type server_port)
m_->server_port_ = server_port;
}
qint64 MessageClient::send_raw_datagram (QByteArray const& message, QHostAddress const& dest_address
, port_type dest_port)
{
if (dest_port && !dest_address.isNull ())
{
return m_->writeDatagram (message, dest_address, dest_port);
}
return 0;
}
void MessageClient::add_blocked_destination (QHostAddress const& a)
{
m_->blocked_addresses_.push_back (a);
if (a == m_->server_)
{
m_->server_.clear ();
Q_EMIT error ("UDP server blocked, please try another");
m_->pending_messages_.clear (); // discard
}
}
void MessageClient::enable (bool flag)
{
m_->enabled_ = flag;
@ -573,7 +559,7 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
, QString const& comments, QString const& name, QDateTime time_on
, QString const& operator_call, QString const& my_call
, QString const& my_grid, QString const& exchange_sent
, QString const& exchange_rcvd)
, QString const& exchange_rcvd, QString const& propmode)
{
if (m_->server_port_ && !m_->server_string_.isEmpty ())
{
@ -582,8 +568,8 @@ void MessageClient::qso_logged (QDateTime time_off, QString const& dx_call, QStr
out << time_off << dx_call.toUtf8 () << dx_grid.toUtf8 () << dial_frequency << mode.toUtf8 ()
<< report_sent.toUtf8 () << report_received.toUtf8 () << tx_power.toUtf8 () << comments.toUtf8 ()
<< name.toUtf8 () << time_on << operator_call.toUtf8 () << my_call.toUtf8 () << my_grid.toUtf8 ()
<< exchange_sent.toUtf8 () << exchange_rcvd.toUtf8 ();
TRACE_UDP ("time off:" << time_off << "DX:" << dx_call << "DX grid:" << dx_grid << "dial:" << dial_frequency << "mode:" << mode << "sent:" << report_sent << "rcvd:" << report_received << "pwr:" << tx_power << "comments:" << comments << "name:" << name << "time on:" << time_on << "op:" << operator_call << "DE:" << my_call << "DE grid:" << my_grid << "exch sent:" << exchange_sent << "exch rcvd:" << exchange_rcvd);
<< exchange_sent.toUtf8 () << exchange_rcvd.toUtf8 () << propmode.toUtf8 ();
TRACE_UDP ("time off:" << time_off << "DX:" << dx_call << "DX grid:" << dx_grid << "dial:" << dial_frequency << "mode:" << mode << "sent:" << report_sent << "rcvd:" << report_received << "pwr:" << tx_power << "comments:" << comments << "name:" << name << "time on:" << time_on << "op:" << operator_call << "DE:" << my_call << "DE grid:" << my_grid << "exch sent:" << exchange_sent << "exch rcvd:" << exchange_rcvd << "prop_mode:" << propmode);
m_->send_message (out, message);
}
}

View File

@ -69,21 +69,13 @@ public:
, QString const& report_received, QString const& tx_power, QString const& comments
, QString const& name, QDateTime time_on, QString const& operator_call
, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd);
, QString const& exchange_sent, QString const& exchange_rcvd
, QString const& propmode);
// ADIF_record argument should be valid ADIF excluding any <EOR> end
// of record marker
Q_SLOT void logged_ADIF (QByteArray const& ADIF_record);
// this may be used to send arbitrary UDP datagrams to and
// destination allowing the underlying socket to be used for general
// UDP messaging if desired
qint64 send_raw_datagram (QByteArray const&, QHostAddress const& dest_address, port_type dest_port);
// disallowed message destination (does not block datagrams sent
// with send_raw_datagram() above)
Q_SLOT void add_blocked_destination (QHostAddress const&);
// this signal is emitted if the server has requested a decode
// window clear action
Q_SIGNAL void clear_decodes (quint8 window);

View File

@ -0,0 +1,62 @@
#include "Network/NetworkAccessManager.hpp"
#include <QString>
#include <QNetworkReply>
#include "moc_NetworkAccessManager.cpp"
NetworkAccessManager::NetworkAccessManager (QWidget * parent)
: QNetworkAccessManager (parent)
, parent_widget_ {parent}
{
// handle SSL errors that have not been cached as allowed
// exceptions and offer them to the user to add to the ignored
// exception cache
connect (this, &QNetworkAccessManager::sslErrors, this, &NetworkAccessManager::filter_SSL_errors);
}
void NetworkAccessManager::filter_SSL_errors (QNetworkReply * reply, QList<QSslError> const& errors)
{
QString message;
QList<QSslError> new_errors;
for (auto const& error: errors)
{
if (!allowed_ssl_errors_.contains (error))
{
new_errors << error;
message += '\n' + reply->request ().url ().toDisplayString () + ": " + error.errorString ();
}
}
if (new_errors.size ())
{
QString certs;
for (auto const& cert : reply->sslConfiguration ().peerCertificateChain ())
{
certs += cert.toText () + '\n';
}
if (MessageBox::Ignore == MessageBox::query_message (parent_widget_
, tr ("Network SSL/TLS Errors")
, message, certs
, MessageBox::Abort | MessageBox::Ignore))
{
// accumulate new SSL error exceptions that have been allowed
allowed_ssl_errors_.append (new_errors);
reply->ignoreSslErrors (allowed_ssl_errors_);
}
}
else
{
// no new exceptions so silently ignore the ones already allowed
reply->ignoreSslErrors (allowed_ssl_errors_);
}
}
QNetworkReply * NetworkAccessManager::createRequest (Operation operation, QNetworkRequest const& request
, QIODevice * outgoing_data)
{
auto reply = QNetworkAccessManager::createRequest (operation, request, outgoing_data);
// errors are usually certificate specific so passing all cached
// exceptions here is ok
reply->ignoreSslErrors (allowed_ssl_errors_);
return reply;
}

View File

@ -4,8 +4,6 @@
#include <QNetworkAccessManager>
#include <QList>
#include <QSslError>
#include <QNetworkReply>
#include <QString>
#include "widgets/MessageBox.hpp"
@ -18,58 +16,18 @@ class QWidget;
class NetworkAccessManager
: public QNetworkAccessManager
{
Q_OBJECT
public:
NetworkAccessManager (QWidget * parent)
: QNetworkAccessManager (parent)
{
// handle SSL errors that have not been cached as allowed
// exceptions and offer them to the user to add to the ignored
// exception cache
connect (this, &QNetworkAccessManager::sslErrors, [this, &parent] (QNetworkReply * reply, QList<QSslError> const& errors) {
QString message;
QList<QSslError> new_errors;
for (auto const& error: errors)
{
if (!allowed_ssl_errors_.contains (error))
{
new_errors << error;
message += '\n' + reply->request ().url ().toDisplayString () + ": "
+ error.errorString ();
}
}
if (new_errors.size ())
{
QString certs;
for (auto const& cert : reply->sslConfiguration ().peerCertificateChain ())
{
certs += cert.toText () + '\n';
}
if (MessageBox::Ignore == MessageBox::query_message (parent, tr ("Network SSL Errors"), message, certs, MessageBox::Abort | MessageBox::Ignore))
{
// accumulate new SSL error exceptions that have been allowed
allowed_ssl_errors_.append (new_errors);
reply->ignoreSslErrors (allowed_ssl_errors_);
}
}
else
{
// no new exceptions so silently ignore the ones already allowed
reply->ignoreSslErrors (allowed_ssl_errors_);
}
});
}
explicit NetworkAccessManager (QWidget * parent);
protected:
QNetworkReply * createRequest (Operation operation, QNetworkRequest const& request, QIODevice * outgoing_data = nullptr) override
{
auto reply = QNetworkAccessManager::createRequest (operation, request, outgoing_data);
// errors are usually certificate specific so passing all cached
// exceptions here is ok
reply->ignoreSslErrors (allowed_ssl_errors_);
return reply;
}
QNetworkReply * createRequest (Operation, QNetworkRequest const&, QIODevice * = nullptr) override;
private:
void filter_SSL_errors (QNetworkReply * reply, QList<QSslError> const& errors);
QWidget * parent_widget_;
QList<QSslError> allowed_ssl_errors_;
};

View File

@ -308,6 +308,7 @@
* My grid utf8
* Exchange sent utf8
* Exchange received utf8
* ADIF Propagation mode utf8
*
* The QSO logged message is sent to the server(s) when the
* WSJT-X user accepts the "Log QSO" dialog by clicking the "OK"

521
Network/PSKReporter.cpp Normal file
View File

@ -0,0 +1,521 @@
#include "PSKReporter.hpp"
// Interface for posting spots to PSK Reporter web site
// Implemented by Edson Pereira PY2SDR
// Updated by Bill Somerville, G4WJS
//
// Reports will be sent in batch mode every 5 minutes.
#include <cmath>
#include <QObject>
#include <QString>
#include <QDateTime>
#include <QSharedPointer>
#include <QUdpSocket>
#include <QTcpSocket>
#include <QHostInfo>
#include <QQueue>
#include <QByteArray>
#include <QDataStream>
#include <QTimer>
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
#endif
#include "Configuration.hpp"
#include "pimpl_impl.hpp"
#include "moc_PSKReporter.cpp"
namespace
{
QLatin1String HOST {"report.pskreporter.info"};
// QLatin1String HOST {"127.0.0.1"};
quint16 SERVICE_PORT {4739};
// quint16 SERVICE_PORT {14739};
int MIN_SEND_INTERVAL {15}; // in seconds
int FLUSH_INTERVAL {4 * 5}; // in send intervals
bool ALIGNMENT_PADDING {true};
int MIN_PAYLOAD_LENGTH {508};
int MAX_PAYLOAD_LENGTH {1400};
}
class PSKReporter::impl final
: public QObject
{
Q_OBJECT
public:
impl (PSKReporter * self, Configuration const * config, QString const& program_info)
: self_ {self}
, config_ {config}
, sequence_number_ {0u}
, send_descriptors_ {0}
, send_receiver_data_ {0}
, flush_counter_ {0u}
, prog_id_ {program_info}
{
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
observation_id_ = qrand();
#else
observation_id_ = QRandomGenerator::global ()->generate ();
#endif
// This timer sets the interval to check for spots to send.
connect (&report_timer_, &QTimer::timeout, [this] () {send_report ();});
// This timer repeats the sending of IPFIX templates and receiver
// information if we are using UDP, in case server has been
// restarted ans lost cached information.
connect (&descriptor_timer_, &QTimer::timeout, [this] () {
if (socket_
&& QAbstractSocket::UdpSocket == socket_->socketType ())
{
// send templates again
send_descriptors_ = 3; // three times
// send receiver data set again
send_receiver_data_ = 3; // three times
}
});
}
void check_connection ()
{
if (!socket_
|| QAbstractSocket::UnconnectedState == socket_->state ()
|| (socket_->socketType () != config_->psk_reporter_tcpip () ? QAbstractSocket::TcpSocket : QAbstractSocket::UdpSocket))
{
// we need to create the appropriate socket
if (socket_
&& QAbstractSocket::UnconnectedState != socket_->state ()
&& QAbstractSocket::ClosingState != socket_->state ())
{
// handle re-opening asynchronously
auto connection = QSharedPointer<QMetaObject::Connection>::create ();
*connection = connect (socket_.data (), &QAbstractSocket::disconnected, [this, connection] () {
disconnect (*connection);
check_connection ();
});
// close gracefully
send_report (true);
socket_->close ();
}
else
{
reconnect ();
}
}
}
void handle_socket_error (QAbstractSocket::SocketError e)
{
switch (e)
{
case QAbstractSocket::RemoteHostClosedError:
socket_->disconnectFromHost ();
break;
case QAbstractSocket::TemporaryError:
break;
default:
spots_.clear ();
Q_EMIT self_->errorOccurred (socket_->errorString ());
break;
}
}
void reconnect ()
{
// Using deleteLater for the deleter as we may eventually
// be called from the disconnected handler above.
if (config_->psk_reporter_tcpip ())
{
socket_.reset (new QTcpSocket, &QObject::deleteLater);
send_descriptors_ = 1;
send_receiver_data_ = 1;
}
else
{
socket_.reset (new QUdpSocket, &QObject::deleteLater);
send_descriptors_ = 3;
send_receiver_data_ = 3;
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
connect (socket_.get (), &QAbstractSocket::errorOccurred, this, &PSKReporter::impl::handle_socket_error);
#else
connect (socket_.data (), QOverload<QAbstractSocket::SocketError>::of (&QAbstractSocket::error), this, &PSKReporter::impl::handle_socket_error);
#endif
// use this for pseudo connection with UDP, allows us to use
// QIODevice::write() instead of QUDPSocket::writeDatagram()
socket_->connectToHost (HOST, SERVICE_PORT, QAbstractSocket::WriteOnly);
if (!report_timer_.isActive ())
{
report_timer_.start (MIN_SEND_INTERVAL * 1000);
}
if (!descriptor_timer_.isActive ())
{
descriptor_timer_.start (1 * 60 * 60 * 1000); // hourly
}
}
void stop ()
{
if (socket_)
{
socket_->disconnectFromHost ();
}
descriptor_timer_.stop ();
report_timer_.stop ();
}
void send_report (bool send_residue = false);
void build_preamble (QDataStream&);
bool flushing ()
{
return FLUSH_INTERVAL && !(++flush_counter_ % FLUSH_INTERVAL);
}
PSKReporter * self_;
Configuration const * config_;
QSharedPointer<QAbstractSocket> socket_;
int dns_lookup_id_;
QByteArray payload_;
quint32 sequence_number_;
int send_descriptors_;
// Currently PSK Reporter requires that a receiver data set is sent
// in every data flow. This memeber variable can be used to only
// send that information at session start (3 times for UDP), when it
// changes (3 times for UDP), or once per hour (3 times) if using
// UDP. Uncomment the relevant code to enable that fuctionality.
int send_receiver_data_;
unsigned flush_counter_;
quint32 observation_id_;
QString rx_call_;
QString rx_grid_;
QString rx_ant_;
QString prog_id_;
QByteArray tx_data_;
QByteArray tx_residue_;
struct Spot
{
bool operator == (Spot const& rhs)
{
return
call_ == rhs.call_
&& grid_ == rhs.grid_
&& mode_ == rhs.mode_
&& std::abs (Radio::FrequencyDelta (freq_ - rhs.freq_)) < 50;
}
QString call_;
QString grid_;
int snr_;
Radio::Frequency freq_;
QString mode_;
QDateTime time_;
};
QQueue<Spot> spots_;
QTimer report_timer_;
QTimer descriptor_timer_;
};
#include "PSKReporter.moc"
namespace
{
void writeUtfString (QDataStream& out, QString const& s)
{
auto const& utf = s.toUtf8 ().left (254);
out << quint8 (utf.size ());
out.writeRawData (utf, utf.size ());
}
int num_pad_bytes (int len)
{
return ALIGNMENT_PADDING ? (4 - len % 4) % 4 : 0;
}
void set_length (QDataStream& out, QByteArray& b)
{
// pad with nulls modulo 4
auto pad_len = num_pad_bytes (b.size ());
out.writeRawData (QByteArray {pad_len, '\0'}.constData (), pad_len);
auto pos = out.device ()->pos ();
out.device ()->seek (sizeof (quint16));
// insert length
out << static_cast<quint16> (b.size ());
out.device ()->seek (pos);
}
}
void PSKReporter::impl::build_preamble (QDataStream& message)
{
// Message Header
message
<< quint16 (10u) // Version Number
<< quint16 (0u) // Length (place-holder filled in later)
<< quint32 (0u) // Export Time (place-holder filled in later)
<< ++sequence_number_ // Sequence Number
<< observation_id_; // Observation Domain ID
if (send_descriptors_)
{
--send_descriptors_;
{
// Sender Information descriptor
QByteArray descriptor;
QDataStream out {&descriptor, QIODevice::WriteOnly};
out
<< quint16 (2u) // Template Set ID
<< quint16 (0u) // Length (place-holder)
<< quint16 (0x50e3) // Link ID
<< quint16 (7u) // Field Count
<< quint16 (0x8000 + 1u) // Option 1 Information Element ID (senderCallsign)
<< quint16 (0xffff) // Option 1 Field Length (variable)
<< quint32 (30351u) // Option 1 Enterprise Number
<< quint16 (0x8000 + 5u) // Option 2 Information Element ID (frequency)
<< quint16 (4u) // Option 2 Field Length
<< quint32 (30351u) // Option 2 Enterprise Number
<< quint16 (0x8000 + 6u) // Option 3 Information Element ID (sNR)
<< quint16 (1u) // Option 3 Field Length
<< quint32 (30351u) // Option 3 Enterprise Number
<< quint16 (0x8000 + 10u) // Option 4 Information Element ID (mode)
<< quint16 (0xffff) // Option 4 Field Length (variable)
<< quint32 (30351u) // Option 4 Enterprise Number
<< quint16 (0x8000 + 3u) // Option 5 Information Element ID (senderLocator)
<< quint16 (0xffff) // Option 5 Field Length (variable)
<< quint32 (30351u) // Option 5 Enterprise Number
<< quint16 (0x8000 + 11u) // Option 6 Information Element ID (informationSource)
<< quint16 (1u) // Option 6 Field Length
<< quint32 (30351u) // Option 6 Enterprise Number
<< quint16 (150u) // Option 7 Information Element ID (dateTimeSeconds)
<< quint16 (4u); // Option 7 Field Length
// insert Length and move to payload
set_length (out, descriptor);
message.writeRawData (descriptor.constData (), descriptor.size ());
}
{
// Receiver Information descriptor
QByteArray descriptor;
QDataStream out {&descriptor, QIODevice::WriteOnly};
out
<< quint16 (3u) // Options Template Set ID
<< quint16 (0u) // Length (place-holder)
<< quint16 (0x50e2) // Link ID
<< quint16 (4u) // Field Count
<< quint16 (0u) // Scope Field Count
<< quint16 (0x8000 + 2u) // Option 1 Information Element ID (receiverCallsign)
<< quint16 (0xffff) // Option 1 Field Length (variable)
<< quint32 (30351u) // Option 1 Enterprise Number
<< quint16 (0x8000 + 4u) // Option 2 Information Element ID (receiverLocator)
<< quint16 (0xffff) // Option 2 Field Length (variable)
<< quint32 (30351u) // Option 2 Enterprise Number
<< quint16 (0x8000 + 8u) // Option 3 Information Element ID (decodingSoftware)
<< quint16 (0xffff) // Option 3 Field Length (variable)
<< quint32 (30351u) // Option 3 Enterprise Number
<< quint16 (0x8000 + 9u) // Option 4 Information Element ID (antennaInformation)
<< quint16 (0xffff) // Option 4 Field Length (variable)
<< quint32 (30351u); // Option 4 Enterprise Number
// insert Length
set_length (out, descriptor);
message.writeRawData (descriptor.constData (), descriptor.size ());
}
}
// if (send_receiver_data_)
{
// --send_receiver_data_;
// Receiver information
QByteArray data;
QDataStream out {&data, QIODevice::WriteOnly};
// Set Header
out
<< quint16 (0x50e2) // Template ID
<< quint16 (0u); // Length (place-holder)
// Set data
writeUtfString (out, rx_call_);
writeUtfString (out, rx_grid_);
writeUtfString (out, prog_id_);
writeUtfString (out, rx_ant_);
// insert Length and move to payload
set_length (out, data);
message.writeRawData (data.constData (), data.size ());
}
}
void PSKReporter::impl::send_report (bool send_residue)
{
if (QAbstractSocket::ConnectedState != socket_->state ()) return;
QDataStream message {&payload_, QIODevice::WriteOnly | QIODevice::Append};
QDataStream tx_out {&tx_data_, QIODevice::WriteOnly | QIODevice::Append};
if (!payload_.size ())
{
// Build header, optional descriptors, and receiver information
build_preamble (message);
}
auto flush = flushing () || send_residue;
while (spots_.size () || flush)
{
if (!payload_.size ())
{
// Build header, optional descriptors, and receiver information
build_preamble (message);
}
if (!tx_data_.size () && (spots_.size () || tx_residue_.size ()))
{
// Set Header
tx_out
<< quint16 (0x50e3) // Template ID
<< quint16 (0u); // Length (place-holder)
}
// insert any residue
if (tx_residue_.size ())
{
tx_out.writeRawData (tx_residue_.constData (), tx_residue_.size ());
tx_residue_.clear ();
}
while (spots_.size () || flush)
{
auto tx_data_size = tx_data_.size ();
if (spots_.size ())
{
auto const& spot = spots_.dequeue ();
// Sender information
writeUtfString (tx_out, spot.call_);
tx_out
<< static_cast<quint32> (spot.freq_)
<< static_cast<qint8> (spot.snr_);
writeUtfString (tx_out, spot.mode_);
writeUtfString (tx_out, spot.grid_);
tx_out
<< quint8 (1u) // REPORTER_SOURCE_AUTOMATIC
<< static_cast<quint32> (
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
spot.time_.toSecsSinceEpoch ()
#else
spot.time_.toMSecsSinceEpoch () / 1000
#endif
);
}
auto len = payload_.size () + tx_data_.size ();
len += num_pad_bytes (tx_data_.size ());
len += num_pad_bytes (len);
if (len > MAX_PAYLOAD_LENGTH // our upper datagram size limit
|| (!spots_.size () && len > MIN_PAYLOAD_LENGTH) // spots drained and above lower datagram size limit
|| (flush && !spots_.size ())) // send what we have, possibly no spots
{
if (tx_data_.size ())
{
if (len <= MAX_PAYLOAD_LENGTH)
{
tx_data_size = tx_data_.size ();
}
QByteArray tx {tx_data_.left (tx_data_size)};
QDataStream out {&tx, QIODevice::WriteOnly | QIODevice::Append};
// insert Length
set_length (out, tx);
message.writeRawData (tx.constData (), tx.size ());
}
// insert Length and Export Time
set_length (message, payload_);
message.device ()->seek (2 * sizeof (quint16));
message << static_cast<quint32> (
#if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
QDateTime::currentDateTime ().toSecsSinceEpoch ()
#else
QDateTime::currentDateTime ().toMSecsSinceEpoch () / 1000
#endif
);
// Send data to PSK Reporter site
socket_->write (payload_); // TODO: handle errors
flush = false; // break loop
message.device ()->seek (0u);
payload_.clear (); // Fresh message
// Save unsent spots
tx_residue_ = tx_data_.right (tx_data_.size () - tx_data_size);
tx_out.device ()->seek (0u);
tx_data_.clear ();
break;
}
}
}
}
PSKReporter::PSKReporter (Configuration const * config, QString const& program_info)
: m_ {this, config, program_info}
{
}
PSKReporter::~PSKReporter ()
{
// m_->send_report (true); // send any pending spots
}
void PSKReporter::reconnect ()
{
m_->reconnect ();
}
void PSKReporter::setLocalStation (QString const& call, QString const& gridSquare, QString const& antenna)
{
m_->check_connection ();
if (call != m_->rx_call_ || gridSquare != m_->rx_grid_ || antenna != m_->rx_ant_)
{
m_->send_receiver_data_ = m_->socket_
&& QAbstractSocket::UdpSocket == m_->socket_->socketType () ? 3 : 1;
m_->rx_call_ = call;
m_->rx_grid_ = gridSquare;
m_->rx_ant_ = antenna;
}
}
bool PSKReporter::addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq
, QString const& mode, int snr)
{
m_->check_connection ();
if (m_->socket_ && m_->socket_->isValid ())
{
if (QAbstractSocket::UnconnectedState == m_->socket_->state ())
{
reconnect ();
}
m_->spots_.enqueue ({call, grid, snr, freq, mode, QDateTime::currentDateTimeUtc ()});
return true;
}
return false;
}
void PSKReporter::sendReport (bool last)
{
m_->check_connection ();
if (m_->socket_ && QAbstractSocket::ConnectedState == m_->socket_->state ())
{
m_->send_report (true);
}
if (last)
{
m_->stop ();
}
}

41
Network/PSKReporter.hpp Normal file
View File

@ -0,0 +1,41 @@
#ifndef PSK_REPORTER_HPP_
#define PSK_REPORTER_HPP_
#include <QObject>
#include "Radio.hpp"
#include "pimpl_h.hpp"
class QString;
class Configuration;
class PSKReporter final
: public QObject
{
Q_OBJECT
public:
explicit PSKReporter (Configuration const *, QString const& program_info);
~PSKReporter ();
void reconnect ();
void setLocalStation (QString const& call, QString const& grid, QString const& antenna);
//
// Returns false if PSK Reporter connection is not available
//
bool addRemoteStation (QString const& call, QString const& grid, Radio::Frequency freq, QString const& mode, int snr);
//
// Flush any pending spots to PSK Reporter
//
void sendReport (bool last = false);
Q_SIGNAL void errorOccurred (QString const& reason);
private:
class impl;
pimpl<impl> m_;
};
#endif

View File

@ -1,140 +0,0 @@
// 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>
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
#include <QRandomGenerator>
#endif
#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
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
m_randomId_h = QString("%1").arg(qrand(),8,16,QChar('0'));
#else
m_randomId_h = QString("%1").arg(QRandomGenerator::global ()->generate (), 8, 16, QChar('0'));
#endif
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);
}
}

View File

@ -1,54 +0,0 @@
// -*- Mode: C++ -*-
#ifndef PSK_REPORTER_H
#define PSK_REPORTER_H
#include <QObject>
#include <QString>
#include <QHostAddress>
#include <QQueue>
#include <QHash>
class MessageClient;
class QTimer;
class QHostInfo;
class PSK_Reporter : public QObject
{
Q_OBJECT
public:
explicit PSK_Reporter(MessageClient *, QObject *parent = nullptr);
void setLocalStation(QString call, QString grid, QString antenna, QString programInfo);
void addRemoteStation(QString call, QString grid, QString freq, QString mode, QString snr, QString time);
signals:
public slots:
void sendReport();
private slots:
void dnsLookupResult(QHostInfo info);
private:
QString m_header_h;
QString m_rxInfoDescriptor_h;
QString m_txInfoDescriptor_h;
QString m_randomId_h;
QString m_linkId_h;
QString m_rxCall;
QString m_rxGrid;
QString m_rxAnt;
QString m_progId;
QHostAddress m_pskReporterAddress;
QQueue< QHash<QString,QString> > m_spotQueue;
MessageClient * m_messageClient;
QTimer *reportTimer;
int m_sequenceNumber;
};
#endif // PSK_REPORTER_H

View File

@ -0,0 +1,12 @@
senderCallsign(30351/1)<string>[65535]
receiverCallsign(30351/2)<string>[65535]
senderLocator(30351/3)<string>[65535]
receiverLocator(30351/4)<string>[65535]
frequency(30351/5)<unsigned32>[4]
sNR(30351/6)<signed8>[1]
iMD(30351/7)<signed8>[1]
decoderSoftware(30351/8)<string>[65535]
antennaInformation(30351/9)<string>[65535]
mode(30351/10)<string>[65535]
informationSource(30351/11)<signed8>[1]
persistentIdentifier(30351/12)<string>[65535]

View File

@ -0,0 +1,94 @@
from __future__ import unicode_literals
import sys
import argparse
import logging
import time
import threading
import ipfix.ie
import ipfix.reader
import ipfix.message
import socketserver
import socket
class IPFixDatagramHandler (socketserver.DatagramRequestHandler):
def handle (self):
logging.info (f'Connection from {self.client_address}')
try:
self.server.msg_buffer.from_bytes (self.packet)
for rec in self.server.msg_buffer.namedict_iterator ():
logging.info (f't: {self.server.msg_buffer.get_export_time()}: {rec}')
except:
logging.error ('Unexpected exception:', sys.exc_info ()[0])
class IPFixUdpServer (socketserver.UDPServer):
def __init__ (self, *args, **kwargs):
self.msg_buffer = ipfix.message.MessageBuffer ()
super ().__init__ (*args, **kwargs)
class IPFixStreamHandler (socketserver.StreamRequestHandler):
def handle (self):
logging.info (f'Connection from {self.client_address}')
try:
msg_reader = ipfix.reader.from_stream (self.rfile)
for rec in msg_reader.namedict_iterator ():
logging.info (f't: {msg_reader.msg.get_export_time()}: {rec}')
logging.info (f'{self.client_address} closed their connection')
except ConnectionResetError:
logging.info (f'{self.client_address} connection reset')
except TimeoutError:
logging.info (f'{self.client_address} connection timed out')
except:
logging.error ('Unexpected exception:', sys.exc_info ()[0])
if __name__ == "__main__":
INTERFACE, PORT = '', 4739
ap = argparse.ArgumentParser (description='Dump IPFIX data collectedover UDP')
ap.add_argument ('-l', '--log', metavar='loglevel', default='WARNING', help='logging level')
ap.add_argument ('-s', '--spec', metavar='specfile', help='iespec file to read')
args = ap.parse_args ()
log_level = getattr (logging, args.log.upper (), None)
if not isinstance (log_level, int):
raise ValueError (f'Invalif log level: {log_level}')
logging.basicConfig (
level=log_level
, format='[%(levelname)s] (%(threadName)-10s) %(message)s'
,
)
logging.info (f'Starting IPFix servers on service port: {PORT}')
ipfix.ie.use_iana_default ()
ipfix.ie.use_5103_default ()
if args.spec:
ipfix.ie.use_specfile (args.spec)
udp_server = IPFixUdpServer ((INTERFACE, PORT), IPFixDatagramHandler)
udp_thread = threading.Thread (name='UDP Server', target=udp_server.serve_forever)
tcp_server = socketserver.TCPServer ((INTERFACE, PORT), IPFixStreamHandler)
tcp_server.allow_reuse_address = True
tcp_server.socket.setsockopt (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
tcp_thread = threading.Thread (name='TCP/IP Server', target=tcp_server.serve_forever)
udp_thread.start ()
tcp_thread.start ()
try:
while True:
time.sleep (1000)
except KeyboardInterrupt:
logging.warning ('Closing down servers')
udp_server.shutdown ()
tcp_server.shutdown ()
udp_thread.join ()
tcp_thread.join ()
logging.info ('Servers closed')

View File

@ -0,0 +1,64 @@
The following changes since commit e487dfbead9965c1a251d110dc55039a7ba4afef:
sphinx doc update (2017-09-20 16:07:22 +0200)
are available in the Git repository at:
git@github.com:g4wjs/python-ipfix.git varlen-and-padding
for you to fetch changes up to 6dcc106f22d25ac21b27f8ccb9b82be12e7eb18e:
Parse and emit data elements correctly when variable length items included (2020-06-19 19:53:33 +0100)
----------------------------------------------------------------
Bill Somerville (2):
Allow for alignment padding when parsing sets
Parse and emit data elements correctly when variable length items included
ipfix/message.py | 3 ++-
ipfix/template.py | 4 ++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/ipfix/message.py b/ipfix/message.py
index 0af2fad..71c1328 100644
--- a/ipfix/message.py
+++ b/ipfix/message.py
@@ -398,7 +398,7 @@ class MessageBuffer(object):
offset += _sethdr_st.size # skip set header in decode
if setid == template.TEMPLATE_SET_ID or\
setid == template.OPTIONS_SET_ID:
- while offset < setend:
+ while offset + 4 < setend: # allow for padding up to 4 byte alignment
(tmpl, offset) = template.decode_template_from(
self.mbuf, offset, setid)
# FIXME handle withdrawal
@@ -431,6 +431,7 @@ class MessageBuffer(object):
# KeyError on template lookup - unknown data set
self.unknown_data_set_hook(self,
self.mbuf[offset-_sethdr_st.size:setend])
+ offset = setend # real end may be greater that accumulated offset
def namedict_iterator(self):
"""
diff --git a/ipfix/template.py b/ipfix/template.py
index 1203e55..26cd8c1 100644
--- a/ipfix/template.py
+++ b/ipfix/template.py
@@ -187,7 +187,7 @@ class Template(object):
offset += packplan.st.size
# short circuit on no varlen
- if not self.varlenslice:
+ if self.varlenslice is None:
return (vals, offset)
# direct iteration over remaining IEs
@@ -239,7 +239,7 @@ class Template(object):
offset += packplan.st.size
# shortcircuit no varlen
- if not self.varlenslice:
+ if self.varlenslice is None:
return offset
# direct iteration over remaining IEs

View File

@ -8,6 +8,8 @@
#include <QTimer>
#include <QFile>
#include <QRegExp>
#include <QRegularExpression>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
@ -18,215 +20,314 @@
namespace
{
char const * const wsprNetUrl = "http://wsprnet.org/post?";
// char const * const wsprNetUrl = "http://127.0.0.1/post?";
char const * const wsprNetUrl = "http://wsprnet.org/post/";
//char const * const wsprNetUrl = "http://127.0.0.1:5000/post/";
//
// tested with this python REST mock of WSPRNet.org
//
/*
# Mock WSPRNet.org RESTful API
from flask import Flask, request, url_for
from flask_restful import Resource, Api
app = Flask(__name__)
@app.route ('/post/', methods=['GET', 'POST'])
def spot ():
if request.method == 'POST':
print (request.form)
return "1 spot(s) added"
with app.test_request_context ():
print (url_for ('spot'))
*/
// regexp to parse FST4W decodes
QRegularExpression fst4_re {R"(
(?<time>\d{4})
\s+(?<db>[-+]?\d+)
\s+(?<dt>[-+]?\d+\.\d+)
\s+(?<freq>\d+)
\s+`
\s+<?(?<call>[A-Z0-9/]+)>?(?:\s(?<grid>[A-R]{2}[0-9]{2}(?:[A-X]{2})?))?(?:\s+(?<dBm>\d+))?
)", QRegularExpression::ExtendedPatternSyntaxOption};
// regexp to parse wspr_spots.txt from wsprd
//
// 130223 2256 7 -21 -0.3 14.097090 DU1MGA PK04 37 0 40 0
// Date Time Sync dBm DT Freq Msg
// 1 2 3 4 5 6 -------7------ 8 9 10
QRegularExpression wspr_re(R"(^(\d+)\s+(\d+)\s+(\d+)\s+([+-]?\d+)\s+([+-]?\d+\.\d+)\s+(\d+\.\d+)\s+([^ ].*[^ ])\s+([+-]?\d+)\s+([+-]?\d+)\s+([+-]?\d+))");
};
WSPRNet::WSPRNet(QNetworkAccessManager * manager, QObject *parent)
: QObject{parent}
, networkManager {manager}
, uploadTimer {new QTimer {this}}
, m_urlQueueSize {0}
WSPRNet::WSPRNet (QNetworkAccessManager * manager, QObject *parent)
: QObject {parent}
, network_manager_ {manager}
, spots_to_send_ {0}
{
connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkReply(QNetworkReply*)));
connect( uploadTimer, SIGNAL(timeout()), this, SLOT(work()));
connect (network_manager_, &QNetworkAccessManager::finished, this, &WSPRNet::networkReply);
connect (&upload_timer_, &QTimer::timeout, this, &WSPRNet::work);
}
void WSPRNet::upload(QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, QString const& tpct, QString const& dbm, QString const& version,
QString const& fileName)
void WSPRNet::upload (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& version, QString const& fileName)
{
m_call = call;
m_grid = grid;
m_rfreq = rfreq;
m_tfreq = tfreq;
m_mode = mode;
m_tpct = tpct;
m_dbm = dbm;
m_vers = version;
m_file = fileName;
m_call = call;
m_grid = grid;
m_rfreq = rfreq;
m_tfreq = tfreq;
m_mode = mode;
TR_period_ = TR_period;
m_tpct = tpct;
m_dbm = dbm;
m_vers = version;
m_file = fileName;
// Open the wsprd.out file
QFile wsprdOutFile(fileName);
if (!wsprdOutFile.open(QIODevice::ReadOnly | QIODevice::Text) ||
wsprdOutFile.size() == 0) {
urlQueue.enqueue( wsprNetUrl + urlEncodeNoSpot());
m_uploadType = 1;
uploadTimer->start(200);
return;
// Open the wsprd.out file
QFile wsprdOutFile (fileName);
if (!wsprdOutFile.open (QIODevice::ReadOnly | QIODevice::Text) || !wsprdOutFile.size ())
{
spot_queue_.enqueue (urlEncodeNoSpot ());
m_uploadType = 1;
}
// Read the contents
while (!wsprdOutFile.atEnd()) {
QHash<QString,QString> query;
if ( decodeLine(wsprdOutFile.readLine(), query) ) {
// Prevent reporting data ouside of the current frequency band
float f = fabs(m_rfreq.toFloat() - query["tqrg"].toFloat());
if (f < 0.0002) {
urlQueue.enqueue( wsprNetUrl + urlEncodeSpot(query));
m_uploadType = 2;
else
{
// Read the contents
while (!wsprdOutFile.atEnd())
{
SpotQueue::value_type query;
if (decodeLine (wsprdOutFile.readLine(), query))
{
// Prevent reporting data ouside of the current frequency band
float f = fabs (m_rfreq.toFloat() - query.queryItemValue ("tqrg", QUrl::FullyDecoded).toFloat());
if (f < 0.0002)
{
spot_queue_.enqueue(urlEncodeSpot (query));
m_uploadType = 2;
}
}
}
}
}
m_urlQueueSize = urlQueue.size();
uploadTimer->start(200);
spots_to_send_ = spot_queue_.size ();
upload_timer_.start (200);
}
void WSPRNet::networkReply(QNetworkReply *reply)
void WSPRNet::post (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& version, QString const& decode_text)
{
m_call = call;
m_grid = grid;
m_rfreq = rfreq;
m_tfreq = tfreq;
m_mode = mode;
TR_period_ = TR_period;
m_tpct = tpct;
m_dbm = dbm;
m_vers = version;
if (!decode_text.size ())
{
if (!spot_queue_.size ())
{
spot_queue_.enqueue (urlEncodeNoSpot ());
m_uploadType = 1;
}
spots_to_send_ = spot_queue_.size ();
upload_timer_.start (200);
}
else
{
auto const& match = fst4_re.match (decode_text);
if (match.hasMatch ())
{
SpotQueue::value_type query;
// Prevent reporting data ouside of the current frequency band
auto tqrg = match.captured ("freq").toInt ();
if (tqrg >= 1400 && tqrg <= 1600)
{
query.addQueryItem ("function", "wspr");
// use time as at 3/4 of T/R period before current to
// ensure date is in Rx period
auto const& date = QDateTime::currentDateTimeUtc ().addSecs (-TR_period * 3. / 4.).date ();
query.addQueryItem ("date", date.toString ("yyMMdd"));
query.addQueryItem ("time", match.captured ("time"));
query.addQueryItem ("sig", match.captured ("db"));
query.addQueryItem ("dt", match.captured ("dt"));
query.addQueryItem ("tqrg", QString::number (rfreq.toDouble () + (tqrg - 1500) / 1e6, 'f', 6));
query.addQueryItem ("tcall", match.captured ("call"));
query.addQueryItem ("drift", "0");
query.addQueryItem ("tgrid", match.captured ("grid"));
query.addQueryItem ("dbm", match.captured ("dBm"));
spot_queue_.enqueue (urlEncodeSpot (query));
m_uploadType = 2;
}
}
}
}
void WSPRNet::networkReply (QNetworkReply * reply)
{
// check if request was ours
if (m_outstandingRequests.removeOne (reply)) {
if (QNetworkReply::NoError != reply->error ()) {
Q_EMIT uploadStatus (QString {"Error: %1"}.arg (reply->error ()));
// not clearing queue or halting queuing as it may be a transient
// one off request error
}
else {
QString serverResponse = reply->readAll();
if( m_uploadType == 2) {
if (!serverResponse.contains(QRegExp("spot\\(s\\) added"))) {
emit uploadStatus(QString {"Upload Failed: %1"}.arg (serverResponse));
urlQueue.clear();
uploadTimer->stop();
if (m_outstandingRequests.removeOne (reply))
{
if (QNetworkReply::NoError != reply->error ())
{
Q_EMIT uploadStatus (QString {"Error: %1"}.arg (reply->error ()));
// not clearing queue or halting queuing as it may be a
// transient one off request error
}
}
else
{
QString serverResponse = reply->readAll ();
if (m_uploadType == 2)
{
if (!serverResponse.contains(QRegExp("spot\\(s\\) added")))
{
Q_EMIT uploadStatus (QString {"Upload Failed: %1"}.arg (serverResponse));
spot_queue_.clear ();
upload_timer_.stop ();
}
}
if (urlQueue.isEmpty()) {
emit uploadStatus("done");
QFile::remove(m_file);
uploadTimer->stop();
}
if (!spot_queue_.size ())
{
Q_EMIT uploadStatus("done");
QFile f {m_file};
if (f.exists ()) f.remove ();
upload_timer_.stop ();
}
}
qDebug () << QString {"WSPRnet.org %1 outstanding requests"}.arg (m_outstandingRequests.size ());
// delete request object instance on return to the event loop otherwise it is leaked
reply->deleteLater ();
}
}
bool WSPRNet::decodeLine (QString const& line, SpotQueue::value_type& query)
{
auto const& rx_match = wspr_re.match (line);
if (rx_match.hasMatch ()) {
int msgType = 0;
QString msg = rx_match.captured (7);
QString call, grid, dbm;
QRegularExpression msgRx;
// Check for Message Type 1
msgRx.setPattern(R"(^([A-Z0-9]{3,6})\s+([A-R]{2}\d{2})\s+(\d+))");
auto match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 1;
call = match.captured (1);
grid = match.captured (2);
dbm = match.captured (3);
}
qDebug () << QString {"WSPRnet.org %1 outstanding requests"}.arg (m_outstandingRequests.size ());
// Check for Message Type 2
msgRx.setPattern(R"(^([A-Z0-9/]+)\s+(\d+))");
match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 2;
call = match.captured (1);
grid = "";
dbm = match.captured (2);
}
// delete request object instance on return to the event loop otherwise it is leaked
reply->deleteLater ();
// Check for Message Type 3
msgRx.setPattern(R"(^<([A-Z0-9/]+)>\s+([A-R]{2}\d{2}[A-X]{2})\s+(\d+))");
match = msgRx.match (msg);
if (match.hasMatch ()) {
msgType = 3;
call = match.captured (1);
grid = match.captured (2);
dbm = match.captured (3);
}
// Unknown message format
if (!msgType) {
return false;
}
query.addQueryItem ("function", "wspr");
query.addQueryItem ("date", rx_match.captured (1));
query.addQueryItem ("time", rx_match.captured (2));
query.addQueryItem ("sig", rx_match.captured (4));
query.addQueryItem ("dt", rx_match.captured(5));
query.addQueryItem ("drift", rx_match.captured(8));
query.addQueryItem ("tqrg", rx_match.captured(6));
query.addQueryItem ("tcall", call);
query.addQueryItem ("tgrid", grid);
query.addQueryItem ("dbm", dbm);
} else {
return false;
}
return true;
}
bool WSPRNet::decodeLine(QString const& line, QHash<QString,QString> &query)
auto WSPRNet::urlEncodeNoSpot () -> SpotQueue::value_type
{
// 130223 2256 7 -21 -0.3 14.097090 DU1MGA PK04 37 0 40 0
// Date Time Sync dBm DT Freq Msg
// 1 2 3 4 5 6 -------7------ 8 9 10
QRegExp rx("^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+\\.\\d+)\\s+(\\d+\\.\\d+)\\s+(.*)\\s+([+-]?\\d+)\\s+([+-]?\\d+)\\s+([+-]?\\d+)");
if (rx.indexIn(line) != -1) {
int msgType = 0;
QString msg = rx.cap(7);
msg.remove(QRegExp("\\s+$"));
msg.remove(QRegExp("^\\s+"));
QString call, grid, dbm;
QRegExp msgRx;
// Check for Message Type 1
msgRx.setPattern("^([A-Z0-9]{3,6})\\s+([A-Z]{2}\\d{2})\\s+(\\d+)");
if (msgRx.indexIn(msg) != -1) {
msgType = 1;
call = msgRx.cap(1);
grid = msgRx.cap(2);
dbm = msgRx.cap(3);
}
// Check for Message Type 2
msgRx.setPattern("^([A-Z0-9/]+)\\s+(\\d+)");
if (msgRx.indexIn(msg) != -1) {
msgType = 2;
call = msgRx.cap(1);
grid = "";
dbm = msgRx.cap(2);
}
// Check for Message Type 3
msgRx.setPattern("^<([A-Z0-9/]+)>\\s+([A-Z]{2}\\d{2}[A-Z]{2})\\s+(\\d+)");
if (msgRx.indexIn(msg) != -1) {
msgType = 3;
call = msgRx.cap(1);
grid = msgRx.cap(2);
dbm = msgRx.cap(3);
}
// Unknown message format
if (!msgType) {
return false;
}
query["function"] = "wspr";
query["date"] = rx.cap(1);
query["time"] = rx.cap(2);
query["sig"] = rx.cap(4);
query["dt"] = rx.cap(5);
query["drift"] = rx.cap(8);
query["tqrg"] = rx.cap(6);
query["tcall"] = call;
query["tgrid"] = grid;
query["dbm"] = dbm;
} else {
return false;
SpotQueue::value_type query;
query.addQueryItem ("function", "wsprstat");
query.addQueryItem ("rcall", m_call);
query.addQueryItem ("rgrid", m_grid);
query.addQueryItem ("rqrg", m_rfreq);
query.addQueryItem ("tpct", m_tpct);
query.addQueryItem ("tqrg", m_tfreq);
query.addQueryItem ("dbm", m_dbm);
query.addQueryItem ("version", m_vers);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
return true;
return query;;
}
QString WSPRNet::urlEncodeNoSpot()
auto WSPRNet::urlEncodeSpot (SpotQueue::value_type& query) -> SpotQueue::value_type
{
QString queryString;
queryString += "function=wsprstat&";
queryString += "rcall=" + m_call + "&";
queryString += "rgrid=" + m_grid + "&";
queryString += "rqrg=" + m_rfreq + "&";
queryString += "tpct=" + m_tpct + "&";
queryString += "tqrg=" + m_tfreq + "&";
queryString += "dbm=" + m_dbm + "&";
queryString += "version=" + m_vers;
if(m_mode=="WSPR") queryString += "&mode=2";
if(m_mode=="WSPR-15") queryString += "&mode=15";
return queryString;;
}
QString WSPRNet::urlEncodeSpot(QHash<QString,QString> const& query)
{
QString queryString;
queryString += "function=" + query["function"] + "&";
queryString += "rcall=" + m_call + "&";
queryString += "rgrid=" + m_grid + "&";
queryString += "rqrg=" + m_rfreq + "&";
queryString += "date=" + query["date"] + "&";
queryString += "time=" + query["time"] + "&";
queryString += "sig=" + query["sig"] + "&";
queryString += "dt=" + query["dt"] + "&";
queryString += "drift=" + query["drift"] + "&";
queryString += "tqrg=" + query["tqrg"] + "&";
queryString += "tcall=" + query["tcall"] + "&";
queryString += "tgrid=" + query["tgrid"] + "&";
queryString += "dbm=" + query["dbm"] + "&";
queryString += "version=" + m_vers;
if(m_mode=="WSPR") queryString += "&mode=2";
if(m_mode=="WSPR-15") queryString += "&mode=15";
return queryString;
query.addQueryItem ("version", m_vers);
query.addQueryItem ("rcall", m_call);
query.addQueryItem ("rgrid", m_grid);
query.addQueryItem ("rqrg", m_rfreq);
if (m_mode == "WSPR") query.addQueryItem ("mode", "2");
if (m_mode == "WSPR-15") query.addQueryItem ("mode", "15");
if (m_mode == "FST4W")
{
query.addQueryItem ("mode", QString::number (static_cast<int> ((TR_period_ / 60.)+.5)));
}
return query;
}
void WSPRNet::work()
{
if (!urlQueue.isEmpty()) {
if (spots_to_send_ && spot_queue_.size ())
{
#if QT_VERSION < QT_VERSION_CHECK (5, 15, 0)
if (QNetworkAccessManager::Accessible != networkManager->networkAccessible ()) {
// try and recover network access for QNAM
networkManager->setNetworkAccessible (QNetworkAccessManager::Accessible);
}
if (QNetworkAccessManager::Accessible != network_manager_->networkAccessible ()) {
// try and recover network access for QNAM
network_manager_->setNetworkAccessible (QNetworkAccessManager::Accessible);
}
#endif
QUrl url(urlQueue.dequeue());
QNetworkRequest request(url);
m_outstandingRequests << networkManager->get(request);
emit uploadStatus(QString {"Uploading Spot %1/%2"}.arg (m_urlQueueSize - urlQueue.size()).arg (m_urlQueueSize));
} else {
uploadTimer->stop();
}
QNetworkRequest request (QUrl {wsprNetUrl});
request.setHeader (QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
auto const& spot = spot_queue_.dequeue ();
m_outstandingRequests << network_manager_->post (request, spot.query (QUrl::FullyEncoded).toUtf8 ());
Q_EMIT uploadStatus(QString {"Uploading Spot %1/%2"}.arg (spots_to_send_ - spot_queue_.size()).arg (spots_to_send_));
}
else
{
upload_timer_.stop ();
}
}
void WSPRNet::abortOutstandingRequests () {
urlQueue.clear ();
spot_queue_.clear ();
for (auto& request : m_outstandingRequests) {
request->abort ();
}
m_urlQueueSize = 0;
}

View File

@ -2,45 +2,58 @@
#define WSPRNET_H
#include <QObject>
#include <QTimer>
#include <QString>
#include <QList>
#include <QHash>
#include <QUrlQuery>
#include <QQueue>
class QNetworkAccessManager;
class QTimer;
class QNetworkReply;
class WSPRNet : public QObject
{
Q_OBJECT;
Q_OBJECT
using SpotQueue = QQueue<QUrlQuery>;
public:
explicit WSPRNet(QNetworkAccessManager *, QObject *parent = nullptr);
void upload(QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, QString const& tpct, QString const& dbm, QString const& version,
QString const& fileName);
static bool decodeLine(QString const& line, QHash<QString,QString> &query);
explicit WSPRNet (QNetworkAccessManager *, QObject *parent = nullptr);
void upload (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_peirod, QString const& tpct, QString const& dbm,
QString const& version, QString const& fileName);
void post (QString const& call, QString const& grid, QString const& rfreq, QString const& tfreq,
QString const& mode, float TR_period, QString const& tpct, QString const& dbm,
QString const& version, QString const& decode_text = QString {});
signals:
void uploadStatus(QString);
void uploadStatus (QString);
public slots:
void networkReply(QNetworkReply *);
void work();
void abortOutstandingRequests ();
void networkReply (QNetworkReply *);
void work ();
void abortOutstandingRequests ();
private:
QNetworkAccessManager *networkManager;
QList<QNetworkReply *> m_outstandingRequests;
QString m_call, m_grid, m_rfreq, m_tfreq, m_mode, m_tpct, m_dbm, m_vers, m_file;
QQueue<QString> urlQueue;
QTimer *uploadTimer;
int m_urlQueueSize;
int m_uploadType;
bool decodeLine (QString const& line, SpotQueue::value_type& query);
SpotQueue::value_type urlEncodeNoSpot ();
SpotQueue::value_type urlEncodeSpot (SpotQueue::value_type& spot);
QString urlEncodeNoSpot();
QString urlEncodeSpot(QHash<QString,QString> const& spot);
QNetworkAccessManager * network_manager_;
QList<QNetworkReply *> m_outstandingRequests;
QString m_call;
QString m_grid;;
QString m_rfreq;
QString m_tfreq;
QString m_mode;
QString m_tpct;
QString m_dbm;
QString m_vers;
QString m_file;
float TR_period_;
int spots_to_send_;
SpotQueue spot_queue_;
QTimer upload_timer_;
int m_uploadType;
};
#endif // WSPRNET_H

View File

@ -13,6 +13,32 @@
Copyright 2001 - 2020 by Joe Taylor, K1JT.
Release: WSJT-X 2.3.0-rc1
Sept 28, 2020
-------------------------
WSJT-X 2.3.0 is a program upgrade offering two new modes designed
especially for use on the LF and MF bands. FST4 is for 2-way QSOs,
and FST4W is for WSPR-like transmissions. Both modes offer a range of
options for T/R sequence lengths and threshold decoding sensitivities
extending well into the -40 dB range. Early tests have shown these
modes frequently spanning intercontinental distances on the 2200 m and
630 m bands. Further details and operating hints can be found in the
"Quick-Start Guide to FST4 and FST4W", posted on the WSJT web site:
https://physics.princeton.edu/pulsar/k1jt/FST4_Quick_Start.pdf
WSJT-X 2.3.0-rc1 is a beta-quality release candidate for a program
upgrade that provides a number of new features and capabilities.
These include:
- New modes FST4 and FST4W
- The *On Dx Echo* Doppler compensation method has been modified in
response to feedback from Users. Basic functionality is unchanged.
See the User Guide (Section 8.1) for more information.
Release: WSJT-X 2.2.2
June 22, 2020
---------------------

View File

@ -88,7 +88,7 @@ SampleDownloader::impl::impl (QSettings * settings
, directory_ {configuration, network_manager}
, button_box_ {QDialogButtonBox::Close, Qt::Vertical}
{
setWindowTitle (windowTitle () + ' ' + tr (title));
setWindowTitle (windowTitle () + ' ' + tr ("Download Samples"));
resize (500, 600);
{
SettingsGroup g {settings_, title};
@ -111,7 +111,7 @@ SampleDownloader::impl::impl (QSettings * settings
details_layout_.setMargin (0);
details_layout_.addRow (tr ("Base URL for samples:"), &url_line_edit_);
details_layout_.addRow (tr ("Only use HTTP:"), &http_only_check_box_);
http_only_check_box_.setToolTip (tr ("Check this is you get SSL/TLS errors"));
http_only_check_box_.setToolTip (tr ("Check this if you get SSL/TLS errors"));
details_widget_.setLayout (&details_layout_);
main_layout_.addLayout (&left_layout_, 0, 0);

View File

@ -409,7 +409,7 @@ QString DXLabSuiteCommanderTransceiver::command_with_reply (QString const& cmd,
{
TRACE_CAT ("DXLabSuiteCommanderTransceiver", "failed to send command:" << commander_->errorString ());
throw error {
tr ("DX Lab Suite Commander failed to send command \"%1\": %2\n")
tr ("DX Lab Suite Commander send command failed \"%1\": %2\n")
.arg (cmd)
.arg (commander_->errorString ())
};

View File

@ -606,6 +606,13 @@ int HamlibTransceiver::do_start ()
}
}
#if HAVE_HAMLIB_CACHING
// we must disable Hamlib caching because it lies about frequency
// for less than 1 Hz resolution rigs
auto orig_cache_timeout = rig_get_cache_timeout_ms (rig_.data (), HAMLIB_CACHE_ALL);
rig_set_cache_timeout_ms (rig_.data (), HAMLIB_CACHE_ALL, 0);
#endif
int resolution {0};
if (freq_query_works_)
{
@ -646,6 +653,11 @@ int HamlibTransceiver::do_start ()
resolution = -1; // best guess
}
#if HAVE_HAMLIB_CACHING
// revert Hamlib cache timeout
rig_set_cache_timeout_ms (rig_.data (), HAMLIB_CACHE_ALL, orig_cache_timeout);
#endif
do_poll ();
TRACE_CAT ("HamlibTransceiver", "exit" << state () << "reversed =" << reversed_ << "resolution = " << resolution);
@ -672,7 +684,7 @@ void HamlibTransceiver::do_stop ()
TRACE_CAT ("HamlibTransceiver", "state:" << state () << "reversed =" << reversed_);
}
auto HamlibTransceiver::get_vfos (bool for_split) const -> std::tuple<vfo_t, vfo_t>
std::tuple<vfo_t, vfo_t> HamlibTransceiver::get_vfos (bool for_split) const
{
if (get_vfo_works_ && rig_->caps->get_vfo)
{

View File

@ -25,8 +25,9 @@ namespace
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Operator"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Call"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "My Grid"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Exchange Sent"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Exchange Rcvd"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Exch Sent"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Exch Rcvd"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Prop"),
QT_TRANSLATE_NOOP ("MessageAggregatorMainWindow", "Comments"),
};
}
@ -212,7 +213,8 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
, QString const& tx_power, QString const& comments
, QString const& name, QDateTime time_on, QString const& operator_call
, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd)
, QString const& exchange_sent, QString const& exchange_rcvd
, QString const& prop_mode)
{
QList<QStandardItem *> row;
row << new QStandardItem {time_on.toString ("dd-MMM-yyyy hh:mm:ss")}
@ -230,6 +232,7 @@ void MessageAggregatorMainWindow::log_qso (QString const& /*id*/, QDateTime time
<< new QStandardItem {my_grid}
<< new QStandardItem {exchange_sent}
<< new QStandardItem {exchange_rcvd}
<< new QStandardItem {prop_mode}
<< new QStandardItem {comments};
log_->appendRow (row);
log_table_view_->resizeColumnsToContents ();

View File

@ -33,7 +33,7 @@ public:
, QString const& report_received, QString const& tx_power, QString const& comments
, QString const& name, QDateTime time_on, QString const& operator_call
, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd);
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
private:
void add_client (QString const& id, QString const& version, QString const& revision);

View File

@ -345,9 +345,10 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
QByteArray my_grid;
QByteArray exchange_sent;
QByteArray exchange_rcvd;
QByteArray prop_mode;
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;
>> exchange_sent >> exchange_rcvd >> prop_mode;
if (check_status (in) != Fail)
{
Q_EMIT self_->qso_logged (id, time_off, QString::fromUtf8 (dx_call), QString::fromUtf8 (dx_grid)
@ -356,7 +357,7 @@ void MessageServer::impl::parse_message (QHostAddress const& sender, port_type s
, 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));
, QString::fromUtf8 (exchange_rcvd), QString::fromUtf8 (prop_mode));
}
}
break;

View File

@ -106,7 +106,7 @@ public:
, QString const& report_received, QString const& tx_power, QString const& comments
, QString const& name, QDateTime time_on, QString const& operator_call
, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd);
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode);
Q_SIGNAL void decodes_cleared (QString const& id);
Q_SIGNAL void logged_ADIF (QString const& id, QByteArray const& ADIF);

View File

@ -102,7 +102,7 @@ public:
, QString const& report_received, QString const& tx_power
, QString const& comments, QString const& name, QDateTime time_on
, QString const& operator_call, QString const& my_call, QString const& my_grid
, QString const& exchange_sent, QString const& exchange_rcvd)
, QString const& exchange_sent, QString const& exchange_rcvd, QString const& prop_mode)
{
if (client_id == id_)
{
@ -111,12 +111,13 @@ public:
<< "rpt_rcvd:" << report_received << "Tx_pwr:" << tx_power << "comments:" << comments
<< "name:" << name << "operator_call:" << operator_call << "my_call:" << my_call
<< "my_grid:" << my_grid << "exchange_sent:" << exchange_sent
<< "exchange_rcvd:" << exchange_rcvd;
<< "exchange_rcvd:" << exchange_rcvd << "prop_mode:" << prop_mode;
std::cout << QByteArray {80, '-'}.data () << '\n';
std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11")
std::cout << tr ("%1: Logged %2 grid: %3 power: %4 sent: %5 recd: %6 freq: %7 time_off: %8 op: %9 my_call: %10 my_grid: %11 exchange_sent: %12 exchange_rcvd: %13 comments: %14 prop_mode: %15")
.arg (id_).arg (dx_call).arg (dx_grid).arg (tx_power).arg (report_sent).arg (report_received)
.arg (dial_frequency).arg (time_off.toString("yyyy-MM-dd hh:mm:ss.z")).arg (operator_call)
.arg (my_call).arg (my_grid).toStdString ()
.arg (my_call).arg (my_grid).arg (exchange_sent).arg (exchange_rcvd)
.arg (comments).arg (prop_mode).toStdString ()
<< std::endl;
}
}

View File

@ -1,6 +1,6 @@
# Version number components
set (WSJTX_VERSION_MAJOR 2)
set (WSJTX_VERSION_MINOR 2)
set (WSJTX_VERSION_PATCH 2)
#set (WSJTX_RC 1) # 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 3)
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

View File

@ -220,7 +220,7 @@ public:
, carry_ {false}
, seed_ {{rand (), rand (), rand (), rand (), rand (), rand (), rand (), rand ()}}
, gen_ {seed_}
, dist_ {1, 100}
, dist_ {0, 99}
{
auto num_bands = configuration_->bands ()->rowCount ();
for (auto& flags : bands_)

View File

@ -2,7 +2,7 @@
#define COMMONS_H
#define NSMAX 6827
#define NTMAX 300
#define NTMAX 30*60
#define RX_SAMPLE_RATE 12000
#ifdef __cplusplus
@ -85,7 +85,7 @@ extern struct {
} echocom_;
extern struct {
float wave[606720];
float wave[(160+2)*134400*4]; /* (nsym+2)*nsps scaled up to 48kHz */
int nslots;
int nfreq;
int i3bit[5];

2046
cty.dat

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,27 @@
Here are the "displayWidgets()" strings for WSJT-X modes
1 2 3
012345678901234567890123456789012
----------------------------------------------
JT4 111010000000110000110000000000000
JT4/VHF 111110010010110110111100000000000
JT9 111010000000111000010000000000001
JT9/VHF 111110101000111110010000000000000
JT9+JT65 111010000001111000010000000000001
JT65 111010000000111000010000000000001
JT65/VHF 111110010000110110101100010000000
QRA64 111110010110110110000000001000000
ISCAT 100111000000000110000000000000000
MSK144 101111110100000000010001000000000
WSPR 000000000000000001010000000000000
Echo 000000000000000000000010000000000
FCal 001101000000000000000000000001000
FT8 111010000100111000010000100110001
FT8/VHF 111010000100111000010000100110001
FT8/Fox 111010000100111000010000000000100
FT8/Hound 111010000100111000010000000000110
012345678901234567890123456789012345
------------------------------------------------
JT4 111010000000110000110000000000000000
JT4/VHF 111110010010110110111100000000000000
JT9 111010000000111000010000000000001000
JT9/VHF 111110101000111110010000000000000000
JT9+JT65 111010000001111000010000000000001000
JT65 111010000000111000010000000000001000
JT65/VHF 111110010000110110101100010000000000
QRA64 111110010110110110000000001000000000
ISCAT 100111000000000110000000000000000000
MSK144 101111110100000000010001000000000000
WSPR 000000000000000001010000000000000000
FST4 111111000100111000010000000100000011
FST4W 000000000000000001010000000000000100
Echo 000000000000000000000010000000000000
FCal 001101000000000000000000000001000000
FT8 111010000100111000010000100110001000
FT8/VHF 111010000100111000010000100110001000
FT8/Fox 111010000100111000010000000000100000
FT8/Hound 111010000100111000010000000000110000
----------------------------------------------
1 2 3
012345678901234567890123456789012
@ -60,3 +62,6 @@ Mapping of column numbers to widgets
30. labDXped
31. cbRxAll
32. cbCQonly
33. sbTR_FST4W
34. sbF_Low
35. sbF_High

View File

@ -30,8 +30,7 @@ set (UG_SRCS
install-mac.adoc
install-windows.adoc
introduction.adoc
measurement_tools.adoc
protocols.adoc
intro_subsections.adoc
logging.adoc
make-qso.adoc
measurement_tools.adoc
@ -53,6 +52,8 @@ set (UG_SRCS
tutorial-example2.adoc
tutorial-example3.adoc
tutorial-example4.adoc
tutorial-example5.adoc
tutorial-example6.adoc
tutorial-main-window.adoc
tutorial-wide-graph-settings.adoc
utilities.adoc
@ -82,6 +83,9 @@ set (UG_IMGS
images/FreqCal_Graph.png
images/FreqCal_Results.png
images/freemsg.png
images/FST4_center.png
images/FST4_Decoding_Limits.png
images/FST4W_RoundRobin.png
images/ft4_decodes.png
images/ft4_waterfall.png
images/ft8_decodes.png

View File

@ -5,10 +5,10 @@ Usage example: include::../common/links.adoc[]
Syntax: [link-id] [link] [displayed text]
Example:
:pskreporter: http://pskreporter.info/pskmap.html[PSK Reporter]
:pskreporter: https://pskreporter.info/pskmap.html[PSK Reporter]
[link-id] = :pskreporter:
[link] http://pskreporter.info/pskmap.html
[link] https://pskreporter.info/pskmap.html
[displayed text] PSK Reporter
Perform searches from the doc root directory: doc
@ -42,53 +42,53 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
// General URL's
//:launchpadac6sl: https://launchpad.net/~jnogatch/+archive/wsjtx[WSJT-X Linux Packages]
:alarmejt: http://f5jmh.free.fr/index.php?page=english[AlarmeJT]
:asciidoc_cheatsheet: http://powerman.name/doc/asciidoc[AsciiDoc Cheatsheet]
:asciidoc_help: http://www.methods.co.nz/asciidoc/userguide.html[AsciiDoc User Guide]
:asciidoc_questions: http://www.methods.co.nz/asciidoc/faq.html[AsciiDoc FAQ]
:asciidoc_cheatsheet: https://powerman.name/doc/asciidoc[AsciiDoc Cheatsheet]
:asciidoc_help: https://www.methods.co.nz/asciidoc/userguide.html[AsciiDoc User Guide]
:asciidoc_questions: https://www.methods.co.nz/asciidoc/faq.html[AsciiDoc FAQ]
:asciidoc_syntax: http://xpt.sourceforge.net/techdocs/nix/tool/asciidoc-syn/ascs01-AsciiDocMarkupSyntaxQuickSummary/single/[AsciiDoc Syntax]
:asciidoctor_style: http://asciidoctor.org/docs/asciidoc-writers-guide/#delimited-blocks[AsciiDoctor Styles Guide]
:asciidoctor_syntax: http://asciidoctor.org/docs/asciidoc-writers-guide/#delimited-blocks[AsciiDoctor Syntax Guide]
:cc_by_sa: http://creativecommons.org/licenses/by-sa/3.0/[Commons Attribution-ShareAlike 3.0 Unported License]
:debian32: http://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_i386.deb[wsjtx_{VERSION}_i386.deb]
:debian64: http://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_amd64.deb[wsjtx_{VERSION}_amd64.deb]
:raspbian: http://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_armhf.deb[wsjtx_{VERSION}_armhf.deb]
:debian: http://www.debian.org/[Debian]
:dev_guide: http://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/wsjt-dev-guide.html[Dev-Guide]
:devsvn: http://sourceforge.net/p/wsjt/wsjt/HEAD/tree/[Devel-SVN]
:asciidoctor_style: https://asciidoctor.org/docs/asciidoc-writers-guide/#delimited-blocks[AsciiDoctor Styles Guide]
:asciidoctor_syntax: https://asciidoctor.org/docs/asciidoc-writers-guide/#delimited-blocks[AsciiDoctor Syntax Guide]
:cc_by_sa: https://creativecommons.org/licenses/by-sa/3.0/[Commons Attribution-ShareAlike 3.0 Unported License]
:debian32: https://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_i386.deb[wsjtx_{VERSION}_i386.deb]
:debian64: https://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_amd64.deb[wsjtx_{VERSION}_amd64.deb]
:raspbian: https://physics.princeton.edu/pulsar/K1JT/wsjtx_{VERSION}_armhf.deb[wsjtx_{VERSION}_armhf.deb]
:debian: https://www.debian.org/[Debian]
:dev_guide: https://www.physics.princeton.edu/pulsar/K1JT/wsjtx-doc/wsjt-dev-guide.html[Dev-Guide]
:devsvn: https://sourceforge.net/p/wsjt/wsjt/HEAD/tree/[Devel-SVN]
:devrepo: https://sourceforge.net/p/wsjt/wsjtx/ci/master/tree/[SourceForge]
:dimension4: http://www.thinkman.com/dimension4/[Thinking Man Software]
:download: http://physics.princeton.edu/pulsar/K1JT/wsjtx.html[Download Page]
:download: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[Download Page]
:dxatlas: http://www.dxatlas.com/[Afreet Software, Inc.]
:dxlcommander: http://www.dxlabsuite.com/commander/[Commander]
:dxlsuite: http://www.dxlabsuite.com/[DX Lab Suite]
:fedora32: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-i686.rpm[wsjtx-{VERSION}-i686.rpm]
:fedora64: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-x86_64.rpm[wsjtx-{VERSION}-x86_64.rpm]
:fmt_arrl: http://www.arrl.org/frequency-measuring-test[ARRL FMT Info]
:dxlcommander: https://www.dxlabsuite.com/commander/[Commander]
:dxlsuite: https://www.dxlabsuite.com/[DX Lab Suite]
:fedora32: https://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-i686.rpm[wsjtx-{VERSION}-i686.rpm]
:fedora64: https://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-x86_64.rpm[wsjtx-{VERSION}-x86_64.rpm]
:fmt_arrl: https://www.arrl.org/frequency-measuring-test[ARRL FMT Info]
:fmt_group: https://groups.yahoo.com/neo/groups/FMT-nuts/info[FMT Group]
:fmt_k5cm: http://www.k5cm.com/[FMT Event Info]
:fmt_wspr: http://www.physics.princeton.edu/pulsar/K1JT/FMT_User.pdf[Accurate Frequency Measurements with your WSPR Setup]
:ft4_protocol: http://physics.princeton.edu/pulsar/k1jt/FT4_Protocol.pdf[The FT4 Protocol for Digital Contesting]
:ft4_ft8_protocols: http://physics.princeton.edu/pulsar/k1jt/FT4_FT8_QEX.pdf[The FT4 and FT8 Communication Protocols]
:ft8_tips: http://www.g4ifb.com/FT8_Hinson_tips_for_HF_DXers.pdf[FT8 Operating Guide]
:ft8_DXped: http://physics.princeton.edu/pulsar/k1jt/FT8_DXpedition_Mode.pdf[FT8 DXpedition Mode]
:gnu_gpl: http://www.gnu.org/licenses/gpl-3.0.txt[GNU General Public License]
:homepage: http://physics.princeton.edu/pulsar/K1JT/[WSJT Home Page]
:fmt_wspr: https://www.physics.princeton.edu/pulsar/K1JT/FMT_User.pdf[Accurate Frequency Measurements with your WSPR Setup]
:ft4_protocol: https://physics.princeton.edu/pulsar/k1jt/FT4_Protocol.pdf[The FT4 Protocol for Digital Contesting]
:ft4_ft8_protocols: https://physics.princeton.edu/pulsar/k1jt/FT4_FT8_QEX.pdf[The FT4 and FT8 Communication Protocols]
:ft8_tips: https://www.g4ifb.com/FT8_Hinson_tips_for_HF_DXers.pdf[FT8 Operating Guide]
:ft8_DXped: https://physics.princeton.edu/pulsar/k1jt/FT8_DXpedition_Mode.pdf[FT8 DXpedition Mode]
:gnu_gpl: https://www.gnu.org/licenses/gpl-3.0.txt[GNU General Public License]
:homepage: https://physics.princeton.edu/pulsar/K1JT/[WSJT Home Page]
:hrd: http://www.hrdsoftwarellc.com/[Ham Radio Deluxe]
:jt4eme: http://physics.princeton.edu/pulsar/K1JT/WSJT-X_1.6.0_for_JT4_v7.pdf[Using WSJT-X for JT4 EME Operation]
:jt65protocol: http://physics.princeton.edu/pulsar/K1JT/JT65.pdf[QEX]
:jtalert: http://hamapps.com/[JTAlert]
:jt4eme: https://physics.princeton.edu/pulsar/K1JT/WSJT-X_1.6.0_for_JT4_v7.pdf[Using WSJT-X for JT4 EME Operation]
:jt65protocol: https://physics.princeton.edu/pulsar/K1JT/JT65.pdf[QEX]
:jtalert: https://hamapps.com/[JTAlert]
:launchpadki7mt: https://launchpad.net/~ki7mt[KI7MT PPA's]
:log4om: http://www.log4om.com[Log4OM]
:lunarEchoes: http://physics.princeton.edu/pulsar/K1JT/LunarEchoes_QEX.pdf[QEX]
:msk144: http://physics.princeton.edu/pulsar/k1jt/MSK144_Protocol_QEX.pdf[QEX]
:log4om: https://www.log4om.com[Log4OM]
:lunarEchoes: https://physics.princeton.edu/pulsar/K1JT/LunarEchoes_QEX.pdf[QEX]
:msk144: https://physics.princeton.edu/pulsar/k1jt/MSK144_Protocol_QEX.pdf[QEX]
:msvcpp_redist: https://www.microsoft.com/en-ph/download/details.aspx?id=40784[Microsoft VC++ 2013 Redistributable]
:msys_url: http://sourceforge.net/projects/mingwbuilds/files/external-binary-packages/[MSYS Download]
:msys_url: https://sourceforge.net/projects/mingwbuilds/files/external-binary-packages/[MSYS Download]
:n1mm_logger: https://n1mm.hamdocs.com/tiki-index.php[N1MM Logger+]
:ntpsetup: http://www.satsignal.eu/ntp/setup.html[Network Time Protocol Setup]
:osx_instructions: http://physics.princeton.edu/pulsar/K1JT/OSX_Readme[Mac OS X Install Instructions]
:ppa: http://en.wikipedia.org/wiki/Personal_Package_Archive[PPA]
:projsummary: http://sourceforge.net/projects/wsjt/[Project Summary]
:pskreporter: http://pskreporter.info/pskmap.html[PSK Reporter]
:ntpsetup: https://www.satsignal.eu/ntp/setup.html[Network Time Protocol Setup]
:osx_instructions: https://physics.princeton.edu/pulsar/K1JT/OSX_Readme[Mac OS X Install Instructions]
:ppa: https://en.wikipedia.org/wiki/Personal_Package_Archive[PPA]
:projsummary: https://sourceforge.net/projects/wsjt/[Project Summary]
:pskreporter: https://pskreporter.info/pskmap.html[PSK Reporter]
:sourceforge: https://sourceforge.net/user/registration[SourceForge]
:sourceforge-jtsdk: https://sourceforge.net/projects/jtsdk[SourceForge JTSDK]
:ubuntu_sdk: https://launchpad.net/~ubuntu-sdk-team/+archive/ppa[Ubuntu SDK Notice]
@ -97,37 +97,37 @@ d). Edit lines as needed. Keeping them in alphabetic order help see dupes.
:win64_openssl: https://slproweb.com/download/Win64OpenSSL_Light-1_1_1g.msi[Win64 OpenSSL Light Package]
:writelog: https://writelog.com/[Writelog]
:wsjtx_group: https://groups.io/g/WSJTX[WSJTX Group]
:wsjtx: http://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]
:wspr0_guide: http://www.physics.princeton.edu/pulsar/K1JT/WSPR0_Instructions.TXT[WSPR0 Guide]
:wspr: http://physics.princeton.edu/pulsar/K1JT/wspr.html[WSPR Home Page]
:wsprnet: http://wsprnet.org/drupal/[WSPRnet]
:wsprnet_activity: http://wsprnet.org/drupal/wsprnet/activity[WSPRnet Activity page]
:wsjtx: https://physics.princeton.edu/pulsar/K1JT/wsjtx.html[WSJT-X]
:wspr0_guide: https://www.physics.princeton.edu/pulsar/K1JT/WSPR0_Instructions.TXT[WSPR0 Guide]
:wspr: https://physics.princeton.edu/pulsar/K1JT/wspr.html[WSPR Home Page]
:wsprnet: https://wsprnet.org/drupal/[WSPRnet]
:wsprnet_activity: https://wsprnet.org/drupal/wsprnet/activity[WSPRnet Activity page]
// Download Links
:cty_dat: http://www.country-files.com/cty/[Amateur Radio Country Files]
:jtbridge: http://jt-bridge.eller.nu/[JT-Bridge]
:jtsdk_doc: http://physics.princeton.edu/pulsar/K1JT/JTSDK-DOC.exe[Download]
:jtsdk_installer: http://sourceforge.net/projects/jtsdk/files/win32/2.0.0/JTSDK-2.0.0-B2-Win32.exe/download[Download]
:jtsdk_omnirig: http://sourceforge.net/projects/jtsdk/files/win32/2.0.0/base/contrib/OmniRig.zip/download[Download]
:jtsdk_py: http://physics.princeton.edu/pulsar/K1JT/JTSDK-PY.exe[Download]
:jtsdk_qt: http://physics.princeton.edu/pulsar/K1JT/JTSDK-QT.exe[Download]
:jtsdk_vcredist: http://sourceforge.net/projects/jtsdk/files/win32/2.0.0/base/contrib/vcredist_x86.exe/download[Download]
:cty_dat: https://www.country-files.com/cty/[Amateur Radio Country Files]
:jtbridge: https://jt-bridge.eller.nu/[JT-Bridge]
:jtsdk_doc: https://physics.princeton.edu/pulsar/K1JT/JTSDK-DOC.exe[Download]
:jtsdk_installer: https://sourceforge.net/projects/jtsdk/files/win32/2.0.0/JTSDK-2.0.0-B2-Win32.exe/download[Download]
:jtsdk_omnirig: https://sourceforge.net/projects/jtsdk/files/win32/2.0.0/base/contrib/OmniRig.zip/download[Download]
:jtsdk_py: https://physics.princeton.edu/pulsar/K1JT/JTSDK-PY.exe[Download]
:jtsdk_qt: https://physics.princeton.edu/pulsar/K1JT/JTSDK-QT.exe[Download]
:jtsdk_vcredist: https://sourceforge.net/projects/jtsdk/files/win32/2.0.0/base/contrib/vcredist_x86.exe/download[Download]
:nh6z: http://www.nh6z.net/Amatuer_Radio_Station_NH6Z/Other_Peoples_Software.html[here]
:omnirig: http://www.dxatlas.com/OmniRig/Files/OmniRig.zip[Omni-Rig]
:osx: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-Darwin.dmg[wsjtx-{VERSION}-Darwin.dmg]
:QRA64_EME: http://physics.princeton.edu/pulsar/K1JT/QRA64_EME.pdf[QRA64 for microwave EME]
:svn: http://subversion.apache.org/packages.html#windows[Subversion]
:win32: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win32.exe[wsjtx-{VERSION}-win32.exe]
:win64: http://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win64.exe[wsjtx-{VERSION}-win64.exe]
:osx: https://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-Darwin.dmg[wsjtx-{VERSION}-Darwin.dmg]
:QRA64_EME: https://physics.princeton.edu/pulsar/K1JT/QRA64_EME.pdf[QRA64 for microwave EME]
:svn: https://subversion.apache.org/packages.html#windows[Subversion]
:win32: https://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win32.exe[wsjtx-{VERSION}-win32.exe]
:win64: https://physics.princeton.edu/pulsar/K1JT/wsjtx-{VERSION}-win64.exe[wsjtx-{VERSION}-win64.exe]
:wsjt-devel: https://lists.sourceforge.net/lists/listinfo/wsjt-devel[here]
:wsjt_repo: https://sourceforge.net/p/wsjt/wsjt_orig/ci/master/tree/[WSJT Source Repository]
:wspr_code: http://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe[WSPRcode.exe]
:wspr_code: https://physics.princeton.edu/pulsar/K1JT/WSPRcode.exe[WSPRcode.exe]
:wspr_svn: https://sourceforge.net/p/wsjt/wspr/ci/master/tree/[WSPR Source Repository]
// MAIL-TO links
:alex_efros: mailto:powerman@powerman.name[Alex Efros]
:bill_somerville: mailto:g4wjs -at- c l a s s d e s i g n -dot- com [G4WJS]
:dev_mail_list: http://sourceforge.net/mailarchive/forum.php?forum_name=wsjt-devel[WSJT Developers Email List]
:dev_mail_list: https://sourceforge.net/mailarchive/forum.php?forum_name=wsjt-devel[WSJT Developers Email List]
:dev_mail_svn: https://sourceforge.net/auth/subscriptions/[WSJT SVN Archives]
:devmail: mailto:wsjt-devel@lists.sourceforge.net[wsjt-devel@lists.sourceforge.net]
:devmail1: mailto:wsjt-devel@lists.sourceforge.net[Post Message]

View File

@ -1,4 +1,4 @@
// Status=review
// Status=edited
A *Status Bar* at the bottom edge of the main window provides useful
information about operating conditions.
@ -9,15 +9,15 @@ image::status-bar-a.png[align="left",alt="Status Bar"]
Labels on the *Status Bar* display such information as the program's
current operating state, configuration name, operating mode, and the
content of your most recent transmitted message. The first label
(operating state) can be Receiving, Tx (for Transmitting), Tune, or
the name of file opened from the *File* menu; this label is
(operating state) can be Receiving, Tx (for Transmitting), Tx: Tune, or
the name of the file opened from the *File* menu. This label is
highlighted in green for Receiving, yellow for Tx, red for Tune, and
light blue for a file name. When transmitting, the Tx message is
displayed exactly as it will be decoded by receiving stations. The
second label (as shown above) will be absent if you are using the
*Default* setting on the *Configurations* menu. A progress bar shows
the elapsed fraction of a Tx or Rx sequence. Finally, if the Watchdog
(WD) timer was enabled on the *Settings | General* tab, a label in the
(WD) timer was enabled on the *Files | Settings | General* tab, a label in the
lower right-hand corner displays the number of minutes remaining
before timeout.

View File

@ -1,4 +1,4 @@
// Status=review
// Status=edited
The following controls appear at the bottom of the Wide Graph window.
Decoding occurs only in the displayed frequency range; otherwise, with
@ -29,7 +29,7 @@ slower, as desired.
- A dropdown list below the *Palette* label lets you select from a
wide range of waterfall color palettes.
- Click *Adjust* to activate a window that allows you to create a
- Click *Adjust* to activate a window that allows you to import or export a
user-defined palette.
- Check *Flatten* if you want _WSJT-X_ to compensate for a sloping or
@ -50,10 +50,10 @@ about right, depending on the input signal level, the chosen palette,
and your own preferences. Hover the mouse over a control to display a
tip reminding you of its function.
- The *Spec nn%* control may be used to set the fractional height of
- The *Spec nn%* control is used to set the fractional height of
the spectrum plotted below the waterfall.
- *Smooth* is active only when *Linear Average* has been selected.
- *Smooth* is active only when *Linear Average* is selected.
Smoothing the displayed spectrum over more than one bin can enhance
your ability to detect weak EME signals with Doppler spread more than
a few Hz.
@ -66,7 +66,7 @@ selected on the Wide Graph. Three sliders at the bottom of the Fast
Graph window can be used to optimize gain and zero-offset for the
displayed information. Hover the mouse over a control to display a
tip reminding you of its function. Clicking the *Auto Level* button
will produce reasonable settings as a starting point.
produces reasonable settings as a starting point.
image::fast-graph-controls.png[align="center",alt="Fast Graph Controls"]
@ -89,7 +89,7 @@ spectra, thereby smoothing the curves over multiple bins.
- Label *N* shows the number of echo pulses averaged.
- Click the *Colors* button to cycle through 6 possible choices of
- Click the *Colors* button to cycle through six possible choices of
color and line width for the plots.
[[CONTROLS_MISCELLANEOUS]]

View File

@ -1,3 +1,5 @@
// Status: edited
_WSJT-X_ is programmed to cooperate closely with several other useful
programs.
@ -38,10 +40,10 @@ logging applications Aether, MacLoggerDX, RUMlog or RUMlogNG. It
checks QSO and QSL status of the call and DXCC entity, as well as many
other features.
* {n1mm_logger} is a free full feature contest logging application. It
* {n1mm_logger} is a free, full-feature contest logging application. It
is only available for Windows. _WSJT-X_ can send logged QSO
information to it via a network connection.
* {writelog} is a non-free full feature contest logging
* {writelog} is a non-free, full-feature contest logging
application. It is only available for Windows. _WSJT-X_ can send
logged QSO information to it via a network connection.

View File

@ -1,19 +1,21 @@
[[AP_Decoding]]
// Status: edited
=== AP Decoding
The _WSJT-X_ decoders for FT4, FT8, JT65, and QRA64 include optional
procedures that take advantage of naturally accumulating information
during a minimal QSO. This _a priori_ (AP) information increases
sensitivity of the decoder by up to 4 dB, at the cost of a slightly
higher rate of false decodes.
The _WSJT-X_ decoders for FT4, FT8, JT65, QRA64, include
procedures that use naturally accumulating information during a
minimal QSO. This _a priori_ (AP) information increases sensitivity
of the decoder by up to 4 dB, at the cost of a slightly higher rate of
false decodes. AP is optional in FT8, JT65, and QRA64, but is always
enabled for FT4.
For example: when you decide to answer a CQ, you already know your own
callsign and that of your potential QSO partner. The software
therefore "`knows`" what might be expected for at least 57 message
bits (28 for each of two callsigns, 1 or more for message type) in the
next received message. The decoder's task can thus be reduced to
bits (28 for each of two callsigns, one or more for message type) in the
next received message. The decoder's task is thus reduced to
determining the remaining 15 bits of the message and ensuring that the
resulting solution is consistent with the message's parity symbols.
resulting solution is reliable.
AP decoding starts by setting AP bits to the hypothesized values, as
if they had been received correctly. We then determine whether the
@ -21,12 +23,12 @@ remaining message and parity bits are consistent with the hypothesized
AP bits, with a specified level of confidence. Successful AP decodes
are labeled with an end-of-line indicator of the form `aP`, where `P`
is one of the single-digit AP decoding types listed in Table 1. For
example, `a2` indicates that the successful decode used *MyCall* as
example, `a2` indicates that the successful decode used MyCall as
hypothetically known information.
[[FT8_AP_INFO_TABLE]]
.FT4 and FT8 AP information types
[width="50%",cols="h10,<m20",frame=topbot,options="header"]
[width="35%",cols="h10,<m20",frame=topbot,options="header"]
|===============================================
|aP | Message components
|a1 | CQ &#160; &#160; ? &#160; &#160; ?
@ -49,7 +51,7 @@ be attempted in each state.
[[FT8_AP_DECODING_TYPES_TABLE]]
.FT4 and FT8 AP decoding types for each QSO state
[width="50%",cols="h10,<m20",frame=topbot,options="header"]
[width="35%",cols="h10,<m20",frame=topbot,options="header"]
|===========================================
|State |AP type
|CALLING STN | 2, 3
@ -60,14 +62,12 @@ be attempted in each state.
|CALLING CQ | 1, 2
|===========================================
Decoding with _a priori_ information behaves slightly differently in
JT65. Some details are provided in Tables 3 and 4. Notations such as
`a63`, use a second digit to indicate the number of Rx intervals
averaged to obtain the decode.
Decoding with _a priori_ information behaves slightly differently
in JT65. Some details are provided in Tables 3 and 4.
[[JT65_AP_INFO_TABLE]]
.JT65 AP information types
[width="50%",cols="h10,<m20",frame=topbot,options="header"]
[width="35%",cols="h10,<m20",frame=topbot,options="header"]
|===============================================
|aP | Message components
|a1 | CQ &#160; &#160; ? &#160; &#160; ?
@ -81,7 +81,7 @@ averaged to obtain the decode.
[[JT65_AP_DECODING_TYPES_TABLE]]
.JT65 AP decoding types for each QSO state
[width="50%",cols="h10,<m20",frame=topbot,options="header"]
[width="35%",cols="h10,<m20",frame=topbot,options="header"]
|===========================================
|State |AP type
|CALLING STN | 2, 3, 6, 7
@ -93,7 +93,6 @@ averaged to obtain the decode.
|===========================================
[[Decoded_Lines]]
=== Decoded Lines
Displayed information accompanying decoded messages generally includes UTC,
@ -110,7 +109,7 @@ summarized in the following Table:
[width="50%",cols="h,3*^",frame=topbot,options="header"]
|===========================================
|Mode |Mode character|Sync character|End of line information
|FT4 | + | | ? &#160; aP
|FT4 | ~ | | ? &#160; aP
|FT8 | ~ | | ? &#160; aP
|JT4 | $ | *, # | f, fN, dCN
|JT9 | @ | |
@ -126,7 +125,7 @@ Sync character::
End of line information::
`?` - Decoded with lower confidence +
`a` - Decoded with aid of some a priori (AP) information +
`a` - Decoded with aid of some _a priori_ (AP) information +
`C` - Confidence indicator [ISCAT and Deep Search; (0-9,*)] +
`d` - Deep Search algorithm +
`f` - Franke-Taylor or Fano algorithm +
@ -140,7 +139,7 @@ Table 6 below shows the meaning of the return codes R in QRA64 mode.
[[QRA64_AP_INFO_TABLE]]
.QRA64 AP return codes
[width="50%",cols="h10,<m20",frame=topbot,options="header"]
[width="35%",cols="h10,<m20",frame=topbot,options="header"]
|===============================================
|rc | Message components
|0 | ? &#160; &#160; ? &#160; &#160; ?

View File

@ -1,10 +1,12 @@
// Status: edited
////
Questions:
Should be short one liners (in the .adoc file) ending with ?::
If your question is too long for one line, consider multiple questions or rephrase
Should be short one-liners (in the .adoc file) ending with ?::
If your question is too long for one line, consider multiple questions or rephrase.
Answers:
Can be bullet or paragraphs. Bullets make for easier reading.
Can be bullets or paragraphs. Bullets make for easier reading.
Bullet Usage:
* = a circle bullet single intent
@ -39,12 +41,12 @@ passband, if such control is available.
How should I configure _WSJT-X_ to run multiple instances?::
Start _WSJT-X_ from a command-prompt window, assigning each instance a
unique identifier as in the following two-instance example. This
procedure will isolate the *Settings* file and the writable file
location for each instance of _WSJT-X_.
Start _WSJT-X_ from a command-prompt window, assigning each a unique
identifier as in the following two-instance example. This procedure
will isolate the *Settings* file and the writable file location for
each instance of _WSJT-X_.
wsjtx --rig-name=TS2000
wsjtx --rig-name=TS590
wsjtx --rig-name=FT847
I am getting a "Network Error - SSL/TLS support not installed" message. What should I do?::
@ -53,19 +55,18 @@ You need to install suitable _OpenSSL_ libraries - see <<OPENSSL,Instructions to
I occasionally get Rig Control Errors if I adjust my Icom rig's VFO. What's wrong?::
By default, most Icom transceivers have *CI-V Tranceive Mode" enabled,
this will cause unsolicited CAT traffic from the rig that disrupts CAT
By default, most Icom transceivers have *CI-V Transceive Mode" enabled. This will cause unsolicited CAT traffic from the rig that disrupts CAT
control by a PC. Disable this option in the rig's menu.
I want to control my transceiver with another application as well as _WSJT-X_, is that possible?::
This only possible to do reliably via some kind of rig control server,
that server must be able to accept both _WSJT-X_ and the other
This only possible to do reliably via some kind of rig control server.
That server must be able to accept both _WSJT-X_ and the other
application(s) as clients. Using a dumb serial port splitter like the
VSPE tool is not supported, it may work but it is not reliable due to
VSPE tool is not supported; it may work but it is not reliable due to
unmanaged CAT control collisions. Applications like the _Hamlib Rig
Control Server (rigctld)_, _{omnirig}_, and _{dxlsuite} Commander_ are
potentially suitable and _WSJT-X_ can act as a client to them all.
potentially suitable; _WSJT-X_ can act as a client to them all.
Rig control through _OmniRig_ seems to fail when I click *Test CAT*. What can I do about it?::
@ -79,7 +80,7 @@ You may see delays up to 20 seconds or so in frequency changes or
other radio commands, due to a bug in HRD. HRD folks are aware of the
problem, and are working to resolve it.
I am running _WSJT-X_ under Ubuntu. The program starts, but menu bar is missing from the top of the main window and the hot-keys don't work.::
I am running _WSJT-X_ under Ubuntu. The program starts, but the menu bar is missing from the top of the main window and the hot-keys don't work.::
Ubuntu's new "`Unity`" desktop puts the menu for the currently active
window at the top of the primary display screen. You can restore menu
@ -100,10 +101,10 @@ I am running _WSJT-X_ on Linux using a KDE desktop. Why does *Menu->Configuratio
The KDE development team have added code to Qt that tries to
automatically add shortcut accelerator keys to all buttons including
pop up menu buttons, this interferes with operation of the application
pop up menu buttons. This interferes with operation of the application
(many other Qt applications have similar issues with KDE). Until this
is fixed by the KDE team you must disable this misfeature. Edit the
file ~/.config/kdeglobals and add a section containing the following:
is fixed by the KDE team you must disable this feature. Edit the
file `~/.config/kdeglobals` and add a section containing the following:
[Development]
AutoCheckAccelerators=false

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -1,7 +1,7 @@
// Status=review
// Status=edited
Source code for _WSJT-X_ is available from a public repository at
{devrepo}. To compile the program you will need to install at least the
{devrepo}. To compile the program, at a minimum you must install the
following packages:
- Git
@ -19,7 +19,7 @@ cd wsjtx
git checkout wsjtx-{VERSION}
=====
and for the current development branch,
and for the current development branch:
=====
git clone git://git.code.sf.net/p/wsjt/wsjtx

View File

@ -1,16 +1,14 @@
// Status=review
// Status=edited
Debian, Ubuntu, and other Debian-based systems including Raspbian:
NOTE: The project team release binary installer packages for Linux
when a new _WSJT-X_ release is announced. These are built to
target one contemporary version of a Linux distribution. Although
these may work on newer Linux versions or even different
distributions, it is unlikely that they will work on older
versions. Check the notes provided with the release for details of the
targeted Linux distributions and versions. If the binary package is
not compatible with your Linux distribution or version you must build
the application from sources.
NOTE: The project team release binary installer packages targeted for
one contemporary version of a Linux distribution. Although these may
work on newer Linux versions or even different distributions, it is
unlikely that they work on older versions. Check the notes provided
with the release for details of the targeted Linux distributions and
versions. If the binary package is not compatible with your Linux
distribution or version, you must build the application from sources.
* 32-bit: {debian32}
- To install:
@ -42,8 +40,11 @@ sudo dpkg -P wsjtx
You may also need to execute the following command in a terminal:
[example]
sudo apt install libqt5multimedia5-plugins libqt5serialport5 libqt5sql5-sqlite libfftw3-single3
....
sudo apt install libgfortran5 libqt5widgets5 libqt5network5 \
libqt5printsupport5 libqt5multimedia5-plugins libqt5serialport5 \
libqt5sql5-sqlite libfftw3-single3 libgomp1 libusb-1.0-0
....
Fedora, CentOS, Red Hat, and other rpm-based systems:
@ -71,5 +72,8 @@ sudo rpm -e wsjtx
You may also need to execute the following command in a terminal:
[example]
sudo dnf install fftw-libs-single qt5-qtmultimedia qt5-qtserialport
....
sudo dnf install libgfortran fftw-libs-single qt5-qtbase \
qt5-qtmultimedia qt5-qtserialport qt5-qtsvg \
qt5-qtserialport libgomp libusbx
....

View File

@ -1,12 +1,12 @@
// These instructions are up-to-date for WSJT-X v2.2
*OS X 10.12* and later: Download the file {osx} to your desktop,
double-click on it and consult its `ReadMe` file for important
*macOS10.13* and later: Download the file {osx} to your desktop,
double-click it and consult its `ReadMe` file for important
installation notes.
If you have already installed a previous version, you can retain it by
changing its name in the *Applications* folder (say, from _WSJT-X_ to
_WSJT-X_2.1_). You can then proceed to the installation phase.
changing its name in the *Applications* folder (such as from _WSJT-X_ to
_WSJT-X_2.2_). You can then proceed to the installation phase.
Take note also of the following:

View File

@ -1,7 +1,7 @@
// Status=edited
Download and execute the package file {win32} (WinXP, Vista, Win 7,
Win 8, Win10, 32-bit) or {win64} (Vista, Win 7, Win 8, Win10, 64-bit)
Download and execute the package file {win32} (Win 7,
Win 8, Win10, 32-bit) or {win64} (Win 7, Win 8, Win10, 64-bit)
following these instructions:
* Install _WSJT-X_ into its own directory, for example `C:\WSJTX` or `C:\WSJT\WSJTX`, rather than the conventional location `C:\Program
@ -21,18 +21,32 @@ TIP: Your computer may be configured so that this directory is
`"%LocalAppData%\WSJT-X\"`.
* The built-in Windows facility for time synchronization is usually
not adequate. We recommend the program _Meinberg NTP_ (see
{ntpsetup} for downloading and installation instructions) or
_Dimension 4_ from {dimension4}. Recent versions of Windows 10 are
now shipped with a more capable Internet time synchronization
service that is suitable if configured appropriately.
not adequate. We recommend the program _Meinberg NTP Client_: see
{ntpsetup} for downloading and installation instructions. Recent
versions of Windows 10 are now shipped with a more capable Internet
time synchronization service that is suitable if configured
appropriately. We do not recommend SNTP time setting tools or others
that make periodic correction steps, _WSJT-X_ requires that the PC
clock be monotonically increasing and smoothly continuous.
NOTE: Having a PC clock that appears to be synchronized to UTC is not
sufficient. "`Monotonically increasing`" means that the clock
must not be stepped backwards. "`Smoothly continuous`" means
that time must increase at a nearly constant rate, without
steps. Any necessary clock corrections must be applied by
adjusting the rate of increase, thereby correcting
synchronization errors gradually.
[[OPENSSL]]
image:LoTW_TLS_error.png[_WSJT-X_ LoTW download TLS error,
align="center"]
* _WSJT-X_ requires installation of the _OpenSSL_ libraries. Suitable libraries may already be installed on your system. If they are not, you will see this error shortly after requesting a fetch of the latest LoTW users database. To fix this, install the _OpenSSL_ libraries.
* _WSJT-X_ requires installation of the _OpenSSL_ libraries. Suitable
libraries may already be installed on your system. If they are not,
you will see this error shortly after requesting a fetch of the
latest LoTW users database. To fix this, install the _OpenSSL_
libraries.
** You can download a suitable _OpenSSL_ package for Windows from
{win_openssl_packages}; you need the latest *Windows Light*

View File

@ -0,0 +1,40 @@
=== Documentation Conventions
In this manual the following icons call attention to particular types
of information:
NOTE: *Notes* containing information that may be of interest to
particular classes of users.
TIP: *Tips* on program features or capabilities that might otherwise be
overlooked.
IMPORTANT: *Warnings* about usage that could lead to undesired
consequences.
=== User Interface in Other Languages
The _WSJT-X_ user interface is now available in many languages. When
a translated user interface is available for the computer's default
System Language, it will appear automatically on program startup.
=== How You Can Contribute
_WSJT-X_ is part of an open-source project released under the
{gnu_gpl} (GPLv3). If you have programming or documentation skills or
would like to contribute to the project in other ways, please make
your interests known to the development team. We especially encourage
those with translation skills to volunteer their help, either for
this _User Guide_ or for the program's user interface.
The project's source-code repository can be found at {devrepo}, and
communication among the developers takes place on the email reflector
{devmail}. Bug reports and suggestions for new features, improvements
to the _WSJT-X_ User Guide, etc., may be sent there as well. You must
join the group before posting to the email list.
=== License
Before using _WSJT-X_, please read our licensing terms
<<LICENSE,here>>.

View File

@ -3,63 +3,73 @@
_WSJT-X_ is a computer program designed to facilitate basic amateur
radio communication using very weak signals. The first four letters in
the program name stand for "`**W**eak **S**ignal communication by
K1**JT**,`" while the suffix "`-X`" indicates that _WSJT-X_ started as
an e**Xt**ended and e**X**perimental branch of the program _WSJT_,
first released in 2001. Bill Somerville, G4WJS, and Steve Franke,
K9AN, have been major contributors to program development since 2013
and 2015, respectively.
K1**JT**,`" while the suffix "`*-X*`" indicates that _WSJT-X_ started
as an extended branch of an earlier program, _WSJT_, first released in
2001. Bill Somerville, G4WJS, and Steve Franke, K9AN, have been major
contributors to development of _WSJT-X_ since 2013 and 2015, respectively.
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers ten different
protocols or modes: *FT4*, *FT8*, *JT4*, *JT9*, *JT65*, *QRA64*,
*ISCAT*, *MSK144*, *WSPR*, and *Echo*. The first six are designed for
making reliable QSOs under weak-signal conditions. They use nearly
identical message structure and source encoding. JT65 and QRA64 were
designed for EME ("`moonbounce`") on the VHF/UHF bands and have also
proven very effective for worldwide QRP communication on the HF bands.
QRA64 has a some advantages over JT65, including better performance
for EME on the higher microwave bands. JT9 was originally designed
for the LF, MF, and lower HF bands. Its submode JT9A is 2 dB more
sensitive than JT65 while using less than 10% of the bandwidth. JT4
offers a wide variety of tone spacings and has proven highly effective
for EME on microwave bands up to 24 GHz. These four "`slow`" modes
use one-minute timed sequences of alternating transmission and
reception, so a minimal QSO takes four to six minutes — two or three
transmissions by each station, one sending in odd UTC minutes and the
other even. FT8 is operationally similar but four times faster
(15-second T/R sequences) and less sensitive by a few dB. FT4 is
faster still (7.5 s T/R sequences) and especially well suited for
radio contesting. On the HF bands, world-wide QSOs are possible with
any of these modes using power levels of a few watts (or even
milliwatts) and compromise antennas. On VHF bands and higher, QSOs
are possible (by EME and other propagation types) at signal levels 10
to 15 dB below those required for CW.
Note that even though their T/R sequences are short, FT4 and FT8 are
classified as slow modes because their message frames are sent only
once per transmission. All fast modes in _WSJT-X_ send their message
frames repeatedly, as many times as will fit into the Tx sequence
length.
_WSJT-X_ Version {VERSION_MAJOR}.{VERSION_MINOR} offers twelve
different protocols or modes: *FST4*, *FT4*, *FT8*, *JT4*, *JT9*,
*JT65*, *QRA64*, *ISCAT*, *MSK144*, *WSPR*, *FST4W*, and *Echo*. The
first seven are designed for making reliable QSOs under weak-signal
conditions. They use nearly identical message structure and source
encoding. JT65 and QRA64 were designed for EME ("`moonbounce`") on
the VHF/UHF bands and have also proven very effective for worldwide
QRP communication on the HF bands. QRA64 has some advantages over
JT65, including better performance for EME on the higher microwave
bands. JT9 was originally designed for the HF and lower bands. Its
submode JT9A is 1 dB more sensitive than JT65 while using less than
10% of the bandwidth. JT4 offers a wide variety of tone spacings and
has proven highly effective for EME on microwave bands up to 24 GHz.
These four "`slow`" modes use one-minute timed sequences of
alternating transmission and reception, so a minimal QSO takes four to
six minutes — two or three transmissions by each station, one sending
in odd UTC minutes and the other even. FT8 is operationally similar
but four times faster (15-second T/R sequences) and less sensitive by
a few dB. FT4 is faster still (7.5 s T/R sequences) and especially
well-suited for radio contesting. FST4 was added to _WSJT-X_ in
version 2.3.0. It is intended especially for use on the LF and MF
bands, and already during its first few months of testing
intercontinental paths have been spanned many times on the 2200 and
630 m bands. Further details can be found in the following section,
<<NEW_FEATURES,New Features in Version 2.3.0>>. On the HF bands,
world-wide QSOs are possible with any of these modes using power
levels of a few watts (or even milliwatts) and compromise antennas.
On VHF bands and higher, QSOs are possible (by EME and other
propagation types) at signal levels 10 to 15 dB below those required
for CW.
*ISCAT*, *MSK144*, and optionally submodes *JT9E-H* are "`fast`"
protocols designed to take advantage of brief signal enhancements from
ionized meteor trails, aircraft scatter, and other types of scatter
propagation. These modes use timed sequences of 5, 10, 15, or 30 s
duration. User messages are transmitted repeatedly at high rate (up
to 250 characters per second, for MSK144) to make good use of the
to 250 characters per second for MSK144) to make good use of the
shortest meteor-trail reflections or "`pings`". ISCAT uses free-form
messages up to 28 characters long, while MSK144 uses the same
structured messages as the slow modes and optionally an abbreviated
format with hashed callsigns.
Note that some of the modes classified as slow can have T/R sequence
lengths as short the fast modes. "`Slow`" in this sense implies
message frames being sent only once per transmission. The fast modes
in _WSJT-X_ send their message frames repeatedly, as many times as
will fit into the Tx sequence length.
*WSPR* (pronounced "`whisper`") stands for **W**eak **S**ignal
**P**ropagation **R**eporter. The WSPR protocol was designed for probing
potential propagation paths using low-power transmissions. WSPR
messages normally carry the transmitting stations callsign, grid
locator, and transmitter power in dBm, and they can be decoded at
signal-to-noise ratios as low as -31 dB in a 2500 Hz bandwidth. WSPR
users with internet access can automatically upload reception
reports to a central database called {wsprnet} that provides a mapping
facility, archival storage, and many other features.
**P**ropagation **R**eporter. The WSPR protocol was designed for
probing potential propagation paths using low-power transmissions.
WSPR messages normally carry the transmitting stations callsign,
grid locator, and transmitter power in dBm, and with two-minute
sequences they can be decoded at signal-to-noise ratios as low
as -31 dB in a 2500 Hz bandwidth. *FST4W* is designed for
similar purposes, but especially for use on LF and MF bands.
It includes optional sequence lengths as long as 30 minutes and
reaches sensitivity tresholds as low as -45 dB. Users
with internet access can automatically upload WSPR and FST4W
reception reports to a central database called {wsprnet} that
provides a mapping facility, archival storage, and many other
features.
*Echo* mode allows you to detect and measure your own station's echoes
from the moon, even if they are far below the audible threshold.
@ -80,4 +90,4 @@ be beta releases leading up to the final release of v2.1.0.
Release candidates should be used _only_ during a short testing
period. They carry an implied obligation to provide feedback to the
program development group. Candidate releases should not be used on
the air after a full release with the same number has been made.
the air after a full release with the same number is made.

View File

@ -1,7 +1,9 @@
//status: edited
A basic logging facility in _WSJT-X_ saves QSO information to files
named `wsjtx.log` (in comma-separated text format) and `wsjtx_log.adi`
(in standard ADIF format). These files can be imported directly into
other programs, for example spreadsheets and popular logging programs.
other programs (such as spreadsheets and popular logging programs).
As described in the <<INSTALL,Installation>> and <<PLATFORM,Platform
Dependencies>> sections, different operating systems may place your
local log files in different locations. You can always navigate to
@ -12,30 +14,32 @@ applications like {jtalert}, which can log QSOs automatically to other
applications including {hrd}, {dxlsuite}, and {log4om}.
The program option *Show DXCC entity and worked before status*
(selectable on the *Settings | General* tab) is intended mostly for
(selectable on the *File | Settings | General* tab) is intended mostly for
use on non-Windows platforms, where {jtalert} is not available. When
this option is checked _WSJT-X_ appends some additional information to
this option is checked, _WSJT-X_ appends some additional information to
all CQ messages displayed in the _Band Activity_ window. The name of
the DXCC entity is shown, abbreviated if necessary. Your "`worked
before`" status for this callsign (according to log file
`wsjtx_log.adi`) is indicated by highlighting colors, if that option
has been selected.
is selected.
_WSJT-X_ includes a built-in `cty.dat` file containing DXCC prefix
information. Updated files can be downloaded from the {cty_dat} web
site when required. If an updated `cty.dat` is present in the logs
folder and readable, it will be used in preference to the built-in
one.
site when required. If an updated and readable `cty.dat` file is
present in the logs folder, it is used in preference to the
built-in file.
The log file `wsjtx_log.adi` is updated whenever you log a QSO from
_WSJT-X_. (Keep in mind that if you erase this file you will lose all
_WSJT-X_. (Keep in mind that if you erase this file, you lose all
"`worked before`" information.) You can append or overwrite the
`wsjtx_log.adi` file by exporting your QSO history as an ADIF file
from another logging program. Turning *Show DXCC entity and worked
before status* off and then on again will cause _WSJT-X_ to re-read
before status* off and then on again causes _WSJT-X_ to re-read
the log file. Very large log files may cause _WSJT-X_ to slow down
when searching for calls. If the ADIF log file has been changed
when searching for calls. If the ADIF log file has been changed
outside of _WSJT-X_ you can force _WSJT-X_ to reload the file from the
*Settings | Colors* tab using the *Rescan ADIF Log* button, see
<<COLORS,Decode Highlighting>>.
Additional features are provided for *Contest* and *Fox* logging.
(more to come, here ...)

View File

@ -37,7 +37,7 @@ assigns more reliable numbers to relatively strong signals.
NOTE: Signals become visible on the waterfall around S/N = 26 dB and
audible (to someone with very good hearing) around 15 dB. Thresholds
for decodability are around -20 dB for FT8, -23 dB for JT4, 25 dB for
JT65, 27 dB for JT9.
JT65, and 27 dB for JT9.
NOTE: Several options are available for circumstances where fast QSOs
are desirable. Double-click the *Tx1* control under _Now_ or _Next_
@ -75,7 +75,7 @@ When calling CQ you may also choose to check the box *Call 1st*.
_WSJT-X_ will then respond automatically to the first decoded
responder to your CQ.
NOTE: When *Auto-Seq* is enabled the program de-activates *Enable Tx*
NOTE: When *Auto-Seq* is enabled, the program de-activates *Enable Tx*
at the end of each QSO. It is not intended that _WSJT-X_ should make
fully automated QSOs.
@ -259,7 +259,7 @@ that a second callsign is never permissible in these messages.
NOTE: During a transmission your outgoing message is displayed in the
first label on the *Status Bar* and shown exactly as another station
will receive it. You can check to see that you are actually
receives it. You can check to see that you are actually
transmitting the message you wish to send.
QSOs involving *Type 2* compound callsigns might look like either
@ -287,7 +287,7 @@ standard structured messages without callsign prefix or suffix.
TIP: If you are using a compound callsign, you may want to
experiment with the option *Message generation for type 2 compound
callsign holders* on the *Settings | General* tab, so that messages
callsign holders* on the *File | Settings | General* tab, so that messages
will be generated that best suit your needs.
=== Pre-QSO Checklist

View File

@ -1,6 +1,8 @@
//Status: edited
=== Frequency Calibration
Many _WSJT-X_ capabilities depend on signal-detection bandwidths no
Many _WSJT-X_ capabilities depend on signal-detection bandwidths of no
more than a few Hz. Frequency accuracy and stability are therefore
unusually important. We provide tools to enable accurate frequency
calibration of your radio, as well as precise frequency measurement of
@ -11,11 +13,11 @@ measuring the error in dial frequency for each signal.
You will probably find it convenient to define and use a special
<<CONFIG-MENU,Configuration>> dedicated to frequency calibration.
Then complete the following steps, as appropriate for your system.
Then complete the following steps, as appropriate, for your system.
- Switch to FreqCal mode
- In the _Working Frequencies_ box on the *Settings -> Frequencies*
- In the _Working Frequencies_ box on the *File | Settings | Frequencies*
tab, delete any default frequencies for *FreqCal* mode that are not
relevant for your location. You may want to replace some of them with
reliably known frequencies receivable at your location.
@ -29,14 +31,14 @@ of WWV at 2.500, 5.000, 10.000, 15.000, and 20.000 MHz, and CHU at
3.330, 7.850, and 14.670 MHz. Similar shortwave signals are available
in other parts of the world.
- In most cases you will want to start by deleting any existing file
`fmt.all` in the directory where your log files are kept.
- In most cases, start by deleting any existing file `fmt.all` in the
directory where your log files are kept.
- To cycle automatically through your chosen list of calibration
frequencies, check *Execute frequency calibration cycle* on the
*Tools* menu. _WSJT-X_ will spend 30 seconds at each
frequency. Initially no measurement data is saved to the `fmt.all`
file although it is displayed on screen, this allows you to check your
file although it is displayed on screen; this allows you to check your
current calibration parameters.
- During the calibration procedure, the radio's USB dial frequency is
@ -61,7 +63,7 @@ the nominal frequency itself (in MHz). For example, the 20 MHz
measurement for WWV shown above produced a measured tone offset of
24.6 Hz, displayed in the _WSJT-X_ decoded text window. The resulting
calibration constant is 24.6/20=1.23 parts per million. This number
may be entered as *Slope* on the *settings -> Frequencies* tab.
may be entered as *Slope* on the *File | Settings | Frequencies* tab.
A more precise calibration can be effected by fitting the intercept
and slope of a straight line to the whole sequence of calibration
@ -81,19 +83,19 @@ After running *Execute frequency calibration cycle* at least once with
good results, check and edit the file `fmt.all` in the log directory
and delete any spurious or outlier measurements. The line-fitting
procedure can then be carried out automatically by clicking *Solve for
calibration parameters* on the *Tools* menu. The results will be
calibration parameters* on the *Tools* menu. The results are
displayed as in the following screen shot. Estimated uncertainties
are included for slope and intercept; `N` is the number of averaged
frequency measurements included in the fit, and `StdDev` is the root
mean square deviation of averaged measurements from the fitted
straight line. If the solution seems valid you will be offered an
*Apply* button to push that will automatically set the calibration
parameters in *Settings -> Frequencies -> Frequency Calibration*.
straight line. If the solution seems valid, you are offered an
*Apply* button to push that automatically sets the calibration
parameters in *File | Settings | Frequencies | Frequency Calibration*.
image::FreqCal_Results.png[align="center",alt="FreqCal_Results"]
For a quick visual check of the resulting calibration, stay in
*FreqCal* mode with the *Measure* option cleared. _WSJT-X_ will show
*FreqCal* mode with the *Measure* option cleared. _WSJT-X_ shows
the adjusted results directly on the waterfall and the displayed
records.
@ -103,8 +105,8 @@ _WSJT-X_ provides a tool that can be used to determine the detailed
shape of your receiver's passband. Disconnect your antenna or tune to
a quiet frequency with no signals. With _WSJT-X_ running in one of
the slow modes, select *Measure reference spectrum* from the *Tools*
menu. Wait for about a minute and then hit the *Stop* button. A file
named `refspec.dat` will appear in your log directory. When you check
menu. Wait for about a minute and then click *Stop*. A file
named `refspec.dat` appears in your log directory. When you check
*Ref Spec* on the *Wide Graph*, the recorded reference spectrum will
then be used to flatten your overall effective passband.
@ -122,39 +124,39 @@ response* generates an undistorted audio waveform equal to the one
generated by the transmitting station. Its Fourier transform is then
used as a frequency-dependent phase reference to compare with the
phase of the received frame's Fourier coefficients. Phase differences
between the reference spectrum and received spectrum will include
between the reference spectrum and received spectrum include
contributions from the originating station's transmit filter, the
propagation channel, and filters in the receiver. If the received
frame originates from a station known to transmit signals having
little phase distortion (say, a station known to use a properly
adjusted software-defined-transceiver) and if the received signal is
little phase distortion (such as a station known to use a properly
adjusted software-defined transceiver), and if the received signal is
relatively free from multipath distortion so that the channel phase is
close to linear, the measured phase differences will be representative
of the local receiver's phase response.
Complete the following steps to generate a phase equalization curve:
- Record a number of wav files that contain decodable signals from
your chosen reference station. Best results will be obtained when the
- Record a number of `wav` files that contain decodable signals from
your chosen reference station. Best results are obtained when the
signal-to-noise ratio of the reference signals is 10 dB or greater.
- Enter the callsign of the reference station in the DX Call box.
- Select *Measure phase response* from the *Tools* menu, and open each
of the wav files in turn. The mode character on decoded text lines
will change from `&` to `^` while _WSJT-X_ is measuring the phase
response, and it will change back to `&` after the measurement is
of the `wav` files in turn. The mode character on decoded text lines
changes from `&` to `^` while _WSJT-X_ is measuring the phase
response, and it changes back to `&` after the measurement is
completed. The program needs to average a number of high-SNR frames to
accurately estimate the phase, so it may be necessary to process
several wav files. The measurement can be aborted at any time by
several `wav` files. The measurement can be aborted at any time by
selecting *Measure phase response* again to toggle the phase
measurement off.
+
When the measurement is complete _WSJT-X_ will save the measured
When the measurement is complete, _WSJT-X_ saves the measured
phase response in the *Log directory*, in a file with suffix
".pcoeff". The filename will contain the callsign of the reference
".pcoeff". The filename contains the callsign of the reference
station and a timestamp, for example `K0TPP_170923_112027.pcoeff`.
- Select *Equalization tools ...* under the *Tools* menu and click the
@ -165,23 +167,23 @@ the proposed phase equalization curve. It's a good idea to repeat the
phase measurement several times, using different wav files for each
measurement, to ensure that your measurements are repeatable.
- Once you are satisfied with a fitted curve, push the *Apply* button
to save the proposed response. The red curve will be replaced with a
- Once you are satisfied with a fitted curve, click the *Apply* button
to save the proposed response. The red curve is replaced with a
light green curve labeled "Current" to indicate that the phase
equalization curve is now being applied to the received data. Another
curve labeled "Group Delay" will appear. The "Group Delay" curve shows
curve labeled "Group Delay" appears. The "Group Delay" curve shows
the group delay variation across the passband, in ms. Click the
*Discard Measured* button to remove the captured data from the plot,
leaving only the applied phase equalization curve and corresponding
group delay curve.
- To revert to no phase equalization, push the *Restore Defaults*
- To revert to no phase equalization, click the *Restore Defaults*
button followed by the *Apply* button.
The three numbers printed at the end of each MSK144 decode line can be
used to assess the improvement provided by equalization. These numbers
are: `N` = Number of frames averaged, `H` = Number of hard bit errors
corrected, `E` = Size of MSK eye diagram opening.
corrected, and `E` = Size of MSK eye diagram opening.
Here is a decode of K0TPP obtained while *Measure phase response* was measuring
the phase response:
@ -196,7 +198,7 @@ scale. Here's how the same decode looks after phase equalization:
103900 17 6.5 1493 & WA8CLT K0TPP +07 1 0 1.6
In this case, equalization has increased the eye opening from 1.2 to
In this case, equalization has increased the eye-opening from 1.2 to
1.6. Larger positive eye openings are associated with reduced
likelihood of bit errors and higher likelihood that a frame will be
successfully decoded. In this case, the larger eye-opening tells us
@ -206,7 +208,7 @@ equalization curve is going to improve decoding of signals other than
those from the reference station, K0TPP.
It's a good idea to carry out before and after comparisons using a
large number of saved wav files with signals from many different
large number of saved `wav` files with signals from many different
stations, to help decide whether your equalization curve improves
decoding for most signals. When doing such comparisons, keep in mind
that equalization may cause _WSJT-X_ to successfully decode a frame

View File

@ -1,107 +1,34 @@
[[NEW_FEATURES]]
=== New in Version {VERSION}
*Improvements to decoders*
_WSJT-X 2.3.0_ introduces *FST4* and *FST4W*, new digital protocols
designed particularly for the LF and MF bands. Decoders for these
modes can take advantage of the very small Doppler spreads present at
these frequencies, even over intercontinental distances. As a
consequence, fundamental sensitivities of FST4 and FST4W are better
than other _WSJT-X_ modes with the same sequence lengths, approaching
the theoretical limits for their rates of information throughput. The
FST4 protocol is optimized for two-way QSOs, while FST4W is for
quasi-beacon transmissions of WSPR-style messages. FST4 and FST4W do
not require the strict, independent phase locking and time
synchronization of modes like EbNaut.
*FT4:* Corrected bugs that prevented AP (_a priori_) decoding and/or
multi-pass decoding in some circumstances. Improved and extended the
algorithm for AP decoding.
The new modes use 4-GFSK modulation and share common software for
encoding and decoding messages. FST4 offers T/R sequence lengths of
15, 30, 60, 120, 300, 900, and 1800 seconds, while FST4W omits the
lengths shorter than 120 s. Submodes are given names like FST4-60,
FST4W-300, etc., the appended numbers indicating sequence length in
seconds. Message payloads contain either 77 bits, as in FT4, FT8, and
MSK144, or 50 bits for the WSPR-like messages of FST4W. Message
formats displayed to the user are like those in the other 77-bit and
50-bit modes in _WSJT-X_. Forward error correction uses a low density
parity check (LDPC) code with 240 information and parity bits.
Transmissions consist of 160 symbols: 120 information-carrying symbols
of two bits each, interspersed with five groups of eight predefined
synchronization symbols.
*FT8:* Decoding is now spread over three intervals. The first starts
11.8 s into an Rx sequence and typically yields around 85% of the
possible decodes, so you 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.
SNR estimates no longer saturate at +20 dB, and large signals in the
passband no longer cause the SNR of weaker signals to be biased low.
Times written to cumulative journal file ALL.TXT are now correct even
when the decode occurs after the T/R sequence boundary. In FT8
DXpedition Mode, AP decoding is now implemented for Hounds when the
Fox has a compound callsign.
*JT4:* Formatting and display of averaged and Deep Search decodes has
been cleaned up and made consistent with other modes used for EME and
extreme weak-signal work on microwave bands.
*JT65:* Many improvements have been made for averaged and Deep Search
decodes, and their display to the user. For details see <<VHF_JT65,JT65>>
in the <<VHF_AND_UP,VHF+ Features>> section of this guide.
*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 message format:* 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
version 2.2.0. See <<CONTEST_MSGS,Contest Messages>> for details.
*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.
- The contest mode FT4 now always uses RR73 for the Tx4 message.
- *Keyboard shortcuts* have been added as an aid to accessibility:
*Alt+R* sets Tx4 message to RR73, *Ctrl+R* sets it to RRR.
- The *Status bar* now displays the number of decodes found in the
most recent Rx sequence.
- As an aid for partial color-blindness, the "`inverted goal posts`"
marking Rx frequency on the Wide Graph's frequency scale are now in a
darker shade of green.
=== Documentation Conventions
In this manual the following icons call attention to particular types
of information:
NOTE: *Notes* containing information that may be of interest to
particular classes of users.
TIP: *Tips* on program features or capabilities that might otherwise be
overlooked.
IMPORTANT: *Warnings* about usage that could lead to undesired
consequences.
=== User Interface in Other Languages
Thanks to Xavi Perez, EA3W, in cooperation with G4WJS, the _WSJT-X_
user interface is now available the Catalan language. Spanish will
follow soon, and other languages when translations are made. When a
translated user interface is available for the computer's default
System Language, it will appear automatically on program startup.
=== How You Can Contribute
_WSJT-X_ is part of an open-source project released under the
{gnu_gpl} (GPLv3). If you have programming or documentation skills or
would like to contribute to the project in other ways, please make
your interests known to the development team. We especially encourage
those with translation skills to volunteer their help, either for
this _User Guide_ or for the program's user interface.
The project's source-code repository can be found at {devrepo}, and
communication among the developers takes place on the email reflector
{devmail}. Bug reports and suggestions for new features, improvements
to the _WSJT-X_ User Guide, etc., may be sent there as well. You must
join the group before posting to the email list.
*We recommend that on the 2200 and 630 m bands FST4 should replace JT9
for making 2-way QSOs, and FST4W should replace WSPR for propagation
tests*. Operating conventions on these LF and MF bands will
eventually determine the most useful T/R sequence lengths for each
type of operation.

View File

@ -1,3 +1,5 @@
//status: edited
[[PROTOCOL_OVERVIEW]]
=== Overview
@ -12,11 +14,11 @@ Special cases allow other information such as add-on callsign prefixes
aim is to compress the most common messages used for minimally valid
QSOs into a fixed 72-bit length.
The information payload for FT4, FT8, and MSK144 contains 77 bits.
The 5 new bits added to the original 72 are used to flag special
message types signifying special message types used for FT8 DXpedition
Mode, contesting, nonstandard callsigns, and a few other
possibilities.
Information payloads for FST4, FT4, FT8, and MSK144 contain 77 bits.
The 5 additional bits are used to flag special message types used for
nonstandard callsigns, contest exchanges, FT8 DXpedition Mode, and a
few other possibilities. Full details have been published in QEX, see
{ft4_ft8_protocols}.
A standard amateur callsign consists of a one- or two-character
prefix, at least one of which must be a letter, followed by a digit
@ -30,17 +32,17 @@ of 4-digit Maidenhead grid locators on earth is 180×180 = 32,400,
which is less than 2^15^ = 32,768; so a grid locator requires 15 bits.
Some 6 million of the possible 28-bit values are not needed for
callsigns. A few of these slots have been assigned to special message
callsigns. A few of these slots are assigned to special message
components such as `CQ`, `DE`, and `QRZ`. `CQ` may be followed by three
digits to indicate a desired callback frequency. (If K1ABC transmits
on a standard calling frequency, say 50.280, and sends `CQ 290 K1ABC
on a standard calling frequency such as 50.280, and sends `CQ 290 K1ABC
FN42`, it means that s/he will listen on 50.290 and respond there to
any replies.) A numerical signal report of the form `nn` or
`Rnn` can be sent in place of a grid locator. (As originally
defined, numerical signal reports `nn` were required to fall between -01
and -30 dB. Recent program versions accommodate reports between
and -30 dB. Recent program versions 2.3 and later accommodate reports between
-50 and +49 dB.) A country prefix or portable suffix may be
attached to one of the callsigns. When this feature is used the
attached to one of the callsigns. When this feature is used, the
additional information is sent in place of the grid locator or by
encoding additional information into some of the 6 million available
slots mentioned above.
@ -52,11 +54,6 @@ were the callsigns `E9AA` through `E9ZZ`. Upon reception they are
converted back to the form `CQ AA` through `CQ ZZ`, for display to the
user.
The FT4, FT8, and MSK144 protocols use different lossless compression
algorithms with features that generate and recognize special messages
used for contesting and other special purposes. Full details have
been published in QEX, see {ft4_ft8_protocols}.
To be useful on channels with low signal-to-noise ratio, this kind of
lossless message compression requires use of a strong forward error
correcting (FEC) code. Different codes are used for each mode.
@ -69,6 +66,20 @@ _WSJT-X_ modes have continuous phase and constant envelope.
[[SLOW_MODES]]
=== Slow Modes
[[FST4PRO]]
==== FST4
FST4 offers T/R sequence lengths of 15, 30, 60, 120, 300, 900, and
1800 seconds. Submodes are given names like FST4-60, FST4-120, etc.,
the appended numbers indicating sequence length in seconds. A 24-bit
cyclic redundancy check (CRC) is appended to the 77-bit message
payload to create a 101-bit message-plus-CRC word. Forward error
correction is accomplished using a (240,101) LDPC code. Transmissions
consist of 160 symbols: 120 information-carrying symbols of two bits
each, interspersed with five groups of eight predefined
synchronization symbols. Modulation uses 4-tone frequency-shift
keying (4-GFSK) with Gaussian smoothing of frequency transitions.
[[FT4PRO]]
==== FT4
@ -147,7 +158,8 @@ following pseudo-random sequence:
The synchronizing tone is normally sent in each interval having a
"`1`" in the sequence. Modulation is 65-FSK at 11025/4096 = 2.692
baud. Frequency spacing between tones is equal to the keying rate for
JT65A, and 2 and 4 times larger for JT65B and JT65C. For EME QSOs the
JT65A, and 2 and 4 times larger for JT65B and JT65C, respectively.
For EME QSOs the
signal report OOO is sometimes used instead of numerical signal
reports. It is conveyed by reversing sync and data positions in the
transmitted sequence. Shorthand messages for RO, RRR, and 73 dispense
@ -155,7 +167,7 @@ with the sync vector entirely and use time intervals of 16384/11025 =
1.486 s for pairs of alternating tones. The lower frequency is the
same as that of the sync tone used in long messages, and the frequency
separation is 110250/4096 = 26.92 Hz multiplied by n for JT65A, with n
= 2, 3, 4 used to convey the messages RO, RRR, and 73.
= 2, 3, 4 used to convey the messages RO, RRR, and 73, respectively.
[[QRA64_PROTOCOL]]
==== QRA64
@ -222,10 +234,24 @@ information the least significant. Thus, on a 0 3 scale, the tone
for a given symbol is twice the value (0 or 1) of the data bit, plus
the sync bit.
[[FST4WPRO]]
==== FST4W
FST4W offers T/R sequence lengths of 120, 300, 900, and 1800 seconds.
Submodes are given names like FST4W-120, FST4W-300, etc., the appended
numbers indicating sequence length in seconds. Message payloads
contain 50 bits, and a 24-bit cyclic redundancy check (CRC) appended
to create a 74-bit message-plus-CRC word. Forward error correction
is accomplished using a (240,74) LDPC code. Transmissions consist of
160 symbols: 120 information-carrying symbols of two bits each,
interspersed with five groups of eight predefined synchronization
symbols. Modulation uses 4-tone frequency-shift keying (4-GFSK) with
Gaussian smoothing of frequency transitions.
[[SLOW_SUMMARY]]
==== Summary
Table 7 provides a brief summary parameters for the slow modes in
Table 7 provides a brief summary of parameters for the slow modes in
_WSJT-X_. Parameters K and r specify the constraint length and rate
of the convolutional codes; n and k specify the sizes of the
(equivalent) block codes; Q is the alphabet size for the
@ -236,17 +262,28 @@ which the probability of decoding is 50% or higher.
[[SLOW_TAB]]
.Parameters of Slow Modes
[width="90%",cols="3h,^3,^2,^1,^2,^2,^2,^2,^2,^2",frame=topbot,options="header"]
[width="100%",cols="3h,^3,^2,^1,^2,^2,^2,^2,^2,^2",frame=topbot,options="header"]
|===============================================================================
|Mode |FEC Type |(n,k) | Q|Modulation type|Keying rate (Baud)|Bandwidth (Hz)
|Sync Energy|Tx Duration (s)|S/N Threshold (dB)
|FT4 |LDPC, r=1/2|(174,91)| 4| 4-GFSK| 20.8333 | 83.3 | 0.15| 5.04 | -17.5
|FT8 |LDPC, r=1/2|(174,91)| 8| 8-GFSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|FST4-15 |LDPC | (240,101)| 4| 4-GFSK| 16.67 | 67.7 | 0.25| 9.6 | -20.7
|FST4-30 |LDPC | (240,101)| 4| 4-GFSK| 7.14 | 28.6 | 0.25| 22.4 | -24.2
|FST4-60 |LDPC | (240,101)| 4| 4-GFSK| 3.09 | 12.4 | 0.25| 51.8 | -28.1
|FST4-120 |LDPC | (240,101)| 4| 4-GFSK| 1.46 | 5.9 | 0.25| 109.3 | -31.3
|FST4-300 |LDPC | (240,101)| 4| 4-GFSK| 0.558 | 2.2 | 0.25| 286.7 | -35.3
|FST4-900 |LDPC | (240,101)| 4| 4-GFSK| 0.180 | 0.72 | 0.25| 887.5 | -40.2
|FST4-1800 |LDPC | (240,101)| 4| 4-GFSK| 0.089 | 0.36 | 0.25| 1792.0| -43.2
|FT4 |LDPC |(174,91)| 4| 4-GFSK| 20.83 | 83.3 | 0.15| 5.04 | -17.5
|FT8 |LDPC |(174,91)| 8| 8-GFSK| 6.25 | 50.0 | 0.27| 12.6 | -21
|JT4A |K=32, r=1/2|(206,72)| 2| 4-FSK| 4.375| 17.5 | 0.50| 47.1 | -23
|JT9A |K=32, r=1/2|(206,72)| 8| 9-FSK| 1.736| 15.6 | 0.19| 49.0 | -27
|JT9A |K=32, r=1/2|(206,72)| 8| 9-FSK| 1.736| 15.6 | 0.19| 49.0 | -26
|JT65A |Reed Solomon|(63,12) |64|65-FSK| 2.692| 177.6 | 0.50| 46.8 | -25
|QRA64A|Q-ary Repeat Accumulate|(63,12) |64|64-FSK|1.736|111.1|0.25|48.4| -26
| WSPR |K=32, r=1/2|(162,50)| 2| 4-FSK| 1.465| 5.9 | 0.50|110.6 | -31
|FST4W-120 |LDPC | (240,74)| 4| 4-GFSK| 1.46 | 5.9 | 0.25| 109.3 | -32.8
|FST4W-300 |LDPC | (240,74)| 4| 4-GFSK| 0.558 | 2.2 | 0.25| 286.7 | -36.8
|FST4W-900 |LDPC | (240,74)| 4| 4-GFSK| 0.180 | 0.72 | 0.25| 887.5 | -41.7
|FST4W-1800 |LDPC | (240,74)| 4| 4-GFSK| 0.089 | 0.36 | 0.25| 1792.0| -44.8
|===============================================================================
Submodes of JT4, JT9, JT65, and QRA64 offer wider tone spacings for
@ -256,12 +293,10 @@ threshold sensitivities of the various submodes when spreading is
comparable to tone spacing.
[[SLOW_SUBMODES]]
.Parameters of Slow Submodes
.Parameters of Slow Submodes with Selectable Tone Spacings
[width="50%",cols="h,3*^",frame=topbot,options="header"]
|=====================================
|Mode |Tone Spacing |BW (Hz)|S/N (dB)
|FT4 |20.8333 | 83.3 |-17.5
|FT8 |6.25 | 50.0 |-21
|JT4A |4.375| 17.5 |-23
|JT4B |8.75 | 30.6 |-22
|JT4C |17.5 | 56.9 |-21
@ -269,7 +304,7 @@ comparable to tone spacing.
|JT4E |78.75| 240.6 |-19
|JT4F |157.5| 476.9 |-18
|JT4G |315.0| 949.4 |-17
|JT9A |1.736| 15.6 |-27
|JT9A |1.736| 15.6 |-26
|JT9B |3.472| 29.5 |-26
|JT9C |6.944| 57.3 |-25
|JT9D |13.889| 112.8 |-24
@ -305,7 +340,7 @@ available character set is:
Transmissions consist of sequences of 24 symbols: a synchronizing
pattern of four symbols at tone numbers 0, 1, 3, and 2, followed by
two symbols with tone number corresponding to (message length) and
(message length + 5), and finally 18 symbols conveying the user's
(message length + 5), and, finally, 18 symbols conveying the user's
message, sent repeatedly character by character. The message always
starts with `@`, the beginning-of-message symbol, which is not
displayed to the user. The sync pattern and message-length indicator

View File

@ -1,7 +1,7 @@
// Status=review
- SSB transceiver and antenna
- Computer running Windows 7 or later, Linux, or OS X
- Computer running Windows 7 or later, macOS 10.13 or later, or Linux
- 1.5 GHz or faster CPU and 200 MB of available memory; faster
machines are better
- Monitor with at least 1024 x 780 resolution

View File

@ -0,0 +1,23 @@
Do not confuse FST4 with FT4, which has a very different purpose!
FST4 is is designed for making 2-way QSOs on the LF and MF bands.
Operation with FST4 is similar to that with other _WSJT-X_ modes: most
on-screen controls, auto-sequencing, and other features behave in
familiar ways. However, operating conventions on the 2200 and 630 m
bands have made some additional user controls desirable. Spin boxes
labeled *F Low* and *F High* set lower and upper frequency limits used
by the FST4 decoder, and these limits are marked by dark green
angle-bracket symbols *< >* on the Wide Graph frequency scale:
image::FST4_Decoding_Limits.png[align="center"]
{empty} +
image::FST4_center.png[align="center"]
It's best to keep the decoding range fairly small, since QRM and
transmissions in other modes or sequence lengths will slow down the
decoding process (and of course will be undecodable). By checking
*Single decode* on the the *File | Settings | General* tab, you can
further limit the decoding range to the setting of *F Tol* on
either side of *Rx Freq*.

View File

@ -0,0 +1,18 @@
FST4W is used in the same way as WSPR, but FST4W has significant
advantages for use on the 2200 and 630 m bands. By default the
central *Rx Freq* is 1500 Hz and *F Tol* is 100 Hz, so the active
decoding range is 1400 to 1600 Hz. However, for added flexibility you
can select different center frequencies and *F Tol* values. We expect
that usage conventions will soon be established for FST4W activity on
2200 and 630 m.
A new drop-down control below *F Tol* offers a round-robin mode for
scheduling FST4W transmissions:
image::FST4W_RoundRobin.png[align="center"]
If three operators agree in advance to select the options *1/3*,
*2/3*, and *3/3*, for example, their FST4W transmissions will occur in
a fixed sequence with no two stations transmitting simultaneously.
Sequence 1 is the first sequence after 00:00 UTC. For WSPR-like
scheduling behavior, you should select *Random* with this control.

View File

@ -92,16 +92,14 @@ thing, both stations will have the required Doppler compensation.
Moreover, anyone else using this option will hear both of you
without the need for manual frequency changes.
- Select *On Dx Echo* when your QSO partner is not using automated
Doppler tracking, and announces his/her transmit frequency and listening
on their own echo frequency. When clicked, this Doppler method will
set your rig frequency on receive to correct for the mutual Doppler
shift. On transmit, your rig frequency will be set so that your
QSO partner will receive you on the same frequency as their own echo
at the start of the QSO. As the QSO proceeds, your QSO partner will
receive you on this starting frequency so that they do not have to
retune their receiver as the Doppler changes. Sked frequency in this
case is set to that announced by your QSO partner.
- Select *On Dx Echo* when your QSO partner announces his/her transmit
frequency and that they are listening on their own echo
frequency. When clicked, this Doppler method will set your rig
frequency on receive to correct for the mutual Doppler shift. On
transmit, your rig frequency will be set so that your QSO partner will
receive you on the same frequency as they receive their own echo.
Sked frequency in this case is set to that announced by your QSO
partner.
- Select *Call DX* after tuning the radio manually to find a station,
with the Doppler mode initially set to *None*. You may be tuning the band

View File

@ -32,6 +32,9 @@ include::introduction.adoc[]
[[NEW_FEATURES]]
include::new_features.adoc[]
[[INTRO_SUBSECTIONS]]
include::intro_subsections.adoc[]
[[SYSREQ]]
== System Requirements
include::system-requirements.adoc[]
@ -162,6 +165,14 @@ include::tutorial-example3.adoc[]
=== FT4
include::tutorial-example4.adoc[]
[[TUT_EX5]]
=== FST4
include::tutorial-example5.adoc[]
[[TUT_EX6]]
=== FST4W
include::tutorial-example6.adoc[]
[[MAKE_QSOS]]
== Making QSOs
include::make-qso.adoc[]

View File

@ -65,12 +65,18 @@ starts. This feature can be used to activate an automatic antenna
tuner (ATU) to tune a multi-band antenna to the newly selected band.
- Depending on your station and antenna setup, band changes might
require other switching besides retuning your radio. To make this
require other switching besides retuning your radio. To make this
possible in an automated way, whenever _WSJT-X_ executes a successful
band-change command to a CAT-controlled radio, it looks for a file
named `user_hardware.bat`, `user_hardware.cmd`, `user_hardware.exe`,
or `user_hardware` in the working directory. If one of these is found,
_WSJT-X_ tries to execute the command
band-change command to a CAT-controlled radio, it looks for an
executable file or script named `user_hardware`. This is done using
`CMD /C user_hardware <band>` on Windows, or `/bin/sh -c user_hardware
<band>` on other platforms, where band is described below. On Windows
the first file with any extension listed on the PATHEXT environment
variable added to the file name root `user_hardware`, and found in the
directories listed on the PATH environment variable will be executed.
On other platforms, the first executable script, or program, named
`user_hardware` found in a directory listed on the PATH environment
variable will be executed.
user_hardware nnn
@ -78,6 +84,11 @@ _WSJT-X_ tries to execute the command
meters. You must write your own program, script, or batch file to do
the necessary switching at your station.
IMPORTANT: The use of the PATH (and PATHEXT on Windows) environment
variables is a new feature. To emulate previous behavior make sure
that the location of your user_hardware script or program is on the
PATH environment variable used by _WSJT-X_.
The following screen shot is an example of WSPR operation with
band hopping enabled:

21
lib/77bit/call_to_c28.f90 Normal file
View File

@ -0,0 +1,21 @@
program call_to_c28
parameter (NTOKENS=2063592,MAX22=4194304)
character*6 call_std
character a1*37,a2*36,a3*10,a4*27
data a1/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
data a2/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
data a3/'0123456789'/
data a4/' ABCDEFGHIJKLMNOPQRSTUVWXYZ'/
! call_std must be right adjusted, length 6
call_std=' K1ABC' !Redefine as needed
i1=index(a1,call_std(1:1))-1
i2=index(a2,call_std(2:2))-1
i3=index(a3,call_std(3:3))-1
i4=index(a4,call_std(4:4))-1
i5=index(a4,call_std(5:5))-1
i6=index(a4,call_std(6:6))-1
n28=NTOKENS + MAX22 + 36*10*27*27*27*i1 + 10*27*27*27*i2 + &
27*27*27*i3 + 27*27*i4 + 27*i5 + i6
write(*,1000) call_std,n28
1000 format('Callsign: ',a6,2x,'c28 as decimal integer:',i10)
end program call_to_c28

58
lib/77bit/free_text.f90 Normal file
View File

@ -0,0 +1,58 @@
program free_text
character*13 c13,w
character*71 f71
character*42 c
character*1 qa(10),qb(10)
data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?'/
c13='TNX BOB 73 GL' !Redefine as needed
call mp_short_init
qa=char(0)
w=adjustr(c13)
do i=1,13
j=index(c,w(i:i))-1
if(j.lt.0) j=0
call mp_short_mult(qb,qa(2:10),9,42) !qb(1:9)=42*qa(2:9)
call mp_short_add(qa,qb(2:10),9,j) !qa(1:9)=qb(2:9)+j
enddo
write(f71,1000) qa(2:10)
1000 format(b7.7,8b8.8)
write(*,1010) c13,f71
1010 format('Free text: ',a13/'f71: ',a71)
end program free_text
subroutine mp_short_ops(w,u)
! Multi-precision arithmetic with storage in character arrays.
character*1 w(*),u(*)
integer i,ireg,j,n,ir,iv,ii1,ii2
character*1 creg(4)
save ii1,ii2
equivalence (ireg,creg)
entry mp_short_init
ireg=256*ichar('2')+ichar('1')
do j=1,4
if (creg(j).eq.'1') ii1=j
if (creg(j).eq.'2') ii2=j
enddo
return
entry mp_short_add(w,u,n,iv)
ireg=256*iv
do j=n,1,-1
ireg=ichar(u(j))+ichar(creg(ii2))
w(j+1)=creg(ii1)
enddo
w(1)=creg(ii2)
return
entry mp_short_mult(w,u,n,iv)
ireg=0
do j=n,1,-1
ireg=ichar(u(j))*iv+ichar(creg(ii2))
w(j+1)=creg(ii1)
enddo
w(1)=creg(ii2)
return
return
end subroutine mp_short_ops

View File

@ -89,3 +89,8 @@ K1ABC/VE3 37
KA1ABC/VEX 37
<PJ4/K1ABC> FK52UD
<K1ABC/W4> FK52UD
<W3CCX> <K1JT> 590001 FN20QI
<W3CCX> <K1JT/P> 590001 FN20QI
<W3CCX/P> <K1JT> 590001 FN20QI
<W3CCX/P> <K1JT/P> 590001 FN20QI
<W3CCX/QRP> <K1JT/QRO> 590001 FN20QI

View File

@ -0,0 +1,13 @@
program nonstd_to_c58
integer*8 n58
character*11 call_nonstd
character*38 c
data c/' 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/'/
call_nonstd='PJ4/K1ABC' !Redifine as needed
n58=0
do i=1,11
n58=n58*38 + index(c,call_nonstd(i:i)) - 1
enddo
write(*,1000) call_nonstd,n58
1000 format('Callsign: ',a11,2x,'c58 as decimal integer:',i20)
end program nonstd_to_c58

View File

@ -2,8 +2,8 @@ module packjt77
! These variables are accessible from outside via "use packjt77":
parameter (MAXHASH=1000,MAXRECENT=10)
character (len=13), dimension(1:1024) :: calls10=''
character (len=13), dimension(1:4096) :: calls12=''
character (len=13), dimension(0:1023) :: calls10=''
character (len=13), dimension(0:4095) :: calls12=''
character (len=13), dimension(1:MAXHASH) :: calls22=''
character (len=13), dimension(1:MAXRECENT) :: recent_calls=''
character (len=13) :: mycall13=''
@ -19,7 +19,7 @@ subroutine hash10(n10,c13)
character*13 c13
c13='<...>'
if(n10.lt.1 .or. n10.gt.1024) return
if(n10.lt.0 .or. n10.gt.1023) return
if(len(trim(calls10(n10))).gt.0) then
c13=calls10(n10)
c13='<'//trim(c13)//'>'
@ -33,7 +33,7 @@ subroutine hash12(n12,c13)
character*13 c13
c13='<...>'
if(n12.lt.1 .or. n12.gt.4096) return
if(n12.lt.0 .or. n12.gt.4095) return
if(len(trim(calls12(n12))).gt.0) then
c13=calls12(n12)
c13='<'//trim(c13)//'>'
@ -90,10 +90,10 @@ subroutine save_hash_call(c13,n10,n12,n22)
if(len(trim(cw)) .lt. 3) return
n10=ihashcall(cw,10)
if(n10.ge.1 .and. n10 .le. 1024 .and. cw.ne.mycall13) calls10(n10)=cw
if(n10.ge.0 .and. n10 .le. 1023 .and. cw.ne.mycall13) calls10(n10)=cw
n12=ihashcall(cw,12)
if(n12.ge.1 .and. n12 .le. 4096 .and. cw.ne.mycall13) calls12(n12)=cw
if(n12.ge.0 .and. n12 .le. 4095 .and. cw.ne.mycall13) calls12(n12)=cw
n22=ihashcall(cw,22)
if(any(ihash22.eq.n22)) then ! If entry exists, make sure callsign is the most recently received one
@ -124,12 +124,14 @@ subroutine pack77(msg0,i3,n3,c77)
integer ntel(3)
msg=msg0
if(i3.eq.0 .and. n3.eq.5) go to 5
i3_hint=i3
n3_hint=n3
i3=-1
n3=-1
if(i3_hint.eq.0 .and. n3_hint.eq.5) go to 5
! Convert msg to upper case; collapse multiple blanks; parse into words.
call split77(msg,nwords,nw,w)
i3=-1
n3=-1
if(msg(1:3).eq.'CQ ' .or. msg(1:3).eq.'DE ' .or. msg(1:4).eq.'QRZ ') go to 100
! Check 0.1 (DXpedition mode)
@ -160,7 +162,7 @@ subroutine pack77(msg0,i3,n3,c77)
go to 900
endif
100 call pack77_06(nwords,w,i3,n3,c77)
100 call pack77_06(nwords,w,i3,n3,c77,i3_hint,n3_hint)
if(i3.ge.0) go to 900
! Check Type 1 (Standard 77-bit message) or Type 2, with optional "/P"
@ -203,7 +205,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
integer ntel(3)
character*77 c77
character*37 msg
character*13 call_1,call_2,call_3
character*13 call_1,call_2,call_3,call_1a
character*13 mycall13_0,dxcall13_0
character*11 c11
character*3 crpt,cntx,cpfx
@ -214,7 +216,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
character*38 c
character*36 a2
integer hashmy10,hashmy12,hashmy22,hashdx10,hashdx12,hashdx22
logical unpk28_success,unpk77_success
logical unpk28_success,unpk77_success,unpkg4_success
logical dxcall13_set,mycall13_set
data a2/'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'/,nzzz/46656/
@ -276,11 +278,16 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
read(c77(72:77),'(2b3)') n3,i3
msg=repeat(' ',37)
if(i3.eq.0 .and. n3.eq.0) then
! 0.0 Free text
call unpacktext77(c77(1:71),msg(1:13))
msg(14:)=' '
msg=adjustl(msg)
if(msg(1:1).eq.' ') then
unpk77_success=.false.
return
endif
else if(i3.eq.0 .and. n3.eq.1) then
! 0.1 K1ABC RR73; W9XYZ <KH1/KH7Z> -11 28 28 10 5 71 DXpedition Mode
@ -346,38 +353,32 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
msg=adjustl(msg)
else if(i3.eq.0 .and. n3.eq.6) then
read(c77(50:50),'(b1)') j2a
j2b=0
if(j2a.eq.0) read(c77(49:49),'(b1)') j2b
j2=2*j2a+j2b
if(j2.eq.0) then
read(c77(49:50),'(2b1)') j2a,j2b
itype=2
if(j2b.eq.0 .and. j2a.eq.0) itype=1
if(j2b.eq.0 .and. j2a.eq.1) itype=3
if(itype.eq.1) then
! WSPR Type 1
read(c77,2010) n28,igrid4,idbm
2010 format(b28.28,b15.15,b5.5)
idbm=nint(idbm*10.0/3.0)
call unpack28(n28,call_1,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
call to_grid4(igrid4,grid4)
call to_grid4(igrid4,grid4,unpkg4_success)
if(.not.unpkg4_success) unpk77_success=.false.
write(crpt,'(i3)') idbm
msg=trim(call_1)//' '//grid4//' '//trim(adjustl(crpt))
if (unpk77_success) call save_hash_call(call_1,n10,n12,n22) !### Is this OK here? ###
else if(j2.eq.1) then
else if(itype.eq.2) then
! WSPR Type 2
read(c77,2030) n28,igrid6
2030 format(b22.22,b25.25)
call unpack28(n28,call_1,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
call to_grid6(igrid6,grid6)
msg=trim(call_1)//' '//grid6
else if(j2.eq.2) then
! WSPR Type 3
read(c77,2020) n28,npfx,idbm
2020 format(b28.28,b16.16,b5.5)
idbm=nint(idbm*10.0/3.0)
call unpack28(n28,call_1,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
write(crpt,'(i3)') idbm
cpfx=' '
if(npfx.lt.nzzz) then
! Prefix
do i=3,1,-1
@ -387,10 +388,11 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
if(npfx.eq.0) exit
enddo
msg=trim(adjustl(cpfx))//'/'//trim(call_1)//' '//trim(adjustl(crpt))
call_1a=trim(adjustl(cpfx))//'/'//trim(call_1)
call save_hash_call(call_1a,n10,n12,n22) !### Is this OK here? ###
else
! Suffix
npfx=npfx-nzzz
cpfx=' '
if(npfx.le.35) then
cpfx(1:1)=a2(npfx+1:npfx+1)
else if(npfx.gt.35 .and. npfx.le.1295) then
@ -405,10 +407,25 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
return
endif
msg=trim(call_1)//'/'//trim(adjustl(cpfx))//' '//trim(adjustl(crpt))
call_1a=trim(call_1)//'/'//trim(adjustl(cpfx))
call save_hash_call(call_1a,n10,n12,n22) !### Is this OK here? ###
endif
else if(itype.eq.3) then
! WSPR Type 3
read(c77,2030) n22,igrid6
2030 format(b22.22,b25.25)
n28=n22+2063592
call unpack28(n28,call_1,unpk28_success)
if(.not.unpk28_success) unpk77_success=.false.
call to_grid(igrid6,grid6,unpkg4_success)
if(.not.unpkg4_success) unpk77_success=.false.
msg=trim(call_1)//' '//grid6
endif
else if(i3.eq.0 .and. n3.gt.6) then
unpk77_success=.false.
else if(i3.eq.1 .or. i3.eq.2) then
! Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest)
read(c77,1000) n28a,ipa,n28b,ipb,ir,igrid4,i3
@ -435,7 +452,8 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
if(i.ge.4) call add_call_to_recent_calls(call_2)
endif
if(igrid4.le.MAXGRID4) then
call to_grid4(igrid4,grid4)
call to_grid4(igrid4,grid4,unpkg4_success)
if(.not.unpkg4_success) unpk77_success=.false.
if(ir.eq.0) msg=trim(call_1)//' '//trim(call_2)//' '//grid4
if(ir.eq.1) msg=trim(call_1)//' '//trim(call_2)//' R '//grid4
if(msg(1:3).eq.'CQ ' .and. ir.eq.1) unpk77_success=.false.
@ -446,7 +464,9 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
if(irpt.eq.3) msg=trim(call_1)//' '//trim(call_2)//' RR73'
if(irpt.eq.4) msg=trim(call_1)//' '//trim(call_2)//' 73'
if(irpt.ge.5) then
write(crpt,'(i3.2)') irpt-35
isnr=irpt-35
if(isnr.gt.50) isnr=isnr-101
write(crpt,'(i3.2)') isnr
if(crpt(1:1).eq.' ') crpt(1:1)='+'
if(ir.eq.0) msg=trim(call_1)//' '//trim(call_2)//' '//crpt
if(ir.eq.1) msg=trim(call_1)//' '//trim(call_2)//' R'//crpt
@ -550,7 +570,7 @@ subroutine unpack77(c77,nrx,msg,unpk77_success)
nrs=52+irpt
write(cexch,1022) nrs,iserial
1022 format(i2,i4.4)
call to_grid6(igrid6,grid6)
call to_grid6(igrid6,grid6,unpk77_success)
if(ir.eq.0) msg=trim(call_1)//' '//trim(call_2)//' '//cexch//' '//grid6
if(ir.eq.1) msg=trim(call_1)//' '//trim(call_2)//' R '//cexch//' '//grid6
@ -897,7 +917,7 @@ subroutine pack77_03(nwords,w,i3,n3,c77)
ntx=-1
j=len(trim(w(nwords-1)))-1
read(w(nwords-1)(1:j),*,err=1) ntx !Number of transmitters
read(w(nwords-1)(1:j),*,err=1,end=1) ntx !Number of transmitters
1 if(ntx.lt.1 .or. ntx.gt.32) return
nclass=ichar(w(nwords-1)(j+1:j+1))-ichar('A')
@ -925,7 +945,7 @@ subroutine pack77_03(nwords,w,i3,n3,c77)
end subroutine pack77_03
subroutine pack77_06(nwords,w,i3,n3,c77)
subroutine pack77_06(nwords,w,i3,n3,c77,i3_hint,n3_hint)
character*13 w(19)
character*77 c77
@ -942,13 +962,14 @@ subroutine pack77_06(nwords,w,i3,n3,c77)
grid4(3:3).ge.'0' .and. grid4(3:3).le.'9' .and. &
grid4(4:4).ge.'0' .and. grid4(4:4).le.'9'
is_grid6(grid6)=len(trim(grid6)).eq.6 .and. &
is_grid6(grid6)=(len(trim(grid6)).eq.6.or.len(trim(grid6)).eq.4).and. &
grid6(1:1).ge.'A' .and. grid6(1:1).le.'R' .and. &
grid6(2:2).ge.'A' .and. grid6(2:2).le.'R' .and. &
grid6(3:3).ge.'0' .and. grid6(3:3).le.'9' .and. &
grid6(4:4).ge.'0' .and. grid6(4:4).le.'9' .and. &
grid6(5:5).ge.'A' .and. grid6(5:5).le.'X' .and. &
grid6(6:6).ge.'A' .and. grid6(6:6).le.'X'
(len(trim(grid6)).eq.4.or. &
(grid6(5:5).ge.'A' .and. grid6(5:5).le.'X' .and. &
grid6(6:6).ge.'A' .and. grid6(6:6).le.'X'))
is_digit(c)=c.ge.'0' .and. c.le.'9'
@ -1020,22 +1041,32 @@ subroutine pack77_06(nwords,w,i3,n3,c77)
go to 900
endif
if(nwords.eq.2 .and. m1.ge.5 .and. m1.le.12 .and. m2.le.6) then
if(i3_hint.eq.0.and.n3_hint.eq.6.and.nwords.eq.2 .and. m1.ge.5 &
.and. m1.le.12 .and. m2.le.6) then
! WSPR Type 3
!n3_hint=6 and i3_hint=0 is a hint that the caller wanted a
!50-bit encoding rather than the possible alternative n3=4 77-bit
!encoding
if(index(w(1),'<').lt.1 .or. index(w(1),'>').lt.1) go to 900
grid6=w(2)(1:6)
if(.not.is_grid6(grid6)) go to 900
i3=0
n3=6
call pack28(w(1),n28)
k1=(ichar(grid6(1:1))-ichar('A'))*18*10*10*24*24
k2=(ichar(grid6(2:2))-ichar('A'))*10*10*24*24
k3=(ichar(grid6(3:3))-ichar('0'))*10*24*24
k4=(ichar(grid6(4:4))-ichar('0'))*24*24
k5=(ichar(grid6(5:5))-ichar('A'))*24
k6=(ichar(grid6(6:6))-ichar('A'))
igrid6=k1+k2+k3+k4+k5+k6
write(c77,1030) n28,igrid6,2,0,n3,i3
n22=n28-2063592
k1=(ichar(grid6(1:1))-ichar('A'))*18*10*10*25*25
k2=(ichar(grid6(2:2))-ichar('A'))*10*10*25*25
k3=(ichar(grid6(3:3))-ichar('0'))*10*25*25
k4=(ichar(grid6(4:4))-ichar('0'))*25*25
if (grid6(5:6).eq.' ') then
igrid6=k1+k2+k3+k4+24*25+24
else
k5=(ichar(grid6(5:5))-ichar('A'))*25
k6=(ichar(grid6(6:6))-ichar('A'))
igrid6=k1+k2+k3+k4+k5+k6
endif
write(c77,1030) n22,igrid6,2,0,n3,i3
1030 format(b22.22,b25.25,b3.3,b21.21,2b3.3)
endif
@ -1083,10 +1114,12 @@ subroutine pack77_1(nwords,w,i3,n3,c77)
if(c1.eq.'+' .or. c1.eq.'-') then
ir=0
read(w(nwords),*,err=900) irpt
if(irpt.ge.-50 .and. irpt.le.-31) irpt=irpt+101
irpt=irpt+35
else if(c2.eq.'R+' .or. c2.eq.'R-') then
ir=1
read(w(nwords)(2:),*) irpt
if(irpt.ge.-50 .and. irpt.le.-31) irpt=irpt+101
irpt=irpt+35
else if(trim(w(nwords)).eq.'RRR') then
ir=0
@ -1467,44 +1500,96 @@ subroutine add_call_to_recent_calls(callsign)
return
end subroutine add_call_to_recent_calls
subroutine to_grid4(n,grid4)
subroutine to_grid4(n,grid4,ok)
character*4 grid4
logical ok
ok=.false.
j1=n/(18*10*10)
if (j1.lt.0.or.j1.gt.17) goto 900
n=n-j1*18*10*10
j2=n/(10*10)
if (j2.lt.0.or.j2.gt.17) goto 900
n=n-j2*10*10
j3=n/10
if (j3.lt.0.or.j3.gt.9) goto 900
j4=n-j3*10
if (j4.lt.0.or.j4.gt.9) goto 900
grid4(1:1)=char(j1+ichar('A'))
grid4(2:2)=char(j2+ichar('A'))
grid4(3:3)=char(j3+ichar('0'))
grid4(4:4)=char(j4+ichar('0'))
return
ok=.true.
900 return
end subroutine to_grid4
subroutine to_grid6(n,grid6)
subroutine to_grid6(n,grid6,ok)
character*6 grid6
logical ok
ok=.false.
j1=n/(18*10*10*24*24)
if (j1.lt.0.or.j1.gt.17) goto 900
n=n-j1*18*10*10*24*24
j2=n/(10*10*24*24)
if (j2.lt.0.or.j2.gt.17) goto 900
n=n-j2*10*10*24*24
j3=n/(10*24*24)
if (j3.lt.0.or.j3.gt.9) goto 900
n=n-j3*10*24*24
j4=n/(24*24)
if (j4.lt.0.or.j4.gt.9) goto 900
n=n-j4*24*24
j5=n/24
if (j5.lt.0.or.j5.gt.23) goto 900
j6=n-j5*24
if (j6.lt.0.or.j6.gt.23) goto 900
grid6(1:1)=char(j1+ichar('A'))
grid6(2:2)=char(j2+ichar('A'))
grid6(3:3)=char(j3+ichar('0'))
grid6(4:4)=char(j4+ichar('0'))
grid6(5:5)=char(j5+ichar('A'))
grid6(6:6)=char(j6+ichar('A'))
ok=.true.
return
900 return
end subroutine to_grid6
subroutine to_grid(n,grid6,ok)
! 4-, or 6-character grid
character*6 grid6
logical ok
ok=.false.
j1=n/(18*10*10*25*25)
if (j1.lt.0.or.j1.gt.17) goto 900
n=n-j1*18*10*10*25*25
j2=n/(10*10*25*25)
if (j2.lt.0.or.j2.gt.17) goto 900
n=n-j2*10*10*25*25
j3=n/(10*25*25)
if (j3.lt.0.or.j3.gt.9) goto 900
n=n-j3*10*25*25
j4=n/(25*25)
if (j4.lt.0.or.j4.gt.9) goto 900
n=n-j4*25*25
j5=n/25
if (j5.lt.0.or.j5.gt.24) goto 900
j6=n-j5*25
if (j6.lt.0.or.j6.gt.24) goto 900
grid6=''
grid6(1:1)=char(j1+ichar('A'))
grid6(2:2)=char(j2+ichar('A'))
grid6(3:3)=char(j3+ichar('0'))
grid6(4:4)=char(j4+ichar('0'))
if (j5.ne.24.or.j6.ne.24) then
grid6(5:5)=char(j5+ichar('A'))
grid6(6:6)=char(j6+ichar('A'))
endif
ok=.true.
900 return
end subroutine to_grid
end module packjt77

441
lib/C_interface_module.f90 Normal file
View File

@ -0,0 +1,441 @@
! FILE: c_interface_module.f90
! PURPOSE: Supplement ISO-C-Binding to provide type aliases and interfaces
! to common ISO-C string functions to aid working with strings.
! AUTHOR: Joseph M. Krahn
! STATUS: Still in development. Reasonably complete, but somewhat limited testing.
!
! The idea is to provide type aliases for all ISO-C types, so that the
! Fortran interface code more explicitly defines the actual C interface.
! This should be updated to support F2008 variable-length allocatable
! strings.
!
! Entity names all have the "C_" prefix, as with ISO-C-Binding, with a
! few exceptions.
!
! Sourced from: http://fortranwiki.org/fortran/show/c_interface_module
!
! One FORALL statement reverted to a DO loop to avoid a gfortran 4.9.2 ICE
!
module C_interface_module
use, intrinsic :: ISO_C_Binding, &
! C type aliases for pointer derived types:
C_ptr => C_ptr , &
C_char_ptr => C_ptr, &
C_const_char_ptr => C_ptr, &
C_void_ptr => C_ptr, &
C_const_void_ptr => C_ptr
implicit none
public
!----------------------------------------------------------------------------
! C type aliases for intrinsic type KIND parameters:
! NOTE: a C enum may not always be a standard C int
integer, parameter :: C_enum = C_int
! Defining off_t is difficult, because it may depend on "LARGEFILE" selection.
! integer, parameter :: C_off_t = ??
! C string terminator alais using the 3-letter ASCII name.
! The C_ prefix is not used because it is just an ASCII character.
character(len=1,kind=C_char), parameter :: NUL = C_NULL_char
! NOTE: In C, "char" is distinct from "signed char", unlike integers.
! The plain "char" type is specific for text/string values, whereas
! "signed char" should indicate 1-byte integer data.
!
! Most ISO-C systems have wide chars "wchar_t", but Fortran compilers
! have limited support for different character kinds. UTF encoding
! adds more complexity. This should be updated as Fortran compilers
! include support for more character types.
!
! Fortran does not (yet) support unsigned types.
integer, parameter :: &
C_unsigned = C_int, &
C_unsigned_short = C_short, &
C_unsigned_long = C_long, &
C_unsigned_long_long = C_long_long, &
C_unsigned_char = C_signed_char, &
C_ssize_t = C_size_t, &
C_uint8_t = C_int8_t, &
C_uint16_t = C_int16_t, &
C_uint32_t = C_int32_t, &
C_uint64_t = C_int64_t, &
C_uint_least8_t = C_int_least8_t, &
C_uint_least16_t = C_int_least16_t, &
C_uint_least32_t = C_int_least32_t, &
C_uint_least64_t = C_int_least64_t, &
C_uint_fast8_t = C_int_fast8_t, &
C_uint_fast16_t = C_int_fast16_t, &
C_uint_fast32_t = C_int_fast32_t, &
C_uint_fast64_t = C_int_fast64_t, &
C_uintmax_t = C_intmax_t
! Note: ptrdiff_t cannot be reliably defined from other types.
! When practical, it is larger than a pointer because it benefits
! from the full unsigned range in both positive and negative directions.
! Integer versions including 'int', where the 'int' is optional:
integer, parameter :: &
C_short_int = C_short, &
C_long_int = C_long, &
C_long_long_int = C_long_long, &
C_unsigned_int = C_unsigned, &
C_unsigned_short_int = C_short, &
C_unsigned_long_int = C_long, &
C_unsigned_long_long_int = C_long_long
interface C_F_string
module procedure C_F_string_ptr
module procedure C_F_string_chars
end interface C_F_string
interface F_C_string
module procedure F_C_string_ptr
module procedure F_C_string_chars
end interface F_C_string
!=======================================================================
! Some useful ISO C library string functions from <string.h>
! These are based on GCC header sections marked as NAMESPACE_STD
interface
! Copy N bytes of SRC to DEST, no aliasing or overlapping allowed.
! extern void *memcpy (void *dest, const void *src, size_t n);
function C_memcpy(dest, src, n) result(result) bind(C,name="memcpy")
import C_void_ptr, C_size_t
type(C_void_ptr) :: result
type(C_void_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_void_ptr), value, intent(in) :: src ! target=intent(in)
integer(C_size_t), value, intent(in) :: n
end function C_memcpy
! Copy N bytes of SRC to DEST, guaranteeing correct behavior for overlapping strings.
!extern void *memmove (void *dest, const void *src, size_t n)
function C_memmove(dest, src, n) result(result) bind(C,name="memmove")
import C_void_ptr, C_size_t
type(C_void_ptr) :: result
type(C_void_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_void_ptr), value, intent(in) :: src
integer(C_size_t), value, intent(in) :: n
end function C_memmove
! Set N bytes of S to C.
!extern void *memset (void *s, int c, size_t n)
function C_memset(s, c, n) result(result) bind(C,name="memset")
import C_void_ptr, C_int, C_size_t
type(C_void_ptr) :: result
type(C_void_ptr), value, intent(in) :: s ! target=intent(out)
integer(C_int), value, intent(in) :: c
integer(C_size_t), value, intent(in) :: n
end function C_memset
! Compare N bytes of S1 and S2.
!extern int memcmp (const void *s1, const void *s2, size_t n)
pure function C_memcmp(s1, s2, n) result(result) bind(C,name="memcmp")
import C_int, C_void_ptr, C_size_t
integer(C_int) :: result
type(C_void_ptr), value, intent(in) :: s1
type(C_void_ptr), value, intent(in) :: s2
integer(C_size_t), value, intent(in) :: n
end function C_memcmp
! Search N bytes of S for C.
!extern void *memchr (const void *s, int c, size_t n)
pure function C_memchr(s, c, n) result(result) bind(C,name="memchr")
import C_void_ptr, C_int, C_size_t
type(C_void_ptr) :: result
type(C_void_ptr), value, intent(in) :: s
integer(C_int), value, intent(in) :: c
integer(C_size_t), value, intent(in) :: n
end function C_memchr
! Copy SRC to DEST.
!extern char *strcpy (char *dest, const char *src)
function C_strcpy(dest, src) result(result) bind(C,name="strcpy")
import C_char_ptr, C_size_t
type(C_char_ptr) :: result
type(C_char_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_char_ptr), value, intent(in) :: src
end function C_strcpy
! Copy no more than N characters of SRC to DEST.
!extern char *strncpy (char *dest, const char *src, size_t n)
function C_strncpy(dest, src, n) result(result) bind(C,name="strncpy")
import C_char_ptr, C_size_t
type(C_char_ptr) :: result
type(C_char_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_char_ptr), value, intent(in) :: src
integer(C_size_t), value, intent(in) :: n
end function C_strncpy
! Append SRC onto DEST.
!extern char *strcat (char *dest, const char *src)
function C_strcat(dest, src) result(result) bind(C,name="strcat")
import C_char_ptr, C_size_t
type(C_char_ptr) :: result
type(C_char_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_char_ptr), value, intent(in) :: src
end function C_strcat
! Append no more than N characters from SRC onto DEST.
!extern char *strncat (char *dest, const char *src, size_t n)
function C_strncat(dest, src, n) result(result) bind(C,name="strncat")
import C_char_ptr, C_size_t
type(C_char_ptr) :: result
type(C_char_ptr), value, intent(in) :: dest ! target=intent(out)
type(C_char_ptr), value, intent(in) :: src
integer(C_size_t), value, intent(in) :: n
end function C_strncat
! Compare S1 and S2.
!extern int strcmp (const char *s1, const char *s2)
pure function C_strcmp(s1, s2) result(result) bind(C,name="strcmp")
import C_int, C_char_ptr, C_size_t
integer(C_int) :: result
type(C_char_ptr), value, intent(in) :: s1
type(C_char_ptr), value, intent(in) :: s2
end function C_strcmp
! Compare N characters of S1 and S2.
!extern int strncmp (const char *s1, const char *s2, size_t n)
pure function C_strncmp(s1, s2, n) result(result) bind(C,name="strncmp")
import C_int, C_char_ptr, C_size_t
integer(C_int) :: result
type(C_char_ptr), value, intent(in) :: s1
type(C_char_ptr), value, intent(in) :: s2
integer(C_size_t), value, intent(in) :: n
end function C_strncmp
! Return the length of S.
!extern size_t strlen (const char *s)
pure function C_strlen(s) result(result) bind(C,name="strlen")
import C_char_ptr, C_size_t
integer(C_size_t) :: result
type(C_char_ptr), value, intent(in) :: s !character(len=*), intent(in)
end function C_strlen
end interface
! End of <string.h>
!=========================================================================
! Standard ISO-C malloc routines:
interface
! void *calloc(size_t nmemb, size_t size);
type(C_void_ptr) function C_calloc(nmemb, size) bind(C,name="calloc")
import C_void_ptr, C_size_t
integer(C_size_t), value, intent(in) :: nmemb, size
end function C_calloc
! void *malloc(size_t size);
type(C_void_ptr) function C_malloc(size) bind(C,name="malloc")
import C_void_ptr, C_size_t
integer(C_size_t), value, intent(in) :: size
end function C_malloc
! void free(void *ptr);
subroutine C_free(ptr) bind(C,name="free")
import C_void_ptr
type(C_void_ptr), value, intent(in) :: ptr
end subroutine C_free
! void *realloc(void *ptr, size_t size);
type(C_void_ptr) function C_realloc(ptr,size) bind(C,name="realloc")
import C_void_ptr, C_size_t
type(C_void_ptr), value, intent(in) :: ptr
integer(C_size_t), value, intent(in) :: size
end function C_realloc
end interface
interface assignment(=)
module procedure F_string_assign_C_string
end interface assignment(=)
!==========================================================================
contains
! HACK: For some reason, C_associated was not defined as pure.
pure logical function C_associated_pure(ptr) result(associated)
type(C_ptr), intent(in) :: ptr
integer(C_intptr_t) :: iptr
iptr = transfer(ptr,iptr)
associated = (iptr /= 0)
end function C_associated_pure
! Set a fixed-length Fortran string to the value of a C string.
subroutine F_string_assign_C_string(F_string, C_string)
character(len=*), intent(out) :: F_string
type(C_ptr), intent(in) :: C_string
character(len=1,kind=C_char), pointer :: p_chars(:)
integer :: i
if (.not. C_associated(C_string) ) then
F_string = ' '
else
call C_F_pointer(C_string,p_chars,[huge(0)])
i=1
do while(p_chars(i)/=NUL .and. i<=len(F_string))
F_string(i:i) = p_chars(i)
i=i+1
end do
if (i<len(F_string)) F_string(i:) = ' '
end if
end subroutine F_string_assign_C_string
! Copy a C string, passed by pointer, to a Fortran string.
! If the C pointer is NULL, the Fortran string is blanked.
! C_string must be NUL terminated, or at least as long as F_string.
! If C_string is longer, it is truncated. Otherwise, F_string is
! blank-padded at the end.
subroutine C_F_string_ptr(C_string, F_string)
type(C_ptr), intent(in) :: C_string
character(len=*), intent(out) :: F_string
character(len=1,kind=C_char), dimension(:), pointer :: p_chars
integer :: i
if (.not. C_associated(C_string)) then
F_string = ' '
else
call C_F_pointer(C_string,p_chars,[huge(0)])
i=1
do while(p_chars(i)/=NUL .and. i<=len(F_string))
F_string(i:i) = p_chars(i)
i=i+1
end do
if (i<len(F_string)) F_string(i:) = ' '
end if
end subroutine C_F_string_ptr
! Copy a C string, passed as a char-array reference, to a Fortran string.
subroutine C_F_string_chars(C_string, F_string)
character(len=1,kind=C_char), intent(in) :: C_string(*)
character(len=*), intent(out) :: F_string
integer :: i
i=1
do while(C_string(i)/=NUL .and. i<=len(F_string))
F_string(i:i) = C_string(i)
i=i+1
end do
if (i<len(F_string)) F_string(i:) = ' '
end subroutine C_F_string_chars
! Copy a Fortran string to an allocated C string pointer.
! If the C pointer is NULL, no action is taken. (Maybe auto allocate via libc call?)
! If the length is not passed, the C string must be at least: len(F_string)+1
! If the length is passed and F_string is too long, it is truncated.
subroutine F_C_string_ptr(F_string, C_string, C_string_len)
character(len=*), intent(in) :: F_string
type(C_ptr), intent(in) :: C_string ! target = intent(out)
integer, intent(in), optional :: C_string_len ! Max string length,
! INCLUDING THE TERMINAL NUL
character(len=1,kind=C_char), dimension(:), pointer :: p_chars
integer :: i, strlen
strlen = len(F_string)
if (present(C_string_len)) then
if (C_string_len <= 0) return
strlen = min(strlen,C_string_len)
end if
if (.not. C_associated(C_string)) then
return
end if
call C_F_pointer(C_string,p_chars,[strlen+1])
forall (i=1:strlen)
p_chars(i) = F_string(i:i)
end forall
p_chars(strlen+1) = NUL
end subroutine F_C_string_ptr
pure function C_strlen_safe(s) result(length)
integer(C_size_t) :: length
type(C_char_ptr), value, intent(in) :: s
if (.not. C_associated_pure(s)) then
length = 0
else
length = C_strlen(s)
end if
end function C_strlen_safe
function C_string_value(C_string) result(F_string)
type(C_ptr), intent(in) :: C_string
character(len=C_strlen_safe(C_string)) :: F_string
character(len=1,kind=C_char), dimension(:), pointer :: p_chars
integer :: i, length
length = len(F_string)
if (length/=0) then
call C_F_pointer(C_string,p_chars,[length])
forall (i=1:length)
F_string(i:i) = p_chars(i)
end forall
end if
end function C_string_value
! Copy a Fortran string to a C string passed by char-array reference.
! If the length is not passed, the C string must be at least: len(F_string)+1
! If the length is passed and F_string is too long, it is truncated.
subroutine F_C_string_chars(F_string, C_string, C_string_len)
character(len=*), intent(in) :: F_string
character(len=1,kind=C_char), dimension(*), intent(out) :: C_string
integer, intent(in), optional :: C_string_len ! Max string length,
! INCLUDING THE TERMINAL NUL
integer :: i, strlen
strlen = len(F_string)
if (present(C_string_len)) then
if (C_string_len <= 0) return
strlen = min(strlen,C_string_len)
end if
forall (i=1:strlen)
C_string(i) = F_string(i:i)
end forall
C_string(strlen+1) = NUL
end subroutine F_C_string_chars
! NOTE: Strings allocated here must be freed by the
! C library, such as via C_free() or C_string_free(),
type(C_ptr) function F_C_string_dup(F_string,length) result(C_string)
character(len=*), intent(in) :: F_string
integer, intent(in), optional :: length
character(len=1,kind=C_char), pointer :: C_string_ptr(:)
integer :: i
integer(C_size_t) :: strlen
if (present(length)) then
strlen = length
else
strlen = len(F_string)
end if
if (strlen <= 0) then
C_string = C_NULL_ptr
else
C_string = C_malloc(strlen+1)
if (C_associated(C_string)) then
call C_F_pointer(C_string,C_string_ptr,[strlen+1])
forall (i=1:strlen)
C_string_ptr(i) = F_string(i:i)
end forall
C_string_ptr(strlen+1) = NUL
end if
end if
end function F_C_string_dup
! NOTE: Strings allocated here must be freed by the
! C library, such as via C_free() or C_string_free(),
type(C_ptr) function C_string_alloc(length) result(C_string)
integer(C_size_t), intent(in) :: length
character(len=1,kind=C_char), pointer :: C_charptr
C_string = C_malloc(length+1)
if (C_associated(C_string)) then
call C_F_pointer(C_string,C_charptr)
C_charptr = NUL
end if
end function C_string_alloc
subroutine C_string_free(string)
type(C_ptr), intent(inout) :: string
if (C_associated(string)) then
call C_free(string)
string = C_NULL_ptr
end if
end subroutine C_string_free
end module C_interface_module

View File

@ -1,5 +1,4 @@
module astro_module
use, intrinsic :: iso_c_binding, only : c_int, c_double, c_bool, c_char, c_ptr, c_size_t, c_f_pointer
implicit none
private
@ -7,50 +6,37 @@ module astro_module
contains
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid_cp,mygrid_len, &
hisgrid_cp,hisgrid_len,AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8, &
subroutine astrosub(nyear,month,nday,uth8,freq8,mygrid_cp, &
hisgrid_cp,AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8, &
ntsky,ndop,ndop00,RAMoon8,DecMoon8,Dgrd8,poloffset8,xnr8,techo8,width1, &
width2,bTx,AzElFileName_cp,AzElFileName_len,jpleph_cp,jpleph_len) &
width2,bTx,AzElFileName_cp,jpleph_file_name_cp) &
bind (C, name="astrosub")
integer, parameter :: dp = selected_real_kind(15, 50)
use :: types, only: dp
use :: C_interface_module, only: C_int, C_double, C_bool, C_ptr, C_string_value, assignment(=)
integer(c_int), intent(in), value :: nyear, month, nday
real(c_double), intent(in), value :: uth8, freq8
real(c_double), intent(out) :: AzSun8, ElSun8, AzMoon8, ElMoon8, AzMoonB8, &
integer(C_int), intent(in), value :: nyear, month, nday
real(C_double), intent(in), value :: uth8, freq8
real(C_double), intent(out) :: AzSun8, ElSun8, AzMoon8, ElMoon8, AzMoonB8, &
ElMoonB8, Ramoon8, DecMoon8, Dgrd8, poloffset8, xnr8, techo8, width1, &
width2
integer(c_int), intent(out) :: ntsky, ndop, ndop00
logical(c_bool), intent(in), value :: bTx
type(c_ptr), intent(in), value :: mygrid_cp, hisgrid_cp, AzElFileName_cp, jpleph_cp
integer(c_size_t), intent(in), value :: mygrid_len, hisgrid_len, AzElFileName_len, jpleph_len
integer(C_int), intent(out) :: ntsky, ndop, ndop00
logical(C_bool), intent(in), value :: bTx
type(C_ptr), value, intent(in) :: mygrid_cp, hisgrid_cp, AzElFileName_cp, &
jpleph_file_name_cp
character(len=6) :: mygrid, hisgrid
character(kind=c_char, len=:), allocatable :: AzElFileName
character(len=:), allocatable :: AzElFileName
character(len=1) :: c1
integer :: ih, im, imin, is, isec, nfreq, nRx
real(dp) :: AzAux, ElAux, dbMoon8, dfdt, dfdt0, doppler, doppler00, HA8, sd8, xlst8
character*256 jpleph_file_name
common/jplcom/jpleph_file_name
block
character(kind=c_char, len=mygrid_len), pointer :: mygrid_fp
character(kind=c_char, len=hisgrid_len), pointer :: hisgrid_fp
character(kind=c_char, len=AzElFileName_len), pointer :: AzElFileName_fp
character(kind=c_char, len=jpleph_len), pointer :: jpleph_fp
call c_f_pointer(cptr=mygrid_cp, fptr=mygrid_fp)
mygrid = mygrid_fp
mygrid_fp => null()
call c_f_pointer(cptr=hisgrid_cp, fptr=hisgrid_fp)
hisgrid = hisgrid_fp
hisgrid_fp => null()
call c_f_pointer(cptr=AzElFileName_cp, fptr=AzElFileName_fp)
AzElFileName = AzElFileName_fp
AzElFileName_fp => null()
call c_f_pointer(cptr=jpleph_cp, fptr=jpleph_fp)
jpleph_file_name = jpleph_fp
jpleph_fp => null()
end block
mygrid = mygrid_cp
hisgrid = hisgrid_cp
AzElFileName = C_string_value (AzElFileName_cp)
jpleph_file_name = jpleph_file_name_cp
call astro0(nyear,month,nday,uth8,freq8,mygrid,hisgrid, &
AzSun8,ElSun8,AzMoon8,ElMoon8,AzMoonB8,ElMoonB8,ntsky,ndop,ndop00, &

57
lib/blanker.f90 Normal file
View File

@ -0,0 +1,57 @@
subroutine blanker(iwave,nz,ndropmax,npct,c_bigfft)
integer*2 iwave(nz)
complex c_bigfft(0:nz/2)
integer hist(0:32768)
real fblank !Fraction of points to be blanked
fblank=0.01*npct
hist=0
do i=1,nz
! ### NB: if iwave(i)=-32768, abs(iwave(i))=-32768 ###
if(iwave(i).eq.-32768) iwave(i)=-32767
n=abs(iwave(i))
hist(n)=hist(n)+1
enddo
n=0
do i=32768,0,-1
n=n+hist(i)
if(n.ge.nint(nz*fblank/ndropmax)) exit
enddo
nthresh=i
ndrop=0
ndropped=0
xx=0.
do i=1,nz
i0=iwave(i)
if(ndrop.gt.0) then
i0=0
ndropped=ndropped+1
ndrop=ndrop-1
endif
! Start to apply blanking
if(abs(i0).gt.nthresh) then
i0=0
ndropped=ndropped+1
ndrop=ndropmax
endif
! Now copy the data into c_bigfft
if(iand(i,1).eq.1) then
xx=i0
else
yy=i0
j=i/2 - 1
c_bigfft(j)=cmplx(xx,yy)
endif
enddo
fblanked=fblanked + 0.1*(float(ndropped)/nz - fblanked)
fblanked=float(ndropped)/nz
! write(*,3001) npct,nthresh,fblanked
!3001 format(2i5,f7.3)
return
end subroutine blanker

View File

@ -1,4 +1,4 @@
integer, parameter :: NTMAX=300
integer, parameter :: NTMAX=30*60
integer, parameter :: NMAX=NTMAX*12000 !Total sample intervals (one minute)
integer, parameter :: NDMAX=NTMAX*1500 !Sample intervals at 1500 Hz rate
integer, parameter :: NSMAX=6827 !Max length of saved spectra

View File

@ -8,6 +8,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
use jt9_decode
use ft8_decode
use ft4_decode
use fst4_decode
include 'jt9com.f90'
include 'timer_common.inc'
@ -32,6 +33,10 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
integer :: decoded
end type counting_ft4_decoder
type, extends(fst4_decoder) :: counting_fst4_decoder
integer :: decoded
end type counting_fst4_decoder
real ss(184,NSMAX)
logical baddata,newdat65,newdat9,single_decode,bVHF,bad0,newdat,ex
integer*2 id2(NTMAX*12000)
@ -48,6 +53,11 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
type(counting_jt9_decoder) :: my_jt9
type(counting_ft8_decoder) :: my_ft8
type(counting_ft4_decoder) :: my_ft4
type(counting_fst4_decoder) :: my_fst4
rms=sqrt(dot_product(float(id2(1:180000)), &
float(id2(1:180000)))/180000.0)
if(rms.lt.3.0) go to 800
!cast C character arrays to Fortran character strings
datetime=transfer(params%datetime, datetime)
@ -62,6 +72,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
my_jt9%decoded = 0
my_ft8%decoded = 0
my_ft4%decoded = 0
my_fst4%decoded = 0
! For testing only: return Rx messages stored in a file as decodes
inquire(file='rx_messages.txt',exist=ex)
@ -180,9 +191,34 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
go to 800
endif
rms=sqrt(dot_product(float(id2(300000:310000)), &
float(id2(300000:310000)))/10000.0)
if(rms.lt.2.0) go to 800
if(params%nmode.eq.240) then
! We're in FST4 mode
ndepth=iand(params%ndepth,3)
iwspr=0
params%nsubmode=0
call timer('dec240 ',0)
call my_fst4%decode(fst4_decoded,id2,params%nutc, &
params%nQSOProgress,params%nfa,params%nfb, &
params%nfqso,ndepth,params%ntr,params%nexp_decode, &
params%ntol,params%emedelay,logical(params%nagain), &
logical(params%lapcqonly),mycall,hiscall,iwspr)
call timer('dec240 ',1)
go to 800
endif
if(params%nmode.eq.241) then
! We're in FST4W mode
ndepth=iand(params%ndepth,3)
iwspr=1
call timer('dec240 ',0)
call my_fst4%decode(fst4_decoded,id2,params%nutc, &
params%nQSOProgress,params%nfa,params%nfb, &
params%nfqso,ndepth,params%ntr,params%nexp_decode, &
params%ntol,params%emedelay,logical(params%nagain), &
logical(params%lapcqonly),mycall,hiscall,iwspr)
call timer('dec240 ',1)
go to 800
endif
! Zap data at start that might come from T/R switching transient?
nadd=100
@ -299,7 +335,7 @@ subroutine multimode_decoder(ss,id2,params,nfsample)
! JT65 is not yet producing info for nsynced, ndecoded.
800 ndecoded = my_jt4%decoded + my_jt65%decoded + my_jt9%decoded + &
my_ft8%decoded + my_ft4%decoded
my_ft8%decoded + my_ft4%decoded + my_fst4%decoded
if(params%nmode.eq.8 .and. params%nzhsym.eq.41) ndec41=ndecoded
if(params%nmode.eq.8 .and. params%nzhsym.eq.47) ndec47=ndecoded
if(params%nmode.eq.8 .and. params%nzhsym.eq.50) then
@ -660,4 +696,67 @@ contains
return
end subroutine ft4_decoded
subroutine fst4_decoded (this,nutc,sync,nsnr,dt,freq,decoded,nap, &
qual,ntrperiod,lwspr,fmid,w50)
use fst4_decode
implicit none
class(fst4_decoder), intent(inout) :: this
integer, intent(in) :: nutc
real, intent(in) :: sync
integer, intent(in) :: nsnr
real, intent(in) :: dt
real, intent(in) :: freq
character(len=37), intent(in) :: decoded
integer, intent(in) :: nap
real, intent(in) :: qual
integer, intent(in) :: ntrperiod
logical, intent(in) :: lwspr
real, intent(in) :: fmid
real, intent(in) :: w50
character*2 annot
character*37 decoded0
character*70 line
decoded0=decoded
annot=' '
if(nap.ne.0) then
write(annot,'(a1,i1)') 'a',nap
if(qual.lt.0.17) decoded0(37:37)='?'
endif
if(ntrperiod.lt.60) then
write(line,1001) nutc,nsnr,dt,nint(freq),decoded0,annot
1001 format(i6.6,i4,f5.1,i5,' ` ',1x,a37,1x,a2)
write(13,1002) nutc,nint(sync),nsnr,dt,freq,0,decoded0
1002 format(i6.6,i4,i5,f6.1,f8.0,i4,3x,a37,' FST4')
else
write(line,1003) nutc,nsnr,dt,nint(freq),decoded0,annot
1003 format(i4.4,i4,f5.1,i5,' ` ',1x,a37,1x,a2,2f7.3)
write(13,1004) nutc,nint(sync),nsnr,dt,freq,0,decoded0
1004 format(i4.4,i4,i5,f6.1,f8.0,i4,3x,a37,' FST4')
endif
if(fmid.ne.-999.0) then
if(w50.lt.0.95) write(line(65:70),'(f6.3)') w50
if(w50.ge.0.95) write(line(65:70),'(f6.2)') w50
endif
write(*,1005) line
1005 format(a70)
call flush(6)
call flush(13)
select type(this)
type is (counting_fst4_decoder)
this%decoded = this%decoded + 1
end select
return
end subroutine fst4_decoded
end subroutine multimode_decoder

BIN
lib/fsk4hf/.DS_Store vendored Normal file

Binary file not shown.

562
lib/fst280_decode.f90 Normal file
View File

@ -0,0 +1,562 @@
module fst280_decode
type :: fst280_decoder
procedure(fst280_decode_callback), pointer :: callback
contains
procedure :: decode
end type fst280_decoder
abstract interface
subroutine fst280_decode_callback (this,nutc,sync,nsnr,dt,freq, &
decoded,nap,qual,ntrperiod)
import fst280_decoder
implicit none
class(fst280_decoder), intent(inout) :: this
integer, intent(in) :: nutc
real, intent(in) :: sync
integer, intent(in) :: nsnr
real, intent(in) :: dt
real, intent(in) :: freq
character(len=37), intent(in) :: decoded
integer, intent(in) :: nap
real, intent(in) :: qual
integer, intent(in) :: ntrperiod
end subroutine fst280_decode_callback
end interface
contains
subroutine decode(this,callback,iwave,nutc,nQSOProgress,nfqso, &
nfa,nfb,nsubmode,ndeep,ntrperiod,nexp_decode,ntol)
use timer_module, only: timer
use packjt77
include 'fst280/fst280_params.f90'
parameter (MAXCAND=100)
class(fst280_decoder), intent(inout) :: this
procedure(fst280_decode_callback) :: callback
character*37 decodes(100)
character*37 msg
character*77 c77
complex, allocatable :: c2(:)
complex, allocatable :: cframe(:)
complex, allocatable :: c_bigfft(:) !Complex waveform
real, allocatable :: r_data(:)
real llr(280),llra(280),llrb(280),llrc(280),llrd(280)
real candidates(100,4)
real bitmetrics(328,4)
real s4(0:3,NN)
integer itone(NN)
integer hmod
integer*1 apmask(280),cw(280)
integer*1 hbits(328)
integer*1 message101(101),message74(74)
logical badsync,unpk77_success,single_decode
integer*2 iwave(300*12000)
this%callback => callback
hmod=2**nsubmode
if(nfqso+nqsoprogress.eq.-999) return
Keff=91
iwspr=0
nmax=15*12000
single_decode=iand(nexp_decode,32).eq.32
if(ntrperiod.eq.15) then
nsps=800
nmax=15*12000
ndown=20/hmod
if(hmod.eq.8) ndown=2
else if(ntrperiod.eq.30) then
nsps=1680
nmax=30*12000
ndown=42/hmod
if(hmod.eq.4) ndown=10
if(hmod.eq.8) ndown=5
else if(ntrperiod.eq.60) then
nsps=3888
nmax=60*12000
ndown=96/hmod
if(hmod.eq.1) ndown=108
else if(ntrperiod.eq.120) then
nsps=8200
nmax=120*12000
if(hmod.eq.1) ndown=205
ndown=100/hmod
else if(ntrperiod.eq.300) then
nsps=21168
nmax=300*12000
ndown=504/hmod
end if
nss=nsps/ndown
fs=12000.0 !Sample rate
fs2=fs/ndown
nspsec=nint(fs2)
dt=1.0/fs !Sample interval (s)
dt2=1.0/fs2
tt=nsps*dt !Duration of "itone" symbols (s)
baud=1.0/tt
nfft1=2*int(nmax/2)
nh1=nfft1/2
allocate( r_data(1:nfft1+2) )
allocate( c_bigfft(0:nfft1/2) )
nfft2=nfft1/ndown
allocate( c2(0:nfft2-1) )
allocate( cframe(0:164*nss-1) )
npts=nmax
if(single_decode) then
fa=max(100,nint(nfqso+1.5*hmod*baud-ntol))
fb=min(4800,nint(nfqso+1.5*hmod*baud+ntol))
else
fa=max(100,nfa)
fb=min(4800,nfb)
endif
if(ndeep.eq.3) then
ntmax=4 ! number of block sizes to try
jittermax=2
norder=3
elseif(ndeep.eq.2) then
ntmax=3
jittermax=2
norder=3
elseif(ndeep.eq.1) then
ntmax=1
jittermax=2
norder=2
endif
! The big fft is done once and is used for calculating the smoothed spectrum
! and also for downconverting/downsampling each candidate.
r_data(1:nfft1)=iwave(1:nfft1)
r_data(nfft1+1:nfft1+2)=0.0
call four2a(r_data,nfft1,1,-1,0)
c_bigfft=cmplx(r_data(1:nfft1+2:2),r_data(2:nfft1+2:2))
! Get first approximation of candidate frequencies
call get_candidates_fst280(c_bigfft,nfft1,nsps,hmod,fs,fa,fb, &
ncand,candidates,base)
ndecodes=0
decodes=' '
isbest1=0
isbest8=0
fc21=0.
fc28=0.
do icand=1,ncand
fc0=candidates(icand,1)
detmet=candidates(icand,2)
! Downconvert and downsample a slice of the spectrum centered on the
! rough estimate of the candidates frequency.
! Output array c2 is complex baseband sampled at 12000/ndown Sa/sec.
! The size of the downsampled c2 array is nfft2=nfft1/ndown
call fst280_downsample(c_bigfft,nfft1,ndown,fc0,c2)
call timer('sync280 ',0)
do isync=0,1
if(isync.eq.0) then
fc1=0.0
is0=1.5*nint(fs2)
ishw=1.5*is0
isst=4*hmod
ifhw=12
df=.1*baud
else if(isync.eq.1) then
fc1=fc21
if(hmod.eq.1) fc1=fc28
is0=isbest1
if(hmod.eq.1) is0=isbest8
ishw=4*hmod
isst=1*hmod
ifhw=7
df=.02*baud
endif
smax1=0.0
smax8=0.0
do if=-ifhw,ifhw
fc=fc1+df*if
do istart=max(1,is0-ishw),is0+ishw,isst
call sync_fst280(c2,istart,fc,hmod,1,nfft2,nss,fs2,sync1)
call sync_fst280(c2,istart,fc,hmod,8,nfft2,nss,fs2,sync8)
if(sync8.gt.smax8) then
fc28=fc
isbest8=istart
smax8=sync8
endif
if(sync1.gt.smax1) then
fc21=fc
isbest1=istart
smax1=sync1
endif
enddo
enddo
enddo
call timer('sync280 ',1)
if(smax8/smax1 .lt. 0.65 ) then
fc2=fc21
isbest=isbest1
if(hmod.gt.1) ntmax=1
njitter=2
else
fc2=fc28
isbest=isbest8
if(hmod.gt.1) ntmax=1
njitter=2
endif
fc_synced = fc0 + fc2
dt_synced = (isbest-fs2)*dt2 !nominal dt is 1 second so frame starts at sample fs2
candidates(icand,3)=fc_synced
candidates(icand,4)=isbest
enddo
! remove duplicate candidates
do icand=1,ncand
fc=candidates(icand,3)
isbest=nint(candidates(icand,4))
do ic2=1,ncand
fc2=candidates(ic2,3)
isbest2=nint(candidates(ic2,4))
if(ic2.ne.icand .and. fc2.gt.0.0) then
if(abs(fc2-fc).lt.0.05*baud) then ! same frequency
if(abs(isbest2-isbest).le.2) then
candidates(ic2,3)=-1
endif
endif
endif
enddo
enddo
ic=0
do icand=1,ncand
if(candidates(icand,3).gt.0) then
ic=ic+1
candidates(ic,:)=candidates(icand,:)
endif
enddo
ncand=ic
do icand=1,ncand
sync=candidates(icand,2)
fc_synced=candidates(icand,3)
isbest=nint(candidates(icand,4))
xdt=(isbest-nspsec)/fs2
call fst280_downsample(c_bigfft,nfft1,ndown,fc_synced,c2)
do ijitter=0,jittermax
if(ijitter.eq.0) ioffset=0
if(ijitter.eq.1) ioffset=1
if(ijitter.eq.2) ioffset=-1
is0=isbest+ioffset
if(is0.lt.0) cycle
cframe=c2(is0:is0+164*nss-1)
bitmetrics=0
call get_fst280_bitmetrics(cframe,nss,hmod,ntmax,bitmetrics,s4,badsync)
if(badsync) cycle
hbits=0
where(bitmetrics(:,1).ge.0) hbits=1
ns1=count(hbits( 71: 78).eq.(/0,0,0,1,1,0,1,1/))
ns2=count(hbits( 79: 86).eq.(/0,1,0,0,1,1,1,0/))
ns3=count(hbits(157:164).eq.(/0,0,0,1,1,0,1,1/))
ns4=count(hbits(165:172).eq.(/0,1,0,0,1,1,1,0/))
ns5=count(hbits(243:250).eq.(/0,0,0,1,1,0,1,1/))
ns6=count(hbits(251:258).eq.(/0,1,0,0,1,1,1,0/))
nsync_qual=ns1+ns2+ns3+ns4+ns5+ns6
if(nsync_qual.lt. 26) cycle !### Value ?? ###
scalefac=2.83
llra( 1: 14)=bitmetrics( 1: 14, 1)
llra( 15: 28)=bitmetrics(315:328, 1)
llra( 29: 42)=bitmetrics( 15: 28, 1)
llra( 43: 56)=bitmetrics(301:314, 1)
llra( 57: 98)=bitmetrics( 29: 70, 1)
llra( 99:168)=bitmetrics( 87:156, 1)
llra(169:238)=bitmetrics(173:242, 1)
llra(239:280)=bitmetrics(259:300, 1)
llra=scalefac*llra
llrb( 1: 14)=bitmetrics( 1: 14, 2)
llrb( 15: 28)=bitmetrics(315:328, 2)
llrb( 29: 42)=bitmetrics( 15: 28, 2)
llrb( 43: 56)=bitmetrics(301:314, 2)
llrb( 57: 98)=bitmetrics( 29: 70, 2)
llrb( 99:168)=bitmetrics( 87:156, 2)
llrb(169:238)=bitmetrics(173:242, 2)
llrb(239:280)=bitmetrics(259:300, 2)
llrb=scalefac*llrb
llrc( 1: 14)=bitmetrics( 1: 14, 3)
llrc( 15: 28)=bitmetrics(315:328, 3)
llrc( 29: 42)=bitmetrics( 15: 28, 3)
llrc( 43: 56)=bitmetrics(301:314, 3)
llrc( 57: 98)=bitmetrics( 29: 70, 3)
llrc( 99:168)=bitmetrics( 87:156, 3)
llrc(169:238)=bitmetrics(173:242, 3)
llrc(239:280)=bitmetrics(259:300, 3)
llrc=scalefac*llrc
llrd( 1: 14)=bitmetrics( 1: 14, 4)
llrd( 15: 28)=bitmetrics(315:328, 4)
llrd( 29: 42)=bitmetrics( 15: 28, 4)
llrd( 43: 56)=bitmetrics(301:314, 4)
llrd( 57: 98)=bitmetrics( 29: 70, 4)
llrd( 99:168)=bitmetrics( 87:156, 4)
llrd(169:238)=bitmetrics(173:242, 4)
llrd(239:280)=bitmetrics(259:300, 4)
llrd=scalefac*llrd
apmask=0
do itry=1,ntmax
if(itry.eq.1) llr=llra
if(itry.eq.2) llr=llrb
if(itry.eq.3) llr=llrc
if(itry.eq.4) llr=llrd
dmin=0.0
nharderrors=-1
unpk77_success=.false.
if(iwspr.eq.0) then
maxosd=2
call timer('d280_101',0)
call decode280_101(llr,Keff,maxosd,norder,apmask,message101, &
cw,ntype,nharderrors,dmin)
call timer('d280_101',1)
else
maxosd=2
call timer('d280_74 ',0)
call decode280_74(llr,Keff,maxosd,norder,apmask,message74,cw, &
ntype,nharderrors,dmin)
call timer('d280_74 ',1)
endif
if(nharderrors .ge.0) then
if(iwspr.eq.0) then
write(c77,'(77i1)') message101(1:77)
call unpack77(c77,0,msg,unpk77_success)
else
write(c77,'(50i1)') message74(1:50)
c77(51:77)='000000000000000000000110000'
call unpack77(c77,0,msg,unpk77_success)
endif
if(unpk77_success) then
idupe=0
do i=1,ndecodes
if(decodes(i).eq.msg) idupe=1
enddo
if(idupe.eq.1) exit
ndecodes=ndecodes+1
decodes(ndecodes)=msg
if(iwspr.eq.0) then
call get_fst280_tones_from_bits(message101,itone,iwspr)
xsig=0
do i=1,NN
xsig=xsig+s4(itone(i),i)**2
enddo
arg=400.0*(xsig/base)-1.0
if(arg.gt.0.0) then
xsnr=10*log10(arg)-21.0-11.7*log10(nsps/800.0)
else
xsnr=-99.9
endif
endif
nsnr=nint(xsnr)
iaptype=0
qual=0.
fsig=fc_synced - 1.5*hmod*baud
!write(21,'(8i4,f7.1,f7.2,3f7.1,1x,a37)') &
! nutc,icand,itry,iaptype,ijitter,ntype,nsync_qual,nharderrors,dmin,sync,xsnr,xdt,fsig,msg
call this%callback(nutc,smax1,nsnr,xdt,fsig,msg, &
iaptype,qual,ntrperiod)
goto 2002
else
cycle
endif
endif
enddo ! metrics
enddo ! istart jitter
2002 continue
enddo !candidate list!ws
return
end subroutine decode
subroutine sync_fst280(cd0,i0,f0,hmod,ncoh,np,nss,fs,sync)
! Compute sync power for a complex, downsampled FST280 signal.
include 'fst280/fst280_params.f90'
complex cd0(0:np-1)
complex, allocatable, save :: csync(:)
complex, allocatable, save :: csynct(:)
complex ctwk(8*nss)
complex z1,z2,z3
logical first
integer hmod,isyncword(0:7)
real f0save
data isyncword/0,1,3,2,1,0,2,3/
data first/.true./,f0save/0.0/,nss0/-1/
save first,twopi,dt,fac,f0save,nss0
p(z1)=(real(z1*fac)**2 + aimag(z1*fac)**2)**0.5 !Compute power
if(nss.ne.nss0 .and. allocated(csync)) deallocate(csync,csynct)
if(first .or. nss.ne.nss0) then
allocate( csync(8*nss) )
allocate( csynct(8*nss) )
twopi=8.0*atan(1.0)
dt=1/fs
k=1
phi=0.0
do i=0,7
dphi=twopi*hmod*(isyncword(i)-1.5)/real(nss)
do j=1,nss
csync(k)=cmplx(cos(phi),sin(phi))
phi=mod(phi+dphi,twopi)
k=k+1
enddo
enddo
first=.false.
nss0=nss
fac=1.0/(8.0*nss)
endif
if(f0.ne.f0save) then
dphi=twopi*f0*dt
phi=0.0
do i=1,8*nss
ctwk(i)=cmplx(cos(phi),sin(phi))
phi=mod(phi+dphi,twopi)
enddo
csynct=ctwk*csync
f0save=f0
endif
i1=i0+35*nss !Costas arrays
i2=i0+78*nss
i3=i0+121*nss
s1=0.0
s2=0.0
s3=0.0
nsec=8/ncoh
do i=1,nsec
is=(i-1)*ncoh*nss
z1=0
if(i1+is.ge.1) then
z1=sum(cd0(i1+is:i1+is+ncoh*nss-1)*conjg(csynct(is+1:is+ncoh*nss)))
endif
z2=sum(cd0(i2+is:i2+is+ncoh*nss-1)*conjg(csynct(is+1:is+ncoh*nss)))
z3=0
if(i3+is+ncoh*nss-1.le.np) then
z3=sum(cd0(i3+is:i3+is+ncoh*nss-1)*conjg(csynct(is+1:is+ncoh*nss)))
endif
s1=s1+abs(z1)/(8*nss)
s2=s2+abs(z2)/(8*nss)
s3=s3+abs(z3)/(8*nss)
enddo
sync = s1+s2+s3
return
end subroutine sync_fst280
subroutine fst280_downsample(c_bigfft,nfft1,ndown,f0,c1)
! Output: Complex data in c(), sampled at 12000/ndown Hz
complex c_bigfft(0:nfft1/2)
complex c1(0:nfft1/ndown-1)
df=12000.0/nfft1
i0=nint(f0/df)
c1(0)=c_bigfft(i0)
nfft2=nfft1/ndown
do i=1,nfft2/2
if(i0+i.le.nfft1/2) c1(i)=c_bigfft(i0+i)
if(i0-i.ge.0) c1(nfft2-i)=c_bigfft(i0-i)
enddo
c1=c1/nfft2
call four2a(c1,nfft2,1,1,1) !c2c FFT back to time domain
return
end subroutine fst280_downsample
subroutine get_candidates_fst280(c_bigfft,nfft1,nsps,hmod,fs,fa,fb, &
ncand,candidates,base)
complex c_bigfft(0:nfft1/2)
integer hmod
integer indx(100)
real candidates(100,4)
real candidates0(100,4)
real snr_cand(100)
real s(18000)
real s2(18000)
data nfft1z/-1/
save nfft1z
nh1=nfft1/2
df1=fs/nfft1
baud=fs/nsps
df2=baud/2.0
nd=df2/df1
ndh=nd/2
ia=nint(max(100.0,fa)/df2)
ib=nint(min(4800.0,fb)/df2)
signal_bw=4*(12000.0/nsps)*hmod
analysis_bw=min(4800.0,fb)-max(100.0,fa)
noise_bw=10.0*signal_bw
if(analysis_bw.gt.noise_bw) then
ina=ia
inb=ib
else
fcenter=(fa+fb)/2.0
fl = max(100.0,fcenter-noise_bw/2.)/df2
fh = min(4800.0,fcenter+noise_bw/2.)/df2
ina=nint(fl)
inb=nint(fh)
endif
s=0.
do i=ina,inb ! noise analysis window includes signal analysis window
j0=nint(i*df2/df1)
do j=j0-ndh,j0+ndh
s(i)=s(i) + real(c_bigfft(j))**2 + aimag(c_bigfft(j))**2
enddo
enddo
ina=max(ina,1+3*hmod)
inb=min(inb,18000-3*hmod)
s2=0.
do i=ina,inb
s2(i)=s(i-hmod*3) + s(i-hmod) +s(i+hmod) +s(i+hmod*3)
enddo
call pctile(s2(ina+hmod*3:inb-hmod*3),inb-ina+1-hmod*6,30,base)
s2=s2/base
thresh=1.25
ncand=0
candidates=0
if(ia.lt.3) ia=3
if(ib.gt.18000-2) ib=18000-2
do i=ia,ib
if((s2(i).gt.s2(i-2)).and. &
(s2(i).gt.s2(i+2)).and. &
(s2(i).gt.thresh).and.ncand.lt.100) then
ncand=ncand+1
candidates(ncand,1)=df2*i
candidates(ncand,2)=s2(i)
endif
enddo
snr_cand=0.
snr_cand(1:ncand)=candidates(1:ncand,2)
call indexx(snr_cand,ncand,indx)
nmax=min(ncand,20)
do i=1,nmax
j=indx(ncand+1-i)
candidates0(i,1:4)=candidates(j,1:4)
enddo
ncand=nmax
candidates(1:ncand,1:4)=candidates0(1:ncand,1:4)
candidates(ncand+1:,1:4)=0.
return
end subroutine get_candidates_fst280
end module fst280_decode

View File

@ -0,0 +1,111 @@
subroutine bpdecode240_101(llr,apmask,maxiterations,message101,cw,nharderror,iter,ncheck)
!
! A log-domain belief propagation decoder for the (240,101) code.
!
integer, parameter:: N=240, K=101, M=N-K
integer*1 cw(N),apmask(N)
integer*1 decoded(K)
integer*1 message101(101)
integer nrw(M),ncw
integer Nm(6,M)
integer Mn(3,N) ! 3 checks per bit
integer synd(M)
real tov(3,N)
real toc(6,M)
real tanhtoc(6,M)
real zn(N)
real llr(N)
real Tmn
include "ldpc_240_101_parity.f90"
decoded=0
toc=0
tov=0
tanhtoc=0
! initialize messages to checks
do j=1,M
do i=1,nrw(j)
toc(i,j)=llr((Nm(i,j)))
enddo
enddo
ncnt=0
nclast=0
do iter=0,maxiterations
! Update bit log likelihood ratios (tov=0 in iteration 0).
do i=1,N
if( apmask(i) .ne. 1 ) then
zn(i)=llr(i)+sum(tov(1:ncw,i))
else
zn(i)=llr(i)
endif
enddo
! Check to see if we have a codeword (check before we do any iteration).
cw=0
where( zn .gt. 0. ) cw=1
ncheck=0
do i=1,M
synd(i)=sum(cw(Nm(1:nrw(i),i)))
if( mod(synd(i),2) .ne. 0 ) ncheck=ncheck+1
! if( mod(synd(i),2) .ne. 0 ) write(*,*) 'check ',i,' unsatisfied'
enddo
if( ncheck .eq. 0 ) then ! we have a codeword - if crc is good, return it
decoded=cw(1:101)
call get_crc24(decoded,101,nbadcrc)
nharderror=count( (2*cw-1)*llr .lt. 0.0 )
if(nbadcrc.eq.0) then
message101=decoded(1:101)
return
endif
endif
if( iter.gt.0 ) then ! this code block implements an early stopping criterion
! if( iter.gt.10000 ) then ! this code block implements an early stopping criterion
nd=ncheck-nclast
if( nd .lt. 0 ) then ! # of unsatisfied parity checks decreased
ncnt=0 ! reset counter
else
ncnt=ncnt+1
endif
! write(*,*) iter,ncheck,nd,ncnt
if( ncnt .ge. 5 .and. iter .ge. 10 .and. ncheck .gt. 15) then
nharderror=-1
return
endif
endif
nclast=ncheck
! Send messages from bits to check nodes
do j=1,M
do i=1,nrw(j)
ibj=Nm(i,j)
toc(i,j)=zn(ibj)
do kk=1,ncw ! subtract off what the bit had received from the check
if( Mn(kk,ibj) .eq. j ) then
toc(i,j)=toc(i,j)-tov(kk,ibj)
endif
enddo
enddo
enddo
! send messages from check nodes to variable nodes
do i=1,M
tanhtoc(1:6,i)=tanh(-toc(1:6,i)/2)
enddo
do j=1,N
do i=1,ncw
ichk=Mn(i,j) ! Mn(:,j) are the checks that include bit j
Tmn=product(tanhtoc(1:nrw(ichk),ichk),mask=Nm(1:nrw(ichk),ichk).ne.j)
call platanh(-Tmn,y)
! y=atanh(-Tmn)
tov(i,j)=2*y
enddo
enddo
enddo
nharderror=-1
return
end subroutine bpdecode240_101

155
lib/fst4/decode240_101.f90 Normal file
View File

@ -0,0 +1,155 @@
subroutine decode240_101(llr,Keff,maxosd,norder,apmask,message101,cw,ntype,nharderror,dmin)
!
! A hybrid bp/osd decoder for the (240,101) code.
!
! maxosd<0: do bp only
! maxosd=0: do bp and then call osd once with channel llrs
! maxosd>1: do bp and then call osd maxosd times with saved bp outputs
! norder : osd decoding depth
!
integer, parameter:: N=240, K=101, M=N-K
integer*1 cw(N),apmask(N)
integer*1 nxor(N),hdec(N)
integer*1 message101(101),m101(101)
integer nrw(M),ncw
integer Nm(6,M)
integer Mn(3,N) ! 3 checks per bit
integer synd(M)
real tov(3,N)
real toc(6,M)
real tanhtoc(6,M)
real zn(N),zsum(N),zsave(N,3)
real llr(N)
real Tmn
include "ldpc_240_101_parity.f90"
maxiterations=30
nosd=0
if(maxosd.gt.3) maxosd=3
if(maxosd.eq.0) then ! osd with channel llrs
nosd=1
zsave(:,1)=llr
elseif(maxosd.gt.0) then !
nosd=maxosd
elseif(maxosd.lt.0) then ! just bp
nosd=0
endif
toc=0
tov=0
tanhtoc=0
! initialize messages to checks
do j=1,M
do i=1,nrw(j)
toc(i,j)=llr((Nm(i,j)))
enddo
enddo
ncnt=0
nclast=0
zsum=0.0
do iter=0,maxiterations
! Update bit log likelihood ratios (tov=0 in iteration 0).
do i=1,N
if( apmask(i) .ne. 1 ) then
zn(i)=llr(i)+sum(tov(1:ncw,i))
else
zn(i)=llr(i)
endif
enddo
zsum=zsum+zn
if(iter.gt.0 .and. iter.le.maxosd) then
zsave(:,iter)=zsum
endif
! Check to see if we have a codeword (check before we do any iteration).
cw=0
where( zn .gt. 0. ) cw=1
ncheck=0
do i=1,M
synd(i)=sum(cw(Nm(1:nrw(i),i)))
if( mod(synd(i),2) .ne. 0 ) ncheck=ncheck+1
enddo
if( ncheck .eq. 0 ) then ! we have a codeword - if crc is good, return it
m101=0
m101(1:101)=cw(1:101)
call get_crc24(m101,101,nbadcrc)
if(nbadcrc.eq.0) then
message101=cw(1:101)
hdec=0
where(llr .ge. 0) hdec=1
nxor=ieor(hdec,cw)
nharderror=sum(nxor)
dmin=sum(nxor*abs(llr))
ntype=1
return
endif
endif
if( iter.gt.0 ) then ! this code block implements an early stopping criterion
! if( iter.gt.10000 ) then ! this code block implements an early stopping criterion
nd=ncheck-nclast
if( nd .lt. 0 ) then ! # of unsatisfied parity checks decreased
ncnt=0 ! reset counter
else
ncnt=ncnt+1
endif
! write(*,*) iter,ncheck,nd,ncnt
if( ncnt .ge. 5 .and. iter .ge. 10 .and. ncheck .gt. 15) then
nharderror=-1
exit
endif
endif
nclast=ncheck
! Send messages from bits to check nodes
do j=1,M
do i=1,nrw(j)
ibj=Nm(i,j)
toc(i,j)=zn(ibj)
do kk=1,ncw ! subtract off what the bit had received from the check
if( Mn(kk,ibj) .eq. j ) then
toc(i,j)=toc(i,j)-tov(kk,ibj)
endif
enddo
enddo
enddo
! send messages from check nodes to variable nodes
do i=1,M
tanhtoc(1:6,i)=tanh(-toc(1:6,i)/2)
enddo
do j=1,N
do i=1,ncw
ichk=Mn(i,j) ! Mn(:,j) are the checks that include bit j
Tmn=product(tanhtoc(1:nrw(ichk),ichk),mask=Nm(1:nrw(ichk),ichk).ne.j)
call platanh(-Tmn,y)
! y=atanh(-Tmn)
tov(i,j)=2*y
enddo
enddo
enddo ! bp iterations
do i=1,nosd
zn=zsave(:,i)
call osd240_101(zn,Keff,apmask,norder,message101,cw,nharderror,dminosd)
if(nharderror.gt.0) then
hdec=0
where(llr .ge. 0) hdec=1
nxor=ieor(hdec,cw)
nharderror=sum(nxor) ! re-calculate nharderror based on input llrs
dmin=sum(nxor*abs(llr))
ntype=1+i
return
endif
enddo
ntype=0
nharderror=-1
dminosd=0.0
return
end subroutine decode240_101

155
lib/fst4/decode240_74.f90 Normal file
View File

@ -0,0 +1,155 @@
subroutine decode240_74(llr,Keff,maxosd,norder,apmask,message74,cw,ntype,nharderror,dmin)
!
! A hybrid bp/osd decoder for the (240,74) code.
!
! maxosd<0: do bp only
! maxosd=0: do bp and then call osd once with channel llrs
! maxosd>1: do bp and then call osd maxosd times with saved bp outputs
! norder : osd decoding depth
!
integer, parameter:: N=240, K=74, M=N-K
integer*1 cw(N),apmask(N)
integer*1 nxor(N),hdec(N)
integer*1 message74(74),m74(74)
integer nrw(M),ncw
integer Nm(5,M)
integer Mn(3,N) ! 3 checks per bit
integer synd(M)
real tov(3,N)
real toc(5,M)
real tanhtoc(5,M)
real zn(N),zsum(N),zsave(N,3)
real llr(N)
real Tmn
include "ldpc_240_74_parity.f90"
maxiterations=30
nosd=0
if(maxosd.gt.3) maxosd=3
if(maxosd.eq.0) then ! osd with channel llrs
nosd=1
zsave(:,1)=llr
elseif(maxosd.gt.0) then !
nosd=maxosd
elseif(maxosd.lt.0) then ! just bp
nosd=0
endif
toc=0
tov=0
tanhtoc=0
! initialize messages to checks
do j=1,M
do i=1,nrw(j)
toc(i,j)=llr((Nm(i,j)))
enddo
enddo
ncnt=0
nclast=0
zsum=0.0
do iter=0,maxiterations
! Update bit log likelihood ratios (tov=0 in iteration 0).
do i=1,N
if( apmask(i) .ne. 1 ) then
zn(i)=llr(i)+sum(tov(1:ncw,i))
else
zn(i)=llr(i)
endif
enddo
zsum=zsum+zn
if(iter.gt.0 .and. iter.le.maxosd) then
zsave(:,iter)=zsum
endif
! Check to see if we have a codeword (check before we do any iteration).
cw=0
where( zn .gt. 0. ) cw=1
ncheck=0
do i=1,M
synd(i)=sum(cw(Nm(1:nrw(i),i)))
if( mod(synd(i),2) .ne. 0 ) ncheck=ncheck+1
enddo
if( ncheck .eq. 0 ) then ! we have a codeword - if crc is good, return it
m74=0
m74(1:74)=cw(1:74)
call get_crc24(m74,74,nbadcrc)
if(nbadcrc.eq.0) then
message74=cw(1:74)
hdec=0
where(llr .ge. 0) hdec=1
nxor=ieor(hdec,cw)
nharderror=sum(nxor)
dmin=sum(nxor*abs(llr))
ntype=1
return
endif
endif
if( iter.gt.0 ) then ! this code block implements an early stopping criterion
! if( iter.gt.10000 ) then ! this code block implements an early stopping criterion
nd=ncheck-nclast
if( nd .lt. 0 ) then ! # of unsatisfied parity checks decreased
ncnt=0 ! reset counter
else
ncnt=ncnt+1
endif
! write(*,*) iter,ncheck,nd,ncnt
if( ncnt .ge. 5 .and. iter .ge. 10 .and. ncheck .gt. 15) then
nharderror=-1
exit
endif
endif
nclast=ncheck
! Send messages from bits to check nodes
do j=1,M
do i=1,nrw(j)
ibj=Nm(i,j)
toc(i,j)=zn(ibj)
do kk=1,ncw ! subtract off what the bit had received from the check
if( Mn(kk,ibj) .eq. j ) then
toc(i,j)=toc(i,j)-tov(kk,ibj)
endif
enddo
enddo
enddo
! send messages from check nodes to variable nodes
do i=1,M
tanhtoc(1:5,i)=tanh(-toc(1:5,i)/2)
enddo
do j=1,N
do i=1,ncw
ichk=Mn(i,j) ! Mn(:,j) are the checks that include bit j
Tmn=product(tanhtoc(1:nrw(ichk),ichk),mask=Nm(1:nrw(ichk),ichk).ne.j)
call platanh(-Tmn,y)
! y=atanh(-Tmn)
tov(i,j)=2*y
enddo
enddo
enddo ! bp iterations
do i=1,nosd
zn=zsave(:,i)
call osd240_74(zn,Keff,apmask,norder,message74,cw,nharderror,dminosd)
if(nharderror.gt.0) then
hdec=0
where(llr .ge. 0) hdec=1
nxor=ieor(hdec,cw)
nharderror=sum(nxor) ! nharderror based on input llrs
dmin=sum(nxor*abs(llr))
ntype=1+i
return
endif
enddo
ntype=0
nharderror=-1
dminosd=0.0
return
end subroutine decode240_74

View File

@ -0,0 +1,46 @@
subroutine encode240_101(message,codeword)
use, intrinsic :: iso_c_binding
use iso_c_binding, only: c_loc,c_size_t
use crc
integer, parameter:: N=240, K=101, M=N-K
character*24 c24
integer*1 codeword(N)
integer*1 gen(M,K)
integer*1 message(K)
integer*1 pchecks(M)
integer*4 ncrc24
include "ldpc_240_101_generator.f90"
logical first
data first/.true./
save first,gen
if( first ) then ! fill the generator matrix
gen=0
do i=1,M
do j=1,26
read(g(i)(j:j),"(Z1)") istr
ibmax=4
if(j.eq.26) ibmax=1
do jj=1, ibmax
icol=(j-1)*4+jj
if( btest(istr,4-jj) ) gen(i,icol)=1
enddo
enddo
enddo
first=.false.
endif
do i=1,M
nsum=0
do j=1,K
nsum=nsum+message(j)*gen(i,j)
enddo
pchecks(i)=mod(nsum,2)
enddo
codeword(1:K)=message
codeword(K+1:N)=pchecks
return
end subroutine encode240_101

46
lib/fst4/encode240_74.f90 Normal file
View File

@ -0,0 +1,46 @@
subroutine encode240_74(message,codeword)
use, intrinsic :: iso_c_binding
use iso_c_binding, only: c_loc,c_size_t
use crc
integer, parameter:: N=240, K=74, M=N-K
character*24 c24
integer*1 codeword(N)
integer*1 gen(M,K)
integer*1 message(K)
integer*1 pchecks(M)
integer*4 ncrc24
include "ldpc_240_74_generator.f90"
logical first
data first/.true./
save first,gen
if( first ) then ! fill the generator matrix
gen=0
do i=1,M
do j=1,19
read(g(i)(j:j),"(Z1)") istr
ibmax=4
if(j.eq.19) ibmax=2
do jj=1, ibmax
icol=(j-1)*4+jj
if( btest(istr,4-jj) ) gen(i,icol)=1
enddo
enddo
enddo
first=.false.
endif
do i=1,M
nsum=0
do j=1,K
nsum=nsum+message(j)*gen(i,j)
enddo
pchecks(i)=mod(nsum,2)
enddo
codeword(1:K)=message
codeword(K+1:N)=pchecks
return
end subroutine encode240_74

View File

@ -0,0 +1,48 @@
subroutine fst4_baseline(s,np,ia,ib,npct,sbase)
! Fit baseline to spectrum (for FST4)
! Input: s(npts) Linear scale in power
! Output: sbase(npts) Baseline
implicit real*8 (a-h,o-z)
real*4 s(np),sw(np)
real*4 sbase(np)
real*4 base
real*8 x(1000),y(1000),a(5)
data nseg/8/
do i=ia,ib
sw(i)=10.0*log10(s(i)) !Convert to dB scale
enddo
nterms=3
nlen=(ib-ia+1)/nseg !Length of test segment
i0=(ib-ia+1)/2 !Midpoint
k=0
do n=1,nseg !Loop over all segments
ja=ia + (n-1)*nlen
jb=ja+nlen-1
call pctile(sw(ja),nlen,npct,base) !Find lowest npct of points
do i=ja,jb
if(sw(i).le.base) then
if (k.lt.1000) k=k+1 !Save all "lower envelope" points
x(k)=i-i0
y(k)=sw(i)
endif
enddo
enddo
kz=k
a=0.
call polyfit(x,y,y,kz,nterms,0,a,chisqr) !Fit a low-order polynomial
sbase=0.0
do i=ia,ib
t=i-i0
sbase(i)=a(1)+t*(a(2)+t*(a(3))) + 0.2
! write(51,3051) i,sw(i),sbase(i)
!3051 format(i8,2f12.3)
enddo
sbase=10**(sbase/10.0)
return
end subroutine fst4_baseline

7
lib/fst4/fst4_params.f90 Normal file
View File

@ -0,0 +1,7 @@
! FST4
! LDPC(240,101)/CRC24 code, five 8x4 sync
parameter (KK=77) !Information bits (77 + CRC24)
parameter (ND=120) !Data symbols
parameter (NS=40) !Sync symbols
parameter (NN=NS+ND) !Sync and data symbols (160)

155
lib/fst4/fst4sim.f90 Normal file
View File

@ -0,0 +1,155 @@
program fst4sim
! Generate simulated signals for experimental slow FT4 mode
use wavhdr
use packjt77
include 'fst4_params.f90' !Set various constants
type(hdr) h !Header for .wav file
logical*1 wspr_hint
character arg*12,fname*17
character msg37*37,msgsent37*37,c77*77
complex, allocatable :: c0(:)
complex, allocatable :: c(:)
real, allocatable :: wave(:)
integer hmod
integer itone(NN)
integer*1 msgbits(101)
integer*2, allocatable :: iwave(:) !Generated full-length waveform
! Get command-line argument(s)
nargs=iargc()
if(nargs.ne.10) then
print*,'Need 10 arguments, got ',nargs
print*,'Usage: fst4sim "message" TRsec f0 DT h fdop del nfiles snr W'
print*,'Examples: fst4sim "K1JT K9AN EN50" 60 1500 0.0 1 0.1 1.0 10 -15 F'
print*,'W (T or F) argument is hint to encoder to use WSPR message when there is abiguity'
go to 999
endif
call getarg(1,msg37) !Message to be transmitted
call getarg(2,arg)
read(arg,*) nsec !TR sequence length, seconds
call getarg(3,arg)
read(arg,*) f00 !Frequency (only used for single-signal)
call getarg(4,arg)
read(arg,*) xdt !Time offset from nominal (s)
call getarg(5,arg)
read(arg,*) hmod !Modulation index, h
call getarg(6,arg)
read(arg,*) fspread !Watterson frequency spread (Hz)
call getarg(7,arg)
read(arg,*) delay !Watterson delay (ms)
call getarg(8,arg)
read(arg,*) nfiles !Number of files
call getarg(9,arg)
read(arg,*) snrdb !SNR_2500
call getarg(10,arg)
read(arg,*) wspr_hint !0:break ties as 77-bit 1:break ties as 50-bit
nfiles=abs(nfiles)
twopi=8.0*atan(1.0)
fs=12000.0 !Sample rate (Hz)
dt=1.0/fs !Sample interval (s)
nsps=0
if(nsec.eq.15) nsps=720
if(nsec.eq.30) nsps=1680
if(nsec.eq.60) nsps=3888
if(nsec.eq.120) nsps=8200
if(nsec.eq.300) nsps=21504
if(nsec.eq.900) nsps=66560
if(nsec.eq.1800) nsps=134400
if(nsps.eq.0) then
print*,'Invalid TR sequence length.'
go to 999
endif
baud=12000.0/nsps !Keying rate (baud)
nmax=nsec*12000
nz=nsps*NN
txt=nz*dt !Transmission length (s)
tt=nsps*dt !Duration of symbols (s)
nwave=max(nmax,(NN+2)*nsps)
allocate( c0(0:nwave-1) )
allocate( c(0:nwave-1) )
allocate( wave(nwave) )
allocate( iwave(nmax) )
bandwidth_ratio=2500.0/(fs/2.0)
sig=sqrt(2*bandwidth_ratio) * 10.0**(0.05*snrdb)
if(snrdb.gt.90.0) sig=1.0
if(wspr_hint) then
i3=0
n3=6
else
i3=-1
n3=-1
endif
call pack77(msg37,i3,n3,c77)
if(i3.eq.0.and.n3.eq.6) iwspr=1
call genfst4(msg37,0,msgsent37,msgbits,itone,iwspr)
write(*,*)
write(*,'(a9,a37,a3,L2,a7,i2)') 'Message: ',msgsent37,'W:',wspr_hint,' iwspr:',iwspr
write(*,1000) f00,xdt,hmod,txt,snrdb
1000 format('f0:',f9.3,' DT:',f6.2,' hmod:',i6,' TxT:',f6.1,' SNR:',f6.1)
write(*,*)
if(i3.eq.1) then
write(*,*) ' mycall hiscall hisgrid'
write(*,'(28i1,1x,i1,1x,28i1,1x,i1,1x,i1,1x,15i1,1x,3i1)') msgbits(1:77)
else
write(*,'(a14)') 'Message bits: '
write(*,'(77i1,1x,24i1)') msgbits
endif
write(*,*)
write(*,'(a17)') 'Channel symbols: '
write(*,'(10i1)') itone
write(*,*)
! call sgran()
fsample=12000.0
icmplx=1
f0=f00+1.5*hmod*baud
call gen_fst4wave(itone,NN,nsps,nwave,fsample,hmod,f0,icmplx,c0,wave)
k=nint((xdt+1.0)/dt)
if(nsec.eq.15) k=nint((xdt+0.5)/dt)
c0=cshift(c0,-k)
if(k.gt.0) c0(0:k-1)=0.0
if(k.lt.0) c0(nmax+k:nmax-1)=0.0
do ifile=1,nfiles
c=c0
if(fspread.ne.0.0 .or. delay.ne.0.0) call watterson(c,nwave,NZ,fs,delay,fspread)
c=sig*c
wave=real(c)
if(snrdb.lt.90) then
do i=1,nmax !Add gaussian noise at specified SNR
xnoise=gran()
wave(i)=wave(i) + xnoise
enddo
endif
gain=100.0
if(snrdb.lt.90.0) then
wave=gain*wave
else
datpk=maxval(abs(wave))
fac=32766.9/datpk
wave=fac*wave
endif
if(any(abs(wave).gt.32767.0)) print*,"Warning - data will be clipped."
iwave=nint(wave(:size(iwave)))
h=default_header(12000,nmax)
if(nmax/12000.le.30) then
write(fname,1102) ifile
1102 format('000000_',i6.6,'.wav')
else
write(fname,1104) ifile
1104 format('000000_',i4.4,'.wav')
endif
open(10,file=trim(fname),status='unknown',access='stream')
write(10) h,iwave !Save to *.wav file
close(10)
write(*,1110) ifile,xdt,f00,snrdb,fname
1110 format(i4,f7.2,f8.2,f7.1,2x,a17)
enddo
999 end program fst4sim

91
lib/fst4/gen_fst4wave.f90 Normal file
View File

@ -0,0 +1,91 @@
subroutine gen_fst4wave(itone,nsym,nsps,nwave,fsample,hmod,f0, &
icmplx,cwave,wave)
parameter(NTAB=65536)
real wave(nwave)
complex cwave(nwave),ctab(0:NTAB-1)
real, allocatable, save :: pulse(:)
real, allocatable :: dphi(:)
integer hmod
integer itone(nsym)
logical first
data first/.true./
data nsps0/-99/
save first,twopi,dt,tsym,nsps0,ctab
if(first) then
twopi=8.0*atan(1.0)
do i=0,NTAB-1
phi=i*twopi/NTAB
ctab(i)=cmplx(cos(phi),sin(phi))
enddo
endif
if(first.or.nsps.ne.nsps0) then
if(allocated(pulse)) deallocate(pulse)
allocate(pulse(1:3*nsps))
dt=1.0/fsample
tsym=nsps/fsample
! Compute the smoothed frequency-deviation pulse
do i=1,3*nsps
tt=(i-1.5*nsps)/real(nsps)
pulse(i)=gfsk_pulse(2.0,tt)
enddo
first=.false.
nsps0=nsps
endif
! Compute the smoothed frequency waveform.
! Length = (nsym+2)*nsps samples, zero-padded
allocate( dphi(0:(nsym+2)*nsps-1) )
dphi_peak=twopi*hmod/real(nsps)
dphi=0.0
do j=1,nsym
ib=(j-1)*nsps
ie=ib+3*nsps-1
dphi(ib:ie) = dphi(ib:ie) + dphi_peak*pulse(1:3*nsps)*itone(j)
enddo
! Calculate and insert the audio waveform
phi=0.0
dphi = dphi + twopi*(f0-1.5*hmod/tsym)*dt !Shift frequency up by f0
if(icmplx.eq.0) wave=0.
if(icmplx.eq.1) cwave=0.
k=0
do j=0,(nsym+2)*nsps-1
k=k+1
i=phi*float(NTAB)/twopi
i=iand(i,NTAB-1)
if(icmplx.eq.0) then
wave(k)=real(ctab(i))
else
cwave(k)=ctab(i)
endif
phi=phi+dphi(j)
if(phi.gt.twopi) phi=phi-twopi
enddo
! Compute the ramp-up and ramp-down symbols
kshift=nsps
if(icmplx.eq.0) then
wave(1:nsps)=0.0
wave(nsps+1:nsps+nsps/4)=wave(nsps+1:nsps+nsps/4) * &
(1.0-cos(twopi*(/(i,i=0,nsps/4-1)/)/real(nsps/2)))/2.0
k1=nsym*nsps+3*nsps/4+1
wave((nsym+1)*nsps+1:)=0.0
wave(k1:k1+nsps/4)=wave(k1:k1+nsps/4) * &
(1.0+cos(twopi*(/(i,i=0,nsps/4)/)/real(nsps/2)))/2.0
wave=cshift(wave,kshift)
else
cwave(1:nsps)=0.0
cwave(nsps+1:nsps+nsps/4)=cwave(nsps+1:nsps+nsps/4) * &
(1.0-cos(twopi*(/(i,i=0,nsps/4-1)/)/real(nsps/2)))/2.0
k1=nsym*nsps+3*nsps/4+1
cwave((nsym+1)*nsps+1:)=0.0
cwave(k1:k1+nsps/4)=cwave(k1:k1+nsps/4) * &
(1.0+cos(twopi*(/(i,i=0,nsps/4)/)/real(nsps/2)))/2.0
cwave=cshift(cwave,kshift)
endif
return
end subroutine gen_fst4wave

111
lib/fst4/genfst4.f90 Normal file
View File

@ -0,0 +1,111 @@
subroutine genfst4(msg0,ichk,msgsent,msgbits,i4tone,iwspr)
! Input:
! - msg0 requested message to be transmitted
! - ichk if ichk=1, return only msgsent
! - msgsent message as it will be decoded
! - i4tone array of audio tone values, {0,1,2,3}
! - iwspr in: 0: FST4 1: FST4W
! out 0: (240,101)/crc24, 1: (240,74)/crc24
!
! Frame structure:
! s8 d30 s8 d30 s8 d30 s8 d30 s8
use packjt77
include 'fst4_params.f90'
character*37 msg0
character*37 message !Message to be generated
character*37 msgsent !Message as it will be received
character*77 c77
character*24 c24
integer*4 i4tone(NN),itmp(ND)
integer*1 codeword(2*ND)
integer*1 msgbits(101),rvec(77)
integer isyncword1(8),isyncword2(8)
integer ncrc24
logical unpk77_success
data isyncword1/0,1,3,2,1,0,2,3/
data isyncword2/2,3,1,0,3,2,0,1/
data rvec/0,1,0,0,1,0,1,0,0,1,0,1,1,1,1,0,1,0,0,0,1,0,0,1,1,0,1,1,0, &
1,0,0,1,0,1,1,0,0,0,0,1,0,0,0,1,0,1,0,0,1,1,1,1,0,0,1,0,1, &
0,1,0,1,0,1,1,0,1,1,1,1,1,0,0,0,1,0,1/
message=msg0
do i=1, 37
if(ichar(message(i:i)).eq.0) then
message(i:37)=' '
exit
endif
enddo
do i=1,37 !Strip leading blanks
if(message(1:1).ne.' ') exit
message=message(i+1:)
enddo
i3=-1
n3=-1
if(iwspr.eq.1) then
i3=0
n3=6
endif
call pack77(message,i3,n3,c77)
call unpack77(c77,0,msgsent,unpk77_success) !Unpack to get msgsent
msgbits=0
iwspr=0
if(i3.eq.0.and.n3.eq.6) then
iwspr=1
read(c77,'(50i1)') msgbits(1:50)
call get_crc24(msgbits,74,ncrc24)
write(c24,'(b24.24)') ncrc24
read(c24,'(24i1)') msgbits(51:74)
else
read(c77,'(77i1)') msgbits(1:77)
msgbits(1:77)=mod(msgbits(1:77)+rvec,2)
call get_crc24(msgbits,101,ncrc24)
write(c24,'(b24.24)') ncrc24
read(c24,'(24i1)') msgbits(78:101)
endif
if(ichk.eq.1) go to 999
if(unpk77_success) go to 2
1 msgbits=0
itone=0
msgsent='*** bad message *** '
go to 999
entry get_fst4_tones_from_bits(msgbits,i4tone,iwspr)
2 continue
if(iwspr.eq.0) then
call encode240_101(msgbits,codeword)
else
call encode240_74(msgbits(1:74),codeword)
endif
! Grayscale mapping:
! bits tone
! 00 0
! 01 1
! 11 2
! 10 3
do i=1,ND
is=codeword(2*i)+2*codeword(2*i-1)
if(is.le.1) itmp(i)=is
if(is.eq.2) itmp(i)=3
if(is.eq.3) itmp(i)=2
enddo
i4tone( 1: 8)=isyncword1
i4tone( 9: 38)=itmp( 1: 30)
i4tone( 39: 46)=isyncword2
i4tone( 47: 76)=itmp( 31: 60)
i4tone( 77: 84)=isyncword1
i4tone( 85:114)=itmp( 61: 90)
i4tone(115:122)=isyncword2
i4tone(123:152)=itmp( 91:120)
i4tone(153:160)=isyncword1
999 return
end subroutine genfst4

Some files were not shown because too many files have changed in this diff Show More