EPIRB TX application update (#3120)

This commit is contained in:
Frederic BORRY
2026-04-03 11:35:46 +02:00
committed by GitHub
parent 605c9efa0c
commit 03131c8a0c
4 changed files with 273 additions and 45 deletions
+175 -22
View File
@@ -98,6 +98,39 @@ void EPIRBTXAppView::on_timer() {
}
}
void EPIRBTXAppView::update_bpsk_frequency() {
bool was_transmitting = false;
if (transmitting && (am_enabled || transmitting_bpsk)) {
// We need to stop transmission before changing frequency
transmitter_model.disable();
was_transmitting = true;
}
transmitter_model.set_target_frequency(bpsk_frequency);
// Update displayed frequency
tx_view.on_show();
if (was_transmitting) {
// Start over
start_tx();
}
}
void EPIRBTXAppView::update_am_transmission() {
if (am_enabled && transmitting && !transmitting_bpsk) {
// Start am transmission
// Restore am frequency
epirb_tx_message.mode_bpsk = false;
transmitter_model.set_target_frequency(am_frequency);
// Send config to baseband
baseband::set_epirb_tx_config(epirb_tx_message);
// Start transmitting
transmitter_model.enable();
} else if (transmitting && !transmitting_bpsk) {
// Stop am transmission
transmitter_model.disable();
tx_view.set_transmitting(false);
}
}
void EPIRBTXAppView::update_frame(bool updateConfig) {
if (mode_file) {
// In file mode, currently selected beacon has changed => load the new one
@@ -136,17 +169,17 @@ void EPIRBTXAppView::update_frame(bool updateConfig) {
}
void EPIRBTXAppView::update_config() {
if (epirb_tx_message.mode_bpsk) {
// Already in BPSK mode => backup bpsk frequency
bpsk_frequency = transmitter_model.target_frequency();
} else {
if (!epirb_tx_message.mode_bpsk) {
// Previously in AM mode => restore bpsk frequency
transmitter_model.set_target_frequency(bpsk_frequency);
// Update displayed frequency
tx_view.on_show();
}
// Set mode to bpsk
epirb_tx_message.mode_bpsk = true;
transmitting_bpsk = true;
// Set pre/post count
epirb_tx_message.pre_count = (150 * TONES_SAMPLERATE) / 1000; // 150 ms
epirb_tx_message.pre_count = (160 * TONES_SAMPLERATE) / 1000; // 160 ms carrier (COSPAS spec.)
epirb_tx_message.post_count = (100 * TONES_SAMPLERATE) / 1000; // 100 ms
// Send config to baseband
baseband::set_epirb_tx_config(epirb_tx_message);
@@ -178,15 +211,12 @@ void EPIRBTXAppView::stop_tx() {
void EPIRBTXAppView::on_tx_progress(const uint32_t progress, const bool done) {
(void)progress;
if (done) {
transmitting_bpsk = false;
if (am_enabled) {
// BPSK frame sent, switch back to 121.5 AM signal
epirb_tx_message.mode_bpsk = false;
// Backup bpsk frequency for next run
bpsk_frequency = transmitter_model.target_frequency();
// Restore am frequency
transmitter_model.set_target_frequency(am_frequency);
// Send config to baseband
baseband::set_epirb_tx_config(epirb_tx_message);
// Start am transmission
update_am_transmission();
} else {
// End of BPSK frame
transmitter_model.disable();
@@ -262,6 +292,8 @@ EPIRBTXAppView::EPIRBTXAppView(
&checkbox_am,
&field_am_frequency,
&checkbox_send_on_change,
&options_am_channel,
&options_bpsk_channel,
&tx_view});
text_beacon.set_style(Theme::getInstance()->fg_light);
@@ -279,6 +311,11 @@ EPIRBTXAppView::EPIRBTXAppView(
options_mode.set_by_value(!mode_file);
transmitter_model.set_target_frequency(bpsk_frequency);
field_am_frequency.set_value(am_frequency);
options_am_channel.set_by_value(am_channel);
options_am_channel.set_style((am_channel == (uint8_t)AmChannel::REAL) ? Theme::getInstance()->fg_red : Theme::getInstance()->bg_darkest);
manual_am_frequency = am_frequency;
options_bpsk_channel.set_by_value(bpsk_channel);
manual_bpsk_frequency = bpsk_frequency;
field_delay.set_value(delay);
options_beacon_type.set_by_value(beacon_type);
options_beacon_protocol.set_by_value(beacon_protocol);
@@ -292,23 +329,23 @@ EPIRBTXAppView::EPIRBTXAppView(
update_mode();
update_location();
options_mode.on_change = [this](size_t index, OptionsField::value_t) {
mode_file = (index == 0);
options_mode.on_change = [this](size_t, OptionsField::value_t value) {
mode_file = (((BeaconMode)value) == BeaconMode::FILE);
update_mode();
update_frame();
set_dirty();
};
options_beacon_type.on_change = [this](size_t index, OptionsField::value_t) {
beacon_params.type = (BeaconType)index;
beacon_type = index;
options_beacon_type.on_change = [this](size_t, OptionsField::value_t value) {
beacon_params.type = (BeaconType)value;
beacon_type = value;
update_frame();
set_dirty();
};
options_beacon_protocol.on_change = [this](size_t index, OptionsField::value_t) {
beacon_params.protocol = (BeaconProtocol)index;
beacon_protocol = index;
options_beacon_protocol.on_change = [this](size_t, OptionsField::value_t value) {
beacon_params.protocol = (BeaconProtocol)value;
beacon_protocol = value;
update_frame();
set_dirty();
};
@@ -320,6 +357,71 @@ EPIRBTXAppView::EPIRBTXAppView(
set_dirty();
};
options_am_channel.on_change = [this](size_t, OptionsField::value_t v) {
bool is_real = false;
switch ((AmChannel)v) {
case AmChannel::REAL:
is_real = true;
am_frequency = AM_REAL_FREQUENCY;
break;
case AmChannel::MANUAL:
am_frequency = manual_am_frequency;
break;
default:
v = (uint8_t)AmChannel::TEST;
// fallthrough
case AmChannel::TEST:
am_frequency = AM_TEST_FREQUENCY;
break;
}
// Actual frequency change will be done by field_am_frequency.on_change()
field_am_frequency.set_value(am_frequency);
am_channel = v;
options_am_channel.set_style(is_real ? Theme::getInstance()->fg_red : Theme::getInstance()->bg_darkest);
set_dirty();
};
options_bpsk_channel.on_change = [this](size_t, OptionsField::value_t v) {
switch ((BpskChannel)v) {
case BpskChannel::MANUAL:
bpsk_frequency = manual_bpsk_frequency;
break;
case BpskChannel::B:
bpsk_frequency = BPSK_FREQUENCY_B;
break;
case BpskChannel::C:
bpsk_frequency = BPSK_FREQUENCY_C;
break;
case BpskChannel::F:
bpsk_frequency = BPSK_FREQUENCY_F;
break;
case BpskChannel::G:
bpsk_frequency = BPSK_FREQUENCY_G;
break;
case BpskChannel::J:
bpsk_frequency = BPSK_FREQUENCY_J;
break;
case BpskChannel::K:
bpsk_frequency = BPSK_FREQUENCY_K;
break;
case BpskChannel::N:
bpsk_frequency = BPSK_FREQUENCY_N;
break;
case BpskChannel::O:
bpsk_frequency = BPSK_FREQUENCY_O;
break;
default:
v = (uint8_t)BpskChannel::HAM;
// fallthrough
case BpskChannel::HAM:
bpsk_frequency = BPSK_FREQUENCY_HAM;
break;
}
bpsk_channel = v;
update_bpsk_frequency();
set_dirty();
};
checkbox_beacon_internal.on_select = [this](Checkbox&, bool v) {
beacon_internal = v;
beacon_params.is_internal = v;
@@ -358,7 +460,7 @@ EPIRBTXAppView::EPIRBTXAppView(
field_am_frequency.on_change = [this](rf::Frequency freq) {
am_frequency = freq;
if (transmitting && !epirb_tx_message.mode_bpsk && am_enabled)
if (transmitting && !transmitting_bpsk && am_enabled)
// Update transmitter frequency
transmitter_model.set_target_frequency(am_frequency);
};
@@ -397,6 +499,8 @@ EPIRBTXAppView::EPIRBTXAppView(
checkbox_am.on_select = [this](Checkbox&, bool v) {
beacon_params.has_121_5 = v;
am_enabled = v;
update_am_transmission();
// We update the additional location device data in the frame based on this
if (!mode_file) update_frame(false);
};
@@ -404,7 +508,21 @@ EPIRBTXAppView::EPIRBTXAppView(
field_am_frequency.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(field_am_frequency.value());
new_view->on_changed = [this](rf::Frequency f) {
field_am_frequency.set_value(f);
switch (f) {
case AM_REAL_FREQUENCY:
am_channel = (uint8_t)AmChannel::REAL;
break;
case AM_TEST_FREQUENCY:
am_channel = (uint8_t)AmChannel::TEST;
break;
default:
manual_am_frequency = f;
am_channel = (uint8_t)AmChannel::MANUAL;
break;
}
// Actual frequency change will be done by options_am_channel.on_change()
options_am_channel.set_by_value(am_channel);
set_dirty();
};
};
@@ -415,8 +533,43 @@ EPIRBTXAppView::EPIRBTXAppView(
tx_view.on_edit_frequency = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(transmitter_model.target_frequency());
new_view->on_changed = [this](rf::Frequency f) {
transmitter_model.set_target_frequency(f);
bpsk_frequency = f;
switch (f) {
case BPSK_FREQUENCY_HAM:
bpsk_channel = (uint8_t)BpskChannel::HAM;
break;
case BPSK_FREQUENCY_B:
bpsk_channel = (uint8_t)BpskChannel::B;
break;
case BPSK_FREQUENCY_C:
bpsk_channel = (uint8_t)BpskChannel::C;
break;
case BPSK_FREQUENCY_F:
bpsk_channel = (uint8_t)BpskChannel::F;
break;
case BPSK_FREQUENCY_G:
bpsk_channel = (uint8_t)BpskChannel::G;
break;
case BPSK_FREQUENCY_J:
bpsk_channel = (uint8_t)BpskChannel::J;
break;
case BPSK_FREQUENCY_K:
bpsk_channel = (uint8_t)BpskChannel::K;
break;
case BPSK_FREQUENCY_N:
bpsk_channel = (uint8_t)BpskChannel::N;
break;
case BPSK_FREQUENCY_O:
bpsk_channel = (uint8_t)BpskChannel::O;
break;
default:
bpsk_channel = (uint8_t)BpskChannel::MANUAL;
manual_bpsk_frequency = bpsk_frequency;
break;
}
// Actual frequency change will be done by options_bpsk_channel.on_change()
options_bpsk_channel.set_by_value(bpsk_channel);
set_dirty();
};
};
+87 -15
View File
@@ -37,8 +37,26 @@
#define BEACON_HEXA_HALF_SIZE 18
#define BEACON_SIZE 18
#define AM_TEST_FREQUENCY 121375000
#define AM_REAL_FREQUENCY 121500000
#define BPSK_FREQUENCY_HAM 433025000
#define BPSK_FREQUENCY_B 406025000
#define BPSK_FREQUENCY_C 406028000
#define BPSK_FREQUENCY_F 406037000
#define BPSK_FREQUENCY_G 406040000
#define BPSK_FREQUENCY_J 406049000
#define BPSK_FREQUENCY_K 406052000
#define BPSK_FREQUENCY_N 406061000
#define BPSK_FREQUENCY_O 406064000
namespace ui::external_app::epirb_tx {
enum class BeaconMode {
FILE = 0,
MODE_MANUAL = 1
};
enum class BeaconType {
EPIRB = 0,
ELT = 1,
@@ -51,6 +69,25 @@ enum class BeaconProtocol {
NATIONAL = 2
};
enum class AmChannel {
TEST = 0,
REAL = 1,
MANUAL = 2
};
enum class BpskChannel {
HAM = 0,
B = 1,
C = 2,
F = 3,
G = 4,
J = 5,
K = 6,
N = 7,
O = 8,
MANUAL = 10
};
struct Location {
std::string locator;
bool south;
@@ -95,6 +132,8 @@ class EPIRBTXAppView : public View {
std::string frame_to_hex_string(bool start);
void generate_frame(BeaconParams params);
void update_frame(bool updateConfig = true);
void update_bpsk_frequency();
void update_am_transmission();
void update_mode();
void update_location(bool updateLocatorField = true);
@@ -114,25 +153,33 @@ class EPIRBTXAppView : public View {
// Frequency of the transmitter before starting the app (used to restore frequency when leaving)
rf::Frequency original_frequency{0};
// Frequency of the AM emergency signal
rf::Frequency am_frequency{121500000};
rf::Frequency am_frequency{AM_TEST_FREQUENCY};
// Frequency of the 406 MHz BPSK signal
rf::Frequency bpsk_frequency{406025000};
rf::Frequency bpsk_frequency{BPSK_FREQUENCY_HAM};
// Selected am channel
uint8_t am_channel{(uint8_t)AmChannel::TEST};
// Selected bpsk channel
uint8_t bpsk_channel{(uint8_t)BpskChannel::HAM};
// Manual AM frequency value
rf::Frequency manual_am_frequency{AM_TEST_FREQUENCY};
// Manual BPSK frequency value
rf::Frequency manual_bpsk_frequency{BPSK_FREQUENCY_HAM};
// True when using a beacon from the BEACONS.TXT file
bool mode_file{true};
bool mode_file{false};
// True when looping on sending beacons is enabled
bool loop_enabled{true};
// True if AM emergency signal transmission is enabled
bool am_enabled{true};
// True if we want to send a new frame each time the user changes the current beacon
bool send_on_change{false};
bool send_on_change{true};
// The current locator string
std::string locator{"JN03RO"};
// The delay between each frame when on loop mode
uint32_t delay{50};
uint8_t beacon_type{0};
uint8_t beacon_type{(uint8_t)BeaconType::EPIRB};
// Currently selected beacon protocol
uint8_t beacon_protocol{0};
uint8_t beacon_protocol{(uint8_t)BeaconProtocol::USER};
// Currently selected beacon country
uint32_t beacon_country{227};
// Current beacon's internal state (true for internal location system)
@@ -150,6 +197,8 @@ class EPIRBTXAppView : public View {
{"sbeacon"sv, &selected_beacon},
{"amfreq"sv, &am_frequency},
{"bpskfreq"sv, &bpsk_frequency},
{"amchan"sv, &am_channel},
{"bpskchan"sv, &bpsk_channel},
{"loop"sv, &loop_enabled},
{"delay"sv, &delay},
{"file"sv, &mode_file},
@@ -166,6 +215,8 @@ class EPIRBTXAppView : public View {
uint32_t last_frame_time{0};
// True when transmission is enabled
bool transmitting{false};
// True when transmitting a BPSK frame
bool transmitting_bpsk{false};
// True when currently looping on sending beacons
bool loop{false};
@@ -179,7 +230,9 @@ class EPIRBTXAppView : public View {
{{UI_POS_X(0), UI_POS_Y(0)}, "Source:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(6)}, "Frame:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(10)}, "Next frame in s.", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(12)}, "AM frequency MHz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(12)}, "AM frequency: MHz", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(14)}, "AM chan.:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(0), UI_POS_Y(15)}, "BPSK chan.:", Theme::getInstance()->fg_light->foreground},
{{UI_POS_X(17), UI_POS_Y(9)}, "s.", Theme::getInstance()->fg_light->foreground}};
// For file mode
@@ -231,15 +284,15 @@ class EPIRBTXAppView : public View {
OptionsField options_beacon_type{
{UI_POS_X(9), UI_POS_Y(1)},
7,
{{"EPIRB", 0},
{"ELT", 1},
{"PLB", 2}}};
{{"EPIRB", (uint8_t)BeaconType::EPIRB},
{"ELT", (uint8_t)BeaconType::ELT},
{"PLB", (uint8_t)BeaconType::PLB}}};
OptionsField options_beacon_protocol{
{UI_POS_X(9 + 7), UI_POS_Y(1)},
30,
{{"User", 0},
{"Standard", 1},
{"National", 2}}};
{{"User", (uint8_t)BeaconProtocol::USER},
{"Standard", (uint8_t)BeaconProtocol::STANDARD},
{"National", (uint8_t)BeaconProtocol::NATIONAL}}};
OptionsField options_beacon_country{
{UI_POS_X(9), UI_POS_Y(2)},
7,
@@ -261,8 +314,8 @@ class EPIRBTXAppView : public View {
OptionsField options_mode{
{UI_POS_X(7), UI_POS_Y(0)},
30,
{{"File (BEACONS.TXT)", 0},
{"Manual (Editor)", 1}}};
{{"File (BEACONS.TXT)", (uint8_t)BeaconMode::FILE},
{"Manual (Editor)", (uint8_t)BeaconMode::MODE_MANUAL}}};
// Frame content
Text text_frame{
@@ -305,6 +358,25 @@ class EPIRBTXAppView : public View {
"START"};
const Style& style_tx_start = *Theme::getInstance()->fg_green;
const Style& style_tx_stop = *Theme::getInstance()->fg_red;
OptionsField options_am_channel{
{UI_POS_X(11), UI_POS_Y(14)},
20,
{{"121.375 MHz (Test)", 0},
{"121.500 MHz /!\\Real", 1},
{"Manual", 2}}};
OptionsField options_bpsk_channel{
{UI_POS_X(11), UI_POS_Y(15)},
20,
{{"433.025 MHz (Ham)", (uint8_t)BpskChannel::HAM},
{"406.025 MHz (B)", (uint8_t)BpskChannel::B},
{"406.028 MHz (C)", (uint8_t)BpskChannel::C},
{"406.037 MHz (F)", (uint8_t)BpskChannel::F},
{"406.040 MHz (G)", (uint8_t)BpskChannel::G},
{"406.049 MHz (J)", (uint8_t)BpskChannel::J},
{"406.052 MHz (K)", (uint8_t)BpskChannel::K},
{"406.061 MHz (N)", (uint8_t)BpskChannel::N},
{"406.064 MHz (O)", (uint8_t)BpskChannel::O},
{"Manual", (uint8_t)BpskChannel::MANUAL}}};
// Transmitter view
TransmitterView tx_view{
+6 -6
View File
@@ -46,15 +46,15 @@ void EPIRBTXProcessor::execute(const buffer_c8_t& buffer) {
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
// Pre-count state: send carrier only during pre-count
bpsk_pre_count++;
re = i_neg;
im = q_neg;
re = i_carrier;
im = q_carrier;
} else if (bpsk_post_count > 0) {
// Post-count: send a negative phase carrier during post-count
// Post-count: send carrier only during post-count
bpsk_post_count++;
re = i_neg;
im = q_neg;
re = i_carrier;
im = q_carrier;
if (bpsk_post_count >= config_post_count) {
// End transmission here
byte_index = 0;
+5 -2
View File
@@ -60,14 +60,17 @@ class EPIRBTXProcessor : public BasebandProcessor {
// 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;
// BPSK parameters: Target phase +/-1.1 RAD as per COSPAS/SARSAT specifications
static constexpr float phase_rad = 1.1f;
// 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;
// I/Q values for carrier only
static constexpr int8_t i_carrier = 127;
static constexpr int8_t q_carrier = 0;
// COSPAS/SARSAT signal is manchester (2 states per bit) encoded 400 bit/sec
static const uint32_t samples_per_halfbit = TONES_SAMPLERATE / 400 / 2;