Porównaj commity
81 Commity
0a5f8aa50f
...
98f5e610d7
Autor | SHA1 | Data |
---|---|---|
Edouard Griffiths | 98f5e610d7 | |
Edouard Griffiths | 631f55e9e8 | |
mxi-box | 32aee52ab3 | |
Daniele Forsi | 92246cdc71 | |
Daniele Forsi | 8711f77fdb | |
f4exb | 6351ae0655 | |
f4exb | 9c72d4fd14 | |
f4exb | 8961e1a353 | |
f4exb | 5ff2ce2575 | |
f4exb | 7c3bf5edcd | |
f4exb | cd7f0c16c4 | |
f4exb | 1691a3dfb3 | |
f4exb | 788df1eda3 | |
f4exb | 3c9f74aeb5 | |
f4exb | a02d1839ef | |
f4exb | c21223771a | |
f4exb | ba075bcf66 | |
Edouard Griffiths | b939a98f6b | |
srcejon | 9c1b1ab4f8 | |
srcejon | c1a2c5cbab | |
srcejon | 3bdba94921 | |
srcejon | 9334a7f16a | |
srcejon | b30588b28a | |
srcejon | 71f5e986ac | |
srcejon | 97cd94496a | |
srcejon | 6134a2cf78 | |
srcejon | c0e7ce4383 | |
srcejon | 2e169a1c4c | |
srcejon | 4955e6ab08 | |
Edouard Griffiths | adfb220c10 | |
RoboSchmied | 6947d892a0 | |
srcejon | c137faf012 | |
srcejon | 700d17c9cf | |
srcejon | 535f5c5e8f | |
srcejon | 35603a8c25 | |
srcejon | 879ce0e17b | |
srcejon | d84facb303 | |
srcejon | 24a3e0b477 | |
srcejon | 3c75ac4f9e | |
srcejon | b70c7dc4a3 | |
srcejon | a0b21221ae | |
srcejon | 5e15edcbcf | |
srcejon | f9b43294a8 | |
srcejon | eac144acba | |
srcejon | 6b2923358e | |
f4exb | 8e77ad01ad | |
Edouard Griffiths | 0b780b158b | |
srcejon | 4d3e9889ec | |
srcejon | 20371ae8fc | |
srcejon | 8d57e040ff | |
srcejon | 610c36004e | |
srcejon | 625513eaeb | |
nugget | 1a1623a300 | |
srcejon | ecc740b91e | |
srcejon | 20455519f0 | |
srcejon | 13ba98eb8a | |
srcejon | 198b971275 | |
srcejon | 10993b447b | |
srcejon | 2be14f944a | |
srcejon | b890c32f13 | |
srcejon | b2778d9138 | |
srcejon | 605628567b | |
srcejon | 7e859c938d | |
srcejon | 0ab0f33d00 | |
srcejon | 4c35cb90ad | |
srcejon | 655295c2c8 | |
srcejon | 49bdb88d0a | |
srcejon | 05fe7b8393 | |
srcejon | 68b833ad97 | |
srcejon | d1bfdbaa63 | |
srcejon | de4bc18337 | |
srcejon | 6db4dfa36a | |
srcejon | ff2c3e9ff7 | |
srcejon | 525f47e5e9 | |
srcejon | be554146bd | |
srcejon | b579225fcc | |
srcejon | c4e2bf2a38 | |
srcejon | 3c8f1abaee | |
srcejon | 4d69da6ec2 | |
srcejon | f77bffed3f | |
srcejon | 29ae05a494 |
|
@ -144,6 +144,7 @@ option(ENABLE_FEATURE_PERTESTER "Enable feature pertester plugin" ON)
|
|||
option(ENABLE_FEATURE_GS232CONTROLLER "Enable feature gs232controller plugin" ON)
|
||||
option(ENABLE_FEATURE_REMOTECONTROL "Enable feature remote control plugin" ON)
|
||||
option(ENABLE_FEATURE_SKYMAP "Enable feature sky map plugin" ON)
|
||||
option(ENABLE_FEATURE_SID "Enable feature sid plugin" ON)
|
||||
|
||||
# on windows always build external libraries
|
||||
if(WIN32)
|
||||
|
|
|
@ -69,6 +69,7 @@ Depends: ${shlibs:Depends},
|
|||
qtspeech5-speechd-plugin,
|
||||
pulseaudio,
|
||||
ffmpeg,
|
||||
gstreamer1.0-libav,
|
||||
qml-module-qtlocation,
|
||||
qml-module-qtpositioning,
|
||||
qml-module-qtquick-window2,
|
||||
|
|
Po Szerokość: | Wysokość: | Rozmiar: 9.5 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 641 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 26 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 935 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 16 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 39 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 18 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 578 KiB |
|
@ -7,6 +7,7 @@ set(ft8_SOURCES
|
|||
ft8.cpp
|
||||
ft8plan.cpp
|
||||
ft8plans.cpp
|
||||
ft8stats.cpp
|
||||
libldpc.cpp
|
||||
osd.cpp
|
||||
packing.cpp
|
||||
|
@ -22,6 +23,7 @@ set(ft8_HEADERS
|
|||
ft8.h
|
||||
ft8plan.h
|
||||
ft8plans.h
|
||||
ft8stats.h
|
||||
libldpc.h
|
||||
osd.h
|
||||
packing.h
|
||||
|
|
643
ft8/ft8.cpp
|
@ -30,7 +30,6 @@
|
|||
#include <stdio.h>
|
||||
// #include <assert.h>
|
||||
#include <math.h>
|
||||
#include <complex>
|
||||
#include <fftw3.h>
|
||||
#include <algorithm>
|
||||
#include <complex>
|
||||
|
@ -120,178 +119,6 @@ std::vector<float> blackmanharris(int n)
|
|||
return h;
|
||||
}
|
||||
|
||||
Stats::Stats(int how, float log_tail, float log_rate) :
|
||||
sum_(0),
|
||||
finalized_(false),
|
||||
how_(how),
|
||||
log_tail_(log_tail),
|
||||
log_rate_(log_rate)
|
||||
{}
|
||||
|
||||
void Stats::add(float x)
|
||||
{
|
||||
a_.push_back(x);
|
||||
sum_ += x;
|
||||
finalized_ = false;
|
||||
}
|
||||
|
||||
void Stats::finalize()
|
||||
{
|
||||
finalized_ = true;
|
||||
|
||||
int n = a_.size();
|
||||
mean_ = sum_ / n;
|
||||
float var = 0;
|
||||
float bsum = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float y = a_[i] - mean_;
|
||||
var += y * y;
|
||||
bsum += fabs(y);
|
||||
}
|
||||
|
||||
var /= n;
|
||||
stddev_ = sqrt(var);
|
||||
b_ = bsum / n;
|
||||
|
||||
// prepare for binary search to find where values lie
|
||||
// in the distribution.
|
||||
if (how_ != 0 && how_ != 5) {
|
||||
std::sort(a_.begin(), a_.end());
|
||||
}
|
||||
}
|
||||
|
||||
float Stats::mean()
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
return mean_;
|
||||
}
|
||||
|
||||
float Stats::stddev()
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
return stddev_;
|
||||
}
|
||||
|
||||
// fraction of distribution that's less than x.
|
||||
// assumes normal distribution.
|
||||
// this is PHI(x), or the CDF at x,
|
||||
// or the integral from -infinity
|
||||
// to x of the PDF.
|
||||
float Stats::gaussian_problt(float x)
|
||||
{
|
||||
float SDs = (x - mean()) / stddev();
|
||||
float frac = 0.5 * (1.0 + erf(SDs / sqrt(2.0)));
|
||||
return frac;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Laplace_distribution
|
||||
// m and b from page 116 of Mark Owen's Practical Signal Processing.
|
||||
float Stats::laplace_problt(float x)
|
||||
{
|
||||
float m = mean();
|
||||
float cdf;
|
||||
|
||||
if (x < m) {
|
||||
cdf = 0.5 * exp((x - m) / b_);
|
||||
} else {
|
||||
cdf = 1.0 - 0.5 * exp(-(x - m) / b_);
|
||||
}
|
||||
|
||||
return cdf;
|
||||
}
|
||||
|
||||
// look into the actual distribution.
|
||||
float Stats::problt(float x)
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
if (how_ == 0) {
|
||||
return gaussian_problt(x);
|
||||
}
|
||||
|
||||
if (how_ == 5) {
|
||||
return laplace_problt(x);
|
||||
}
|
||||
|
||||
// binary search.
|
||||
auto it = std::lower_bound(a_.begin(), a_.end(), x);
|
||||
int i = it - a_.begin();
|
||||
int n = a_.size();
|
||||
|
||||
if (how_ == 1)
|
||||
{
|
||||
// index into the distribution.
|
||||
// works poorly for values that are off the ends
|
||||
// of the distribution, since those are all
|
||||
// mapped to 0.0 or 1.0, regardless of magnitude.
|
||||
return i / (float)n;
|
||||
}
|
||||
|
||||
if (how_ == 2)
|
||||
{
|
||||
// use a kind of logistic regression for
|
||||
// values near the edges of the distribution.
|
||||
if (i < log_tail_ * n)
|
||||
{
|
||||
float x0 = a_[(int)(log_tail_ * n)];
|
||||
float y = 1.0 / (1.0 + exp(-log_rate_ * (x - x0)));
|
||||
// y is 0..0.5
|
||||
y /= 5;
|
||||
return y;
|
||||
}
|
||||
else if (i > (1 - log_tail_) * n)
|
||||
{
|
||||
float x0 = a_[(int)((1 - log_tail_) * n)];
|
||||
float y = 1.0 / (1.0 + exp(-log_rate_ * (x - x0)));
|
||||
// y is 0.5..1
|
||||
// we want (1-log_tail)..1
|
||||
y -= 0.5;
|
||||
y *= 2;
|
||||
y *= log_tail_;
|
||||
y += (1 - log_tail_);
|
||||
return y;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
if (how_ == 3)
|
||||
{
|
||||
// gaussian for values near the edge of the distribution.
|
||||
if (i < log_tail_ * n) {
|
||||
return gaussian_problt(x);
|
||||
} else if (i > (1 - log_tail_) * n) {
|
||||
return gaussian_problt(x);
|
||||
} else {
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
if (how_ == 4)
|
||||
{
|
||||
// gaussian for values outside the distribution.
|
||||
if (x < a_[0] || x > a_.back()) {
|
||||
return gaussian_problt(x);
|
||||
} else {
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// a-priori probability of each of the 174 LDPC codeword
|
||||
// bits being one. measured from reconstructed correct
|
||||
// codewords, into ft8bits, then python bprob.py.
|
||||
|
@ -1406,6 +1233,32 @@ std::vector<std::vector<float>> FT8::un_gray_code_r(const std::vector<std::vecto
|
|||
return m79a;
|
||||
}
|
||||
|
||||
//
|
||||
// Generic Gray decoding for magnitudes (floats)
|
||||
//
|
||||
std::vector<std::vector<float>> FT8::un_gray_code_r_gen(const std::vector<std::vector<float>> &mags)
|
||||
{
|
||||
if (mags.size() == 0) {
|
||||
return mags;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> magsa(mags.size());
|
||||
int nsyms = mags.front().size();
|
||||
|
||||
for (unsigned int si = 0; si < mags.size(); si++)
|
||||
{
|
||||
magsa[si].resize(nsyms);
|
||||
|
||||
for (int bini = 0; bini < nsyms; bini++)
|
||||
{
|
||||
int grayi = bini ^ (bini >> 1);
|
||||
magsa[si][bini] = mags[si][grayi];
|
||||
}
|
||||
}
|
||||
|
||||
return magsa;
|
||||
}
|
||||
|
||||
//
|
||||
// normalize levels by windowed median.
|
||||
// this helps, but why?
|
||||
|
@ -1584,6 +1437,93 @@ std::vector<std::vector<std::complex<float>>> FT8::c_convert_to_snr(
|
|||
return n79;
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> FT8::convert_to_snr_gen(const FT8Params& params, int nbSymbolBits, const std::vector<std::vector<float>> &mags)
|
||||
{
|
||||
if (params.snr_how < 0 || params.snr_win < 0) {
|
||||
return mags;
|
||||
}
|
||||
|
||||
//
|
||||
// for each symbol time, what's its "noise" level?
|
||||
//
|
||||
std::vector<float> mm(mags.size());
|
||||
int nbSymbols = 1<<nbSymbolBits;
|
||||
|
||||
for (int si = 0; si < (int) mags.size(); si++)
|
||||
{
|
||||
std::vector<float> v(nbSymbols);
|
||||
float sum = 0.0;
|
||||
|
||||
for (int bini = 0; bini < nbSymbols; bini++)
|
||||
{
|
||||
float x = mags[si][bini];
|
||||
v[bini] = x;
|
||||
sum += x;
|
||||
}
|
||||
|
||||
if (params.snr_how != 1) {
|
||||
std::sort(v.begin(), v.end());
|
||||
}
|
||||
|
||||
int mid = nbSymbols / 2;
|
||||
|
||||
if (params.snr_how == 0) {
|
||||
// median
|
||||
mm[si] = (v[mid-1] + v[mid]) / 2;
|
||||
} else if (params.snr_how == 1) {
|
||||
mm[si] = sum / nbSymbols;
|
||||
} else if (params.snr_how == 2) {
|
||||
// all but strongest tone.
|
||||
mm[si] = std::accumulate(v.begin(), v.end() - 1, 0.0f) / (v.size() - 1);
|
||||
} else if (params.snr_how == 3) {
|
||||
mm[si] = v.front(); // weakest tone
|
||||
} else if (params.snr_how == 4) {
|
||||
mm[si] = v.back(); // strongest tone
|
||||
} else if (params.snr_how == 5) {
|
||||
mm[si] = v[v.size()-2]; // second-strongest tone
|
||||
} else {
|
||||
mm[si] = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
// we're going to take a windowed average.
|
||||
std::vector<float> winwin;
|
||||
|
||||
if (params.snr_win > 0) {
|
||||
winwin = blackman(2 * params.snr_win + 1);
|
||||
} else {
|
||||
winwin.push_back(1.0);
|
||||
}
|
||||
|
||||
std::vector<std::vector<float>> snr(mags.size());
|
||||
|
||||
for (int si = 0; si < (int) mags.size(); si++)
|
||||
{
|
||||
float sum = 0;
|
||||
|
||||
for (int dd = si - params.snr_win; dd <= si + params.snr_win; dd++)
|
||||
{
|
||||
int wi = dd - (si - params.snr_win);
|
||||
|
||||
if (dd >= 0 && dd < (int) mags.size()) {
|
||||
sum += mm[dd] * winwin[wi];
|
||||
} else if (dd < 0) {
|
||||
sum += mm[0] * winwin[wi];
|
||||
} else {
|
||||
sum += mm[mags.size()-1] * winwin[wi];
|
||||
}
|
||||
}
|
||||
|
||||
snr[si].resize(nbSymbols);
|
||||
|
||||
for (int bi = 0; bi < nbSymbols; bi++) {
|
||||
snr[si][bi] = mags[si][bi] / sum;
|
||||
}
|
||||
}
|
||||
|
||||
return snr;
|
||||
}
|
||||
|
||||
//
|
||||
// statistics to decide soft probabilities,
|
||||
// to drive LDPC decoder.
|
||||
|
@ -1643,6 +1583,38 @@ void FT8::make_stats(
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// generalized version of the above for any number of symbols and no Costas
|
||||
// used by FT-chirp decoder
|
||||
//
|
||||
void FT8::make_stats_gen(
|
||||
const std::vector<std::vector<float>> &mags,
|
||||
int nbSymbolBits,
|
||||
Stats &bests,
|
||||
Stats &all
|
||||
)
|
||||
{
|
||||
int nbBins = 1<<nbSymbolBits;
|
||||
|
||||
for (unsigned int si = 0; si < mags.size(); si++)
|
||||
{
|
||||
float mx = 0;
|
||||
|
||||
for (int bi = 0; bi < nbBins; bi++)
|
||||
{
|
||||
float x = mags[si][bi];
|
||||
|
||||
if (x > mx) {
|
||||
mx = x;
|
||||
}
|
||||
|
||||
all.add(x);
|
||||
}
|
||||
|
||||
bests.add(mx);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// convert 79x8 complex FFT bins to magnitudes.
|
||||
//
|
||||
|
@ -1767,6 +1739,7 @@ std::vector<std::vector<float>> FT8::soft_c2m(const FFTEngine::ffts_t &c79)
|
|||
// returns log-likelihood, zero is positive, one is negative.
|
||||
//
|
||||
float FT8::bayes(
|
||||
FT8Params& params,
|
||||
float best_zero,
|
||||
float best_one,
|
||||
int lli,
|
||||
|
@ -1799,6 +1772,7 @@ float FT8::bayes(
|
|||
|
||||
// zero
|
||||
float a = pzero * bests.problt(best_zero) * (1.0 - all.problt(best_one));
|
||||
// printf("FT8::bayes: a: %f bp: %f ap: %f \n", a, bests.problt(best_zero), all.problt(best_one));
|
||||
|
||||
if (params.bayes_how == 1) {
|
||||
a *= all.problt(all.mean() + (best_zero - best_one));
|
||||
|
@ -1806,6 +1780,7 @@ float FT8::bayes(
|
|||
|
||||
// one
|
||||
float b = pone * bests.problt(best_one) * (1.0 - all.problt(best_zero));
|
||||
// printf("FT8::bayes: b: %f bp: %f ap: %f \n", b, bests.problt(best_one), all.problt(best_zero));
|
||||
|
||||
if (params.bayes_how == 1) {
|
||||
b *= all.problt(all.mean() + (best_one - best_zero));
|
||||
|
@ -1819,6 +1794,8 @@ float FT8::bayes(
|
|||
p = a / (a + b);
|
||||
}
|
||||
|
||||
// printf("FT8::bayes: all.mean: %f a: %f b: %f p: %f\n", all.mean(), a, b, p);
|
||||
|
||||
if (1 - p == 0.0) {
|
||||
ll = maxlog;
|
||||
} else {
|
||||
|
@ -1856,6 +1833,52 @@ void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[])
|
|||
m79 = un_gray_code_r(m79);
|
||||
int lli = 0;
|
||||
|
||||
// tone numbers that make second index bit zero or one.
|
||||
int zeroi[4][3];
|
||||
int onei[4][3];
|
||||
|
||||
for (int biti = 0; biti < 3; biti++)
|
||||
{
|
||||
if (biti == 0)
|
||||
{
|
||||
// high bit
|
||||
zeroi[0][0] = 0;
|
||||
zeroi[1][0] = 1;
|
||||
zeroi[2][0] = 2;
|
||||
zeroi[3][0] = 3;
|
||||
onei[0][0] = 4;
|
||||
onei[1][0] = 5;
|
||||
onei[2][0] = 6;
|
||||
onei[3][0] = 7;
|
||||
}
|
||||
|
||||
if (biti == 1)
|
||||
{
|
||||
// middle bit
|
||||
zeroi[0][1] = 0;
|
||||
zeroi[1][1] = 1;
|
||||
zeroi[2][1] = 4;
|
||||
zeroi[3][1] = 5;
|
||||
onei[0][1] = 2;
|
||||
onei[1][1] = 3;
|
||||
onei[2][1] = 6;
|
||||
onei[3][1] = 7;
|
||||
}
|
||||
|
||||
if (biti == 2)
|
||||
{
|
||||
// low bit
|
||||
zeroi[0][2] = 0;
|
||||
zeroi[1][2] = 2;
|
||||
zeroi[2][2] = 4;
|
||||
zeroi[3][2] = 6;
|
||||
onei[0][2] = 1;
|
||||
onei[1][2] = 3;
|
||||
onei[2][2] = 5;
|
||||
onei[3][2] = 7;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i79 = 0; i79 < 79; i79++)
|
||||
{
|
||||
if (i79 < 7 || (i79 >= 36 && i79 < 36 + 7) || i79 >= 72) {
|
||||
|
@ -1872,56 +1895,13 @@ void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[])
|
|||
|
||||
for (int biti = 0; biti < 3; biti++)
|
||||
{
|
||||
// tone numbers that make this bit zero or one.
|
||||
int zeroi[4];
|
||||
int onei[4];
|
||||
|
||||
if (biti == 0)
|
||||
{
|
||||
// high bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 1;
|
||||
zeroi[2] = 2;
|
||||
zeroi[3] = 3;
|
||||
onei[0] = 4;
|
||||
onei[1] = 5;
|
||||
onei[2] = 6;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
if (biti == 1)
|
||||
{
|
||||
// middle bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 1;
|
||||
zeroi[2] = 4;
|
||||
zeroi[3] = 5;
|
||||
onei[0] = 2;
|
||||
onei[1] = 3;
|
||||
onei[2] = 6;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
if (biti == 2)
|
||||
{
|
||||
// low bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 2;
|
||||
zeroi[2] = 4;
|
||||
zeroi[3] = 6;
|
||||
onei[0] = 1;
|
||||
onei[1] = 3;
|
||||
onei[2] = 5;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
// strongest tone that would make this bit be zero.
|
||||
int got_best_zero = 0;
|
||||
float best_zero = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float x = m79[i79][zeroi[i]];
|
||||
float x = m79[i79][zeroi[i][biti]];
|
||||
|
||||
if (got_best_zero == 0 || x > best_zero)
|
||||
{
|
||||
|
@ -1936,7 +1916,7 @@ void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[])
|
|||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float x = m79[i79][onei[i]];
|
||||
float x = m79[i79][onei[i][biti]];
|
||||
if (got_best_one == 0 || x > best_one)
|
||||
{
|
||||
got_best_one = 1;
|
||||
|
@ -1944,13 +1924,90 @@ void FT8::soft_decode(const FFTEngine::ffts_t &c79, float ll174[])
|
|||
}
|
||||
}
|
||||
|
||||
float ll = bayes(best_zero, best_one, lli, bests, all);
|
||||
float ll = bayes(params, best_zero, best_one, lli, bests, all);
|
||||
ll174[lli++] = ll;
|
||||
}
|
||||
}
|
||||
// assert(lli == 174);
|
||||
}
|
||||
|
||||
//
|
||||
// mags is the vector of 2^nbSymbolBits vector of magnitudes at each symbol time
|
||||
// ll174 is the resulting 174 soft bits of payload
|
||||
// used in FT-chirp modulation scheme - generalized to any number of symbol bits
|
||||
//
|
||||
void FT8::soft_decode_mags(FT8Params& params, const std::vector<std::vector<float>>& mags_, int nbSymbolBits, float ll174[])
|
||||
{
|
||||
std::vector<std::vector<float>> mags = convert_to_snr_gen(params, nbSymbolBits, mags_);
|
||||
// statistics to decide soft probabilities.
|
||||
// distribution of strongest tones, and
|
||||
// distribution of noise.
|
||||
Stats bests(params.problt_how_sig, params.log_tail, params.log_rate);
|
||||
Stats all(params.problt_how_noise, params.log_tail, params.log_rate);
|
||||
make_stats_gen(mags, nbSymbolBits, bests, all);
|
||||
mags = un_gray_code_r_gen(mags);
|
||||
int lli = 0;
|
||||
int zoX = 1<<(nbSymbolBits-1);
|
||||
int zoY = nbSymbolBits;
|
||||
int *zeroi = new int[zoX*zoY];
|
||||
int *onei = new int[zoX*zoY];
|
||||
|
||||
for (int biti = 0; biti < nbSymbolBits; biti++)
|
||||
{
|
||||
int i = biti * zoX;
|
||||
set_ones_zeroes(&onei[i], &zeroi[i], nbSymbolBits, biti);
|
||||
}
|
||||
|
||||
for (unsigned int si = 0; si < mags.size(); si++)
|
||||
{
|
||||
// for each of the symbol bits, look at the strongest tone
|
||||
// that would make it a zero, and the strongest tone that
|
||||
// would make it a one. use Bayes to decide which is more
|
||||
// likely, comparing each against the distribution of noise
|
||||
// and the distribution of strongest tones.
|
||||
// most-significant-bit first.
|
||||
for (int biti = nbSymbolBits - 1; biti >= 0; biti--)
|
||||
{
|
||||
// strongest tone that would make this bit be zero.
|
||||
int got_best_zero = 0;
|
||||
float best_zero = 0;
|
||||
|
||||
for (int i = 0; i < 1<<(nbSymbolBits-1); i++)
|
||||
{
|
||||
float x = mags[si][zeroi[i+biti*zoX]];
|
||||
// printf("FT8::soft_decode_mags:: biti: %d i: %d zeroi: %d x: %f best_zero: %f\n", biti, i, zeroi[i+biti*zoX], x, best_zero);
|
||||
|
||||
if (got_best_zero == 0 || x > best_zero)
|
||||
{
|
||||
got_best_zero = 1;
|
||||
best_zero = x;
|
||||
}
|
||||
}
|
||||
|
||||
// strongest tone that would make this bit be one.
|
||||
int got_best_one = 0;
|
||||
float best_one = 0;
|
||||
|
||||
for (int i = 0; i < 1<<(nbSymbolBits-1); i++)
|
||||
{
|
||||
float x = mags[si][onei[i+biti*zoX]];
|
||||
// printf("FT8::soft_decode_mags:: biti: %d i: %d onei: %d x: %f best_one: %f\n", biti, i, onei[i+biti*zoX], x, best_one);
|
||||
|
||||
if (got_best_one == 0 || x > best_one)
|
||||
{
|
||||
got_best_one = 1;
|
||||
best_one = x;
|
||||
}
|
||||
}
|
||||
|
||||
// printf("FT8::soft_decode_mags: biti: %d best_zero: %f best_one: %f\n", biti, best_zero, best_one);
|
||||
|
||||
float ll = bayes(params, best_zero, best_one, lli, bests, all);
|
||||
ll174[lli++] = ll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// c79 is 79x8 complex tones, before un-gray-coding.
|
||||
//
|
||||
|
@ -2046,6 +2103,52 @@ void FT8::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[])
|
|||
m79 = un_gray_code_r(m79);
|
||||
int lli = 0;
|
||||
|
||||
// tone numbers that make second index bit zero or one.
|
||||
int zeroi[4][3];
|
||||
int onei[4][3];
|
||||
|
||||
for (int biti = 0; biti < 3; biti++)
|
||||
{
|
||||
if (biti == 0)
|
||||
{
|
||||
// high bit
|
||||
zeroi[0][0] = 0;
|
||||
zeroi[1][0] = 1;
|
||||
zeroi[2][0] = 2;
|
||||
zeroi[3][0] = 3;
|
||||
onei[0][0] = 4;
|
||||
onei[1][0] = 5;
|
||||
onei[2][0] = 6;
|
||||
onei[3][0] = 7;
|
||||
}
|
||||
|
||||
if (biti == 1)
|
||||
{
|
||||
// middle bit
|
||||
zeroi[0][1] = 0;
|
||||
zeroi[1][1] = 1;
|
||||
zeroi[2][1] = 4;
|
||||
zeroi[3][1] = 5;
|
||||
onei[0][1] = 2;
|
||||
onei[1][1] = 3;
|
||||
onei[2][1] = 6;
|
||||
onei[3][1] = 7;
|
||||
}
|
||||
|
||||
if (biti == 2)
|
||||
{
|
||||
// low bit
|
||||
zeroi[0][2] = 0;
|
||||
zeroi[1][2] = 2;
|
||||
zeroi[2][2] = 4;
|
||||
zeroi[3][2] = 6;
|
||||
onei[0][2] = 1;
|
||||
onei[1][2] = 3;
|
||||
onei[2][2] = 5;
|
||||
onei[3][2] = 7;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i79 = 0; i79 < 79; i79++)
|
||||
{
|
||||
if (i79 < 7 || (i79 >= 36 && i79 < 36 + 7) || i79 >= 72) {
|
||||
|
@ -2062,56 +2165,13 @@ void FT8::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[])
|
|||
|
||||
for (int biti = 0; biti < 3; biti++)
|
||||
{
|
||||
// tone numbers that make this bit zero or one.
|
||||
int zeroi[4];
|
||||
int onei[4];
|
||||
|
||||
if (biti == 0)
|
||||
{
|
||||
// high bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 1;
|
||||
zeroi[2] = 2;
|
||||
zeroi[3] = 3;
|
||||
onei[0] = 4;
|
||||
onei[1] = 5;
|
||||
onei[2] = 6;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
if (biti == 1)
|
||||
{
|
||||
// middle bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 1;
|
||||
zeroi[2] = 4;
|
||||
zeroi[3] = 5;
|
||||
onei[0] = 2;
|
||||
onei[1] = 3;
|
||||
onei[2] = 6;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
if (biti == 2)
|
||||
{
|
||||
// low bit
|
||||
zeroi[0] = 0;
|
||||
zeroi[1] = 2;
|
||||
zeroi[2] = 4;
|
||||
zeroi[3] = 6;
|
||||
onei[0] = 1;
|
||||
onei[1] = 3;
|
||||
onei[2] = 5;
|
||||
onei[3] = 7;
|
||||
}
|
||||
|
||||
// strongest tone that would make this bit be zero.
|
||||
int got_best_zero = 0;
|
||||
float best_zero = 0;
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float x = m79[i79][zeroi[i]];
|
||||
float x = m79[i79][zeroi[i][biti]];
|
||||
|
||||
if (got_best_zero == 0 || x > best_zero)
|
||||
{
|
||||
|
@ -2126,7 +2186,7 @@ void FT8::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[])
|
|||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float x = m79[i79][onei[i]];
|
||||
float x = m79[i79][onei[i][biti]];
|
||||
|
||||
if (got_best_one == 0 || x > best_one)
|
||||
{
|
||||
|
@ -2135,13 +2195,50 @@ void FT8::c_soft_decode(const FFTEngine::ffts_t &c79x, float ll174[])
|
|||
}
|
||||
}
|
||||
|
||||
float ll = bayes(best_zero, best_one, lli, bests, all);
|
||||
float ll = bayes(params, best_zero, best_one, lli, bests, all);
|
||||
ll174[lli++] = ll;
|
||||
}
|
||||
}
|
||||
// assert(lli == 174);
|
||||
}
|
||||
|
||||
//
|
||||
// set ones and zero symbol indexes. Bit index is LSB
|
||||
//
|
||||
void FT8::set_ones_zeroes(int ones[], int zeroes[], int nbBits, int bitIndex)
|
||||
{
|
||||
int nbIndexes = 1 << (nbBits - 1);
|
||||
|
||||
if (bitIndex == 0)
|
||||
{
|
||||
for (int i = 0; i < nbIndexes; i++)
|
||||
{
|
||||
zeroes[i] = i<<1;
|
||||
ones[i] = zeroes[i] | 1;
|
||||
}
|
||||
}
|
||||
else if (bitIndex == nbBits - 1)
|
||||
{
|
||||
for (int i = 0; i < nbIndexes; i++)
|
||||
{
|
||||
zeroes[i] = i;
|
||||
ones[i] = (1<<(nbBits-1)) | zeroes[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int mask = (1<<nbBits) - 1;
|
||||
int maskLow = (1<<bitIndex) - 1;
|
||||
int maskHigh = mask ^ maskLow;
|
||||
|
||||
for (int i = 0; i < nbIndexes; i++)
|
||||
{
|
||||
zeroes[i] = (i & maskLow) + ((i & maskHigh)<<1);
|
||||
ones[i] = zeroes[i] + (1<<bitIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// turn 79 symbol numbers into 174 bits.
|
||||
// strip out the three Costas sync blocks,
|
||||
|
@ -2304,7 +2401,7 @@ void FT8::soft_decode_pairs(
|
|||
float best_zero = bitinfo[si * 3 + i].zero;
|
||||
float best_one = bitinfo[si * 3 + i].one;
|
||||
// ll174[lli++] = best_zero > best_one ? 4.99 : -4.99;
|
||||
float ll = bayes(best_zero, best_one, lli, bests, all);
|
||||
float ll = bayes(params, best_zero, best_one, lli, bests, all);
|
||||
ll174[lli++] = ll;
|
||||
}
|
||||
}
|
||||
|
@ -2468,7 +2565,7 @@ void FT8::soft_decode_triples(
|
|||
float best_zero = bitinfo[si * 3 + i].zero;
|
||||
float best_one = bitinfo[si * 3 + i].one;
|
||||
// ll174[lli++] = best_zero > best_one ? 4.99 : -4.99;
|
||||
float ll = bayes(best_zero, best_one, lli, bests, all);
|
||||
float ll = bayes(params, best_zero, best_one, lli, bests, all);
|
||||
ll174[lli++] = ll;
|
||||
}
|
||||
}
|
||||
|
@ -2496,8 +2593,14 @@ int FT8::decode(const float ll174[], int a174[], FT8Params& _params, int use_osd
|
|||
if (OSD::check_crc(a174)) {
|
||||
// success!
|
||||
return 1;
|
||||
} else {
|
||||
comment = "CRC fail";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
comment = "LDPC fail";
|
||||
}
|
||||
|
||||
if (use_osd && _params.osd_depth >= 0 && ldpc_ok >= _params.osd_ldpc_thresh)
|
||||
{
|
||||
|
@ -2512,6 +2615,10 @@ int FT8::decode(const float ll174[], int a174[], FT8Params& _params, int use_osd
|
|||
OSD::ldpc_encode(oplain, a174);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
comment = "OSD fail";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -3108,7 +3215,7 @@ int FT8::one_iter1(
|
|||
hz1_for_cb,
|
||||
params.use_osd,
|
||||
"",
|
||||
m79
|
||||
m79
|
||||
);
|
||||
|
||||
if (ret) {
|
||||
|
|
89
ft8/ft8.h
|
@ -30,6 +30,7 @@
|
|||
#include <QString>
|
||||
|
||||
#include "fft.h"
|
||||
#include "ft8stats.h"
|
||||
#include "export.h"
|
||||
|
||||
class QThread;
|
||||
|
@ -51,53 +52,6 @@ public:
|
|||
virtual QString get_name() = 0;
|
||||
};
|
||||
|
||||
//
|
||||
// manage statistics for soft decoding, to help
|
||||
// decide how likely each symbol is to be correct,
|
||||
// to drive LDPC decoding.
|
||||
//
|
||||
// meaning of the how (problt_how) parameter:
|
||||
// 0: gaussian
|
||||
// 1: index into the actual distribution
|
||||
// 2: do something complex for the tails.
|
||||
// 3: index into the actual distribution plus gaussian for tails.
|
||||
// 4: similar to 3.
|
||||
// 5: laplace
|
||||
//
|
||||
class FT8_API Stats
|
||||
{
|
||||
public:
|
||||
std::vector<float> a_;
|
||||
float sum_;
|
||||
bool finalized_;
|
||||
float mean_; // cached
|
||||
float stddev_; // cached
|
||||
float b_; // cached
|
||||
int how_;
|
||||
|
||||
public:
|
||||
Stats(int how, float log_tail, float log_rate);
|
||||
void add(float x);
|
||||
void finalize();
|
||||
float mean();
|
||||
float stddev();
|
||||
|
||||
// fraction of distribution that's less than x.
|
||||
// assumes normal distribution.
|
||||
// this is PHI(x), or the CDF at x,
|
||||
// or the integral from -infinity
|
||||
// to x of the PDF.
|
||||
float gaussian_problt(float x);
|
||||
// https://en.wikipedia.org/wiki/Laplace_distribution
|
||||
// m and b from page 116 of Mark Owen's Practical Signal Processing.
|
||||
float laplace_problt(float x);
|
||||
// look into the actual distribution.
|
||||
float problt(float x);
|
||||
|
||||
private:
|
||||
float log_tail_;
|
||||
float log_rate_;
|
||||
};
|
||||
|
||||
class FT8_API Strength
|
||||
{
|
||||
|
@ -220,8 +174,8 @@ struct FT8_API FT8Params
|
|||
third_off_win = 0.075;
|
||||
log_tail = 0.1;
|
||||
log_rate = 8.0;
|
||||
problt_how_noise = 0;
|
||||
problt_how_sig = 0;
|
||||
problt_how_noise = 0; // Gaussian
|
||||
problt_how_sig = 0; // Gaussian
|
||||
use_apriori = 1;
|
||||
use_hints = 2; // 1 means use all hints, 2 means just CQ hints
|
||||
win_type = 1;
|
||||
|
@ -320,6 +274,23 @@ public:
|
|||
// append the 83 bits to the 91 bits messag e+ crc to obbain the 174 bit payload
|
||||
static void encode(int a174[], int s77[]);
|
||||
|
||||
//
|
||||
// set ones and zero symbol indexes
|
||||
//
|
||||
static void set_ones_zeroes(int ones[], int zeroes[], int nbBits, int bitIndex);
|
||||
|
||||
//
|
||||
// mags is the vector of 2^nbSymbolBits vector of magnitudes at each symbol time
|
||||
// ll174 is the resulting 174 soft bits of payload
|
||||
// used in FT-chirp modulation scheme - generalized to any number of symbol bits
|
||||
//
|
||||
static void soft_decode_mags(FT8Params& params, const std::vector<std::vector<float>>& mags, int nbSymbolBits, float ll174[]);
|
||||
|
||||
//
|
||||
// Generic Gray decoding for magnitudes (floats)
|
||||
//
|
||||
static std::vector<std::vector<float>> un_gray_code_r_gen(const std::vector<std::vector<float>> &mags);
|
||||
|
||||
private:
|
||||
//
|
||||
// reduce the sample rate from arate to brate.
|
||||
|
@ -444,6 +415,11 @@ private:
|
|||
// normalize levels by windowed median.
|
||||
// this helps, but why?
|
||||
//
|
||||
static std::vector<std::vector<float>> convert_to_snr_gen(const FT8Params& params, int nbSymbolBits, const std::vector<std::vector<float>> &mags);
|
||||
//
|
||||
// normalize levels by windowed median.
|
||||
// this helps, but why?
|
||||
//
|
||||
std::vector<std::vector<std::complex<float>>> c_convert_to_snr(
|
||||
const std::vector<std::vector<std::complex<float>>> &m79
|
||||
);
|
||||
|
@ -453,12 +429,22 @@ private:
|
|||
// distribution of strongest tones, and
|
||||
// distribution of noise.
|
||||
//
|
||||
void make_stats(
|
||||
static void make_stats(
|
||||
const std::vector<std::vector<float>> &m79,
|
||||
Stats &bests,
|
||||
Stats &all
|
||||
);
|
||||
//
|
||||
// generalized version of the above for any number of symbols and no Costas
|
||||
// used by FT-chirp decoder
|
||||
//
|
||||
static void make_stats_gen(
|
||||
const std::vector<std::vector<float>> &mags,
|
||||
int nbSymbolBits,
|
||||
Stats &bests,
|
||||
Stats &all
|
||||
);
|
||||
//
|
||||
// convert 79x8 complex FFT bins to magnitudes.
|
||||
//
|
||||
// exploits local phase coherence by decreasing magnitudes of bins
|
||||
|
@ -477,7 +463,8 @@ private:
|
|||
//
|
||||
// returns log-likelihood, zero is positive, one is negative.
|
||||
//
|
||||
float bayes(
|
||||
static float bayes(
|
||||
FT8Params& params,
|
||||
float best_zero,
|
||||
float best_one,
|
||||
int lli,
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
|
||||
// reformatted and adapted to Qt and SDRangel context //
|
||||
// //
|
||||
// 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 <math.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ft8stats.h"
|
||||
|
||||
namespace FT8 {
|
||||
|
||||
Stats::Stats(int how, float log_tail, float log_rate) :
|
||||
sum_(0),
|
||||
finalized_(false),
|
||||
how_(how),
|
||||
log_tail_(log_tail),
|
||||
log_rate_(log_rate)
|
||||
{}
|
||||
|
||||
void Stats::add(float x)
|
||||
{
|
||||
a_.push_back(x);
|
||||
sum_ += x;
|
||||
finalized_ = false;
|
||||
}
|
||||
|
||||
void Stats::finalize()
|
||||
{
|
||||
finalized_ = true;
|
||||
|
||||
int n = a_.size();
|
||||
mean_ = sum_ / n;
|
||||
float var = 0;
|
||||
float bsum = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float y = a_[i] - mean_;
|
||||
var += y * y;
|
||||
bsum += fabs(y);
|
||||
}
|
||||
|
||||
var /= n;
|
||||
stddev_ = sqrt(var);
|
||||
b_ = bsum / n;
|
||||
|
||||
// prepare for binary search to find where values lie
|
||||
// in the distribution.
|
||||
if (how_ != 0 && how_ != 5) {
|
||||
std::sort(a_.begin(), a_.end());
|
||||
}
|
||||
}
|
||||
|
||||
float Stats::mean()
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
return mean_;
|
||||
}
|
||||
|
||||
float Stats::stddev()
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
return stddev_;
|
||||
}
|
||||
|
||||
// fraction of distribution that's less than x.
|
||||
// assumes normal distribution.
|
||||
// this is PHI(x), or the CDF at x,
|
||||
// or the integral from -infinity
|
||||
// to x of the PDF.
|
||||
float Stats::gaussian_problt(float x)
|
||||
{
|
||||
float SDs = (x - mean()) / stddev();
|
||||
float frac = 0.5 * (1.0 + erf(SDs / sqrt(2.0)));
|
||||
return frac;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Laplace_distribution
|
||||
// m and b from page 116 of Mark Owen's Practical Signal Processing.
|
||||
float Stats::laplace_problt(float x)
|
||||
{
|
||||
float m = mean();
|
||||
float cdf;
|
||||
|
||||
if (x < m) {
|
||||
cdf = 0.5 * exp((x - m) / b_);
|
||||
} else {
|
||||
cdf = 1.0 - 0.5 * exp(-(x - m) / b_);
|
||||
}
|
||||
|
||||
return cdf;
|
||||
}
|
||||
|
||||
// look into the actual distribution.
|
||||
float Stats::problt(float x)
|
||||
{
|
||||
if (!finalized_) {
|
||||
finalize();
|
||||
}
|
||||
|
||||
if (how_ == 0) {
|
||||
return gaussian_problt(x);
|
||||
}
|
||||
|
||||
if (how_ == 5) {
|
||||
return laplace_problt(x);
|
||||
}
|
||||
|
||||
// binary search.
|
||||
auto it = std::lower_bound(a_.begin(), a_.end(), x);
|
||||
int i = it - a_.begin();
|
||||
int n = a_.size();
|
||||
|
||||
if (how_ == 1)
|
||||
{
|
||||
// index into the distribution.
|
||||
// works poorly for values that are off the ends
|
||||
// of the distribution, since those are all
|
||||
// mapped to 0.0 or 1.0, regardless of magnitude.
|
||||
return i / (float)n;
|
||||
}
|
||||
|
||||
if (how_ == 2)
|
||||
{
|
||||
// use a kind of logistic regression for
|
||||
// values near the edges of the distribution.
|
||||
if (i < log_tail_ * n)
|
||||
{
|
||||
float x0 = a_[(int)(log_tail_ * n)];
|
||||
float y = 1.0 / (1.0 + exp(-log_rate_ * (x - x0)));
|
||||
// y is 0..0.5
|
||||
y /= 5;
|
||||
return y;
|
||||
}
|
||||
else if (i > (1 - log_tail_) * n)
|
||||
{
|
||||
float x0 = a_[(int)((1 - log_tail_) * n)];
|
||||
float y = 1.0 / (1.0 + exp(-log_rate_ * (x - x0)));
|
||||
// y is 0.5..1
|
||||
// we want (1-log_tail)..1
|
||||
y -= 0.5;
|
||||
y *= 2;
|
||||
y *= log_tail_;
|
||||
y += (1 - log_tail_);
|
||||
return y;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
if (how_ == 3)
|
||||
{
|
||||
// gaussian for values near the edge of the distribution.
|
||||
if (i < log_tail_ * n) {
|
||||
return gaussian_problt(x);
|
||||
} else if (i > (1 - log_tail_) * n) {
|
||||
return gaussian_problt(x);
|
||||
} else {
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
if (how_ == 4)
|
||||
{
|
||||
// gaussian for values outside the distribution.
|
||||
if (x < a_[0] || x > a_.back()) {
|
||||
return gaussian_problt(x);
|
||||
} else {
|
||||
return i / (float)n;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace FT8
|
|
@ -0,0 +1,79 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// This is the code from ft8mon: https://github.com/rtmrtmrtmrtm/ft8mon //
|
||||
// reformatted and adapted to Qt and SDRangel context //
|
||||
// //
|
||||
// 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 _ft8stats_h_
|
||||
#define _ft8stats_h_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "export.h"
|
||||
|
||||
namespace FT8 {
|
||||
//
|
||||
// manage statistics for soft decoding, to help
|
||||
// decide how likely each symbol is to be correct,
|
||||
// to drive LDPC decoding.
|
||||
//
|
||||
// meaning of the how (problt_how) parameter:
|
||||
// 0: gaussian
|
||||
// 1: index into the actual distribution
|
||||
// 2: do something complex for the tails.
|
||||
// 3: index into the actual distribution plus gaussian for tails.
|
||||
// 4: similar to 3.
|
||||
// 5: laplace
|
||||
//
|
||||
class FT8_API Stats
|
||||
{
|
||||
public:
|
||||
std::vector<float> a_;
|
||||
float sum_;
|
||||
bool finalized_;
|
||||
float mean_; // cached
|
||||
float stddev_; // cached
|
||||
float b_; // cached
|
||||
int how_;
|
||||
|
||||
public:
|
||||
Stats(int how, float log_tail, float log_rate);
|
||||
void add(float x);
|
||||
void finalize();
|
||||
float mean();
|
||||
float stddev();
|
||||
|
||||
// fraction of distribution that's less than x.
|
||||
// assumes normal distribution.
|
||||
// this is PHI(x), or the CDF at x,
|
||||
// or the integral from -infinity
|
||||
// to x of the PDF.
|
||||
float gaussian_problt(float x);
|
||||
// https://en.wikipedia.org/wiki/Laplace_distribution
|
||||
// m and b from page 116 of Mark Owen's Practical Signal Processing.
|
||||
float laplace_problt(float x);
|
||||
// look into the actual distribution.
|
||||
float problt(float x);
|
||||
|
||||
private:
|
||||
float log_tail_;
|
||||
float log_rate_;
|
||||
};
|
||||
|
||||
} //namespace FT8
|
||||
|
||||
#endif // _ft8stats_h_
|
|
@ -934,11 +934,11 @@ bool Packing::packfree(int a77[], const std::string& msg)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Packing::pack1(int a77[], int c28_1, int c28_2, int g15, int report)
|
||||
void Packing::pack1(int a77[], int c28_1, int c28_2, int g15, int reply)
|
||||
{
|
||||
pa64(a77, 0, 28, c28_1);
|
||||
pa64(a77, 28+1, 28, c28_2);
|
||||
a77[28+1+28+1] = report;
|
||||
a77[28+1+28+1] = reply;
|
||||
pa64(a77, 28+1+28+2, 15, g15);
|
||||
pa64(a77, 28+1+28+2+15, 3, 1);
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
static bool packcall_std(int& c28, const std::string& callstr);
|
||||
static bool packgrid(int& g15, const std::string& locstr);
|
||||
static bool packfree(int a77[], const std::string& msg);
|
||||
static void pack1(int a77[], int c28_1, int c28_2, int g15, int report);
|
||||
static void pack1(int a77[], int c28_1, int c28_2, int g15, int reply);
|
||||
|
||||
private:
|
||||
static int ihashcall(std::string call, int m);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 Edouard Griffiths, F4EXB //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2023-2024 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 //
|
||||
|
@ -18,8 +18,6 @@
|
|||
|
||||
#include <QDebug>
|
||||
|
||||
#include "channelpowergui.h"
|
||||
|
||||
#include "device/deviceuiset.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "dsp/dspengine.h"
|
||||
|
@ -36,6 +34,7 @@
|
|||
#include "maincore.h"
|
||||
|
||||
#include "channelpower.h"
|
||||
#include "channelpowergui.h"
|
||||
|
||||
ChannelPowerGUI* ChannelPowerGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
||||
{
|
||||
|
@ -62,11 +61,14 @@ QByteArray ChannelPowerGUI::serialize() const
|
|||
|
||||
bool ChannelPowerGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
if(m_settings.deserialize(data)) {
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
displaySettings();
|
||||
applyAllSettings();
|
||||
return true;
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
|
@ -90,8 +92,7 @@ bool ChannelPowerGUI::handleMessage(const Message& message)
|
|||
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
||||
m_deviceCenterFrequency = notif.getCenterFrequency();
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
|
||||
ui->deltaFrequencyLabel->setToolTip(tr("Range %1 %L2 Hz").arg(QChar(0xB1)).arg(m_basebandSampleRate/2));
|
||||
calcOffset();
|
||||
ui->rfBW->setValueRange(floor(log10(m_basebandSampleRate))+1, 0, m_basebandSampleRate);
|
||||
updateAbsoluteCenterFrequency();
|
||||
return true;
|
||||
|
@ -115,9 +116,23 @@ void ChannelPowerGUI::handleInputMessages()
|
|||
|
||||
void ChannelPowerGUI::channelMarkerChangedByCursor()
|
||||
{
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
applySetting("inputFrequencyOffset");
|
||||
m_settings.m_frequency = m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset;
|
||||
|
||||
qint64 value = 0;
|
||||
|
||||
if (m_settings.m_frequencyMode == ChannelPowerSettings::Offset) {
|
||||
value = m_settings.m_inputFrequencyOffset;
|
||||
} else if (m_settings.m_frequencyMode == ChannelPowerSettings::Absolute) {
|
||||
value = m_settings.m_frequency;
|
||||
}
|
||||
|
||||
ui->deltaFrequency->blockSignals(true);
|
||||
ui->deltaFrequency->setValue(value);
|
||||
ui->deltaFrequency->blockSignals(false);
|
||||
|
||||
updateAbsoluteCenterFrequency();
|
||||
applySettings({"frequency", "inputFrequencyOffset"});
|
||||
}
|
||||
|
||||
void ChannelPowerGUI::channelMarkerHighlightedByCursor()
|
||||
|
@ -127,10 +142,23 @@ void ChannelPowerGUI::channelMarkerHighlightedByCursor()
|
|||
|
||||
void ChannelPowerGUI::on_deltaFrequency_changed(qint64 value)
|
||||
{
|
||||
m_channelMarker.setCenterFrequency(value);
|
||||
qint64 offset = 0;
|
||||
|
||||
if (m_settings.m_frequencyMode == ChannelPowerSettings::Offset)
|
||||
{
|
||||
offset = value;
|
||||
m_settings.m_frequency = m_deviceCenterFrequency + offset;
|
||||
}
|
||||
else if (m_settings.m_frequencyMode == ChannelPowerSettings::Absolute)
|
||||
{
|
||||
m_settings.m_frequency = value;
|
||||
offset = m_settings.m_frequency - m_deviceCenterFrequency;
|
||||
}
|
||||
|
||||
m_channelMarker.setCenterFrequency(offset);
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
updateAbsoluteCenterFrequency();
|
||||
applySetting("inputFrequencyOffset");
|
||||
applySettings({"frequency", "inputFrequencyOffset"});
|
||||
}
|
||||
|
||||
void ChannelPowerGUI::on_rfBW_changed(qint64 value)
|
||||
|
@ -255,7 +283,6 @@ ChannelPowerGUI::ChannelPowerGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet,
|
|||
|
||||
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
|
||||
|
||||
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
|
||||
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
||||
|
||||
|
@ -334,7 +361,8 @@ void ChannelPowerGUI::displaySettings()
|
|||
|
||||
blockApplySettings(true);
|
||||
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
ui->frequencyMode->setCurrentIndex((int) m_settings.m_frequencyMode);
|
||||
on_frequencyMode_currentIndexChanged((int) m_settings.m_frequencyMode);
|
||||
|
||||
ui->rfBW->setValue(m_settings.m_rfBandwidth);
|
||||
|
||||
|
@ -427,6 +455,47 @@ void ChannelPowerGUI::tick()
|
|||
m_tickCount++;
|
||||
}
|
||||
|
||||
void ChannelPowerGUI::on_frequencyMode_currentIndexChanged(int index)
|
||||
{
|
||||
m_settings.m_frequencyMode = (ChannelPowerSettings::FrequencyMode) index;
|
||||
ui->deltaFrequency->blockSignals(true);
|
||||
|
||||
if (m_settings.m_frequencyMode == ChannelPowerSettings::Offset)
|
||||
{
|
||||
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
||||
ui->deltaFrequency->setValue(m_settings.m_inputFrequencyOffset);
|
||||
ui->deltaUnits->setText("Hz");
|
||||
}
|
||||
else if (m_settings.m_frequencyMode == ChannelPowerSettings::Absolute)
|
||||
{
|
||||
ui->deltaFrequency->setValueRange(true, 11, 0, 99999999999, 0);
|
||||
ui->deltaFrequency->setValue(m_settings.m_frequency);
|
||||
ui->deltaUnits->setText("Hz");
|
||||
}
|
||||
|
||||
ui->deltaFrequency->blockSignals(false);
|
||||
|
||||
updateAbsoluteCenterFrequency();
|
||||
applySetting("frequencyMode");
|
||||
}
|
||||
|
||||
// Calculate input frequency offset, when device center frequency changes
|
||||
void ChannelPowerGUI::calcOffset()
|
||||
{
|
||||
if (m_settings.m_frequencyMode == ChannelPowerSettings::Offset)
|
||||
{
|
||||
ui->deltaFrequency->setValueRange(false, 7, -m_basebandSampleRate/2, m_basebandSampleRate/2);
|
||||
}
|
||||
else
|
||||
{
|
||||
qint64 offset = m_settings.m_frequency - m_deviceCenterFrequency;
|
||||
m_channelMarker.setCenterFrequency(offset);
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
updateAbsoluteCenterFrequency();
|
||||
applySetting("inputFrequencyOffset");
|
||||
}
|
||||
}
|
||||
|
||||
void ChannelPowerGUI::on_clearMeasurements_clicked()
|
||||
{
|
||||
m_channelPower->resetMagLevels();
|
||||
|
@ -434,6 +503,7 @@ void ChannelPowerGUI::on_clearMeasurements_clicked()
|
|||
|
||||
void ChannelPowerGUI::makeUIConnections()
|
||||
{
|
||||
QObject::connect(ui->frequencyMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ChannelPowerGUI::on_frequencyMode_currentIndexChanged);
|
||||
QObject::connect(ui->deltaFrequency, &ValueDialZ::changed, this, &ChannelPowerGUI::on_deltaFrequency_changed);
|
||||
QObject::connect(ui->rfBW, &ValueDial::changed, this, &ChannelPowerGUI::on_rfBW_changed);
|
||||
QObject::connect(ui->pulseTH, QOverload<int>::of(&QDial::valueChanged), this, &ChannelPowerGUI::on_pulseTH_valueChanged);
|
||||
|
@ -443,5 +513,12 @@ void ChannelPowerGUI::makeUIConnections()
|
|||
|
||||
void ChannelPowerGUI::updateAbsoluteCenterFrequency()
|
||||
{
|
||||
setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
|
||||
setStatusFrequency(m_settings.m_frequency);
|
||||
if ( (m_basebandSampleRate > 1)
|
||||
&& ( (m_settings.m_inputFrequencyOffset >= m_basebandSampleRate / 2)
|
||||
|| (m_settings.m_inputFrequencyOffset < -m_basebandSampleRate / 2))) {
|
||||
setStatusText("Frequency out of band");
|
||||
} else {
|
||||
setStatusText("");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,6 +92,7 @@ private:
|
|||
void displaySettings();
|
||||
bool handleMessage(const Message& message);
|
||||
void makeUIConnections();
|
||||
void calcOffset();
|
||||
void updateAbsoluteCenterFrequency();
|
||||
void on_clearMeasurements_clicked();
|
||||
|
||||
|
@ -99,6 +100,7 @@ private:
|
|||
void enterEvent(EnterEventType*);
|
||||
|
||||
private slots:
|
||||
void on_frequencyMode_currentIndexChanged(int index);
|
||||
void on_deltaFrequency_changed(qint64 value);
|
||||
void on_rfBW_changed(qint64 value);
|
||||
void on_clearChannelPower_clicked();
|
||||
|
|
|
@ -74,16 +74,32 @@
|
|||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="deltaFrequencyLabel">
|
||||
<widget class="QComboBox" name="frequencyMode">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<width>40</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Df</string>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Select frequency entry mode.</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Δf</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>f</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
|
@ -36,6 +36,8 @@ void ChannelPowerSettings::resetToDefaults()
|
|||
m_rfBandwidth = 10000.0f;
|
||||
m_pulseThreshold= -50.0f;
|
||||
m_averagePeriodUS = 100000;
|
||||
m_frequencyMode = Offset;
|
||||
m_frequency = 0;
|
||||
m_rgbColor = QColor(102, 40, 220).rgb();
|
||||
m_title = "Channel Power";
|
||||
m_streamIndex = 0;
|
||||
|
@ -56,6 +58,8 @@ QByteArray ChannelPowerSettings::serialize() const
|
|||
s.writeFloat(2, m_rfBandwidth);
|
||||
s.writeFloat(3, m_pulseThreshold);
|
||||
s.writeS32(4, m_averagePeriodUS);
|
||||
s.writeS32(5, (int) m_frequencyMode);
|
||||
s.writeS64(6, m_frequency);
|
||||
|
||||
s.writeU32(21, m_rgbColor);
|
||||
s.writeString(22, m_title);
|
||||
|
@ -102,6 +106,8 @@ bool ChannelPowerSettings::deserialize(const QByteArray& data)
|
|||
d.readFloat(2, &m_rfBandwidth, 10000.0f);
|
||||
d.readFloat(3, &m_pulseThreshold, 50.0f);
|
||||
d.readS32(4, &m_averagePeriodUS, 100000);
|
||||
d.readS32(5, (int *) &m_frequencyMode, (int) Offset);
|
||||
d.readS64(6, &m_frequency);
|
||||
|
||||
d.readU32(21, &m_rgbColor, QColor(102, 40, 220).rgb());
|
||||
d.readString(22, &m_title, "Channel Power");
|
||||
|
@ -161,6 +167,18 @@ void ChannelPowerSettings::applySettings(const QStringList& settingsKeys, const
|
|||
if (settingsKeys.contains("averagePeriodUS")) {
|
||||
m_averagePeriodUS = settings.m_averagePeriodUS;
|
||||
}
|
||||
if (settingsKeys.contains("frequencyMode")) {
|
||||
m_frequencyMode = settings.m_frequencyMode;
|
||||
}
|
||||
if (settingsKeys.contains("frequency")) {
|
||||
m_frequency = settings.m_frequency;
|
||||
}
|
||||
if (settingsKeys.contains("rgbColor")) {
|
||||
m_rgbColor = settings.m_rgbColor;
|
||||
}
|
||||
if (settingsKeys.contains("title")) {
|
||||
m_title = settings.m_title;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI")) {
|
||||
m_useReverseAPI = settings.m_useReverseAPI;
|
||||
}
|
||||
|
@ -191,6 +209,12 @@ QString ChannelPowerSettings::getDebugString(const QStringList& settingsKeys, bo
|
|||
if (settingsKeys.contains("averagePeriodUS") || force) {
|
||||
ostr << " m_averagePeriodUS: " << m_averagePeriodUS;
|
||||
}
|
||||
if (settingsKeys.contains("frequencyMode") || force) {
|
||||
ostr << " m_frequencyMode: " << m_frequencyMode;
|
||||
}
|
||||
if (settingsKeys.contains("frequency") || force) {
|
||||
ostr << " m_frequency: " << m_frequency;
|
||||
}
|
||||
if (settingsKeys.contains("useReverseAPI") || force) {
|
||||
ostr << " m_useReverseAPI: " << m_useReverseAPI;
|
||||
}
|
||||
|
@ -200,7 +224,7 @@ QString ChannelPowerSettings::getDebugString(const QStringList& settingsKeys, bo
|
|||
if (settingsKeys.contains("reverseAPIPort") || force) {
|
||||
ostr << " m_reverseAPIPort: " << m_reverseAPIPort;
|
||||
}
|
||||
if (settingsKeys.contains("everseAPIDeviceIndex") || force) {
|
||||
if (settingsKeys.contains("reverseAPIDeviceIndex") || force) {
|
||||
ostr << " m_reverseAPIDeviceIndex: " << m_reverseAPIDeviceIndex;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 Edouard Griffiths, F4EXB. //
|
||||
// Copyright (C) 2023 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2023-2024 Jon Beniston, M7RCE //
|
||||
// //
|
||||
// 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 //
|
||||
|
@ -32,6 +32,11 @@ struct ChannelPowerSettings
|
|||
Real m_rfBandwidth;
|
||||
float m_pulseThreshold;
|
||||
int m_averagePeriodUS;
|
||||
enum FrequencyMode {
|
||||
Offset,
|
||||
Absolute
|
||||
} m_frequencyMode;
|
||||
qint64 m_frequency;
|
||||
|
||||
quint32 m_rgbColor;
|
||||
QString m_title;
|
||||
|
|
|
@ -86,7 +86,7 @@ Clicking the Display Settings button will open the Display Settings dialog, whic
|
|||
* The font used for the table.
|
||||
* Whether demodulator statistics are displayed (primarily an option for developers).
|
||||
* Whether the columns in the table are automatically resized after an aircraft is added to it. If unchecked, columns can be resized manually and should be saved with presets.
|
||||
* The transistion altitude in feet for use in ATC mode. Below the TA, altitude will be displayed. Above the TA flight levels will be displayed.
|
||||
* The transition altitude in feet for use in ATC mode. Below the TA, altitude will be displayed. Above the TA flight levels will be displayed.
|
||||
|
||||
You can also enter an [aviationstack](https://aviationstack.com/product) API key, needed to download flight information (such as departure and arrival airports and times).
|
||||
|
||||
|
|
|
@ -413,7 +413,6 @@ void AISDemodSink::applySettings(const AISDemodSettings& settings, bool force)
|
|||
m_interpolator.create(16, m_channelSampleRate, settings.m_rfBandwidth / 2.2);
|
||||
m_interpolatorDistance = (Real) m_channelSampleRate / (Real) AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
m_interpolatorDistanceRemain = m_interpolatorDistance;
|
||||
m_lowpass.create(301, AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f);
|
||||
}
|
||||
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force)
|
||||
{
|
||||
|
@ -423,7 +422,7 @@ void AISDemodSink::applySettings(const AISDemodSettings& settings, bool force)
|
|||
if ((settings.m_baud != m_settings.m_baud) || force)
|
||||
{
|
||||
m_samplesPerSymbol = AISDemodSettings::AISDEMOD_CHANNEL_SAMPLE_RATE / settings.m_baud;
|
||||
qDebug() << "ISDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol << " baud " << settings.m_baud;
|
||||
qDebug() << "AISDemodSink::applySettings: m_samplesPerSymbol: " << m_samplesPerSymbol << " baud " << settings.m_baud;
|
||||
m_pulseShape.create(0.5, 3, m_samplesPerSymbol);
|
||||
|
||||
// Recieve buffer, long enough for one max length message
|
||||
|
|
|
@ -113,7 +113,6 @@ private:
|
|||
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
|
||||
Lowpass<Complex> m_lowpass; // RF input filter
|
||||
PhaseDiscriminators m_phaseDiscri; // FM demodulator
|
||||
Gaussian<Real> m_pulseShape; // Pulse shaping filter
|
||||
Real *m_rxBuf; // Receive sample buffer, large enough for one max length messsage
|
||||
|
|
|
@ -268,7 +268,7 @@ void AMDemodGUI::channelMarkerChangedByCursor()
|
|||
}
|
||||
|
||||
m_settings.m_frequency = m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset;
|
||||
int value = 0;
|
||||
qint64 value = 0;
|
||||
|
||||
if (m_settings.m_frequencyMode == AMDemodSettings::Offset) {
|
||||
value = m_settings.m_inputFrequencyOffset;
|
||||
|
@ -292,8 +292,6 @@ void AMDemodGUI::channelMarkerChangedByCursor()
|
|||
|
||||
void AMDemodGUI::on_deltaFrequency_changed(qint64 value)
|
||||
{
|
||||
(void) value;
|
||||
|
||||
qint64 offset = 0;
|
||||
|
||||
if (m_settings.m_frequencyMode == AMDemodSettings::Offset)
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
project(chirpchat)
|
||||
|
||||
if (FT8_SUPPORT)
|
||||
set(chirpchatmod_FT8_LIB ft8)
|
||||
set(chirpchatmod_FT8_INCLUDE ${CMAKE_SOURCE_DIR}/ft8)
|
||||
endif()
|
||||
|
||||
set(chirpchat_SOURCES
|
||||
chirpchatdemod.cpp
|
||||
chirpchatdemodsettings.cpp
|
||||
|
@ -10,6 +15,7 @@ set(chirpchat_SOURCES
|
|||
chirpchatdemoddecodertty.cpp
|
||||
chirpchatdemoddecoderascii.cpp
|
||||
chirpchatdemoddecoderlora.cpp
|
||||
chirpchatdemoddecoderft.cpp
|
||||
chirpchatdemodmsg.cpp
|
||||
)
|
||||
|
||||
|
@ -22,12 +28,14 @@ set(chirpchat_HEADERS
|
|||
chirpchatdemoddecodertty.h
|
||||
chirpchatdemoddecoderascii.h
|
||||
chirpchatdemoddecoderlora.h
|
||||
chirpchatdemoddecoderft.h
|
||||
chirpchatdemodmsg.h
|
||||
chirpchatplugin.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
${chirpchatmod_FT8_INCLUDE}
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
|
@ -61,6 +69,7 @@ target_link_libraries(${TARGET_NAME}
|
|||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
swagger
|
||||
${chirpchatmod_FT8_LIB}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
||||
|
|
|
@ -44,11 +44,10 @@
|
|||
#include "maincore.h"
|
||||
|
||||
#include "chirpchatdemodmsg.h"
|
||||
#include "chirpchatdemoddecoder.h"
|
||||
#include "chirpchatdemod.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemod::MsgConfigureChirpChatDemod, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemod::MsgReportDecodeBytes, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemod::MsgReportDecodeString, Message)
|
||||
|
||||
const char* const ChirpChatDemod::m_channelIdURI = "sdrangel.channel.chirpchatdemod";
|
||||
const char* const ChirpChatDemod::m_channelId = "ChirpChatDemod";
|
||||
|
@ -57,7 +56,9 @@ ChirpChatDemod::ChirpChatDemod(DeviceAPI* deviceAPI) :
|
|||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_thread(nullptr),
|
||||
m_decoderThread(nullptr),
|
||||
m_basebandSink(nullptr),
|
||||
m_decoder(nullptr),
|
||||
m_running(false),
|
||||
m_spectrumVis(SDR_RX_SCALEF),
|
||||
m_basebandSampleRate(0),
|
||||
|
@ -122,7 +123,7 @@ void ChirpChatDemod::feed(const SampleVector::const_iterator& begin, const Sampl
|
|||
(void) pO;
|
||||
|
||||
if (m_running) {
|
||||
m_basebandSink->feed(begin, end);
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,10 +134,25 @@ void ChirpChatDemod::start()
|
|||
}
|
||||
|
||||
qDebug() << "ChirpChatDemod::start";
|
||||
m_decoderThread = new QThread(this);
|
||||
m_decoder = new ChirpChatDemodDecoder();
|
||||
m_decoder->setOutputMessageQueue(getInputMessageQueue());
|
||||
m_decoder->setNbSymbolBits(m_settings.m_spreadFactor, m_settings.m_deBits);
|
||||
m_decoder->setCodingScheme(m_settings.m_codingScheme);
|
||||
m_decoder->setLoRaHasHeader(m_settings.m_hasHeader);
|
||||
m_decoder->setLoRaHasCRC(m_settings.m_hasCRC);
|
||||
m_decoder->setLoRaParityBits(m_settings.m_nbParityBits);
|
||||
m_decoder->setLoRaPacketLength(m_settings.m_packetLength);
|
||||
m_decoder->moveToThread(m_decoderThread);
|
||||
|
||||
QObject::connect(m_decoderThread, &QThread::finished, m_decoder, &QObject::deleteLater);
|
||||
QObject::connect(m_decoderThread, &QThread::finished, m_decoderThread, &QThread::deleteLater);
|
||||
m_decoderThread->start();
|
||||
|
||||
m_thread = new QThread(this);
|
||||
m_basebandSink = new ChirpChatDemodBaseband();
|
||||
m_basebandSink->setSpectrumSink(&m_spectrumVis);
|
||||
m_basebandSink->setDecoderMessageQueue(getInputMessageQueue()); // Decoder held on the main thread
|
||||
m_basebandSink->setDecoderMessageQueue(m_decoder->getInputMessageQueue());
|
||||
m_basebandSink->moveToThread(m_thread);
|
||||
|
||||
QObject::connect(m_thread, &QThread::finished, m_basebandSink, &QObject::deleteLater);
|
||||
|
@ -167,6 +183,9 @@ void ChirpChatDemod::stop()
|
|||
m_running = false;
|
||||
m_thread->exit();
|
||||
m_thread->wait();
|
||||
m_decoderThread->exit();
|
||||
m_decoderThread->wait();
|
||||
m_decoderThread = nullptr;
|
||||
}
|
||||
|
||||
bool ChirpChatDemod::handleMessage(const Message& cmd)
|
||||
|
@ -180,29 +199,28 @@ bool ChirpChatDemod::handleMessage(const Message& cmd)
|
|||
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemodMsg::MsgDecodeSymbols::match(cmd))
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeBytes::match(cmd))
|
||||
{
|
||||
qDebug() << "ChirpChatDemod::handleMessage: MsgDecodeSymbols";
|
||||
ChirpChatDemodMsg::MsgDecodeSymbols& msg = (ChirpChatDemodMsg::MsgDecodeSymbols&) cmd;
|
||||
qDebug() << "ChirpChatDemod::handleMessage: MsgReportDecodeBytes";
|
||||
ChirpChatDemodMsg::MsgReportDecodeBytes& msg = (ChirpChatDemodMsg::MsgReportDecodeBytes&) cmd;
|
||||
m_lastMsgSignalDb = msg.getSingalDb();
|
||||
m_lastMsgNoiseDb = msg.getNoiseDb();
|
||||
m_lastMsgSyncWord = msg.getSyncWord();
|
||||
m_lastMsgTimestamp = msg.getMsgTimestamp();
|
||||
|
||||
if (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingLoRa)
|
||||
{
|
||||
m_decoder.decodeSymbols(msg.getSymbols(), m_lastMsgBytes);
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
m_lastMsgTimestamp = dt.toString(Qt::ISODateWithMs);
|
||||
m_lastMsgPacketLength = m_decoder.getPacketLength();
|
||||
m_lastMsgNbParityBits = m_decoder.getNbParityBits();
|
||||
m_lastMsgHasCRC = m_decoder.getHasCRC();
|
||||
m_lastMsgNbSymbols = m_decoder.getNbSymbols();
|
||||
m_lastMsgNbCodewords = m_decoder.getNbCodewords();
|
||||
m_lastMsgEarlyEOM = m_decoder.getEarlyEOM();
|
||||
m_lastMsgHeaderCRC = m_decoder.getHeaderCRCStatus();
|
||||
m_lastMsgHeaderParityStatus = m_decoder.getHeaderParityStatus();
|
||||
m_lastMsgPayloadCRC = m_decoder.getPayloadCRCStatus();
|
||||
m_lastMsgPayloadParityStatus = m_decoder.getPayloadParityStatus();
|
||||
m_lastMsgBytes = msg.getBytes();
|
||||
m_lastMsgPacketLength = msg.getPacketSize();
|
||||
m_lastMsgNbParityBits = msg.getNbParityBits();
|
||||
m_lastMsgHasCRC = msg.getHasCRC();
|
||||
m_lastMsgNbSymbols = msg.getNbSymbols();
|
||||
m_lastMsgNbCodewords = msg.getNbCodewords();
|
||||
m_lastMsgEarlyEOM = msg.getEarlyEOM();
|
||||
m_lastMsgHeaderCRC = msg.getHeaderCRCStatus();
|
||||
m_lastMsgHeaderParityStatus = msg.getHeaderParityStatus();
|
||||
m_lastMsgPayloadCRC = msg.getPayloadCRCStatus();
|
||||
m_lastMsgPayloadParityStatus = msg.getPayloadParityStatus();
|
||||
|
||||
QByteArray bytesCopy(m_lastMsgBytes);
|
||||
bytesCopy.truncate(m_lastMsgPacketLength);
|
||||
|
@ -215,23 +233,8 @@ bool ChirpChatDemod::handleMessage(const Message& cmd)
|
|||
m_udpSink.writeUnbuffered(bytes, m_lastMsgPacketLength);
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportDecodeBytes *msgToGUI = MsgReportDecodeBytes::create(m_lastMsgBytes);
|
||||
msgToGUI->setSyncWord(m_lastMsgSyncWord);
|
||||
msgToGUI->setSignalDb(m_lastMsgSignalDb);
|
||||
msgToGUI->setNoiseDb(m_lastMsgNoiseDb);
|
||||
msgToGUI->setPacketSize(m_lastMsgPacketLength);
|
||||
msgToGUI->setNbParityBits(m_lastMsgNbParityBits);
|
||||
msgToGUI->setHasCRC(m_lastMsgHasCRC);
|
||||
msgToGUI->setNbSymbols(m_lastMsgNbSymbols);
|
||||
msgToGUI->setNbCodewords(m_lastMsgNbCodewords);
|
||||
msgToGUI->setEarlyEOM(m_lastMsgEarlyEOM);
|
||||
msgToGUI->setHeaderParityStatus(m_lastMsgHeaderParityStatus);
|
||||
msgToGUI->setHeaderCRCStatus(m_lastMsgHeaderCRC);
|
||||
msgToGUI->setPayloadParityStatus(m_lastMsgPayloadParityStatus);
|
||||
msgToGUI->setPayloadCRCStatus(m_lastMsgPayloadCRC);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
if (getMessageQueueToGUI()) {
|
||||
getMessageQueueToGUI()->push(new ChirpChatDemodMsg::MsgReportDecodeBytes(msg)); // make a copy
|
||||
}
|
||||
|
||||
// Is this an APRS packet?
|
||||
|
@ -243,7 +246,7 @@ bool ChirpChatDemod::handleMessage(const Message& cmd)
|
|||
&& (greaterThanIdx != -1)
|
||||
&& (colonIdx != -1)
|
||||
&& ((m_lastMsgHasCRC && m_lastMsgPayloadCRC) || !m_lastMsgHasCRC)
|
||||
)
|
||||
)
|
||||
{
|
||||
QByteArray packet;
|
||||
|
||||
|
@ -289,29 +292,68 @@ bool ChirpChatDemod::handleMessage(const Message& cmd)
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeString::match(cmd))
|
||||
{
|
||||
qDebug() << "ChirpChatDemod::handleMessage: MsgReportDecodeString";
|
||||
ChirpChatDemodMsg::MsgReportDecodeString& msg = (ChirpChatDemodMsg::MsgReportDecodeString&) cmd;
|
||||
m_lastMsgSignalDb = msg.getSingalDb();
|
||||
m_lastMsgNoiseDb = msg.getNoiseDb();
|
||||
m_lastMsgSyncWord = msg.getSyncWord();
|
||||
m_lastMsgTimestamp = msg.getMsgTimestamp();
|
||||
m_lastMsgString = msg.getString();
|
||||
|
||||
if (m_settings.m_sendViaUDP)
|
||||
{
|
||||
m_decoder.decodeSymbols(msg.getSymbols(), m_lastMsgString);
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
m_lastMsgTimestamp = dt.toString(Qt::ISODateWithMs);
|
||||
const QByteArray& byteArray = m_lastMsgString.toUtf8();
|
||||
const uint8_t *bytes = reinterpret_cast<const uint8_t*>(byteArray.data());
|
||||
m_udpSink.writeUnbuffered(bytes, byteArray.size());
|
||||
}
|
||||
|
||||
if (m_settings.m_sendViaUDP)
|
||||
{
|
||||
const QByteArray& byteArray = m_lastMsgString.toUtf8();
|
||||
const uint8_t *bytes = reinterpret_cast<const uint8_t*>(byteArray.data());
|
||||
m_udpSink.writeUnbuffered(bytes, byteArray.size());
|
||||
}
|
||||
if (getMessageQueueToGUI()) {
|
||||
getMessageQueueToGUI()->push(new ChirpChatDemodMsg::MsgReportDecodeString(msg)); // make a copy
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI())
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeFT::match(cmd))
|
||||
{
|
||||
qDebug() << "ChirpChatDemod::handleMessage: MsgReportDecodeFT";
|
||||
ChirpChatDemodMsg::MsgReportDecodeFT& msg = (ChirpChatDemodMsg::MsgReportDecodeFT&) cmd;
|
||||
m_lastMsgSignalDb = msg.getSingalDb();
|
||||
m_lastMsgNoiseDb = msg.getNoiseDb();
|
||||
m_lastMsgSyncWord = msg.getSyncWord();
|
||||
m_lastMsgTimestamp = msg.getMsgTimestamp();
|
||||
m_lastMsgString = msg.getMessage(); // for now we do not handle message components (call1, ...)
|
||||
int nbSymbolBits = m_settings.m_spreadFactor - m_settings.m_deBits;
|
||||
m_lastMsgNbSymbols = (174 / nbSymbolBits) + ((174 % nbSymbolBits) == 0 ? 0 : 1);
|
||||
|
||||
if (m_settings.m_autoNbSymbolsMax)
|
||||
{
|
||||
ChirpChatDemodSettings settings = m_settings;
|
||||
settings.m_nbSymbolsMax = m_lastMsgNbSymbols;
|
||||
applySettings(settings);
|
||||
|
||||
if (getMessageQueueToGUI()) // forward to GUI if any
|
||||
{
|
||||
MsgReportDecodeString *msgToGUI = MsgReportDecodeString::create(m_lastMsgString);
|
||||
msgToGUI->setSyncWord(m_lastMsgSyncWord);
|
||||
msgToGUI->setSignalDb(m_lastMsgSignalDb);
|
||||
msgToGUI->setNoiseDb(m_lastMsgNoiseDb);
|
||||
MsgConfigureChirpChatDemod *msgToGUI = MsgConfigureChirpChatDemod::create(settings, false);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_settings.m_sendViaUDP)
|
||||
{
|
||||
const QByteArray& byteArray = m_lastMsgString.toUtf8();
|
||||
const uint8_t *bytes = reinterpret_cast<const uint8_t*>(byteArray.data());
|
||||
m_udpSink.writeUnbuffered(bytes, byteArray.size());
|
||||
}
|
||||
|
||||
if (getMessageQueueToGUI()) {
|
||||
getMessageQueueToGUI()->push(new ChirpChatDemodMsg::MsgReportDecodeFT(msg)); // make a copy
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
|
@ -420,38 +462,51 @@ void ChirpChatDemod::applySettings(const ChirpChatDemodSettings& settings, bool
|
|||
}
|
||||
|
||||
if ((settings.m_spreadFactor != m_settings.m_spreadFactor)
|
||||
|| (settings.m_deBits != m_settings.m_deBits) || force) {
|
||||
m_decoder.setNbSymbolBits(settings.m_spreadFactor, settings.m_deBits);
|
||||
|| (settings.m_deBits != m_settings.m_deBits) || force)
|
||||
{
|
||||
if (m_decoder) {
|
||||
m_decoder->setNbSymbolBits(settings.m_spreadFactor, settings.m_deBits);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_codingScheme != m_settings.m_codingScheme) || force)
|
||||
{
|
||||
reverseAPIKeys.append("codingScheme");
|
||||
m_decoder.setCodingScheme(settings.m_codingScheme);
|
||||
if (m_decoder) {
|
||||
m_decoder->setCodingScheme(settings.m_codingScheme);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_hasHeader != m_settings.m_hasHeader) || force)
|
||||
{
|
||||
reverseAPIKeys.append("hasHeader");
|
||||
m_decoder.setLoRaHasHeader(settings.m_hasHeader);
|
||||
if (m_decoder) {
|
||||
m_decoder->setLoRaHasHeader(settings.m_hasHeader);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_hasCRC != m_settings.m_hasCRC) || force)
|
||||
{
|
||||
reverseAPIKeys.append("hasCRC");
|
||||
m_decoder.setLoRaHasCRC(settings.m_hasCRC);
|
||||
if (m_decoder) {
|
||||
m_decoder->setLoRaHasCRC(settings.m_hasCRC);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_nbParityBits != m_settings.m_nbParityBits) || force)
|
||||
{
|
||||
reverseAPIKeys.append("nbParityBits");
|
||||
m_decoder.setLoRaParityBits(settings.m_nbParityBits);
|
||||
if (m_decoder) {
|
||||
m_decoder->setLoRaParityBits(settings.m_nbParityBits);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_packetLength != m_settings.m_packetLength) || force)
|
||||
{
|
||||
reverseAPIKeys.append("packetLength");
|
||||
m_decoder.setLoRaPacketLength(settings.m_packetLength);
|
||||
if (m_decoder) {
|
||||
m_decoder->setLoRaPacketLength(settings.m_packetLength);
|
||||
}
|
||||
}
|
||||
|
||||
if ((settings.m_decodeActive != m_settings.m_decodeActive) || force) {
|
||||
|
|
|
@ -35,13 +35,13 @@
|
|||
#include "util/udpsinkutil.h"
|
||||
|
||||
#include "chirpchatdemodbaseband.h"
|
||||
#include "chirpchatdemoddecoder.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeviceAPI;
|
||||
class QThread;
|
||||
class ObjectPipe;
|
||||
class ChirpChatDemodDecoder;
|
||||
|
||||
class ChirpChatDemod : public BasebandSampleSink, public ChannelAPI {
|
||||
public:
|
||||
|
@ -68,141 +68,6 @@ public:
|
|||
{ }
|
||||
};
|
||||
|
||||
class MsgReportDecodeBytes : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QByteArray& getBytes() const { return m_bytes; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
unsigned int getPacketSize() const { return m_packetSize; }
|
||||
unsigned int getNbParityBits() const { return m_nbParityBits; }
|
||||
unsigned int getNbSymbols() const { return m_nbSymbols; }
|
||||
unsigned int getNbCodewords() const { return m_nbCodewords; }
|
||||
bool getHasCRC() const { return m_hasCRC; }
|
||||
bool getEarlyEOM() const { return m_earlyEOM; }
|
||||
int getHeaderParityStatus() const { return m_headerParityStatus; }
|
||||
bool getHeaderCRCStatus() const { return m_headerCRCStatus; }
|
||||
int getPayloadParityStatus() const { return m_payloadParityStatus; }
|
||||
bool getPayloadCRCStatus() const { return m_payloadCRCStatus; }
|
||||
|
||||
static MsgReportDecodeBytes* create(const QByteArray& bytes) {
|
||||
return new MsgReportDecodeBytes(bytes);
|
||||
}
|
||||
void setSyncWord(unsigned int syncWord) {
|
||||
m_syncWord = syncWord;
|
||||
}
|
||||
void setSignalDb(float db) {
|
||||
m_signalDb = db;
|
||||
}
|
||||
void setNoiseDb(float db) {
|
||||
m_noiseDb = db;
|
||||
}
|
||||
void setPacketSize(unsigned int packetSize) {
|
||||
m_packetSize = packetSize;
|
||||
}
|
||||
void setNbParityBits(unsigned int nbParityBits) {
|
||||
m_nbParityBits = nbParityBits;
|
||||
}
|
||||
void setNbSymbols(unsigned int nbSymbols) {
|
||||
m_nbSymbols = nbSymbols;
|
||||
}
|
||||
void setNbCodewords(unsigned int nbCodewords) {
|
||||
m_nbCodewords = nbCodewords;
|
||||
}
|
||||
void setHasCRC(bool hasCRC) {
|
||||
m_hasCRC = hasCRC;
|
||||
}
|
||||
void setEarlyEOM(bool earlyEOM) {
|
||||
m_earlyEOM = earlyEOM;
|
||||
}
|
||||
void setHeaderParityStatus(int headerParityStatus) {
|
||||
m_headerParityStatus = headerParityStatus;
|
||||
}
|
||||
void setHeaderCRCStatus(bool headerCRCStatus) {
|
||||
m_headerCRCStatus = headerCRCStatus;
|
||||
}
|
||||
void setPayloadParityStatus(int payloadParityStatus) {
|
||||
m_payloadParityStatus = payloadParityStatus;
|
||||
}
|
||||
void setPayloadCRCStatus(bool payloadCRCStatus) {
|
||||
m_payloadCRCStatus = payloadCRCStatus;
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_bytes;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
unsigned int m_packetSize;
|
||||
unsigned int m_nbParityBits;
|
||||
unsigned int m_nbSymbols;
|
||||
unsigned int m_nbCodewords;
|
||||
bool m_hasCRC;
|
||||
bool m_earlyEOM;
|
||||
int m_headerParityStatus;
|
||||
bool m_headerCRCStatus;
|
||||
int m_payloadParityStatus;
|
||||
bool m_payloadCRCStatus;
|
||||
|
||||
MsgReportDecodeBytes(const QByteArray& bytes) :
|
||||
Message(),
|
||||
m_bytes(bytes),
|
||||
m_syncWord(0),
|
||||
m_signalDb(0.0),
|
||||
m_noiseDb(0.0),
|
||||
m_packetSize(0),
|
||||
m_nbParityBits(0),
|
||||
m_nbSymbols(0),
|
||||
m_nbCodewords(0),
|
||||
m_hasCRC(false),
|
||||
m_earlyEOM(false),
|
||||
m_headerParityStatus(false),
|
||||
m_headerCRCStatus(false),
|
||||
m_payloadParityStatus(false),
|
||||
m_payloadCRCStatus(false)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportDecodeString : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getString() const { return m_str; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
|
||||
static MsgReportDecodeString* create(const QString& str)
|
||||
{
|
||||
return new MsgReportDecodeString(str);
|
||||
}
|
||||
void setSyncWord(unsigned int syncWord) {
|
||||
m_syncWord = syncWord;
|
||||
}
|
||||
void setSignalDb(float db) {
|
||||
m_signalDb = db;
|
||||
}
|
||||
void setNoiseDb(float db) {
|
||||
m_noiseDb = db;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_str;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
|
||||
MsgReportDecodeString(const QString& str) :
|
||||
Message(),
|
||||
m_str(str),
|
||||
m_syncWord(0),
|
||||
m_signalDb(0.0),
|
||||
m_noiseDb(0.0)
|
||||
{ }
|
||||
};
|
||||
|
||||
ChirpChatDemod(DeviceAPI* deviceAPI);
|
||||
virtual ~ChirpChatDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
@ -276,9 +141,10 @@ public:
|
|||
private:
|
||||
DeviceAPI *m_deviceAPI;
|
||||
QThread *m_thread;
|
||||
QThread *m_decoderThread;
|
||||
ChirpChatDemodBaseband *m_basebandSink;
|
||||
ChirpChatDemodDecoder *m_decoder;
|
||||
bool m_running;
|
||||
ChirpChatDemodDecoder m_decoder;
|
||||
ChirpChatDemodSettings m_settings;
|
||||
SpectrumVis m_spectrumVis;
|
||||
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||
|
|
|
@ -15,18 +15,25 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QTime>
|
||||
|
||||
#include "chirpchatdemoddecoder.h"
|
||||
#include "chirpchatdemoddecodertty.h"
|
||||
#include "chirpchatdemoddecoderascii.h"
|
||||
#include "chirpchatdemoddecoderlora.h"
|
||||
#include "chirpchatdemoddecoderft.h"
|
||||
#include "chirpchatdemodmsg.h"
|
||||
|
||||
ChirpChatDemodDecoder::ChirpChatDemodDecoder() :
|
||||
m_codingScheme(ChirpChatDemodSettings::CodingTTY),
|
||||
m_nbSymbolBits(5),
|
||||
m_nbParityBits(1),
|
||||
m_hasCRC(true),
|
||||
m_hasHeader(true)
|
||||
{}
|
||||
m_hasHeader(true),
|
||||
m_outputMessageQueue(nullptr)
|
||||
{
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
}
|
||||
|
||||
ChirpChatDemodDecoder::~ChirpChatDemodDecoder()
|
||||
{}
|
||||
|
@ -54,7 +61,7 @@ void ChirpChatDemodDecoder::decodeSymbols(const std::vector<unsigned short>& sym
|
|||
}
|
||||
break;
|
||||
case ChirpChatDemodSettings::CodingASCII:
|
||||
if (m_nbSymbolBits == 5) {
|
||||
if (m_nbSymbolBits == 7) {
|
||||
ChirpChatDemodDecoderASCII::decodeSymbols(symbols, str);
|
||||
}
|
||||
break;
|
||||
|
@ -99,3 +106,132 @@ void ChirpChatDemodDecoder::decodeSymbols(const std::vector<unsigned short>& sym
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ChirpChatDemodDecoder::decodeSymbols( //!< For FT coding scheme
|
||||
const std::vector<std::vector<float>>& mags, // vector of symbols magnitudes
|
||||
int nbSymbolBits, //!< number of bits per symbol
|
||||
std::string& msg, //!< formatted message
|
||||
std::string& call1, //!< 1st callsign or shorthand
|
||||
std::string& call2, //!< 2nd callsign
|
||||
std::string& loc, //!< locator, report or shorthand
|
||||
bool& reply //!< true if message is a reply report
|
||||
)
|
||||
{
|
||||
if (m_codingScheme != ChirpChatDemodSettings::CodingFT) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChirpChatDemodDecoderFT::decodeSymbols(
|
||||
mags,
|
||||
nbSymbolBits,
|
||||
msg,
|
||||
call1,
|
||||
call2,
|
||||
loc,
|
||||
reply,
|
||||
m_payloadParityStatus,
|
||||
m_payloadCRCStatus
|
||||
);
|
||||
}
|
||||
|
||||
bool ChirpChatDemodDecoder::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (ChirpChatDemodMsg::MsgDecodeSymbols::match(cmd))
|
||||
{
|
||||
qDebug("ChirpChatDemodDecoder::handleMessage: MsgDecodeSymbols");
|
||||
ChirpChatDemodMsg::MsgDecodeSymbols& msg = (ChirpChatDemodMsg::MsgDecodeSymbols&) cmd;
|
||||
float msgSignalDb = msg.getSingalDb();
|
||||
float msgNoiseDb = msg.getNoiseDb();
|
||||
unsigned int msgSyncWord = msg.getSyncWord();
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
QString msgTimestamp = dt.toString(Qt::ISODateWithMs);
|
||||
|
||||
if (m_codingScheme == ChirpChatDemodSettings::CodingLoRa)
|
||||
{
|
||||
QByteArray msgBytes;
|
||||
decodeSymbols(msg.getSymbols(), msgBytes);
|
||||
|
||||
if (m_outputMessageQueue)
|
||||
{
|
||||
ChirpChatDemodMsg::MsgReportDecodeBytes *outputMsg = ChirpChatDemodMsg::MsgReportDecodeBytes::create(msgBytes);
|
||||
outputMsg->setSyncWord(msgSyncWord);
|
||||
outputMsg->setSignalDb(msgSignalDb);
|
||||
outputMsg->setNoiseDb(msgNoiseDb);
|
||||
outputMsg->setMsgTimestamp(msgTimestamp);
|
||||
outputMsg->setPacketSize(getPacketLength());
|
||||
outputMsg->setNbParityBits(getNbParityBits());
|
||||
outputMsg->setHasCRC(getHasCRC());
|
||||
outputMsg->setNbSymbols(getNbSymbols());
|
||||
outputMsg->setNbCodewords(getNbCodewords());
|
||||
outputMsg->setEarlyEOM(getEarlyEOM());
|
||||
outputMsg->setHeaderParityStatus(getHeaderParityStatus());
|
||||
outputMsg->setHeaderCRCStatus(getHeaderCRCStatus());
|
||||
outputMsg->setPayloadParityStatus(getPayloadParityStatus());
|
||||
outputMsg->setPayloadCRCStatus(getPayloadCRCStatus());
|
||||
m_outputMessageQueue->push(outputMsg);
|
||||
}
|
||||
}
|
||||
else if (m_codingScheme == ChirpChatDemodSettings::CodingFT)
|
||||
{
|
||||
std::string fmsg, call1, call2, loc;
|
||||
bool reply;
|
||||
decodeSymbols(
|
||||
msg.getMagnitudes(),
|
||||
m_nbSymbolBits,
|
||||
fmsg,
|
||||
call1,
|
||||
call2,
|
||||
loc,
|
||||
reply
|
||||
);
|
||||
|
||||
if (m_outputMessageQueue)
|
||||
{
|
||||
ChirpChatDemodMsg::MsgReportDecodeFT *outputMsg = ChirpChatDemodMsg::MsgReportDecodeFT::create();
|
||||
outputMsg->setSyncWord(msgSyncWord);
|
||||
outputMsg->setSignalDb(msgSignalDb);
|
||||
outputMsg->setNoiseDb(msgNoiseDb);
|
||||
outputMsg->setMsgTimestamp(msgTimestamp);
|
||||
outputMsg->setMessage(QString(fmsg.c_str()));
|
||||
outputMsg->setCall1(QString(call1.c_str()));
|
||||
outputMsg->setCall2(QString(call2.c_str()));
|
||||
outputMsg->setLoc(QString(loc.c_str()));
|
||||
outputMsg->setReply(reply);
|
||||
outputMsg->setPayloadParityStatus(getPayloadParityStatus());
|
||||
outputMsg->setPayloadCRCStatus(getPayloadCRCStatus());
|
||||
m_outputMessageQueue->push(outputMsg);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
QString msgString;
|
||||
decodeSymbols(msg.getSymbols(), msgString);
|
||||
|
||||
if (m_outputMessageQueue)
|
||||
{
|
||||
ChirpChatDemodMsg::MsgReportDecodeString *outputMsg = ChirpChatDemodMsg::MsgReportDecodeString::create(msgString);
|
||||
outputMsg->setSyncWord(msgSyncWord);
|
||||
outputMsg->setSignalDb(msgSignalDb);
|
||||
outputMsg->setNoiseDb(msgNoiseDb);
|
||||
outputMsg->setMsgTimestamp(msgTimestamp);
|
||||
m_outputMessageQueue->push(outputMsg);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChirpChatDemodDecoder::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,15 @@
|
|||
#define INCLUDE_CHIRPCHATDEMODDECODER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "util/messagequeue.h"
|
||||
#include "chirpchatdemodsettings.h"
|
||||
|
||||
class ChirpChatDemodDecoder
|
||||
class ChirpChatDemodDecoder : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ChirpChatDemodDecoder();
|
||||
~ChirpChatDemodDecoder();
|
||||
|
@ -35,8 +40,22 @@ public:
|
|||
void setLoRaHasHeader(bool hasHeader) { m_hasHeader = hasHeader; }
|
||||
void setLoRaHasCRC(bool hasCRC) { m_hasCRC = hasCRC; }
|
||||
void setLoRaPacketLength(unsigned int packetLength) { m_packetLength = packetLength; }
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
void setOutputMessageQueue(MessageQueue *messageQueue) { m_outputMessageQueue = messageQueue; }
|
||||
|
||||
private:
|
||||
bool handleMessage(const Message& cmd);
|
||||
void decodeSymbols(const std::vector<unsigned short>& symbols, QString& str); //!< For ASCII and TTY
|
||||
void decodeSymbols(const std::vector<unsigned short>& symbols, QByteArray& bytes); //!< For raw bytes (original LoRa)
|
||||
void decodeSymbols( //!< For FT coding scheme
|
||||
const std::vector<std::vector<float>>& mags, // vector of symbols magnitudes
|
||||
int nbSymbolBits, //!< number of bits per symbol
|
||||
std::string& msg, //!< formatted message
|
||||
std::string& call1, //!< 1st callsign or shorthand
|
||||
std::string& call2, //!< 2nd callsign
|
||||
std::string& loc, //!< locator, report or shorthand
|
||||
bool& reply //!< true if message is a reply report
|
||||
);
|
||||
unsigned int getNbParityBits() const { return m_nbParityBits; }
|
||||
unsigned int getPacketLength() const { return m_packetLength; }
|
||||
bool getHasCRC() const { return m_hasCRC; }
|
||||
|
@ -48,7 +67,6 @@ public:
|
|||
int getPayloadParityStatus() const { return m_payloadParityStatus; }
|
||||
bool getPayloadCRCStatus() const { return m_payloadCRCStatus; }
|
||||
|
||||
private:
|
||||
ChirpChatDemodSettings::CodingScheme m_codingScheme;
|
||||
unsigned int m_spreadFactor;
|
||||
unsigned int m_deBits;
|
||||
|
@ -65,6 +83,11 @@ private:
|
|||
bool m_headerCRCStatus;
|
||||
int m_payloadParityStatus;
|
||||
bool m_payloadCRCStatus;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
MessageQueue *m_outputMessageQueue;
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_CHIRPCHATDEMODDECODER_H
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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 "chirpchatdemodsettings.h"
|
||||
#include "chirpchatdemoddecoderft.h"
|
||||
|
||||
#ifndef HAS_FT8
|
||||
void ChirpChatDemodDecoderFT::decodeSymbols(
|
||||
const std::vector<std::vector<float>>& mags, // vector of symbols magnitudes
|
||||
int nbSymbolBits, //!< number of bits per symbol
|
||||
QString& msg, //!< formatted message
|
||||
QString& call1, //!< 1st callsign or shorthand
|
||||
QString& call2, //!< 2nd callsign
|
||||
QString& loc, //!< locator, report or shorthand
|
||||
bool& reply //!< true if message is a reply report
|
||||
)
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: not implemented");
|
||||
}
|
||||
#else
|
||||
|
||||
#include "ft8.h"
|
||||
#include "packing.h"
|
||||
|
||||
void ChirpChatDemodDecoderFT::decodeSymbols(
|
||||
const std::vector<std::vector<float>>& mags, // vector of symbols magnitudes
|
||||
int nbSymbolBits, //!< number of bits per symbol
|
||||
std::string& msg, //!< formatted message
|
||||
std::string& call1, //!< 1st callsign or shorthand
|
||||
std::string& call2, //!< 2nd callsign
|
||||
std::string& loc, //!< locator, report or shorthand
|
||||
bool& reply, //!< true if message is a reply report
|
||||
int& payloadParityStatus,
|
||||
bool& payloadCRCStatus
|
||||
)
|
||||
{
|
||||
if (mags.size()*nbSymbolBits < 174)
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: insufficient number of symbols for FT payload");
|
||||
return;
|
||||
}
|
||||
|
||||
// float *lls = new float[mags.size()*nbSymbolBits]; // bits log likelihoods (>0 for 0, <0 for 1)
|
||||
// std::fill(lls, lls+mags.size()*nbSymbolBits, 0.0);
|
||||
FT8::FT8Params params;
|
||||
// FT8::FT8::soft_decode_mags(params, mags, nbSymbolBits, lls);
|
||||
int r174[174];
|
||||
std::string comments;
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityOK;
|
||||
payloadCRCStatus = false;
|
||||
std::vector<std::vector<float>> magsp = mags;
|
||||
|
||||
qDebug("ChirpChatDemodDecoderFT::decodeSymbols: try decode with symbol shift 0");
|
||||
int res = decodeWithShift(params, magsp, nbSymbolBits, r174, comments);
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
std::vector<std::vector<float>> magsn = mags;
|
||||
int shiftcount = 0;
|
||||
|
||||
while ((res == 0) && (shiftcount < 7))
|
||||
{
|
||||
qDebug("ChirpChatDemodDecoderFT::decodeSymbols: try decode with symbol shift %d", shiftcount + 1);
|
||||
res = decodeWithShift(params, magsp, nbSymbolBits, r174, comments, 1);
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
qDebug("ChirpChatDemodDecoderFT::decodeSymbols: try decode with symbol shift -%d", shiftcount + 1);
|
||||
res = decodeWithShift(params, magsn, nbSymbolBits, r174, comments, -1);
|
||||
}
|
||||
|
||||
shiftcount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (res == 0)
|
||||
{
|
||||
if (comments == "LDPC fail")
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: LDPC failed");
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityError;
|
||||
}
|
||||
else if (comments == "OSD fail")
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: OSD failed");
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityError;
|
||||
}
|
||||
else if (comments == "CRC fail")
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: CRC failed");
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning("ChirpChatDemodDecoderFT::decodeSymbols: decode failed for unknown reason");
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityUndefined;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
payloadCRCStatus = true;
|
||||
FT8::Packing packing;
|
||||
std::string msgType;
|
||||
msg = packing.unpack(r174, call1, call2, loc, msgType);
|
||||
reply = false;
|
||||
|
||||
if ((msgType == "0.3") || (msgType == "0.3")) {
|
||||
reply = r174[56] != 0;
|
||||
}
|
||||
if ((msgType == "1") || (msgType == "2")) {
|
||||
reply = r174[58] != 0;
|
||||
}
|
||||
if ((msgType == "3")) {
|
||||
reply = r174[57] != 0;
|
||||
}
|
||||
if ((msgType == "5")) {
|
||||
reply = r174[34] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
int ChirpChatDemodDecoderFT::decodeWithShift(
|
||||
FT8::FT8Params& params,
|
||||
std::vector<std::vector<float>>& mags,
|
||||
int nbSymbolBits,
|
||||
int *r174,
|
||||
std::string& comments,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
if (shift > 0)
|
||||
{
|
||||
for (unsigned int si = 0; si < mags.size(); si++)
|
||||
{
|
||||
for (int bini = (1<<nbSymbolBits) - 1; bini > 0; bini--)
|
||||
{
|
||||
float x = mags[si][bini - 1];
|
||||
mags[si][bini - 1] = mags[si][bini];
|
||||
mags[si][bini] = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shift < 0)
|
||||
{
|
||||
for (unsigned int si = 0; si < mags.size(); si++)
|
||||
{
|
||||
for (int bini = 0; bini < (1<<nbSymbolBits) - 1; bini++)
|
||||
{
|
||||
float x = mags[si][bini + 1];
|
||||
mags[si][bini + 1] = mags[si][bini];
|
||||
mags[si][bini] = x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float *lls = new float[mags.size()*nbSymbolBits]; // bits log likelihoods (>0 for 0, <0 for 1)
|
||||
std::fill(lls, lls+mags.size()*nbSymbolBits, 0.0);
|
||||
FT8::FT8::soft_decode_mags(params, mags, nbSymbolBits, lls);
|
||||
return FT8::FT8::decode(lls, r174, params, 0, comments);
|
||||
}
|
||||
|
||||
#endif // HAS_FT8
|
|
@ -0,0 +1,63 @@
|
|||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2024 Edouard Griffiths, F4EXB <f4exb06@gmail.com> //
|
||||
// //
|
||||
// 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_CHIRPCHATDEMODDECODERFT_H
|
||||
#define INCLUDE_CHIRPCHATDEMODDECODERFT_H
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace FT8 {
|
||||
class FT8Params;
|
||||
}
|
||||
|
||||
class ChirpChatDemodDecoderFT
|
||||
{
|
||||
public:
|
||||
enum ParityStatus
|
||||
{
|
||||
ParityUndefined,
|
||||
ParityError,
|
||||
ParityCorrected,
|
||||
ParityOK
|
||||
};
|
||||
|
||||
static void decodeSymbols(
|
||||
const std::vector<std::vector<float>>& mags, // vector of symbols magnitudes
|
||||
int nbSymbolBits, //!< number of bits per symbol
|
||||
std::string& msg, //!< formatted message
|
||||
std::string& call1, //!< 1st callsign or shorthand
|
||||
std::string& call2, //!< 2nd callsign
|
||||
std::string& loc, //!< locator, report or shorthand
|
||||
bool& reply, //!< true if message is a reply report
|
||||
int& payloadParityStatus,
|
||||
bool& payloadCRCStatus
|
||||
);
|
||||
|
||||
private:
|
||||
static int decodeWithShift(
|
||||
FT8::FT8Params& params,
|
||||
std::vector<std::vector<float>>& mags,
|
||||
int nbSymbolBits,
|
||||
int *r174,
|
||||
std::string& comments,
|
||||
int shift = 0
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
|
@ -17,6 +17,7 @@
|
|||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "chirpchatdemodsettings.h"
|
||||
#include "chirpchatdemoddecoderlora.h"
|
||||
|
||||
void ChirpChatDemodDecoderLoRa::decodeHeader(
|
||||
|
@ -71,14 +72,14 @@ void ChirpChatDemodDecoderLoRa::decodeHeader(
|
|||
|
||||
if (bad)
|
||||
{
|
||||
headerParityStatus = (int) ParityError;
|
||||
headerParityStatus = (int) ChirpChatDemodSettings::ParityError;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (error) {
|
||||
headerParityStatus = (int) ParityCorrected;
|
||||
headerParityStatus = (int) ChirpChatDemodSettings::ParityCorrected;
|
||||
} else {
|
||||
headerParityStatus = (int) ParityOK;
|
||||
headerParityStatus = (int) ChirpChatDemodSettings::ParityOK;
|
||||
}
|
||||
|
||||
if (bytes[2] != 0) {
|
||||
|
@ -300,11 +301,11 @@ void ChirpChatDemodDecoderLoRa::decodeBytes(
|
|||
}
|
||||
|
||||
if (bad) {
|
||||
payloadParityStatus = (int) ParityError;
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityError;
|
||||
} else if (error) {
|
||||
payloadParityStatus = (int) ParityCorrected;
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityCorrected;
|
||||
} else {
|
||||
payloadParityStatus = (int) ParityOK;
|
||||
payloadParityStatus = (int) ChirpChatDemodSettings::ParityOK;
|
||||
}
|
||||
|
||||
// finalization:
|
||||
|
|
|
@ -26,14 +26,6 @@
|
|||
class ChirpChatDemodDecoderLoRa
|
||||
{
|
||||
public:
|
||||
enum ParityStatus
|
||||
{
|
||||
ParityUndefined,
|
||||
ParityError,
|
||||
ParityCorrected,
|
||||
ParityOK
|
||||
};
|
||||
|
||||
static void decodeBytes(
|
||||
QByteArray& bytes,
|
||||
const std::vector<unsigned short>& inSymbols,
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "maincore.h"
|
||||
|
||||
#include "chirpchatdemod.h"
|
||||
#include "chirpchatdemodmsg.h"
|
||||
#include "chirpchatdemodgui.h"
|
||||
|
||||
ChirpChatDemodGUI* ChirpChatDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
||||
|
@ -103,7 +104,7 @@ bool ChirpChatDemodGUI::handleMessage(const Message& message)
|
|||
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemod::MsgReportDecodeBytes::match(message))
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeBytes::match(message))
|
||||
{
|
||||
if (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingLoRa) {
|
||||
showLoRaMessage(message);
|
||||
|
@ -111,11 +112,19 @@ bool ChirpChatDemodGUI::handleMessage(const Message& message)
|
|||
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemod::MsgReportDecodeString::match(message))
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeString::match(message))
|
||||
{
|
||||
if ((m_settings.m_codingScheme == ChirpChatDemodSettings::CodingASCII)
|
||||
|| (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingTTY)) {
|
||||
showTextMessage(message);
|
||||
|| (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingTTY)) {
|
||||
showTextMessage(message);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (ChirpChatDemodMsg::MsgReportDecodeFT::match(message))
|
||||
{
|
||||
if (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingFT) {
|
||||
showFTMessage(message);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -280,6 +289,7 @@ void ChirpChatDemodGUI::on_header_stateChanged(int state)
|
|||
|
||||
ui->fecParity->setEnabled(state != Qt::Checked);
|
||||
ui->crc->setEnabled(state != Qt::Checked);
|
||||
ui->packetLength->setEnabled(state != Qt::Checked);
|
||||
|
||||
applySettings();
|
||||
}
|
||||
|
@ -543,11 +553,11 @@ void ChirpChatDemodGUI::displaySquelch()
|
|||
|
||||
void ChirpChatDemodGUI::displayLoRaStatus(int headerParityStatus, bool headerCRCStatus, int payloadParityStatus, bool payloadCRCStatus)
|
||||
{
|
||||
if (m_settings.m_hasHeader && (headerParityStatus == (int) ParityOK)) {
|
||||
if (m_settings.m_hasHeader && (headerParityStatus == (int) ChirpChatDemodSettings::ParityOK)) {
|
||||
ui->headerHammingStatus->setStyleSheet("QLabel { background-color : green; }");
|
||||
} else if (m_settings.m_hasHeader && (headerParityStatus == (int) ParityError)) {
|
||||
} else if (m_settings.m_hasHeader && (headerParityStatus == (int) ChirpChatDemodSettings::ParityError)) {
|
||||
ui->headerHammingStatus->setStyleSheet("QLabel { background-color : red; }");
|
||||
} else if (m_settings.m_hasHeader && (headerParityStatus == (int) ParityCorrected)) {
|
||||
} else if (m_settings.m_hasHeader && (headerParityStatus == (int) ChirpChatDemodSettings::ParityCorrected)) {
|
||||
ui->headerHammingStatus->setStyleSheet("QLabel { background-color : blue; }");
|
||||
} else {
|
||||
ui->headerHammingStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
|
@ -561,11 +571,11 @@ void ChirpChatDemodGUI::displayLoRaStatus(int headerParityStatus, bool headerCRC
|
|||
ui->headerCRCStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
}
|
||||
|
||||
if (payloadParityStatus == (int) ParityOK) {
|
||||
if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityOK) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : green; }");
|
||||
} else if (payloadParityStatus == (int) ParityError) {
|
||||
} else if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityError) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : red; }");
|
||||
} else if (payloadParityStatus == (int) ParityCorrected) {
|
||||
} else if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityCorrected) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : blue; }");
|
||||
} else {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
|
@ -588,6 +598,25 @@ void ChirpChatDemodGUI::resetLoRaStatus()
|
|||
ui->nbCodewordsText->setText("---");
|
||||
}
|
||||
|
||||
void ChirpChatDemodGUI::displayFTStatus(int payloadParityStatus, bool payloadCRCStatus)
|
||||
{
|
||||
if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityOK) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : green; }");
|
||||
} else if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityError) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : red; }");
|
||||
} else if (payloadParityStatus == (int) ChirpChatDemodSettings::ParityCorrected) {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background-color : blue; }");
|
||||
} else {
|
||||
ui->payloadFECStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }");
|
||||
}
|
||||
|
||||
if (payloadCRCStatus) {
|
||||
ui->payloadCRCStatus->setStyleSheet("QLabel { background-color : green; }");
|
||||
} else {
|
||||
ui->payloadCRCStatus->setStyleSheet("QLabel { background-color : red; }");
|
||||
}
|
||||
}
|
||||
|
||||
void ChirpChatDemodGUI::setBandwidths()
|
||||
{
|
||||
int maxBandwidth = m_basebandSampleRate/ChirpChatDemodSettings::oversampling;
|
||||
|
@ -607,7 +636,7 @@ void ChirpChatDemodGUI::setBandwidths()
|
|||
|
||||
void ChirpChatDemodGUI::showLoRaMessage(const Message& message)
|
||||
{
|
||||
const ChirpChatDemod::MsgReportDecodeBytes& msg = (ChirpChatDemod::MsgReportDecodeBytes&) message;
|
||||
const ChirpChatDemodMsg::MsgReportDecodeBytes& msg = (ChirpChatDemodMsg::MsgReportDecodeBytes&) message;
|
||||
QByteArray bytes = msg.getBytes();
|
||||
QString syncWordStr((tr("%1").arg(msg.getSyncWord(), 2, 16, QChar('0'))));
|
||||
|
||||
|
@ -643,7 +672,7 @@ void ChirpChatDemodGUI::showLoRaMessage(const Message& message)
|
|||
.arg(msg.getHeaderCRCStatus() ? "ok" : "err");
|
||||
|
||||
displayStatus(loRaStatus);
|
||||
displayLoRaStatus(msg.getHeaderParityStatus(), msg.getHeaderCRCStatus(), (int) ParityUndefined, true);
|
||||
displayLoRaStatus(msg.getHeaderParityStatus(), msg.getHeaderCRCStatus(), (int) ChirpChatDemodSettings::ParityUndefined, true);
|
||||
ui->payloadCRCStatus->setStyleSheet("QLabel { background:rgb(79,79,79); }"); // reset payload CRC
|
||||
}
|
||||
else
|
||||
|
@ -677,7 +706,7 @@ void ChirpChatDemodGUI::showLoRaMessage(const Message& message)
|
|||
|
||||
void ChirpChatDemodGUI::showTextMessage(const Message& message)
|
||||
{
|
||||
const ChirpChatDemod::MsgReportDecodeString& msg = (ChirpChatDemod::MsgReportDecodeString&) message;
|
||||
const ChirpChatDemodMsg::MsgReportDecodeString& msg = (ChirpChatDemodMsg::MsgReportDecodeString&) message;
|
||||
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
QString dateStr = dt.toString("HH:mm:ss");
|
||||
|
@ -693,6 +722,27 @@ void ChirpChatDemodGUI::showTextMessage(const Message& message)
|
|||
displayText(msg.getString());
|
||||
}
|
||||
|
||||
void ChirpChatDemodGUI::showFTMessage(const Message& message)
|
||||
{
|
||||
const ChirpChatDemodMsg::MsgReportDecodeFT& msg = (ChirpChatDemodMsg::MsgReportDecodeFT&) message;
|
||||
|
||||
QDateTime dt = QDateTime::currentDateTime();
|
||||
QString dateStr = dt.toString("HH:mm:ss");
|
||||
ui->sText->setText(tr("%1").arg(msg.getSingalDb(), 0, 'f', 1));
|
||||
ui->snrText->setText(tr("%1").arg(msg.getSingalDb() - msg.getNoiseDb(), 0, 'f', 1));
|
||||
|
||||
QString status = tr("%1 S:%2 SN:%3 FEC:%4 CRC:%5")
|
||||
.arg(dateStr)
|
||||
.arg(msg.getSingalDb(), 0, 'f', 1)
|
||||
.arg(msg.getSingalDb() - msg.getNoiseDb(), 0, 'f', 1)
|
||||
.arg(getParityStr(msg.getPayloadParityStatus()))
|
||||
.arg(msg.getPayloadCRCStatus() ? "ok" : "err");
|
||||
|
||||
displayStatus(status);
|
||||
displayText(msg.getMessage()); // We do not show constituents of the message (call1, ...)
|
||||
displayFTStatus(msg.getPayloadParityStatus(), msg.getPayloadCRCStatus());
|
||||
}
|
||||
|
||||
void ChirpChatDemodGUI::displayText(const QString& text)
|
||||
{
|
||||
QTextCursor cursor = ui->messageText->textCursor();
|
||||
|
@ -753,11 +803,11 @@ void ChirpChatDemodGUI::displayStatus(const QString& status)
|
|||
|
||||
QString ChirpChatDemodGUI::getParityStr(int parityStatus)
|
||||
{
|
||||
if (parityStatus == (int) ParityError) {
|
||||
if (parityStatus == (int) ChirpChatDemodSettings::ParityError) {
|
||||
return "err";
|
||||
} else if (parityStatus == (int) ParityCorrected) {
|
||||
} else if (parityStatus == (int) ChirpChatDemodSettings::ParityCorrected) {
|
||||
return "fix";
|
||||
} else if (parityStatus == (int) ParityOK) {
|
||||
} else if (parityStatus == (int) ChirpChatDemodSettings::ParityOK) {
|
||||
return "ok";
|
||||
} else {
|
||||
return "n/a";
|
||||
|
|
|
@ -89,14 +89,6 @@ private slots:
|
|||
void tick();
|
||||
|
||||
private:
|
||||
enum ParityStatus // matches decoder status
|
||||
{
|
||||
ParityUndefined,
|
||||
ParityError,
|
||||
ParityCorrected,
|
||||
ParityOK
|
||||
};
|
||||
|
||||
Ui::ChirpChatDemodGUI* ui;
|
||||
PluginAPI* m_pluginAPI;
|
||||
DeviceUISet* m_deviceUISet;
|
||||
|
@ -120,12 +112,14 @@ private:
|
|||
void displaySettings();
|
||||
void displaySquelch();
|
||||
void setBandwidths();
|
||||
void showLoRaMessage(const Message& message);
|
||||
void showLoRaMessage(const Message& message); //!< For LoRa coding scheme
|
||||
void showTextMessage(const Message& message); //!< For TTY and ASCII
|
||||
void showFTMessage(const Message& message); //!< For FT coding scheme
|
||||
void displayText(const QString& text);
|
||||
void displayBytes(const QByteArray& bytes);
|
||||
void displayStatus(const QString& status);
|
||||
void displayLoRaStatus(int headerParityStatus, bool headerCRCStatus, int payloadParityStatus, bool payloadCRCStatus);
|
||||
void displayFTStatus(int payloadParityStatus, bool payloadCRCStatus);
|
||||
QString getParityStr(int parityStatus);
|
||||
void resetLoRaStatus();
|
||||
bool handleMessage(const Message& message);
|
||||
|
|
|
@ -585,6 +585,11 @@
|
|||
<string>TTY</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>FT</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -718,7 +723,7 @@
|
|||
<item>
|
||||
<widget class="QCheckBox" name="messageLengthAuto">
|
||||
<property name="toolTip">
|
||||
<string>Set message length in symbols automatically to provided message length (LoRa only)</string>
|
||||
<string>Set message length in symbols automatically to provided message length (LoRa and FT only)</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto</string>
|
||||
|
|
|
@ -20,3 +20,6 @@
|
|||
#include "chirpchatdemodmsg.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemodMsg::MsgDecodeSymbols, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemodMsg::MsgReportDecodeBytes, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemodMsg::MsgReportDecodeString, Message)
|
||||
MESSAGE_CLASS_DEFINITION(ChirpChatDemodMsg::MsgReportDecodeFT, Message)
|
||||
|
|
|
@ -21,6 +21,8 @@
|
|||
#include <QObject>
|
||||
#include "util/message.h"
|
||||
|
||||
#include "chirpchatdemodsettings.h"
|
||||
|
||||
namespace ChirpChatDemodMsg
|
||||
{
|
||||
class MsgDecodeSymbols : public Message {
|
||||
|
@ -28,6 +30,7 @@ namespace ChirpChatDemodMsg
|
|||
|
||||
public:
|
||||
const std::vector<unsigned short>& getSymbols() const { return m_symbols; }
|
||||
const std::vector<std::vector<float>>& getMagnitudes() const { return m_magnitudes; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
|
@ -48,6 +51,10 @@ namespace ChirpChatDemodMsg
|
|||
m_noiseDb = db;
|
||||
}
|
||||
|
||||
void pushBackMagnitudes(const std::vector<float>& magnitudes) {
|
||||
m_magnitudes.push_back(magnitudes);
|
||||
}
|
||||
|
||||
static MsgDecodeSymbols* create() {
|
||||
return new MsgDecodeSymbols();
|
||||
}
|
||||
|
@ -57,6 +64,7 @@ namespace ChirpChatDemodMsg
|
|||
|
||||
private:
|
||||
std::vector<unsigned short> m_symbols;
|
||||
std::vector<std::vector<float>> m_magnitudes;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
|
@ -74,6 +82,235 @@ namespace ChirpChatDemodMsg
|
|||
m_noiseDb(0.0)
|
||||
{ m_symbols = symbols; }
|
||||
};
|
||||
|
||||
class MsgReportDecodeBytes : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QByteArray& getBytes() const { return m_bytes; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
const QString& getMsgTimestamp() const { return m_msgTimestamp; }
|
||||
unsigned int getPacketSize() const { return m_packetSize; }
|
||||
unsigned int getNbParityBits() const { return m_nbParityBits; }
|
||||
unsigned int getNbSymbols() const { return m_nbSymbols; }
|
||||
unsigned int getNbCodewords() const { return m_nbCodewords; }
|
||||
bool getHasCRC() const { return m_hasCRC; }
|
||||
bool getEarlyEOM() const { return m_earlyEOM; }
|
||||
int getHeaderParityStatus() const { return m_headerParityStatus; }
|
||||
bool getHeaderCRCStatus() const { return m_headerCRCStatus; }
|
||||
int getPayloadParityStatus() const { return m_payloadParityStatus; }
|
||||
bool getPayloadCRCStatus() const { return m_payloadCRCStatus; }
|
||||
|
||||
static MsgReportDecodeBytes* create(const QByteArray& bytes) {
|
||||
return new MsgReportDecodeBytes(bytes);
|
||||
}
|
||||
void setSyncWord(unsigned int syncWord) {
|
||||
m_syncWord = syncWord;
|
||||
}
|
||||
void setSignalDb(float db) {
|
||||
m_signalDb = db;
|
||||
}
|
||||
void setNoiseDb(float db) {
|
||||
m_noiseDb = db;
|
||||
}
|
||||
void setMsgTimestamp(const QString& ts) {
|
||||
m_msgTimestamp = ts;
|
||||
}
|
||||
void setPacketSize(unsigned int packetSize) {
|
||||
m_packetSize = packetSize;
|
||||
}
|
||||
void setNbParityBits(unsigned int nbParityBits) {
|
||||
m_nbParityBits = nbParityBits;
|
||||
}
|
||||
void setNbSymbols(unsigned int nbSymbols) {
|
||||
m_nbSymbols = nbSymbols;
|
||||
}
|
||||
void setNbCodewords(unsigned int nbCodewords) {
|
||||
m_nbCodewords = nbCodewords;
|
||||
}
|
||||
void setHasCRC(bool hasCRC) {
|
||||
m_hasCRC = hasCRC;
|
||||
}
|
||||
void setEarlyEOM(bool earlyEOM) {
|
||||
m_earlyEOM = earlyEOM;
|
||||
}
|
||||
void setHeaderParityStatus(int headerParityStatus) {
|
||||
m_headerParityStatus = headerParityStatus;
|
||||
}
|
||||
void setHeaderCRCStatus(bool headerCRCStatus) {
|
||||
m_headerCRCStatus = headerCRCStatus;
|
||||
}
|
||||
void setPayloadParityStatus(int payloadParityStatus) {
|
||||
m_payloadParityStatus = payloadParityStatus;
|
||||
}
|
||||
void setPayloadCRCStatus(bool payloadCRCStatus) {
|
||||
m_payloadCRCStatus = payloadCRCStatus;
|
||||
}
|
||||
|
||||
private:
|
||||
QByteArray m_bytes;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
QString m_msgTimestamp;
|
||||
unsigned int m_packetSize;
|
||||
unsigned int m_nbParityBits;
|
||||
unsigned int m_nbSymbols;
|
||||
unsigned int m_nbCodewords;
|
||||
bool m_hasCRC;
|
||||
bool m_earlyEOM;
|
||||
int m_headerParityStatus;
|
||||
bool m_headerCRCStatus;
|
||||
int m_payloadParityStatus;
|
||||
bool m_payloadCRCStatus;
|
||||
|
||||
MsgReportDecodeBytes(const QByteArray& bytes) :
|
||||
Message(),
|
||||
m_bytes(bytes),
|
||||
m_syncWord(0),
|
||||
m_signalDb(0.0),
|
||||
m_noiseDb(0.0),
|
||||
m_packetSize(0),
|
||||
m_nbParityBits(0),
|
||||
m_nbSymbols(0),
|
||||
m_nbCodewords(0),
|
||||
m_hasCRC(false),
|
||||
m_earlyEOM(false),
|
||||
m_headerParityStatus(false),
|
||||
m_headerCRCStatus(false),
|
||||
m_payloadParityStatus((int) ChirpChatDemodSettings::ParityUndefined),
|
||||
m_payloadCRCStatus(false)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportDecodeString : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getString() const { return m_str; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
const QString& getMsgTimestamp() const { return m_msgTimestamp; }
|
||||
|
||||
static MsgReportDecodeString* create(const QString& str)
|
||||
{
|
||||
return new MsgReportDecodeString(str);
|
||||
}
|
||||
void setSyncWord(unsigned int syncWord) {
|
||||
m_syncWord = syncWord;
|
||||
}
|
||||
void setSignalDb(float db) {
|
||||
m_signalDb = db;
|
||||
}
|
||||
void setNoiseDb(float db) {
|
||||
m_noiseDb = db;
|
||||
}
|
||||
void setMsgTimestamp(const QString& ts) {
|
||||
m_msgTimestamp = ts;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_str;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
QString m_msgTimestamp;
|
||||
|
||||
MsgReportDecodeString(const QString& str) :
|
||||
Message(),
|
||||
m_str(str),
|
||||
m_syncWord(0),
|
||||
m_signalDb(0.0),
|
||||
m_noiseDb(0.0)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportDecodeFT : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const QString& getMessage() const { return m_message; }
|
||||
const QString& getCall1() const { return m_call1; }
|
||||
const QString& getCall2() const { return m_call2; }
|
||||
const QString& getLoc() const { return m_loc; }
|
||||
bool isReply() const { return m_reply; }
|
||||
bool isFreeText() const { return m_freeText; }
|
||||
unsigned int getSyncWord() const { return m_syncWord; }
|
||||
float getSingalDb() const { return m_signalDb; }
|
||||
float getNoiseDb() const { return m_noiseDb; }
|
||||
const QString& getMsgTimestamp() const { return m_msgTimestamp; }
|
||||
int getPayloadParityStatus() const { return m_payloadParityStatus; }
|
||||
bool getPayloadCRCStatus() const { return m_payloadCRCStatus; }
|
||||
|
||||
static MsgReportDecodeFT* create()
|
||||
{
|
||||
return new MsgReportDecodeFT();
|
||||
}
|
||||
void setMessage(const QString& message) {
|
||||
m_message = message;
|
||||
}
|
||||
void setCall1(const QString& call1) {
|
||||
m_call1 = call1;
|
||||
}
|
||||
void setCall2(const QString& call2) {
|
||||
m_call2 = call2;
|
||||
}
|
||||
void setLoc(const QString& loc) {
|
||||
m_loc = loc;
|
||||
}
|
||||
void setReply(bool reply) {
|
||||
m_reply = reply;
|
||||
}
|
||||
void setFreeText(bool freeText) {
|
||||
m_freeText = freeText;
|
||||
}
|
||||
void setSyncWord(unsigned int syncWord) {
|
||||
m_syncWord = syncWord;
|
||||
}
|
||||
void setSignalDb(float db) {
|
||||
m_signalDb = db;
|
||||
}
|
||||
void setNoiseDb(float db) {
|
||||
m_noiseDb = db;
|
||||
}
|
||||
void setMsgTimestamp(const QString& ts) {
|
||||
m_msgTimestamp = ts;
|
||||
}
|
||||
void setPayloadParityStatus(int payloadParityStatus) {
|
||||
m_payloadParityStatus = payloadParityStatus;
|
||||
}
|
||||
void setPayloadCRCStatus(bool payloadCRCStatus) {
|
||||
m_payloadCRCStatus = payloadCRCStatus;
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
QString m_call1;
|
||||
QString m_call2;
|
||||
QString m_loc;
|
||||
bool m_reply;
|
||||
bool m_freeText;
|
||||
unsigned int m_syncWord;
|
||||
float m_signalDb;
|
||||
float m_noiseDb;
|
||||
QString m_msgTimestamp;
|
||||
int m_payloadParityStatus;
|
||||
bool m_payloadCRCStatus;
|
||||
|
||||
MsgReportDecodeFT() :
|
||||
Message(),
|
||||
m_reply(false),
|
||||
m_freeText(false),
|
||||
m_syncWord(0),
|
||||
m_signalDb(0.0),
|
||||
m_noiseDb(0.0),
|
||||
m_payloadParityStatus((int) ChirpChatDemodSettings::ParityUndefined),
|
||||
m_payloadCRCStatus(false)
|
||||
{ }
|
||||
};
|
||||
}
|
||||
|
||||
#endif // INCLUDE_CHIRPCHATDEMODMSG_H
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
const int ChirpChatDemodSettings::bandwidths[] = {
|
||||
325, // 384k / 1024
|
||||
488, // 500k / 1024
|
||||
750, // 384k / 512
|
||||
1500, // 384k / 256
|
||||
2604, // 333k / 128
|
||||
|
@ -53,7 +54,7 @@ const int ChirpChatDemodSettings::bandwidths[] = {
|
|||
400000, // 400k / 1
|
||||
500000 // 500k / 1
|
||||
};
|
||||
const int ChirpChatDemodSettings::nbBandwidths = 3*8 + 3;
|
||||
const int ChirpChatDemodSettings::nbBandwidths = 3*8 + 4;
|
||||
const int ChirpChatDemodSettings::oversampling = 2;
|
||||
|
||||
ChirpChatDemodSettings::ChirpChatDemodSettings() :
|
||||
|
|
|
@ -37,7 +37,16 @@ struct ChirpChatDemodSettings
|
|||
{
|
||||
CodingLoRa, //!< Standard LoRa
|
||||
CodingASCII, //!< plain ASCII (7 bits)
|
||||
CodingTTY //!< plain TTY (5 bits)
|
||||
CodingTTY, //!< plain TTY (5 bits)
|
||||
CodingFT //!< FT8/4 scheme (payload 174 bits LDPC)
|
||||
};
|
||||
|
||||
enum ParityStatus
|
||||
{
|
||||
ParityUndefined,
|
||||
ParityError,
|
||||
ParityCorrected,
|
||||
ParityOK
|
||||
};
|
||||
|
||||
int m_inputFrequencyOffset;
|
||||
|
|
|
@ -286,8 +286,10 @@ void ChirpChatDemodSink::processSample(const Complex& ci)
|
|||
|
||||
m_preambleHistory[m_chirpCount] = imax;
|
||||
m_chirpCount++;
|
||||
double preDrop = magsqPre - magsqSFD;
|
||||
double dropRatio = -preDrop / magsqSFD;
|
||||
|
||||
if (magsqPre < magsqSFD) // preamble drop
|
||||
if ((preDrop < 0.0) && (dropRatio > 0.5)) // preamble drop
|
||||
{
|
||||
m_magsqTotalAvg(magsqSFDTotal);
|
||||
|
||||
|
@ -382,18 +384,63 @@ void ChirpChatDemodSink::processSample(const Complex& ci)
|
|||
m_fft->transform();
|
||||
m_fftCounter = 0;
|
||||
double magsq, magsqTotal;
|
||||
unsigned short symbol;
|
||||
|
||||
unsigned short symbol = evalSymbol(
|
||||
argmax(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
magsqTotal,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
)
|
||||
) % m_nbSymbolsEff;
|
||||
if (m_settings.m_codingScheme == ChirpChatDemodSettings::CodingFT)
|
||||
{
|
||||
std::vector<float> magnitudes;
|
||||
symbol = evalSymbol(
|
||||
extractMagnitudes(
|
||||
magnitudes,
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
magsqTotal,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
)
|
||||
) % m_nbSymbolsEff;
|
||||
m_decodeMsg->pushBackSymbol(symbol);
|
||||
m_decodeMsg->pushBackMagnitudes(magnitudes);
|
||||
}
|
||||
else
|
||||
{
|
||||
int imax;
|
||||
|
||||
if (m_settings.m_deBits > 0)
|
||||
{
|
||||
double magSqNoise;
|
||||
imax = argmaxSpreaded(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
magSqNoise,
|
||||
magsqTotal,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
);
|
||||
// double dbS = CalcDb::dbPower(magsq);
|
||||
// double dbN = CalcDb::dbPower(magSqNoise);
|
||||
// qDebug("ChirpChatDemodSink::processSample: S: %5.2f N: %5.2f S/N: %5.2f", dbS, dbN, dbS - dbN);
|
||||
}
|
||||
else
|
||||
{
|
||||
imax = argmax(
|
||||
m_fft->out(),
|
||||
m_fftInterpolation,
|
||||
m_fftLength,
|
||||
magsq,
|
||||
magsqTotal,
|
||||
m_spectrumBuffer,
|
||||
m_fftInterpolation
|
||||
);
|
||||
}
|
||||
|
||||
symbol = evalSymbol(imax) % m_nbSymbolsEff;
|
||||
m_decodeMsg->pushBackSymbol(symbol);
|
||||
}
|
||||
|
||||
if (m_spectrumSink) {
|
||||
m_spectrumSink->feed(m_spectrumBuffer, m_nbSymbols);
|
||||
|
@ -405,8 +452,6 @@ void ChirpChatDemodSink::processSample(const Complex& ci)
|
|||
|
||||
m_magsqTotalAvg(magsq);
|
||||
|
||||
m_decodeMsg->pushBackSymbol(symbol);
|
||||
|
||||
if ((m_chirpCount == 0)
|
||||
|| (m_settings.m_eomSquelchTenths == 121) // max - disable squelch
|
||||
|| ((m_settings.m_eomSquelchTenths*magsq)/10.0 > m_magsqMax))
|
||||
|
@ -417,7 +462,7 @@ void ChirpChatDemodSink::processSample(const Complex& ci)
|
|||
|
||||
if (m_chirpCount > m_settings.m_nbSymbolsMax)
|
||||
{
|
||||
qDebug("ChirpChatDemodSink::processSample: message length exceeded");
|
||||
qDebug("ChirpChatDemodSink::processSample: message length reached");
|
||||
m_state = ChirpChatStateReset;
|
||||
m_decodeMsg->setSignalDb(CalcDb::dbPower(m_magsqOnAvg.asDouble() / (1<<m_settings.m_spreadFactor)));
|
||||
m_decodeMsg->setNoiseDb(CalcDb::dbPower(m_magsqOffAvg.asDouble() / (1<<m_settings.m_spreadFactor)));
|
||||
|
@ -508,38 +553,93 @@ unsigned int ChirpChatDemodSink::argmax(
|
|||
return imax;
|
||||
}
|
||||
|
||||
unsigned int ChirpChatDemodSink::extractMagnitudes(
|
||||
std::vector<float>& magnitudes,
|
||||
const Complex *fftBins,
|
||||
unsigned int fftMult,
|
||||
unsigned int fftLength,
|
||||
double& magsqMax,
|
||||
double& magsqTotal,
|
||||
Complex *specBuffer,
|
||||
unsigned int specDecim)
|
||||
{
|
||||
magsqMax = 0.0;
|
||||
magsqTotal = 0.0;
|
||||
unsigned int imax = 0;
|
||||
double magSum = 0.0;
|
||||
unsigned int spread = fftMult * (1<<m_settings.m_deBits);
|
||||
unsigned int istart = fftMult*fftLength - spread/2 + 1;
|
||||
float magnitude = 0.0;
|
||||
|
||||
for (unsigned int i2 = istart; i2 < istart + fftMult*fftLength; i2++)
|
||||
{
|
||||
int i = i2 % (fftMult*fftLength);
|
||||
double magsq = std::norm(fftBins[i]);
|
||||
magsqTotal += magsq;
|
||||
magnitude += magsq;
|
||||
|
||||
if (i % spread == (spread/2)-1) // boundary (inclusive)
|
||||
{
|
||||
if (magnitude > magsqMax)
|
||||
{
|
||||
imax = (i/spread)*spread;
|
||||
magsqMax = magnitude;
|
||||
}
|
||||
|
||||
magnitudes.push_back(magnitude);
|
||||
magnitude = 0.0;
|
||||
}
|
||||
|
||||
if (specBuffer)
|
||||
{
|
||||
magSum += magsq;
|
||||
|
||||
if (i % specDecim == specDecim - 1)
|
||||
{
|
||||
specBuffer[i/specDecim] = Complex(std::polar(magSum, 0.0));
|
||||
magSum = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
magsqTotal /= fftMult*fftLength;
|
||||
|
||||
return imax;
|
||||
}
|
||||
|
||||
unsigned int ChirpChatDemodSink::argmaxSpreaded(
|
||||
const Complex *fftBins,
|
||||
unsigned int fftMult,
|
||||
unsigned int fftLength,
|
||||
double& magsqMax,
|
||||
double& magsqNoise,
|
||||
double& magSqTotal,
|
||||
double& magsqTotal,
|
||||
Complex *specBuffer,
|
||||
unsigned int specDecim)
|
||||
{
|
||||
magsqMax = 0.0;
|
||||
magsqNoise = 0.0;
|
||||
magSqTotal = 0.0;
|
||||
magsqTotal = 0.0;
|
||||
unsigned int imax = 0;
|
||||
double magSum = 0.0;
|
||||
double magSymbol = 0.0;
|
||||
unsigned int nbsymbols = 1<<(m_settings.m_spreadFactor - m_settings.m_deBits);
|
||||
unsigned int spread = fftMult * (1<<m_settings.m_deBits);
|
||||
unsigned int istart = fftMult*fftLength - spread/2 + 1;
|
||||
double magSymbol = 0.0;
|
||||
|
||||
for (unsigned int i2 = istart; i2 < istart + fftMult*fftLength; i2++)
|
||||
{
|
||||
unsigned int i = i2 % (fftMult*fftLength);
|
||||
int i = i2 % (fftMult*fftLength);
|
||||
double magsq = std::norm(fftBins[i]);
|
||||
magsqTotal += magsq;
|
||||
magSymbol += magsq;
|
||||
magSqTotal += magsq;
|
||||
|
||||
if (i % spread == spread/2) // boundary (inclusive)
|
||||
if (i % spread == (spread/2)-1) // boundary (inclusive)
|
||||
{
|
||||
if (magSymbol > magsqMax)
|
||||
{
|
||||
imax = (i/spread)*spread;
|
||||
magsqMax = magSymbol;
|
||||
imax = i;
|
||||
}
|
||||
|
||||
magsqNoise += magSymbol;
|
||||
|
@ -559,10 +659,12 @@ unsigned int ChirpChatDemodSink::argmaxSpreaded(
|
|||
}
|
||||
|
||||
magsqNoise -= magsqMax;
|
||||
magsqNoise /= fftLength;
|
||||
magSqTotal /= fftMult*fftLength;
|
||||
magsqNoise /= (nbsymbols - 1);
|
||||
magsqTotal /= nbsymbols;
|
||||
// magsqNoise /= fftLength;
|
||||
// magsqTotal /= fftMult*fftLength;
|
||||
|
||||
return imax / spread;
|
||||
return imax;
|
||||
}
|
||||
|
||||
void ChirpChatDemodSink::decimateSpectrum(Complex *in, Complex *out, unsigned int size, unsigned int decimation)
|
||||
|
|
|
@ -138,6 +138,16 @@ private:
|
|||
Complex *specBuffer,
|
||||
unsigned int specDecim
|
||||
);
|
||||
unsigned int extractMagnitudes(
|
||||
std::vector<float>& magnitudes,
|
||||
const Complex *fftBins,
|
||||
unsigned int fftMult,
|
||||
unsigned int fftLength,
|
||||
double& magsqMax,
|
||||
double& magSqTotal,
|
||||
Complex *specBuffer,
|
||||
unsigned int specDecim
|
||||
);
|
||||
void decimateSpectrum(Complex *in, Complex *out, unsigned int size, unsigned int decimation);
|
||||
int toSigned(int u, int intSize);
|
||||
unsigned int evalSymbol(unsigned int rawSymbol);
|
||||
|
|
|
@ -69,6 +69,7 @@ Thus available bandwidths are:
|
|||
- **2604** (333333 / 128) Hz not in LoRa standard
|
||||
- **1500** (384000 / 256) Hz not in LoRa standard
|
||||
- **750** (384000 / 512) Hz not in LoRa standard
|
||||
- **488** (500000 / 1024) Hz not in LoRa standard
|
||||
- **375** (384000 / 1024) Hz not in LoRa standard
|
||||
|
||||
The ChirpChat signal is oversampled by two therefore it needs a baseband of at least twice the bandwidth. This drives the maximum value on the slider automatically.
|
||||
|
@ -128,6 +129,7 @@ In addition to the LoRa standard plain ASCII and TTY have been added for pure te
|
|||
- **LoRa**: LoRa standard (see LoRa documentation)
|
||||
- **ASCII**: This is plain 7 bit ASCII coded characters. It needs exactly 7 effective bits per symbols (SF - DE = 7)
|
||||
- **TTY**: Baudot (Teletype) 5 bit encoded characters. It needs exactly 5 effective bits per symbols (SF - DE = 5)
|
||||
- **FT**: FT8/4 protocol. The 174 payload bits are packed into chirp symbols with zero padding if necessary
|
||||
|
||||
<h4>A.2: Start/Stop decoder</h4>
|
||||
|
||||
|
@ -145,7 +147,7 @@ This is the expected number of symbols in a message. When a header is present in
|
|||
|
||||
<h4>A.5: Auto message length</h4>
|
||||
|
||||
LoRa mode only. Set message length (A.4) equal to the number of symbols specified in the message just received. When messages are sent repeatedly this helps adjusting in possible message length changes automatically.
|
||||
LoRa and DT modes only. Set message length (A.4) equal to the number of symbols specified (or implied for FT) in the message just received. When messages are sent repeatedly this helps adjusting in possible message length changes automatically.
|
||||
|
||||
<h4>A.6: Sync word</h4>
|
||||
|
||||
|
@ -163,21 +165,21 @@ When a header is expected this control is disabled because the value used is the
|
|||
|
||||
<h4>A.9: Payload CRC presence</h4>
|
||||
|
||||
Use this checkbox to tell if you expect a 2 byte CRC at the end of the payload.
|
||||
LoRa mode: Use this checkbox to tell if you expect a 2 byte CRC at the end of the payload. FT mode: there is always a CRC.
|
||||
|
||||
When a header is expected this control is disabled because the value used is the one found in the header.
|
||||
LoRa: When a header is expected this control is disabled because the value used is the one found in the header.
|
||||
|
||||
<h4>A.10: Packet length</h4>
|
||||
|
||||
This is the expected packet length in bytes without header and CRC.
|
||||
This is the expected packet length in bytes without header and CRC. For FT this is the number of symbols.
|
||||
|
||||
When a header is expected this control is disabled because the value used is the one found in the header.
|
||||
LoRa: When a header is expected this control is disabled because the value used is the one found in the header.
|
||||
|
||||
<h4>A.11: Number of symbols and codewords</h4>
|
||||
|
||||
This is the number of symbols (left of slash) and codewords (right of slash) used for the payload including header and CRC.
|
||||
|
||||
<h4>A.12: Header FEC indicator</h4>
|
||||
<h4>A.12: Header FEC indicator (LoRa)</h4>
|
||||
|
||||
Header uses a H(4,8) FEC. The color of the indicator gives the status of header parity checks:
|
||||
|
||||
|
@ -186,27 +188,27 @@ Header uses a H(4,8) FEC. The color of the indicator gives the status of header
|
|||
- **Blue**: recovered error
|
||||
- **Green**: no errors
|
||||
|
||||
<h4>A.13: Header CRC indicator</h4>
|
||||
<h4>A.13: Header CRC indicator (LoRa)</h4>
|
||||
|
||||
The header has a one byte CRC. The color of this indicator gives the CRC status:
|
||||
|
||||
- **Green**: CRC OK
|
||||
- **Red**: CRC error
|
||||
|
||||
<h4>A.14: Payload FEC indicator</h4>
|
||||
<h4>A.14: Payload FEC indicator (LoRa and FT)</h4>
|
||||
|
||||
The color of the indicator gives the status of payload parity checks:
|
||||
|
||||
- **Grey**: undefined
|
||||
- **Red**: unrecoverable error. H(4,7) cannot distinguish between recoverable and unrecoverable error. Therefore this is never displayed for H(4,7)
|
||||
- **Blue**: recovered error
|
||||
- **Red**: unrecoverable error. H(4,7) cannot distinguish between recoverable and unrecoverable error. Therefore this is never displayed for H(4,7). For FT it means that LDPC decoding failed.
|
||||
- **Blue**: recovered error (LoRa only)
|
||||
- **Green**: no errors
|
||||
|
||||
<h4>A.15: Payload CRC indicator</h4>
|
||||
<h4>A.15: Payload CRC indicator (LoRa and FT)</h4>
|
||||
|
||||
The payload can have a two byte CRC. The color of this indicator gives the CRC status:
|
||||
|
||||
- **Grey**: No CRC
|
||||
- **Grey**: No CRC (LoRa)
|
||||
- **Green**: CRC OK
|
||||
- **Red**: CRC error
|
||||
|
||||
|
@ -216,7 +218,7 @@ Use this push button to clear the message window (12)
|
|||
|
||||
<h3>12: Message window</h3>
|
||||
|
||||
This is where the message and status data are displayed. The display varies if the coding scheme is purely text based (TTY, ASCII) or text/binary mixed based (LoRa). The text vs binary consideration concerns the content of the message not the way it is transmitted on air that is by itself binary.
|
||||
This is where the message and status data are displayed. The display varies if the coding scheme is purely text based (TTY, ASCII, FT) or text/binary mixed based (LoRa). The text vs binary consideration concerns the content of the message not the way it is transmitted on air that is by itself binary.
|
||||
|
||||
<h4>12.a: Text messages</h4>
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ Use this dial to flip through standard DATV symbol rates: 25, 33, 66, 125, 250,
|
|||
|
||||
<h5>B.2a.4: FEC rate</h5>
|
||||
|
||||
Dpends on the standard and modulation
|
||||
Depends on the standard and modulation
|
||||
|
||||
- DVB-S with all modulations: 1/2 , 2/3 , 3/4, 5/6 and 7/8.
|
||||
- DVB-S2 and QPSK: 1/4, 1/3, 2/5, 1/2, 3/5, 2/3, 3/4, 4/5, 5/6, 8/9, 9/10
|
||||
|
|
|
@ -736,7 +736,7 @@
|
|||
<string>Light Batt</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Market light battery condtion</string>
|
||||
<string>Market light battery condition</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
This plugin can be used to demodulate End-of-Train packets. These are packets transmitted by an [End-of-Train Device](https://en.wikipedia.org/wiki/End-of-train_device),
|
||||
that can be found on some trains.
|
||||
It transmits information about whether motion is detected, brake pressue, whether the marker light is on and battery information.
|
||||
It transmits information about whether motion is detected, brake pressure, whether the marker light is on and battery information.
|
||||
|
||||
* Frequency: 457.9375 MHz (North America, India), 477.7 MHz (Australia) and 450.2625 MHz (New Zealand).
|
||||
* Modulation: FSK, 1800Hz space, 1200 mark, +-3kHz deviation.
|
||||
|
@ -93,7 +93,7 @@ The received packets table displays the contents of the packets that have been r
|
|||
* Disc - Discretionary bit that is used for varying data by different vendors.
|
||||
* Valve - Valve circuit status (Ok or Fail).
|
||||
* Conf - Confirmation indicator.
|
||||
* Turbine - Air tubine equiped.
|
||||
* Turbine - Air turbine equipped.
|
||||
* Motion - Whether motion is detected (i.e. is the rear of the train is moving).
|
||||
* Light Batt - Marker light battery condition (Ok or Low).
|
||||
* Light - Marker light status (On or off).
|
||||
|
|
|
@ -390,7 +390,7 @@ bool M17DemodProcessor::decode_packet(modemm17::M17FrameDecoder::packet_buffer_t
|
|||
<< " Via: " << ax25.m_via
|
||||
<< " Type: " << ax25.m_type
|
||||
<< " PID: " << ax25.m_pid
|
||||
<< " Data: " << ax25.m_dataASCII;
|
||||
<< " Data: " << QString::fromUtf8(ax25.m_data);
|
||||
|
||||
if (m_demodInputMessageQueue)
|
||||
{
|
||||
|
@ -402,7 +402,7 @@ bool M17DemodProcessor::decode_packet(modemm17::M17FrameDecoder::packet_buffer_t
|
|||
ax25.m_via,
|
||||
ax25.m_type,
|
||||
ax25.m_pid,
|
||||
ax25.m_dataASCII
|
||||
ax25.m_data
|
||||
);
|
||||
msg->getPacket() = packet;
|
||||
m_demodInputMessageQueue->push(msg);
|
||||
|
|
|
@ -216,7 +216,7 @@ bool PacketDemod::handleMessage(const Message& cmd)
|
|||
<< "\"" << ax25.m_via << "\","
|
||||
<< ax25.m_type << ","
|
||||
<< ax25.m_pid << ","
|
||||
<< "\"" << ax25.m_dataASCII << "\","
|
||||
<< "\"" << QString::fromUtf8(ax25.m_data) << "\","
|
||||
<< "\"" << ax25.m_dataHex << "\"\n";
|
||||
}
|
||||
else
|
||||
|
@ -348,7 +348,7 @@ void PacketDemod::applySettings(const PacketDemodSettings& settings, bool force)
|
|||
if (newFile)
|
||||
{
|
||||
// Write header
|
||||
m_logStream << "Date,Time,Data,From,To,Via,Type,PID,Data ASCII,Data Hex\n";
|
||||
m_logStream << "Date,Time,Data,From,To,Via,Type,PID,Data UTF-8,Data Hex\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -59,7 +59,7 @@ void PacketDemodGUI::resizeTable()
|
|||
ui->packets->setItem(row, PACKET_COL_VIA, new QTableWidgetItem("123456-15-"));
|
||||
ui->packets->setItem(row, PACKET_COL_TYPE, new QTableWidgetItem("Type-"));
|
||||
ui->packets->setItem(row, PACKET_COL_PID, new QTableWidgetItem("PID-"));
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_STRING, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_HEX, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
|
||||
ui->packets->resizeColumnsToContents();
|
||||
ui->packets->removeRow(row);
|
||||
|
@ -168,7 +168,7 @@ void PacketDemodGUI::packetReceived(const QByteArray& packet, QDateTime dateTime
|
|||
QTableWidgetItem *viaItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *typeItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *pidItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *dataASCIIItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *dataStringItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *dataHexItem = new QTableWidgetItem();
|
||||
ui->packets->setItem(row, PACKET_COL_DATE, dateItem);
|
||||
ui->packets->setItem(row, PACKET_COL_TIME, timeItem);
|
||||
|
@ -177,7 +177,7 @@ void PacketDemodGUI::packetReceived(const QByteArray& packet, QDateTime dateTime
|
|||
ui->packets->setItem(row, PACKET_COL_VIA, viaItem);
|
||||
ui->packets->setItem(row, PACKET_COL_TYPE, typeItem);
|
||||
ui->packets->setItem(row, PACKET_COL_PID, pidItem);
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, dataASCIIItem);
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_STRING, dataStringItem);
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_HEX, dataHexItem);
|
||||
dateItem->setText(dateTime.date().toString());
|
||||
timeItem->setText(dateTime.time().toString());
|
||||
|
@ -186,7 +186,7 @@ void PacketDemodGUI::packetReceived(const QByteArray& packet, QDateTime dateTime
|
|||
viaItem->setText(ax25.m_via);
|
||||
typeItem->setText(ax25.m_type);
|
||||
pidItem->setText(ax25.m_pid);
|
||||
dataASCIIItem->setText(ax25.m_dataASCII);
|
||||
dataStringItem->setText(QString::fromUtf8(ax25.m_data)); // Should possibly support different encodings here. PacketMod uses UTF8.
|
||||
dataHexItem->setText(ax25.m_dataHex);
|
||||
filterRow(row);
|
||||
ui->packets->setSortingEnabled(true);
|
||||
|
|
|
@ -116,7 +116,7 @@ private:
|
|||
PACKET_COL_VIA,
|
||||
PACKET_COL_TYPE,
|
||||
PACKET_COL_PID,
|
||||
PACKET_COL_DATA_ASCII,
|
||||
PACKET_COL_DATA_STRING,
|
||||
PACKET_COL_DATA_HEX
|
||||
};
|
||||
|
||||
|
|
|
@ -736,10 +736,10 @@
|
|||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Data (ASCII)</string>
|
||||
<string>Data</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Packet data as ASCII</string>
|
||||
<string>Packet data as UTF-8 character string</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
|
@ -757,9 +757,10 @@
|
|||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
<class>RollupContents</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/rollupcontents.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDialZ</class>
|
||||
|
@ -768,10 +769,9 @@
|
|||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>RollupContents</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/rollupcontents.h</header>
|
||||
<container>1</container>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LevelMeterSignalDB</class>
|
||||
|
|
|
@ -92,5 +92,5 @@ The received packets table displays the contents of the packets that have been r
|
|||
* Via - List of addresses of repeaters the packet has passed through or directed via.
|
||||
* Type - The AX.25 frame type.
|
||||
* PID - Protocol Identifier.
|
||||
* Data (ASCII) - The AX.25 information field displayed as ASCII.
|
||||
* Data - The AX.25 information field displayed as UTF-8 character string.
|
||||
* Data (Hex) - The AX.25 information field displayed as hexadecimal.
|
||||
|
|
|
@ -879,7 +879,7 @@ void RadiosondeDemodGUI::on_logOpen_clicked()
|
|||
QStringList cols;
|
||||
|
||||
QList<ObjectPipe*> radiosondePipes;
|
||||
MainCore::instance()->getMessagePipes().getMessagePipes(this, "radiosonde", radiosondePipes);
|
||||
MainCore::instance()->getMessagePipes().getMessagePipes(m_radiosondeDemod, "radiosonde", radiosondePipes);
|
||||
|
||||
while (!cancelled && CSV::readRow(in, &cols))
|
||||
{
|
||||
|
|
|
@ -159,7 +159,7 @@ Use this button to toggle noise reduction on/off. Right click on this button to
|
|||
|
||||
<h4>13.4.1: Noise reduction scheme</h4>
|
||||
|
||||
Use this combo box to choose the noise reduction scheme among the follwing:
|
||||
Use this combo box to choose the noise reduction scheme among the following:
|
||||
|
||||
- **Average**: calculates the average of magnitudes of the FFT (PSD) and sets the magnitude threshold to this average multiplied by a factor that can be set with the control next (13.4.2).
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ Specifies the channel (such as an AM, NFM or DSD Demod), by device set and chann
|
|||
|
||||
<h3>2: Minimum frequency shift from center frequency of reception for channel</h3>
|
||||
|
||||
Use the wheels of keyboard to adjust the minimim frequency shift in Hz from the center frequency of reception for the channel (1).
|
||||
Use the wheels of keyboard to adjust the minimum frequency shift in Hz from the center frequency of reception for the channel (1).
|
||||
|
||||
This setting is typically used to avoid having the channel (1) centered at DC, which can be problematic for some demodulators used with SDRs with a DC spike.
|
||||
|
||||
|
@ -34,7 +34,7 @@ Power threshold in dB that determines whether a frequency is active or not.
|
|||
|
||||
Specifies the time in milliseconds that the Frequency Scanner should wait after adjusting the device center frequency, before starting a measurement.
|
||||
This time should take in to account PLL settle time and the device to host transfer latency, so that the measurement only starts when IQ data
|
||||
that corresponds to the set frequency is being recieved.
|
||||
that corresponds to the set frequency is being received.
|
||||
|
||||
<h3>6: t_s - Scan time</h3>
|
||||
|
||||
|
@ -93,7 +93,7 @@ The frequency table contains the list of frequencies to be scanned, along with r
|
|||
|
||||
- Freq (Hz): Specifies the channel center frequencies to be scanned. Values should be entered in Hertz.
|
||||
- Annotation: An annotation (description) for the frequency, that is obtained from the closest matching [annotation marker](../../../sdrgui/gui/spectrummarkers.md) in the Main Spectrum.
|
||||
- Enable: Determines whether the frequency will be scanned. This can be used to temporaily disable frequencies you aren't interested in.
|
||||
- Enable: Determines whether the frequency will be scanned. This can be used to temporarily disable frequencies you aren't interested in.
|
||||
- Power (dB): Displays the measured power in decibels from the last scan. The cell will have a green background if the power was above the threshold (4).
|
||||
- Active Count: Displays the number of scans in which the power for this frequency was above the threshold (4). This allows you to see which frequencies are commonly in use.
|
||||
- Notes: Available for user-entry of notes/information about this frequency.
|
||||
|
|
|
@ -198,7 +198,7 @@ void HeatMapSink::applySettings(const HeatMapSettings& settings, bool force)
|
|||
|| (settings.m_sampleRate != m_settings.m_sampleRate)
|
||||
|| force)
|
||||
{
|
||||
m_averageCnt = (int)((settings.m_averagePeriodUS * settings.m_sampleRate / 1e6));
|
||||
m_averageCnt = (int)((settings.m_averagePeriodUS * (qint64)settings.m_sampleRate / 1e6));
|
||||
// For low sample rates, we want a small buffer, so scope update isn't too slow
|
||||
if (settings.m_sampleRate < 100) {
|
||||
m_sampleBufferSize = 1;
|
||||
|
|
|
@ -84,6 +84,7 @@ void ChirpChatModEncoderFT::encodeMsg(
|
|||
|
||||
if ((i % nbSymbolBits) == (nbSymbolBits - 1))
|
||||
{
|
||||
symbol = symbol ^ (symbol >> 1); // Gray code
|
||||
symbols.push_back(symbol);
|
||||
symbol = 0;
|
||||
}
|
||||
|
@ -96,7 +97,7 @@ void ChirpChatModEncoderFT::encodeTextMsg(const QString& text, int a174[])
|
|||
std::fill(a77, a77 + 77, 0);
|
||||
QString sentMsg = text.rightJustified(13, ' ', true);
|
||||
|
||||
if (!FT8::Packing::packfree(a77, sentMsg.toStdString()))
|
||||
if (!FT8::Packing::packfree(a77, sentMsg.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeTextMsg: failed to encode free text message (%s)", qPrintable(sentMsg));
|
||||
return;
|
||||
|
@ -109,21 +110,27 @@ void ChirpChatModEncoderFT::encodeMsgBeaconOrCQ(const QString& myCall, const QSt
|
|||
{
|
||||
int c28_1, c28_2, g15;
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_1, shorthand.toStdString())) //
|
||||
if (!FT8::Packing::packcall_std(c28_1, shorthand.toUpper().toStdString())) //
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call1 (%s)", qPrintable(shorthand));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeaconOrCQ: failed to encode call1 (%s)", qPrintable(shorthand));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toStdString()))
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeaconOrCQ: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, myLocator.toStdString()))
|
||||
if (myLocator.size() < 4)
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode locator (%s)", qPrintable(myLocator));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeaconOrCQ: locator invalid (%s)", qPrintable(myLocator));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, myLocator.left(4).toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeaconOrCQ: failed to encode locator (%s)", qPrintable(myLocator));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -137,21 +144,27 @@ void ChirpChatModEncoderFT::encodeMsgReply(const QString& myCall, const QString&
|
|||
{
|
||||
int c28_1, c28_2, g15;
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toStdString())) //
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toUpper().toStdString())) //
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReply: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toStdString()))
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReply: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, myLocator.toStdString()))
|
||||
if (myLocator.size() < 4)
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode locator (%s)", qPrintable(myLocator));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReply: locator invalid (%s)", qPrintable(myLocator));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, myLocator.left(4).toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReply: failed to encode locator (%s)", qPrintable(myLocator));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -165,21 +178,21 @@ void ChirpChatModEncoderFT::encodeMsgReport(const QString& myCall, const QString
|
|||
{
|
||||
int c28_1, c28_2, g15;
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toStdString())) //
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toUpper().toStdString())) //
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReport: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toStdString()))
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReport: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, myReport.toStdString()))
|
||||
if (!FT8::Packing::packgrid(g15, myReport.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode report (%s)", qPrintable(myReport));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgReport: failed to encode report (%s)", qPrintable(myReport));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -193,21 +206,21 @@ void ChirpChatModEncoderFT::encodeMsgFinish(const QString& myCall, const QString
|
|||
{
|
||||
int c28_1, c28_2, g15;
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toStdString())) //
|
||||
if (!FT8::Packing::packcall_std(c28_1, urCall.toUpper().toStdString())) //
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgFinish: failed to encode call1 (%s)", qPrintable(urCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toStdString()))
|
||||
if (!FT8::Packing::packcall_std(c28_2, myCall.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgFinish: failed to encode call2 (%s)", qPrintable(myCall));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!FT8::Packing::packgrid(g15, shorthand.toStdString()))
|
||||
if (!FT8::Packing::packgrid(g15, shorthand.toUpper().toStdString()))
|
||||
{
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgBeacon: failed to encode shorthand (%s)", qPrintable(shorthand));
|
||||
qDebug("ChirpChatModEncoderFT::encodeMsgFinish: failed to encode shorthand (%s)", qPrintable(shorthand));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
const int ChirpChatModSettings::bandwidths[] = {
|
||||
325, // 384k / 1024
|
||||
488, // 500k / 1024
|
||||
750, // 384k / 512
|
||||
1500, // 384k / 256
|
||||
2604, // 333k / 128
|
||||
|
@ -55,7 +56,7 @@ const int ChirpChatModSettings::bandwidths[] = {
|
|||
400000, // 400k / 1
|
||||
500000 // 500k / 1
|
||||
};
|
||||
const int ChirpChatModSettings::nbBandwidths = 3*8 + 3;
|
||||
const int ChirpChatModSettings::nbBandwidths = 3*8 + 4;
|
||||
const int ChirpChatModSettings::oversampling = 4;
|
||||
|
||||
ChirpChatModSettings::ChirpChatModSettings() :
|
||||
|
|
|
@ -67,6 +67,7 @@ Thus available bandwidths are:
|
|||
- **2604** (333333 / 128) Hz not in LoRa standard
|
||||
- **1500** (384000 / 256) Hz not in LoRa standard
|
||||
- **750** (384000 / 512) Hz not in LoRa standard
|
||||
- **488** (500000 / 1024) Hz not in LoRa standard
|
||||
- **375** (384000 / 1024) Hz not in LoRa standard
|
||||
|
||||
The ChirpChat signal is oversampled by four therefore it needs a baseband of at least four times the bandwidth. This drives the maximum value on the slider automatically.
|
||||
|
|
|
@ -594,8 +594,9 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt
|
|||
// PID
|
||||
*p++ = m_settings.m_ax25PID;
|
||||
// Data
|
||||
len = data.length();
|
||||
memcpy(p, data.toUtf8(), len);
|
||||
QByteArray dataBytes = data.toUtf8();
|
||||
len = dataBytes.length();
|
||||
memcpy(p, dataBytes, len);
|
||||
p += len;
|
||||
// CRC (do not include flags)
|
||||
crc.calculate(crc_start, p-crc_start);
|
||||
|
|
|
@ -74,7 +74,7 @@ Enter the routing for the packet. To have the packet repeated by digipeaters, us
|
|||
|
||||
<h3>16: Data</h3>
|
||||
|
||||
The packet of data to send. To send an APRS status message, use the format <tt>>Status</tt>. The APRS specification can be found at: http://www.aprs.org/doc/APRS101.PDF. APRS messages can be tracked on https://aprs.fi
|
||||
The packet of data to send. This is encoded using UTF-8. To send an APRS status message, use the format <tt>>Status</tt>. The APRS specification can be found at: http://www.aprs.org/doc/APRS101.PDF. APRS messages can be tracked on https://aprs.fi
|
||||
|
||||
<h3>17: TX</h3>
|
||||
|
||||
|
|
|
@ -80,7 +80,7 @@ The substitutions are applied when the Transmit Settings dialog is closed.
|
|||
|
||||
<h3>14: Transmitted Text</h3>
|
||||
|
||||
The trasnmitted text area shows characters as they are transmitted.
|
||||
The transmitted text area shows characters as they are transmitted.
|
||||
|
||||
Holding the cursor over an acronym may show a tooltip with the decoded acronym.
|
||||
|
||||
|
|
|
@ -117,7 +117,7 @@ The substitutions are applied when the Transmit Settings dialog is closed.
|
|||
|
||||
<h3>21: Transmitted Text</h3>
|
||||
|
||||
The trasnmitted text area shows characters as they are transmitted.
|
||||
The transmitted text area shows characters as they are transmitted.
|
||||
|
||||
Holding the cursor over an acronym may show a tooltip with the decoded acronym.
|
||||
|
||||
|
|
|
@ -120,3 +120,9 @@ if (ENABLE_FEATURE_REMOTECONTROL)
|
|||
else()
|
||||
message(STATUS "Not building remotecontrol (ENABLE_FEATURE_REMOTECONTROL=${ENABLE_FEATURE_REMOTECONTROL})")
|
||||
endif()
|
||||
|
||||
if (ENABLE_FEATURE_SID)
|
||||
add_subdirectory(sid)
|
||||
else()
|
||||
message(STATUS "Not building SID (ENABLED_FEATURE_SID=${ENABLED_FEATURE_SID})")
|
||||
endif()
|
||||
|
|
|
@ -408,7 +408,7 @@ bool APRSGUI::handleMessage(const Message& message)
|
|||
else
|
||||
{
|
||||
qDebug() << "APRSGUI::handleMessage: Failed to decode as APRS";
|
||||
qDebug() << ax25.m_from << " " << ax25.m_to << " " << ax25.m_via << " " << ax25.m_type << " " << ax25.m_pid << " "<< ax25.m_dataASCII;
|
||||
qDebug() << "From:" << ax25.m_from << "To:" << ax25.m_to << "Via:" << ax25.m_via << "Type:" << ax25.m_type << "PID:" << ax25.m_pid << "Data:" << QString::fromLatin1(ax25.m_data);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
|
@ -102,20 +102,25 @@ bool APRSWorker::handleMessage(const Message& cmd)
|
|||
{
|
||||
MainCore::MsgPacket& report = (MainCore::MsgPacket&) cmd;
|
||||
AX25Packet ax25;
|
||||
APRSPacket *aprs = new APRSPacket();
|
||||
|
||||
if (ax25.decode(report.getPacket()))
|
||||
{
|
||||
if (aprs->decode(ax25))
|
||||
APRSPacket aprs;
|
||||
|
||||
// #2029 - Forward data even if we can't decode it fully
|
||||
aprs.decode(ax25);
|
||||
|
||||
if (!aprs.m_data.isEmpty())
|
||||
{
|
||||
// See: http://www.aprs-is.net/IGateDetails.aspx for gating rules
|
||||
if (!aprs->m_via.contains("TCPIP")
|
||||
&& !aprs->m_via.contains("TCPXX")
|
||||
&& !aprs->m_via.contains("NOGATE")
|
||||
&& !aprs->m_via.contains("RFONLY"))
|
||||
if (!aprs.m_via.contains("TCPIP")
|
||||
&& !aprs.m_via.contains("TCPXX")
|
||||
&& !aprs.m_via.contains("NOGATE")
|
||||
&& !aprs.m_via.contains("RFONLY"))
|
||||
{
|
||||
aprs->m_dateTime = report.getDateTime();
|
||||
QString igateMsg = aprs->toTNC2(m_settings.m_igateCallsign);
|
||||
send(igateMsg.toUtf8(), igateMsg.length());
|
||||
aprs.m_dateTime = report.getDateTime();
|
||||
QByteArray igateMsg = aprs.toTNC2(m_settings.m_igateCallsign);
|
||||
send(igateMsg.data(), igateMsg.length());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +212,7 @@ void APRSWorker::recv()
|
|||
if (!m_loggedIn)
|
||||
{
|
||||
// Log in with callsign and passcode
|
||||
QString login = QString("user %1 pass %2 vers SDRangel 6.4.0%3\r\n").arg(m_settings.m_igateCallsign).arg(m_settings.m_igatePasscode).arg(m_settings.m_igateFilter.isEmpty() ? "" : QString(" filter %1").arg(m_settings.m_igateFilter));
|
||||
QString login = QString("user %1 pass %2 vers SDRangel 7.19.2%3\r\n").arg(m_settings.m_igateCallsign).arg(m_settings.m_igatePasscode).arg(m_settings.m_igateFilter.isEmpty() ? "" : QString(" filter %1").arg(m_settings.m_igateFilter));
|
||||
send(login.toLatin1(), login.length());
|
||||
m_loggedIn = true;
|
||||
if (m_msgQueueToFeature)
|
||||
|
|
|
@ -54,7 +54,6 @@ if(NOT SERVER_MODE)
|
|||
mapibpbeacondialog.ui
|
||||
mapradiotimedialog.cpp
|
||||
mapradiotimedialog.ui
|
||||
mapcolordialog.cpp
|
||||
mapmodel.cpp
|
||||
mapitem.cpp
|
||||
mapwebsocketserver.cpp
|
||||
|
@ -75,7 +74,6 @@ if(NOT SERVER_MODE)
|
|||
mapbeacondialog.h
|
||||
mapibpbeacon.h
|
||||
mapradiotimedialog.h
|
||||
mapcolordialog.h
|
||||
mapmodel.h
|
||||
mapitem.h
|
||||
mapwebsocketserver.h
|
||||
|
|
|
@ -275,3 +275,13 @@ void CesiumInterface::setPosition(const QGeoCoordinate& position)
|
|||
{
|
||||
m_czml.setPosition(position);
|
||||
}
|
||||
|
||||
void CesiumInterface::save(const QString& filename, const QString& dataDir)
|
||||
{
|
||||
QJsonObject obj {
|
||||
{"command", "save"},
|
||||
{"filename", filename},
|
||||
{"dataDir", dataDir}
|
||||
};
|
||||
send(obj);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,6 @@ public:
|
|||
bool m_reverse;
|
||||
bool m_loop;
|
||||
bool m_stop; // Stop looped animation
|
||||
float m_delay; // Delay in seconds before animation starts
|
||||
float m_startOffset; // [0..1] What point to start playing animation
|
||||
float m_duration; // How long to play animation for
|
||||
float m_multiplier; // Speed to play animation at
|
||||
|
@ -80,6 +79,7 @@ public:
|
|||
void update(PolygonMapItem *mapItem);
|
||||
void update(PolylineMapItem *mapItem);
|
||||
void setPosition(const QGeoCoordinate& position);
|
||||
void save(const QString& filename, const QString& dataDir);
|
||||
|
||||
protected:
|
||||
|
||||
|
|
|
@ -264,7 +264,9 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
|||
const QStringList heightReferences = {"NONE", "CLAMP_TO_GROUND", "RELATIVE_TO_GROUND", "NONE"};
|
||||
QString dt;
|
||||
|
||||
if (mapItem->m_takenTrackDateTimes.size() > 0) {
|
||||
if (mapItem->m_availableFrom.isValid()) {
|
||||
dt = mapItem->m_availableFrom.toString(Qt::ISODateWithMs);
|
||||
} else if (mapItem->m_takenTrackDateTimes.size() > 0) {
|
||||
dt = mapItem->m_takenTrackDateTimes.last()->toString(Qt::ISODateWithMs);
|
||||
} else {
|
||||
dt = QDateTime::currentDateTimeUtc().toString(Qt::ISODateWithMs);
|
||||
|
@ -580,6 +582,14 @@ QJsonObject CZML::update(ObjectMapItem *mapItem, bool isTarget, bool isSelected)
|
|||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mapItem->m_availableUntil.isValid())
|
||||
{
|
||||
QString period = QString("%1/%2").arg(m_ids[id]).arg(mapItem->m_availableUntil.toString(Qt::ISODateWithMs));
|
||||
obj.insert("availability", period);
|
||||
}
|
||||
}
|
||||
m_lastPosition.insert(id, coords);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -364,6 +364,52 @@
|
|||
["railways", railwaysLayer]
|
||||
]);
|
||||
|
||||
function downloadBlob(filename, blob) {
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(blob, filename);
|
||||
} else {
|
||||
const elem = window.document.createElement("a");
|
||||
elem.href = window.URL.createObjectURL(blob);
|
||||
elem.download = filename;
|
||||
document.body.appendChild(elem);
|
||||
elem.click();
|
||||
document.body.removeChild(elem);
|
||||
}
|
||||
}
|
||||
|
||||
function downloadText(filename, text) {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
var dataDir = ""; // Directory where 3D models are stored
|
||||
function modelCallback(modelGraphics, time, externalFiles) {
|
||||
const resource = modelGraphics.uri.getValue(time);
|
||||
console.log("modelcallback " + resource);
|
||||
|
||||
const regex = /http:\/\/127.0.0.1:\d+/;
|
||||
|
||||
var file = resource.url.replace(regex, dataDir);
|
||||
|
||||
// KML only supports Collada files. User will have to convert the models if required
|
||||
file = file.replace(/glb$/, "dae");
|
||||
file = file.replace(/gltf$/, "dae");
|
||||
|
||||
if (navigator.platform.indexOf('Win') > -1) {
|
||||
file = file.replace(/\//g, "\\");
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
// Use WebSockets for handling commands from MapPlugin
|
||||
// (CZML doesn't support camera control, for example)
|
||||
// and sending events back to it
|
||||
|
@ -469,30 +515,40 @@
|
|||
viewer.scene.postProcessStages.fxaa.enabled = false;
|
||||
}
|
||||
} else if (command.command == "showMUF") {
|
||||
if (mufGeoJSONStream != null) {
|
||||
viewer.dataSources.remove(mufGeoJSONStream, true);
|
||||
mufGeoJSONStream = null;
|
||||
}
|
||||
if (command.show == true) {
|
||||
viewer.dataSources.add(
|
||||
Cesium.GeoJsonDataSource.load(
|
||||
"muf.geojson",
|
||||
{ describe: describeMUF }
|
||||
)
|
||||
).then(function (dataSource) { mufGeoJSONStream = dataSource; });
|
||||
).then(function (dataSource) {
|
||||
if (mufGeoJSONStream != null) {
|
||||
viewer.dataSources.remove(mufGeoJSONStream, true);
|
||||
mufGeoJSONStream = null;
|
||||
}
|
||||
mufGeoJSONStream = dataSource;
|
||||
});
|
||||
} else {
|
||||
viewer.dataSources.remove(mufGeoJSONStream, true);
|
||||
mufGeoJSONStream = null;
|
||||
}
|
||||
} else if (command.command == "showfoF2") {
|
||||
if (foF2GeoJSONStream != null) {
|
||||
viewer.dataSources.remove(foF2GeoJSONStream, true);
|
||||
foF2GeoJSONStream = null;
|
||||
}
|
||||
if (command.show == true) {
|
||||
viewer.dataSources.add(
|
||||
Cesium.GeoJsonDataSource.load(
|
||||
"fof2.geojson",
|
||||
{ describe: describefoF2 }
|
||||
)
|
||||
).then(function (dataSource) { foF2GeoJSONStream = dataSource; });
|
||||
).then(function (dataSource) {
|
||||
if (foF2GeoJSONStream != null) {
|
||||
viewer.dataSources.remove(foF2GeoJSONStream, true);
|
||||
foF2GeoJSONStream = null;
|
||||
}
|
||||
foF2GeoJSONStream = dataSource;
|
||||
});
|
||||
} else {
|
||||
viewer.dataSources.remove(foF2GeoJSONStream, true);
|
||||
foF2GeoJSONStream = null;
|
||||
}
|
||||
} else if (command.command == "showLayer") {
|
||||
layers.get(command.layer).show = command.show;
|
||||
|
@ -639,7 +695,7 @@
|
|||
czmlStream.process(command);
|
||||
} else {
|
||||
var promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, [position]);
|
||||
Cesium.when(promise, function(updatedPositions) {
|
||||
Cesium.when(promise, function (updatedPositions) {
|
||||
if (height < updatedPositions[0].height) {
|
||||
if (size == 3) {
|
||||
command.position.cartographicDegrees[2] = updatedPositions[0].height;
|
||||
|
@ -648,7 +704,7 @@
|
|||
}
|
||||
}
|
||||
czmlStream.process(command);
|
||||
}, function() {
|
||||
}, function () {
|
||||
console.log(`Terrain doesn't support sampleTerrainMostDetailed`);
|
||||
czmlStream.process(command);
|
||||
});
|
||||
|
@ -657,47 +713,47 @@
|
|||
console.log(`Can't currently use altitudeReference when more than one position`);
|
||||
czmlStream.process(command);
|
||||
}
|
||||
} else if ( (command.hasOwnProperty('polygon') && command.polygon.hasOwnProperty('altitudeReference'))
|
||||
|| (command.hasOwnProperty('polyline') && command.polyline.hasOwnProperty('altitudeReference'))) {
|
||||
} else if ((command.hasOwnProperty('polygon') && command.polygon.hasOwnProperty('altitudeReference'))
|
||||
|| (command.hasOwnProperty('polyline') && command.polyline.hasOwnProperty('altitudeReference'))) {
|
||||
// Support per vertex height reference in polygons and CLIP_TO_GROUND in polylines
|
||||
var prim = command.hasOwnProperty('polygon') ? command.polygon : command.polyline;
|
||||
var clipToGround = prim.altitudeReference == "CLIP_TO_GROUND";
|
||||
var clampToGround = prim.altitudeReference == "CLAMP_TO_GROUND";
|
||||
var size = prim.positions.cartographicDegrees.length;
|
||||
var positionCount = size/3;
|
||||
var positionCount = size / 3;
|
||||
var positions = new Array(positionCount);
|
||||
if (viewer.terrainProvider instanceof Cesium.EllipsoidTerrainProvider) {
|
||||
if (clampToGround) {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
prim.positions.cartographicDegrees[i*3+2] = 0;
|
||||
prim.positions.cartographicDegrees[i * 3 + 2] = 0;
|
||||
}
|
||||
} else if (clipToGround) {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
if (prim.positions.cartographicDegrees[i*3+2] < 0) {
|
||||
prim.positions.cartographicDegrees[i*3+2] = 0;
|
||||
if (prim.positions.cartographicDegrees[i * 3 + 2] < 0) {
|
||||
prim.positions.cartographicDegrees[i * 3 + 2] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
czmlStream.process(command);
|
||||
} else {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
positions[i] = Cesium.Cartographic.fromDegrees(prim.positions.cartographicDegrees[i*3+0], prim.positions.cartographicDegrees[i*3+1]);
|
||||
positions[i] = Cesium.Cartographic.fromDegrees(prim.positions.cartographicDegrees[i * 3 + 0], prim.positions.cartographicDegrees[i * 3 + 1]);
|
||||
}
|
||||
var promise = Cesium.sampleTerrainMostDetailed(viewer.terrainProvider, positions);
|
||||
Cesium.when(promise, function(updatedPositions) {
|
||||
Cesium.when(promise, function (updatedPositions) {
|
||||
if (clampToGround) {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
prim.positions.cartographicDegrees[i*3+2] = updatedPositions[i].height;
|
||||
prim.positions.cartographicDegrees[i * 3 + 2] = updatedPositions[i].height;
|
||||
}
|
||||
} else if (clipToGround) {
|
||||
for (let i = 0; i < positionCount; i++) {
|
||||
if (prim.positions.cartographicDegrees[i*3+2] < updatedPositions[i].height) {
|
||||
prim.positions.cartographicDegrees[i*3+2] = updatedPositions[i].height;
|
||||
if (prim.positions.cartographicDegrees[i * 3 + 2] < updatedPositions[i].height) {
|
||||
prim.positions.cartographicDegrees[i * 3 + 2] = updatedPositions[i].height;
|
||||
}
|
||||
}
|
||||
}
|
||||
czmlStream.process(command);
|
||||
}, function() {
|
||||
}, function () {
|
||||
console.log(`Terrain doesn't support sampleTerrainMostDetailed`);
|
||||
czmlStream.process(command);
|
||||
});
|
||||
|
@ -705,7 +761,20 @@
|
|||
} else {
|
||||
czmlStream.process(command);
|
||||
}
|
||||
|
||||
} else if (command.command == "save") {
|
||||
// Export to kml/kmz
|
||||
dataDir = command.dataDir;
|
||||
Cesium.exportKml({
|
||||
entities: czmlStream.entities,
|
||||
kmz: command.filename.endsWith("kmz"),
|
||||
modelCallback: modelCallback
|
||||
}).then(function (result) {
|
||||
if (command.filename.endsWith("kmz")) {
|
||||
downloadBlob(command.filename, result.kmz);
|
||||
} else {
|
||||
downloadText(command.filename, result.kml);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log(`Unknown command ${command.command}`);
|
||||
}
|
||||
|
@ -759,10 +828,13 @@
|
|||
Cesium.knockout.getObservable(viewer.clockViewModel, 'multiplier').subscribe(function(multiplier) {
|
||||
reportClock();
|
||||
});
|
||||
// This is called every frame
|
||||
// This is called every frame, which is too fast, so instead use setInterval with 1 second period
|
||||
//Cesium.knockout.getObservable(viewer.clockViewModel, 'currentTime').subscribe(function(currentTime) {
|
||||
//reportClock();
|
||||
//});
|
||||
setInterval(function () {
|
||||
reportClock();
|
||||
}, 1000);
|
||||
viewer.timeline.addEventListener('settime', reportClock, false);
|
||||
|
||||
socket.onopen = () => {
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
#include "util/maidenhead.h"
|
||||
#include "util/morse.h"
|
||||
#include "util/navtex.h"
|
||||
#include "util/vlftransmitters.h"
|
||||
#include "maplocationdialog.h"
|
||||
#include "mapmaidenheaddialog.h"
|
||||
#include "mapsettingsdialog.h"
|
||||
|
@ -306,6 +307,9 @@ MapGUI::MapGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *featur
|
|||
connect(ui->web->page(), &QWebEnginePage::loadingChanged, this, &MapGUI::loadingChanged);
|
||||
connect(ui->web, &QWebEngineView::renderProcessTerminated, this, &MapGUI::renderProcessTerminated);
|
||||
#endif
|
||||
|
||||
QWebEngineProfile *profile = QWebEngineProfile::defaultProfile();
|
||||
connect(profile, &QWebEngineProfile::downloadRequested, this, &MapGUI::downloadRequested);
|
||||
#endif
|
||||
|
||||
// Get station position
|
||||
|
@ -489,41 +493,24 @@ void MapGUI::addIBPBeacons()
|
|||
}
|
||||
}
|
||||
|
||||
// https://sidstation.loudet.org/stations-list-en.xhtml
|
||||
// https://core.ac.uk/download/pdf/224769021.pdf -- Table 1
|
||||
// GQD/GQZ callsigns: https://groups.io/g/VLF/message/19212?p=%2C%2C%2C20%2C0%2C0%2C0%3A%3Arecentpostdate%2Fsticky%2C%2C19.6%2C20%2C2%2C0%2C38924431
|
||||
const QList<RadioTimeTransmitter> MapGUI::m_vlfTransmitters = {
|
||||
// Other signals possibly seen: 13800, 19000
|
||||
{"VTX2", 17000, 8.387015, 77.752762, -1}, // South Vijayanarayanam, India
|
||||
{"GQD", 19580, 54.911643, -3.278456, 100}, // Anthorn, UK, Often referred to as GBZ
|
||||
{"NWC", 19800, -21.816325, 114.16546, 1000}, // Exmouth, Aus
|
||||
{"ICV", 20270, 40.922946, 9.731881, 50}, // Isola di Tavolara, Italy (Can be distorted on 3D map if terrain used)
|
||||
{"FTA", 20900, 48.544632, 2.579429, 50}, // Sainte-Assise, France (Satellite imagary obfuscated)
|
||||
{"NPM", 21400, 21.420166, -158.151140, 600}, // Pearl Harbour, Lualuahei, USA (Not seen?)
|
||||
{"HWU", 21750, 46.713129, 1.245248, 200}, // Rosnay, France
|
||||
{"GQZ", 22100, 54.731799, -2.883033, 100}, // Skelton, UK (GVT in paper)
|
||||
{"DHO38", 23400, 53.078900, 7.615000, 300}, // Rhauderfehn, Germany - Off air 7-8 UTC - Not seen on air!
|
||||
{"NAA", 24000, 44.644506, -67.284565, 1000}, // Cutler, Maine, USA
|
||||
{"TFK/NRK", 37500, 63.850365, -22.466773, 100}, // Grindavik, Iceland
|
||||
{"SRC/SHR", 38000, 57.120328, 16.153083, -1}, // Ruda, Sweden
|
||||
};
|
||||
|
||||
void MapGUI::addVLF()
|
||||
{
|
||||
for (int i = 0; i < m_vlfTransmitters.size(); i++)
|
||||
for (int i = 0; i < VLFTransmitters::m_transmitters.size(); i++)
|
||||
{
|
||||
SWGSDRangel::SWGMapItem vlfMapItem;
|
||||
// Need to suffix frequency, as there are multiple becaons with same callsign at different locations
|
||||
QString name = QString("%1").arg(m_vlfTransmitters[i].m_callsign);
|
||||
QString name = QString("%1").arg(VLFTransmitters::m_transmitters[i].m_callsign);
|
||||
vlfMapItem.setName(new QString(name));
|
||||
vlfMapItem.setLatitude(m_vlfTransmitters[i].m_latitude);
|
||||
vlfMapItem.setLongitude(m_vlfTransmitters[i].m_longitude);
|
||||
vlfMapItem.setLatitude(VLFTransmitters::m_transmitters[i].m_latitude);
|
||||
vlfMapItem.setLongitude(VLFTransmitters::m_transmitters[i].m_longitude);
|
||||
vlfMapItem.setAltitude(0.0);
|
||||
vlfMapItem.setImage(new QString("antenna.png"));
|
||||
vlfMapItem.setImageRotation(0);
|
||||
QString text = QString("VLF Transmitter\nCallsign: %1\nFrequency: %2 kHz")
|
||||
.arg(m_vlfTransmitters[i].m_callsign)
|
||||
.arg(m_vlfTransmitters[i].m_frequency/1000.0);
|
||||
.arg(VLFTransmitters::m_transmitters[i].m_callsign)
|
||||
.arg(VLFTransmitters::m_transmitters[i].m_frequency/1000.0);
|
||||
if (VLFTransmitters::m_transmitters[i].m_power > 0) {
|
||||
text.append(QString("\nPower: %1 kW").arg(VLFTransmitters::m_transmitters[i].m_power));
|
||||
}
|
||||
vlfMapItem.setText(new QString(text));
|
||||
vlfMapItem.setModel(new QString("antenna.glb"));
|
||||
vlfMapItem.setFixedPosition(true);
|
||||
|
@ -535,7 +522,6 @@ void MapGUI::addVLF()
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
const QList<RadioTimeTransmitter> MapGUI::m_radioTimeTransmitters = {
|
||||
{"MSF", 60000, 54.9075f, -3.27333f, 17}, // UK
|
||||
{"DCF77", 77500, 50.01611111f, 9.00805556f, 50}, // Germany
|
||||
|
@ -722,12 +708,18 @@ void MapGUI::addIonosonde()
|
|||
m_giro = GIRO::create();
|
||||
if (m_giro)
|
||||
{
|
||||
connect(m_giro, &GIRO::indexUpdated, this, &MapGUI::giroIndexUpdated);
|
||||
connect(m_giro, &GIRO::dataUpdated, this, &MapGUI::giroDataUpdated);
|
||||
connect(m_giro, &GIRO::mufUpdated, this, &MapGUI::mufUpdated);
|
||||
connect(m_giro, &GIRO::foF2Updated, this, &MapGUI::foF2Updated);
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::giroIndexUpdated(const QList<GIRO::DataSet>& data)
|
||||
{
|
||||
(void) data;
|
||||
}
|
||||
|
||||
void MapGUI::giroDataUpdated(const GIRO::GIROStationData& data)
|
||||
{
|
||||
if (!data.m_station.isEmpty())
|
||||
|
@ -761,6 +753,8 @@ void MapGUI::giroDataUpdated(const GIRO::GIROStationData& data)
|
|||
ionosondeStationMapItem.setLabel(new QString(station->m_label));
|
||||
ionosondeStationMapItem.setLabelAltitudeOffset(4.5);
|
||||
ionosondeStationMapItem.setAltitudeReference(1);
|
||||
ionosondeStationMapItem.setAvailableFrom(new QString(data.m_dateTime.toString(Qt::ISODateWithMs)));
|
||||
ionosondeStationMapItem.setAvailableUntil(new QString(data.m_dateTime.addDays(3).toString(Qt::ISODateWithMs))); // Remove after data is too old
|
||||
update(m_map, &ionosondeStationMapItem, "Ionosonde Stations");
|
||||
}
|
||||
}
|
||||
|
@ -786,6 +780,24 @@ void MapGUI::foF2Updated(const QJsonDocument& document)
|
|||
}
|
||||
}
|
||||
|
||||
void MapGUI::updateGIRO(const QDateTime& mapDateTime)
|
||||
{
|
||||
if (m_giro)
|
||||
{
|
||||
if (m_settings.m_displayMUF || m_settings.m_displayfoF2)
|
||||
{
|
||||
QString giroRunId = m_giro->getRunId(mapDateTime);
|
||||
if (m_giroRunId.isEmpty() || (!giroRunId.isEmpty() && (giroRunId != m_giroRunId)))
|
||||
{
|
||||
m_giro->getMUF(giroRunId);
|
||||
m_giro->getMUF(giroRunId);
|
||||
m_giroRunId = giroRunId;
|
||||
m_giroDateTime = mapDateTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::pathUpdated(const QString& radarPath, const QString& satellitePath)
|
||||
{
|
||||
m_radarPath = radarPath;
|
||||
|
@ -1683,6 +1695,7 @@ void MapGUI::displayToolbar()
|
|||
ui->displayNASAGlobalImagery->setVisible(overlayButtons);
|
||||
ui->displayMUF->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->displayfoF2->setVisible(!narrow && m_settings.m_map3DEnabled);
|
||||
ui->save->setVisible(m_settings.m_map3DEnabled);
|
||||
}
|
||||
|
||||
void MapGUI::setEnableOverlay()
|
||||
|
@ -1803,11 +1816,10 @@ void MapGUI::applyMap3DSettings(bool reloadMap)
|
|||
m_polylineMapModel.allUpdated();
|
||||
}
|
||||
MapSettings::MapItemSettings *ionosondeItemSettings = getItemSettings("Ionosonde Stations");
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (ionosondeItemSettings) {
|
||||
m_giro->getDataPeriodically(ionosondeItemSettings->m_enabled ? 2 : 0);
|
||||
}
|
||||
m_giro->getMUFPeriodically(m_settings.m_displayMUF ? 15 : 0);
|
||||
m_giro->getfoF2Periodically(m_settings.m_displayfoF2 ? 15 : 0);
|
||||
#else
|
||||
ui->displayMUF->setVisible(false);
|
||||
ui->displayfoF2->setVisible(false);
|
||||
|
@ -2211,7 +2223,7 @@ void MapGUI::on_displayMUF_clicked(bool checked)
|
|||
m_settings.m_displayMUF = checked;
|
||||
// Only call show if disabling, so we don't get two updates
|
||||
// (as getMUFPeriodically results in a call to showMUF when the data is available)
|
||||
m_giro->getMUFPeriodically(m_settings.m_displayMUF ? 15 : 0);
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (m_cesium && !m_settings.m_displayMUF) {
|
||||
m_cesium->showMUF(m_settings.m_displayMUF);
|
||||
}
|
||||
|
@ -2226,7 +2238,7 @@ void MapGUI::on_displayfoF2_clicked(bool checked)
|
|||
m_displayfoF2->setChecked(checked);
|
||||
}
|
||||
m_settings.m_displayfoF2 = checked;
|
||||
m_giro->getfoF2Periodically(m_settings.m_displayfoF2 ? 15 : 0);
|
||||
m_giro->getIndexPeriodically((m_settings.m_displayMUF || m_settings.m_displayfoF2) ? 15 : 0);
|
||||
if (m_cesium && !m_settings.m_displayfoF2) {
|
||||
m_cesium->showfoF2(m_settings.m_displayfoF2);
|
||||
}
|
||||
|
@ -2442,6 +2454,22 @@ void MapGUI::track3D(const QString& target)
|
|||
}
|
||||
}
|
||||
|
||||
void MapGUI::on_save_clicked()
|
||||
{
|
||||
if (m_cesium)
|
||||
{
|
||||
m_fileDialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||
m_fileDialog.setNameFilter("*.kml *.kmz");
|
||||
if (m_fileDialog.exec())
|
||||
{
|
||||
QStringList fileNames = m_fileDialog.selectedFiles();
|
||||
if (fileNames.size() > 0) {
|
||||
m_cesium->save(fileNames[0], getDataDir());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapGUI::on_deleteAll_clicked()
|
||||
{
|
||||
m_objectMapModel.removeAll();
|
||||
|
@ -2543,6 +2571,7 @@ void MapGUI::receivedCesiumEvent(const QJsonObject &obj)
|
|||
bool canAnimate = obj.value("canAnimate").toBool();
|
||||
bool shouldAnimate = obj.value("shouldAnimate").toBool();
|
||||
m_map->setMapDateTime(mapDateTime, systemDateTime, canAnimate && shouldAnimate ? multiplier : 0.0);
|
||||
updateGIRO(mapDateTime);
|
||||
}
|
||||
}
|
||||
else if (event == "link")
|
||||
|
@ -2715,6 +2744,17 @@ void MapGUI::fullScreenRequested(QWebEngineFullScreenRequest fullScreenRequest)
|
|||
ui->splitter->addWidget(ui->web);
|
||||
}
|
||||
}
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void MapGUI::downloadRequested(QWebEngineDownloadRequest *download)
|
||||
{
|
||||
download->accept();
|
||||
}
|
||||
#else
|
||||
void MapGUI::downloadRequested(QWebEngineDownloadItem *download)
|
||||
{
|
||||
download->accept();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void MapGUI::preferenceChanged(int elementType)
|
||||
|
@ -2787,6 +2827,7 @@ void MapGUI::makeUIConnections()
|
|||
QObject::connect(ui->displayfoF2, &ButtonSwitch::clicked, this, &MapGUI::on_displayfoF2_clicked);
|
||||
QObject::connect(ui->find, &QLineEdit::returnPressed, this, &MapGUI::on_find_returnPressed);
|
||||
QObject::connect(ui->maidenhead, &QToolButton::clicked, this, &MapGUI::on_maidenhead_clicked);
|
||||
QObject::connect(ui->save, &QToolButton::clicked, this, &MapGUI::on_save_clicked);
|
||||
QObject::connect(ui->deleteAll, &QToolButton::clicked, this, &MapGUI::on_deleteAll_clicked);
|
||||
QObject::connect(ui->displaySettings, &QToolButton::clicked, this, &MapGUI::on_displaySettings_clicked);
|
||||
QObject::connect(ui->mapTypes, qOverload<int>(&QComboBox::currentIndexChanged), this, &MapGUI::on_mapTypes_currentIndexChanged);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <QQuickWidget>
|
||||
#include <QTextEdit>
|
||||
#include <QJsonObject>
|
||||
#include <QFileDialog>
|
||||
#ifdef QT_WEBENGINE_FOUND
|
||||
#include <QWebEngineFullScreenRequest>
|
||||
#include <QWebEnginePage>
|
||||
|
@ -194,6 +195,7 @@ private:
|
|||
RollupState m_rollupState;
|
||||
bool m_doApplySettings;
|
||||
AvailableChannelOrFeatureList m_availableChannelOrFeatures;
|
||||
QFileDialog m_fileDialog;
|
||||
|
||||
Map* m_map;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
@ -217,6 +219,8 @@ private:
|
|||
MapTileServer *m_mapTileServer;
|
||||
QTimer m_redrawMapTimer;
|
||||
GIRO *m_giro;
|
||||
QDateTime m_giroDateTime;
|
||||
QString m_giroRunId;
|
||||
QHash<QString, IonosondeStation *> m_ionosondeStations;
|
||||
QSharedPointer<const QList<NavAid *>> m_navAids;
|
||||
QSharedPointer<const QList<Airspace *>> m_airspaces;
|
||||
|
@ -279,6 +283,7 @@ private:
|
|||
void openKiwiSDR(const QString& url);
|
||||
void openSpyServer(const QString& url);
|
||||
QString formatFrequency(qint64 frequency) const;
|
||||
void updateGIRO(const QDateTime& mapDateTime);
|
||||
|
||||
static QString getDataDir();
|
||||
static const QList<RadioTimeTransmitter> m_radioTimeTransmitters;
|
||||
|
@ -315,6 +320,7 @@ private slots:
|
|||
void on_layersMenu_clicked();
|
||||
void on_find_returnPressed();
|
||||
void on_maidenhead_clicked();
|
||||
void on_save_clicked();
|
||||
void on_deleteAll_clicked();
|
||||
void on_displaySettings_clicked();
|
||||
void on_mapTypes_currentIndexChanged(int index);
|
||||
|
@ -330,10 +336,14 @@ private slots:
|
|||
void renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void loadingChanged(const QWebEngineLoadingInfo &loadingInfo);
|
||||
void downloadRequested(QWebEngineDownloadRequest *download);
|
||||
#else
|
||||
void downloadRequested(QWebEngineDownloadItem *download);
|
||||
#endif
|
||||
#endif
|
||||
void statusChanged(QQuickWidget::Status status);
|
||||
void preferenceChanged(int elementType);
|
||||
void giroIndexUpdated(const QList<GIRO::DataSet>& data);
|
||||
void giroDataUpdated(const GIRO::GIROStationData& data);
|
||||
void mufUpdated(const QJsonDocument& document);
|
||||
void foF2Updated(const QJsonDocument& document);
|
||||
|
|
|
@ -424,6 +424,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="save">
|
||||
<property name="toolTip">
|
||||
<string>Save to .kml</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/save.png</normaloff>:/save.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="deleteAll">
|
||||
<property name="toolTip">
|
||||
|
|
|
@ -96,6 +96,11 @@ void ObjectMapItem::update(SWGSDRangel::SWGMapItem *mapItem)
|
|||
updateTrack(mapItem->getTrack());
|
||||
updatePredictedTrack(mapItem->getPredictedTrack());
|
||||
}
|
||||
if (mapItem->getAvailableFrom()) {
|
||||
m_availableFrom = QDateTime::fromString(*mapItem->getAvailableFrom(), Qt::ISODateWithMs);
|
||||
} else {
|
||||
m_availableFrom = QDateTime();
|
||||
}
|
||||
if (mapItem->getAvailableUntil()) {
|
||||
m_availableUntil = QDateTime::fromString(*mapItem->getAvailableUntil(), Qt::ISODateWithMs);
|
||||
} else {
|
||||
|
|
|
@ -61,6 +61,7 @@ protected:
|
|||
float m_latitude; // Position for label
|
||||
float m_longitude;
|
||||
float m_altitude; // In metres
|
||||
QDateTime m_availableFrom; // Date & time this item is visible from. Invalid date/time is forever
|
||||
QDateTime m_availableUntil; // Date & time this item is visible until (for 3D map). Invalid date/time is forever
|
||||
};
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ const QStringList MapSettings::m_pipeTypes = {
|
|||
QStringLiteral("Radiosonde"),
|
||||
QStringLiteral("StarTracker"),
|
||||
QStringLiteral("SatelliteTracker"),
|
||||
QStringLiteral("SID"),
|
||||
QStringLiteral("VORLocalizer")
|
||||
};
|
||||
|
||||
|
@ -57,6 +58,7 @@ const QStringList MapSettings::m_pipeURIs = {
|
|||
QStringLiteral("sdrangel.feature.radiosonde"),
|
||||
QStringLiteral("sdrangel.feature.startracker"),
|
||||
QStringLiteral("sdrangel.feature.satellitetracker"),
|
||||
QStringLiteral("sdrangel.feature.sid"),
|
||||
QStringLiteral("sdrangel.feature.vorlocalizer")
|
||||
};
|
||||
|
||||
|
@ -125,6 +127,7 @@ MapSettings::MapSettings() :
|
|||
stationSettings->m_display3DTrack = false;
|
||||
m_itemSettings.insert("Station", stationSettings);
|
||||
m_itemSettings.insert("VORLocalizer", new MapItemSettings("VORLocalizer", true, QColor(255, 255, 0), false, true, 11));
|
||||
m_itemSettings.insert("SID", new MapItemSettings("SID", true, QColor(255, 255, 0), false, true, 3));
|
||||
|
||||
MapItemSettings *ionosondeItemSettings = new MapItemSettings("Ionosonde Stations", true, QColor(255, 255, 0), false, true, 4);
|
||||
ionosondeItemSettings->m_display2DIcon = false;
|
||||
|
|
|
@ -28,66 +28,10 @@
|
|||
#endif
|
||||
|
||||
#include "util/units.h"
|
||||
#include "gui/colordialog.h"
|
||||
|
||||
#include "mapsettingsdialog.h"
|
||||
#include "maplocationdialog.h"
|
||||
#include "mapcolordialog.h"
|
||||
|
||||
static QString rgbToColor(quint32 rgb)
|
||||
{
|
||||
QColor color = QColor::fromRgba(rgb);
|
||||
return QString("%1,%2,%3").arg(color.red()).arg(color.green()).arg(color.blue());
|
||||
}
|
||||
|
||||
static QString backgroundCSS(quint32 rgb)
|
||||
{
|
||||
// Must specify a border, otherwise we end up with a gradient instead of solid background
|
||||
return QString("QToolButton { background-color: rgb(%1); border: none; }").arg(rgbToColor(rgb));
|
||||
}
|
||||
|
||||
static QString noColorCSS()
|
||||
{
|
||||
return "QToolButton { background-color: black; border: none; }";
|
||||
}
|
||||
|
||||
MapColorGUI::MapColorGUI(QTableWidget *table, int row, int col, bool noColor, quint32 color) :
|
||||
m_noColor(noColor),
|
||||
m_color(color)
|
||||
{
|
||||
m_colorButton = new QToolButton(table);
|
||||
m_colorButton->setFixedSize(22, 22);
|
||||
if (!m_noColor)
|
||||
{
|
||||
m_colorButton->setStyleSheet(backgroundCSS(m_color));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_colorButton->setStyleSheet(noColorCSS());
|
||||
m_colorButton->setText("-");
|
||||
}
|
||||
table->setCellWidget(row, col, m_colorButton);
|
||||
connect(m_colorButton, &QToolButton::clicked, this, &MapColorGUI::on_color_clicked);
|
||||
}
|
||||
|
||||
void MapColorGUI::on_color_clicked()
|
||||
{
|
||||
MapColorDialog dialog(QColor::fromRgba(m_color), m_colorButton);
|
||||
if (dialog.exec() == QDialog::Accepted)
|
||||
{
|
||||
m_noColor = dialog.noColorSelected();
|
||||
if (!m_noColor)
|
||||
{
|
||||
m_colorButton->setText("");
|
||||
m_color = dialog.selectedColor().rgba();
|
||||
m_colorButton->setStyleSheet(backgroundCSS(m_color));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_colorButton->setText("-");
|
||||
m_colorButton->setStyleSheet(noColorCSS());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MapItemSettingsGUI::MapItemSettingsGUI(QTableWidget *table, int row, MapSettings::MapItemSettings *settings) :
|
||||
m_track2D(table, row, MapSettingsDialog::COL_2D_TRACK, !settings->m_display2DTrack, settings->m_2DTrackColor),
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include <QProgressDialog>
|
||||
|
||||
#include "gui/httpdownloadmanagergui.h"
|
||||
#include "gui/tablecolorchooser.h"
|
||||
#include "util/openaip.h"
|
||||
#include "util/ourairportsdb.h"
|
||||
#include "util/waypoints.h"
|
||||
|
@ -34,34 +35,15 @@
|
|||
#include "ui_mapsettingsdialog.h"
|
||||
#include "mapsettings.h"
|
||||
|
||||
class MapColorGUI : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
MapColorGUI(QTableWidget *table, int row, int col, bool noColor, quint32 color);
|
||||
|
||||
public slots:
|
||||
void on_color_clicked();
|
||||
|
||||
private:
|
||||
QToolButton *m_colorButton;
|
||||
|
||||
public:
|
||||
// Have copies of settings, so we don't change unless main dialog is accepted
|
||||
bool m_noColor;
|
||||
quint32 m_color;
|
||||
|
||||
};
|
||||
|
||||
class MapItemSettingsGUI : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
MapItemSettingsGUI(QTableWidget *table, int row, MapSettings::MapItemSettings *settings);
|
||||
|
||||
MapColorGUI m_track2D;
|
||||
MapColorGUI m_point3D;
|
||||
MapColorGUI m_track3D;
|
||||
TableColorChooser m_track2D;
|
||||
TableColorChooser m_point3D;
|
||||
TableColorChooser m_track3D;
|
||||
QSpinBox *m_minZoom;
|
||||
QSpinBox *m_minPixels;
|
||||
QDoubleSpinBox *m_labelScale;
|
||||
|
|
|
@ -14,10 +14,11 @@ On top of this, it can plot data from other plugins, such as:
|
|||
* Weather balloons from the Radiosonde feature,
|
||||
* RF Heat Maps from the Heap Map channel,
|
||||
* Radials and estimated position from the VOR localizer feature,
|
||||
* ILS course line and glide path from the ILS Demodulator.
|
||||
* DSC geographic call areas.
|
||||
* ILS course line and glide path from the ILS Demodulator,
|
||||
* DSC geographic call areas,
|
||||
* SID paths.
|
||||
|
||||
As well as internet data sources:
|
||||
As well as internet and built-in data sources:
|
||||
|
||||
* AM, FM and DAB transmitters in the UK and DAB transmitters in France,
|
||||
* Airports, NavAids and airspaces,
|
||||
|
@ -40,7 +41,7 @@ It can also create tracks showing the path aircraft, ships, radiosondes and APRS
|
|||
|
||||
![3D Map feature](../../../doc/img/Map_plugin_apt.png)
|
||||
|
||||
3D Models are not included with SDRangel. They must be downloaded by pressing the Download 3D Models button in the Display Settings dialog (20).
|
||||
3D Models are not included with SDRangel. They must be downloaded by pressing the Download 3D Models button in the Display Settings dialog (21).
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
|
@ -143,14 +144,14 @@ This is only supported on 2D raster maps and the 3D map.
|
|||
<h3>11: Display MUF Contours</h3>
|
||||
|
||||
When checked, contours will be downloaded and displayed on the 3D map, showing the MUF (Maximum Usable Frequency) for a 3000km path that reflects off the ionosphere.
|
||||
The contours will be updated every 15 minutes. The latest contour data will always be displayed, irrespective of the time set on the 3D Map.
|
||||
The contours will be updated every 15 minutes. MUF contour data is available for the preceding 5 days.
|
||||
|
||||
![MUF contours](../../../doc/img/Map_plugin_muf.png)
|
||||
|
||||
<h3>12: Display coF2 Contours</h3>
|
||||
|
||||
When checked, contours will be downloaded and displayed on the 3D map, showing coF2 (F2 layer critical frequency), the maximum frequency at which radio waves will be reflected vertically from the F2 region of the ionosphere.
|
||||
The contours will be updated every 15 minutes. The latest contour data will always be displayed, irrespective of the time set on the 3D Map.
|
||||
The contours will be updated every 15 minutes. coF2 contour data is available for the preceding 5 days.
|
||||
|
||||
<h3>13: Display NASA GIBS Data</h3>
|
||||
|
||||
|
@ -158,7 +159,7 @@ When checked, enables overlay of data from NASA GIBS (Global Imagery Browse Serv
|
|||
such as land and sea temperatures, atmospheric conditions, flux measurements and the like.
|
||||
Details of available data products can be found [here](https://nasa-gibs.github.io/gibs-api-docs/available-visualizations/#visualization-product-catalog).
|
||||
|
||||
For some data sets, GIBS has data spanning many decades. The data period may be hours, days or months. The 3D map will attemp to show data from the closest time set in the 3D map's timescale.
|
||||
For some data sets, GIBS has data spanning many decades. The data period may be hours, days or months. The 3D map will attempt to show data from the closest time set in the 3D map's timescale.
|
||||
The 2D map will only show data from the default date (which is displayed in the table at the bottom).
|
||||
|
||||
![NASA GIBS](../../../doc/img/Map_plugin_GIBS.png)
|
||||
|
@ -185,11 +186,19 @@ When checked, displays the track (taken or predicted) for the selected object.
|
|||
|
||||
When checked, displays the track (taken or predicted) for the all objects.
|
||||
|
||||
<h3>19: Delete</h3>
|
||||
<h3>19: Save to .kml</h3>
|
||||
|
||||
When clicked, items and tracks on the map will be saved to a [KML](https://en.wikipedia.org/wiki/Keyhole_Markup_Language) (.kml or .kmz) file, for use in other applications.
|
||||
|
||||
Note that the KML format requires 3D models in the Collada (.dae) format. However, SDRangel's models are in glTF (.glb or .gltf) format.
|
||||
If you wish to view the models in a KML viewer, you will need to manually convert them. Note that you should still be able to view tracks without the models.
|
||||
Note that the .glbe files cannot be converted to .dae.
|
||||
|
||||
<h3>20: Delete</h3>
|
||||
|
||||
When clicked, all items will be deleted from the map.
|
||||
|
||||
<h3>20: Display settings</h3>
|
||||
<h3>21: Display settings</h3>
|
||||
|
||||
When clicked, opens the Map Display Settings dialog:
|
||||
|
||||
|
@ -212,7 +221,7 @@ For the 3D map, the settings include:
|
|||
For ECI (Earth Centred Inertial) the camera is fixed in space and the globe will rotate under it.
|
||||
|
||||
The "Download 3D Models" button will download the 3D models of aircraft, ships and satellites that are required for the 3D map.
|
||||
These are not included with the SDRangel distribution, so must be downloaded. It is recommeded to restart SDRangel after downloading the models.
|
||||
These are not included with the SDRangel distribution, so must be downloaded. It is recommended to restart SDRangel after downloading the models.
|
||||
|
||||
![Map Display Settings Dialog Items Tab](../../../doc/img/Map_plugin_display_settings_items.png)
|
||||
|
||||
|
@ -256,7 +265,7 @@ To the right of the timeline is the fullscreen toggle button, which allows the 3
|
|||
|
||||
<h4>SDRs</h4>
|
||||
|
||||
The map can display KiwiSDRs and Spy Servers that are publically accessible via the internet. A URL is displayed in the info box.
|
||||
The map can display KiwiSDRs and Spy Servers that are publicly accessible via the internet. A URL is displayed in the info box.
|
||||
Clicking on the URL will open a new KiwiSDR or RemoteTCPInput device which will connect to the corresponding SDR.
|
||||
Before connecting, you should check the whether the number of users is below the maximum. Server data is updated every 2 minutes.
|
||||
|
||||
|
@ -281,6 +290,17 @@ MUF and foF2 can be displayed as contours:
|
|||
|
||||
The contours can be clicked on which will display the data for that contour in the info box.
|
||||
|
||||
<h4>VLF Transmitters</h4>
|
||||
|
||||
The Map contains a built-in list of VLF transmitters. This can be overridden by a user-defined list contained in a file `vlftransmitters.csv` in the application data directory.
|
||||
|
||||
The file must have the following columns:
|
||||
|
||||
```
|
||||
Callsign,Frequency,Latitude,Longitude,Power
|
||||
GQD,19580,54.911643,-3.278456,10
|
||||
```
|
||||
|
||||
<h2>Attribution</h2>
|
||||
|
||||
IARU Region 1 beacon list used with permission from: https://iaru-r1-c5-beacons.org/ To add or update a beacon, see: https://iaru-r1-c5-beacons.org/index.php/beacon-update/
|
||||
|
|
|
@ -53,7 +53,7 @@ void WebServer::incomingConnection(qintptr socket)
|
|||
// Don't include leading or trailing / in from
|
||||
void WebServer::addPathSubstitution(const QString &from, const QString &to)
|
||||
{
|
||||
qDebug() << "Mapping " << from << " to " << to;
|
||||
//qDebug() << "Mapping " << from << " to " << to;
|
||||
m_pathSubstitutions.insert(from, to);
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ void WebServer::readClient()
|
|||
if (socket->canReadLine())
|
||||
{
|
||||
QString line = socket->readLine();
|
||||
qDebug() << "WebServer HTTP Request: " << line;
|
||||
//qDebug() << "WebServer HTTP Request: " << line;
|
||||
|
||||
QStringList tokens = QString(line).split(QRegularExpression("[ \r\n][ \r\n]*"));
|
||||
if (tokens[0] == "GET")
|
||||
|
|
|
@ -197,6 +197,13 @@ void SatelliteSelectionDialog::displaySatInfo(const QString& name)
|
|||
{
|
||||
SatNogsSatellite *sat = m_satellites[name];
|
||||
m_satInfo = sat;
|
||||
if (!sat)
|
||||
{
|
||||
// Might not be null if satellite name entered via API
|
||||
ui->satInfo->setText("");
|
||||
ui->satImage->setPixmap(QPixmap());
|
||||
return;
|
||||
}
|
||||
QStringList info;
|
||||
info.append(QString("Name: %1").arg(sat->m_name));
|
||||
if (sat->m_names.size() > 0)
|
||||
|
|
|
@ -713,7 +713,7 @@
|
|||
<string>Norad ID</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Norad catalog idenfitier for the satellite</string>
|
||||
<string>Norad catalog identifier for the satellite</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include "satellitetrackersettings.h"
|
||||
|
||||
#define DEAFULT_TARGET "ISS"
|
||||
#define DEFAULT_TLES {"https://db.satnogs.org/api/tle/", "https://www.amsat.org/tle/current/nasabare.txt", "http://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle", "https://celestrak.org/NORAD/elements/gp.php?GROUP=gps-ops&FORMAT=tle"}
|
||||
#define DEFAULT_TLES {"https://db.satnogs.org/api/tle/", "https://www.amsat.org/tle/current/nasabare.txt", "http://celestrak.org/NORAD/elements/gp.php?GROUP=weather&FORMAT=tle", "https://celestrak.org/NORAD/elements/gp.php?GROUP=gps-ops&FORMAT=tle", "https://celestrak.org/NORAD/elements/gp.php?CATNR=36395&FORMAT=tle"}
|
||||
#define DEFAULT_DATE_FORMAT "yyyy/MM/dd"
|
||||
#define DEFAULT_AOS_SPEECH "${name} is visible for ${duration} minutes. Max elevation, ${elevation} degrees."
|
||||
#define DEFAULT_LOS_SPEECH "${name} is no longer visible."
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
project(sid)
|
||||
|
||||
set(sid_SOURCES
|
||||
sid.cpp
|
||||
sidsettings.cpp
|
||||
sidplugin.cpp
|
||||
sidwebapiadapter.cpp
|
||||
sidworker.cpp
|
||||
)
|
||||
|
||||
set(sid_HEADERS
|
||||
sid.h
|
||||
sidsettings.h
|
||||
sidplugin.h
|
||||
sidwebapiadapter.h
|
||||
sidworker.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
set(sid_SOURCES
|
||||
${sid_SOURCES}
|
||||
sidgui.cpp
|
||||
sidgui.ui
|
||||
sidsettingsdialog.cpp
|
||||
sidsettingsdialog.ui
|
||||
icons.qrc
|
||||
)
|
||||
set(sid_HEADERS
|
||||
${sid_HEADERS}
|
||||
sidgui.h
|
||||
sidsettingsdialog.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME featuresid)
|
||||
set(TARGET_LIB Qt::Widgets Qt::Charts Qt::Multimedia Qt::MultimediaWidgets)
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
|
||||
else()
|
||||
set(TARGET_NAME featuresidsrv)
|
||||
set(TARGET_LIB "")
|
||||
set(TARGET_LIB_GUI "")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
|
||||
endif()
|
||||
|
||||
add_library(${TARGET_NAME} SHARED
|
||||
${sid_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt::Core
|
||||
${TARGET_LIB}
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
||||
|
||||
if(WIN32)
|
||||
# Run deployqt for MultimediaWidgets, which isn't used in other plugins
|
||||
include(DeployQt)
|
||||
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} "")
|
||||
endif()
|
||||
|
||||
|
||||
# Install debug symbols
|
||||
if (WIN32)
|
||||
install(FILES $<TARGET_PDB_FILE:${TARGET_NAME}> CONFIGURATIONS Debug RelWithDebInfo DESTINATION ${INSTALL_FOLDER} )
|
||||
endif()
|
|
@ -0,0 +1,16 @@
|
|||
<RCC>
|
||||
<qresource prefix="/sid/">
|
||||
<file>icons/sun.png</file>
|
||||
<file>icons/chartcombined.png</file>
|
||||
<file>icons/chartseparate.png</file>
|
||||
<file>icons/legend.png</file>
|
||||
<file>icons/xlp.svg</file>
|
||||
<file>icons/xls.svg</file>
|
||||
<file>icons/xsp.svg</file>
|
||||
<file>icons/xss.svg</file>
|
||||
<file>icons/delta.svg</file>
|
||||
<file>icons/gamma.svg</file>
|
||||
<file>icons/proton.svg</file>
|
||||
<file>icons/solar-orbiter.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
Po Szerokość: | Wysokość: | Rozmiar: 209 B |
Po Szerokość: | Wysokość: | Rozmiar: 252 B |
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 15.1 13" style="enable-background:new 0 0 15.1 13;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-66-TEX-N-394" class="st0" d="M0.9-13c-0.1,0-0.1,0.1-0.1,0.1c0,0,1,2.1,3.1,6.3S7-0.2,7-0.1C7.1,0,7.2,0,7.5,0
|
||||
C7.8,0,8,0,8-0.1c0,0,1.1-2.2,3.1-6.4s3.1-6.3,3.1-6.3c0,0,0-0.1-0.1-0.1H0.9z M9.2-6.7L7-2.2l-4.5-9.1l4.5,0h4.5
|
||||
C11.4-11.3,10.7-9.8,9.2-6.7z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 779 B |
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 11.7 13.8" style="enable-background:new 0 0 11.7 13.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-56-TEX-I-1D6FE" class="st0" d="M1.3-4.4c-0.2,0-0.4,0.1-0.4,0.2C0.9-4,1-3.7,1.2-3.4s0.4,0.7,0.7,1.1
|
||||
s0.7,0.7,1.1,1s1,0.4,1.4,0.4c0.3,0,0.5,0,0.6,0c0.6-0.1,1.1-0.5,1.4-1s0.7-1.3,1-2.4C7.6-5,7.7-5.5,7.7-5.8c0-0.1,0-0.1,0-0.1
|
||||
l0.2,0.5c0.2,0.6,0.5,1.3,0.8,1.9s0.6,1.2,0.8,1.6s0.3,0.7,0.4,0.7c0,0,0.1,0,0.3,0h0.2c0.1-0.1,0.1-0.1,0.1-0.2
|
||||
c0,0-0.1-0.3-0.4-0.8c-0.2-0.3-0.6-1.1-1-2.1c-0.5-1-0.9-2.1-1.3-3.3C7.7-7.8,7.6-8.2,7.5-8.8C7.4-9.4,7.3-10,7.2-10.7
|
||||
C7-11.5,6.8-12,6.7-12.3s-0.3-0.5-0.5-0.5s-0.3,0.2-0.3,0.5c0,0.6,0.3,1.9,1,4L7-8v0.3c0,0.1,0,0.3,0,0.6c0,1.9-0.3,3.2-1,3.9
|
||||
C5.5-2.7,4.9-2.5,4.2-2.5c-0.5,0-1.1-0.1-1.5-0.4s-0.8-0.7-1-1.2c0-0.1,0-0.2-0.1-0.2C1.5-4.4,1.4-4.4,1.3-4.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 1.2 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 265 B |
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20.6 18.4" style="enable-background:new 0 0 20.6 18.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-5-TEX-I-1D45D" class="st0" d="M0.4-9.7c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0.1,0.4c0,0.2,0.1,0.4,0.2,0.6S0.9-8.3,1-8
|
||||
c0.1,0.2,0.2,0.4,0.4,0.5s0.3,0.3,0.5,0.4C2-7,2.2-6.9,2.4-6.9c0.9,0,1.5-0.4,1.7-1.2l0.2,0.2c0.7,0.7,1.5,1,2.1,1
|
||||
c0.8,0,1.4-0.3,1.8-0.9C8.8-8.4,9-9,9-9.9c0-1.3-0.5-2.5-1.4-3.6s-2-1.7-3.1-1.7c-0.3,0-0.5,0-0.7,0.1
|
||||
c-0.1,0.1-0.3,0.2-0.4,0.3c-0.1,0.1-0.3,0.2-0.3,0.3l-0.1,0.1c0,0-0.1-0.5-0.4-1.6s-0.4-1.6-0.4-1.6c0-0.1,0-0.1,0.1-0.1
|
||||
c0.1,0,0.3,0,0.7-0.1h0.5c0.1-0.1,0.1-0.1,0.1-0.2c0,0,0-0.1-0.1-0.3c0-0.1-0.1-0.2-0.1-0.3c0,0-0.1-0.1-0.2-0.1
|
||||
c0,0-0.1,0-0.2,0s-0.3,0-0.6,0s-0.7,0-1.1,0c-0.8,0-1.4,0-1.6,0h-0.1c-0.1,0.1-0.1,0.2-0.1,0.2c0,0.3,0.1,0.5,0.2,0.6h0.4
|
||||
c0.4,0,0.6,0.1,0.7,0.2c0,0.1,0.4,1.5,1.1,4.3s1.1,4.3,1.1,4.5c0,0.1,0,0.1,0,0.3c0,0.5-0.2,0.7-0.5,0.7
|
||||
c-0.3,0-0.5-0.2-0.7-0.5C1.5-8.5,1.4-8.8,1.3-9.2C1.2-9.6,1.1-9.8,1.1-9.9c0,0-0.1,0-0.3,0H0.5C0.5-9.8,0.4-9.8,0.4-9.7z
|
||||
M3.2-13.1c0.3-0.9,0.7-1.4,1.3-1.4c0.4,0,0.7,0.1,1,0.4c0.3,0.3,0.6,0.6,0.8,1c0.2,0.4,0.4,1.1,0.7,2s0.3,1.6,0.3,2v0.1
|
||||
c0,0.9-0.4,1.3-1.1,1.3c-0.1,0-0.3,0-0.4-0.1c-0.1,0-0.3-0.1-0.4-0.2C5.4-7.9,5.3-8,5.2-8.1C5.1-8.1,5-8.2,4.9-8.3
|
||||
C4.8-8.4,4.7-8.5,4.6-8.6C4.5-8.7,4.4-8.8,4.4-8.9S4.3-9,4.3-9L4.2-9.1c0,0,0-0.1-0.1-0.3c0-0.2-0.1-0.4-0.2-0.8
|
||||
c-0.1-0.4-0.2-0.7-0.3-0.9C3.4-12.4,3.2-13,3.2-13.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(536,413) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-5-TEX-N-2B" class="st0" d="M-743.4-590.4c0,0,0,0.1,0,0.2c0,0.2,0.1,0.3,0.3,0.4h5.4v2.7l0,2.7
|
||||
c0.1,0.2,0.2,0.2,0.3,0.2c0.2,0,0.3-0.1,0.4-0.3v-5.4h5.4c0.2-0.1,0.3-0.2,0.3-0.4s-0.1-0.3-0.3-0.4h-5.4v-5.4
|
||||
c-0.1-0.2-0.2-0.3-0.3-0.3h0h0c-0.1,0-0.3,0.1-0.3,0.3v5.4h-5.4c-0.2,0.1-0.3,0.2-0.3,0.4V-590.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 2.2 KiB |
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 89.49 83.83">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: none;
|
||||
stroke: #fff;
|
||||
stroke-miterlimit: 10;
|
||||
stroke-width: 3px;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: #fff;
|
||||
stroke-width: 0px;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<circle class="cls-1" cx="35.5" cy="48.81" r="22.2"/>
|
||||
<circle class="cls-2" cx="69.18" cy="16.79" r="6.33"/>
|
||||
<rect class="cls-2" x="48.53" y="4.39" width="16.03" height="4.81" transform="translate(15.91 -33.1) rotate(37.62)"/>
|
||||
<rect class="cls-2" x="73.67" y="24.72" width="16.03" height="4.81" transform="translate(33.54 -44.22) rotate(37.62)"/>
|
||||
<rect class="cls-2" x="52.72" y="69.63" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(63.37 -20.38) rotate(42.8)"/>
|
||||
<rect class="cls-2" x="10.15" y="25.57" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(22.11 -3.19) rotate(42.8)"/>
|
||||
<rect class="cls-2" x="8.33" y="68.52" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(-45.29 29.78) rotate(-45)"/>
|
||||
<rect class="cls-2" x="52.72" y="27.33" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(-3.17 49.11) rotate(-45)"/>
|
||||
<rect class="cls-2" x="0" y="47.77" width="9.94" height="2.08" rx="1.04" ry="1.04"/>
|
||||
<rect class="cls-2" x="60.18" y="47.77" width="9.94" height="2.08" rx="1.04" ry="1.04"/>
|
||||
<rect class="cls-2" x="32.21" y="17.72" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(18.42 55.94) rotate(-90)"/>
|
||||
<rect class="cls-2" x="31.17" y="77.82" width="9.94" height="2.08" rx="1.04" ry="1.04" transform="translate(-42.72 115) rotate(-90)"/>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 1.7 KiB |
Po Szerokość: | Wysokość: | Rozmiar: 414 B |
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 25.2 15.1" style="enable-background:new 0 0 25.2 15.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-38-TEX-I-1D44B" class="st0" d="M0.8-12.4L0.8-12.4c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0.1,0.3
|
||||
c0,0.2,0.1,0.3,0.1,0.3c0,0,0.2,0,0.3,0.1c1,0,1.9,0.3,2.4,0.9c0.1,0.1,0.8,0.9,2.1,2.3s1.9,2.1,1.9,2.1
|
||||
c-1.3,3.4-2,5.1-2.1,5.1C5.3-0.9,4.9-0.8,4.2-0.8H3.7C3.7-0.8,3.6-0.7,3.6-0.7s0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.3
|
||||
c0.4,0,1.1,0,2.2,0C6.7,0,7,0,7.4,0S8,0,8.2,0s0.3,0,0.4,0c0.2,0,0.3-0.1,0.3-0.2c0,0,0-0.1,0-0.2c0-0.2-0.1-0.3-0.1-0.3
|
||||
c0,0-0.1-0.1-0.3-0.1C8-0.9,7.7-1,7.4-1.1l1.4-3.5l1,1.1c1.3,1.4,1.9,2.1,1.9,2.3c0,0.2-0.1,0.4-0.4,0.5c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.2c0,0,0.2,0,0.5,0c0.3,0,0.6,0,1,0s0.6,0,0.7,0
|
||||
c1.1,0,1.7,0,1.8,0.1h0.1c0.1-0.1,0.1-0.2,0.1-0.2c0-0.3-0.1-0.5-0.2-0.6h-0.3c-0.4,0-0.8-0.1-1.1-0.2s-0.6-0.2-0.7-0.3
|
||||
c-0.2-0.1-0.3-0.2-0.4-0.3l-0.2-0.2c0,0-0.6-0.6-1.7-1.9L9.1-5.4c0,0,0.2-0.5,0.6-1.4s0.8-1.9,1.2-2.9c0.4-1,0.6-1.5,0.7-1.6
|
||||
c0.1-0.1,0.5-0.2,1.1-0.2c0.4,0,0.6-0.1,0.6-0.2c0,0,0-0.1,0-0.3c0-0.2-0.1-0.3-0.1-0.3s-0.1-0.1-0.3-0.1c0,0-0.2,0-0.6,0
|
||||
s-0.9,0-1.6,0c-0.7,0-1.3,0-1.7,0s-0.6,0-0.6,0c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.1,0,0.2,0.1,0.3s0.1,0.1,0.1,0.1
|
||||
s0.1,0,0.2,0c0.1,0,0.2,0,0.3,0c0.1,0,0.3,0,0.5,0.1c0.2,0.1,0.3,0.2,0.3,0.2c0,0-0.3,0.7-0.9,2.1L7.8-7
|
||||
c-2.3-2.5-3.4-3.8-3.5-3.9c0-0.1-0.1-0.2-0.1-0.2c0-0.2,0.2-0.4,0.5-0.5c0,0,0,0,0.1,0s0.1,0,0.1,0c0,0,0.1,0,0.1,0s0,0,0.1,0
|
||||
c0,0,0,0,0.1-0.1s0-0.1,0-0.1c0-0.1,0-0.2,0-0.3c0-0.2-0.1-0.2-0.1-0.3c0,0-0.1,0-0.3-0.1c0,0-0.1,0-0.3,0s-0.4,0-0.8,0
|
||||
s-0.7,0-1.1,0C1.6-12.3,1-12.3,0.8-12.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(861,-150) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-38-TEX-I-1D43F" class="st0" d="M-1191.6,202.4c-0.4,0-0.6,0-0.7,0.1c0,0,0,0.1,0,0.1c0,0.3,0.1,0.5,0.2,0.6
|
||||
c0,0,0.1,0,0.3,0c0.7,0,1.4-0.1,2.3-0.1c1.7,0,2.7,0,2.9,0.1h0.2c0.1-0.1,0.1-0.1,0.1-0.2c0,0,0-0.1,0-0.3
|
||||
c-0.1-0.2-0.1-0.3-0.2-0.3h-0.7c-0.8,0-1.3-0.1-1.5-0.2c-0.1,0-0.2-0.2-0.3-0.5c-0.1-0.3-0.5-1.9-1.2-4.8
|
||||
c-0.2-0.8-0.4-1.6-0.6-2.5c-0.2-0.9-0.4-1.5-0.5-2l-0.2-0.7c0,0,0.1-0.1,0.2-0.1c0.1,0,0.5,0,1.1,0h0.3c0.4,0,0.7,0,0.9,0
|
||||
c0.2,0,0.5,0.1,0.9,0.1c0.4,0.1,0.7,0.2,0.9,0.3c0.2,0.1,0.5,0.3,0.8,0.6s0.5,0.6,0.8,1c0.2,0.4,0.4,0.8,0.5,1.2
|
||||
s0.3,0.7,0.3,0.8c0.1,0,0.2,0.1,0.3,0.1h0.2c0.1-0.1,0.1-0.2,0.1-0.2s-0.1-0.4-0.4-1.1c-0.2-0.7-0.5-1.4-0.8-2.2
|
||||
s-0.4-1.2-0.5-1.3c0-0.1,0-0.1-0.1-0.1s-0.1,0-0.3-0.1s-0.4,0-0.8,0c-0.1,0-0.6,0-1.4,0s-1.5,0-2.2,0h-3.2
|
||||
c-1,0-1.5,0.1-1.5,0.2c0,0.1,0,0.2,0,0.3c0.1,0.2,0.1,0.4,0.2,0.4c0,0,0.1,0,0.3,0h0.1c0.3,0,0.7,0,1.1,0.1
|
||||
c0.2,0,0.3,0.1,0.3,0.2c0,0,0.5,1.7,1.3,5c0.8,3.3,1.2,5.1,1.2,5.2C-1190.6,202.3-1191,202.3-1191.6,202.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 3.2 KiB |
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 25.2 19.1" style="enable-background:new 0 0 25.2 19.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-39-TEX-I-1D44B" class="st0" d="M0.8-14.6L0.8-14.6c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0.1,0.3
|
||||
c0,0.2,0.1,0.3,0.1,0.3c0,0,0.2,0,0.3,0.1c1,0,1.9,0.3,2.4,0.9c0.1,0.1,0.8,0.9,2.1,2.3s1.9,2.1,1.9,2.1
|
||||
c-1.3,3.4-2,5.1-2.1,5.1C5.3-3.2,4.9-3.1,4.2-3.1H3.7C3.7-3,3.6-3,3.6-3c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.3
|
||||
c0.4,0,1.1,0,2.2,0c0.4,0,0.8,0,1.1,0s0.6,0,0.8,0c0.2,0,0.3,0,0.4,0c0.2,0,0.3-0.1,0.3-0.2c0,0,0-0.1,0-0.2
|
||||
c0-0.2-0.1-0.3-0.1-0.3c0,0-0.1-0.1-0.3-0.1c-0.4,0-0.7-0.1-1-0.3l1.4-3.5l1,1.1c1.3,1.4,1.9,2.1,1.9,2.3
|
||||
c0,0.2-0.1,0.4-0.4,0.5c-0.1,0-0.1,0-0.2,0c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.2c0,0,0.2,0,0.5,0
|
||||
c0.3,0,0.6,0,1,0c0.4,0,0.6,0,0.7,0c1.1,0,1.7,0,1.8,0.1h0.1c0.1-0.1,0.1-0.2,0.1-0.2c0-0.3-0.1-0.5-0.2-0.6h-0.3
|
||||
c-0.4,0-0.8-0.1-1.1-0.2c-0.3-0.1-0.6-0.2-0.7-0.3c-0.2-0.1-0.3-0.2-0.4-0.3l-0.2-0.2c0,0-0.6-0.6-1.7-1.9L9.1-7.7
|
||||
c0,0,0.2-0.5,0.6-1.4c0.4-1,0.8-1.9,1.2-2.9s0.6-1.5,0.7-1.6c0.1-0.1,0.5-0.2,1.1-0.2c0.4,0,0.6-0.1,0.6-0.2c0,0,0-0.1,0-0.3
|
||||
c0-0.2-0.1-0.3-0.1-0.3c0,0-0.1-0.1-0.3-0.1c0,0-0.2,0-0.6,0c-0.4,0-0.9,0-1.6,0c-0.7,0-1.3,0-1.7,0c-0.4,0-0.6,0-0.6,0
|
||||
c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.1,0,0.2,0.1,0.3c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0,0.2,0c0.1,0,0.2,0,0.3,0
|
||||
c0.1,0,0.3,0,0.5,0.1c0.2,0.1,0.3,0.2,0.3,0.2c0,0-0.3,0.7-0.9,2.1L7.8-9.2c-2.3-2.5-3.4-3.8-3.5-3.9c0-0.1-0.1-0.2-0.1-0.2
|
||||
c0-0.2,0.2-0.4,0.5-0.5c0,0,0,0,0.1,0s0.1,0,0.1,0c0,0,0.1,0,0.1,0s0,0,0.1,0c0,0,0,0,0.1-0.1c0,0,0-0.1,0-0.1
|
||||
c0-0.1,0-0.2,0-0.3c0-0.2-0.1-0.2-0.1-0.3c0,0-0.1,0-0.3-0.1c0,0-0.1,0-0.3,0c-0.2,0-0.4,0-0.8,0c-0.3,0-0.7,0-1.1,0
|
||||
C1.6-14.6,1-14.6,0.8-14.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(936.2,413) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-39-TEX-V-2032" class="st0" d="M-1298.8-593.5c-0.1,0-0.2,0-0.5,0.1c-0.3,0.1-0.4,0.1-0.4,0.2
|
||||
c0,0.1,0.3,1.5,1,4.2c0.7,2.7,1,4.1,1.1,4.3c0.2,0.4,0.5,0.6,0.9,0.6c0.2,0,0.5-0.1,0.8-0.3s0.4-0.4,0.4-0.8c0-0.1,0-0.2,0-0.3
|
||||
c0-0.1-0.5-1.4-1.6-4C-1298.1-592.1-1298.7-593.5-1298.8-593.5C-1298.7-593.5-1298.8-593.5-1298.8-593.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(861,-247) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-39-TEX-I-1D43F" class="st0" d="M-1191.7,333.9c-0.4,0-0.6,0-0.7,0.1c0,0,0,0.1,0,0.1c0,0.3,0.1,0.5,0.2,0.6
|
||||
c0,0,0.1,0,0.3,0c0.7,0,1.4-0.1,2.3-0.1c1.7,0,2.7,0,2.9,0.1h0.2c0.1-0.1,0.1-0.1,0.1-0.2s0-0.1,0-0.3
|
||||
c-0.1-0.2-0.1-0.3-0.2-0.3h-0.7c-0.8,0-1.3-0.1-1.5-0.2c-0.1,0-0.2-0.2-0.3-0.5c-0.1-0.3-0.5-1.9-1.2-4.8
|
||||
c-0.2-0.8-0.4-1.6-0.6-2.5c-0.2-0.9-0.4-1.5-0.5-2l-0.2-0.7c0,0,0.1-0.1,0.2-0.1c0.1,0,0.5,0,1.1,0h0.3c0.4,0,0.7,0,0.9,0
|
||||
c0.2,0,0.5,0.1,0.9,0.1s0.7,0.2,0.9,0.3c0.2,0.1,0.5,0.3,0.8,0.6s0.5,0.6,0.8,1c0.2,0.4,0.4,0.8,0.5,1.2
|
||||
c0.2,0.4,0.3,0.7,0.3,0.8c0.1,0,0.2,0.1,0.3,0.1h0.2c0.1-0.1,0.1-0.2,0.1-0.2s-0.1-0.4-0.4-1.1s-0.5-1.4-0.8-2.2
|
||||
s-0.4-1.2-0.5-1.3c0-0.1,0-0.1-0.1-0.1c0,0-0.1,0-0.3-0.1c-0.2,0-0.4,0-0.8,0c-0.1,0-0.6,0-1.4,0c-0.8,0-1.5,0-2.2,0h-3.2
|
||||
c-1,0-1.5,0.1-1.5,0.2c0,0.1,0,0.2,0,0.3c0.1,0.2,0.1,0.4,0.2,0.4c0,0,0.1,0,0.3,0h0.1c0.3,0,0.7,0,1.1,0.1
|
||||
c0.2,0,0.3,0.1,0.3,0.2c0,0,0.5,1.7,1.3,5c0.8,3.3,1.2,5,1.2,5.2C-1190.6,333.8-1191,333.8-1191.7,333.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 3.7 KiB |
|
@ -0,0 +1,48 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24.7 15.4" style="enable-background:new 0 0 24.7 15.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-40-TEX-I-1D44B" class="st0" d="M0.8-12.4L0.8-12.4c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0.1,0.3
|
||||
c0,0.2,0.1,0.3,0.1,0.3c0,0,0.2,0,0.3,0.1c1,0,1.9,0.3,2.4,0.9c0.1,0.1,0.8,0.9,2.1,2.3s1.9,2.1,1.9,2.1
|
||||
c-1.3,3.4-2,5.1-2.1,5.1C5.3-0.9,4.9-0.8,4.2-0.8H3.7C3.7-0.8,3.6-0.7,3.6-0.7c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.3
|
||||
c0.4,0,1.1,0,2.2,0C6.7,0,7,0,7.4,0C7.7,0,8,0,8.2,0c0.2,0,0.3,0,0.4,0c0.2,0,0.3-0.1,0.3-0.2c0,0,0-0.1,0-0.2
|
||||
c0-0.2-0.1-0.3-0.1-0.3S8.6-0.8,8.4-0.8C8-0.9,7.7-1,7.4-1.1l1.4-3.5l1,1.1c1.3,1.4,1.9,2.1,1.9,2.3c0,0.2-0.1,0.4-0.4,0.5
|
||||
c-0.1,0-0.1,0-0.2,0c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.2c0,0,0.2,0,0.5,0c0.3,0,0.6,0,1,0
|
||||
c0.4,0,0.6,0,0.7,0c1.1,0,1.7,0,1.8,0.1h0.1c0.1-0.1,0.1-0.2,0.1-0.2c0-0.3-0.1-0.5-0.2-0.6h-0.3c-0.4,0-0.8-0.1-1.1-0.2
|
||||
s-0.6-0.2-0.7-0.3c-0.2-0.1-0.3-0.2-0.4-0.3l-0.2-0.2c0,0-0.6-0.6-1.7-1.9L9.1-5.4c0,0,0.2-0.5,0.6-1.4s0.8-1.9,1.2-2.9
|
||||
c0.4-1,0.6-1.5,0.7-1.6c0.1-0.1,0.5-0.2,1.1-0.2c0.4,0,0.6-0.1,0.6-0.2c0,0,0-0.1,0-0.3c0-0.2-0.1-0.3-0.1-0.3
|
||||
c0,0-0.1-0.1-0.3-0.1c0,0-0.2,0-0.6,0c-0.4,0-0.9,0-1.6,0c-0.7,0-1.3,0-1.7,0c-0.4,0-0.6,0-0.6,0c-0.2,0-0.3,0.1-0.3,0.2
|
||||
c0,0,0,0.1,0,0.3c0,0.1,0,0.2,0.1,0.3c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0,0.2,0c0.1,0,0.2,0,0.3,0c0.1,0,0.3,0,0.5,0.1
|
||||
c0.2,0.1,0.3,0.2,0.3,0.2c0,0-0.3,0.7-0.9,2.1L7.8-7c-2.3-2.5-3.4-3.8-3.5-3.9c0-0.1-0.1-0.2-0.1-0.2c0-0.2,0.2-0.4,0.5-0.5
|
||||
c0,0,0,0,0.1,0s0.1,0,0.1,0c0,0,0.1,0,0.1,0s0,0,0.1,0c0,0,0,0,0.1-0.1s0-0.1,0-0.1c0-0.1,0-0.2,0-0.3c0-0.2-0.1-0.2-0.1-0.3
|
||||
c0,0-0.1,0-0.3-0.1c0,0-0.1,0-0.3,0c-0.2,0-0.4,0-0.8,0c-0.3,0-0.7,0-1.1,0C1.6-12.3,1-12.3,0.8-12.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(861,-150) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-40-TEX-I-1D446" class="st0" d="M-1190.2,191.3c0.7,0,1.4,0.3,2,0.9c0.6,0.6,0.9,1.4,0.9,2.2
|
||||
c0,0.8-0.3,1.3-0.9,1.6c-1.3,0.3-2.1,0.5-2.5,0.7c-0.4,0.1-0.6,0.2-0.8,0.4c-0.7,0.5-1.1,1.2-1.1,2.2c0,0.7,0.2,1.4,0.6,2
|
||||
c0.4,0.6,0.8,1.1,1.2,1.4c0.3,0.3,0.8,0.5,1.3,0.7c0.5,0.2,0.9,0.3,1.2,0.3h0.2h0.1c1.2,0,2-0.4,2.5-1.2c0,0,0.1,0.1,0.2,0.2
|
||||
s0.3,0.3,0.5,0.5s0.3,0.3,0.4,0.4c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0.2,0,0.2,0,0.2-0.1c0-0.1-0.2-0.8-0.5-2.2
|
||||
c-0.3-1.4-0.5-2.1-0.6-2.1c0,0-0.1-0.1-0.3-0.1c-0.2,0-0.4,0.1-0.4,0.2c0,0.1,0,0.1,0,0.2c0,0,0,0.2,0,0.3c0,0.2,0,0.3,0,0.5
|
||||
c0,0.4-0.1,0.8-0.2,1.1c-0.1,0.3-0.2,0.5-0.3,0.7c-0.1,0.1-0.3,0.3-0.5,0.5c-0.4,0.3-0.9,0.4-1.5,0.4c-0.7,0-1.3-0.3-1.9-0.8
|
||||
c-0.6-0.6-0.9-1.2-0.9-1.9c0-0.4,0.1-0.7,0.3-0.9c0.2-0.3,0.4-0.5,0.7-0.6c0,0,0.5-0.1,1.3-0.3c0.8-0.2,1.2-0.3,1.2-0.3
|
||||
c0.4-0.1,0.8-0.4,1.2-0.9s0.6-1,0.6-1.8c0-0.3,0-0.7-0.1-1c-0.1-0.3-0.2-0.6-0.3-0.9c-0.2-0.3-0.4-0.7-0.7-1.1
|
||||
c-0.3-0.4-0.7-0.7-1.2-1.1c-0.5-0.3-1.1-0.6-1.7-0.7c-0.1,0-0.3,0-0.6,0c-1.2,0-2.1,0.3-2.8,1l-0.2,0.2l-0.5-0.6
|
||||
c-0.3-0.3-0.4-0.5-0.5-0.6s-0.1-0.1-0.2-0.1c-0.2,0-0.2,0-0.2,0.1c0,0,0.3,1.5,1,4.3c0,0.1,0.1,0.1,0.4,0.1h0.2
|
||||
c0.1-0.1,0.1-0.1,0.1-0.2c0,0,0-0.1,0-0.2c0-0.1,0-0.2-0.1-0.4c0-0.2,0-0.4,0-0.6c0-0.5,0.1-0.9,0.3-1.2
|
||||
c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.3,0.9-0.3C-1190.9,191.3-1190.5,191.3-1190.2,191.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 3.6 KiB |
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 28.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 24.7 19.4" style="enable-background:new 0 0 24.7 19.4;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g transform="scale(1,-1)">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path id="MJX-41-TEX-I-1D44B" class="st0" d="M0.8-14.6L0.8-14.6c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0.1,0.3
|
||||
c0,0.2,0.1,0.3,0.1,0.3s0.2,0,0.3,0.1c1,0,1.9,0.3,2.4,0.9c0.1,0.1,0.8,0.9,2.1,2.3s1.9,2.1,1.9,2.1c-1.3,3.4-2,5.1-2.1,5.1
|
||||
C5.3-3.2,4.9-3.1,4.2-3.1H3.7C3.7-3,3.6-3,3.6-3c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.3c0.4,0,1.1,0,2.2,0
|
||||
c0.4,0,0.8,0,1.1,0s0.6,0,0.8,0c0.2,0,0.3,0,0.4,0c0.2,0,0.3-0.1,0.3-0.2c0,0,0-0.1,0-0.2c0-0.2-0.1-0.3-0.1-0.3
|
||||
S8.6-3.1,8.4-3.1c-0.4,0-0.7-0.1-1-0.3l1.4-3.5l1,1.1c1.3,1.4,1.9,2.1,1.9,2.3c0,0.2-0.1,0.4-0.4,0.5c-0.1,0-0.1,0-0.2,0
|
||||
c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.2,0.1,0.3,0.2,0.3h0.2c0,0,0.2,0,0.5,0s0.6,0,1,0c0.4,0,0.6,0,0.7,0
|
||||
c1.1,0,1.7,0,1.8,0.1h0.1c0.1-0.1,0.1-0.2,0.1-0.2c0-0.3-0.1-0.5-0.2-0.6h-0.3c-0.4,0-0.8-0.1-1.1-0.2
|
||||
c-0.3-0.1-0.6-0.2-0.7-0.3c-0.2-0.1-0.3-0.2-0.4-0.3l-0.2-0.2c0,0-0.6-0.6-1.7-1.9L9.1-7.7c0,0,0.2-0.5,0.6-1.4
|
||||
c0.4-1,0.8-1.9,1.2-2.9c0.4-1,0.6-1.5,0.7-1.6c0.1-0.1,0.5-0.2,1.1-0.2c0.4,0,0.6-0.1,0.6-0.2c0,0,0-0.1,0-0.3
|
||||
c0-0.2-0.1-0.3-0.1-0.3c0,0-0.1-0.1-0.3-0.1c0,0-0.2,0-0.6,0c-0.4,0-0.9,0-1.6,0c-0.7,0-1.3,0-1.7,0s-0.6,0-0.6,0
|
||||
c-0.2,0-0.3,0.1-0.3,0.2c0,0,0,0.1,0,0.3c0,0.1,0,0.2,0.1,0.3c0,0,0.1,0.1,0.1,0.1c0,0,0.1,0,0.2,0c0.1,0,0.2,0,0.3,0
|
||||
s0.3,0,0.5,0.1c0.2,0.1,0.3,0.2,0.3,0.2c0,0-0.3,0.7-0.9,2.1L7.8-9.2c-2.3-2.5-3.4-3.8-3.5-3.9c0-0.1-0.1-0.2-0.1-0.2
|
||||
c0-0.2,0.2-0.4,0.5-0.5c0,0,0,0,0.1,0c0.1,0,0.1,0,0.1,0c0,0,0.1,0,0.1,0c0,0,0,0,0.1,0c0,0,0,0,0.1-0.1c0,0,0-0.1,0-0.1
|
||||
c0-0.1,0-0.2,0-0.3c0-0.2-0.1-0.2-0.1-0.3c0,0-0.1,0-0.3-0.1c0,0-0.1,0-0.3,0c-0.2,0-0.4,0-0.8,0c-0.3,0-0.7,0-1.1,0
|
||||
C1.6-14.6,1-14.6,0.8-14.6z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(936.2,413) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-41-TEX-V-2032" class="st0" d="M-1298.8-593.5c-0.1,0-0.2,0-0.5,0.1c-0.3,0.1-0.4,0.1-0.4,0.2
|
||||
c0,0.1,0.3,1.5,1,4.2c0.7,2.7,1,4.1,1.1,4.3c0.2,0.4,0.5,0.6,0.9,0.6c0.2,0,0.5-0.1,0.8-0.3c0.3-0.2,0.4-0.4,0.4-0.8
|
||||
c0-0.1,0-0.2,0-0.3c0-0.1-0.5-1.4-1.6-4C-1298.1-592.1-1298.7-593.5-1298.8-593.5C-1298.7-593.5-1298.8-593.5-1298.8-593.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="translate(861,-247) scale(0.707)">
|
||||
<g>
|
||||
<path id="MJX-41-TEX-I-1D446" class="st0" d="M-1190.2,322.8c0.7,0,1.4,0.3,2,0.9c0.6,0.6,0.9,1.4,0.9,2.2
|
||||
c0,0.8-0.3,1.3-0.9,1.6c-1.3,0.3-2.1,0.5-2.5,0.7c-0.4,0.1-0.6,0.2-0.8,0.4c-0.7,0.5-1.1,1.2-1.1,2.2c0,0.7,0.2,1.4,0.6,2
|
||||
c0.4,0.6,0.8,1.1,1.2,1.4c0.3,0.3,0.8,0.5,1.3,0.7c0.5,0.2,0.9,0.3,1.2,0.3h0.2h0.1c1.2,0,2-0.4,2.5-1.2c0,0,0.1,0.1,0.2,0.2
|
||||
c0.1,0.2,0.3,0.3,0.5,0.5c0.2,0.2,0.3,0.3,0.4,0.4c0,0,0,0,0.1,0c0,0,0.1,0,0.1,0c0.2,0,0.2,0,0.2-0.1c0-0.1-0.2-0.8-0.5-2.2
|
||||
c-0.3-1.4-0.5-2.1-0.6-2.1c0,0-0.1-0.1-0.3-0.1c-0.2,0-0.4,0.1-0.4,0.2c0,0.1,0,0.1,0,0.2c0,0,0,0.2,0,0.3s0,0.3,0,0.5
|
||||
c0,0.4-0.1,0.8-0.2,1.1c-0.1,0.3-0.2,0.5-0.3,0.7c-0.1,0.1-0.3,0.3-0.5,0.5c-0.4,0.3-0.9,0.4-1.5,0.4c-0.7,0-1.3-0.3-1.9-0.8
|
||||
c-0.6-0.6-0.9-1.2-0.9-1.9c0-0.4,0.1-0.7,0.3-0.9c0.2-0.3,0.4-0.5,0.7-0.6c0,0,0.5-0.1,1.3-0.3c0.8-0.2,1.2-0.3,1.2-0.3
|
||||
c0.4-0.1,0.8-0.4,1.2-0.9c0.4-0.4,0.6-1,0.6-1.8c0-0.3,0-0.7-0.1-1c-0.1-0.3-0.2-0.6-0.3-0.9c-0.2-0.3-0.4-0.7-0.7-1.1
|
||||
c-0.3-0.4-0.7-0.7-1.2-1.1s-1.1-0.6-1.7-0.7c-0.1,0-0.3,0-0.6,0c-1.2,0-2.1,0.3-2.8,1l-0.2,0.2l-0.5-0.6
|
||||
c-0.3-0.3-0.4-0.5-0.5-0.6c-0.1,0-0.1-0.1-0.2-0.1c-0.2,0-0.2,0-0.2,0.1c0,0,0.3,1.5,1,4.3c0,0.1,0.1,0.1,0.4,0.1h0.2
|
||||
c0.1-0.1,0.1-0.1,0.1-0.2c0,0,0-0.1,0-0.2c0-0.1,0-0.2-0.1-0.4c0-0.2,0-0.4,0-0.6c0-0.5,0.1-0.9,0.3-1.2
|
||||
c0.2-0.3,0.5-0.6,0.8-0.7c0.3-0.2,0.6-0.3,0.9-0.3C-1190.9,322.8-1190.5,322.8-1190.2,322.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 4.1 KiB |