Files
mayhem-firmware/firmware/application/external/epirb_tx/beacon.hpp
Frederic BORRY 5aecd4097e 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
2026-03-12 22:05:42 +13:00

356 lines
11 KiB
C++

/*
* 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 __BEACON_H__
#define __BEACON_H__
#include "portapack.hpp"
#include "ui_epirb_tx.hpp"
#include "location.hpp"
namespace ui::external_app::epirb_tx {
/**
* Get bits frop the provided frame
* @param data the frame data
* @param startBit the start bit number (one based to match documentation)
* @param endBit the end bit number (one based to match documentation)
* @return the selected bits (max 64)
*/
static uint64_t get_bits(uint8_t* data, int startBit, int endBit) {
uint64_t result = 0;
// 0 bases bit count
startBit--;
int numBits = endBit - startBit;
// get a pointer to the starting byte...
const uint8_t* pData = &(data[startBit / 8]);
uint8_t b = *pData;
// calculate the starting bit within that byte...
int bitOffset = 7 - (startBit % 8);
// iterate for the desired number of bits...
for (int i = 0; i < numBits; ++i) {
// make room for the next bit...
result <<= 1;
// copy the bit...
result |= ((b >> bitOffset) & 0x01);
// reached the end of the current byte?
if (--bitOffset < 0) {
b = *(++pData); // go to the next byte...
bitOffset = 7; // restart at the first bit in that byte...
}
}
// all done...
return result;
}
/**
* Compute a BCH code
* @param frame the frame data
* @param startBit the bit to start BCH calculation
* @param endBit the bit to stop calculation
* @param poly the BCH polynome
* @param polyLength the BCH polynome length
* @return the BCH code
*/
static uint64_t compute_bch(uint8_t* frame, int startBit, int endBit, unsigned long poly, int polyLength) { // Length of data to be checked (not including the BCH code)
int dataLength = endBit - startBit + 1;
// Total lengh (including the BCH code that will be padded to zeros (BCH code length is polyLengh-1))
int totalLength = dataLength + polyLength - 1;
// Start with the first polyLength bits
uint64_t result = get_bits(frame, startBit, startBit + polyLength - 1);
for (int i = polyLength; i <= totalLength; i++) { // Iterate on each bit after the first polyLength batch
bool firstBit = result >> (polyLength - 1);
if (firstBit) { // We have a leading 1 => xor the result with the poly
result = result ^ poly;
}
if (i < totalLength) { // Move to next bit
result = result << 1;
if (i < dataLength) { // Append next bit
result |= get_bits(frame, startBit + i, startBit + i);
} // else : 0 padding after data length
}
}
return result;
}
// 21 bits BCH polynomial
#define BCH_21_POLYNOMIAL 0b1001101101100111100011UL
#define BCH_21_POLY_LENGTH 22
// 12 bits BCH polynomial
#define BCH_12_POLYNOMIAL 0b1010100111001UL
#define BCH_12_POLY_LENGTH 13
/**
* Combyte BCH1 code for the provided frame
*/
static uint64_t compute_bch1(uint8_t* frame) {
return compute_bch(frame, 25, 85, BCH_21_POLYNOMIAL, BCH_21_POLY_LENGTH);
}
/**
* Combyte BCH2 code for the provided frame
*/
static uint64_t compute_bch2(uint8_t* frame) {
return compute_bch(frame, 107, 132, BCH_12_POLYNOMIAL, BCH_12_POLY_LENGTH);
}
/**
* Set a bit in the provided frame
* @param buf the frame
* @param bit the position of the bit to set (0 based)
* @param v the bit value
*/
static void set_bit(uint8_t* buf, int bit, bool v) {
int byte = bit >> 3;
int off = 7 - (bit & 7);
if (v)
buf[byte] |= (1 << off);
else
buf[byte] &= ~(1 << off);
}
/**
* Push bits to the provided frame
* @param buf the frame
* @param pos the current frame possition (in/out, will be incremented during the opreration)
* @param v the bits to push (max 64)
* @param n the number of bits to push
*/
static void push_bits(uint8_t* buf, int& pos, uint64_t v, int n) {
for (int i = n - 1; i >= 0; i--)
set_bit(buf, pos++, (v >> i) & 1);
}
/**
* Convert a beacon to hex string representation
* @param frame the frame to convert
* @param start true for the first half of the frame, false for the second half
* @return the hex string representation of the specieid half of the frame
*/
std::string beacon_to_hex_string(const uint8_t* frame, bool start) {
static const char hex[] = "0123456789ABCDEF";
std::string out;
out.resize(18);
int offset = start ? 0 : 9;
for (int i = 0; i < 9; i++) {
uint8_t b = frame[offset + i];
out[i * 2] = hex[b >> 4];
out[i * 2 + 1] = hex[b & 0x0F];
}
return out;
}
/**
* Generate a beacon in the provided buffer
* @param frame the buffer to generate the frame in
* @param params the beacon parameters
* @return the size of the generated frame
*/
size_t generate_beacon(uint8_t* frame, const BeaconParams& params) {
// Clear the content of the frame
memset(frame, 0, 18);
int pos = 0;
uint32_t deg, min, sec;
// bit sync
for (int i = 0; i < 15; i++)
push_bits(frame, pos, 1, 1);
// frame sync
push_bits(frame, pos, params.is_test ? 0b011010000 : 0b000101111, 9);
// PDF-1 (Protexted Data field 1)
int pdf1_start = pos;
push_bits(frame, pos, 1, 1); // format flag (long)
bool is_user = (params.protocol == BeaconProtocol::USER);
bool is_standard = (params.protocol == BeaconProtocol::STANDARD);
push_bits(frame, pos, is_user, 1); // protocol flag
push_bits(frame, pos, params.country, 10); // country code
switch (params.type) {
case BeaconType::EPIRB:
if (is_user)
push_bits(frame, pos, 0b010, 3);
else if (is_standard)
push_bits(frame, pos, 0b0010, 4);
else
push_bits(frame, pos, 0b1010, 4);
break;
case BeaconType::PLB:
if (is_user)
push_bits(frame, pos, 0b011, 3);
else if (is_standard)
push_bits(frame, pos, 0b0111, 4);
else
push_bits(frame, pos, 0b1011, 4);
break;
default:
case BeaconType::ELT:
if (is_user)
push_bits(frame, pos, 0b001, 3);
else if (is_standard)
push_bits(frame, pos, 0b0011, 4);
else
push_bits(frame, pos, 0b1000, 4);
break;
}
// Fill the rest of PDF 1 with zeros
while (pos < pdf1_start + 61)
push_bits(frame, pos, 0, 1);
if (is_user) {
// User Location Protocol: no position in PDF1
if (params.type == BeaconType::PLB) {
// Set bits for PLB
set_bit(frame, 40 - 1, 1);
set_bit(frame, 41 - 1, 1);
set_bit(frame, 42 - 1, 0);
}
set_bit(frame, 85 - 1, params.has_121_5);
} else if (is_standard) {
// Standard Location Protocol
// North / south
set_bit(frame, 65 - 1, params.location.south);
// E / W
set_bit(frame, 75 - 1, params.location.west);
pos = 66 - 1;
// Latitude 1/4 degrees (9 bits)
deg = (params.location.lat_deg << 2) + (params.location.lat_min / 15);
push_bits(frame, pos, deg, 9);
pos = 76 - 1;
// Longitude 1/4 degrees (10 bits)
deg = (params.location.long_deg << 2) + (params.location.long_min / 15);
push_bits(frame, pos, deg, 10);
pos = pdf1_start + 61;
} else {
// National Location Protocol
// North / south
set_bit(frame, 59 - 1, params.location.south);
// E / W
set_bit(frame, 72 - 1, params.location.west);
pos = 60 - 1;
// Latitude degrees (7 bits)
push_bits(frame, pos, params.location.lat_deg, 7);
// Latitude min (5 bits, 2 min increment)
min = params.location.lat_min / 2;
push_bits(frame, pos, min, 5);
pos = 73 - 1;
// Longitude degrees (8 bits)
push_bits(frame, pos, params.location.long_deg, 8);
// Longiitude min (5 bits, 2 min increment)
min = params.location.long_min / 2;
push_bits(frame, pos, min, 5);
pos = pdf1_start + 61;
}
// Set BCH1
uint64_t bch1 = compute_bch1(frame);
push_bits(frame, pos, bch1, 21);
// PDF-2 (Protexted Data Field 2)
int pdf2_start = pos;
if (is_user) {
// User Location Protocol
push_bits(frame, pos, params.is_internal, 1);
// Latitude N/S
push_bits(frame, pos, params.location.south, 1);
// Latitude degrees (7 bits)
push_bits(frame, pos, params.location.lat_deg, 7);
// Latitude minutes (4 bits, 4 minutes precision)
min = params.location.lat_min / 4;
push_bits(frame, pos, min, 4);
// Longitude E/W
push_bits(frame, pos, params.location.west, 1);
// Longitude degrees (8 bits)
push_bits(frame, pos, params.location.long_deg, 8);
// Longitude minutes (4 bits, 4 minutes precision)
min = params.location.long_min / 4;
push_bits(frame, pos, min, 4);
} else if (is_standard) {
// Standard Location Protocol
push_bits(frame, pos, 0b1101, 4);
push_bits(frame, pos, params.is_internal, 1);
push_bits(frame, pos, params.has_121_5, 1);
// Latiitude
// +
push_bits(frame, pos, 1, 1);
// Min
min = params.location.lat_min % 15;
push_bits(frame, pos, min, 5);
// Sec (4 bits, 4 sec precision)
sec = params.location.lat_sec / 4;
push_bits(frame, pos, sec, 4);
// Longitude
// +
push_bits(frame, pos, 1, 1);
// Min
min = params.location.long_min % 15;
push_bits(frame, pos, min, 5);
// Sec (4 bits, 4 sec precision)
sec = params.location.long_sec / 4;
push_bits(frame, pos, sec, 4);
} else {
// National Location Protocol
push_bits(frame, pos, 0b1101, 4);
push_bits(frame, pos, params.is_internal, 1);
push_bits(frame, pos, params.has_121_5, 1);
// Lat
// +
push_bits(frame, pos, 1, 1);
// Min
push_bits(frame, pos, (params.location.lat_min % 2), 2);
// Sec (4 bits, 4 sec precision)
sec = params.location.lat_sec / 4;
push_bits(frame, pos, sec, 4);
// LLon
// +
push_bits(frame, pos, 1, 1);
// Min
push_bits(frame, pos, (params.location.long_min % 2), 2);
// Sec (4 bits, 4 sec precision)
sec = params.location.long_sec / 4;
push_bits(frame, pos, sec, 4);
}
while (pos < pdf2_start + 26)
push_bits(frame, pos, 0, 1);
// Compute BCH 2
uint64_t bch2 = compute_bch2(frame);
push_bits(frame, pos, bch2, 12);
return 18;
}
} // namespace ui::external_app::epirb_tx
#endif /*__BEACON_H__*/