mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-25 05:38:46 -05:00
Merge branch 'release-2.3.0'
This commit is contained in:
commit
450f44c9ab
11
.gitignore
vendored
11
.gitignore
vendored
@ -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
5
Audio/Audio.pri
Normal 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
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 ()
|
||||
|
@ -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
|
||||
|
@ -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"));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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 ()
|
||||
{
|
||||
|
@ -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_;
|
||||
|
178
Configuration.ui
178
Configuration.ui
@ -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><html><head/><body><p>Number of data bits used to communicate with your radio's CAT interface (usually eight).</p></body></html></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><html><head/><body><p>Number of stop bits used when communicating with your radio's CAT interface</p><p>(consult you radio's manual for details).</p></body></html></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><html><head/><body><p>Flow control protocol used between this computer and your radio's CAT interface (usually &quot;None&quot; but some require &quot;Hardware&quot;).</p></body></html></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>&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&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&tput:</string>
|
||||
<string>&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><html><head/><body><p>The program can send your station details and all decoded signals with grid squares as spots to the http://pskreporter.info web site.</p><p>This is used for reverse beacon analysis which is very useful for assessing propagation and system performance.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable &PSK Reporter Spotting</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="psk_reporter_tcpip_check_box">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Check this option if a reliable connection is needed</p><p>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.</p></body></html></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><html><head/><body><p>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.</p></body></html></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><html><head/><body><p>Adjust this spin box to set the age threshold of LotW user's last upload date that is accepted as a current LotW user.</p></body></html></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><html><head/><body><p>FT8 DXpedition mode: Hound operator calling the DX.</p></body></html></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><html><head/><body><p>North American VHF/UHF/Microwave contests and others in which a 4-character grid locator is the required exchange.</p></body></html></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><html><head/><body><p>FT8 DXpedition mode: Fox (DXpedition) operator.</p></body></html></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><html><head/><body><p>European VHF+ contests requiring a signal report, serial number, and 6-character locator.</p></body></html></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><html><head/><body><p>ARRL RTTY Roundup and similar contests. Exchange is US state, Canadian province, or &quot;DX&quot;.</p></body></html></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><html><head/><body><p>ARRL Field Day exchange: number of transmitters, Class, and ARRL/RAC section or &quot;DX&quot;.</p></body></html></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><html><head/><body><p>World-Wide Digi-mode contest</p><p><br/></p></body></html></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>
|
||||
|
@ -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
3
Decoder/decodedtext.pri
Normal file
@ -0,0 +1,3 @@
|
||||
SOURCES += Decoder/decodedtext.cpp
|
||||
|
||||
HEADERS += Decoder/decodedtext.h
|
@ -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
3
Detector/Detector.pri
Normal file
@ -0,0 +1,3 @@
|
||||
SOURCES += Detector/Detector.cpp
|
||||
|
||||
HEADERS += Detector/Detector.hpp
|
@ -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
14
INSTALL
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
3
Modulator/Modulator.pri
Normal file
@ -0,0 +1,3 @@
|
||||
SOURCES += Modulator/Modulator.cpp
|
||||
|
||||
HEADERS += Modulator/Mpdulator.hpp
|
26
NEWS
26
NEWS
@ -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
|
||||
---------------------
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
62
Network/NetworkAccessManager.cpp
Normal file
62
Network/NetworkAccessManager.cpp
Normal 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;
|
||||
}
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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
521
Network/PSKReporter.cpp
Normal 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
41
Network/PSKReporter.hpp
Normal 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
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
12
Network/tests/PSKReporter/PSKReporter.IESpec
Normal file
12
Network/tests/PSKReporter/PSKReporter.IESpec
Normal 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]
|
94
Network/tests/PSKReporter/listener.py
Normal file
94
Network/tests/PSKReporter/listener.py
Normal 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')
|
64
Network/tests/PSKReporter/python-ipfix.patch
Normal file
64
Network/tests/PSKReporter/python-ipfix.patch
Normal 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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
---------------------
|
||||
|
@ -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);
|
||||
|
@ -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 ())
|
||||
};
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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 ();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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_)
|
||||
|
@ -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];
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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]]
|
||||
|
@ -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.
|
@ -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     ?     ?
|
||||
@ -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     ?     ?
|
||||
@ -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 | + | | ?   aP
|
||||
|FT4 | ~ | | ?   aP
|
||||
|FT8 | ~ | | ?   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 | ?     ?     ?
|
||||
|
@ -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
|
||||
|
BIN
doc/user_guide/en/images/FST4W_RoundRobin.png
Normal file
BIN
doc/user_guide/en/images/FST4W_RoundRobin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
doc/user_guide/en/images/FST4_Decoding_Limits.png
Normal file
BIN
doc/user_guide/en/images/FST4_Decoding_Limits.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
doc/user_guide/en/images/FST4_center.png
Normal file
BIN
doc/user_guide/en/images/FST4_center.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
@ -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
|
||||
|
@ -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
|
||||
....
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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*
|
||||
|
40
doc/user_guide/en/intro_subsections.adoc
Normal file
40
doc/user_guide/en/intro_subsections.adoc
Normal 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>>.
|
@ -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 station’s 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 station’s 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.
|
||||
|
@ -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 ...)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
`R–nn` 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
|
||||
|
@ -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
|
||||
|
23
doc/user_guide/en/tutorial-example5.adoc
Normal file
23
doc/user_guide/en/tutorial-example5.adoc
Normal 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*.
|
||||
|
18
doc/user_guide/en/tutorial-example6.adoc
Normal file
18
doc/user_guide/en/tutorial-example6.adoc
Normal 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.
|
@ -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
|
||||
|
@ -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[]
|
||||
|
@ -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
21
lib/77bit/call_to_c28.f90
Normal 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
58
lib/77bit/free_text.f90
Normal 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
|
@ -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
|
||||
|
13
lib/77bit/nonstd_to_c58.f90
Normal file
13
lib/77bit/nonstd_to_c58.f90
Normal 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
|
@ -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
441
lib/C_interface_module.f90
Normal 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
|
@ -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
57
lib/blanker.f90
Normal 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
|
@ -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
|
||||
|
107
lib/decoder.f90
107
lib/decoder.f90
@ -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
BIN
lib/fsk4hf/.DS_Store
vendored
Normal file
Binary file not shown.
562
lib/fst280_decode.f90
Normal file
562
lib/fst280_decode.f90
Normal 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
|
111
lib/fst4/bpdecode240_101.f90
Normal file
111
lib/fst4/bpdecode240_101.f90
Normal 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
155
lib/fst4/decode240_101.f90
Normal 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
155
lib/fst4/decode240_74.f90
Normal 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
|
46
lib/fst4/encode240_101.f90
Normal file
46
lib/fst4/encode240_101.f90
Normal 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
46
lib/fst4/encode240_74.f90
Normal 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
|
48
lib/fst4/fst4_baseline.f90
Normal file
48
lib/fst4/fst4_baseline.f90
Normal 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
7
lib/fst4/fst4_params.f90
Normal 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
155
lib/fst4/fst4sim.f90
Normal 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
91
lib/fst4/gen_fst4wave.f90
Normal 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
111
lib/fst4/genfst4.f90
Normal 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
Loading…
Reference in New Issue
Block a user