FT8 demod: implemented decoder

This commit is contained in:
f4exb 2023-01-19 23:51:43 +01:00
parent 0d77b37ec1
commit b1cf15213c
22 changed files with 968 additions and 72 deletions

View File

@ -37,4 +37,7 @@ target_link_libraries(ft8
sdrbase
)
# remove or comment when debugging is done
# set_property(TARGET ft8 PROPERTY COMPILE_OPTIONS "$<$<CONFIG:Debug>:-Og>")
install(TARGETS ft8 DESTINATION ${INSTALL_LIB_DIR})

View File

@ -27,19 +27,17 @@
namespace FT8 {
int Packing::ihashcall(std::string call, int m)
int Packing::ihashcall(std::string rawcall, int m)
{
while (call.size() > 0 && call[0] == ' ')
call.erase(0, 1);
while (call.size() > 0 && call[call.size() - 1] == ' ')
call.erase(call.end() - 1);
std::string call = trim(rawcall);
const char *chars = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/";
while (call.size() < 11)
while (call.size() < 11) {
call += " ";
}
unsigned long long x = 0;
for (int i = 0; i < 11; i++)
{
int c = call[i];
@ -48,6 +46,7 @@ int Packing::ihashcall(std::string call, int m)
int j = p - chars;
x = 38 * x + j;
}
x = x * 47055833459LL;
x = x >> (64 - m);
@ -66,17 +65,24 @@ std::string Packing::unpackcall(int x)
const char *c3 = "0123456789";
const char *c4 = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (x == 0)
if (x == 0) {
return "DE";
if (x == 1)
}
if (x == 1) {
return "QRZ";
if (x == 2)
}
if (x == 2) {
return "CQ";
}
if (x <= 1002)
{
sprintf(tmp, "CQ %d", x - 3);
return tmp;
return std::string(tmp);
}
if (x <= 532443)
{
x -= 1003;
@ -88,11 +94,10 @@ std::string Packing::unpackcall(int x)
x %= 27;
int ci4 = x;
sprintf(tmp, "CQ %c%c%c%c", c4[ci1], c4[ci2], c4[ci3], c4[ci4]);
return tmp;
return std::string(tmp);
}
if (x < NTOKENS)
{
if (x < NTOKENS) {
return "<TOKEN>";
}
@ -103,14 +108,13 @@ std::string Packing::unpackcall(int x)
// 22-bit hash...
std::string s;
hashes_mu.lock();
if (hashes22.count(x) > 0)
{
if (hashes22.count(x) > 0) {
s = hashes22[x];
}
else
{
} else {
s = "<...22>";
}
hashes_mu.unlock();
return s;
}
@ -133,7 +137,7 @@ std::string Packing::unpackcall(int x)
a[6] = '\0';
return a;
return std::string(a);
}
// unpack a 15-bit grid square &c.
@ -172,44 +176,41 @@ std::string Packing::unpackgrid(int ng, int ir, int i3)
ng -= NGBASE;
if (ng == 1)
{
if (ng == 1) {
return " "; // ???
}
if (ng == 2)
{
if (ng == 2) {
return "RRR ";
}
if (ng == 3)
{
if (ng == 3) {
return "RR73";
}
if (ng == 4)
{
if (ng == 4) {
return "73 ";
}
int db = ng - 35;
char tmp[16];
if (db >= 0)
{
if (db >= 0) {
sprintf(tmp, "%s+%02d", ir ? "R" : "", db);
}
else
{
} else {
sprintf(tmp, "%s-%02d", ir ? "R" : "", 0 - db);
}
return tmp;
return std::string(tmp);
}
void Packing::remember_call(std::string call)
{
hashes_mu.lock();
if (call.size() >= 3 && call[0] != '<')
{
hashes22[ihashcall(call, 22)] = call;
hashes12[ihashcall(call, 12)] = call;
}
hashes_mu.unlock();
}
@ -236,11 +237,14 @@ std::string Packing::unpack_4(int a77[], std::string& call1str, std::string& cal
}
call[11] = '\0';
std::string callstr(call);
remember_call(call);
remember_call(callstr);
if (un64(a77, 73, 1) == 1) {
return std::string("CQ ") + call;
if (un64(a77, 73, 1) == 1)
{
call1str = std::string("CQ ") + callstr;
return call1str;
}
int x12 = un64(a77, 0, 12);
@ -255,7 +259,6 @@ std::string Packing::unpack_4(int a77[], std::string& call1str, std::string& cal
}
hashes_mu.unlock();
int swap = un64(a77, 70, 1);
std::string msg;
@ -274,14 +277,16 @@ std::string Packing::unpack_4(int a77[], std::string& call1str, std::string& cal
int suffix = un64(a77, 71, 2);
if (suffix == 1) {
msg += " RRR";
if (suffix == 1)
{
locstr = " RRR";
} else if (suffix == 2) {
msg += " RR73";
locstr = " RR73";
} else if (suffix == 3) {
msg += " 73";
locstr = " 73";
}
msg += locstr;
return msg;
}
@ -323,7 +328,7 @@ std::string Packing::unpack_1(int a77[], std::string& call1str, std::string& cal
remember_call(call1str);
remember_call(call2str);
const char *pr = (i3 == 1 ? "/R" : "/P");
const std::string pr = (i3 == 1 ? "/R" : "/P");
return call1str + (rover1 ? pr : "") + " " + call2str + (rover2 ? pr : "") + " " + locstr;
}
@ -340,11 +345,13 @@ std::string Packing::unpack_0_0(int a77[], std::string& call1str, std::string& c
const char *cc = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
boost::multiprecision::int128_t x = un128(a77, 0, 71);
std::string msg = "0123456789123";
for (int i = 0; i < 13; i++)
{
msg[13 - 1 - i] = cc[(int) (x % 42)];
x = x / 42;
}
call1str = msg;
return msg;
}
@ -392,6 +399,7 @@ std::string Packing::unpack_3(int a77[], std::string& call1str, std::string& cal
int statei = serial - 8001;
std::string serialstr;
int nstates = sizeof(ru_states) / sizeof(ru_states[0]);
if (serial > 8000 && statei < nstates)
{
serialstr = ru_states[statei];
@ -400,16 +408,17 @@ std::string Packing::unpack_3(int a77[], std::string& call1str, std::string& cal
{
char tmp[32];
sprintf(tmp, "%04d", serial);
serialstr = tmp;
serialstr = std::string(tmp);
}
std::string msg;
if (tu)
{
if (tu) {
msg += "TU; ";
}
msg += call1str + " " + call2str + " ";
if (r)
{
msg += "R ";
@ -417,8 +426,9 @@ std::string Packing::unpack_3(int a77[], std::string& call1str, std::string& cal
{
char tmp[16];
sprintf(tmp, "%d ", rst);
msg += tmp;
msg += std::string(tmp);
}
msg += serialstr;
remember_call(call1str);
@ -479,7 +489,7 @@ std::string Packing::unpack_0_3(int a77[], int n3, std::string& call1str, std::s
{
char tmp[16];
sprintf(tmp, "%d%c ", n_transmitters + 1, clss + 'A');
msg += tmp;
msg += std::string(tmp);
}
if (section - 1 >= 0 && section - 1 < (int)(sizeof(sections) / sizeof(sections[0]))) {
@ -531,8 +541,9 @@ std::string Packing::unpack(int a77[], std::string& call1, std::string& call2, s
}
char tmp[64];
call1 = "UNK";
sprintf(tmp, "UNK i3=%d n3=%d", i3, n3);
return tmp;
return std::string(tmp);
}
} // namespace FT8

View File

@ -24,7 +24,7 @@
#include <string>
#include <map>
#include <QMutex>
#include <QRecursiveMutex>
#include "export.h"
@ -36,7 +36,7 @@ public:
std::string unpack(int a91[], std::string& call1str, std::string& call2str, std::string& locstr);
private:
int ihashcall(std::string call, int m);
static int ihashcall(std::string call, int m);
std::string unpackcall(int x);
std::string unpackgrid(int ng, int ir, int i3);
void remember_call(std::string call);
@ -46,7 +46,7 @@ private:
std::string unpack_3(int a77[], std::string& call1str, std::string& call2str, std::string& locstr);
std::string unpack_0_3(int a77[], int n3, std::string& call1str, std::string& call2str, std::string& locstr);
QMutex hashes_mu;
QRecursiveMutex hashes_mu;
std::map<int, std::string> hashes12;
std::map<int, std::string> hashes22;

View File

@ -24,6 +24,7 @@ set(demodft8_HEADERS
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${CMAKE_SOURCE_DIR}/ft8
)
if(NOT SERVER_MODE)
@ -57,6 +58,7 @@ target_link_libraries(${TARGET_NAME}
sdrbase
${TARGET_LIB_GUI}
swagger
ft8
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -276,6 +276,18 @@ void FT8Demod::applySettings(const FT8DemodSettings& settings, bool force)
if ((m_settings.m_agc != settings.m_agc) || force) {
reverseAPIKeys.append("agc");
}
if ((m_settings.m_recordWav != settings.m_recordWav) || force) {
reverseAPIKeys.append("recordWav");
}
if ((m_settings.m_logMessages != settings.m_logMessages) || force) {
reverseAPIKeys.append("logMessages");
}
if ((m_settings.m_nbDecoderThreads != settings.m_nbDecoderThreads) || force) {
reverseAPIKeys.append("nbDecoderThreads");
}
if ((m_settings.m_decoderTimeBudget != settings.m_decoderTimeBudget) || force) {
reverseAPIKeys.append("decoderTimeBudget");
}
if (m_settings.m_streamIndex != settings.m_streamIndex)
{
@ -444,6 +456,18 @@ void FT8Demod::webapiUpdateChannelSettings(
if (channelSettingsKeys.contains("agc")) {
settings.m_agc = response.getFt8DemodSettings()->getAgc() != 0;
}
if (channelSettingsKeys.contains("recordWav")) {
settings.m_recordWav = response.getFt8DemodSettings()->getRecordWav() != 0;
}
if (channelSettingsKeys.contains("m_logMessages")) {
settings.m_logMessages = response.getFt8DemodSettings()->getLogMessages() != 0;
}
if (channelSettingsKeys.contains("nbDecoderThreads")) {
settings.m_nbDecoderThreads = response.getFt8DemodSettings()->getNbDecoderThreads();
}
if (channelSettingsKeys.contains("decoderTimeBudget")) {
settings.m_decoderTimeBudget = response.getFt8DemodSettings()->getDecoderTimeBudget();
}
if (channelSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getFt8DemodSettings()->getRgbColor();
}
@ -500,6 +524,10 @@ void FT8Demod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& resp
response.getFt8DemodSettings()->setFftWindow((int) settings.m_filterBank[settings.m_filterIndex].m_fftWindow);
response.getFt8DemodSettings()->setVolume(settings.m_volume);
response.getFt8DemodSettings()->setAgc(settings.m_agc ? 1 : 0);
response.getFt8DemodSettings()->setRecordWav(settings.m_recordWav ? 1 : 0);
response.getFt8DemodSettings()->setLogMessages(settings.m_logMessages ? 1 : 0);
response.getFt8DemodSettings()->setNbDecoderThreads(settings.m_nbDecoderThreads);
response.getFt8DemodSettings()->setDecoderTimeBudget(settings.m_decoderTimeBudget);
response.getFt8DemodSettings()->setRgbColor(settings.m_rgbColor);
if (response.getFt8DemodSettings()->getTitle()) {
@ -671,6 +699,18 @@ void FT8Demod::webapiFormatChannelSettings(
if (channelSettingsKeys.contains("agc") || force) {
swgFT8DemodSettings->setAgc(settings.m_agc ? 1 : 0);
}
if (channelSettingsKeys.contains("recordWav") || force) {
swgFT8DemodSettings->setRecordWav(settings.m_recordWav ? 1 : 0);
}
if (channelSettingsKeys.contains("logMessages") || force) {
swgFT8DemodSettings->setRecordWav(settings.m_logMessages ? 1 : 0);
}
if (channelSettingsKeys.contains("nbDecoderThreads") || force) {
swgFT8DemodSettings->setNbDecoderThreads(settings.m_nbDecoderThreads);
}
if (channelSettingsKeys.contains("decoderTimeBudget") || force) {
swgFT8DemodSettings->setDecoderTimeBudget(settings.m_decoderTimeBudget);
}
if (channelSettingsKeys.contains("rgbColor") || force) {
swgFT8DemodSettings->setRgbColor(settings.m_rgbColor);
}

View File

@ -58,7 +58,8 @@ FT8DemodBaseband::FT8DemodBaseband() :
this,
&FT8DemodBaseband::bufferReady,
m_ft8DemodWorker,
&FT8DemodWorker::processBuffer
&FT8DemodWorker::processBuffer,
Qt::QueuedConnection
);
m_workerThread->start();
@ -204,6 +205,30 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc
}
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_lowCutoff != settings.m_filterBank[settings.m_filterIndex].m_lowCutoff) || force) {
m_ft8DemodWorker->setLowFrequency(settings.m_filterBank[settings.m_filterIndex].m_lowCutoff);
}
if ((m_settings.m_filterBank[m_settings.m_filterIndex].m_rfBandwidth != settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth) || force) {
m_ft8DemodWorker->setHighFrequency(settings.m_filterBank[settings.m_filterIndex].m_rfBandwidth);
}
if ((settings.m_recordWav != m_settings.m_recordWav) || force) {
m_ft8DemodWorker->setRecordSamples(settings.m_recordWav);
}
if ((settings.m_logMessages != m_settings.m_logMessages) || force) {
m_ft8DemodWorker->setLogMessages(settings.m_logMessages);
}
if ((settings.m_nbDecoderThreads != m_settings.m_nbDecoderThreads) || force) {
m_ft8DemodWorker->setNbDecoderThreads(settings.m_nbDecoderThreads);
}
if ((settings.m_decoderTimeBudget != m_settings.m_decoderTimeBudget) || force) {
m_ft8DemodWorker->setDecoderTimeBudget(settings.m_decoderTimeBudget);
}
m_sink.applySettings(settings, force);
m_settings = settings;
}

View File

@ -204,6 +204,32 @@ void FT8DemodGUI::on_filterIndex_valueChanged(int value)
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
}
void FT8DemodGUI::on_recordWav_toggled(bool checked)
{
m_settings.m_recordWav = checked;
applySettings();
}
void FT8DemodGUI::on_logMessages_toggled(bool checked)
{
m_settings.m_logMessages = checked;
applySettings();
}
void FT8DemodGUI::on_nbThreads_valueChanged(int value)
{
ui->nbThreadsText->setText(tr("%1").arg(value));
m_settings.m_nbDecoderThreads = value;
applySettings();
}
void FT8DemodGUI::on_timeBudget_valueChanged(int value)
{
m_settings.m_decoderTimeBudget = value / 10.0f;
ui->timeBudgetText->setText(tr("%1").arg(m_settings.m_decoderTimeBudget, 0, 'f', 1));
applySettings();
}
void FT8DemodGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
@ -335,6 +361,9 @@ FT8DemodGUI::FT8DemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, Baseban
applyBandwidths(m_settings.m_filterBank[m_settings.m_filterIndex].m_spanLog2, true); // does applySettings(true)
DialPopup::addPopupsToChildDials(this);
// Resize the table using dummy data
resizeMessageTable();
}
FT8DemodGUI::~FT8DemodGUI()
@ -500,6 +529,13 @@ void FT8DemodGUI::displaySettings()
ui->volume->setValue(volume);
ui->volumeText->setText(QString("%1").arg(volume));
ui->recordWav->setChecked(m_settings.m_recordWav);
ui->logMessages->setChecked(m_settings.m_logMessages);
ui->nbThreads->setValue(m_settings.m_nbDecoderThreads);
ui->nbThreadsText->setText(tr("%1").arg(m_settings.m_nbDecoderThreads));
ui->timeBudget->setValue(m_settings.m_decoderTimeBudget*10);
ui->timeBudgetText->setText(tr("%1").arg(m_settings.m_decoderTimeBudget, 0, 'f', 1));
updateIndexLabel();
getRollupContents()->restoreState(m_rollupState);
@ -549,9 +585,33 @@ void FT8DemodGUI::makeUIConnections()
QObject::connect(ui->spanLog2, &QSlider::valueChanged, this, &FT8DemodGUI::on_spanLog2_valueChanged);
QObject::connect(ui->fftWindow, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &FT8DemodGUI::on_fftWindow_currentIndexChanged);
QObject::connect(ui->filterIndex, &QDial::valueChanged, this, &FT8DemodGUI::on_filterIndex_valueChanged);
QObject::connect(ui->recordWav, &ButtonSwitch::toggled, this, &FT8DemodGUI::on_recordWav_toggled);
QObject::connect(ui->logMessages, &ButtonSwitch::toggled, this, &FT8DemodGUI::on_logMessages_toggled);
QObject::connect(ui->nbThreads, &QDial::valueChanged, this, &FT8DemodGUI::on_nbThreads_valueChanged);
QObject::connect(ui->timeBudget, &QDial::valueChanged, this, &FT8DemodGUI::on_timeBudget_valueChanged);
}
void FT8DemodGUI::updateAbsoluteCenterFrequency()
{
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
}
void FT8DemodGUI::resizeMessageTable()
{
// Fill table with a row of dummy data that will size the columns nicely
// Trailing spaces are for sort arrow
int row = ui->messages->rowCount();
ui->messages->setRowCount(row + 1);
ui->messages->setItem(row, MESSAGE_COL_UTC, new QTableWidgetItem("000000"));
ui->messages->setItem(row, MESSAGE_COL_N, new QTableWidgetItem("0"));
ui->messages->setItem(row, MESSAGE_COL_SNR, new QTableWidgetItem("-24"));
ui->messages->setItem(row, MESSAGE_COL_DEC, new QTableWidgetItem("174"));
ui->messages->setItem(row, MESSAGE_COL_DT, new QTableWidgetItem("0.0"));
ui->messages->setItem(row, MESSAGE_COL_DF, new QTableWidgetItem("0000"));
ui->messages->setItem(row, MESSAGE_COL_CALL1, new QTableWidgetItem("123456789ABCD"));
ui->messages->setItem(row, MESSAGE_COL_CALL2, new QTableWidgetItem("HF7SIEMA"));
ui->messages->setItem(row, MESSAGE_COL_LOC, new QTableWidgetItem("JN00"));
ui->messages->setItem(row, MESSAGE_COL_INFO, new QTableWidgetItem("hint1"));
ui->messages->resizeColumnsToContents();
ui->messages->removeRow(row);
}

View File

@ -100,6 +100,21 @@ private:
void leaveEvent(QEvent*);
void enterEvent(EnterEventType*);
void resizeMessageTable();
enum MessageCol {
MESSAGE_COL_UTC,
MESSAGE_COL_N,
MESSAGE_COL_SNR,
MESSAGE_COL_DEC,
MESSAGE_COL_DT,
MESSAGE_COL_DF,
MESSAGE_COL_CALL1,
MESSAGE_COL_CALL2,
MESSAGE_COL_LOC,
MESSAGE_COL_INFO,
};
private slots:
void on_deltaFrequency_changed(qint64 value);
void on_BW_valueChanged(int value);
@ -109,6 +124,10 @@ private slots:
void on_spanLog2_valueChanged(int value);
void on_fftWindow_currentIndexChanged(int index);
void on_filterIndex_valueChanged(int value);
void on_recordWav_toggled(bool checked);
void on_logMessages_toggled(bool checked);
void on_nbThreads_valueChanged(int value);
void on_timeBudget_valueChanged(int value);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void handleInputMessages();

View File

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>414</width>
<height>190</height>
<height>731</height>
</rect>
</property>
<property name="sizePolicy">
@ -667,9 +667,9 @@
<property name="geometry">
<rect>
<x>10</x>
<y>170</y>
<y>193</y>
<width>218</width>
<height>284</height>
<height>261</height>
</rect>
</property>
<property name="sizePolicy">
@ -737,6 +737,298 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="tft8Container" native="true">
<property name="geometry">
<rect>
<x>0</x>
<y>460</y>
<width>412</width>
<height>251</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>412</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>FT8 details</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="ft8Settings">
<item>
<widget class="QLabel" name="nbThreadsLabel">
<property name="text">
<string>Th</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="nbThreads">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Number of decoder threads</string>
</property>
<property name="minimum">
<number>2</number>
</property>
<property name="maximum">
<number>12</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>6</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="nbThreadsText">
<property name="text">
<string>6</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeBudgetLabel">
<property name="text">
<string>TB</string>
</property>
</widget>
</item>
<item>
<widget class="QDial" name="timeBudget">
<property name="maximumSize">
<size>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="toolTip">
<string>Decoder time budget (seconds)</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>50</number>
</property>
<property name="singleStep">
<number>1</number>
</property>
<property name="pageStep">
<number>1</number>
</property>
<property name="value">
<number>25</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="timeBudgetText">
<property name="text">
<string>2.5</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="nbDecodesLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="nbDecodesText">
<property name="toolTip">
<string>Number of messages decoded in the last sequence</string>
</property>
<property name="text">
<string>00</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="ButtonSwitch" name="logMessages">
<property name="toolTip">
<string>Log messages</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="recordWav">
<property name="toolTip">
<string>Record wav files</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/record_off.png</normaloff>:/record_off.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableWidget" name="messages">
<property name="toolTip">
<string>Decoded messages</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>UTC</string>
</property>
<property name="toolTip">
<string>Sequence UTC time HHMMSS</string>
</property>
</column>
<column>
<property name="text">
<string>P</string>
</property>
<property name="toolTip">
<string>Successful decoder pass index</string>
</property>
</column>
<column>
<property name="text">
<string>SNR</string>
</property>
<property name="toolTip">
<string>Signal to noise ratio (dB) in 2.5 kHz bandwidth</string>
</property>
</column>
<column>
<property name="text">
<string>OKb</string>
</property>
<property name="toolTip">
<string>Number of correct bits before correction</string>
</property>
</column>
<column>
<property name="text">
<string>dt</string>
</property>
<property name="toolTip">
<string>Time delay</string>
</property>
</column>
<column>
<property name="text">
<string>df</string>
</property>
<property name="toolTip">
<string>Frequency shift</string>
</property>
</column>
<column>
<property name="text">
<string>Call1</string>
</property>
<property name="toolTip">
<string>Fist call area</string>
</property>
</column>
<column>
<property name="text">
<string>Call2</string>
</property>
<property name="toolTip">
<string>Second call area</string>
</property>
</column>
<column>
<property name="text">
<string>Loc</string>
</property>
<property name="toolTip">
<string>Locator area</string>
</property>
</column>
<column>
<property name="text">
<string>Info</string>
</property>
<property name="toolTip">
<string>Extra decoder information</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>

View File

@ -43,6 +43,10 @@ FT8DemodSettings::FT8DemodSettings() :
void FT8DemodSettings::resetToDefaults()
{
m_agc = false;
m_recordWav = false;
m_logMessages = false;
m_nbDecoderThreads = 6;
m_decoderTimeBudget = 0.5;
m_volume = 1.0;
m_inputFrequencyOffset = 0;
m_rgbColor = QColor(0, 192, 255).rgb();
@ -69,6 +73,10 @@ QByteArray FT8DemodSettings::serialize() const
}
s.writeU32(5, m_rgbColor);
s.writeBool(6, m_recordWav);
s.writeBool(7, m_logMessages);
s.writeS32(8, m_nbDecoderThreads);
s.writeFloat(9, m_decoderTimeBudget);
s.writeBool(11, m_agc);
s.writeString(16, m_title);
s.writeBool(18, m_useReverseAPI);
@ -126,6 +134,10 @@ bool FT8DemodSettings::deserialize(const QByteArray& data)
}
d.readU32(5, &m_rgbColor);
d.readBool(6, &m_recordWav, false);
d.readBool(7, &m_logMessages, false);
d.readS32(8, &m_nbDecoderThreads, 6);
d.readFloat(9, &m_decoderTimeBudget, 0.5);
d.readBool(11, &m_agc, false);
d.readString(16, &m_title, "SSB Demodulator");
d.readBool(18, &m_useReverseAPI, false);

View File

@ -46,8 +46,11 @@ struct FT8DemodSettings
// Real m_rfBandwidth;
// Real m_lowCutoff;
Real m_volume;
// int m_spanLog2;
bool m_agc;
bool m_recordWav;
bool m_logMessages;
int m_nbDecoderThreads;
float m_decoderTimeBudget;
quint32 m_rgbColor;
QString m_title;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).

View File

@ -20,11 +20,84 @@
#include <QDateTime>
#include "dsp/wavfilerecord.h"
#include "util/messagequeue.h"
#include "util/ft8message.h"
#include "ft8demodsettings.h"
#include "ft8demodworker.h"
FT8DemodWorker::FT8DemodWorker()
FT8DemodWorker::FT8Callback::FT8Callback(const QDateTime& periodTS, FT8::Packing& packing) :
m_packing(packing),
m_periodTS(periodTS)
{
m_msgReportFT8Messages = MsgReportFT8Messages::create();
}
int FT8DemodWorker::FT8Callback::hcb(
int *a91,
float hz0,
float off,
const char *comment,
float snr,
int pass,
int correct_bits
)
{
std::string call1;
std::string call2;
std::string loc;
std::string msg = m_packing.unpack(a91, call1, call2, loc);
cycle_mu.lock();
if (cycle_already.count(msg) > 0)
{
// already decoded this message on this cycle
cycle_mu.unlock();
return 1; // 1 => already seen, don't subtract.
}
cycle_already[msg] = true;
cycle_mu.unlock();
QList<FT8Message>& ft8Messages = m_msgReportFT8Messages->getFT8Messages();
ft8Messages.push_back(FT8Message());
FT8Message& ft8Message = ft8Messages.back();
ft8Message.ts = m_periodTS;
ft8Message.pass = pass;
ft8Message.snr = (int) snr;
ft8Message.nbCorrectBits = correct_bits;
ft8Message.dt = off - 0.5;
ft8Message.df = hz0;
ft8Message.call1 = QString(call1.c_str());
ft8Message.call2 = QString(call2.c_str());
ft8Message.loc = QString(loc.c_str());
ft8Message.decoderInfo = QString(comment);
qDebug("FT8DemodWorker::FT8Callback::hcb: %d %3d %3d %5.2f %6.1f %s [%s:%s:%s] (%s)",
pass,
(int)snr,
correct_bits,
off - 0.5,
hz0,
msg.c_str(),
call1.c_str(),
call2.c_str(),
loc.c_str(),
comment
);
return 2; // 2 => new decode, do subtract.
}
FT8DemodWorker::FT8DemodWorker() :
m_recordSamples(false),
m_nbDecoderThreads(6),
m_decoderTimeBudget(0.5),
m_lowFreq(200),
m_highFreq(3000),
m_reportingMessageQueue(nullptr)
{
QString relPath = "sdrangel/ft8/save";
QDir dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation));
@ -38,16 +111,58 @@ FT8DemodWorker::~FT8DemodWorker()
void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
{
qDebug("FT8DemodWorker::processBuffer: %s", qPrintable(periodTS.toString("yyyy-MM-dd HH:mm:ss")));
WavFileRecord *wavFileRecord = new WavFileRecord(FT8DemodSettings::m_ft8SampleRate);
QFileInfo wfi(QDir(m_samplesPath), periodTS.toString("yyyyMMdd_HHmmss"));
QString wpath = wfi.absoluteFilePath();
qDebug("FT8DemodWorker::processBuffer: WAV file: %s.wav", qPrintable(wpath));
wavFileRecord->setFileName(wpath);
wavFileRecord->setFileBaseIsFileName(true);
wavFileRecord->setMono(true);
wavFileRecord->startRecording();
wavFileRecord->writeMono(buffer, 15*FT8DemodSettings::m_ft8SampleRate);
wavFileRecord->stopRecording();
delete wavFileRecord;
qDebug("FT8DemodWorker::processBuffer: %s %d:%f [%d:%d]", qPrintable(periodTS.toString("yyyy-MM-dd HH:mm:ss")),
m_nbDecoderThreads, m_decoderTimeBudget, m_lowFreq, m_highFreq);
if (m_recordSamples)
{
WavFileRecord *wavFileRecord = new WavFileRecord(FT8DemodSettings::m_ft8SampleRate);
QFileInfo wfi(QDir(m_samplesPath), periodTS.toString("yyyyMMdd_HHmmss"));
QString wpath = wfi.absoluteFilePath();
qDebug("FT8DemodWorker::processBuffer: WAV file: %s.wav", qPrintable(wpath));
wavFileRecord->setFileName(wpath);
wavFileRecord->setFileBaseIsFileName(true);
wavFileRecord->setMono(true);
wavFileRecord->startRecording();
wavFileRecord->writeMono(buffer, 15*FT8DemodSettings::m_ft8SampleRate);
wavFileRecord->stopRecording();
delete wavFileRecord;
}
int hints[2] = { 2, 0 }; // CQ
FT8Callback ft8Callback(periodTS, m_packing);
m_ft8Decoder.getParams().nthreads = m_nbDecoderThreads;
std::vector<float> samples(15*FT8DemodSettings::m_ft8SampleRate);
std::transform(
buffer,
buffer + (15*FT8DemodSettings::m_ft8SampleRate),
samples.begin(),
[](const int16_t& s) -> float { return s / 32768.0f; }
);
m_ft8Decoder.entry(
samples.data(),
samples.size(),
0.5 * FT8DemodSettings::m_ft8SampleRate,
FT8DemodSettings::m_ft8SampleRate,
m_lowFreq,
m_highFreq,
hints,
hints,
m_decoderTimeBudget,
m_decoderTimeBudget,
&ft8Callback,
0,
(struct FT8::cdecode *) nullptr
);
m_ft8Decoder.wait(m_decoderTimeBudget + 1.0); // add one second to budget to force quit threads
qDebug("FT8DemodWorker::processBuffer: done: %d messages", ft8Callback.getReportMessage()->getFT8Messages().size());
if (m_reportingMessageQueue) {
m_reportingMessageQueue->push(ft8Callback.getReportMessage());
} else {
delete ft8Callback.getReportMessage();
}
}

View File

@ -20,7 +20,12 @@
#include <QObject>
#include "ft8.h"
#include "unpack.h"
class QDateTime;
class MessageQueue;
class MsgReportFT8Messages;
class FT8DemodWorker : public QObject
{
@ -30,9 +35,52 @@ public:
~FT8DemodWorker();
void processBuffer(int16_t *buffer, QDateTime periodTS);
void setRecordSamples(bool recordSamples) { m_recordSamples = recordSamples; }
void setLogMessages(bool logMessages) { m_logMessages = logMessages; }
void setNbDecoderThreads(int nbDecoderThreads) { m_nbDecoderThreads = nbDecoderThreads; }
void setDecoderTimeBudget(float decoderTimeBudget) { m_decoderTimeBudget = decoderTimeBudget; }
void setLowFrequency(int lowFreq) { m_lowFreq = lowFreq; }
void setHighFrequency(int highFreq) { m_highFreq = highFreq; }
void setReportingMessageQueue(MessageQueue *messageQueue) { m_reportingMessageQueue = messageQueue; }
private:
class FT8Callback : public FT8::CallbackInterface
{
public:
FT8Callback(const QDateTime& periodTS, FT8::Packing& packing);
virtual int hcb(
int *a91,
float hz0,
float off,
const char *comment,
float snr,
int pass,
int correct_bits
);
const std::map<std::string, bool>& getMsgMap() {
return cycle_already;
}
MsgReportFT8Messages *getReportMessage() {
return m_msgReportFT8Messages;
}
private:
QMutex cycle_mu;
std::map<std::string, bool> cycle_already;
FT8::Packing& m_packing;
MsgReportFT8Messages *m_msgReportFT8Messages;
const QDateTime& m_periodTS;
};
QString m_samplesPath;
bool m_recordSamples;
bool m_logMessages;
int m_nbDecoderThreads;
float m_decoderTimeBudget;
int m_lowFreq;
int m_highFreq;
FT8::FT8Decoder m_ft8Decoder;
FT8::Packing m_packing;
MessageQueue *m_reportingMessageQueue;
};
#endif // INCLUDE_FT8DEMODWORKER_H

View File

@ -185,6 +185,7 @@ set(sdrbase_SOURCES
util/fixedtraits.cpp
util/fits.cpp
util/flightinformation.cpp
util/ft8message.cpp
util/giro.cpp
util/golay2312.cpp
util/httpdownloadmanager.cpp
@ -408,6 +409,7 @@ set(sdrbase_HEADERS
util/fixedtraits.h
util/fits.h
util/flightinformation.h
util/ft8message.h
util/giro.h
util/golay2312.h
util/httpdownloadmanager.h

View File

@ -5611,6 +5611,22 @@ margin-bottom: 20px;
"type" : "integer",
"description" : "AGC (1 if AGC active else 0)"
},
"recordWav" : {
"type" : "integer",
"description" : "Record received audio as 12000S/s 16 bit .wav file\n * 0 - Do not record\n * 1 - Record as sdrangel/ft8/save/YYYYMMDD_HHMMSS.wav in writable location\n"
},
"logMessages" : {
"type" : "integer",
"description" : "Log decoded messages in WSJT CALL.TXT format\n * 0 - Do not log messages\n * 1 - Log messages as sdrangel/ft8/logs/YYYYMMDD_call.txt in writable location (daily files)\n"
},
"nbDecoderThreads" : {
"type" : "integer",
"description" : "Number of threads in the FT8 decoder"
},
"decoderTimeBudget" : {
"type" : "number",
"format" : "float"
},
"rgbColor" : {
"type" : "integer"
},
@ -56873,7 +56889,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2023-01-17T00:44:14.657+01:00
Generated 2023-01-19T23:18:51.935+01:00
</div>
</div>
</div>

View File

@ -33,6 +33,25 @@ FT8DemodSettings:
agc:
description: AGC (1 if AGC active else 0)
type: integer
recordWav:
type: integer
description: >
Record received audio as 12000S/s 16 bit .wav file
* 0 - Do not record
* 1 - Record as sdrangel/ft8/save/YYYYMMDD_HHMMSS.wav in writable location
logMessages:
type: integer
description: >
Log decoded messages in WSJT CALL.TXT format
* 0 - Do not log messages
* 1 - Log messages as sdrangel/ft8/logs/YYYYMMDD_call.txt in writable location (daily files)
nbDecoderThreads:
type: integer
description: Number of threads in the FT8 decoder
decoderTimeBudget:
type: number
format: float
desctiption: Decoder time budget in seconds (will stop after running this time)
rgbColor:
type: integer
title:

View File

@ -0,0 +1,20 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "ft8message.h"
MESSAGE_CLASS_DEFINITION(MsgReportFT8Messages, Message)

58
sdrbase/util/ft8message.h Normal file
View File

@ -0,0 +1,58 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2023 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as version 3 of the License, or //
// (at your option) any later version. //
// //
// This program is distributed in the hope that it will be useful, //
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
// GNU General Public License V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FT8MESSAGE_H
#define INCLUDE_FT8MESSAGE_H
#include <QString>
#include <QDateTime>
#include <QList>
#include "export.h"
#include "message.h"
struct SDRBASE_API FT8Message
{
QDateTime ts;
int pass;
int snr;
int nbCorrectBits;
float dt;
float df;
QString call1;
QString call2;
QString loc;
QString decoderInfo;
};
class FT8_API MsgReportFT8Messages : public Message {
MESSAGE_CLASS_DECLARATION
public:
QList<FT8Message>& getFT8Messages() { return m_ft8Messages; }
static MsgReportFT8Messages* create() {
return new MsgReportFT8Messages();
}
private:
QList<FT8Message> m_ft8Messages;
MsgReportFT8Messages() :
Message()
{ }
};
#endif // INCLUDE_FT8MESSAGE_H

View File

@ -33,6 +33,25 @@ FT8DemodSettings:
agc:
description: AGC (1 if AGC active else 0)
type: integer
recordWav:
type: integer
description: >
Record received audio as 12000S/s 16 bit .wav file
* 0 - Do not record
* 1 - Record as sdrangel/ft8/save/YYYYMMDD_HHMMSS.wav in writable location
logMessages:
type: integer
description: >
Log decoded messages in WSJT CALL.TXT format
* 0 - Do not log messages
* 1 - Log messages as sdrangel/ft8/logs/YYYYMMDD_call.txt in writable location (daily files)
nbDecoderThreads:
type: integer
description: Number of threads in the FT8 decoder
decoderTimeBudget:
type: number
format: float
desctiption: Decoder time budget in seconds (will stop after running this time)
rgbColor:
type: integer
title:

View File

@ -5611,6 +5611,22 @@ margin-bottom: 20px;
"type" : "integer",
"description" : "AGC (1 if AGC active else 0)"
},
"recordWav" : {
"type" : "integer",
"description" : "Record received audio as 12000S/s 16 bit .wav file\n * 0 - Do not record\n * 1 - Record as sdrangel/ft8/save/YYYYMMDD_HHMMSS.wav in writable location\n"
},
"logMessages" : {
"type" : "integer",
"description" : "Log decoded messages in WSJT CALL.TXT format\n * 0 - Do not log messages\n * 1 - Log messages as sdrangel/ft8/logs/YYYYMMDD_call.txt in writable location (daily files)\n"
},
"nbDecoderThreads" : {
"type" : "integer",
"description" : "Number of threads in the FT8 decoder"
},
"decoderTimeBudget" : {
"type" : "number",
"format" : "float"
},
"rgbColor" : {
"type" : "integer"
},
@ -56873,7 +56889,7 @@ except ApiException as e:
</div>
<div id="generator">
<div class="content">
Generated 2023-01-17T00:44:14.657+01:00
Generated 2023-01-19T23:18:51.935+01:00
</div>
</div>
</div>

View File

@ -44,6 +44,14 @@ SWGFT8DemodSettings::SWGFT8DemodSettings() {
m_volume_isSet = false;
agc = 0;
m_agc_isSet = false;
record_wav = 0;
m_record_wav_isSet = false;
log_messages = 0;
m_log_messages_isSet = false;
nb_decoder_threads = 0;
m_nb_decoder_threads_isSet = false;
decoder_time_budget = 0.0f;
m_decoder_time_budget_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = nullptr;
@ -90,6 +98,14 @@ SWGFT8DemodSettings::init() {
m_volume_isSet = false;
agc = 0;
m_agc_isSet = false;
record_wav = 0;
m_record_wav_isSet = false;
log_messages = 0;
m_log_messages_isSet = false;
nb_decoder_threads = 0;
m_nb_decoder_threads_isSet = false;
decoder_time_budget = 0.0f;
m_decoder_time_budget_isSet = false;
rgb_color = 0;
m_rgb_color_isSet = false;
title = new QString("");
@ -125,6 +141,10 @@ SWGFT8DemodSettings::cleanup() {
if(title != nullptr) {
delete title;
}
@ -174,6 +194,14 @@ SWGFT8DemodSettings::fromJsonObject(QJsonObject &pJson) {
::SWGSDRangel::setValue(&agc, pJson["agc"], "qint32", "");
::SWGSDRangel::setValue(&record_wav, pJson["recordWav"], "qint32", "");
::SWGSDRangel::setValue(&log_messages, pJson["logMessages"], "qint32", "");
::SWGSDRangel::setValue(&nb_decoder_threads, pJson["nbDecoderThreads"], "qint32", "");
::SWGSDRangel::setValue(&decoder_time_budget, pJson["decoderTimeBudget"], "float", "");
::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", "");
::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString");
@ -236,6 +264,18 @@ SWGFT8DemodSettings::asJsonObject() {
if(m_agc_isSet){
obj->insert("agc", QJsonValue(agc));
}
if(m_record_wav_isSet){
obj->insert("recordWav", QJsonValue(record_wav));
}
if(m_log_messages_isSet){
obj->insert("logMessages", QJsonValue(log_messages));
}
if(m_nb_decoder_threads_isSet){
obj->insert("nbDecoderThreads", QJsonValue(nb_decoder_threads));
}
if(m_decoder_time_budget_isSet){
obj->insert("decoderTimeBudget", QJsonValue(decoder_time_budget));
}
if(m_rgb_color_isSet){
obj->insert("rgbColor", QJsonValue(rgb_color));
}
@ -353,6 +393,46 @@ SWGFT8DemodSettings::setAgc(qint32 agc) {
this->m_agc_isSet = true;
}
qint32
SWGFT8DemodSettings::getRecordWav() {
return record_wav;
}
void
SWGFT8DemodSettings::setRecordWav(qint32 record_wav) {
this->record_wav = record_wav;
this->m_record_wav_isSet = true;
}
qint32
SWGFT8DemodSettings::getLogMessages() {
return log_messages;
}
void
SWGFT8DemodSettings::setLogMessages(qint32 log_messages) {
this->log_messages = log_messages;
this->m_log_messages_isSet = true;
}
qint32
SWGFT8DemodSettings::getNbDecoderThreads() {
return nb_decoder_threads;
}
void
SWGFT8DemodSettings::setNbDecoderThreads(qint32 nb_decoder_threads) {
this->nb_decoder_threads = nb_decoder_threads;
this->m_nb_decoder_threads_isSet = true;
}
float
SWGFT8DemodSettings::getDecoderTimeBudget() {
return decoder_time_budget;
}
void
SWGFT8DemodSettings::setDecoderTimeBudget(float decoder_time_budget) {
this->decoder_time_budget = decoder_time_budget;
this->m_decoder_time_budget_isSet = true;
}
qint32
SWGFT8DemodSettings::getRgbColor() {
return rgb_color;
@ -492,6 +572,18 @@ SWGFT8DemodSettings::isSet(){
if(m_agc_isSet){
isObjectUpdated = true; break;
}
if(m_record_wav_isSet){
isObjectUpdated = true; break;
}
if(m_log_messages_isSet){
isObjectUpdated = true; break;
}
if(m_nb_decoder_threads_isSet){
isObjectUpdated = true; break;
}
if(m_decoder_time_budget_isSet){
isObjectUpdated = true; break;
}
if(m_rgb_color_isSet){
isObjectUpdated = true; break;
}

View File

@ -69,6 +69,18 @@ public:
qint32 getAgc();
void setAgc(qint32 agc);
qint32 getRecordWav();
void setRecordWav(qint32 record_wav);
qint32 getLogMessages();
void setLogMessages(qint32 log_messages);
qint32 getNbDecoderThreads();
void setNbDecoderThreads(qint32 nb_decoder_threads);
float getDecoderTimeBudget();
void setDecoderTimeBudget(float decoder_time_budget);
qint32 getRgbColor();
void setRgbColor(qint32 rgb_color);
@ -130,6 +142,18 @@ private:
qint32 agc;
bool m_agc_isSet;
qint32 record_wav;
bool m_record_wav_isSet;
qint32 log_messages;
bool m_log_messages_isSet;
qint32 nb_decoder_threads;
bool m_nb_decoder_threads_isSet;
float decoder_time_budget;
bool m_decoder_time_budget_isSet;
qint32 rgb_color;
bool m_rgb_color_isSet;