From 569bcaad222641082e615c4694ef3950c85eecc0 Mon Sep 17 00:00:00 2001 From: Arne Luehrs Date: Thu, 11 Jun 2026 11:01:54 +0200 Subject: [PATCH] Epirb afc wide capture (#3213) * EPIRB RX: widen AFC capture range to +/-5 kHz The AFC estimate was only applied after carrier lock, so carrier acquisition ran on raw phase deltas and could only tolerate offsets of a few hundred Hz before the 0.6/0.7/1.6 rad detection thresholds (on the 12-sample accumulator) broke down. Track the carrier offset continuously in the IDLE state with a first-order loop (AFC_TRACK_ALPHA), so the de-biased accumulator self-centers for any offset within the discriminator Nyquist (~24 kHz) *before* the thresholds run. ALPHA = 0.005 pulls a +/-5 kHz offset under the 0.6 rad lock threshold in ~11 ms, well inside the 160 ms preamble / 80 ms stability window. The IDLE rise-detect threshold is also made symmetric (fabsf) now that the bias is removed. Also add the missing include to test_convert.cpp so the application_test suite compiles under the current toolchain. Verified: baseband_epirb_rx.elf builds (flash 53%, RAM 12%) and baseband_test passes. --- .../external/epirb_rx/ui_beaconlist.cpp | 27 +- .../external/epirb_rx/ui_epirb_rx.cpp | 1216 +++++++++-------- .../external/epirb_rx/ui_epirb_rx.hpp | 646 ++++----- firmware/application/ui/ui_channel.cpp | 6 +- firmware/application/ui/ui_channel.hpp | 8 + firmware/baseband/proc_epirb.cpp | 610 +++++---- firmware/baseband/proc_epirb.hpp | 564 ++++---- firmware/test/application/test_convert.cpp | 1 + 8 files changed, 1599 insertions(+), 1479 deletions(-) diff --git a/firmware/application/external/epirb_rx/ui_beaconlist.cpp b/firmware/application/external/epirb_rx/ui_beaconlist.cpp index ea4e7cdd0..dffb81a9b 100644 --- a/firmware/application/external/epirb_rx/ui_beaconlist.cpp +++ b/firmware/application/external/epirb_rx/ui_beaconlist.cpp @@ -41,29 +41,26 @@ void BeaconUIList::paint(Painter& painter) { auto base_style = Theme::getInstance()->bg_darkest; for (auto offset = 0u; offset < BEACON_HISTORY_SIZE; ++offset) { - // The whole frame needs to be cleared so every line 'slot' - // is redrawn even when `text` just left empty. - auto text = std::string{}; auto index = start_index_ + offset; - auto line_position = rect.location() + Point{0, 1 + (int)offset * char_height}; - auto is_selected = offset == selected_index_; - auto style = base_style; if (index < db_->size()) { + auto line_position = rect.location() + Point{0, 1 + (int)offset * char_height}; + auto is_selected = (offset == selected_index_); + auto style = base_style; + // Get beacon entry and format it's summary auto& entry = db_->get_beacon(index); char buffer[64]; entry.formatSummary(buffer, true); - text = std::string(buffer); + + if (index == db_->get_current_beacon_index()) + // If this is the currently displayed beacon change color + style = Theme::getInstance()->bg_medium; + + // Draw entry line using stack buffer directly to avoid heap allocation + painter.draw_string( + line_position, (is_selected ? style->invert() : *style), buffer); } - - if (index == db_->get_current_beacon_index()) - // If this is the currently displayed beacon change color - style = Theme::getInstance()->bg_medium; - - // Draw entry line - painter.draw_string( - line_position, (is_selected ? style->invert() : *style), text); } // Draw a bounding rectangle when focused. diff --git a/firmware/application/external/epirb_rx/ui_epirb_rx.cpp b/firmware/application/external/epirb_rx/ui_epirb_rx.cpp index 84aa5540f..82107e2dc 100644 --- a/firmware/application/external/epirb_rx/ui_epirb_rx.cpp +++ b/firmware/application/external/epirb_rx/ui_epirb_rx.cpp @@ -1,600 +1,616 @@ -/* - * Copyright (C) 2024 EPIRB Decoder Implementation - * 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 "baseband_api.hpp" -#include "portapack_persistent_memory.hpp" -#include "file_path.hpp" - -#include "ui_epirb_rx.hpp" - -using namespace portapack; - -#include "rtc_time.hpp" -#include "string_format.hpp" -#include "ui.hpp" - -#include "message.hpp" -#include "resources.hpp" - -namespace ui::external_app::epirb_rx { - -// URL templates -#define MAPS_URL_TEMPLATE "https://www.google.com/maps/search/?api=1&query=%s%%2C%s" -#define BEACON_URL_TEMPALTE "https://decoder2.herokuapp.com/decoded/" - -#ifndef DISABLE_COUNTRY_CACHE -int CountryManager::cache_count = 0; -Country CountryManager::cache[16]; -#endif - -// ResourceManager class -ResourceManager* ResourceManager::current = nullptr; - -void ResourceManager::destroy() { - if (current != nullptr) - delete current; - current = nullptr; -} - -ResourceManager* ResourceManager::getInstance() { - if (current == nullptr) current = new ResourceManager(); - return ResourceManager::current; -} - -// TextArea class -TextArea::TextArea( - Rect parent_rect) - : Widget{parent_rect} { -} - -void TextArea::paint(Painter& painter) { - const auto rect = screen_rect(); - const Style& s = has_focus() ? style().invert() : style(); - - painter.fill_rectangle(rect, s.background); - // We use \t as line separator since \n is used in STR_COLOR_GREEN - auto rows = split_string(content, '\t'); - - const int line_height = s.font.line_height(); - size_t line_idx = 0; - for (auto row : rows) { - painter.draw_string(rect.location() + Point(0, line_idx * line_height), s, row); - line_idx++; - } -} - -void TextArea::set_content(std::string_view value) { - content = std::string{value}; - set_dirty(); -} - -#ifdef RESET_TIMER -bool TextArea::on_key(const KeyEvent key) { - if (key == KeyEvent::Select && on_select) { - on_select(*this); - set_dirty(); - return true; - } - return false; -} -#endif - -// EPIRBAppView class -void EPIRBAppView::decode_packet(const baseband::Packet& packet, Beacon& beacon) { - // Convert packet bits to byte array for easier processing - uint8_t data[BEACON_DATA_SIZE]{}; - for (size_t i = 0; i < std::min(packet.size() / 8, (size_t)BEACON_DATA_SIZE); i++) { - uint8_t byte_val = 0; - for (int bit = 0; bit < 8 && (i * 8 + bit) < packet.size(); bit++) { - if (packet[i * 8 + bit]) { - byte_val |= (1 << (7 - bit)); - } - } - data[i] = byte_val; - } - beacon.setFrame(data); - // Set timestamp - rtc::RTC datetime; - rtcGetTime(&RTCD1, &datetime); - beacon.date = datetime; -} - -#ifdef LOGGER -void EPIRBLogger::on_packet(Beacon& beacon) { - std::string entry = std::string(beacon.getType()) + "," + - beacon.hexId + "," + - beacon.getProtocolName(); // + ","; - // to_string_dec_uint(static_cast(beacon.emergency_type)) + ","; - /*if (!beacon.location.isUnknown()) { - entry += beacon.location.toString(Location::LocationFormat::DECIMAL); - } else { - entry += ","; - } - entry += "," + beacon.country.toString() + "," + - beacon.getSatus() + "\n";*/ - log_file.write_entry(beacon.date, entry); -} -#endif - -// EPIRBDetailView class -EPIRBDetailView::EPIRBDetailView( - Rect parent_rect, - EPIRBAppView& parent) - : View(parent_rect), parent_app(parent) { - add_children({&text_beacon}); - set_focusable(true); -} - -#ifdef DETAIL_TAB_BEACON_SEL -bool EPIRBDetailView::on_encoder(EncoderEvent delta) { - // Change position in the list according to encoder - int32_t size = (int32_t)parent_app.beacon_db.size(); - if (size == 0) return true; - // Get current index - int32_t current_index = (int32_t)parent_app.beacon_db.get_current_beacon_index(); - // Compute new index with wrap around - int32_t new_index = (current_index + delta + size) % size; - // Set new index - parent_app.beacon_db.set_current_beacon(new_index % size); - parent_app.on_beacon_change(); - return true; -} -#endif - -// Append "CYAN