EPIRB TX Application (#3081)

* First step to epirb-tx app

* Next step to epirb_tx app

* Fixed merge

* EPIRB TX app progress

- Fixed UI
- Added automatic frame resend after delay
- Added load of beacons from EPIRB/BEACONS.TXT file

* First step to adding 121.5 MHz signal

* Refactored 121.5 signal generation code

* Added frequency change when switching from BPSK to AM

* Added checkbox for AM signal.

Added START/STOP button
Added AM frequency
First step to editor mode.

* Next step to frame editor

* Fixed location bits.

* Refactored code + fixed location

* Added location fields

* UI fixes + settings backup

Fixed lat/long display
Added send on change checkbox
Added settings backup
Fixed locator edition
Fixed BEACONS.TXT to move to Self test beacons.

* Update ui_epirb_tx.cpp

Fixed timeout reset

* Added national location protocol.

Added national location protocol.
Fixed send on change.

* UI fixes

Added country selection
Separated beacon protocol and beacon type selectors

* Standard protocol

Added standard protocol
Added internal checkbox

* Fixed fomratting

* Code cleanup

* Comments / doc

* More comments / doc

* More comments / doc

Fixed pre count duration.
Added frequency restore after leaving

* Prepare merge

* Prepare merge

* Finished merge

* More comments and doc

* Update ui_epirb_tx.cpp

Fixed out of memory error when using AlphanumView

* Update proc_epirb_tx.hpp

Fixed comment

* Fixed copilot PR comments.

* Update proc_epirb_tx.hpp

Fixed formatting

* Update external.ld

Merge with kiss_tnc app
This commit is contained in:
Frederic BORRY
2026-03-12 10:05:42 +01:00
committed by GitHub
parent 2c2959df30
commit 5aecd4097e
17 changed files with 1885 additions and 0 deletions
+8
View File
@@ -535,6 +535,14 @@ set(MODE_CPPSRC
)
DeclareTargets(PEPI epirb_rx)
### EPIRB TX
set(MODE_CPPSRC
proc_epirb_tx.cpp
)
DeclareTargets(PEPT epirb_tx)
### NRF RX
+191
View File
@@ -0,0 +1,191 @@
/*
* 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.
*/
#include "proc_epirb_tx.hpp"
#include "portapack_shared_memory.hpp"
#include "sine_table_int8.hpp"
#include "event_m4.hpp"
#include <cstdint>
#include <cstring>
/**
* Processing method for this processor
*/
void EPIRBTXProcessor::execute(const buffer_c8_t& buffer) {
if (!configured) return;
// Iterate on each sample of the buffer
for (size_t i = 0; i < buffer.count; i++) {
if (end_of_transmission) {
// Stop transmission
configured = false;
end_of_transmission = false;
txprogress_message.done = true;
shared_memory.application_queue.push(txprogress_message);
}
if (mode_bpsk) {
// BPSK Manchester beacon signal
if (bpsk_pre_count < config_pre_count) {
// Pre-count state: send a negative phase carrier during pre-count
bpsk_pre_count++;
re = i_neg;
im = q_neg;
} else if (bpsk_post_count > 0) {
// Post-count: send a negative phase carrier during post-count
bpsk_post_count++;
re = i_neg;
im = q_neg;
if (bpsk_post_count >= config_post_count) {
// End transmission here
byte_index = 0;
bpsk_post_count = 0;
bpsk_pre_count = 0;
end_of_transmission = true;
}
} else {
if (sample_counter == 0 && manchester_half == false) {
if (bit_index == 0) {
// Read current byte
current_byte = frame_data[byte_index];
// Move to next byte
byte_index++;
}
// Get current bit
current_bit = (current_byte >> (7 - bit_index)) & 0x01;
}
// Manchester encoding
if (current_bit == 1) {
// 1 = falling signal
if (manchester_half == false) {
re = i_pos;
im = q_pos;
} else {
re = i_neg;
im = q_neg;
}
} else {
// 0 = rising signal
if (manchester_half == false) {
re = i_neg;
im = q_neg;
} else {
re = i_pos;
im = q_pos;
}
}
// Move to next sample
sample_counter++;
if (sample_counter >= samples_per_halfbit) {
// Move to next half-bit
sample_counter = 0;
manchester_half = !manchester_half;
// Next bit after two half bits
if (manchester_half == false) {
// Move to next bit
bit_index++;
if (bit_index >= 8) {
// End of byte
bit_index = 0;
if (byte_index >= frame_data_len) {
// End of frame => move to post-count
bpsk_post_count = 1;
}
}
}
}
}
} else {
// AM 127.5 MHz sine sweep
// ---- 3 Hz Sweep ----
sweep_phase += sweep_inc;
uint8_t sweep_index = (sweep_phase & 0xFF000000) >> 24;
int8_t sweep = sine_table_i8[sweep_index]; // -128..127
// Audio frequency based on sweep
int32_t audio_freq = center_freq + sweep * freq_dev;
// ---- Audio signal (sine wave) ----
uint32_t audio_inc = audio_freq * freq_scale;
audio_phase += audio_inc;
uint8_t audio_index = (audio_phase & 0xFF000000) >> 24;
int8_t audio = sine_table_i8[audio_index];
// ---- AM ----
// Double Side Band modulation with modulation index of ~80% (100/128) + offset (74)
int16_t amplitude = 74 + ((100 * audio) >> 7); // 1/128 via shift
if (amplitude > 127) amplitude = 127;
if (amplitude < -128) amplitude = -128;
re = (int8_t)amplitude;
im = 0;
}
buffer.p[i] = {re, im};
}
};
void EPIRBTXProcessor::on_message(const Message* const msg) {
// Configure the processor
switch (msg->id) {
case Message::ID::EPIRBTXData: {
const auto message = *reinterpret_cast<const EPIRBTXDataMessage*>(msg);
// Check transmission mode
mode_bpsk = message.mode_bpsk;
if (mode_bpsk) {
// BPSK mode for 406 frame
config_pre_count = message.pre_count;
config_post_count = message.post_count;
frame_data_len = message.data_len;
// Get the frame data from the message
memcpy(frame_data, message.data, std::min(frame_data_len, EPIRBTXDataMessage::max_len));
// Init BPSK sequencer
sample_counter = 0;
bpsk_pre_count = 0;
bpsk_post_count = 0;
bit_index = 0;
byte_index = 0;
current_byte = 0;
current_bit = 0;
} else {
// AM mode for 121.5 signal => init AM sequencer
sweep_phase = 0;
audio_phase = 0;
}
// Tell the processor to start
configured = true;
} break;
default:
break;
}
}
int main() {
EventDispatcher event_dispatcher{std::make_unique<EPIRBTXProcessor>()};
event_dispatcher.run();
return 0;
}
+111
View File
@@ -0,0 +1,111 @@
/*
* 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_TX_H__
#define __PROC_EPIRB_TX_H__
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "portapack_shared_memory.hpp"
#include "tonesets.hpp"
#include <cmath>
/**
* Processor used by epirb_tx app to simulate a COSPAS/SARSAT emergency beacon
* The processor will alternatively:
* - Send a 406 MHz Manchester encoded BPSK signal containing the beacon information
* - Send a 127.5 MHz AM distress audio signal
*/
class EPIRBTXProcessor : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const msg) override;
private:
// True when the processor has received a configuration message from the app
bool configured{false};
// True when in BPSK transmission mode, false for AM transmission mode
bool mode_bpsk{false};
// True when the transmission has to be stopped (e.g. at the end of a frame)
bool end_of_transmission{};
// I/Q values for current sample (ranging from -128 to 127)
int8_t re{0}, im{0};
// Configured pre-count value: used to generate a carrier for config_pre_count samples before starting a frame
uint32_t config_pre_count = 0;
// Configured post-count value: used to continue the carrier for config_post_count samples after the end of a frame
uint32_t config_post_count = 0;
// Data of the frame to send in BPSK mode
uint8_t frame_data[18]{0};
// Size of the frame to send in BPSK mode
uint8_t frame_data_len = 0;
// BPSK parameters: Target phase +/-63° as per COSPAS/SARSAT specifications
static constexpr float phase_rad = 63.0f * M_PI / 180.0f;
// I/Q values for BPSK (positive phase and negative phase)
int8_t i_pos = (int8_t)(cos(phase_rad) * 127);
int8_t q_pos = (int8_t)(sin(phase_rad) * 127);
int8_t i_neg = i_pos;
int8_t q_neg = -q_pos;
// COSPAS/SARSAT signal is manchester (2 states per bit) encoded 400 bit/sec
static const uint32_t samples_per_halfbit = TONES_SAMPLERATE / 400 / 2;
// Sequencer state for BPSK
uint32_t sample_counter = 0;
uint32_t bpsk_pre_count = 0;
uint32_t bpsk_post_count = 0;
// Bit position in current byte
uint32_t bit_index = 0;
// Byte position in current frame
uint32_t byte_index = 0;
// Value of the current byte
uint8_t current_byte = 0;
// Value of the current bit
uint8_t current_bit = 0;
// Position in the current manchester bit
bool manchester_half = false; // false = first half
// 127.5 AM signal parameters
static const uint32_t sweep_rate = 3; // 3 Hz
static const uint32_t f_min = 300; // Sweep min frequency (Hz)
static const uint32_t f_max = 1600; // Sweep max frequency (Hz)
static const uint32_t freq_span = f_max - f_min;
// Frequency
static const uint32_t freq_scale = (1ULL << 32) / TONES_SAMPLERATE;
static const int32_t center_freq = f_min + (freq_span / 2);
static const int32_t freq_dev = freq_span / 256;
// Increments
static const uint32_t sweep_inc = sweep_rate * freq_scale;
// Phase accumulators for sweep and audio
uint32_t sweep_phase = 0;
uint32_t audio_phase = 0;
TXProgressMessage txprogress_message{};
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{TONES_SAMPLERATE, this, baseband::Direction::Transmit};
};
#endif