diff --git a/CMakeLists.txt b/CMakeLists.txt index 24448a30..6b2ea222 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ option(OPT_BUILD_M17_DECODER "Build the M17 decoder module (Dependencies: codec2 option(OPT_BUILD_METEOR_DEMODULATOR "Build the meteor demodulator module (no dependencies required)" ON) option(OPT_BUILD_PAGER_DECODER "Build the pager decoder module (no dependencies required)" ON) option(OPT_BUILD_RADIO "Main audio modulation decoder (AM, FM, SSB, etc...)" ON) +option(OPT_BUILD_RYFI_DECODER "RyFi data link decoder" OFF) option(OPT_BUILD_WEATHER_SAT_DECODER "Build the HRPT decoder module (no dependencies required)" OFF) # Misc @@ -260,6 +261,10 @@ if (OPT_BUILD_RADIO) add_subdirectory("decoder_modules/radio") endif (OPT_BUILD_RADIO) +if (OPT_BUILD_RYFI_DECODER) +add_subdirectory("decoder_modules/ryfi_decoder") +endif (OPT_BUILD_RYFI_DECODER) + if (OPT_BUILD_WEATHER_SAT_DECODER) add_subdirectory("decoder_modules/weather_sat_decoder") endif (OPT_BUILD_WEATHER_SAT_DECODER) diff --git a/decoder_modules/ryfi_decoder/CMakeLists.txt b/decoder_modules/ryfi_decoder/CMakeLists.txt new file mode 100644 index 00000000..5b600041 --- /dev/null +++ b/decoder_modules/ryfi_decoder/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.13) +project(ryfi_decoder) + +file(GLOB_RECURSE SRC "src/*.cpp" "src/*.c") + +include(${SDRPP_MODULE_CMAKE}) + +target_include_directories(ryfi_decoder PRIVATE "src/") \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/main.cpp b/decoder_modules/ryfi_decoder/src/main.cpp new file mode 100644 index 00000000..950b47e5 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/main.cpp @@ -0,0 +1,139 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ryfi/receiver.h" + +#define CONCAT(a, b) ((std::string(a) + b).c_str()) + +SDRPP_MOD_INFO{ + /* Name: */ "ryfi_decoder", + /* Description: */ "RyFi decoder for SDR++", + /* Author: */ "Ryzerth", + /* Version: */ 0, 1, 0, + /* Max instances */ -1 +}; + +#define INPUT_BANDWIDTH 800000 +#define INPUT_SAMPLE_RATE 1500000 +#define INPUT_BAUDRATE 720000 + +#define SYMBOL_DIAG_RATE 30 +#define SYMBOL_DIAG_COUNT 1024 + +class RyFiDecoderModule : public ModuleManager::Instance { +public: + RyFiDecoderModule(std::string name) { + this->name = name; + + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, 0, INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true); + rx.init(vfo->output, INPUT_BAUDRATE, INPUT_SAMPLE_RATE); + reshape.init(rx.softOut, SYMBOL_DIAG_COUNT, (INPUT_BAUDRATE / SYMBOL_DIAG_RATE) - SYMBOL_DIAG_COUNT); + symSink.init(&reshape.out, symSinkHandler, this); + rx.onPacket.bind(&RyFiDecoderModule::packetHandler, this); + + rx.start(); + reshape.start(); + symSink.start(); + + gui::menu.registerEntry(name, menuHandler, this, this); + } + + ~RyFiDecoderModule() { + rx.stop(); + reshape.stop(); + symSink.stop(); + sigpath::vfoManager.deleteVFO(vfo); + gui::menu.removeEntry(name); + } + + void postInit() {} + + void enable() { + double bw = gui::waterfall.getBandwidth(); + vfo = sigpath::vfoManager.createVFO(name, ImGui::WaterfallVFO::REF_CENTER, std::clamp(0, -bw / 2.0, bw / 2.0), INPUT_BANDWIDTH, INPUT_SAMPLE_RATE, INPUT_BANDWIDTH, INPUT_BANDWIDTH, true); + + rx.setInput(vfo->output); + + rx.start(); + reshape.start(); + symSink.start(); + + enabled = true; + } + + void disable() { + rx.stop(); + reshape.stop(); + symSink.stop(); + + sigpath::vfoManager.deleteVFO(vfo); + enabled = false; + } + + bool isEnabled() { + return enabled; + } + +private: + void packetHandler(ryfi::Packet pkt) { + flog::debug("Got a {} byte packet!", pkt.size()); + } + + static void menuHandler(void* ctx) { + RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx; + + float menuWidth = ImGui::GetContentRegionAvail().x; + + if (!_this->enabled) { style::beginDisabled(); } + + ImGui::SetNextItemWidth(menuWidth); + _this->constDiagram.draw(); + + if (!_this->enabled) { style::endDisabled(); } + } + + static void symSinkHandler(dsp::complex_t* data, int count, void* ctx) { + RyFiDecoderModule* _this = (RyFiDecoderModule*)ctx; + + dsp::complex_t* buf = _this->constDiagram.acquireBuffer(); + memcpy(buf, data, 1024 * sizeof(dsp::complex_t)); + _this->constDiagram.releaseBuffer(); + } + + std::string name; + bool enabled = true; + + // DSP Chain + VFOManager::VFO* vfo; + ryfi::Receiver rx; + dsp::buffer::Reshaper reshape; + dsp::sink::Handler symSink; + + ImGui::ConstellationDiagram constDiagram; +}; + +MOD_EXPORT void _INIT_() { + +} + +MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) { + return new RyFiDecoderModule(name); +} + +MOD_EXPORT void _DELETE_INSTANCE_(void* instance) { + delete (RyFiDecoderModule*)instance; +} + +MOD_EXPORT void _END_() { + +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp b/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp new file mode 100644 index 00000000..c20082d7 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.cpp @@ -0,0 +1,74 @@ +#include "conv_codec.h" + +namespace ryfi { + ConvEncoder::ConvEncoder(dsp::stream* in) { + // Create the convolutional encoder instance + conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial); + + // Init the base class + base_type::init(in); + } + + ConvEncoder::~ConvEncoder() { + // Destroy the convolutional encoder instance + correct_convolutional_destroy(conv); + } + + int ConvEncoder::encode(const uint8_t* in, uint8_t* out, int count) { + // Run convolutional encoder on the data + return correct_convolutional_encode(conv, in, count, out); + } + + int ConvEncoder::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); + + base_type::_in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + ConvDecoder::ConvDecoder(dsp::stream* in) { + // Create the convolutional encoder instance + conv = correct_convolutional_create(2, 7, correct_conv_r12_7_polynomial); + + // Allocate the soft symbol buffer + soft = dsp::buffer::alloc(STREAM_BUFFER_SIZE); + + // Init the base class + base_type::init(in); + } + + ConvDecoder::~ConvDecoder() { + // Destroy the convolutional encoder instance + correct_convolutional_destroy(conv); + + // Free the soft symbol buffer + dsp::buffer::free(soft); + } + + int ConvDecoder::decode(const dsp::complex_t* in, uint8_t* out, int count) { + // Convert to uint8 + const float* _in = (const float*)in; + count *= 2; + for (int i = 0; i < count; i++) { + soft[i] = std::clamp((_in[i] * 127.0f) + 128.0f, 0, 255); + } + + // Run convolutional decoder on the data + return correct_convolutional_decode_soft(conv, soft, count, out); + } + + int ConvDecoder::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count); + + base_type::_in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h b/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h new file mode 100644 index 00000000..10c0ae61 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/conv_codec.h @@ -0,0 +1,70 @@ +#pragma once +#include +#include "dsp/processor.h" + +extern "C" { + #include "correct.h" +} + +namespace ryfi { + /** + * RyFi Convolutional Encoder. + */ + class ConvEncoder : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a convolutional encoder specifying an input stream. + * @param in Input stream. + */ + ConvEncoder(dsp::stream* in = NULL); + + // Destructor + ~ConvEncoder(); + + /** + * Encode data. + * @param in Input bytes. + * @param out Output bits. + * @param count Number of input bytes. + * @return Number of output bits. + */ + int encode(const uint8_t* in, uint8_t* out, int count); + + private: + int run(); + + correct_convolutional* conv; + }; + + /** + * RyFi Convolutional Decoder. + */ + class ConvDecoder : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a convolutional encoder specifying an input stream. + * @param in Input stream. + */ + ConvDecoder(dsp::stream* in = NULL); + + // Destructor + ~ConvDecoder(); + + /** + * Decode soft symbols. + * @param in Input soft symbols. + * @param out Output bytes. + * @param count Number of input bytes. + * @return Number of output bits. + */ + int decode(const dsp::complex_t* in, uint8_t* out, int count); + + private: + int run(); + + correct_convolutional* conv; + uint8_t* soft = NULL; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/frame.cpp b/decoder_modules/ryfi_decoder/src/ryfi/frame.cpp new file mode 100644 index 00000000..a95b8069 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/frame.cpp @@ -0,0 +1,37 @@ +#include "frame.h" + +namespace ryfi { + int Frame::serialize(uint8_t* bytes) const { + // Write the counter + bytes[0] = (counter >> 8) & 0xFF; + bytes[1] = counter & 0xFF; + + // Write the first packet pointer + bytes[2] = (firstPacket >> 8) & 0xFF; + bytes[3] = firstPacket & 0xFF; + + // Write the last packet pointer + bytes[4] = (lastPacket >> 8) & 0xFF; + bytes[5] = lastPacket & 0xFF; + + // Write the data + memcpy(&bytes[6], content, FRAME_DATA_SIZE); + + // Return the length of a serialized frame + return FRAME_SIZE; + } + + void Frame::deserialize(const uint8_t* bytes, Frame& frame) { + // Read the counter + frame.counter = (((uint16_t)bytes[0]) << 8) | ((uint16_t)bytes[1]); + + // Read the first packet pointer + frame.firstPacket = (((uint16_t)bytes[2]) << 8) | ((uint16_t)bytes[3]); + + // Read the last packet pointer + frame.lastPacket = (((uint16_t)bytes[4]) << 8) | ((uint16_t)bytes[5]); + + // Read the data + memcpy(frame.content, &bytes[6], FRAME_DATA_SIZE); + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/frame.h b/decoder_modules/ryfi_decoder/src/ryfi/frame.h new file mode 100644 index 00000000..81267dd2 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/frame.h @@ -0,0 +1,42 @@ +#pragma once +#include +#include "rs_codec.h" + +namespace ryfi { + enum PacketOffset { + PKT_OFFS_NONE = 0xFFFF + }; + + struct Frame { + /** + * Serialize the frame to bytes. + * @param bytes Buffer to write the serialized frame to. + */ + int serialize(uint8_t* bytes) const; + + /** + * Deserialize a frame from bytes. + * @param bytes Buffer to deserialize the frame from. + * @param frame Object that will contain the deserialize frame. + */ + static void deserialize(const uint8_t* bytes, Frame& frame); + + // Size of a serialized frame + static inline const int FRAME_SIZE = RS_BLOCK_DEC_SIZE*RS_BLOCK_COUNT; + + // Size of the data area of the frame + static inline const int FRAME_DATA_SIZE = FRAME_SIZE - 6; + + // Steadily increasing counter. + uint16_t counter = 0; + + // Byte offset of the first packet in the frame. + uint16_t firstPacket = 0; + + // Byte offset of the last packet in the frame. + uint16_t lastPacket = 0; + + // Data area of the frame. + uint8_t content[FRAME_DATA_SIZE]; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/framing.cpp b/decoder_modules/ryfi_decoder/src/ryfi/framing.cpp new file mode 100644 index 00000000..6bb1f428 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/framing.cpp @@ -0,0 +1,137 @@ +#include "framing.h" + +namespace ryfi { + dsp::complex_t QPSK_SYMBOLS[4] = { + { -0.070710678118f, -0.070710678118f }, + { -0.070710678118f, 0.070710678118f }, + { 0.070710678118f, -0.070710678118f }, + { 0.070710678118f, 0.070710678118f }, + }; + + Framer::Framer(dsp::stream* in) { + // Generate the sync symbols + int k = 0; + for (int i = 62; i >= 0; i -= 2) { + syncSyms[k++] = QPSK_SYMBOLS[(SYNC_WORD >> i) & 0b11]; + } + + + // Initialize base class + base_type::init(in); + } + + int Framer::encode(const uint8_t* in, dsp::complex_t* out, int count) { + // Copy sync symbols + memcpy(out, syncSyms, SYNC_SYMS*sizeof(dsp::complex_t)); + + // Modulate the rest of the bits + dsp::complex_t* dataOut = &out[SYNC_SYMS]; + int dataSyms = count / 2; + for (int i = 0; i < dataSyms; i++) { + uint8_t bits = (in[i >> 2] >> (6 - 2*(i & 0b11))) & 0b11; + dataOut[i] = QPSK_SYMBOLS[bits]; + } + + // Compute and return the total number of symbols + return SYNC_SYMS + dataSyms; + } + + int Framer::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); + + base_type::_in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + Deframer::Deframer(dsp::stream *in) { + // Compute sync word rotations + // 0: 00 01 11 10 + // 90: 10 00 01 11 + // 180: 11 10 00 01 + // 270: 01 11 10 00 + + // For 0 and 180 it's the sync and its complement + syncRots[ROT_0_DEG] = SYNC_WORD; + syncRots[ROT_180_DEG] = ~SYNC_WORD; + + // For 90 and 270 its the quadrature and its complement + uint64_t quad; + for (int i = 62; i >= 0; i -= 2) { + // Get the symbol + uint8_t sym = (SYNC_WORD >> i) & 0b11; + + // Rotate it 90 degrees + uint8_t rsym; + switch (sym) { + case 0b00: rsym = 0b10; break; + case 0b01: rsym = 0b00; break; + case 0b11: rsym = 0b01; break; + case 0b10: rsym = 0b11; break; + } + + // Push it into the quadrature + quad = (quad << 2) | rsym; + } + syncRots[ROT_90_DEG] = quad; + syncRots[ROT_270_DEG] = ~quad; + + base_type::init(in); + } + + int Deframer::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + dsp::complex_t* in = base_type::_in->readBuf; + + for (int i = 0; i < count; i++) { + if (recv) { + // Copy the symbol to the output and rotate it approprieate + base_type::out.writeBuf[outCount++] = in[i] * symRot; + + // Check if we're done receiving the frame, send it out + if (!(--recv)) { + if (!base_type::out.swap(outCount)) { + base_type::_in->flush(); + return -1; + } + } + } + else { + // Get the raw symbol + dsp::complex_t fsym = in[i]; + + // Decode the symbol + uint8_t sym = ((fsym.re > 0) ? 0b10 : 0b00) | ((fsym.im > 0) ? 0b01 : 0b00); + + // Push it to the shift register + shift = (shift << 2) | sym; + + // Find the rotation starting with the last known one + for (int i = 0; i < 4; i++) { + // Get the test rotation + int testRot = (knownRot+i) & 0b11; + + // Check if the hamming distance is close enough + int dist; + if (distance(shift, syncRots[testRot]) < 6) { + // Save the new rotation + knownRot = testRot; + + // Start reading in symbols for the frame + symRot = symRots[knownRot]; + recv = 8168; // TODO: Don't hardcode! + outCount = 0; + } + } + } + } + + base_type::_in->flush(); + return count; + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/framing.h b/decoder_modules/ryfi_decoder/src/ryfi/framing.h new file mode 100644 index 00000000..7b4739a2 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/framing.h @@ -0,0 +1,86 @@ +#pragma once +#include "dsp/processor.h" +#include + +namespace ryfi { + // Synchronization word. + inline const uint64_t SYNC_WORD = 0x341CC540819D8963; + + // Number of synchronization bits. + inline const int SYNC_BITS = 64; + + // Number of synchronization symbols. + inline const int SYNC_SYMS = SYNC_BITS / 2; + + // Possible constellation rotations + enum { + ROT_0_DEG = 0, + ROT_90_DEG = 1, + ROT_180_DEG = 2, + ROT_270_DEG = 3 + }; + + /** + * RyFi Framer. + */ + class Framer : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a framer specifying an input stream. + * @param in Input stream. + */ + Framer(dsp::stream* in = NULL); + + /** + * Encode a frame to symbols adding a sync word. + */ + int encode(const uint8_t* in, dsp::complex_t* out, int count); + + private: + int run(); + + dsp::complex_t syncSyms[SYNC_SYMS]; + }; + + class Deframer : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a deframer specifying an input stream. + * @param in Input stream. + */ + Deframer(dsp::stream *in = NULL); + + private: + int run(); + + inline static constexpr int distance(uint64_t a, uint64_t b) { + int dist = 0; + for (int i = 0; i < 64; i++) { + dist += ((a & 1) != (b & 1)); + a >>= 1; + b >>= 1; + } + return dist; + } + + // Frame reading counters + int recv = 0; + int outCount = 0; + + // Rotation handling + int knownRot = 0; + uint64_t syncRots[4]; + dsp::complex_t symRot; + const dsp::complex_t symRots[4] = { + { 1.0f, 0.0f }, // 0 deg + { 0.0f, -1.0f }, // 90 deg + { -1.0f, 0.0f }, // 180 deg + { 0.0f, 1.0f }, // 270 deg + }; + + // Shift register + uint64_t shift; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/packet.cpp b/decoder_modules/ryfi_decoder/src/ryfi/packet.cpp new file mode 100644 index 00000000..c6d41702 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/packet.cpp @@ -0,0 +1,126 @@ +#include "packet.h" +#include "string.h" +#include + +namespace ryfi { + Packet::Packet() {} + + Packet::Packet(uint8_t* content, int size) { + // Check that the size isn't too large + if (size > MAX_CONTENT_SIZE) { + throw std::runtime_error("Content size is too large to fit in a packet"); + } + + + // Allocate the buffer + allocate(size); + + // Copy over the content + memcpy(_content, content, size); + } + + Packet::Packet(const Packet& b) { + // Reallocate the buffer + allocate(b._size); + + // Copy over the content + memcpy(_content, b._content, b._size); + } + + Packet::Packet(Packet&& b) { + // Move members + _content = b._content; + _size = b._size; + + // Destroy old object + b._content = NULL; + b._size = 0; + } + + Packet::~Packet() { + // Delete the content + if (_content) { delete[] _content; } + } + + Packet& Packet::operator=(const Packet& b) { + // Reallocate the buffer + allocate(b._size); + + // Copy over the content + memcpy(_content, b._content, b._size); + + // Return self + return *this; + } + + Packet& Packet::operator=(Packet&& b) { + // Move members + _content = b._content; + _size = b._size; + + // Destroy old object + b._content = NULL; + b._size = 0; + + // Return self + return *this; + } + + Packet::operator bool() const { + return _size > 0; + } + + int Packet::size() const { + // Return the size + return _size; + } + + const uint8_t* Packet::data() const { + // Return the size + return _content; + } + + void Packet::setContent(uint8_t* content, int size) { + // Check that the size isn't too large + if (size > MAX_CONTENT_SIZE) { + throw std::runtime_error("Content size is too large to fit in a packet"); + } + + // Reallocate the buffer + allocate(size); + + // Copy over the content + memcpy(_content, content, size); + } + + int Packet::serializedSize() const { + // Two size bytes + Size of the content + return _size + 2; + } + + int Packet::serialize(uint8_t* bytes) const { + // Write the size in big-endian + bytes[0] = (_size >> 8) & 0xFF; + bytes[1] = _size & 0xFF; + + // Copy the content of the packet + memcpy(&bytes[2], _content, _size); + + // Return the serialized size + return serializedSize(); + } + + void Packet::allocate(int newSize) { + // If the size hasn't changed, do nothing + if (newSize == _size) { return; } + + // Free the old buffer + if (_content) { delete[] _content; }; + + // Update the size + _size = newSize; + + // Allocate the buffer + _content = new uint8_t[newSize]; + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/packet.h b/decoder_modules/ryfi_decoder/src/ryfi/packet.h new file mode 100644 index 00000000..6c62fa16 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/packet.h @@ -0,0 +1,88 @@ +#pragma once +#include + +namespace ryfi { + /** + * RyFi Protocol Packet. + */ + class Packet { + public: + // Default constructor + Packet(); + + /** + * Create a packet from its content. + * @param content Content of the packet. + * @param size Number of bytes of content. + */ + Packet(uint8_t* content, int size); + + // Copy constructor + Packet(const Packet& b); + + // Move constructor + Packet(Packet&& b); + + // Destructor + ~Packet(); + + // Copy assignment operator + Packet& operator=(const Packet& b); + + // Move assignment operator + Packet& operator=(Packet&& b); + + // Cast to bool operator + operator bool() const; + + /** + * Get the size of the content of the packet. + * @return Size of the content of the packet. + */ + int size() const; + + /** + * Get the content of the packet. The pointer is only valid until reallocation or deletion. + * @return Content of the packet. + */ + const uint8_t* data() const; + + /** + * Set the content of the packet. + * @param content Content of the packet. + * @param size Number of bytes of content. + */ + void setContent(uint8_t* content, int size); + + /** + * Get the size of the serialized packet. + * @return Size of the serialized packet. + */ + int serializedSize() const; + + /** + * Serialize the packet to bytes. + * @param bytes Buffer to which to write the serialized packet. + * @return Size of the serialized packet. + */ + int serialize(uint8_t* bytes) const; + + /** + * Deserialize a packet from bytes. + * TODO + */ + static bool deserialize(uint8_t* bytes, int size, Packet& pkt); + + // Maximum size of the content of the packet. + static inline const int MAX_CONTENT_SIZE = 0xFFFF; + + // Maximum size of the serialized packet. + static inline const int MAX_SERIALIZED_SIZE = MAX_CONTENT_SIZE + 2; + + private: + void allocate(int newSize); + + uint8_t* _content = NULL; + int _size = 0; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp b/decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp new file mode 100644 index 00000000..79572d08 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/receiver.cpp @@ -0,0 +1,191 @@ +#include "receiver.h" + +#include "utils/flog.h" + +namespace ryfi { + Receiver::Receiver() {} + + Receiver::Receiver(dsp::stream* in, double baudrate, double samplerate) { + init(in, baudrate, samplerate); + } + + Receiver::~Receiver() { + // Stop everything + stop(); + } + + void Receiver::init(dsp::stream* in, double baudrate, double samplerate) { + // Initialize the DSP + demod.init(in, baudrate, samplerate, 31, 0.6, 0.1f, 0.005f, 1e-6, 0.01); + doubler.init(&demod.out); + softOut = &doubler.outA; + deframer.setInput(&doubler.outB); + conv.setInput(&deframer.out); + rs.setInput(&conv.out); + } + + void Receiver::setInput(dsp::stream* in) { + demod.setInput(in); + } + + void Receiver::start() { + // Do nothing if already running + if (running) { return; } + + // Start the worker thread + workerThread = std::thread(&Receiver::worker, this); + + // Start the DSP + demod.start(); + doubler.start(); + deframer.start(); + conv.start(); + rs.start(); + + // Update the running state + running = true; + } + + void Receiver::stop() { + // Do nothing if not running + if (!running) { return; } + + // Stop the worker thread + rs.out.stopReader(); + if (workerThread.joinable()) { workerThread.join(); } + rs.out.clearReadStop(); + + // Stop the DSP + demod.stop(); + doubler.stop(); + deframer.stop(); + conv.stop(); + rs.stop(); + + // Update the running state + running = false; + } + + void Receiver::worker() { + Frame frame; + uint16_t lastCounter = 0; + uint8_t* pktBuffer = new uint8_t[Packet::MAX_CONTENT_SIZE]; + int pktExpected = 0; + int pktRead = 0; + + while (true) { + // Read a frame + int count = rs.out.read(); + if (count <= 0) { break; } + + // Deserialize the frame + Frame::deserialize(rs.out.readBuf, frame); + + // Flush the stream + rs.out.flush(); + + //flog::info("Frame[{}]: FirstPacket={}, LastPacket={}", frame.counter, frame.firstPacket, frame.lastPacket); + + // Compute the expected frame counter + uint16_t expectedCounter = lastCounter + 1; + lastCounter = frame.counter; + + // If the frames aren't consecutive + int frameRead = 0; + if (frame.counter != expectedCounter) { + //flog::warn("Lost at least {} frames", ((int)frame.counter - (int)expectedCounter + 0x10000) % 0x10000); + + // Cancel the partial packet if there was one + pktExpected = 0; + pktRead = 0; + + // If this frame is not an idle frame or continuation frame + if (frame.firstPacket != PKT_OFFS_NONE) { + // If the offset of the first packet is not plausible + if (frame.firstPacket > Frame::FRAME_DATA_SIZE-2) { + flog::warn("Packet had non-plausible offset: {}", frameRead); + + // Skip the frame + continue; + } + + // Skip to the end of the packet + frameRead = frame.firstPacket; + } + } + + // If there is no partial packet and the frame doesn't contain a packet start, skip it + if (!pktExpected && frame.firstPacket == PKT_OFFS_NONE) { continue; } + + // Extract packets from the frame + bool firstPacket = true; + bool lastPacket = false; + while (frameRead < Frame::FRAME_DATA_SIZE) { + // If there is a partial packet read as much as possible from it + if (pktExpected) { + // Compute how many bytes of the packet are available in the frame + int readable = std::min(pktExpected - pktRead, Frame::FRAME_DATA_SIZE - frameRead); + //flog::debug("Reading {} bytes", readable); + + // Write them to the packet + memcpy(&pktBuffer[pktRead], &frame.content[frameRead], readable); + pktRead += readable; + frameRead += readable; + + // If the packet is read entirely + if (pktRead >= pktExpected) { + // Create the packet object + Packet pkt(pktBuffer, pktExpected); + + // Send off the packet + onPacket(pkt); + + // Prepare for the next packet + pktRead = 0; + pktExpected = 0; + + // If this was the last packet of the frame + if (lastPacket || frame.firstPacket == PKT_OFFS_NONE) { + // Skip the rest of the frame + frameRead = Frame::FRAME_DATA_SIZE; + continue; + } + } + + // Go to next packet + continue; + } + + // If the packet offset is not plausible + if (Frame::FRAME_DATA_SIZE - frameRead < 2) { + flog::warn("Packet had non-plausible offset: {}", frameRead); + + // Skip the rest of the frame and the packet + frameRead = Frame::FRAME_DATA_SIZE; + pktExpected = 0; + pktRead = 0; + continue; + } + + // If this is the first packet, use the frame info to skip possible left over data + if (firstPacket) { + frameRead = frame.firstPacket; + firstPacket = false; + } + + // Check if this is the last packet + lastPacket = (frameRead == frame.lastPacket); + + // Parse the packet size + pktExpected = ((uint16_t)frame.content[frameRead]) << 8; + pktExpected |= (uint16_t)frame.content[frameRead+1]; + //flog::debug("Starting to read a {} byte packet at offset {}", pktExpected, frameRead); + + // Skip to the packet content + frameRead += 2; + } + } + + delete[] pktBuffer; + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/receiver.h b/decoder_modules/ryfi_decoder/src/ryfi/receiver.h new file mode 100644 index 00000000..593437da --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/receiver.h @@ -0,0 +1,69 @@ +#pragma once +#include "utils/new_event.h" +#include "dsp/demod/psk.h" +#include "dsp/routing/doubler.h" +#include "packet.h" +#include "frame.h" +#include "rs_codec.h" +#include "conv_codec.h" +#include "framing.h" +#include + +namespace ryfi { + class Receiver { + public: + Receiver(); + + /** + * Create a transmitter. + * @param in Baseband input. + * @param baudrate Baudrate to use over the air. + * @param samplerate Samplerate of the baseband. + */ + Receiver(dsp::stream* in, double baudrate, double samplerate); + + /** + * Create a transmitter. + * @param in Baseband input. + * @param baudrate Baudrate to use over the air. + * @param samplerate Samplerate of the baseband. + */ + void init(dsp::stream* in, double baudrate, double samplerate); + + /** + * Set the input stream. + * @param in Baseband input. + */ + void setInput(dsp::stream* in); + + // Destructor + ~Receiver(); + + /** + * Start the transmitter's DSP. + */ + void start(); + + /** + * Stop the transmitter's DSP. + */ + void stop(); + + dsp::stream* softOut; + + NewEvent onPacket; + + private: + void worker(); + + // DSP + dsp::demod::PSK<4> demod; + dsp::routing::Doubler doubler; + Deframer deframer; + ConvDecoder conv; + RSDecoder rs; + + bool running = false; + std::thread workerThread; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp b/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp new file mode 100644 index 00000000..4b358034 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.cpp @@ -0,0 +1,169 @@ +#include "rs_codec.h" + +namespace ryfi { + RSEncoder::RSEncoder(dsp::stream* in) { + // Create the convolutional encoder instance + rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32); + + // Init the base class + base_type::init(in); + } + + RSEncoder::~RSEncoder() { + // Destroy the convolutional encoder instance + correct_reed_solomon_destroy(rs); + } + + int RSEncoder::encode(const uint8_t* in, uint8_t* out, int count) { + // Check the size + assert(count == RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE); + + // Go through each block + uint8_t block[RS_BLOCK_ENC_SIZE]; + for (int i = 0; i < RS_BLOCK_COUNT; i++) { + // Encode block + correct_reed_solomon_encode(rs, &in[i*RS_BLOCK_DEC_SIZE], RS_BLOCK_DEC_SIZE, block); + + // Interleave into the frame + int k = 0; + for (int j = i; j < RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT; j += RS_BLOCK_COUNT) { + out[j] = block[k++]; + } + } + + // Scramble + for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) { + out[i] ^= RS_SCRAMBLER_SEQ[i]; + } + + return RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; + } + + int RSEncoder::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + count = encode(base_type::_in->readBuf, base_type::out.writeBuf, count); + + base_type::_in->flush(); + if (!out.swap(count)) { return -1; } + return count; + } + + RSDecoder::RSDecoder(dsp::stream* in) { + // Create the convolutional encoder instance + rs = correct_reed_solomon_create(correct_rs_primitive_polynomial_ccsds, 1, 1, 32); + + // Init the base class + base_type::init(in); + } + + RSDecoder::~RSDecoder() { + // Destroy the convolutional encoder instance + correct_reed_solomon_destroy(rs); + } + + int RSDecoder::decode(uint8_t* in, uint8_t* out, int count) { + // Check the size + assert(count == RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE); + + // Descramble (TODO: Don't do it in-place) + for (int i = 0; i < RS_BLOCK_COUNT*RS_BLOCK_ENC_SIZE; i++) { + in[i] ^= RS_SCRAMBLER_SEQ[i]; + } + + // Go through each block + uint8_t block[RS_BLOCK_ENC_SIZE]; + for (int i = 0; i < RS_BLOCK_COUNT; i++) { + // Deinterleave out of the frame + int k = 0; + for (int j = i; j < count; j += RS_BLOCK_COUNT) { + block[k++] = in[j]; + } + + // Decode block and return if decoding fails + int res = correct_reed_solomon_decode(rs, block, RS_BLOCK_ENC_SIZE, &out[i*RS_BLOCK_DEC_SIZE]); + if (res < 0) { return 0; } + } + + return RS_BLOCK_COUNT*RS_BLOCK_DEC_SIZE; + } + + int RSDecoder::run() { + int count = base_type::_in->read(); + if (count < 0) { return -1; } + + count = decode(base_type::_in->readBuf, base_type::out.writeBuf, count); + + base_type::_in->flush(); + if (count && !out.swap(count)) { return -1; } + return count; + } + + const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT] = { + 0x75, 0x05, 0x7C, 0xCE, 0xF1, 0xD0, 0x6C, 0xF6, 0xFA, 0x65, 0xF6, 0xFC, 0xE0, 0x0A, 0x82, 0x17, + 0x6C, 0xBE, 0x76, 0xA0, 0xD6, 0x46, 0x12, 0x2E, 0xDE, 0xB5, 0xF7, 0xAD, 0xCB, 0x51, 0x63, 0x47, + 0x27, 0x30, 0x7E, 0x43, 0xD1, 0xA1, 0xCB, 0x10, 0x08, 0x49, 0xDF, 0x86, 0xD4, 0xC4, 0xD7, 0x3C, + 0x6D, 0x03, 0x07, 0x37, 0x5B, 0xB3, 0xCD, 0x79, 0x6F, 0x1E, 0xBA, 0xC5, 0x6E, 0xC3, 0x8C, 0x7A, + 0x25, 0x99, 0x61, 0x54, 0x5A, 0x96, 0x57, 0x9B, 0xE0, 0x60, 0x5B, 0x09, 0x6D, 0x8B, 0x2D, 0x9D, + 0x15, 0x9D, 0x0E, 0xBF, 0x57, 0xFB, 0x9C, 0x49, 0x82, 0x2C, 0x48, 0x59, 0x92, 0x47, 0x79, 0x17, + 0x16, 0x74, 0xEA, 0xEA, 0xBB, 0xC5, 0x72, 0x32, 0x17, 0xD1, 0xB3, 0xDE, 0xEB, 0x15, 0xC7, 0x55, + 0x8A, 0xF2, 0x88, 0xC2, 0x33, 0xA6, 0x17, 0x8B, 0xD4, 0x77, 0x22, 0x00, 0x63, 0x47, 0x45, 0x5F, + 0x36, 0x35, 0x58, 0x8B, 0x88, 0xEC, 0xCA, 0xC4, 0x60, 0x53, 0x9E, 0xBD, 0xB2, 0xF5, 0x51, 0x46, + 0x34, 0x9A, 0x07, 0x25, 0x3F, 0xF5, 0x65, 0x63, 0x77, 0x3C, 0x5A, 0xFA, 0x4E, 0x0C, 0xF7, 0x1B, + 0x82, 0xAB, 0x73, 0x06, 0x7F, 0xB7, 0xC6, 0x6B, 0xBF, 0xB1, 0x46, 0xF3, 0x01, 0x91, 0xB1, 0xFF, + 0x5C, 0x6F, 0xF9, 0x43, 0x0E, 0x6A, 0x70, 0x89, 0x0B, 0xEA, 0x8C, 0xD4, 0x1B, 0x51, 0x01, 0x31, + 0x71, 0x2E, 0xDF, 0x24, 0xC1, 0xD5, 0xDB, 0x0E, 0xF5, 0xEB, 0x78, 0x79, 0x39, 0x5B, 0xAD, 0xC3, + 0xA9, 0xA6, 0x60, 0x30, 0xA2, 0x9A, 0x7B, 0xA0, 0xF4, 0xAA, 0xC5, 0x57, 0xB3, 0x16, 0xF9, 0xB5, + 0x79, 0x20, 0xC1, 0x88, 0x9A, 0x00, 0x43, 0xB2, 0xC6, 0x84, 0x8D, 0x03, 0xF2, 0xD8, 0x90, 0x7A, + 0x21, 0x37, 0x7E, 0xF7, 0x75, 0xE5, 0xFB, 0xC9, 0xDC, 0xAB, 0x4B, 0xBC, 0x35, 0x38, 0xB9, 0x3A, + 0x53, 0x89, 0x7E, 0xD5, 0x94, 0x12, 0x2D, 0x9B, 0x91, 0x90, 0x1D, 0x4D, 0x0E, 0xE0, 0x93, 0xF3, + 0xC1, 0xA1, 0x9B, 0x73, 0x27, 0x22, 0x41, 0x27, 0xEE, 0x2A, 0xD7, 0x45, 0xBC, 0x8F, 0x9B, 0xA2, + 0x36, 0x11, 0x16, 0x37, 0x1A, 0xF1, 0x2E, 0x71, 0xCF, 0x86, 0x89, 0x83, 0x5A, 0xF1, 0x24, 0x6C, + 0x56, 0x71, 0x53, 0xE4, 0xD2, 0xCB, 0xCA, 0x86, 0x1E, 0xA0, 0xD5, 0x83, 0x3B, 0xEF, 0x09, 0x09, + 0xC2, 0x07, 0x53, 0x86, 0xE6, 0x8A, 0xC6, 0x70, 0xFB, 0x91, 0x43, 0xCB, 0x91, 0x6E, 0xA9, 0xBC, + 0x31, 0x42, 0x61, 0x0C, 0x88, 0xB8, 0x2C, 0xED, 0xD8, 0xE6, 0xA3, 0xEC, 0xAC, 0xB9, 0x45, 0x5E, + 0x2C, 0x73, 0x3F, 0x2E, 0x06, 0xE0, 0xBF, 0x73, 0xDD, 0x2E, 0x45, 0x50, 0x6C, 0x53, 0x55, 0xF0, + 0x7F, 0x6E, 0x61, 0xFA, 0xA0, 0x7A, 0x1C, 0xF0, 0xBD, 0xAC, 0x48, 0x61, 0x03, 0x6B, 0xED, 0x54, + 0x2A, 0x27, 0x94, 0xF6, 0xF9, 0x6A, 0x04, 0x08, 0x0B, 0x3C, 0xC3, 0x30, 0x66, 0x01, 0xFB, 0xDC, + 0xC9, 0x65, 0x03, 0x83, 0x7D, 0x0A, 0xDF, 0xA5, 0x04, 0x14, 0xE4, 0xF2, 0x4C, 0x01, 0xDF, 0x04, + 0xD2, 0x80, 0xB9, 0x9B, 0xD9, 0x5E, 0xF8, 0x2A, 0x93, 0x8D, 0x8C, 0x09, 0x9B, 0x38, 0xEC, 0x3B, + 0xC4, 0x29, 0x90, 0x7C, 0x65, 0x3A, 0xF2, 0x4B, 0x69, 0xD3, 0x63, 0x9B, 0x40, 0x95, 0xC3, 0xFB, + 0x67, 0x54, 0x40, 0x9B, 0x26, 0x9F, 0x52, 0xFE, 0xD8, 0xD0, 0x24, 0x9C, 0x5C, 0xD4, 0xEF, 0xDE, + 0x28, 0x66, 0x75, 0x04, 0xCB, 0xA4, 0xC0, 0xB9, 0x4B, 0xC9, 0x20, 0x4B, 0x56, 0xC7, 0x86, 0xC5, + 0x39, 0x45, 0x18, 0xA7, 0x48, 0x14, 0x1A, 0x51, 0xCA, 0xD0, 0xC0, 0x15, 0xDD, 0xC1, 0x28, 0x4A, + 0x7A, 0xD2, 0x10, 0xEA, 0x83, 0xD3, 0x3A, 0xEF, 0x48, 0x29, 0x41, 0xA4, 0xD4, 0x57, 0xA6, 0x1D, + 0x76, 0x24, 0x93, 0x58, 0x7E, 0xB7, 0xDD, 0x0B, 0xF2, 0xCE, 0x71, 0x55, 0xF5, 0xAB, 0x8C, 0xC8, + 0x70, 0x59, 0x73, 0x69, 0x9D, 0x29, 0x5E, 0x59, 0xF4, 0xB2, 0xC4, 0x97, 0x75, 0xF0, 0x65, 0x1B, + 0x66, 0x5F, 0xA4, 0x33, 0x5C, 0xC7, 0xBF, 0x45, 0xE6, 0x20, 0xC0, 0xBD, 0xAD, 0xAE, 0x9F, 0x97, + 0x05, 0xD8, 0x04, 0x2B, 0x0A, 0x46, 0xE8, 0xB8, 0xCB, 0x00, 0xE2, 0x7C, 0x70, 0x1B, 0x49, 0xDE, + 0x81, 0xEB, 0x24, 0xAC, 0x1B, 0x3E, 0x09, 0xFB, 0xAC, 0xB7, 0xF2, 0xD1, 0xB2, 0x78, 0xF3, 0xAC, + 0xC7, 0x6A, 0xA2, 0x07, 0x4C, 0xED, 0x61, 0xAD, 0x04, 0x7F, 0x45, 0x83, 0x59, 0x31, 0x27, 0xF0, + 0x16, 0x6B, 0x0C, 0xAA, 0xD4, 0xD1, 0xCB, 0x1C, 0x51, 0x41, 0x0D, 0x2F, 0x8F, 0xF9, 0xF9, 0x7F, + 0x22, 0x89, 0x46, 0xF4, 0xB8, 0x93, 0x98, 0x9E, 0x3E, 0x23, 0xF1, 0x6E, 0x64, 0x08, 0xB6, 0xC9, + 0x6E, 0x53, 0x53, 0xED, 0xAD, 0x21, 0xCD, 0x1A, 0xF0, 0x45, 0xFC, 0x14, 0x00, 0xEA, 0xF7, 0x42, + 0xEE, 0xDA, 0x58, 0x0D, 0x85, 0xBC, 0x74, 0xFB, 0x73, 0x78, 0xB5, 0x5E, 0x5E, 0x6F, 0x6F, 0x7E, + 0x39, 0xC2, 0x05, 0x50, 0xDB, 0x3D, 0xB8, 0xF3, 0x8F, 0x80, 0xEC, 0x46, 0x29, 0x39, 0x89, 0xF3, + 0x55, 0x9C, 0x6A, 0x5F, 0x7C, 0xD9, 0x7C, 0x13, 0xE4, 0x56, 0x5E, 0xE9, 0x60, 0x19, 0xE2, 0x7D, + 0xC4, 0x41, 0x92, 0x8D, 0xDA, 0x21, 0x58, 0x20, 0xE9, 0xA8, 0x4C, 0x16, 0x34, 0x99, 0xAC, 0xB7, + 0x30, 0xBD, 0x39, 0x19, 0xAC, 0x9B, 0x4B, 0x27, 0xFA, 0x32, 0xC1, 0x48, 0xA1, 0x80, 0x34, 0x36, + 0x1E, 0xFB, 0x92, 0x43, 0x35, 0x72, 0x2D, 0xEF, 0xD2, 0xF2, 0xFC, 0xC2, 0x85, 0xAB, 0x59, 0x40, + 0x8D, 0x9D, 0x1A, 0x1F, 0xE2, 0x92, 0x87, 0xA2, 0xF9, 0x2C, 0x78, 0xE4, 0xC3, 0x26, 0x56, 0x07, + 0xB3, 0x78, 0xAF, 0x79, 0x3D, 0x88, 0xF4, 0xAD, 0x66, 0x7C, 0x07, 0x58, 0x98, 0x82, 0x1A, 0x26, + 0xF7, 0xFD, 0xCE, 0xFF, 0x75, 0xED, 0xAB, 0xBD, 0xAE, 0x6D, 0x5C, 0x28, 0x91, 0xF3, 0xB7, 0x5C, + 0x27, 0x05, 0xEC, 0x3B, 0xE3, 0xDD, 0x93, 0x24, 0x7F, 0xAD, 0x14, 0xAA, 0x49, 0x61, 0x8F, 0x96, + 0x1F, 0xAA, 0xB2, 0xEE, 0xA8, 0x24, 0x41, 0x7C, 0xDC, 0xF1, 0x28, 0x26, 0xE6, 0x7F, 0x98, 0x20, + 0x50, 0x5F, 0x90, 0x21, 0x8A, 0x09, 0x26, 0x59, 0xD0, 0x07, 0x2F, 0xE1, 0x35, 0x4D, 0x0B, 0x20, + 0xB2, 0xD5, 0xDD, 0xB5, 0xAC, 0x1B, 0xFE, 0xD9, 0xE3, 0x35, 0xF1, 0xB8, 0x3F, 0x3D, 0xFC, 0x0B, + 0x5A, 0x57, 0xA9, 0x92, 0x2B, 0xC8, 0x3E, 0xC2, 0xAA, 0xEF, 0xB9, 0x98, 0x2C, 0xA8, 0xAB, 0xF6, + 0xA1, 0xBF, 0xBC, 0x8D, 0x97, 0xA2, 0x74, 0xD9, 0xE5, 0x99, 0x85, 0x81, 0x15, 0xB0, 0xE7, 0x8B, + 0x48, 0x86, 0xF4, 0x94, 0x9C, 0x62, 0x82, 0xD1, 0x2C, 0x24, 0x4B, 0xAC, 0x7A, 0xB8, 0x4E, 0x4A, + 0xD2, 0xF6, 0xAA, 0xED, 0xE0, 0x9C, 0x98, 0xD2, 0xDF, 0xC1, 0xBC, 0xBF, 0x55, 0x7D, 0x40, 0xB5, + 0xDE, 0xD4, 0x25, 0xBB, 0x81, 0xF4, 0x07, 0x1D, 0xE7, 0x3C, 0xB4, 0x62, 0xC9, 0x55, 0x0A, 0x3A, + 0xD5, 0xCE, 0x97, 0xED, 0x30, 0x76, 0x76, 0x51, 0xBC, 0x8C, 0xE4, 0x54, 0xBE, 0xB7, 0xB5, 0xCD, + 0xF8, 0x76, 0x37, 0x53, 0x2C, 0x9F, 0xE4, 0xC7, 0xEB, 0xF5, 0x8D, 0x23, 0x8A, 0xDA, 0xD1, 0xA9, + 0xD8, 0x4C, 0x53, 0xF3, 0x49, 0xA7, 0x1A, 0x5D, 0xE5, 0x03, 0x49, 0x52, 0xD3, 0xE2, 0x1F, 0xA5, + 0x35, 0x9C, 0xBB, 0x0B, 0xC7, 0x0D, 0xA4, 0x65, 0x54, 0x8B, 0x39, 0xF1, 0x3B, 0x67, 0x21, 0x71, + 0x10, 0xE7, 0x76, 0xC4, 0xA8, 0xC2, 0x9D, 0x93, 0xC6, 0x51, 0xBA, 0x23 + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h b/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h new file mode 100644 index 00000000..97cdefdd --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/rs_codec.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include "dsp/processor.h" + +extern "C" { + #include "correct.h" +} + +namespace ryfi { + // Size of an encoded reed-solomon block. + inline const int RS_BLOCK_ENC_SIZE = 255; + + // Size of a decoded reed-solomon block. + inline const int RS_BLOCK_DEC_SIZE = 223; + + // Number of reed-solomon blocks. + inline const int RS_BLOCK_COUNT = 4; + + // Scrambler sequence + extern const uint8_t RS_SCRAMBLER_SEQ[RS_BLOCK_ENC_SIZE*RS_BLOCK_COUNT]; + + /** + * RyFi Reed-Solomon Encoder. + */ + class RSEncoder : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a reed-solomon encoder specifying an input stream. + * @param in Input stream + */ + RSEncoder(dsp::stream* in = NULL); + + // Destructor + ~RSEncoder(); + + /** + * Encode data. + * @param in Input bytes. + * @param out Output bytes. + * @param count Number of input bytes. + * @return Number of output bytes. + */ + int encode(const uint8_t* in, uint8_t* out, int count); + + private: + int run(); + + correct_reed_solomon* rs; + }; + + /** + * RyFi Reed-Solomon Decoder. + */ + class RSDecoder : public dsp::Processor { + using base_type = dsp::Processor; + public: + /** + * Create a reed-solomon decoder specifying an input stream. + * @param in Input stream + */ + RSDecoder(dsp::stream* in = NULL); + + // Destructor + ~RSDecoder(); + + /** + * Decode data. + * @param in Input bytes. + * @param out Output bytes. + * @param count Number of input bytes. + * @return Number of output bytes. + */ + int decode(uint8_t* in, uint8_t* out, int count); + + private: + int run(); + + correct_reed_solomon* rs; + }; +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp b/decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp new file mode 100644 index 00000000..d8d05489 --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/transmitter.cpp @@ -0,0 +1,177 @@ +#include "transmitter.h" + +namespace ryfi { + Transmitter::Transmitter(double baudrate, double samplerate) { + // Initialize the DSP + rs.setInput(&in); + conv.setInput(&rs.out); + framer.setInput(&conv.out); + resamp.init(&framer.out, baudrate, samplerate); + + rrcTaps = dsp::taps::rootRaisedCosine(511, 0.6, baudrate, samplerate); + // Normalize the taps + float tot = 0.0f; + for (int i = 0; i < rrcTaps.size; i++) { + tot += rrcTaps.taps[i]; + } + for (int i = 0; i < rrcTaps.size; i++) { + rrcTaps.taps[i] /= tot; + } + + rrc.init(&resamp.out, rrcTaps); + out = &rrc.out; + } + + Transmitter::~Transmitter() { + // Stop everything + stop(); + } + + void Transmitter::start() { + // Do nothing if already running + if (running) { return; } + + // Start the worker thread + workerThread = std::thread(&Transmitter::worker, this); + + // Start the DSP + rs.start(); + conv.start(); + framer.start(); + resamp.start(); + rrc.start(); + + // Update the running state + running = true; + } + + void Transmitter::stop() { + // Do nothing if not running + if (!running) { return; } + + // Stop the worker thread + in.stopWriter(); + if (workerThread.joinable()) { workerThread.join(); } + in.clearWriteStop(); + + // Stop the DSP + rs.stop(); + conv.stop(); + framer.stop(); + resamp.stop(); + rrc.stop(); + + // Update the running state + running = false; + } + + bool Transmitter::send(const Packet& pkt) { + // Acquire the packet queue + std::lock_guard lck(packetsMtx); + + // If there are too many packets queued up, drop the packet + if (packets.size() >= MAX_QUEUE_SIZE) { return false; } + + // Push the packet onto the queue + packets.push(pkt); + } + + bool Transmitter::txFrame(const Frame& frame) { + // Serialize the frame + int count = frame.serialize(in.writeBuf); + + // Send it off + return in.swap(count); + } + + Packet Transmitter::popPacket() { + // Acquire the packet queue + std::unique_lock lck(packetsMtx); + + // If no packets are available, return empty packet + if (!packets.size()) { return Packet(); } + + // Pop the front packet and return it + Packet pkt = packets.front(); + packets.pop(); + return pkt; + } + + void Transmitter::worker() { + Frame frame; + Packet pkt; + uint16_t counter = 0; + int pktToWrite = 0; + int pktWritten = 0; + uint8_t* pktBuffer = new uint8_t[Packet::MAX_SERIALIZED_SIZE]; + + while (true) { + // Initialize the frame + frame.counter = counter++; + frame.firstPacket = PKT_OFFS_NONE; + frame.lastPacket = PKT_OFFS_NONE; + int frameOffset = 0; + + // Fill the frame with as much packet data as possible + while (frameOffset < sizeof(Frame::content)) { + // If there is no packet in the process of being sent + if (!pktWritten) { + // If there is not enough space for the size of the packet + if ((sizeof(Frame::content) - frameOffset) < 2) { + // Fill the rest of the frame with noise and send it + for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); } + break; + } + + // Get the next packet + pkt = popPacket(); + + // If there was an available packet + if (pkt) { + // Serialize the packet + pktToWrite = pkt.serializedSize(); + pkt.serialize(pktBuffer); + } + } + + // If none was available + if (!pkt) { + // Fill the rest of the frame with noise and send it + for (int i = frameOffset; i < sizeof(Frame::content); i++) { frame.content[i] = rand(); } + break; + } + + // If this is the beginning of the packet + if (!pktWritten) { + //flog::debug("Starting to write a {} byte packet at offset {}", pktToWrite-2, frameOffset); + + // If this is the first packet of the frame, update its offset + if (frame.firstPacket == PKT_OFFS_NONE) { frame.firstPacket = frameOffset; } + + // Update the last packet pointer + frame.lastPacket = frameOffset; + } + + // Compute the amount of data writeable to the frame + int writeable = std::min(pktToWrite - pktWritten, sizeof(Frame::content) - frameOffset); + + // Copy the data to the frame + memcpy(&frame.content[frameOffset], &pktBuffer[pktWritten], writeable); + pktWritten += writeable; + frameOffset += writeable; + + // If the packet is done being sent + if (pktWritten >= pktToWrite) { + // Prepare for a new packet + pktToWrite = 0; + pktWritten = 0; + } + } + + // Send the frame + if (!txFrame(frame)) { break; } + } + + delete[] pktBuffer; + } +} \ No newline at end of file diff --git a/decoder_modules/ryfi_decoder/src/ryfi/transmitter.h b/decoder_modules/ryfi_decoder/src/ryfi/transmitter.h new file mode 100644 index 00000000..d6f552fd --- /dev/null +++ b/decoder_modules/ryfi_decoder/src/ryfi/transmitter.h @@ -0,0 +1,69 @@ +#pragma once +#include "dsp/multirate/rational_resampler.h" +#include "dsp/taps/root_raised_cosine.h" +#include "dsp/filter/fir.h" +#include "packet.h" +#include "frame.h" +#include "rs_codec.h" +#include "conv_codec.h" +#include "framing.h" +#include +#include + +namespace ryfi { + class Transmitter { + public: + /** + * Create a transmitter. + * @param baudrate Baudrate to use over the air. + * @param samplerate Samplerate of the baseband. + */ + Transmitter(double baudrate, double samplerate); + + // Destructor + ~Transmitter(); + + /** + * Start the transmitter's DSP. + */ + void start(); + + /** + * Stop the transmitter's DSP. + */ + void stop(); + + /** + * Send a packet. + * @param pkg Packet to send. + * @return True if the packet was send, false if it was dropped. + */ + bool send(const Packet& pkt); + + // Baseband output + dsp::stream* out; + + static inline const int MAX_QUEUE_SIZE = 32; + + private: + bool txFrame(const Frame& frame); + Packet popPacket(); + void worker(); + + // Packet queue + std::mutex packetsMtx; + std::queue packets; + + // DSP + dsp::stream in; + RSEncoder rs; + ConvEncoder conv; + Framer framer; + dsp::multirate::RationalResampler resamp; + dsp::tap rrcTaps; + dsp::filter::FIR rrc; + + bool running = false; + std::thread workerThread; + }; +} \ No newline at end of file