/* * Copyright (C) 2024 EPIRB Receiver Implementation * Copyright (C) 2026 Frederic BORRY - ADRASEC 31 * * This file is part of PortaPack. * * 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 2, 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; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ #ifndef __PROC_EPIRB_H__ #define __PROC_EPIRB_H__ #include #include #include #include #include "baseband_processor.hpp" #include "baseband_thread.hpp" #include "rssi_thread.hpp" #include "channel_decimator.hpp" #include "matched_filter.hpp" #include "packet_builder.hpp" #include "baseband_packet.hpp" #include "message.hpp" #include "buffer.hpp" // Specan is disable to keep application size below the 32k limit // #define SPECAN #ifdef SPECAN #include "spectrum_collector.hpp" #endif #include "audio_output.hpp" #include "dsp_demodulate.hpp" // Forward declarations for types only used as pointers/references class Message; namespace baseband { class Packet; } // COSPAS / SARSAT 406 frame constants // Size of preamble (bits) #define COSPAS_PREAMBLE_SIZE 24 // Size of long frame (bits) #define COSPAS_LONG_FRAME_SIZE 144 // Siz of short frame (bits) #define COSPAS_SHORT_FRAME_SIZE 112 // Preamble for real frames #define COSPAS_REAL_PREAMBLE 0b1111'1111'1111'1110'0010'1111 // Preable for test frames #define COSPAS_TEST_PREAMBLE 0b1111'1111'1111'1110'1101'0000 // Dedicated EPIRB PacketBuilder // Usees diedicated preamble detection logic to find both real and test frames // Also as a dedicated packet size detection based on frame's size bit class EPIRBPacketBuilder { public: using EPIRBHandler = void (*)(void* context, const baseband::Packet& packet); EPIRBPacketBuilder( void* context, EPIRBHandler handler) : context(context), handler(handler) { } void execute( const uint_fast8_t symbol) { bit_history.add(symbol); switch (state) { case State::Preamble: { // Detect both real and test fram preambles bool is_real = real_sync_matcher(bit_history, packet.size()); bool is_test = test_sync_matcher(bit_history, packet.size()); if (is_real || is_test) { // Append preamble to the begining of the packet uint64_t preamble = is_real ? COSPAS_REAL_PREAMBLE : COSPAS_TEST_PREAMBLE; for (int8_t i = (COSPAS_PREAMBLE_SIZE - 1); i >= 0; i--) { packet.add((preamble >> i) & 0x1); } state = State::Format; } } break; case State::Format: packet.add(symbol); // 144 bits for long frames and 112 for short frames size = symbol ? COSPAS_LONG_FRAME_SIZE : COSPAS_SHORT_FRAME_SIZE; state = State::Payload; break; case State::Payload: packet.add(symbol); if (packet.size() >= size) { flush(); } else { if (packet.size() >= packet.capacity()) { reset_state(); } } break; default: reset_state(); break; } } void flush() { // Timestamp is not set here to save some app space (not used on ui side) // packet.set_timestamp(Timestamp::now()); if (handler) handler(context, packet); reset_state(); } void reset_state() { packet.clear(); bit_history = BitHistory(); state = State::Preamble; } private: enum State { Preamble, Format, Payload, }; BitHistory bit_history{}; BitPattern real_sync_matcher{COSPAS_REAL_PREAMBLE, COSPAS_PREAMBLE_SIZE}; BitPattern test_sync_matcher{COSPAS_TEST_PREAMBLE, COSPAS_PREAMBLE_SIZE}; void* context; EPIRBHandler handler; uint8_t size{0}; State state{State::Preamble}; baseband::Packet packet{}; }; class EPIRBProcessor : public BasebandProcessor { public: EPIRBProcessor(); void execute(const buffer_c8_t& buffer) override; void on_message(const Message* const message) override; private: // Baseband frequency is set to 3,072,000 samples / sec static constexpr uint32_t BASEBAND_SAMPLE_RATE = 3072000; static constexpr uint32_t SAMPLE_RATE = BASEBAND_SAMPLE_RATE / 8 / 8; // We use to decimators with factor 8 each static constexpr uint32_t SYMBOL_RATE = 800; // 400 bps + Manchester (2 1/2 bits per symbol) => 800 static constexpr size_t SAMPLES_PER_SYMBOL = SAMPLE_RATE / SYMBOL_RATE; // = 60 samples per symbol static constexpr size_t SAMPLES_PER_BIT = SAMPLES_PER_SYMBOL * 2; // = 120 samples per bit static constexpr size_t SAMPLES_MARGIN = SAMPLES_PER_SYMBOL / 3; // = Allow 20 sample drift static constexpr size_t SAMPLES_ACCUMUMLATOR = SAMPLES_PER_SYMBOL / 5; // Accumulate phase change across 12 samples static constexpr size_t RISE_FILTER_SAMPLES = SAMPLES_PER_SYMBOL / 20; // Filter peaks of less than 3 samples static constexpr size_t CARRIER_SAMPLES_THRESHOLD = 0.080f * SAMPLE_RATE; // Carrier before frame lasts 160ms, require at least 80ms static constexpr size_t CARRIER_MAX_SAMPLES = 0.900f * SAMPLE_RATE; // Carrier + frame lasts 160ms + 520ms + 100ms post carrier = 880ms static constexpr size_t FRAME_MAX_SAMPLES = SAMPLES_PER_BIT * (144 * 1.1f); // Frame max length (add 1% error margin) AudioOutput audio_output{}; // Config uint8_t squelch_level{50}; // Audio on/off logic is disabled to save app space // bool audio_on{true}; #ifdef SPECAN bool spectrum_on{false}; #endif std::array audio{}; const buffer_f32_t audio_buffer{ audio.data(), audio.size()}; // Last received bit (for manchester deconding) bool last_bit = false; // Sample count since last symbol uint16_t sample_count{0}; // Sample count since frame start uint16_t frame_sample_count{0}; // True if last phase shift was positive, false otherwise bool last_phase_positive = false; // Counter used for peak filtering uint16_t rise_detection_count{0}; // Frame detection state machine states enum State { IDLE, CARRIER_LOCKED, DATA_SYNC, POST_FRAME }; // Current state for frame detection state machine State current_state = IDLE; // Carrier detection counter uint32_t stability_counter = 0; // Phase delta accumulator (6 samples) static constexpr size_t PHASE_DELTA_ACC_SIZE = SAMPLES_ACCUMUMLATOR; float phase_delta_buffer[PHASE_DELTA_ACC_SIZE] = {0.0f}; size_t pahse_delta_index = 0; float phase_delta_acc = 0.0f; std::array dst{}; const buffer_c16_t dst_buffer{ dst.data(), dst.size()}; // Decimation chain for 406 MHz EPIRB signal processing dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{}; dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; // Audio filtering dsp::decimate::FIRAndDecimateComplex channel_filter{}; // Audio demodulation dsp::demodulate::FM demod{}; #ifdef SPECAN SpectrumCollector channel_spectrum{}; #endif // Store last stample for phase delta calculation complex16_t last_sample{}; // EPIRB packet structure: // - Sync pattern: 111111111111111 (15 bits) // - Frame sync: 000101111(real) / 011010000(test) (9 bits) // - Data: 120 bits (long frame) / // bits (short frame) // - BCH error correction: 10 bits // Total: 144 bits (long frame) / 112 bits (short frame) EPIRBPacketBuilder packet_builder{ this, [](void* ctx, const baseband::Packet& p) { static_cast(ctx)->payload_handler(p); }}; void payload_handler(const baseband::Packet& packet); // Compute phase diff between two samples float get_phase_diff(const complex16_t& sample0, const complex16_t& sample1); // End current frame void frame_end(); // Rise detection with peak filtering bool filtered_rise_detect(bool condition); // Configure audio processing void configure_audio(); /* NB: Threads should be the last members in the class definition. */ BasebandThread baseband_thread{ BASEBAND_SAMPLE_RATE, this, baseband::Direction::Receive, /*auto_start*/ false}; RSSIThread rssi_thread{}; }; #endif /*__PROC_EPIRB_H__*/