Add a IEEE 802.15.4 compatible decoder

Add a IEEE 802.15.4 like decoder, which supports
the IEEE 802.15.4 standard but also a large variety
of ICs based on this framing scheme. Such framings
are quite common in many Cubesat missions.

The decoder has been tested an works well with at least
the Reaktor Hello World satellite
This commit is contained in:
Manolis Surligas 2019-09-12 19:45:45 +03:00
parent 142c995370
commit c40b83211f
12 changed files with 623 additions and 49 deletions

View File

@ -98,6 +98,35 @@ Before submitting a new merge request, rebase the `master` branch and
confirm that the automated CI tests have successfully completed for all platforms
mandated by the `.gitlab-ci.yml` recipe.
### Coding style
For the C++ code, `gr-satnogs` uses a slightly modified version of the
**Stroustrup** style, which is a nicer adaptation of the well known K&R style.
In addition, we decided to decrease the indentation from 4 to 2 spaces.
This choice was made mainly to avoid braking statements with long namespaces.
We also found ourselves, that with smaller indentation we use more descriptive
variable names, avoiding frustrating abbreviations without phoenixes etc.
At the root directory of the project there is the `astyle` options
file `.astylerc` containing the proper configuration.
Developers can import this configuration to their favorite editor.
In addition the `hooks/pre-commit` file contains a Git hook,
that can be used to perform before every commit, code style formatting
with `astyle` and the `.astylerc` parameters.
To enable this hook developers should copy the hook at their `.git/hooks`
directory.
Failing to comply with the coding style described by the `.astylerc`
will result to failure of the automated tests running on our CI services.
So make sure that you either import on your editor the coding style rules
or use the `pre-commit` Git hook.
Regarding the naming of files and variables, we use the underscore naming
convention (`do_this`) instead of camel cases (`DoNotDoThis`).
Exception to this rule is the CMake module filenames. In addition,
all private variables of a C++ class, should start with the prefix
`d_` allowing the developers to spot easily private members of the object.
### Adding a new Satellite Demodulator
Demodulators are responsible for filtering, resampling and demodulating an
analog signal and converting it into suitable form, for a decoder to be able
@ -125,15 +154,16 @@ The `satnogs::decoder` class, is a virtual class providing a generic API that
every derived decoder class should implement.
The core of this class is the
```
decoder_status_t decode (const void *in, int len)
```cpp
decoder_status_t
decode(const void *in, int len)
```
method. This method accepts an input buffer `in`. The type of the items depends
on the implementation. It also takes the `len` argument specifying the number
of items available in the `in` buffer.
The method returns a `decoder_status_t` class object.
```
```cpp
class decoder_status
{
public:
@ -164,32 +194,6 @@ decoded and its data are available on the `data` field.
information regarding it, using the `gr-satnogs` metadata format. More about them
in the [Metadata](#metadata) section
### Coding style
For the C++ code, `gr-satnogs` uses a slightly modified version of the
**Stroustrup** style, which is a nicer adaptation of the well known K&R style.
In addition, we decided to decrease the indentation from 4 to 2 spaces.
This choice was made mainly to avoid braking statements with long namespaces.
We also found ourselves, that with smaller indentation we use more descriptive
variable names, avoiding frustrating abbreviations without phoenixes etc.
At the root directory of the project there is the `astyle` options
file `.astylerc` containing the proper configuration.
Developers can import this configuration to their favorite editor.
In addition the `hooks/pre-commit` file contains a Git hook,
that can be used to perform before every commit, code style formatting
with `astyle` and the `.astylerc` parameters.
To enable this hook developers should copy the hook at their `.git/hooks`
directory.
Failing to comply with the coding style described by the `.astylerc`
will result to failure of the automated tests running on our CI services.
So make sure that you either import on your editor the coding style rules
or use the `pre-commit` Git hook.
Regarding the naming of files and variables, we use the underscore naming convention (`do_this`) instead of
camel cases (`DoNotDoThis`).
Exception to this rule is the CMake module filenames. In addition, all private variables of a C++ class, should start with the prefix `d_` allowing the developers to spot easily private members of the object.
### Metadata
Each decoder generates a `pmt::pmt_t` dictionary containing the decoded data and
@ -216,7 +220,7 @@ metadata::keys()` static method which returns a list with the available
metadata keys. This method is also available in Python through the Swig interface.
For example:
```
```python
$ python
>>> import satnogs
>>> satnogs.metadata.keys()
@ -230,7 +234,24 @@ containing the raw bytes of the JSON string, which then can be passed to a UDP
sink targeting the `satnogs-client`.
The `json_converter` block accepts also a string that may be used to inject
an arbitrary number of additional information under the `extra` JSON field.
Of course, this string should be in a JSON valid format.
Of course, this string should be in a JSON valid format.
For example, such a JSON string with information on the extra field could be like
```json
{
"corrected_bits" : 0,
"extra" :
{
"x" : 3,
"y" : "test"
},
"pdu" : "igAg7nRAOCAniUMAtIoAAAAAAAAAAAAAAABNJ4kfAFD4wwAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==",
"symbol_erasures" : 0,
"time" : "2019-09-11T15:39:13.514138Z"
}
```
### Release Policy
The `gr-satnogs` OOT module uses the `major.api-compatibility.minor`

View File

@ -50,6 +50,7 @@ list(APPEND enabled_blocks
satnogs_ogg_source.xml
satnogs_noaa_apt_sink.xml
satnogs_whitening.xml
satnogs_ieee802_15_4_variant_decoder.xml
)
if(${INCLUDE_DEBUG_BLOCKS})

View File

@ -4,6 +4,7 @@
<name>Decoders</name>
<block>variable_ax25_decoder</block>
<block>variable_amsat_duv_decoder</block>
<block>variable_ieee802_15_4_variant_decoder</block>
</cat>
<cat>
<name>Satellites</name>

View File

@ -0,0 +1,80 @@
<?xml version="1.0"?>
<block>
<name>IEEE 802.15.4 Decoder Definition</name>
<key>variable_ieee802_15_4_variant_decoder</key>
<import>import satnogs</import>
<var_make>self.$(id) = $(id) = satnogs.ieee802_15_4_variant_decoder_make($preamble, $preamble_thrsh, $sync_word, $sync_thrsh, $crc, $whitening, $var_len, $frame_len)</var_make>
<var_value>satnogs.ieee802_15_4_variant_decoder_make($preamble, $preamble_thrsh, $sync_word, $sync_thrsh, $crc, $whitening, $var_len, $frame_len)</var_value>
<make></make>
<param>
<name>Ignore Me</name>
<key>value</key>
<value>'ok'</value>
<type>raw</type>
<hide>all</hide>
</param>
<param>
<name>Variable Length</name>
<key>var_len</key>
<type>enum</type>
<option>
<name>Variable Frame Length</name>
<key>True</key>
</option>
<option>
<name>Constant Frame Length</name>
<key>False</key>
</option>
</param>
<param>
<name>Frame Preamble</name>
<key>preamble</key>
<value>[0x55, 0x55, 0x55, 0x55, 0x55]</value>
<type>raw</type>
</param>
<param>
<name>Preamble Threshold</name>
<key>preamble_thrsh</key>
<value>5</value>
<type>int</type>
</param>
<param>
<name>Synchronization Word</name>
<key>sync_word</key>
<value>[0x31, 0xe5]</value>
<type>raw</type>
</param>
<param>
<name>Synchronization Word Threshold</name>
<key>sync_thrsh</key>
<value>3</value>
<type>int</type>
</param>
<param>
<name>Maximum Frame Length</name>
<key>frame_len</key>
<value>254</value>
<type>int</type>
</param>
<param>
<name>CRC</name>
<key>crc</key>
<value>None</value>
<type>raw</type>
</param>
<param>
<name>Whitening</name>
<key>whitening</key>
<value>None</value>
<type>raw</type>
</param>
</block>

View File

@ -33,15 +33,12 @@ namespace satnogs {
class SATNOGS_API crc {
public:
typedef enum crc_type {
PDU = 0,
CRC_VALID,
FREQ_OFFSET,
CORRECTED_BITS,
TIME,
SAMPLE_START,
SAMPLE_CNT,
SYMBOL_ERASURES,
KEYS_NUM
CRC_NONE = 0,
CRC16_CCITT,
CRC16_CCITT_REVERSED,
CRC16_AX25,
CRC16_IBM,
CRC_METHODS_NUM
} crc_t;
static uint16_t
@ -56,6 +53,9 @@ public:
static uint16_t
crc16_ibm(const uint8_t *data, size_t len);
static size_t
crc_size(crc_t t);
private:
static const uint16_t crc16_ccitt_table_reverse[256];
static const uint16_t crc16_ccitt_table[256];

View File

@ -101,6 +101,9 @@ public:
virtual void
reset() = 0;
virtual size_t
input_multiple() const;
size_t
max_frame_len() const;

View File

@ -22,8 +22,11 @@
#define INCLUDED_SATNOGS_IEEE802_15_4_VARIANT_DECODER_H
#include <satnogs/api.h>
#include <satnogs/decoder.h>
#include <satnogs/whitening.h>
#include <satnogs/crc.h>
#include <satnogs/shift_reg.h>
namespace gr {
namespace satnogs {
@ -38,15 +41,114 @@ namespace satnogs {
* scheme.
*
*/
class SATNOGS_API ieee802_15_4_variant_decoder {
class SATNOGS_API ieee802_15_4_variant_decoder : public decoder {
public:
/**
*
* @param preamble the preamble should be a repeated word. Note that due to AGC
* settling, the receiver may not receive the whole preamble. If the preamble
* is indeed a repeated pattern, a portion of it can be given as parameter.
* The block should be able to deal with this. However, a quite small subset
* may lead to a larger number of false alarms
*
* @param preamble_threshold the maximum number of bits that are
* allowed to be wrong at the preamble
*
* @param sync the synchronization work following the preamble
*
* @param sync_threshold the maximum number of bits that are
* allowed to be wrong at the synchronization word
*
* @param crc the CRC scheme to use
*
* @param descrambler if set, data will be first descrambled by this descrambling
* method
*
* @param var_len if set to true, variable length decoding is used. Otherwise,
* the \p max_len parameter indicates the fixed frame size
*
* @param max_len the maximum allowed decode-able frame length
*
* @return shared pointer of the decoder
*/
static decoder::decoder_sptr
make(const std::vector<uint8_t> &preamble,
size_t preamble_threshold,
const std::vector<uint8_t> &sync,
size_t sync_threshold,
crc::crc_t crc,
whitening::whitening_sptr descrambler, bool var_len = true,
size_t max_len = 1024);
ieee802_15_4_variant_decoder(const std::vector<uint8_t> &preamble,
size_t preamble_threshold,
const std::vector<uint8_t> &sync,
size_t sync_threshold,
crc::crc_t crc,
whitening::whitening_sptr descrambler);
whitening::whitening_sptr descrambler,
bool var_len = true,
size_t max_len = 1024);
~ieee802_15_4_variant_decoder();
decoder_status_t
decode(const void *in, int len);
void
reset();
size_t
input_multiple() const;
private:
/**
* Decoding FSM
*/
typedef enum {
SEARCHING, //!< when searching for the start of the preamble
SEARCHING_SYNC, //!< We have preamble, search for sync
DECODING_GENERIC_FRAME_LEN, //!< Decoding the frame length
DECODING_PAYLOAD //!< Decoding the payload
} decoding_state_t;
shift_reg d_preamble;
shift_reg d_preamble_shift_reg;
const size_t d_preamble_len;
const size_t d_preamble_thrsh;
shift_reg d_sync;
shift_reg d_sync_shift_reg;
const size_t d_sync_len;
const size_t d_sync_thrsh;
crc::crc_t d_crc;
whitening::whitening_sptr d_descrambler;
const bool d_var_len;
size_t d_len;
size_t d_length_field_len;
decoding_state_t d_state;
size_t d_cnt;
uint8_t *d_pdu;
decoder_status_t
decode_var_len(const void *in, int len);
decoder_status_t
decode_const_len(const void *in, int len);
int
search_preamble(const uint8_t *in, int len);
int
search_sync(const uint8_t *in, int len);
int
decode_frame_len(const uint8_t *in);
void
decode_payload(decoder_status_t &status, const uint8_t *in, int len);
bool
check_crc();
};
} // namespace satnogs

View File

@ -149,6 +149,27 @@ crc::crc16_ibm(const uint8_t *data, size_t len)
return crc;
}
/**
*
* @param t the CRC method
* @return the size of the specified CRC in bytes
*/
size_t
crc::crc_size(crc_t t)
{
switch (t) {
case CRC_NONE:
return 0;
case CRC16_CCITT:
case CRC16_CCITT_REVERSED:
case CRC16_AX25:
case CRC16_IBM:
return sizeof(uint16_t);
default:
throw std::invalid_argument("crc: Invalid CRC method");
}
}
} /* namespace satnogs */
} /* namespace gr */

View File

@ -55,6 +55,22 @@ decoder::~decoder()
{
}
/**
* By default, the number of input items can be arbitrary (multiple of 1).
* Decoders that require the number of input items to be a multiple of a
* specific number, should override this method.
*
* The frame_decoder block, will use the number returned from this method
* to properly alter the behavior of the GNU Radio scheduler
*
* @return the number that input items should be multiple
*/
size_t
decoder::input_multiple() const
{
return 1;
}
/**
*
* @return the maximum allowed frame length

View File

@ -50,6 +50,14 @@ frame_decoder_impl::frame_decoder_impl(decoder::decoder_sptr decoder_object,
"frame_decoder: Size mismatch between the block input and the decoder");
}
/*
* Each decoder implementing the generic decoder API may have special
* requirements on the number of input items. If not, by default this will
* set a multiple of 1 item, which corresponds to an arbitrary number of
* samples
*/
set_output_multiple(d_decoder->input_multiple());
message_port_register_in(pmt::mp("reset"));
message_port_register_out(pmt::mp("out"));

View File

@ -23,21 +23,342 @@
#endif
#include <gnuradio/io_signature.h>
#include <satnogs/log.h>
#include <satnogs/ieee802_15_4_variant_decoder.h>
#include <satnogs/metadata.h>
#include <arpa/inet.h>
namespace gr {
namespace satnogs {
ieee802_15_4_variant_decoder::ieee802_15_4_variant_decoder(
const std::vector<uint8_t> &preamble, size_t preamble_threshold,
const std::vector<uint8_t> &sync, crc::crc_t crc,
whitening::whitening_sptr descrambler)
decoder::decoder_sptr
ieee802_15_4_variant_decoder::make(const std::vector<uint8_t> &preamble,
size_t preamble_threshold,
const std::vector<uint8_t> &sync,
size_t sync_threshold,
crc::crc_t crc,
whitening::whitening_sptr descrambler,
bool var_len,
size_t max_len)
{
return decoder::decoder_sptr(
new ieee802_15_4_variant_decoder(preamble, preamble_threshold,
sync, sync_threshold, crc,
descrambler, var_len, max_len));
}
ieee802_15_4_variant_decoder::ieee802_15_4_variant_decoder(
const std::vector<uint8_t> &preamble,
size_t preamble_threshold,
const std::vector<uint8_t> &sync,
size_t sync_threshold,
crc::crc_t crc,
whitening::whitening_sptr descrambler,
bool var_len, size_t max_len) :
decoder(sizeof(uint8_t), max_len),
d_preamble(preamble.size() * 8),
d_preamble_shift_reg(preamble.size() * 8),
d_preamble_len(preamble.size() * 8),
d_preamble_thrsh(preamble_threshold),
d_sync(sync.size() * 8),
d_sync_shift_reg(sync.size() * 8),
d_sync_len(sync.size() * 8),
d_sync_thrsh(sync_threshold),
d_descrambler(descrambler),
d_crc(crc),
d_var_len(var_len),
d_len(max_len + crc::crc_size(crc)),
d_length_field_len(0),
d_state(SEARCHING),
d_cnt(0),
d_pdu(new uint8_t[max_len])
{
for (uint8_t b : preamble) {
d_preamble <<= (b >> 7);
d_preamble <<= ((b >> 6) & 0x1);
d_preamble <<= ((b >> 5) & 0x1);
d_preamble <<= ((b >> 4) & 0x1);
d_preamble <<= ((b >> 3) & 0x1);
d_preamble <<= ((b >> 2) & 0x1);
d_preamble <<= ((b >> 1) & 0x1);
d_preamble <<= (b & 0x1);
}
for (uint8_t b : sync) {
d_sync <<= (b >> 7);
d_sync <<= ((b >> 6) & 0x1);
d_sync <<= ((b >> 5) & 0x1);
d_sync <<= ((b >> 4) & 0x1);
d_sync <<= ((b >> 3) & 0x1);
d_sync <<= ((b >> 2) & 0x1);
d_sync <<= ((b >> 1) & 0x1);
d_sync <<= (b & 0x1);
}
/* Parameters checking */
if (max_len == 0) {
throw std::invalid_argument(
"The maximum frame size should be at least 1 byte");
}
if (d_sync_len < 8) {
throw std::invalid_argument("SYNC word should be at least 8 bits");
}
if (d_preamble_len < 8) {
throw std::invalid_argument("Preamble should be at least 8 bits");
}
if (d_preamble_len < 2 * d_preamble_thrsh) {
throw std::invalid_argument(
"Too many error bits are allowed for the preamble."
"Consider lowering the threshold");
}
if (d_sync_len < 2 * d_sync_thrsh) {
throw std::invalid_argument(
"Too many error bits are allowed for the sync word. "
"Consider lowering the threshold");
}
if (var_len) {
d_length_field_len = 1;
}
}
ieee802_15_4_variant_decoder::~ieee802_15_4_variant_decoder()
{
delete [] d_pdu;
}
decoder_status_t
ieee802_15_4_variant_decoder::decode(const void *in, int len)
{
if (d_var_len) {
return decode_var_len(in, len);
}
return decode_const_len(in, len);
}
void
ieee802_15_4_variant_decoder::reset()
{
if (d_descrambler) {
d_descrambler->reset();
}
d_cnt = 0;
d_state = SEARCHING;
d_preamble_shift_reg.reset();
d_sync_shift_reg.reset();
}
decoder_status_t
ieee802_15_4_variant_decoder::decode_var_len(const void *in, int len)
{
decoder_status_t status;
switch (d_state) {
case SEARCHING:
status.consumed = search_preamble((const uint8_t *) in, len);
break;
case SEARCHING_SYNC:
status.consumed = search_sync((const uint8_t *) in, len);
break;
case DECODING_GENERIC_FRAME_LEN:
status.consumed = decode_frame_len((const uint8_t *) in);
break;
case DECODING_PAYLOAD:
decode_payload(status, (const uint8_t *) in, len);
break;
default:
throw std::runtime_error("ieee802_15_4_variant_decoder: Invalid state");
}
return status;
}
decoder_status_t
ieee802_15_4_variant_decoder::decode_const_len(const void *in, int len)
{
decoder_status_t status;
switch (d_state) {
case SEARCHING:
status.consumed = search_preamble((const uint8_t *) in, len);
break;
case SEARCHING_SYNC:
status.consumed = search_preamble((const uint8_t *) in, len);
break;
case DECODING_PAYLOAD:
decode_payload(status, (const uint8_t *) in, len);
break;
default:
throw std::runtime_error("ieee802_15_4_variant_decoder: Invalid state");
}
return status;
}
/**
* To greatly simplify the logic, the decoder requests that the number of
* input items should be a multiple of 8
* @return 8
*/
size_t
ieee802_15_4_variant_decoder::input_multiple() const
{
return 8;
}
int
ieee802_15_4_variant_decoder::search_preamble(const uint8_t *in, int len)
{
for (int i = 0; i < len; i++) {
d_preamble_shift_reg <<= in[i];
shift_reg tmp = d_preamble_shift_reg ^ d_preamble;
if (tmp.count() <= d_preamble_thrsh) {
d_state = SEARCHING_SYNC;
d_cnt = 0;
return i + 1;
}
}
return len;
}
int
ieee802_15_4_variant_decoder::search_sync(const uint8_t *in, int len)
{
for (int i = 0; i < len; i++) {
d_sync_shift_reg <<= in[i];
shift_reg tmp = d_sync_shift_reg ^ d_sync;
d_cnt++;
if (tmp.count() <= d_sync_thrsh) {
if (d_var_len) {
d_state = DECODING_GENERIC_FRAME_LEN;
}
else {
d_state = DECODING_PAYLOAD;
}
d_cnt = 0;
return i + 1;
}
/* The sync word should be available by now */
if (d_cnt > d_preamble_len * 2 + d_sync_len) {
reset();
return i + 1;
}
}
return len;
}
int
ieee802_15_4_variant_decoder::decode_frame_len(const uint8_t *in)
{
uint8_t b = 0x0;
b |= in[0] << 7;
b |= in[1] << 6;
b |= in[2] << 5;
b |= in[3] << 4;
b |= in[4] << 3;
b |= in[5] << 2;
b |= in[6] << 1;
b |= in[7];
if (d_descrambler) {
uint8_t descrambled = 0x0;
d_descrambler->descramble((uint8_t *)&descrambled,
(const uint8_t *)&b, 1, false);
d_len = descrambled;
d_pdu[0] = descrambled;
}
else {
d_len = b;
d_pdu[0] = b;
}
d_len += crc::crc_size(d_crc);
/* Length field is needed for the CRC calculation */
d_cnt = 1;
d_state = DECODING_PAYLOAD;
return 8;
}
void
ieee802_15_4_variant_decoder::decode_payload(decoder_status_t &status,
const uint8_t *in, int len)
{
const int s = len / 8;
for (int i = 0; i < s; i++) {
uint8_t b = 0x0;
b = in[i * 8] << 7;
b |= in[i * 8 + 1] << 6;
b |= in[i * 8 + 2] << 5;
b |= in[i * 8 + 3] << 4;
b |= in[i * 8 + 4] << 3;
b |= in[i * 8 + 5] << 2;
b |= in[i * 8 + 6] << 1;
b |= in[i * 8 + 7];
d_pdu[d_cnt++] = b;
if (d_cnt == d_len + d_length_field_len) {
if (d_descrambler) {
d_descrambler->descramble(d_pdu + d_length_field_len,
d_pdu + d_length_field_len, d_len, false);
}
status.decode_success = true;
status.consumed = (i + 1) * 8;
if (check_crc()) {
metadata::add_time_iso8601(status.data);
metadata::add_pdu(status.data, d_pdu + d_length_field_len,
d_len - crc::crc_size(d_crc));
metadata::add_crc_valid(status.data, true);
}
else {
metadata::add_pdu(status.data, d_pdu + d_length_field_len, d_len);
metadata::add_crc_valid(status.data, false);
}
reset();
return;
}
}
status.consumed = s * 8;
}
bool
ieee802_15_4_variant_decoder::check_crc()
{
uint16_t crc16_c;
uint16_t crc16_received;
switch (d_crc) {
case crc::CRC_NONE:
return true;
case crc::CRC16_CCITT:
crc16_c = crc::crc16_ccitt(d_pdu, d_len + d_length_field_len - 2);
memcpy(&crc16_received, d_pdu + d_length_field_len + d_len - 2, 2);
crc16_received = ntohs(crc16_received);
LOG_DEBUG("Received: 0x%02x Computed: 0x%02x", crc16_received, crc16_c);
if (crc16_c == crc16_received) {
return true;
}
return false;
case crc::CRC16_CCITT_REVERSED:
crc16_c = crc::crc16_ccitt_reversed(d_pdu, d_len + d_length_field_len - 2);
memcpy(&crc16_received, d_pdu + d_length_field_len + d_len - 2, 2);
crc16_received = ntohs(crc16_received);
LOG_DEBUG("Received: 0x%02x Computed: 0x%02x", crc16_received, crc16_c);
if (crc16_c == crc16_received) {
return true;
}
return false;
case crc::CRC16_IBM:
crc16_c = crc::crc16_ibm(d_pdu, d_len + d_length_field_len - 2);
memcpy(&crc16_received, d_pdu + d_length_field_len + d_len - 2, 2);
crc16_received = ntohs(crc16_received);
LOG_WARN("Received: 0x%02x Computed: 0x%02x", crc16_received, crc16_c);
if (crc16_c == crc16_received) {
return true;
}
return false;
default:
throw std::runtime_error("ieee802_15_4_variant_decoder: Invalid CRC");
}
}
} /* namespace satnogs */

View File

@ -43,16 +43,18 @@
#include "satnogs/lrpt_sync.h"
#include "satnogs/metadata.h"
#include "satnogs/crc.h"
#include "satnogs/ieee802_15_4_variant_decoder.h"
%}
%include "satnogs/whitening.h"
%include "satnogs/morse_tree.h"
%include "satnogs/decoder.h"
%include "satnogs/amsat_duv_decoder.h"
%include "satnogs/ax25_decoder.h"
%include "satnogs/metadata.h"
%include "satnogs/crc.h"
%include "satnogs/ieee802_15_4_variant_decoder.h"
%include "satnogs/morse_decoder.h"
GR_SWIG_BLOCK_MAGIC2(satnogs, morse_decoder);
@ -81,8 +83,6 @@ GR_SWIG_BLOCK_MAGIC2(satnogs, doppler_correction_cc);
%include "satnogs/upsat_fsk_frame_encoder.h"
GR_SWIG_BLOCK_MAGIC2(satnogs, upsat_fsk_frame_encoder);
%include "satnogs/whitening.h"
%include "satnogs/udp_msg_sink.h"
GR_SWIG_BLOCK_MAGIC2(satnogs, udp_msg_sink);