mirror of
https://github.com/f4exb/sdrangel.git
synced 2024-12-23 01:55:48 -05:00
FT8 demod: make OSD optional and log OSD information
This commit is contained in:
parent
cb548b7546
commit
a86cc53945
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 34 KiB |
Binary file not shown.
114
ft8/ft8.cpp
114
ft8/ft8.cpp
@ -894,7 +894,7 @@ void FT8::go(int npasses)
|
||||
}
|
||||
|
||||
int off = order[ii].off_;
|
||||
int ret = one(bins, samples_.size(), hz, off);
|
||||
int ret = one_merge(bins, samples_.size(), hz, off);
|
||||
|
||||
if (ret)
|
||||
{
|
||||
@ -2499,12 +2499,11 @@ int FT8::decode(const float ll174[], int a174[], int use_osd, std::string &comme
|
||||
if (ldpc_ok >= ok_thresh)
|
||||
{
|
||||
// plain[] is 91 systematic data bits, 83 parity bits.
|
||||
for (int i = 0; i < 174; i++)
|
||||
{
|
||||
for (int i = 0; i < 174; i++) {
|
||||
a174[i] = plain[i];
|
||||
}
|
||||
if (check_crc(a174))
|
||||
{
|
||||
|
||||
if (OSD::check_crc(a174)) {
|
||||
// success!
|
||||
return 1;
|
||||
}
|
||||
@ -2512,17 +2511,15 @@ int FT8::decode(const float ll174[], int a174[], int use_osd, std::string &comme
|
||||
|
||||
if (use_osd && params.osd_depth >= 0 && ldpc_ok >= params.osd_ldpc_thresh)
|
||||
{
|
||||
extern int osd_decode(float codeword[174], int depth, int out[91], int *);
|
||||
extern void ldpc_encode(int plain[91], int codeword[174]);
|
||||
|
||||
int oplain[91];
|
||||
int got_depth = -1;
|
||||
int osd_ok = osd_decode((float *)ll174, params.osd_depth, oplain, &got_depth);
|
||||
int osd_ok = OSD::osd_decode((float *)ll174, params.osd_depth, oplain, &got_depth);
|
||||
|
||||
if (osd_ok)
|
||||
{
|
||||
// reconstruct all 174.
|
||||
comment += "OSD-" + std::to_string(got_depth) + "-" + std::to_string(ldpc_ok);
|
||||
ldpc_encode(oplain, a174);
|
||||
OSD::ldpc_encode(oplain, a174);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@ -2669,7 +2666,7 @@ std::vector<float> FT8::down_v7_f(const std::vector<std::complex<float>> &bins,
|
||||
//
|
||||
// XXX merge with one_iter().
|
||||
//
|
||||
int FT8::one(const std::vector<std::complex<float>> &bins, int len, float hz, int off)
|
||||
int FT8::one_merge(const std::vector<std::complex<float>> &bins, int len, float hz, int off)
|
||||
{
|
||||
//
|
||||
// set up to search for best frequency and time offset.
|
||||
@ -3018,38 +3015,69 @@ int FT8::one_iter1(
|
||||
|
||||
if (params.soft_ones)
|
||||
{
|
||||
if (params.soft_ones == 1)
|
||||
{
|
||||
if (params.soft_ones == 1) {
|
||||
soft_decode(m79, ll174);
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
c_soft_decode(m79, ll174);
|
||||
}
|
||||
int ret = try_decode(samples200, ll174, best_hz, best_off,
|
||||
hz0_for_cb, hz1_for_cb, 1, "", m79);
|
||||
if (ret)
|
||||
|
||||
int ret = try_decode(
|
||||
samples200,
|
||||
ll174,
|
||||
best_hz,
|
||||
best_off,
|
||||
hz0_for_cb,
|
||||
hz1_for_cb,
|
||||
params.use_osd,
|
||||
"",
|
||||
m79
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (params.soft_pairs)
|
||||
{
|
||||
float p174[174];
|
||||
soft_decode_pairs(m79, p174);
|
||||
int ret = try_decode(samples200, p174, best_hz, best_off,
|
||||
hz0_for_cb, hz1_for_cb, 1, "", m79);
|
||||
if (ret)
|
||||
int ret = try_decode(
|
||||
samples200,
|
||||
p174,
|
||||
best_hz,
|
||||
best_off,
|
||||
hz0_for_cb,
|
||||
hz1_for_cb,
|
||||
params.use_osd,
|
||||
"",
|
||||
m79
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
if (params.soft_ones == 0)
|
||||
}
|
||||
|
||||
if (params.soft_ones == 0) {
|
||||
std::copy(p174, p174 + 174, ll174);
|
||||
}
|
||||
}
|
||||
|
||||
if (params.soft_triples)
|
||||
{
|
||||
float p174[174];
|
||||
soft_decode_triples(m79, p174);
|
||||
int ret = try_decode(samples200, p174, best_hz, best_off,
|
||||
hz0_for_cb, hz1_for_cb, 1, "", m79);
|
||||
int ret = try_decode(
|
||||
samples200,
|
||||
p174,
|
||||
best_hz,
|
||||
best_off,
|
||||
hz0_for_cb,
|
||||
hz1_for_cb,
|
||||
params.use_osd,
|
||||
"",
|
||||
m79
|
||||
);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
@ -3085,10 +3113,20 @@ int FT8::one_iter1(
|
||||
n174[i] = ll174[i];
|
||||
}
|
||||
}
|
||||
int ret = try_decode(samples200, n174, best_hz, best_off,
|
||||
hz0_for_cb, hz1_for_cb, 0, "hint1", m79);
|
||||
if (ret)
|
||||
{
|
||||
|
||||
int ret = try_decode(
|
||||
samples200,
|
||||
n174,
|
||||
best_hz,
|
||||
best_off,
|
||||
hz0_for_cb,
|
||||
hz1_for_cb,
|
||||
0,
|
||||
"hint1",
|
||||
m79
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@ -3120,10 +3158,20 @@ int FT8::one_iter1(
|
||||
n174[i] = ll174[i];
|
||||
}
|
||||
}
|
||||
int ret = try_decode(samples200, n174, best_hz, best_off,
|
||||
hz0_for_cb, hz1_for_cb, 0, "hint2", m79);
|
||||
if (ret)
|
||||
{
|
||||
|
||||
int ret = try_decode(
|
||||
samples200,
|
||||
n174,
|
||||
best_hz,
|
||||
best_off,
|
||||
hz0_for_cb,
|
||||
hz1_for_cb,
|
||||
0,
|
||||
"hint2",
|
||||
m79
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -154,6 +154,7 @@ struct FT8_API FT8Params
|
||||
int use_apriori;
|
||||
int use_hints; // 1 means use all hints, 2 means just CQ hints
|
||||
int win_type;
|
||||
int use_osd;
|
||||
int osd_depth; // 6; // don't increase beyond 6, produces too much garbage
|
||||
int osd_ldpc_thresh; // demand this many correct LDPC parity bits before OSD
|
||||
int ncoarse; // number of offsets per hz produced by coarse()
|
||||
@ -222,6 +223,7 @@ struct FT8_API FT8Params
|
||||
use_apriori = 1;
|
||||
use_hints = 2; // 1 means use all hints, 2 means just CQ hints
|
||||
win_type = 1;
|
||||
use_osd = 1;
|
||||
osd_depth = 0; // 6; // don't increase beyond 6, produces too much garbage
|
||||
osd_ldpc_thresh = 70; // demand this many correct LDPC parity bits before OSD
|
||||
ncoarse = 1; // number of offsets per hz produced by coarse()
|
||||
@ -563,7 +565,7 @@ public:
|
||||
//
|
||||
// XXX merge with one_iter().
|
||||
//
|
||||
int one(const std::vector<std::complex<float>> &bins, int len, float hz, int off);
|
||||
int one_merge(const std::vector<std::complex<float>> &bins, int len, float hz, int off);
|
||||
// return 2 if it decodes to a brand-new message.
|
||||
// return 1 if it decodes but we've already seen it,
|
||||
// perhaps in a different pass.
|
||||
|
61
ft8/osd.cpp
61
ft8/osd.cpp
@ -34,7 +34,7 @@ namespace FT8 {
|
||||
//
|
||||
// check the FT8 CRC-14
|
||||
//
|
||||
int check_crc(const int a91[91])
|
||||
int OSD::check_crc(const int a91[91])
|
||||
{
|
||||
int aa[91];
|
||||
int non_zero = 0;
|
||||
@ -73,7 +73,7 @@ int check_crc(const int a91[91])
|
||||
// plain is 91 bits of plain-text.
|
||||
// returns a 174-bit codeword.
|
||||
// mimics wsjt-x's encode174_91.f90.
|
||||
void ldpc_encode(int plain[91], int codeword[174])
|
||||
void OSD::ldpc_encode(int plain[91], int codeword[174])
|
||||
{
|
||||
// the systematic 91 bits.
|
||||
for (int i = 0; i < 91; i++)
|
||||
@ -97,7 +97,7 @@ void ldpc_encode(int plain[91], int codeword[174])
|
||||
// ll174 is what was received.
|
||||
// ldpc-encode xplain; how close is the
|
||||
// result to what we received?
|
||||
float osd_score(int xplain[91], float ll174[174])
|
||||
float OSD::osd_score(int xplain[91], float ll174[174])
|
||||
{
|
||||
int xcode[174];
|
||||
ldpc_encode(xplain, xcode);
|
||||
@ -121,7 +121,7 @@ float osd_score(int xplain[91], float ll174[174])
|
||||
}
|
||||
|
||||
// does a decode look plausible?
|
||||
int osd_check(const int plain[91])
|
||||
int OSD::osd_check(const int plain[91])
|
||||
{
|
||||
int allzero = 1;
|
||||
for (int i = 0; i < 91; i++)
|
||||
@ -144,7 +144,7 @@ int osd_check(const int plain[91])
|
||||
return 1;
|
||||
}
|
||||
|
||||
void matmul(int a[91][91], int b[91], int c[91])
|
||||
void OSD::matmul(int a[91][91], int b[91], int c[91])
|
||||
{
|
||||
for (int i = 0; i < 91; i++)
|
||||
{
|
||||
@ -164,10 +164,11 @@ void matmul(int a[91][91], int b[91], int c[91])
|
||||
// first 91 bits are plaintext, remaining 83 are parity.
|
||||
// returns 0 or 1, with decoded plain bits in out91[].
|
||||
// and actual depth used in *out_depth.
|
||||
int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
int OSD::osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
{
|
||||
// strength = abs(codeword)
|
||||
float strength[174];
|
||||
|
||||
for (int i = 0; i < 174; i++)
|
||||
{
|
||||
float x = codeword[i];
|
||||
@ -176,58 +177,62 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
|
||||
// sort, strongest first; we'll use strongest 91.
|
||||
std::vector<int> which(174);
|
||||
for (int i = 0; i < 174; i++)
|
||||
|
||||
for (int i = 0; i < 174; i++) {
|
||||
which[i] = i;
|
||||
std::sort(which.begin(),
|
||||
which.end(),
|
||||
[=](int a, int b)
|
||||
{
|
||||
return strength[a] > strength[b];
|
||||
});
|
||||
}
|
||||
|
||||
std::sort(
|
||||
which.begin(),
|
||||
which.end(),
|
||||
[=](int a, int b) {
|
||||
return strength[a] > strength[b];
|
||||
}
|
||||
);
|
||||
|
||||
// gen_sys[174 rows][91 cols] has a row per each of the 174 codeword bits,
|
||||
// indicating how to generate it by xor with each of the 91 plain bits.
|
||||
|
||||
// generator matrix, reordered strongest codeword bit first.
|
||||
int b[174][91 * 2];
|
||||
|
||||
for (int i = 0; i < 174; i++)
|
||||
{
|
||||
int ii = which[i];
|
||||
|
||||
for (int j = 0; j < 91 * 2; j++)
|
||||
{
|
||||
if (j < 91)
|
||||
{
|
||||
if (j < 91) {
|
||||
b[i][j] = gen_sys[ii][j];
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
b[i][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int xwhich[174];
|
||||
for (int i = 0; i < 174; i++)
|
||||
|
||||
for (int i = 0; i < 174; i++) {
|
||||
xwhich[i] = which[i];
|
||||
}
|
||||
|
||||
int ok = 0;
|
||||
gauss_jordan(91, 174, b, xwhich, &ok);
|
||||
if (ok == 0)
|
||||
{
|
||||
|
||||
if (ok == 0) {
|
||||
fprintf(stderr, "gauss_jordan failed\n");
|
||||
}
|
||||
|
||||
int gen1_inv[91][91];
|
||||
|
||||
for (int i = 0; i < 91; i++)
|
||||
{
|
||||
for (int j = 0; j < 91; j++)
|
||||
{
|
||||
for (int j = 0; j < 91; j++) {
|
||||
gen1_inv[i][j] = b[i][91 + j];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 174; i++)
|
||||
{
|
||||
for (int i = 0; i < 174; i++) {
|
||||
which[i] = xwhich[i];
|
||||
}
|
||||
|
||||
@ -235,6 +240,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
// more or less strongest-first, converted from
|
||||
// log-likihood to 0/1.
|
||||
int y1[91];
|
||||
|
||||
for (int i = 0; i < 91; i++)
|
||||
{
|
||||
int j = which[i];
|
||||
@ -251,9 +257,9 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
matmul(gen1_inv, y1, xplain); // also does mod 2
|
||||
|
||||
int osd_thresh = -500;
|
||||
|
||||
float xscore = osd_score(xplain, codeword);
|
||||
int ch = osd_check(xplain);
|
||||
|
||||
if (xscore < osd_thresh && ch)
|
||||
{
|
||||
if (got_a_best == 0 || xscore < best_score)
|
||||
@ -284,6 +290,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
y1[i] ^= 1;
|
||||
float xscore = osd_score(xplain, codeword);
|
||||
int ch = osd_check(xplain);
|
||||
|
||||
if (xscore < osd_thresh && ch)
|
||||
{
|
||||
if (got_a_best == 0 || xscore < best_score)
|
||||
@ -308,7 +315,7 @@ int osd_decode(float codeword[174], int depth, int out[91], int *out_depth)
|
||||
}
|
||||
}
|
||||
|
||||
int gen_sys[174][91] = {
|
||||
const int OSD::gen_sys[174][91] = {
|
||||
{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
|
||||
{ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, },
|
||||
|
18
ft8/osd.h
18
ft8/osd.h
@ -23,13 +23,17 @@
|
||||
|
||||
namespace FT8 {
|
||||
|
||||
extern int gen_sys[174][91];
|
||||
int check_crc(const int a91[91]);
|
||||
void ldpc_encode(int plain[91], int codeword[174]);
|
||||
float osd_score(int xplain[91], float ll174[174]);
|
||||
int osd_check(const int plain[91]);
|
||||
void matmul(int a[91][91], int b[91], int c[91]);
|
||||
int osd_decode(float codeword[174], int depth, int out[91], int *out_depth);
|
||||
class OSD
|
||||
{
|
||||
public:
|
||||
static const int gen_sys[174][91];
|
||||
static int check_crc(const int a91[91]);
|
||||
static void ldpc_encode(int plain[91], int codeword[174]);
|
||||
static float osd_score(int xplain[91], float ll174[174]);
|
||||
static int osd_check(const int plain[91]);
|
||||
static void matmul(int a[91][91], int b[91], int c[91]);
|
||||
static int osd_decode(float codeword[174], int depth, int out[91], int *out_depth);
|
||||
};
|
||||
|
||||
} // namepsace FT8
|
||||
|
||||
|
@ -245,6 +245,18 @@ void FT8DemodBaseband::applySettings(const FT8DemodSettings& settings, bool forc
|
||||
m_ft8DemodWorker->setDecoderTimeBudget(settings.m_decoderTimeBudget);
|
||||
}
|
||||
|
||||
if ((settings.m_useOSD != m_settings.m_useOSD) || force) {
|
||||
m_ft8DemodWorker->setUseOSD(settings.m_useOSD);
|
||||
}
|
||||
|
||||
if ((settings.m_osdDepth != m_settings.m_osdDepth) || force) {
|
||||
m_ft8DemodWorker->setOSDDepth(settings.m_osdDepth);
|
||||
}
|
||||
|
||||
if ((settings.m_osdLDPCThreshold != m_settings.m_osdLDPCThreshold) || force) {
|
||||
m_ft8DemodWorker->setOSDLDPCThreshold(settings.m_osdLDPCThreshold);
|
||||
}
|
||||
|
||||
m_sink.applySettings(settings, force);
|
||||
m_settings = settings;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
// 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 "ft8demodsettings.h"
|
||||
#include "ft8demodfilterproxy.h"
|
||||
|
||||
FT8DemodFilterProxy::FT8DemodFilterProxy(QObject *parent) :
|
||||
@ -56,6 +57,13 @@ void FT8DemodFilterProxy::setFilterLoc(const QString& locString)
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void FT8DemodFilterProxy::setFilterInfo(const QString& infoString)
|
||||
{
|
||||
m_filterActive = FILTER_INFO;
|
||||
m_info= infoString;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool FT8DemodFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
if (m_filterActive == FILTER_NONE) {
|
||||
@ -64,30 +72,42 @@ bool FT8DemodFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sou
|
||||
|
||||
if (m_filterActive == FILTER_UTC)
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_UTC, sourceParent);
|
||||
return sourceModel()->data(index).toString() == m_utc;
|
||||
}
|
||||
|
||||
if (m_filterActive == FILTER_DF)
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 4, sourceParent);
|
||||
QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_DF, sourceParent);
|
||||
int df = sourceModel()->data(index).toInt();
|
||||
return (df >= m_df - 4) && (df <= m_df + 4); // +/- 4 Hz tolerance which is about one symbol width
|
||||
}
|
||||
|
||||
if (m_filterActive == FILTER_CALL)
|
||||
{
|
||||
QModelIndex indexCall1 = sourceModel()->index(sourceRow, 6, sourceParent);
|
||||
QModelIndex indexCall2 = sourceModel()->index(sourceRow, 7, sourceParent);
|
||||
QModelIndex indexCall1 = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_CALL1, sourceParent);
|
||||
QModelIndex indexCall2 = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_CALL2, sourceParent);
|
||||
return (sourceModel()->data(indexCall1).toString() == m_call) ||
|
||||
(sourceModel()->data(indexCall2).toString() == m_call);
|
||||
}
|
||||
|
||||
if (m_filterActive == FILTER_LOC)
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 8, sourceParent);
|
||||
QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_LOC, sourceParent);
|
||||
return sourceModel()->data(index).toString() == m_loc;
|
||||
}
|
||||
|
||||
if (m_filterActive == FILTER_INFO)
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, FT8DemodSettings::MESSAGE_COL_INFO, sourceParent);
|
||||
const QString& content = sourceModel()->data(index).toString();
|
||||
|
||||
if (m_info.startsWith("OSD")) {
|
||||
return content.startsWith("OSD");
|
||||
} else {
|
||||
return !content.startsWith("OSD");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ public:
|
||||
void setFilterDf(int df);
|
||||
void setFilterCall(const QString& utcString);
|
||||
void setFilterLoc(const QString& utcString);
|
||||
void setFilterInfo(const QString& infoString);
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
@ -41,7 +42,8 @@ private:
|
||||
FILTER_UTC,
|
||||
FILTER_DF,
|
||||
FILTER_CALL,
|
||||
FILTER_LOC
|
||||
FILTER_LOC,
|
||||
FILTER_INFO
|
||||
};
|
||||
|
||||
bool dfInRange(int df) const;
|
||||
@ -50,6 +52,7 @@ private:
|
||||
int m_df;
|
||||
QString m_call;
|
||||
QString m_loc;
|
||||
QString m_info;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FT8DEMODFILTERPROXY_H
|
||||
|
@ -475,6 +475,24 @@ void FT8DemodGUI::on_settings_clicked()
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("useOSD"))
|
||||
{
|
||||
m_settings.m_useOSD = settings.m_useOSD;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("osdDepth"))
|
||||
{
|
||||
m_settings.m_osdDepth = settings.m_osdDepth;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("osdLDPCThreshold"))
|
||||
{
|
||||
m_settings.m_osdLDPCThreshold = settings.m_osdLDPCThreshold;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (settingsKeys.contains("bandPresets"))
|
||||
{
|
||||
m_settings.m_bandPresets = settings.m_bandPresets;
|
||||
@ -907,6 +925,8 @@ void FT8DemodGUI::filterMessages()
|
||||
m_messagesFilterProxy.setFilterUTC(m_selectedData.toString());
|
||||
} else if (m_selectedColumn == FT8DemodSettings::MESSAGE_COL_DF) {
|
||||
m_messagesFilterProxy.setFilterDf(m_selectedData.toInt());
|
||||
} else if (m_selectedColumn == FT8DemodSettings::MESSAGE_COL_INFO) {
|
||||
m_messagesFilterProxy.setFilterInfo(m_selectedData.toString());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -823,7 +823,7 @@
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Total number of decodes displayed</string>
|
||||
<string>Total number of decodes since start</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
|
@ -47,6 +47,9 @@ void FT8DemodSettings::resetToDefaults()
|
||||
m_logMessages = false;
|
||||
m_nbDecoderThreads = 3;
|
||||
m_decoderTimeBudget = 0.5;
|
||||
m_useOSD = false;
|
||||
m_osdDepth = 0;
|
||||
m_osdLDPCThreshold = 70;
|
||||
m_volume = 1.0;
|
||||
m_inputFrequencyOffset = 0;
|
||||
m_rgbColor = QColor(0, 192, 255).rgb();
|
||||
@ -107,6 +110,9 @@ QByteArray FT8DemodSettings::serialize() const
|
||||
s.writeS32(8, m_nbDecoderThreads);
|
||||
s.writeFloat(9, m_decoderTimeBudget);
|
||||
s.writeBool(11, m_agc);
|
||||
s.writeBool(12, m_useOSD);
|
||||
s.writeS32(13, m_osdDepth);
|
||||
s.writeS32(14, m_osdLDPCThreshold);
|
||||
s.writeString(16, m_title);
|
||||
s.writeBool(18, m_useReverseAPI);
|
||||
s.writeString(19, m_reverseAPIAddress);
|
||||
@ -172,6 +178,9 @@ bool FT8DemodSettings::deserialize(const QByteArray& data)
|
||||
d.readS32(8, &m_nbDecoderThreads, 3);
|
||||
d.readFloat(9, &m_decoderTimeBudget, 0.5);
|
||||
d.readBool(11, &m_agc, false);
|
||||
d.readBool(12, &m_useOSD, false);
|
||||
d.readS32(13, &m_osdDepth, 0);
|
||||
d.readS32(14, &m_osdLDPCThreshold, 70);
|
||||
d.readString(16, &m_title, "SSB Demodulator");
|
||||
d.readBool(18, &m_useReverseAPI, false);
|
||||
d.readString(19, &m_reverseAPIAddress, "127.0.0.1");
|
||||
|
@ -72,6 +72,9 @@ struct FT8DemodSettings
|
||||
bool m_logMessages;
|
||||
int m_nbDecoderThreads;
|
||||
float m_decoderTimeBudget;
|
||||
bool m_useOSD;
|
||||
int m_osdDepth;
|
||||
int m_osdLDPCThreshold;
|
||||
quint32 m_rgbColor;
|
||||
QString m_title;
|
||||
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
|
||||
|
@ -29,6 +29,11 @@ FT8DemodSettingsDialog::FT8DemodSettingsDialog(FT8DemodSettings& settings, QStri
|
||||
ui->setupUi(this);
|
||||
ui->decoderNbThreads->setValue(m_settings.m_nbDecoderThreads);
|
||||
ui->decoderTimeBudget->setValue(m_settings.m_decoderTimeBudget);
|
||||
ui->osdEnable->setChecked(m_settings.m_useOSD);
|
||||
ui->osdDepth->setValue(m_settings.m_osdDepth);
|
||||
ui->osdDepthText->setText(tr("%1").arg(m_settings.m_osdDepth));
|
||||
ui->osdLDPCThreshold->setValue(m_settings.m_osdLDPCThreshold);
|
||||
ui->osdLDPCThresholdText->setText(tr("%1").arg(m_settings.m_osdLDPCThreshold));
|
||||
resizeBandsTable();
|
||||
populateBandsTable();
|
||||
connect(ui->bands, &QTableWidget::cellChanged, this, &FT8DemodSettingsDialog::textCellChanged);
|
||||
@ -118,6 +123,35 @@ void FT8DemodSettingsDialog::on_decoderTimeBudget_valueChanged(double value)
|
||||
}
|
||||
}
|
||||
|
||||
void FT8DemodSettingsDialog::on_osdEnable_toggled(bool checked)
|
||||
{
|
||||
m_settings.m_useOSD = checked;
|
||||
|
||||
if (!m_settingsKeys.contains("useOSD")) {
|
||||
m_settingsKeys.append("useOSD");
|
||||
}
|
||||
}
|
||||
|
||||
void FT8DemodSettingsDialog::on_osdDepth_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_osdDepth = value;
|
||||
ui->osdDepthText->setText(tr("%1").arg(m_settings.m_osdDepth));
|
||||
|
||||
if (!m_settingsKeys.contains("osdDepth")) {
|
||||
m_settingsKeys.append("osdDepth");
|
||||
}
|
||||
}
|
||||
|
||||
void FT8DemodSettingsDialog::on_osdLDPCThreshold_valueChanged(int value)
|
||||
{
|
||||
m_settings.m_osdLDPCThreshold = value;
|
||||
ui->osdLDPCThresholdText->setText(tr("%1").arg(m_settings.m_osdLDPCThreshold));
|
||||
|
||||
if (!m_settingsKeys.contains("osdLDPCThreshold")) {
|
||||
m_settingsKeys.append("osdLDPCThreshold");
|
||||
}
|
||||
}
|
||||
|
||||
void FT8DemodSettingsDialog::on_addBand_clicked()
|
||||
{
|
||||
int currentRow = ui->bands->currentRow();
|
||||
|
@ -51,6 +51,9 @@ private slots:
|
||||
void reject();
|
||||
void on_decoderNbThreads_valueChanged(int value);
|
||||
void on_decoderTimeBudget_valueChanged(double value);
|
||||
void on_osdEnable_toggled(bool checked);
|
||||
void on_osdDepth_valueChanged(int value);
|
||||
void on_osdLDPCThreshold_valueChanged(int value);
|
||||
void on_addBand_clicked();
|
||||
void on_deleteBand_clicked();
|
||||
void on_moveBandUp_clicked();
|
||||
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>349</width>
|
||||
<height>316</height>
|
||||
<height>333</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
@ -25,11 +25,8 @@
|
||||
<property name="title">
|
||||
<string>Decoder</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="verticalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="threadsGroup">
|
||||
<item>
|
||||
<widget class="QLabel" name="decoderNbThreadsLabel">
|
||||
@ -98,6 +95,124 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="osdLayout">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="osdEnable">
|
||||
<property name="toolTip">
|
||||
<string>Toggle Ordered Statistics Decoding</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>OSD</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="osdDepthlLabel">
|
||||
<property name="text">
|
||||
<string>Depth</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="osdDepth">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Order Statistics Decoding depth</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>6</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="osdDepthText">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="osdLDPCThresholdLabel">
|
||||
<property name="text">
|
||||
<string>LDPC Thr</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDial" name="osdLDPCThreshold">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>24</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Number of correct LDPC bits required to activate OSD </string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>60</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>83</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>70</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="osdLDPCThresholdText">
|
||||
<property name="text">
|
||||
<string>60</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@ -282,6 +397,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
|
@ -110,6 +110,9 @@ FT8DemodWorker::FT8DemodWorker() :
|
||||
m_recordSamples(false),
|
||||
m_nbDecoderThreads(6),
|
||||
m_decoderTimeBudget(0.5),
|
||||
m_useOSD(false),
|
||||
m_osdDepth(0),
|
||||
m_osdLDPCThreshold(70),
|
||||
m_lowFreq(200),
|
||||
m_highFreq(3000),
|
||||
m_invalidSequence(true),
|
||||
@ -157,6 +160,9 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
|
||||
int hints[2] = { 2, 0 }; // CQ
|
||||
FT8Callback ft8Callback(periodTS, m_baseFrequency, m_packing, channelReference);
|
||||
m_ft8Decoder.getParams().nthreads = m_nbDecoderThreads;
|
||||
m_ft8Decoder.getParams().use_osd = m_useOSD ? 1 : 0;
|
||||
m_ft8Decoder.getParams().osd_depth = m_osdDepth;
|
||||
m_ft8Decoder.getParams().osd_ldpc_thresh = m_osdLDPCThreshold;
|
||||
std::vector<float> samples(15*FT8DemodSettings::m_ft8SampleRate);
|
||||
|
||||
std::transform(
|
||||
@ -217,7 +223,7 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
|
||||
continue;
|
||||
}
|
||||
|
||||
QString logMessage = QString("%1 %2 Rx FT8 %3 %4 %5 %6 %7 %8")
|
||||
QString logMessage = QString("%1 %2 Rx FT8 %3 %4 %5 %6 %7 %8 %9")
|
||||
.arg(periodTS.toString("yyyyMMdd_HHmmss"))
|
||||
.arg(baseFrequencyMHz, 9, 'f', 3)
|
||||
.arg(ft8Message.snr, 6)
|
||||
@ -225,7 +231,8 @@ void FT8DemodWorker::processBuffer(int16_t *buffer, QDateTime periodTS)
|
||||
.arg(ft8Message.df, 4, 'f', 0)
|
||||
.arg(ft8Message.call1)
|
||||
.arg(ft8Message.call2)
|
||||
.arg(ft8Message.loc);
|
||||
.arg(ft8Message.loc)
|
||||
.arg(ft8Message.decoderInfo);
|
||||
logMessage.remove(0, 2);
|
||||
logFile << logMessage.toStdString() << std::endl;
|
||||
}
|
||||
|
@ -40,6 +40,9 @@ public:
|
||||
void setLogMessages(bool logMessages) { m_logMessages = logMessages; }
|
||||
void setNbDecoderThreads(int nbDecoderThreads) { m_nbDecoderThreads = nbDecoderThreads; }
|
||||
void setDecoderTimeBudget(float decoderTimeBudget) { m_decoderTimeBudget = decoderTimeBudget; }
|
||||
void setUseOSD(bool useOSD) { m_useOSD = useOSD; }
|
||||
void setOSDDepth(int osdDepth) { m_osdDepth = osdDepth; }
|
||||
void setOSDLDPCThreshold(int osdLDPCThreshold) { m_osdLDPCThreshold = osdLDPCThreshold; }
|
||||
void setLowFrequency(int lowFreq) { m_lowFreq = lowFreq; }
|
||||
void setHighFrequency(int highFreq) { m_highFreq = highFreq; }
|
||||
void setReportingMessageQueue(MessageQueue *messageQueue) { m_reportingMessageQueue = messageQueue; }
|
||||
@ -86,6 +89,9 @@ private:
|
||||
bool m_logMessages;
|
||||
int m_nbDecoderThreads;
|
||||
float m_decoderTimeBudget;
|
||||
bool m_useOSD;
|
||||
int m_osdDepth;
|
||||
int m_osdLDPCThreshold;
|
||||
int m_lowFreq;
|
||||
int m_highFreq;
|
||||
bool m_invalidSequence;
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
This plugin can be used to demodulate and decode FT8 signals. FT8 is used by amateur radio to perform contacts (QSOs) with very weak signals. It is used mostly but not limited to HF bands. The protocol and modulation details are described [here](http://www.rtmrtm.org/basicft8/)
|
||||
This plugin can be used to demodulate and decode FT8 signals. FT8 is used by amateur radio to perform contacts (QSOs) with very weak signals. It is used mostly but not limited to HF bands. The protocol and modulation details are described in [QEX July/August 2020 article](https://www.iz3mez.it/wp-content/library/appunti/Protocols%20FT4%20FT8%20QEX.pdf)
|
||||
|
||||
The decoder code is based on [ft8mon](https://github.com/rtmrtmrtmrtm/ft8mon) code written by Robert Morris, AB1HL. The core of the decoder is stored in a separate `libft8` library in the `ft8` folder of this repository and is placed under the `FT8` namespace.
|
||||
|
||||
@ -124,6 +124,7 @@ Toggles the filtering of messages. Messages are filtered based on the selected c
|
||||
- **Call1**: will filter messages matching the call1 area value either in the call1 or call2 areas
|
||||
- **Call2**: same as above but taking the call2 value
|
||||
- **Loc**: will filter messages matching the value in the locator (loc) area
|
||||
- **Info**: will filter values starting with "OSD" or not starting with "OSD" thus filter messages decoded via OSD or not
|
||||
|
||||
<h3>C.5: Band preset selection</h3>
|
||||
|
||||
@ -139,7 +140,26 @@ Empties the message table (C.10)
|
||||
|
||||
<h3>C.8: Log messages</h3>
|
||||
|
||||
Toggles the logging of messages. Messages will be logged in the same format as the original WSJT-X format. The splitting and naming of files is different however.
|
||||
Toggles the logging of messages. Messages will be logged in the same format as the original WSJT-X format except if the message has decoder information in which case this information is appended at the end of the line.
|
||||
|
||||
Example:
|
||||
|
||||
<code><pre>
|
||||
230128_003030 10.136 Rx FT8 -22 0.1 2049 KE0DKZ W2ZI EL99 OSD-0-71
|
||||
---- 1 ------ --2--- -3- -4- --5- --6--- --7- --8- ---9----
|
||||
</pre></code>
|
||||
|
||||
- **1**: Date and time of decoder slot in YYMMDD_HHmmss fomat
|
||||
- **2**: Base frequency in MHz
|
||||
- **3**: SNR in 2.5 kHz bandwidth
|
||||
- **4**: Message start delay in seconds from standard sequence start
|
||||
- **5**: Message carrier shift from base frequency in Hz
|
||||
- **6**: First callsign area. May contain spaces (ex: "CQ DX")
|
||||
- **7**: Second callsign area
|
||||
- **8**: Locator area
|
||||
- **9**: Decoder information if any
|
||||
|
||||
The splitting and naming of files is different from WSJT-X scheme.
|
||||
|
||||
The file name is constructed as follows where date is the day date in YYYYMMDD format:
|
||||
|
||||
@ -171,7 +191,7 @@ Displays the received messages in a table which columns are the following:
|
||||
- **Call1**: This is the first call area and may contain the caller callsign, a CQ or a custom 13 character message in which case the second call and locator areas are empty
|
||||
- **Call2**: This is the second call area and will contain the callsign of the responding station
|
||||
- **Loc**: Locator area which contains the 4 character Maidenhead locator, a report, an acknowledgement (RRR) or a greetings (73 or RR73)
|
||||
- **Info**: FT8 decoder information if any. This comes from he original `ft8mon` code
|
||||
- **Info**: FT8 decoder information if any. If OSD is active (see C.1.3) and OSD was activated it reports the OSD decoder status as `OSD-N-MM` where N is the OSD depth reached and MM is the number of correct LDPC bits.
|
||||
|
||||
<h3>C.1: More FT8 decoder settings</h2>
|
||||
|
||||
@ -187,7 +207,21 @@ When processing the audio baseband several instances of the core decoder will be
|
||||
|
||||
This is the time in seconds after which the decoder threads will be prompted to stop. It can be varied from 0.1 to 5 seconds. You can imagine that the longer the decoder runs the more messages it will harvest however the default of 0.5s will be enough in most cases. You can still experiment with it to find what value is the best in your own case.
|
||||
|
||||
<h4>C.1.3: Band presets table</h4>
|
||||
<h4>C.1.3: Toggle Ordered Statistics Decoding</h4>
|
||||
|
||||
This toggles the Ordered Statistics Decoding (OSD). OSD is used if the CRC check fails after LDPC processing. Be careful with OSD as it will try to find solutions that validate the CRC but these solutions can be wrong and thus yield false messages. When post processing the results it is recommended to check against a database of valid callsigns. At least you should use a list of valid country prefixes and test the locator against the country prefix.
|
||||
|
||||
With reasonable depth (C.1.4) and minimum correct LDPC bits (C.1.5) values the amount of false messages should be low so OSD may still be interesting. However if you require best accuracy you should filter messages in a post processing step as suggested above.
|
||||
|
||||
<h4>C.1.4: Ordered Statistics Decoding depth</h4>
|
||||
|
||||
Sets the maximum depth of OSD search.
|
||||
|
||||
<h4>C.1.5: LDPC correct bits threshold</h4>
|
||||
|
||||
Sets the minimum number of correct LDPC bits (out of 83) necessary to trigger OSD.
|
||||
|
||||
<h4>C.1.6: Band presets table</h4>
|
||||
|
||||
This table shows the band presets values that will appear in (C.5)
|
||||
|
||||
@ -197,23 +231,23 @@ This table shows the band presets values that will appear in (C.5)
|
||||
|
||||
You can edit these values by clicking on the cell in the table.
|
||||
|
||||
<h4>C.1.4: Add preset</h4>
|
||||
<h4>C.1.7: Add preset</h4>
|
||||
|
||||
Use this button to create a new preset. It will take the values from the row of the selected cell in the table (if selected) and put the new preset at the bottom of the table
|
||||
|
||||
<h4>C.1.5: Delete preset</h4>
|
||||
<h4>C.1.8: Delete preset</h4>
|
||||
|
||||
Delete the preset designated by the selected cell in the table.
|
||||
|
||||
<h4>C.1.6: Move up preset</h4>
|
||||
<h4>C.1.9: Move up preset</h4>
|
||||
|
||||
Move up the preset designated by the selected cell in the table.
|
||||
|
||||
<h4>C.1.7: Move down preset</h4>
|
||||
<h4>C.1.10: Move down preset</h4>
|
||||
|
||||
Move down the preset designated by the selected cell in the table.
|
||||
|
||||
<h4>C.1.8: Restore defaults</h4>
|
||||
<h4>C.1.11: Restore defaults</h4>
|
||||
|
||||
This restores the default band preset values:
|
||||
|
||||
@ -235,10 +269,10 @@ This restores the default band preset values:
|
||||
|
||||
Channel offsets are all set to 0 kHz.
|
||||
|
||||
<h4>C.1.9 Commit changes</h4>
|
||||
<h4>C.1.12 Commit changes</h4>
|
||||
|
||||
Click on the "OK" button to commit changes and close dialog.
|
||||
|
||||
<h4>C.1.9 Cancel changes</h4>
|
||||
<h4>C.1.13 Cancel changes</h4>
|
||||
|
||||
Click on the "Cancel" button to close dialog without making changes.
|
||||
|
@ -205,6 +205,7 @@ void MainBench::testFT8(const QString& wavFile, const QString& argsStr)
|
||||
|
||||
FT8::FT8Decoder decoder;
|
||||
decoder.getParams().nthreads = nthreads;
|
||||
decoder.getParams().use_osd = 0;
|
||||
|
||||
decoder.entry(
|
||||
samples.data(),
|
||||
|
Loading…
Reference in New Issue
Block a user