mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-11-18 03:00:46 -05:00
Epirb 406 v2 (#2761)
* Adding channel selection to EPIRB receiver * UI enhancement, Packet error check and color display of error * code formating
This commit is contained in:
parent
a6f886ad0a
commit
29bba4d0ea
2 changed files with 235 additions and 9 deletions
|
|
@ -29,6 +29,7 @@ using namespace portapack;
|
|||
|
||||
#include "rtc_time.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "message.hpp"
|
||||
|
||||
|
|
@ -53,6 +54,11 @@ EPIRBBeacon EPIRBDecoder::decode_packet(const baseband::Packet& packet) {
|
|||
data[i] = byte_val;
|
||||
}
|
||||
|
||||
// Perform BCH error detection and correction
|
||||
uint8_t error_count = 0;
|
||||
beacon.packet_status = perform_bch_check(data, error_count);
|
||||
beacon.error_count = error_count;
|
||||
|
||||
// Extract beacon ID (bits 26-85, 15 hex digits)
|
||||
beacon.beacon_id = 0;
|
||||
for (int i = 3; i < 11; i++) {
|
||||
|
|
@ -161,6 +167,98 @@ std::string EPIRBDecoder::decode_vessel_name(const std::array<uint8_t, 16>& /* d
|
|||
return "";
|
||||
}
|
||||
|
||||
PacketStatus EPIRBDecoder::perform_bch_check(std::array<uint8_t, 16>& data, uint8_t& error_count) {
|
||||
// Make a copy to detect changes
|
||||
std::array<uint8_t, 16> original_data = data;
|
||||
|
||||
// Calculate BCH syndrome
|
||||
uint32_t syndrome = calculate_bch_syndrome(data);
|
||||
|
||||
if (syndrome == 0) {
|
||||
// No errors detected
|
||||
error_count = 0;
|
||||
return PacketStatus::Valid;
|
||||
}
|
||||
|
||||
// Try to correct single-bit error
|
||||
if (correct_single_error(data, syndrome)) {
|
||||
// Successfully corrected
|
||||
error_count = count_bit_errors(original_data, data);
|
||||
return PacketStatus::Corrected;
|
||||
}
|
||||
|
||||
// Multiple errors or uncorrectable
|
||||
error_count = 255; // Indicate unknown error count
|
||||
return PacketStatus::Error;
|
||||
}
|
||||
|
||||
uint32_t EPIRBDecoder::calculate_bch_syndrome(const std::array<uint8_t, 16>& data) {
|
||||
// BCH(127,92,5) polynomial for EPIRB: x^35 + x^2 + x + 1
|
||||
// This is a simplified implementation - actual EPIRB uses BCH(63,21,6)
|
||||
uint32_t syndrome = 0;
|
||||
uint32_t polynomial = 0x80000007; // x^31 + x^2 + x + 1 (simplified)
|
||||
|
||||
// Process each byte of the data
|
||||
for (int i = 0; i < 14; i++) { // Only data bits, not parity
|
||||
uint32_t byte_val = data[i];
|
||||
for (int bit = 7; bit >= 0; bit--) {
|
||||
syndrome <<= 1;
|
||||
if (byte_val & (1 << bit)) {
|
||||
syndrome |= 1;
|
||||
}
|
||||
|
||||
// XOR with polynomial if MSB is set
|
||||
if (syndrome & 0x80000000) {
|
||||
syndrome ^= polynomial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XOR with parity bits
|
||||
syndrome ^= (data[14] << 8) | data[15];
|
||||
|
||||
return syndrome & 0xFFFF; // 16-bit syndrome
|
||||
}
|
||||
|
||||
bool EPIRBDecoder::correct_single_error(std::array<uint8_t, 16>& data, uint32_t syndrome) {
|
||||
// Simplified single-error correction
|
||||
// This is a basic implementation - real BCH correction is more complex
|
||||
|
||||
if (syndrome == 0) return true; // No error
|
||||
|
||||
// Look up table for single-bit error patterns (simplified)
|
||||
// In a real implementation, this would be a proper BCH syndrome table
|
||||
for (int byte_idx = 0; byte_idx < 14; byte_idx++) {
|
||||
for (int bit_idx = 0; bit_idx < 8; bit_idx++) {
|
||||
// Create test error pattern
|
||||
std::array<uint8_t, 16> test_data = data;
|
||||
test_data[byte_idx] ^= (1 << bit_idx);
|
||||
|
||||
// Check if this correction produces zero syndrome
|
||||
if (calculate_bch_syndrome(test_data) == 0) {
|
||||
// Found the error location, apply correction
|
||||
data[byte_idx] ^= (1 << bit_idx);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // Could not correct
|
||||
}
|
||||
|
||||
uint8_t EPIRBDecoder::count_bit_errors(const std::array<uint8_t, 16>& original, const std::array<uint8_t, 16>& corrected) {
|
||||
uint8_t count = 0;
|
||||
for (size_t i = 0; i < 16; i++) {
|
||||
uint8_t diff = original[i] ^ corrected[i];
|
||||
// Count set bits in diff
|
||||
while (diff) {
|
||||
count += diff & 1;
|
||||
diff >>= 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void EPIRBLogger::on_packet(const EPIRBBeacon& beacon) {
|
||||
std::string entry = "EPIRB," +
|
||||
to_string_dec_uint(beacon.beacon_id, 15, '0') + "," +
|
||||
|
|
@ -174,7 +272,9 @@ void EPIRBLogger::on_packet(const EPIRBBeacon& beacon) {
|
|||
entry += ",";
|
||||
}
|
||||
|
||||
entry += "," + to_string_dec_uint(beacon.country_code) + "\n";
|
||||
entry += "," + to_string_dec_uint(beacon.country_code) + "," +
|
||||
format_packet_status(beacon.packet_status) + "," +
|
||||
to_string_dec_uint(beacon.error_count) + "\n";
|
||||
|
||||
log_file.write_entry(beacon.timestamp, entry);
|
||||
}
|
||||
|
|
@ -221,6 +321,32 @@ std::string format_emergency_type(EmergencyType type) {
|
|||
}
|
||||
}
|
||||
|
||||
std::string format_packet_status(PacketStatus status) {
|
||||
switch (status) {
|
||||
case PacketStatus::Valid:
|
||||
return "OK";
|
||||
case PacketStatus::Corrected:
|
||||
return "CORR";
|
||||
case PacketStatus::Error:
|
||||
return "ERR";
|
||||
default:
|
||||
return "UNK";
|
||||
}
|
||||
}
|
||||
|
||||
ui::Color get_packet_status_color(PacketStatus status) {
|
||||
switch (status) {
|
||||
case PacketStatus::Valid:
|
||||
return ui::Color::green();
|
||||
case PacketStatus::Corrected:
|
||||
return ui::Color::yellow();
|
||||
case PacketStatus::Error:
|
||||
return ui::Color::red();
|
||||
default:
|
||||
return ui::Color::white();
|
||||
}
|
||||
}
|
||||
|
||||
EPIRBBeaconDetailView::EPIRBBeaconDetailView(ui::NavigationView& nav) {
|
||||
add_children({&button_done,
|
||||
&button_see_map});
|
||||
|
|
@ -297,6 +423,15 @@ void EPIRBBeaconDetailView::paint(ui::Painter& painter) {
|
|||
draw_cursor = draw_field(painter, {draw_cursor, {200, 16}}, s,
|
||||
"Time", to_string_datetime(beacon_.timestamp, HMS))
|
||||
.location();
|
||||
|
||||
// Show packet status with appropriate color
|
||||
std::string status_text = format_packet_status(beacon_.packet_status);
|
||||
if (beacon_.error_count > 0 && beacon_.packet_status == PacketStatus::Corrected) {
|
||||
status_text += " (" + to_string_dec_uint(beacon_.error_count) + " err)";
|
||||
}
|
||||
draw_cursor = draw_field(painter, {draw_cursor, {200, 16}}, s,
|
||||
"Status", status_text)
|
||||
.location();
|
||||
}
|
||||
|
||||
ui::Rect EPIRBBeaconDetailView::draw_field(
|
||||
|
|
@ -318,6 +453,7 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
||||
|
||||
add_children({&label_frequency,
|
||||
&options_frequency,
|
||||
&field_rf_amp,
|
||||
&field_lna,
|
||||
&field_vga,
|
||||
|
|
@ -328,6 +464,7 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||
&label_beacons_count,
|
||||
&label_latest,
|
||||
&text_latest_info,
|
||||
&label_packet_stats,
|
||||
&console,
|
||||
&button_map,
|
||||
&button_clear,
|
||||
|
|
@ -345,11 +482,16 @@ EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|||
this->on_toggle_log();
|
||||
};
|
||||
|
||||
options_frequency.on_change = [this](size_t, ui::OptionsField::value_t v) {
|
||||
receiver_model.set_target_frequency(v);
|
||||
};
|
||||
options_frequency.set_by_value(receiver_model.target_frequency());
|
||||
|
||||
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
||||
this->on_tick_second();
|
||||
};
|
||||
|
||||
// Configure receiver for 406.028 MHz EPIRB frequency
|
||||
// Configure receiver for default EPIRB frequency (406.028 MHz)
|
||||
receiver_model.set_target_frequency(406028000);
|
||||
receiver_model.set_rf_amp(true);
|
||||
receiver_model.set_lna(32);
|
||||
|
|
@ -386,7 +528,7 @@ void EPIRBAppView::paint(ui::Painter& /* painter */) {
|
|||
}
|
||||
|
||||
void EPIRBAppView::focus() {
|
||||
field_rf_amp.focus();
|
||||
options_frequency.focus();
|
||||
}
|
||||
|
||||
void EPIRBAppView::on_packet(const baseband::Packet& packet) {
|
||||
|
|
@ -400,6 +542,20 @@ void EPIRBAppView::on_packet(const baseband::Packet& packet) {
|
|||
|
||||
void EPIRBAppView::on_beacon_decoded(const EPIRBBeacon& beacon) {
|
||||
beacons_received++;
|
||||
|
||||
// Track packet statistics
|
||||
switch (beacon.packet_status) {
|
||||
case PacketStatus::Valid:
|
||||
packets_valid++;
|
||||
break;
|
||||
case PacketStatus::Corrected:
|
||||
packets_corrected++;
|
||||
break;
|
||||
case PacketStatus::Error:
|
||||
packets_error++;
|
||||
break;
|
||||
}
|
||||
|
||||
recent_beacons.push_back(beacon);
|
||||
|
||||
// Keep only last 50 beacons
|
||||
|
|
@ -415,11 +571,34 @@ void EPIRBAppView::on_beacon_decoded(const EPIRBBeacon& beacon) {
|
|||
logger->on_packet(beacon);
|
||||
}
|
||||
|
||||
// Display in console with full details
|
||||
// Display in console with full details and colored status
|
||||
std::string beacon_info = format_beacon_summary(beacon);
|
||||
if (beacon.emergency_type != EmergencyType::Other) {
|
||||
beacon_info += " [" + format_emergency_type(beacon.emergency_type) + "]";
|
||||
}
|
||||
|
||||
// Add colored status indicator
|
||||
std::string status_color;
|
||||
switch (beacon.packet_status) {
|
||||
case PacketStatus::Valid:
|
||||
status_color = STR_COLOR_GREEN;
|
||||
break;
|
||||
case PacketStatus::Corrected:
|
||||
status_color = STR_COLOR_YELLOW;
|
||||
break;
|
||||
case PacketStatus::Error:
|
||||
status_color = STR_COLOR_RED;
|
||||
break;
|
||||
default:
|
||||
status_color = STR_COLOR_WHITE;
|
||||
break;
|
||||
}
|
||||
|
||||
beacon_info += " [" + status_color + format_packet_status(beacon.packet_status) + STR_COLOR_WHITE + "]";
|
||||
if (beacon.error_count > 0 && beacon.packet_status == PacketStatus::Corrected) {
|
||||
beacon_info += " (" + to_string_dec_uint(beacon.error_count) + "e)";
|
||||
}
|
||||
|
||||
console.write(beacon_info + "\n");
|
||||
}
|
||||
|
||||
|
|
@ -463,6 +642,9 @@ void EPIRBAppView::on_show_map() {
|
|||
void EPIRBAppView::on_clear_beacons() {
|
||||
recent_beacons.clear();
|
||||
beacons_received = 0;
|
||||
packets_valid = 0;
|
||||
packets_corrected = 0;
|
||||
packets_error = 0;
|
||||
console.clear(true);
|
||||
update_display();
|
||||
}
|
||||
|
|
@ -490,6 +672,13 @@ void EPIRBAppView::on_tick_second() {
|
|||
void EPIRBAppView::update_display() {
|
||||
label_beacons_count.set("Beacons: " + to_string_dec_uint(beacons_received));
|
||||
|
||||
// Update packet statistics display
|
||||
std::string stats = std::string("Stats: ") +
|
||||
STR_COLOR_GREEN + to_string_dec_uint(packets_valid) + "OK " +
|
||||
STR_COLOR_YELLOW + to_string_dec_uint(packets_corrected) + "CORR " +
|
||||
STR_COLOR_RED + to_string_dec_uint(packets_error) + "ERR" + STR_COLOR_WHITE;
|
||||
label_packet_stats.set(stats);
|
||||
|
||||
if (!recent_beacons.empty()) {
|
||||
const auto& latest = recent_beacons.back();
|
||||
text_latest_info.set(format_beacon_summary(latest));
|
||||
|
|
@ -504,6 +693,9 @@ std::string EPIRBAppView::format_beacon_summary(const EPIRBBeacon& beacon) {
|
|||
summary += " " + format_location(beacon.location);
|
||||
}
|
||||
|
||||
// Add status indicator for summary display
|
||||
summary += " " + format_packet_status(beacon.packet_status);
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ struct EPIRBLocation {
|
|||
: latitude(lat), longitude(lon), valid(true) {}
|
||||
};
|
||||
|
||||
enum class PacketStatus : uint8_t {
|
||||
Valid = 0,
|
||||
Corrected = 1,
|
||||
Error = 2
|
||||
};
|
||||
|
||||
struct EPIRBBeacon {
|
||||
uint32_t beacon_id;
|
||||
BeaconType beacon_type;
|
||||
|
|
@ -87,9 +93,11 @@ struct EPIRBBeacon {
|
|||
std::string vessel_name;
|
||||
rtc::RTC timestamp;
|
||||
uint32_t sequence_number;
|
||||
PacketStatus packet_status;
|
||||
uint8_t error_count;
|
||||
|
||||
EPIRBBeacon()
|
||||
: beacon_id(0), beacon_type(BeaconType::Other), emergency_type(EmergencyType::Other), location(), country_code(0), vessel_name(), timestamp(), sequence_number(0) {}
|
||||
: beacon_id(0), beacon_type(BeaconType::Other), emergency_type(EmergencyType::Other), location(), country_code(0), vessel_name(), timestamp(), sequence_number(0), packet_status(PacketStatus::Error), error_count(0) {}
|
||||
};
|
||||
|
||||
class EPIRBDecoder {
|
||||
|
|
@ -102,6 +110,12 @@ class EPIRBDecoder {
|
|||
static EmergencyType decode_emergency_type(uint8_t emergency_bits);
|
||||
static uint32_t decode_country_code(const std::array<uint8_t, 16>& data);
|
||||
static std::string decode_vessel_name(const std::array<uint8_t, 16>& data);
|
||||
|
||||
// BCH error correction methods
|
||||
static PacketStatus perform_bch_check(std::array<uint8_t, 16>& data, uint8_t& error_count);
|
||||
static uint32_t calculate_bch_syndrome(const std::array<uint8_t, 16>& data);
|
||||
static bool correct_single_error(std::array<uint8_t, 16>& data, uint32_t syndrome);
|
||||
static uint8_t count_bit_errors(const std::array<uint8_t, 16>& original, const std::array<uint8_t, 16>& corrected);
|
||||
};
|
||||
|
||||
class EPIRBLogger {
|
||||
|
|
@ -119,6 +133,8 @@ class EPIRBLogger {
|
|||
// Forward declarations of formatting functions
|
||||
std::string format_beacon_type(BeaconType type);
|
||||
std::string format_emergency_type(EmergencyType type);
|
||||
std::string format_packet_status(PacketStatus status);
|
||||
ui::Color get_packet_status_color(PacketStatus status);
|
||||
|
||||
class EPIRBBeaconDetailView : public ui::View {
|
||||
public:
|
||||
|
|
@ -178,11 +194,22 @@ class EPIRBAppView : public ui::View {
|
|||
|
||||
EPIRBBeaconDetailView beacon_detail_view{nav_};
|
||||
|
||||
static constexpr auto header_height = 3 * 16;
|
||||
static constexpr auto header_height = 4 * 16;
|
||||
|
||||
ui::Text label_frequency{
|
||||
{0 * 8, 0 * 16, 10 * 8, 1 * 16},
|
||||
"406.028 MHz"};
|
||||
{0 * 8, 0 * 16, 4 * 8, 1 * 16},
|
||||
"Freq"};
|
||||
|
||||
ui::OptionsField options_frequency{
|
||||
{5 * 8, 0 * 16},
|
||||
7,
|
||||
{
|
||||
{"406.028", 406028000},
|
||||
{"406.025", 406025000},
|
||||
{"406.037", 406037000},
|
||||
{"433.025", 433025000},
|
||||
{"144.875", 144875000},
|
||||
}};
|
||||
|
||||
ui::RFAmpField field_rf_amp{
|
||||
{13 * 8, 0 * 16}};
|
||||
|
|
@ -211,6 +238,10 @@ class EPIRBAppView : public ui::View {
|
|||
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
|
||||
"Beacons: 0"};
|
||||
|
||||
ui::Text label_packet_stats{
|
||||
{0 * 8, 3 * 16, 29 * 8, 1 * 16},
|
||||
""};
|
||||
|
||||
// Latest beacon info display
|
||||
ui::Text label_latest{
|
||||
{0 * 8, 2 * 16, 8 * 8, 1 * 16},
|
||||
|
|
@ -222,7 +253,7 @@ class EPIRBAppView : public ui::View {
|
|||
|
||||
// Beacon list
|
||||
ui::Console console{
|
||||
{0, 3 * 16, 240, 168}};
|
||||
{0, 4 * 16, 240, 152}};
|
||||
|
||||
ui::Button button_map{
|
||||
{0, 224, 60, 24},
|
||||
|
|
@ -238,6 +269,9 @@ class EPIRBAppView : public ui::View {
|
|||
|
||||
SignalToken signal_token_tick_second{};
|
||||
uint32_t beacons_received = 0;
|
||||
uint32_t packets_valid = 0;
|
||||
uint32_t packets_corrected = 0;
|
||||
uint32_t packets_error = 0;
|
||||
|
||||
MessageHandlerRegistration message_handler_packet{
|
||||
Message::ID::EPIRBPacket,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue