mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-31 20:09:04 +00:00
5aecd4097e
* 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
356 lines
11 KiB
C++
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__*/ |