Compare commits
No commits in common. "1f9a95e60d9970d403f19d1a3f6b6e71987c0c9a" and "a6561ef59baac52fe6a4478a58c050cae253d3b8" have entirely different histories.
1f9a95e60d
...
a6561ef59b
|
|
@ -8,11 +8,8 @@
|
||||||
# - AlignTrailingComments:
|
# - AlignTrailingComments:
|
||||||
# Kind: Always
|
# Kind: Always
|
||||||
# OverEmptyLines: 1
|
# OverEmptyLines: 1
|
||||||
# - BraceWrapping:
|
|
||||||
# AfterClass: true
|
|
||||||
# - Standard: c++14
|
# - Standard: c++14
|
||||||
# - BreakBeforeBinaryOperators: NonAssignment
|
# - BreakBeforeBinaryOperators: NonAssignment
|
||||||
# - PenaltyBreakAssignment: 30
|
|
||||||
# Original header below
|
# Original header below
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# SPDX-License-Identifier: GPL-2.0
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
|
@ -49,7 +46,7 @@ AlwaysBreakTemplateDeclarations: false
|
||||||
BinPackArguments: true
|
BinPackArguments: true
|
||||||
BinPackParameters: true
|
BinPackParameters: true
|
||||||
BraceWrapping:
|
BraceWrapping:
|
||||||
AfterClass: true
|
AfterClass: false
|
||||||
AfterControlStatement: false
|
AfterControlStatement: false
|
||||||
AfterEnum: false
|
AfterEnum: false
|
||||||
AfterFunction: true
|
AfterFunction: true
|
||||||
|
|
@ -106,10 +103,9 @@ ObjCBinPackProtocolList: Auto
|
||||||
ObjCBlockIndentWidth: 4
|
ObjCBlockIndentWidth: 4
|
||||||
ObjCSpaceAfterProperty: true
|
ObjCSpaceAfterProperty: true
|
||||||
ObjCSpaceBeforeProtocolList: true
|
ObjCSpaceBeforeProtocolList: true
|
||||||
PackConstructorInitializers: NextLine
|
|
||||||
|
|
||||||
# Taken from git's rules
|
# Taken from git's rules
|
||||||
PenaltyBreakAssignment: 30
|
PenaltyBreakAssignment: 10
|
||||||
PenaltyBreakBeforeFirstCallParameter: 30
|
PenaltyBreakBeforeFirstCallParameter: 30
|
||||||
PenaltyBreakComment: 10
|
PenaltyBreakComment: 10
|
||||||
PenaltyBreakFirstLessLess: 0
|
PenaltyBreakFirstLessLess: 0
|
||||||
|
|
|
||||||
|
|
@ -121,8 +121,6 @@ jobs:
|
||||||
run: meson test -C build "M17 Golay Unit Test"
|
run: meson test -C build "M17 Golay Unit Test"
|
||||||
- name: M17 RRC Test
|
- name: M17 RRC Test
|
||||||
run: meson test -C build "M17 RRC Test"
|
run: meson test -C build "M17 RRC Test"
|
||||||
- name: M17 Callsign Unit Test
|
|
||||||
run: meson test -C build "M17 Callsign Unit Test"
|
|
||||||
- name: Codeplug Test
|
- name: Codeplug Test
|
||||||
run: meson test -C build "Codeplug Test"
|
run: meson test -C build "Codeplug Test"
|
||||||
- name: minmea Conversion Test
|
- name: minmea Conversion Test
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,6 @@ target_sources(app
|
||||||
openrtx/src/protocols/M17/M17DSP.cpp
|
openrtx/src/protocols/M17/M17DSP.cpp
|
||||||
openrtx/src/protocols/M17/M17Golay.cpp
|
openrtx/src/protocols/M17/M17Golay.cpp
|
||||||
openrtx/src/protocols/M17/M17Callsign.cpp
|
openrtx/src/protocols/M17/M17Callsign.cpp
|
||||||
openrtx/src/protocols/M17/Callsign.cpp
|
|
||||||
openrtx/src/protocols/M17/M17Modulator.cpp
|
openrtx/src/protocols/M17/M17Modulator.cpp
|
||||||
openrtx/src/protocols/M17/M17Demodulator.cpp
|
openrtx/src/protocols/M17/M17Demodulator.cpp
|
||||||
openrtx/src/protocols/M17/M17FrameEncoder.cpp
|
openrtx/src/protocols/M17/M17FrameEncoder.cpp
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@ openrtx_src = ['openrtx/src/core/state.c',
|
||||||
'openrtx/src/protocols/M17/M17DSP.cpp',
|
'openrtx/src/protocols/M17/M17DSP.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Golay.cpp',
|
'openrtx/src/protocols/M17/M17Golay.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Callsign.cpp',
|
'openrtx/src/protocols/M17/M17Callsign.cpp',
|
||||||
'openrtx/src/protocols/M17/Callsign.cpp',
|
|
||||||
'openrtx/src/protocols/M17/M17Modulator.cpp',
|
'openrtx/src/protocols/M17/M17Modulator.cpp',
|
||||||
'openrtx/src/protocols/M17/M17Demodulator.cpp',
|
'openrtx/src/protocols/M17/M17Demodulator.cpp',
|
||||||
'openrtx/src/protocols/M17/M17FrameEncoder.cpp',
|
'openrtx/src/protocols/M17/M17FrameEncoder.cpp',
|
||||||
|
|
@ -1058,10 +1057,6 @@ m17_viterbi_test = executable('m17_viterbi_test',
|
||||||
sources : unit_test_src + ['tests/unit/M17_viterbi.cpp'],
|
sources : unit_test_src + ['tests/unit/M17_viterbi.cpp'],
|
||||||
kwargs : unit_test_opts)
|
kwargs : unit_test_opts)
|
||||||
|
|
||||||
m17_callsign_test = executable('m17_callsign_test',
|
|
||||||
sources : unit_test_src + ['tests/unit/M17_callsign.cpp'],
|
|
||||||
kwargs : unit_test_opts)
|
|
||||||
|
|
||||||
m17_demodulator_test = executable('m17_demodulator_test',
|
m17_demodulator_test = executable('m17_demodulator_test',
|
||||||
sources: unit_test_src + ['tests/unit/M17_demodulator.cpp'],
|
sources: unit_test_src + ['tests/unit/M17_demodulator.cpp'],
|
||||||
kwargs: unit_test_opts)
|
kwargs: unit_test_opts)
|
||||||
|
|
@ -1094,7 +1089,6 @@ test('M17 Golay Unit Test', m17_golay_test)
|
||||||
test('M17 Viterbi Unit Test', m17_viterbi_test)
|
test('M17 Viterbi Unit Test', m17_viterbi_test)
|
||||||
## test('M17 Demodulator Test', m17_demodulator_test) # Skipped for now as this test no longer works after an M17 refactor
|
## test('M17 Demodulator Test', m17_demodulator_test) # Skipped for now as this test no longer works after an M17 refactor
|
||||||
test('M17 RRC Test', m17_rrc_test)
|
test('M17 RRC Test', m17_rrc_test)
|
||||||
test('M17 Callsign Unit Test', m17_callsign_test)
|
|
||||||
test('Codeplug Test', cps_test)
|
test('Codeplug Test', cps_test)
|
||||||
## test('Linux InputStream Test', linux_inputStream_test)
|
## test('Linux InputStream Test', linux_inputStream_test)
|
||||||
## test('Sine Test', sine_test)
|
## test('Sine Test', sine_test)
|
||||||
|
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
||||||
* Niccolò Izzo IU2KIN *
|
|
||||||
* Frederik Saraci IU2NRO *
|
|
||||||
* Silvano Seva IU2KWO *
|
|
||||||
* *
|
|
||||||
* 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; either 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 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 CALLSIGN_H
|
|
||||||
#define CALLSIGN_H
|
|
||||||
|
|
||||||
#ifndef __cplusplus
|
|
||||||
#error This header is C++ only!
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include "M17Datatypes.hpp"
|
|
||||||
|
|
||||||
namespace M17
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class representing an M17 callsign object.
|
|
||||||
*/
|
|
||||||
class Callsign
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Default constructor.
|
|
||||||
* By default, an uninitialized callsign is set to invalid.
|
|
||||||
*/
|
|
||||||
Callsign();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a callsign object from an std::string
|
|
||||||
* The callsign can have up to 9 characters
|
|
||||||
*
|
|
||||||
* @param callsign: callsign string
|
|
||||||
*/
|
|
||||||
Callsign(const std::string callsign);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a callsign object from a NULL-terminated string
|
|
||||||
* The callsign can have up to 9 characters
|
|
||||||
*
|
|
||||||
* @param callsign: callsign string
|
|
||||||
*/
|
|
||||||
Callsign(const char *callsign);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct a callsign object from a base-40 encoded callsign
|
|
||||||
*
|
|
||||||
* @param encodedCall: encoded callsign value
|
|
||||||
*/
|
|
||||||
Callsign(const call_t &encodedCall);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if callsign is empty.
|
|
||||||
* A callsign is considered empty when its first character is NULL.
|
|
||||||
*
|
|
||||||
* @return true if the callsign is empty
|
|
||||||
*/
|
|
||||||
inline bool isEmpty() const
|
|
||||||
{
|
|
||||||
return call[0] == '\0';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test if callsign is a special one.
|
|
||||||
*
|
|
||||||
* @return true if the callsign is either ALL, INFO or ECHO
|
|
||||||
*/
|
|
||||||
bool isSpecial() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-conversion operator to retrieve the callsign in encoded format
|
|
||||||
*
|
|
||||||
* @return the base-40 encoded version of the callsign
|
|
||||||
*/
|
|
||||||
operator call_t() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-conversion operator to retrieve the callsign as a std::string
|
|
||||||
*
|
|
||||||
* @return a std::string containing the callsign
|
|
||||||
*/
|
|
||||||
operator std::string() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Type-conversion operator to retrieve the callsign as a NULL-terminated
|
|
||||||
* string
|
|
||||||
*
|
|
||||||
* @return the callsign as a NULL-terminated string
|
|
||||||
*/
|
|
||||||
operator const char *() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Comparison operator.
|
|
||||||
*
|
|
||||||
* @param other the incoming callsign to compare against
|
|
||||||
* @return true if callsigns are equivalent
|
|
||||||
*/
|
|
||||||
bool operator==(const Callsign &other) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
static constexpr size_t MAX_CALLSIGN_CHARS = 9;
|
|
||||||
static constexpr size_t MAX_CALLSIGN_LEN = MAX_CALLSIGN_CHARS + 1;
|
|
||||||
char call[MAX_CALLSIGN_LEN];
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace M17
|
|
||||||
|
|
||||||
#endif // CALLSIGN_H
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
||||||
* Niccolò Izzo IU2KIN *
|
|
||||||
* Frederik Saraci IU2NRO *
|
|
||||||
* Silvano Seva IU2KWO *
|
|
||||||
* *
|
|
||||||
* 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; either 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 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 CLOCK_RECOVERY_H
|
|
||||||
#define CLOCK_RECOVERY_H
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <array>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class to construct clock recovery objects.
|
|
||||||
* The clock recovery algorithm estimates the best sampling point by finding,
|
|
||||||
* within a symbol, the point with maximum energy. The algorithm will work
|
|
||||||
* properly only if correctly synchronized with the baseband stream.
|
|
||||||
*/
|
|
||||||
template <size_t SAMPLES_PER_SYMBOL>
|
|
||||||
class ClockRecovery
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
ClockRecovery()
|
|
||||||
{
|
|
||||||
reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
~ClockRecovery()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the internal state.
|
|
||||||
*/
|
|
||||||
void reset()
|
|
||||||
{
|
|
||||||
curIdx = 0;
|
|
||||||
prevSample = 0;
|
|
||||||
numSamples = 0;
|
|
||||||
updateReq = false;
|
|
||||||
energy.fill(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a new sample.
|
|
||||||
*
|
|
||||||
* @param sample: baseband sample.
|
|
||||||
*/
|
|
||||||
void sample(int16_t &sample)
|
|
||||||
{
|
|
||||||
int32_t delta = static_cast<int32_t>(sample)
|
|
||||||
- static_cast<int32_t>(prevSample);
|
|
||||||
|
|
||||||
if ((sample + prevSample) < 0)
|
|
||||||
delta = -delta;
|
|
||||||
|
|
||||||
energy[curIdx] += delta;
|
|
||||||
prevSample = sample;
|
|
||||||
curIdx = (curIdx + 1) % SAMPLES_PER_SYMBOL;
|
|
||||||
numSamples += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the best sampling point estimate.
|
|
||||||
*/
|
|
||||||
void update()
|
|
||||||
{
|
|
||||||
if (numSamples == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint8_t index = 0;
|
|
||||||
bool is_positive = false;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < SAMPLES_PER_SYMBOL; i++) {
|
|
||||||
int32_t phase = energy[i];
|
|
||||||
|
|
||||||
if (!is_positive && phase > 0) {
|
|
||||||
is_positive = true;
|
|
||||||
} else if (is_positive && phase < 0) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index == 0)
|
|
||||||
sp = SAMPLES_PER_SYMBOL - 1;
|
|
||||||
else
|
|
||||||
sp = index - 1;
|
|
||||||
|
|
||||||
energy.fill(0);
|
|
||||||
numSamples = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the best sampling point estimate.
|
|
||||||
* The returned value is within the space of a simbol, that is in the
|
|
||||||
* range [0 SAMPLES_PER_SYMBOL - 1].
|
|
||||||
*
|
|
||||||
* @return sampling point.
|
|
||||||
*/
|
|
||||||
uint8_t samplingPoint()
|
|
||||||
{
|
|
||||||
return sp;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::array<int32_t, SAMPLES_PER_SYMBOL> energy;
|
|
||||||
size_t curIdx;
|
|
||||||
size_t numSamples;
|
|
||||||
int16_t prevSample;
|
|
||||||
uint8_t sp;
|
|
||||||
bool updateReq;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
||||||
* Niccolò Izzo IU2KIN *
|
|
||||||
* Frederik Saraci IU2NRO *
|
|
||||||
* Silvano Seva IU2KWO *
|
|
||||||
* *
|
|
||||||
* 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; either 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 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 DEV_ESTIMATOR_H
|
|
||||||
#define DEV_ESTIMATOR_H
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Symbol deviation estimator.
|
|
||||||
* This module allows to estimate the outer symbol deviation of a baseband
|
|
||||||
* stream. The baseband samples used for the estimation should be takend at the
|
|
||||||
* ideal sampling point. To work properly, the estimator needs to be initialized
|
|
||||||
* with a reference outer symbol deviation.
|
|
||||||
*/
|
|
||||||
class DevEstimator
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
DevEstimator() : outerDev({0, 0}), offset(0), posAccum(0), negAccum(0),
|
|
||||||
posCnt(0), negCnt(0)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destructor
|
|
||||||
*/
|
|
||||||
~DevEstimator()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the estimator state.
|
|
||||||
* Calling this function clears the internal state.
|
|
||||||
*
|
|
||||||
* @param outerDev: initial value for outer symbol deviation
|
|
||||||
*/
|
|
||||||
void init(const std::pair<int32_t, int32_t> &outerDev)
|
|
||||||
{
|
|
||||||
this->outerDev = outerDev;
|
|
||||||
offset = 0;
|
|
||||||
posAccum = 0;
|
|
||||||
negAccum = 0;
|
|
||||||
posCnt = 0;
|
|
||||||
negCnt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a new sample.
|
|
||||||
*
|
|
||||||
* @param sample: baseband sample.
|
|
||||||
*/
|
|
||||||
void sample(int16_t value)
|
|
||||||
{
|
|
||||||
int32_t posThresh = (2 * outerDev.first) / 3;
|
|
||||||
int32_t negThresh = (2 * outerDev.second) / 3;
|
|
||||||
|
|
||||||
if (value > posThresh) {
|
|
||||||
posAccum += value;
|
|
||||||
posCnt += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < negThresh) {
|
|
||||||
posAccum += value;
|
|
||||||
posCnt += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the estimation of outer symbol deviation and zero-offset and
|
|
||||||
* start a new acquisition cycle.
|
|
||||||
*/
|
|
||||||
void update()
|
|
||||||
{
|
|
||||||
if ((posCnt == 0) || (negCnt == 0))
|
|
||||||
return;
|
|
||||||
|
|
||||||
int32_t max = posAccum / posCnt;
|
|
||||||
int32_t min = negAccum / negCnt;
|
|
||||||
offset = (max + min) / 2;
|
|
||||||
outerDev.first = max - offset;
|
|
||||||
outerDev.second = min - offset;
|
|
||||||
posAccum = 0;
|
|
||||||
negAccum = 0;
|
|
||||||
posCnt = 0;
|
|
||||||
negCnt = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the estimated outer symbol deviation from the last update.
|
|
||||||
* The function returns a std::pair where the first element is the positive
|
|
||||||
* deviation and the second the negative one.
|
|
||||||
*
|
|
||||||
* @return outer deviation.
|
|
||||||
*/
|
|
||||||
std::pair<int32_t, int32_t> outerDeviation()
|
|
||||||
{
|
|
||||||
return outerDev;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the estimated zero-offset from the last update.
|
|
||||||
*
|
|
||||||
* @return zero-offset.
|
|
||||||
*/
|
|
||||||
int32_t zeroOffset()
|
|
||||||
{
|
|
||||||
return offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::pair<int32_t, int32_t> outerDev;
|
|
||||||
int32_t offset;
|
|
||||||
int32_t posAccum;
|
|
||||||
int32_t negAccum;
|
|
||||||
uint32_t posCnt;
|
|
||||||
uint32_t negCnt;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -40,8 +40,6 @@
|
||||||
#include "protocols/M17/M17Constants.hpp"
|
#include "protocols/M17/M17Constants.hpp"
|
||||||
#include "protocols/M17/Correlator.hpp"
|
#include "protocols/M17/Correlator.hpp"
|
||||||
#include "protocols/M17/Synchronizer.hpp"
|
#include "protocols/M17/Synchronizer.hpp"
|
||||||
#include "protocols/M17/DevEstimator.hpp"
|
|
||||||
#include "protocols/M17/ClockRecovery.hpp"
|
|
||||||
|
|
||||||
namespace M17
|
namespace M17
|
||||||
{
|
{
|
||||||
|
|
@ -138,8 +136,10 @@ private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* State handler function for DemodState::SYNC_UPDATE
|
* State handler function for DemodState::SYNC_UPDATE
|
||||||
|
*
|
||||||
|
* @param sample: current baseband sample
|
||||||
*/
|
*/
|
||||||
void syncUpdateState();
|
void syncUpdateState(int16_t sample);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* M17 baseband signal sampled at 24kHz, half of an M17 frame is processed
|
* M17 baseband signal sampled at 24kHz, half of an M17 frame is processed
|
||||||
|
|
@ -160,7 +160,7 @@ private:
|
||||||
UNLOCKED, ///< Not locked
|
UNLOCKED, ///< Not locked
|
||||||
SYNCED, ///< Synchronized, validate syncword
|
SYNCED, ///< Synchronized, validate syncword
|
||||||
LOCKED, ///< Locked
|
LOCKED, ///< Locked
|
||||||
SYNC_UPDATE ///< Updating the synchronization state
|
SYNC_UPDATE ///< Updating the sampling point
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -176,22 +176,20 @@ private:
|
||||||
std::unique_ptr<frame_t > demodFrame; ///< Frame being demodulated.
|
std::unique_ptr<frame_t > demodFrame; ///< Frame being demodulated.
|
||||||
std::unique_ptr<frame_t > readyFrame; ///< Fully demodulated frame to be returned.
|
std::unique_ptr<frame_t > readyFrame; ///< Fully demodulated frame to be returned.
|
||||||
bool newFrame; ///< A new frame has been fully decoded.
|
bool newFrame; ///< A new frame has been fully decoded.
|
||||||
bool resetClockRec; ///< Clock recovery reset request.
|
|
||||||
bool updateSampPoint; ///< Sampling point update pending.
|
|
||||||
uint16_t frameIndex; ///< Index for filling the raw frame.
|
uint16_t frameIndex; ///< Index for filling the raw frame.
|
||||||
uint32_t sampleIndex; ///< Sample index, from 0 to (SAMPLES_PER_SYMBOL - 1)
|
uint32_t sampleIndex; ///< Sample index, from 0 to (SAMPLES_PER_SYMBOL - 1)
|
||||||
uint32_t samplingPoint; ///< Symbol sampling point
|
uint32_t samplingPoint; ///< Symbol sampling point
|
||||||
uint32_t sampleCount; ///< Free-running sample counter
|
uint32_t sampleCount; ///< Free-running sample counter
|
||||||
uint8_t missedSyncs; ///< Counter of missed synchronizations
|
uint8_t missedSyncs; ///< Counter of missed synchronizations
|
||||||
uint32_t initCount; ///< Downcounter for initialization
|
uint32_t initCount; ///< Downcounter for initialization
|
||||||
|
uint32_t syncCount; ///< Downcounter for resynchronization
|
||||||
|
std::pair < int32_t, int32_t > outerDeviation; ///< Deviation of outer symbols
|
||||||
float corrThreshold; ///< Correlation threshold
|
float corrThreshold; ///< Correlation threshold
|
||||||
struct dcBlock dcBlock; ///< State of the DC removal filter
|
struct dcBlock dcBlock; ///< State of the DC removal filter
|
||||||
|
|
||||||
Correlator < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > correlator;
|
Correlator < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > correlator;
|
||||||
Synchronizer < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > streamSync{{ -3, -3, -3, -3, +3, +3, -3, +3 }};
|
Synchronizer < M17_SYNCWORD_SYMBOLS, SAMPLES_PER_SYMBOL > streamSync{{ -3, -3, -3, -3, +3, +3, -3, +3 }};
|
||||||
Iir < 3 > sampleFilter{sfNum, sfDen};
|
Iir < 3 > sampleFilter{sfNum, sfDen};
|
||||||
DevEstimator devEstimator;
|
|
||||||
ClockRecovery< SAMPLES_PER_SYMBOL > clockRec;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} /* M17 */
|
} /* M17 */
|
||||||
|
|
|
||||||
|
|
@ -35,12 +35,13 @@
|
||||||
namespace M17
|
namespace M17
|
||||||
{
|
{
|
||||||
|
|
||||||
enum class M17FrameType : uint8_t {
|
enum class M17FrameType : uint8_t
|
||||||
PREAMBLE = 0, ///< Frame contains a preamble.
|
{
|
||||||
LINK_SETUP = 1, ///< Frame is a Link Setup Frame.
|
PREAMBLE = 0, ///< Frame contains a preamble.
|
||||||
STREAM = 2, ///< Frame is a stream data frame.
|
LINK_SETUP = 1, ///< Frame is a Link Setup Frame.
|
||||||
PACKET = 3, ///< Frame is a packet data frame.
|
STREAM = 2, ///< Frame is a stream data frame.
|
||||||
UNKNOWN = 4 ///< Frame is unknown.
|
PACKET = 3, ///< Frame is a packet data frame.
|
||||||
|
UNKNOWN = 4 ///< Frame is unknown.
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,6 +50,7 @@ enum class M17FrameType : uint8_t {
|
||||||
class M17FrameDecoder
|
class M17FrameDecoder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
*/
|
*/
|
||||||
|
|
@ -71,7 +73,7 @@ public:
|
||||||
* @param frame: byte array containg frame data.
|
* @param frame: byte array containg frame data.
|
||||||
* @return the type of frame recognized.
|
* @return the type of frame recognized.
|
||||||
*/
|
*/
|
||||||
M17FrameType decodeFrame(const frame_t &frame);
|
M17FrameType decodeFrame(const frame_t& frame);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the latest Link Setup Frame decoded. Check of the validity of the
|
* Get the latest Link Setup Frame decoded. Check of the validity of the
|
||||||
|
|
@ -79,7 +81,7 @@ public:
|
||||||
*
|
*
|
||||||
* @return a reference to the latest Link Setup Frame decoded.
|
* @return a reference to the latest Link Setup Frame decoded.
|
||||||
*/
|
*/
|
||||||
const M17LinkSetupFrame &getLsf()
|
const M17LinkSetupFrame& getLsf()
|
||||||
{
|
{
|
||||||
return lsf;
|
return lsf;
|
||||||
}
|
}
|
||||||
|
|
@ -89,12 +91,13 @@ public:
|
||||||
*
|
*
|
||||||
* @return a reference to the latest stream data frame decoded.
|
* @return a reference to the latest stream data frame decoded.
|
||||||
*/
|
*/
|
||||||
const M17StreamFrame &getStreamFrame()
|
const M17StreamFrame& getStreamFrame()
|
||||||
{
|
{
|
||||||
return streamFrame;
|
return streamFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine frame type by searching which syncword among the standard M17
|
* Determine frame type by searching which syncword among the standard M17
|
||||||
* ones has the minumum hamming distance from the given one. If the hamming
|
* ones has the minumum hamming distance from the given one. If the hamming
|
||||||
|
|
@ -104,7 +107,7 @@ private:
|
||||||
* @param syncWord: frame syncword.
|
* @param syncWord: frame syncword.
|
||||||
* @return frame type based on the given syncword.
|
* @return frame type based on the given syncword.
|
||||||
*/
|
*/
|
||||||
M17FrameType getFrameType(const std::array<uint8_t, 2> &syncWord);
|
M17FrameType getFrameType(const std::array< uint8_t, 2 >& syncWord);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode Link Setup Frame data and update the internal LSF field with
|
* Decode Link Setup Frame data and update the internal LSF field with
|
||||||
|
|
@ -112,7 +115,7 @@ private:
|
||||||
*
|
*
|
||||||
* @param data: byte array containg frame data, without sync word.
|
* @param data: byte array containg frame data, without sync word.
|
||||||
*/
|
*/
|
||||||
void decodeLSF(const std::array<uint8_t, 46> &data);
|
void decodeLSF(const std::array< uint8_t, 46 >& data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode stream data and update the internal LSF field with the new
|
* Decode stream data and update the internal LSF field with the new
|
||||||
|
|
@ -120,7 +123,7 @@ private:
|
||||||
*
|
*
|
||||||
* @param data: byte array containg frame data, without sync word.
|
* @param data: byte array containg frame data, without sync word.
|
||||||
*/
|
*/
|
||||||
void decodeStream(const std::array<uint8_t, 46> &data);
|
void decodeStream(const std::array< uint8_t, 46 >& data);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a LICH block.
|
* Decode a LICH block.
|
||||||
|
|
@ -130,21 +133,19 @@ private:
|
||||||
* @param lich: LICH block to be decoded.
|
* @param lich: LICH block to be decoded.
|
||||||
* @return true when the LICH block is successfully decoded.
|
* @return true when the LICH block is successfully decoded.
|
||||||
*/
|
*/
|
||||||
bool decodeLich(std::array<uint8_t, 6> &segment, const lich_t &lich);
|
bool decodeLich(std::array< uint8_t, 6 >& segment, const lich_t& lich);
|
||||||
|
|
||||||
uint8_t lsfSegmentMap; ///< Bitmap for LSF reassembly from LICH
|
|
||||||
M17LinkSetupFrame lsf; ///< Latest LSF received.
|
uint8_t lsfSegmentMap; ///< Bitmap for LSF reassembly from LICH
|
||||||
M17LinkSetupFrame lsfFromLich; ///< LSF assembled from LICH segments.
|
M17LinkSetupFrame lsf; ///< Latest LSF received.
|
||||||
M17StreamFrame streamFrame; ///< Latest stream dat frame received.
|
M17LinkSetupFrame lsfFromLich; ///< LSF assembled from LICH segments.
|
||||||
M17HardViterbi viterbi; ///< Viterbi decoder.
|
M17StreamFrame streamFrame; ///< Latest stream dat frame received.
|
||||||
|
M17HardViterbi viterbi; ///< Viterbi decoder.
|
||||||
|
|
||||||
///< Maximum allowed hamming distance when determining the frame type.
|
///< Maximum allowed hamming distance when determining the frame type.
|
||||||
static constexpr uint8_t MAX_SYNC_HAMM_DISTANCE = 4;
|
static constexpr uint8_t MAX_SYNC_HAMM_DISTANCE = 4;
|
||||||
|
|
||||||
///< Maximum number of corrected bit errors allowed in a stream frame.
|
|
||||||
static constexpr uint16_t MAX_VITERBI_ERRORS = 15;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace M17
|
} // namespace M17
|
||||||
|
|
||||||
#endif // M17FRAMEDECODER_H
|
#endif // M17FRAMEDECODER_H
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include "M17Datatypes.hpp"
|
#include "M17Datatypes.hpp"
|
||||||
#include "Callsign.hpp"
|
|
||||||
|
|
||||||
namespace M17
|
namespace M17
|
||||||
{
|
{
|
||||||
|
|
@ -65,28 +64,28 @@ public:
|
||||||
*
|
*
|
||||||
* @param callsign: string containing the source callsign.
|
* @param callsign: string containing the source callsign.
|
||||||
*/
|
*/
|
||||||
void setSource(const Callsign& callsign);
|
void setSource(const std::string& callsign);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get source callsign.
|
* Get source callsign.
|
||||||
*
|
*
|
||||||
* @return: string containing the source callsign.
|
* @return: string containing the source callsign.
|
||||||
*/
|
*/
|
||||||
Callsign getSource();
|
std::string getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set destination callsign.
|
* Set destination callsign.
|
||||||
*
|
*
|
||||||
* @param callsign: string containing the destination callsign.
|
* @param callsign: string containing the destination callsign.
|
||||||
*/
|
*/
|
||||||
void setDestination(const Callsign& callsign);
|
void setDestination(const std::string& callsign);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get destination callsign.
|
* Get destination callsign.
|
||||||
*
|
*
|
||||||
* @return: string containing the destination callsign.
|
* @return: string containing the destination callsign.
|
||||||
*/
|
*/
|
||||||
Callsign getDestination();
|
std::string getDestination();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get stream type field.
|
* Get stream type field.
|
||||||
|
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
||||||
* Niccolò Izzo IU2KIN *
|
|
||||||
* Frederik Saraci IU2NRO *
|
|
||||||
* Silvano Seva IU2KWO *
|
|
||||||
* *
|
|
||||||
* 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; either 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 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 <cstring>
|
|
||||||
#include "protocols/M17/Callsign.hpp"
|
|
||||||
|
|
||||||
using namespace M17;
|
|
||||||
|
|
||||||
static const char BROADCAST_CALL[] = "ALL";
|
|
||||||
static const char INVALID_CALL[] = "INVALID";
|
|
||||||
static const char charMap[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/.";
|
|
||||||
|
|
||||||
Callsign::Callsign() : Callsign(INVALID_CALL)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::Callsign(const std::string callsign) : Callsign(callsign.c_str())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::Callsign(const char *callsign)
|
|
||||||
{
|
|
||||||
std::memset(call, 0, sizeof(call));
|
|
||||||
std::strncpy(call, callsign, MAX_CALLSIGN_CHARS);
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::Callsign(const call_t &encodedCall)
|
|
||||||
{
|
|
||||||
bool isBroadcast = true;
|
|
||||||
bool isInvalid = true;
|
|
||||||
|
|
||||||
for (auto &elem : encodedCall) {
|
|
||||||
if (elem != 0xFF)
|
|
||||||
isBroadcast = false;
|
|
||||||
|
|
||||||
if (elem != 0x00)
|
|
||||||
isInvalid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memset(call, 0, sizeof(call));
|
|
||||||
|
|
||||||
if (isBroadcast) {
|
|
||||||
std::strncpy(call, BROADCAST_CALL, sizeof(call));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInvalid) {
|
|
||||||
std::strncpy(call, INVALID_CALL, sizeof(call));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert to little endian format
|
|
||||||
uint64_t encoded = 0;
|
|
||||||
auto p = reinterpret_cast<uint8_t *>(&encoded);
|
|
||||||
std::copy(encodedCall.rbegin(), encodedCall.rend(), p);
|
|
||||||
|
|
||||||
size_t pos = 0;
|
|
||||||
while (encoded != 0 && pos < MAX_CALLSIGN_CHARS) {
|
|
||||||
call[pos++] = charMap[encoded % 40];
|
|
||||||
encoded /= 40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Callsign::isSpecial() const
|
|
||||||
{
|
|
||||||
if ((std::strcmp(call, "INFO") == 0) || (std::strcmp(call, "ECHO") == 0)
|
|
||||||
|| (std::strcmp(call, "ALL") == 0))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::operator std::string() const
|
|
||||||
{
|
|
||||||
return std::string(call);
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::operator const char *() const
|
|
||||||
{
|
|
||||||
return call;
|
|
||||||
}
|
|
||||||
|
|
||||||
Callsign::operator call_t() const
|
|
||||||
{
|
|
||||||
call_t encoded;
|
|
||||||
uint64_t tmp = 0;
|
|
||||||
|
|
||||||
if (strcmp(call, BROADCAST_CALL) == 0) {
|
|
||||||
encoded.fill(0xFF);
|
|
||||||
return encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strcmp(call, INVALID_CALL) == 0) {
|
|
||||||
encoded.fill(0x00);
|
|
||||||
return encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = strlen(call) - 1; i >= 0; i--) {
|
|
||||||
tmp *= 40;
|
|
||||||
|
|
||||||
if (call[i] >= 'A' && call[i] <= 'Z') {
|
|
||||||
tmp += (call[i] - 'A') + 1;
|
|
||||||
} else if (call[i] >= '0' && call[i] <= '9') {
|
|
||||||
tmp += (call[i] - '0') + 27;
|
|
||||||
} else if (call[i] == '-') {
|
|
||||||
tmp += 37;
|
|
||||||
} else if (call[i] == '/') {
|
|
||||||
tmp += 38;
|
|
||||||
} else if (call[i] == '.') {
|
|
||||||
tmp += 39;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return encoded callsign in big endian format
|
|
||||||
auto *ptr = reinterpret_cast<uint8_t *>(&tmp);
|
|
||||||
std::copy(ptr, ptr + 6, encoded.rbegin());
|
|
||||||
|
|
||||||
return encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Callsign::operator==(const Callsign &other) const
|
|
||||||
{
|
|
||||||
// find slash and possibly truncate if slash is within first 3 chars
|
|
||||||
const char *truncatedLocal = call;
|
|
||||||
const char *truncatedIncoming = other.call;
|
|
||||||
|
|
||||||
const char *slash = std::strchr(call, '/');
|
|
||||||
if (slash && (slash - call) <= 2)
|
|
||||||
truncatedLocal = slash + 1;
|
|
||||||
|
|
||||||
slash = std::strchr(other.call, '/');
|
|
||||||
if (slash && (slash - other.call) <= 2)
|
|
||||||
truncatedIncoming = slash + 1;
|
|
||||||
|
|
||||||
return std::strcmp(truncatedLocal, truncatedIncoming) == 0;
|
|
||||||
}
|
|
||||||
|
|
@ -249,23 +249,7 @@ bool M17Demodulator::update(const bool invertPhase)
|
||||||
if(invertPhase) elem = 0.0f - elem;
|
if(invertPhase) elem = 0.0f - elem;
|
||||||
sample = static_cast< int16_t >(M17::rrc_24k(elem));
|
sample = static_cast< int16_t >(M17::rrc_24k(elem));
|
||||||
|
|
||||||
// Clock recovery reset MUST come before sampling
|
// Update correlator and sample filter for correlation thresholds
|
||||||
if((sampleIndex == 0) && resetClockRec) {
|
|
||||||
clockRec.reset();
|
|
||||||
resetClockRec = false;
|
|
||||||
updateSampPoint = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update sample point only when "far enough" from the last sampling,
|
|
||||||
// to avoid sampling issues when SP rolls over.
|
|
||||||
int diff = samplingPoint - sampleIndex;
|
|
||||||
if(updateSampPoint && (std::abs(diff) == SAMPLES_PER_SYMBOL/2)) {
|
|
||||||
clockRec.update();
|
|
||||||
samplingPoint = clockRec.samplingPoint();
|
|
||||||
updateSampPoint = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
clockRec.sample(sample);
|
|
||||||
correlator.sample(sample);
|
correlator.sample(sample);
|
||||||
corrThreshold = sampleFilter(std::abs(sample));
|
corrThreshold = sampleFilter(std::abs(sample));
|
||||||
|
|
||||||
|
|
@ -293,7 +277,7 @@ bool M17Demodulator::update(const bool invertPhase)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DemodState::SYNC_UPDATE:
|
case DemodState::SYNC_UPDATE:
|
||||||
syncUpdateState();
|
syncUpdateState(sample);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,7 +290,6 @@ bool M17Demodulator::update(const bool invertPhase)
|
||||||
|
|
||||||
void M17Demodulator::quantize(stream_sample_t sample)
|
void M17Demodulator::quantize(stream_sample_t sample)
|
||||||
{
|
{
|
||||||
auto outerDeviation = devEstimator.outerDeviation();
|
|
||||||
int8_t symbol;
|
int8_t symbol;
|
||||||
|
|
||||||
if(sample > (2 * outerDeviation.first)/3)
|
if(sample > (2 * outerDeviation.first)/3)
|
||||||
|
|
@ -328,6 +311,13 @@ void M17Demodulator::quantize(stream_sample_t sample)
|
||||||
|
|
||||||
setSymbol(*demodFrame, frameIndex, symbol);
|
setSymbol(*demodFrame, frameIndex, symbol);
|
||||||
frameIndex += 1;
|
frameIndex += 1;
|
||||||
|
|
||||||
|
if(frameIndex >= M17_FRAME_SYMBOLS)
|
||||||
|
{
|
||||||
|
std::swap(readyFrame, demodFrame);
|
||||||
|
frameIndex = 0;
|
||||||
|
newFrame = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17Demodulator::reset()
|
void M17Demodulator::reset()
|
||||||
|
|
@ -355,9 +345,8 @@ void M17Demodulator::syncedState()
|
||||||
{
|
{
|
||||||
// Set sampling point and deviation, zero frame symbol count
|
// Set sampling point and deviation, zero frame symbol count
|
||||||
samplingPoint = streamSync.samplingIndex();
|
samplingPoint = streamSync.samplingIndex();
|
||||||
auto deviation = correlator.maxDeviation(samplingPoint);
|
outerDeviation = correlator.maxDeviation(samplingPoint);
|
||||||
frameIndex = 0;
|
frameIndex = 0;
|
||||||
devEstimator.init(deviation);
|
|
||||||
|
|
||||||
// Quantize the syncword taking data from the correlator
|
// Quantize the syncword taking data from the correlator
|
||||||
// memory.
|
// memory.
|
||||||
|
|
@ -384,38 +373,56 @@ void M17Demodulator::lockedState(int16_t sample)
|
||||||
if(sampleIndex != samplingPoint)
|
if(sampleIndex != samplingPoint)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Quantize and update frame at each sampling point
|
||||||
quantize(sample);
|
quantize(sample);
|
||||||
devEstimator.sample(sample);
|
|
||||||
|
|
||||||
if(frameIndex == M17_FRAME_SYMBOLS) {
|
// When we have reached almost the end of a frame, switch
|
||||||
devEstimator.update();
|
// to syncpoint update.
|
||||||
std::swap(readyFrame, demodFrame);
|
if(frameIndex == (M17_FRAME_SYMBOLS - M17_SYNCWORD_SYMBOLS/2)) {
|
||||||
|
|
||||||
frameIndex = 0;
|
|
||||||
newFrame = true;
|
|
||||||
updateSampPoint = true;
|
|
||||||
demodState = DemodState::SYNC_UPDATE;
|
demodState = DemodState::SYNC_UPDATE;
|
||||||
|
syncCount = SYNCWORD_SAMPLES * 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17Demodulator::syncUpdateState()
|
void M17Demodulator::syncUpdateState(int16_t sample)
|
||||||
{
|
{
|
||||||
uint8_t streamHd = hammingDistance((*demodFrame)[0], STREAM_SYNC_WORD[0])
|
// Keep filling the ongoing frame!
|
||||||
+ hammingDistance((*demodFrame)[1], STREAM_SYNC_WORD[1]);
|
if(sampleIndex == samplingPoint)
|
||||||
|
quantize(sample);
|
||||||
|
|
||||||
uint8_t eotHd = hammingDistance((*demodFrame)[0], EOT_SYNC_WORD[0])
|
// Find the new correlation peak
|
||||||
+ hammingDistance((*demodFrame)[1], EOT_SYNC_WORD[1]);
|
int32_t syncThresh = static_cast< int32_t >(corrThreshold * 33.0f);
|
||||||
|
int8_t syncStatus = streamSync.update(correlator, syncThresh, -syncThresh);
|
||||||
|
|
||||||
|
// Correlation has to coincide with a syncword!
|
||||||
|
if((syncStatus != 0) && (frameIndex == M17_SYNCWORD_SYMBOLS)) {
|
||||||
|
uint8_t hd = hammingDistance((*demodFrame)[0], STREAM_SYNC_WORD[0])
|
||||||
|
+ hammingDistance((*demodFrame)[1], STREAM_SYNC_WORD[1]);
|
||||||
|
|
||||||
|
// Valid sync found: update deviation and sample
|
||||||
|
// point, then go back to locked state
|
||||||
|
if(hd <= 1) {
|
||||||
|
outerDeviation = correlator.maxDeviation(samplingPoint);
|
||||||
|
samplingPoint = streamSync.samplingIndex();
|
||||||
|
missedSyncs = 0;
|
||||||
|
demodState = DemodState::LOCKED;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No syncword found within the window, increase the count
|
||||||
|
// of missed syncs and choose where to go. The lock is lost
|
||||||
|
// after four consecutive sync misses.
|
||||||
|
if(syncCount == 0) {
|
||||||
|
if(missedSyncs >= 4)
|
||||||
|
demodState = DemodState::UNLOCKED;
|
||||||
|
else
|
||||||
|
demodState = DemodState::LOCKED;
|
||||||
|
|
||||||
if(streamHd <= 1)
|
|
||||||
missedSyncs = 0;
|
|
||||||
else
|
|
||||||
missedSyncs += 1;
|
missedSyncs += 1;
|
||||||
|
}
|
||||||
|
|
||||||
// The lock is lost after four consecutive sync misses or an EOT frame.
|
syncCount -= 1;
|
||||||
if((missedSyncs > 4) || (eotHd <= 1))
|
|
||||||
demodState = DemodState::UNLOCKED;
|
|
||||||
else
|
|
||||||
demodState = DemodState::LOCKED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr std::array < float, 3 > M17Demodulator::sfNum;
|
constexpr std::array < float, 3 > M17Demodulator::sfNum;
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,9 @@
|
||||||
|
|
||||||
using namespace M17;
|
using namespace M17;
|
||||||
|
|
||||||
M17FrameDecoder::M17FrameDecoder()
|
M17FrameDecoder::M17FrameDecoder() { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
M17FrameDecoder::~M17FrameDecoder()
|
M17FrameDecoder::~M17FrameDecoder() { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void M17FrameDecoder::reset()
|
void M17FrameDecoder::reset()
|
||||||
{
|
{
|
||||||
|
|
@ -45,10 +41,10 @@ void M17FrameDecoder::reset()
|
||||||
streamFrame.clear();
|
streamFrame.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
M17FrameType M17FrameDecoder::decodeFrame(const frame_t &frame)
|
M17FrameType M17FrameDecoder::decodeFrame(const frame_t& frame)
|
||||||
{
|
{
|
||||||
std::array<uint8_t, 2> syncWord;
|
std::array< uint8_t, 2 > syncWord;
|
||||||
std::array<uint8_t, 46> data;
|
std::array< uint8_t, 46 > data;
|
||||||
|
|
||||||
std::copy_n(frame.begin(), 2, syncWord.begin());
|
std::copy_n(frame.begin(), 2, syncWord.begin());
|
||||||
std::copy(frame.begin() + 2, frame.end(), data.begin());
|
std::copy(frame.begin() + 2, frame.end(), data.begin());
|
||||||
|
|
@ -59,7 +55,8 @@ M17FrameType M17FrameDecoder::decodeFrame(const frame_t &frame)
|
||||||
|
|
||||||
auto type = getFrameType(syncWord);
|
auto type = getFrameType(syncWord);
|
||||||
|
|
||||||
switch (type) {
|
switch(type)
|
||||||
|
{
|
||||||
case M17FrameType::LINK_SETUP:
|
case M17FrameType::LINK_SETUP:
|
||||||
decodeLSF(data);
|
decodeLSF(data);
|
||||||
break;
|
break;
|
||||||
|
|
@ -75,19 +72,18 @@ M17FrameType M17FrameDecoder::decodeFrame(const frame_t &frame)
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
M17FrameType
|
M17FrameType M17FrameDecoder::getFrameType(const std::array< uint8_t, 2 >& syncWord)
|
||||||
M17FrameDecoder::getFrameType(const std::array<uint8_t, 2> &syncWord)
|
|
||||||
{
|
{
|
||||||
// Preamble
|
// Preamble
|
||||||
M17FrameType type = M17FrameType::PREAMBLE;
|
M17FrameType type = M17FrameType::PREAMBLE;
|
||||||
uint8_t minDistance = hammingDistance(syncWord[0], 0x77)
|
uint8_t minDistance = hammingDistance(syncWord[0], 0x77)
|
||||||
+ hammingDistance(syncWord[1], 0x77);
|
+ hammingDistance(syncWord[1], 0x77);
|
||||||
|
|
||||||
// Link setup frame
|
// Link setup frame
|
||||||
uint8_t hammDistance = hammingDistance(syncWord[0], LSF_SYNC_WORD[0])
|
uint8_t hammDistance = hammingDistance(syncWord[0], LSF_SYNC_WORD[0])
|
||||||
+ hammingDistance(syncWord[1], LSF_SYNC_WORD[1]);
|
+ hammingDistance(syncWord[1], LSF_SYNC_WORD[1]);
|
||||||
|
if(hammDistance < minDistance)
|
||||||
if (hammDistance < minDistance) {
|
{
|
||||||
type = M17FrameType::LINK_SETUP;
|
type = M17FrameType::LINK_SETUP;
|
||||||
minDistance = hammDistance;
|
minDistance = hammDistance;
|
||||||
}
|
}
|
||||||
|
|
@ -95,43 +91,45 @@ M17FrameDecoder::getFrameType(const std::array<uint8_t, 2> &syncWord)
|
||||||
// Stream frame
|
// Stream frame
|
||||||
hammDistance = hammingDistance(syncWord[0], STREAM_SYNC_WORD[0])
|
hammDistance = hammingDistance(syncWord[0], STREAM_SYNC_WORD[0])
|
||||||
+ hammingDistance(syncWord[1], STREAM_SYNC_WORD[1]);
|
+ hammingDistance(syncWord[1], STREAM_SYNC_WORD[1]);
|
||||||
|
if(hammDistance < minDistance)
|
||||||
if (hammDistance < minDistance) {
|
{
|
||||||
type = M17FrameType::STREAM;
|
type = M17FrameType::STREAM;
|
||||||
minDistance = hammDistance;
|
minDistance = hammDistance;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check value of minimum hamming distance found, if exceeds the allowed
|
// Check value of minimum hamming distance found, if exceeds the allowed
|
||||||
// limit consider the frame as of unknown type.
|
// limit consider the frame as of unknown type.
|
||||||
if (minDistance > MAX_SYNC_HAMM_DISTANCE) {
|
if(minDistance > MAX_SYNC_HAMM_DISTANCE)
|
||||||
|
{
|
||||||
type = M17FrameType::UNKNOWN;
|
type = M17FrameType::UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17FrameDecoder::decodeLSF(const std::array<uint8_t, 46> &data)
|
void M17FrameDecoder::decodeLSF(const std::array< uint8_t, 46 >& data)
|
||||||
{
|
{
|
||||||
std::array<uint8_t, sizeof(M17LinkSetupFrame)> tmp;
|
std::array< uint8_t, sizeof(M17LinkSetupFrame) > tmp;
|
||||||
|
|
||||||
viterbi.decodePunctured(data, tmp, LSF_PUNCTURE);
|
viterbi.decodePunctured(data, tmp, LSF_PUNCTURE);
|
||||||
memcpy(&lsf.data, tmp.data(), tmp.size());
|
memcpy(&lsf.data, tmp.data(), tmp.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17FrameDecoder::decodeStream(const std::array<uint8_t, 46> &data)
|
void M17FrameDecoder::decodeStream(const std::array< uint8_t, 46 >& data)
|
||||||
{
|
{
|
||||||
// Extract and unpack the LICH segment contained at beginning of frame
|
// Extract and unpack the LICH segment contained at beginning of frame
|
||||||
lich_t lich;
|
lich_t lich;
|
||||||
std::array<uint8_t, 6> lsfSegment;
|
std::array < uint8_t, 6 > lsfSegment;
|
||||||
|
|
||||||
std::copy_n(data.begin(), lich.size(), lich.begin());
|
std::copy_n(data.begin(), lich.size(), lich.begin());
|
||||||
bool decodeOk = decodeLich(lsfSegment, lich);
|
bool decodeOk = decodeLich(lsfSegment, lich);
|
||||||
|
|
||||||
if (decodeOk) {
|
if(decodeOk)
|
||||||
|
{
|
||||||
// Append LICH segment
|
// Append LICH segment
|
||||||
uint8_t segmentNum = lsfSegment[5];
|
uint8_t segmentNum = lsfSegment[5];
|
||||||
uint8_t segmentSize = lsfSegment.size() - 1;
|
uint8_t segmentSize = lsfSegment.size() - 1;
|
||||||
uint8_t *ptr = reinterpret_cast<uint8_t *>(&lsfFromLich.data);
|
uint8_t *ptr = reinterpret_cast < uint8_t * >(&lsfFromLich.data);
|
||||||
ptr += segmentNum * segmentSize;
|
ptr += segmentNum * segmentSize;
|
||||||
memcpy(ptr, lsfSegment.data(), segmentSize);
|
memcpy(ptr, lsfSegment.data(), segmentSize);
|
||||||
|
|
||||||
|
|
@ -139,30 +137,28 @@ void M17FrameDecoder::decodeStream(const std::array<uint8_t, 46> &data)
|
||||||
lsfSegmentMap |= 1 << segmentNum;
|
lsfSegmentMap |= 1 << segmentNum;
|
||||||
|
|
||||||
// Check if we have received all the six LICH segments
|
// Check if we have received all the six LICH segments
|
||||||
if (lsfSegmentMap == 0x3F) {
|
if(lsfSegmentMap == 0x3F)
|
||||||
if (lsfFromLich.valid())
|
{
|
||||||
lsf = lsfFromLich;
|
if(lsfFromLich.valid()) lsf = lsfFromLich;
|
||||||
lsfSegmentMap = 0;
|
lsfSegmentMap = 0;
|
||||||
lsfFromLich.clear();
|
lsfFromLich.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract and decode stream data
|
// Extract and decode stream data
|
||||||
std::array<uint8_t, 34> punctured;
|
std::array< uint8_t, 34 > punctured;
|
||||||
std::array<uint8_t, sizeof(M17StreamFrame)> tmp;
|
std::array< uint8_t, sizeof(M17StreamFrame) > tmp;
|
||||||
|
|
||||||
auto begin = data.begin();
|
auto begin = data.begin();
|
||||||
begin += lich.size();
|
begin += lich.size();
|
||||||
std::copy(begin, data.end(), punctured.begin());
|
std::copy(begin, data.end(), punctured.begin());
|
||||||
|
|
||||||
// Skip payload copy if BER is too high to avoid audio artifacts
|
viterbi.decodePunctured(punctured, tmp, DATA_PUNCTURE);
|
||||||
uint16_t bitErrs = viterbi.decodePunctured(punctured, tmp, DATA_PUNCTURE);
|
memcpy(&streamFrame.data, tmp.data(), tmp.size());
|
||||||
if (bitErrs < MAX_VITERBI_ERRORS)
|
|
||||||
memcpy(&streamFrame.data, tmp.data(), tmp.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool M17FrameDecoder::decodeLich(std::array<uint8_t, 6> &segment,
|
bool M17FrameDecoder::decodeLich(std::array < uint8_t, 6 >& segment,
|
||||||
const lich_t &lich)
|
const lich_t& lich)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Extract and unpack the LICH segment contained in the frame header.
|
* Extract and unpack the LICH segment contained in the frame header.
|
||||||
|
|
@ -177,26 +173,31 @@ bool M17FrameDecoder::decodeLich(std::array<uint8_t, 6> &segment,
|
||||||
|
|
||||||
segment.fill(0x00);
|
segment.fill(0x00);
|
||||||
|
|
||||||
size_t index = 0;
|
size_t index = 0;
|
||||||
uint32_t block = 0;
|
uint32_t block = 0;
|
||||||
|
|
||||||
for (size_t i = 0; i < 4; i++) {
|
for(size_t i = 0; i < 4; i++)
|
||||||
memcpy(&block, lich.data() + 3 * i, 3);
|
{
|
||||||
|
memcpy(&block, lich.data() + 3*i, 3);
|
||||||
block = __builtin_bswap32(block) >> 8;
|
block = __builtin_bswap32(block) >> 8;
|
||||||
uint16_t decoded = golay24_decode(block);
|
uint16_t decoded = golay24_decode(block);
|
||||||
|
|
||||||
// Unrecoverable error, abort decoding
|
// Unrecoverable error, abort decoding
|
||||||
if (decoded == 0xFFFF) {
|
if(decoded == 0xFFFF)
|
||||||
|
{
|
||||||
segment.fill(0x00);
|
segment.fill(0x00);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i & 1) {
|
if(i & 1)
|
||||||
segment[index++] |= (decoded >> 8); // upper 4 bits
|
{
|
||||||
segment[index++] = (decoded & 0xFF); // lower 8 bits
|
segment[index++] |= (decoded >> 8); // upper 4 bits
|
||||||
} else {
|
segment[index++] = (decoded & 0xFF); // lower 8 bits
|
||||||
segment[index++] |= (decoded >> 4); // upper 8 bits
|
}
|
||||||
segment[index] = (decoded & 0x0F) << 4; // lower 4 bits
|
else
|
||||||
|
{
|
||||||
|
segment[index++] |= (decoded >> 4); // upper 8 bits
|
||||||
|
segment[index] = (decoded & 0x0F) << 4; // lower 4 bits
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +206,7 @@ bool M17FrameDecoder::decodeLich(std::array<uint8_t, 6> &segment,
|
||||||
// zero and five.
|
// zero and five.
|
||||||
segment[5] >>= 5;
|
segment[5] >>= 5;
|
||||||
|
|
||||||
if (segment[5] > 5)
|
if(segment[5] > 5)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -41,24 +41,24 @@ void M17LinkSetupFrame::clear()
|
||||||
data.dst.fill(0xFF);
|
data.dst.fill(0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17LinkSetupFrame::setSource(const Callsign& callsign)
|
void M17LinkSetupFrame::setSource(const std::string& callsign)
|
||||||
{
|
{
|
||||||
data.src = callsign;
|
encode_callsign(callsign, data.src);
|
||||||
}
|
}
|
||||||
|
|
||||||
Callsign M17LinkSetupFrame::getSource()
|
std::string M17LinkSetupFrame::getSource()
|
||||||
{
|
{
|
||||||
return Callsign(data.src);
|
return decode_callsign(data.src);
|
||||||
}
|
}
|
||||||
|
|
||||||
void M17LinkSetupFrame::setDestination(const Callsign& callsign)
|
void M17LinkSetupFrame::setDestination(const std::string& callsign)
|
||||||
{
|
{
|
||||||
data.dst = callsign;
|
encode_callsign(callsign, data.dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
Callsign M17LinkSetupFrame::getDestination()
|
std::string M17LinkSetupFrame::getDestination()
|
||||||
{
|
{
|
||||||
return Callsign(data.dst);
|
return decode_callsign(data.dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
streamType_t M17LinkSetupFrame::getType()
|
streamType_t M17LinkSetupFrame::getType()
|
||||||
|
|
|
||||||
|
|
@ -206,9 +206,8 @@ void OpMode_M17::rxState(rtxStatus_t *const status)
|
||||||
dataValid = true;
|
dataValid = true;
|
||||||
|
|
||||||
// Retrieve stream source and destination data
|
// Retrieve stream source and destination data
|
||||||
Callsign dst = lsf.getDestination();
|
std::string dst = lsf.getDestination();
|
||||||
Callsign src = lsf.getSource();
|
std::string src = lsf.getSource();
|
||||||
strncpy(status->M17_dst, dst, 10);
|
|
||||||
|
|
||||||
// Retrieve extended callsign data
|
// Retrieve extended callsign data
|
||||||
streamType_t streamType = lsf.getType();
|
streamType_t streamType = lsf.getType();
|
||||||
|
|
@ -219,8 +218,8 @@ void OpMode_M17::rxState(rtxStatus_t *const status)
|
||||||
extendedCall = true;
|
extendedCall = true;
|
||||||
|
|
||||||
meta_t& meta = lsf.metadata();
|
meta_t& meta = lsf.metadata();
|
||||||
Callsign exCall1(meta.extended_call_sign.call1);
|
std::string exCall1 = decode_callsign(meta.extended_call_sign.call1);
|
||||||
Callsign exCall2(meta.extended_call_sign.call2);
|
std::string exCall2 = decode_callsign(meta.extended_call_sign.call2);
|
||||||
|
|
||||||
//
|
//
|
||||||
// The source callsign only contains the last link when
|
// The source callsign only contains the last link when
|
||||||
|
|
@ -228,16 +227,21 @@ void OpMode_M17::rxState(rtxStatus_t *const status)
|
||||||
// the true source of a transmission, we need to store the first
|
// the true source of a transmission, we need to store the first
|
||||||
// extended callsign in M17_src.
|
// extended callsign in M17_src.
|
||||||
//
|
//
|
||||||
strncpy(status->M17_src, exCall1, 10);
|
strncpy(status->M17_src, exCall1.c_str(), 10);
|
||||||
strncpy(status->M17_refl, exCall2, 10);
|
strncpy(status->M17_refl, exCall2.c_str(), 10);
|
||||||
strncpy(status->M17_link, src, 10);
|
|
||||||
} else {
|
extendedCall = true;
|
||||||
strncpy(status->M17_src, src, 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set source and destination fields.
|
// Set source and destination fields.
|
||||||
// If we have received an extended callsign the src will be the RF link address
|
// If we have received an extended callsign the src will be the RF link address
|
||||||
// The M17_src will already be stored from the extended callsign
|
// The M17_src will already be stored from the extended callsign
|
||||||
|
strncpy(status->M17_dst, dst.c_str(), 10);
|
||||||
|
|
||||||
|
if(extendedCall)
|
||||||
|
strncpy(status->M17_link, src.c_str(), 10);
|
||||||
|
else
|
||||||
|
strncpy(status->M17_src, src.c_str(), 10);
|
||||||
|
|
||||||
// Check CAN on RX, if enabled.
|
// Check CAN on RX, if enabled.
|
||||||
// If check is disabled, force match to true.
|
// If check is disabled, force match to true.
|
||||||
|
|
@ -246,8 +250,7 @@ void OpMode_M17::rxState(rtxStatus_t *const status)
|
||||||
|
|
||||||
// Check if the destination callsign of the incoming transmission
|
// Check if the destination callsign of the incoming transmission
|
||||||
// matches with ours
|
// matches with ours
|
||||||
bool callMatch = (Callsign(status->source_address) == dst)
|
bool callMatch = compareCallsigns(std::string(status->source_address), dst);
|
||||||
|| dst.isSpecial();
|
|
||||||
|
|
||||||
// Open audio path only if CAN and callsign match
|
// Open audio path only if CAN and callsign match
|
||||||
uint8_t pthSts = audioPath_getStatus(rxAudioPath);
|
uint8_t pthSts = audioPath_getStatus(rxAudioPath);
|
||||||
|
|
@ -303,14 +306,13 @@ void OpMode_M17::txState(rtxStatus_t *const status)
|
||||||
{
|
{
|
||||||
startTx = false;
|
startTx = false;
|
||||||
|
|
||||||
|
std::string src(status->source_address);
|
||||||
|
std::string dst(status->destination_address);
|
||||||
M17LinkSetupFrame lsf;
|
M17LinkSetupFrame lsf;
|
||||||
|
|
||||||
lsf.clear();
|
lsf.clear();
|
||||||
lsf.setSource(status->source_address);
|
lsf.setSource(src);
|
||||||
|
if(!dst.empty()) lsf.setDestination(dst);
|
||||||
Callsign dst(status->destination_address);
|
|
||||||
if(!dst.isEmpty())
|
|
||||||
lsf.setDestination(dst);
|
|
||||||
|
|
||||||
streamType_t type;
|
streamType_t type;
|
||||||
type.fields.dataMode = M17_DATAMODE_STREAM; // Stream
|
type.fields.dataMode = M17_DATAMODE_STREAM; // Stream
|
||||||
|
|
|
||||||
|
|
@ -69,12 +69,8 @@ openrtx/include/interfaces/radio.h
|
||||||
openrtx/include/peripherals/gps.h
|
openrtx/include/peripherals/gps.h
|
||||||
openrtx/include/peripherals/rng.h
|
openrtx/include/peripherals/rng.h
|
||||||
openrtx/include/peripherals/rtc.h
|
openrtx/include/peripherals/rtc.h
|
||||||
openrtx/include/protocols/M17/Callsign.hpp
|
|
||||||
openrtx/include/protocols/M17/M17FrameDecoder.hpp
|
|
||||||
openrtx/src/core/dsp.cpp
|
openrtx/src/core/dsp.cpp
|
||||||
openrtx/src/core/memory_profiling.cpp
|
openrtx/src/core/memory_profiling.cpp
|
||||||
openrtx/src/protocols/M17/Callsign.cpp
|
|
||||||
openrtx/src/protocols/M17/M17FrameDecoder.cpp
|
|
||||||
platform/drivers/ADC/ADC0_GDx.h
|
platform/drivers/ADC/ADC0_GDx.h
|
||||||
platform/drivers/audio/MAX9814.h
|
platform/drivers/audio/MAX9814.h
|
||||||
platform/drivers/baseband/MCP4551.h
|
platform/drivers/baseband/MCP4551.h
|
||||||
|
|
@ -92,7 +88,6 @@ platform/targets/linux/emulator/sdl_engine.h
|
||||||
platform/targets/ttwrplus/pmu.h
|
platform/targets/ttwrplus/pmu.h
|
||||||
tests/platform/mic_test.c
|
tests/platform/mic_test.c
|
||||||
tests/platform/codec2_encode_test.c
|
tests/platform/codec2_encode_test.c
|
||||||
tests/unit/M17_callsign.cpp
|
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2025 by Federico Amedeo Izzo IU2NUO, *
|
|
||||||
* Niccolò Izzo IU2KIN *
|
|
||||||
* Frederik Saraci IU2NRO *
|
|
||||||
* Silvano Seva IU2KWO *
|
|
||||||
* *
|
|
||||||
* *
|
|
||||||
* 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; either 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 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 <cstdio>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <cstring>
|
|
||||||
#include <array>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <iostream>
|
|
||||||
#include "protocols/M17/Callsign.hpp"
|
|
||||||
#include "protocols/M17/M17Datatypes.hpp"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
int test_encode_ab1cd()
|
|
||||||
{
|
|
||||||
const char callsign[] = "AB1CD";
|
|
||||||
M17::call_t expected = { 0x00, 0x00, 0x00, 0x9f, 0xdd, 0x51 };
|
|
||||||
|
|
||||||
M17::Callsign test = M17::Callsign(callsign);
|
|
||||||
M17::call_t actual = test;
|
|
||||||
if (equal(begin(actual), end(actual), begin(expected), end(expected))) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_encode_empty()
|
|
||||||
{
|
|
||||||
const char callsign[] = "";
|
|
||||||
M17::call_t expected = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
|
||||||
|
|
||||||
M17::Callsign test = M17::Callsign(callsign);
|
|
||||||
M17::call_t actual = test;
|
|
||||||
if (equal(begin(actual), end(actual), begin(expected), end(expected))) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int test_decode_ab1cd()
|
|
||||||
{
|
|
||||||
M17::call_t callsign = { 0x00, 0x00, 0x00, 0x9f, 0xdd, 0x51 };
|
|
||||||
|
|
||||||
M17::Callsign test = M17::Callsign(callsign);
|
|
||||||
const char expected[] = "AB1CD";
|
|
||||||
const char *actual = test;
|
|
||||||
if (strcmp(expected, actual) == 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int main()
|
|
||||||
{
|
|
||||||
if (test_encode_ab1cd()) {
|
|
||||||
printf("Error in encoding callsign ab1cd!\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (test_encode_empty()) {
|
|
||||||
printf("Error in encoding empty callsign !\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (test_decode_ab1cd()) {
|
|
||||||
printf("Error in decoding callsign ab1cd!\n");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue