mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-08-19 06:03:59 +00:00
c34a6940ca
* First step to EPIRB rx app fix * Added spike filtering on carrier detection for more robustness. * Created dedicated packet builder * First step to fixing UI. * First step to epirb-rx ui rework. * Made static methods static members * Code optim * Code cleanup + first step to new UI * Fixed display * Added BeaconList widget + BeaconDB * Epirb TX app memory optim * Fixed OOM when opening Map Display or Locator editor on EPIRB TX app * First step to tabs + code cleanup to free some space... * Added spectrum analyzer + fixed display * Fixed display refresh issue after leaving spectrum. * More code optim + first step to detail view. * Fixed float coord display. * Fixed protocol names * Fixed location display * Externalized country base to sd card + more code optim * Fixed country display * Fixed path * Added protocol description and resource manager * Added main/aux loc device * Removed specan to save space * First step to adding QR code * Used a custom TextArea component instead of Console * Fixed QR code display for map URL * Added beacon selection management + fixes * First step to map/data qr. * QR tab : added data + detail/map toggle * Removed sqhelch to save space * Removed audio to save space * Removed freq label to save space * Removed packet timestamp to save space * Moved frequency values to resource file * Fixed detection parameters to suite real beacons (slower phase shift time). * Fixed countdown field size * Fixed frequency list. * Fixed detail view on select. * First step to fix settings. * Fixed squelch for a 0-99 range. * Added bch code correction (single bit error). * More code optim, disable country cache, removed inline from location to reduce code size. * Removed inline, code formatting. * Added bch code correction display. * Fixed focus bug, fixed packet size check. Added BCH1/BCH2 correction test cases in BEACONS.TXT file for EPIRB TX application. * Fixed merge * Added countdown setting. * Added selection display to beacon list * Restored squelch control. Added countdown reset. * Code optim on formatSummary * More code optim * Made ResourceManager methods static * Externalized beacon strings to res file *Removed frame parameter from methods * Added test beacons for emergency. Removed old res file. * Fixed resource manager * Fixed resources. Added emergency display. * BCH + res fixes * Fixed beacons * Fixed app color. Fixed HexId and Serial calculation. * Fixed frequency list. Comments. * Code cleanup and comments * Added slideshow mode to EPIRB TX application. Code comments / cleanup. * Fixed frame end after wrong code cleanup * More code optim and comments. Removed touch from beacon list. * Added beacon selection with encoder on detail view. * Added beacon paging in header. Fixed timeout display * Fixed build for PRALINE * Fixed formatting * Potential fix for pull request finding * stop on carriage return * empty string if unknown * zeroing data buffer before use * fix typo * add include * fix case if pair == 0 * fix scpectrum_on to spectrum_on * fix typos * replaced 3 inlined offset blocks with apply_offset calls * set_beacon consolidation
601 lines
20 KiB
C++
601 lines
20 KiB
C++
/*
|
|
* 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<uint8_t>(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<label>:WHITE <value>\t" — the most repeated pattern in set_beacon.
|
|
// Shared format string deduplicates ~5 unique formats; %s for label lets the
|
|
// per-call-site cost drop to a single function call.
|
|
static char* append_field(char* p, const char* label, const char* value) {
|
|
return p + sprintf(p, STR_COLOR_CYAN "%s:" STR_COLOR_WHITE " %s\t", label, value);
|
|
}
|
|
|
|
void EPIRBDetailView::set_beacon(Beacon& beacon) {
|
|
// We use a single TextArea widget to display beacon information for code size optimization
|
|
char buffer[400];
|
|
char* p = buffer;
|
|
bool isReal = (beacon.frameMode == Beacon::FrameMode::NORMAL);
|
|
p += sprintf(p, STR_COLOR_CYAN "Beacon:" STR_COLOR_WHITE " %s(%s%s" STR_COLOR_WHITE ") - ",
|
|
beacon.getType(),
|
|
isReal ? STR_COLOR_YELLOW : STR_COLOR_GREEN,
|
|
isReal ? "Real" : "Test");
|
|
p += beacon.formatTime(p);
|
|
*p++ = '\t';
|
|
p = append_field(p, "Protocol", beacon.getProtocolName());
|
|
p += sprintf(p, "%s\t", ResourceManager::get_beacon_resource((uint8_t)beacon.protocol));
|
|
if (beacon.hasAdditionalData) {
|
|
p += sprintf(p, "%s\t", beacon.additionalData);
|
|
}
|
|
if (beacon.hasEmergency) {
|
|
p += sprintf(p, STR_COLOR_CYAN "Emergency:" STR_COLOR_RED " %s\t", beacon.emergencyType);
|
|
}
|
|
p += sprintf(p, STR_COLOR_CYAN "Country:" STR_COLOR_WHITE " %s(%d) - %s\t" STR_COLOR_CYAN "Location:" STR_COLOR_WHITE " ",
|
|
beacon.country.alphaCode, beacon.country.code, beacon.country.shortName);
|
|
p += beacon.location.toString(p, Location::LocationFormat::MAIDENHEAD_LOCATOR, 8);
|
|
*p++ = '\t';
|
|
if (!beacon.location.isUnknown()) {
|
|
p += beacon.location.toString(p, Location::LocationFormat::SEXAGESIMAL);
|
|
*p++ = '\t';
|
|
p += beacon.location.toString(p, Location::LocationFormat::DECIMAL);
|
|
*p++ = '\t';
|
|
}
|
|
// BCH status: color depends on validity/correction, so the color stays a %s substitution.
|
|
p += sprintf(p, STR_COLOR_CYAN "Control: %s%s",
|
|
beacon.isBch1Valid() ? (beacon.bch1Corrected ? STR_COLOR_YELLOW : STR_COLOR_GREEN) : STR_COLOR_RED,
|
|
beacon.isBch1Valid() ? "BCH1-OK" : "BCH1-KO");
|
|
if (beacon.hasBch2) {
|
|
p += sprintf(p, " %s%s",
|
|
beacon.isBch2Valid() ? (beacon.bch2Corrected ? STR_COLOR_YELLOW : STR_COLOR_GREEN) : STR_COLOR_RED,
|
|
beacon.isBch2Valid() ? "BCH2-OK" : "BCH2-KO");
|
|
}
|
|
*p++ = '\t';
|
|
p = append_field(p, "Hex ID", beacon.hexId);
|
|
if (beacon.hasSerialNumber) {
|
|
p = append_field(p, "S/N", beacon.serialNumber);
|
|
}
|
|
if (beacon.hasMainLocatingDevice()) {
|
|
p = append_field(p, "Main loc. dev.", beacon.getMainLocatingDeviceName());
|
|
if (beacon.hasAuxLocatingDevice()) {
|
|
append_field(p, "Aux loc. dev.", beacon.getAuxLocatingDeviceName());
|
|
}
|
|
}
|
|
text_beacon.set_content(buffer);
|
|
}
|
|
|
|
// EPIRBMapView class
|
|
EPIRBMapView::EPIRBMapView(
|
|
Rect parent_rect)
|
|
: View(parent_rect) {
|
|
add_children({&geomap});
|
|
geomap.set_mode(DISPLAY);
|
|
geomap.set_manual_panning(false);
|
|
geomap.init();
|
|
geomap.set_focusable(true);
|
|
geomap.clear_markers();
|
|
geomap.move(lon_, lat_);
|
|
// Hide for now
|
|
geomap.hidden(true);
|
|
}
|
|
|
|
void EPIRBMapView::set_main_marker(const std::string& label, float lat, float lon) {
|
|
geomap.set_tag(label);
|
|
lat_ = lat;
|
|
lon_ = lon;
|
|
geomap.move(lon_, lat_);
|
|
}
|
|
|
|
void EPIRBMapView::clear_markers() {
|
|
geomap.clear_markers();
|
|
}
|
|
|
|
void EPIRBMapView::add_marker(GeoMarker& marker) {
|
|
geomap.store_marker(marker);
|
|
}
|
|
|
|
void EPIRBMapView::paint(Painter& painter) {
|
|
// Prevent view from clearing background if map is not hidden
|
|
if (map_hidden) {
|
|
View::paint(painter);
|
|
painter.draw_string({UI_POS_X_CENTER(7), UI_POS_MAXHEIGHT / 2 - (UI_POS_HEIGHT(1) / 2)}, *Theme::getInstance()->fg_light, "No data");
|
|
}
|
|
}
|
|
|
|
void EPIRBMapView::on_show() {
|
|
// Force redrawing map
|
|
repaint();
|
|
}
|
|
|
|
void EPIRBMapView::hide_map(bool hide) {
|
|
map_hidden = hide;
|
|
geomap.hidden(hide);
|
|
}
|
|
|
|
void EPIRBMapView::repaint() {
|
|
// Fake orientation change to force map redraw
|
|
geomap.update_my_orientation(180, false);
|
|
geomap.update_my_orientation(0, true);
|
|
set_dirty();
|
|
}
|
|
|
|
// EPRIBQRView class
|
|
EPRIBQRView::EPRIBQRView(Rect parent_rect)
|
|
: View(parent_rect) {
|
|
add_children({&text_data, &options_qr, &qr_code});
|
|
// Hide for now
|
|
qr_code.hidden(true);
|
|
options_qr.on_change = [this](size_t, ui::OptionsField::value_t v) {
|
|
show_map = (v == 0);
|
|
update_qr();
|
|
};
|
|
update_display();
|
|
}
|
|
|
|
void EPRIBQRView::set_beacon(Beacon* beacon) {
|
|
current_beacon = beacon;
|
|
update_qr();
|
|
update_display();
|
|
}
|
|
|
|
void EPRIBQRView::update_display() {
|
|
// Update data => we use a single TextArea component for code size optimizaton
|
|
char buffer[128];
|
|
char* buffer_pointer = buffer;
|
|
buffer_pointer += sprintf(buffer_pointer, "%sQR:%s\t\t\t\t\t\t\t\t", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
|
if (current_beacon) {
|
|
buffer_pointer += sprintf(buffer_pointer, "%sData:%s\t", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
|
// HEX ID 30 Hexa or HEX ID 22 Hexa bit 26 to 112
|
|
buffer_pointer += current_beacon->toHexString(buffer_pointer, current_beacon->frame, true, 3, 11);
|
|
(*(buffer_pointer++)) = '\t';
|
|
if (current_beacon->longFrame) {
|
|
current_beacon->toHexString(buffer_pointer, current_beacon->frame, true, 11, 18);
|
|
} else {
|
|
current_beacon->toHexString(buffer_pointer, current_beacon->frame, true, 11, 14);
|
|
}
|
|
}
|
|
text_data.set_content(buffer);
|
|
}
|
|
|
|
void EPRIBQRView::update_qr() {
|
|
// Update QR code
|
|
bool show_qr = false;
|
|
if (current_beacon) {
|
|
// We have a beacon
|
|
if (show_map) {
|
|
// Map is selected
|
|
if (!current_beacon->location.isUnknown()) {
|
|
// Loation is known => actually draw QR
|
|
current_beacon->location.formatFloatLocation(qr_url, MAPS_URL_TEMPLATE);
|
|
show_qr = true;
|
|
}
|
|
} else {
|
|
// Detail is selected
|
|
char* buffer_pointer = qr_url;
|
|
// Send to heroku decoder
|
|
buffer_pointer += sprintf(qr_url, BEACON_URL_TEMPALTE);
|
|
current_beacon->hexString(buffer_pointer, false);
|
|
show_qr = true;
|
|
}
|
|
}
|
|
if (show_qr) qr_code.set_text(qr_url);
|
|
qr_code.hidden(!show_qr);
|
|
set_dirty();
|
|
}
|
|
|
|
#ifdef SPECAN
|
|
EPIRBRxView::EPIRBRxView(
|
|
EPIRBAppView& parent,
|
|
Rect parent_rect)
|
|
: spectrum::WaterfallView(), app_view(parent) {
|
|
ui::Rect waterfall_rect{0, EPIRB_TAB_POS_Y, parent_rect.width(), parent_rect.height()};
|
|
spectrum::WaterfallView::set_parent_rect(waterfall_rect);
|
|
}
|
|
|
|
void EPIRBRxView::on_show() {
|
|
// Turn on spectrum
|
|
app_view.epirb_rx_config_message.spectrum_on = true;
|
|
app_view.send_config();
|
|
start();
|
|
}
|
|
|
|
void EPIRBRxView::on_hide() {
|
|
// Turn off spectrum
|
|
stop();
|
|
app_view.epirb_rx_config_message.spectrum_on = false;
|
|
app_view.send_config();
|
|
app_view.refresh();
|
|
}
|
|
#endif
|
|
|
|
// App View
|
|
EPIRBAppView::EPIRBAppView(ui::NavigationView& nav)
|
|
: nav_(nav) {
|
|
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
|
|
|
|
add_children({&options_frequency,
|
|
&field_rf_amp,
|
|
&field_lna,
|
|
&field_vga,
|
|
&rssi,
|
|
//&audio,
|
|
#ifdef SQUELCH
|
|
&field_squelch,
|
|
#endif
|
|
&field_volume,
|
|
&channel,
|
|
&text_status,
|
|
&text_timeout,
|
|
&tab_view,
|
|
&view_list,
|
|
&view_detail,
|
|
&view_map,
|
|
#ifdef SPECAN
|
|
&view_rx,
|
|
#endif
|
|
&view_qr});
|
|
using option_t = std::pair<std::string, int32_t>;
|
|
using options_t = std::vector<option_t>;
|
|
options_t frequ_options;
|
|
// Set frequency combo content according to FREQ.TXT file content
|
|
for (auto freq : ResourceManager::get_frequencies()) {
|
|
int32_t freq_value = atol(freq.c_str());
|
|
frequ_options.emplace_back(to_string_rounded_freq(freq_value, 3), freq_value);
|
|
}
|
|
options_frequency.set_options(frequ_options);
|
|
|
|
options_frequency.on_change = [this](size_t, ui::OptionsField::value_t v) {
|
|
receiver_model.set_target_frequency(v);
|
|
};
|
|
// Restore frequency from preferencies
|
|
options_frequency.set_by_value(receiver_model.target_frequency());
|
|
|
|
// Tick second timer
|
|
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
|
|
this->on_tick_second();
|
|
};
|
|
|
|
#ifdef RESET_TIMER
|
|
text_timeout.set_focusable(true);
|
|
text_timeout.on_select = [this](TextArea&) {
|
|
timeout = ((countdown + 1) * -1);
|
|
on_tick_second();
|
|
};
|
|
#endif
|
|
|
|
tab_view.set_parent_rect(Rect(0, UI_POS_HEIGHT(4), screen_width, 3 * 8));
|
|
view_list.hidden(false);
|
|
view_detail.hidden(true);
|
|
view_map.hidden(true);
|
|
#ifdef SPECAN
|
|
view_rx.hidden(true);
|
|
#endif
|
|
view_qr.hidden(true);
|
|
|
|
view_list.set_db(beacon_db);
|
|
|
|
view_list.on_select = [this](size_t selected) {
|
|
beacon_db.set_current_beacon(selected);
|
|
on_beacon_change();
|
|
};
|
|
|
|
#ifdef SQUELCH
|
|
// Restore squelch value
|
|
field_squelch.set_value(squelch);
|
|
#endif
|
|
epirb_rx_config_message.squelch = squelch;
|
|
send_config();
|
|
#ifdef SQUELCH
|
|
field_squelch.on_change = [this](int32_t v) {
|
|
squelch = v;
|
|
epirb_rx_config_message.squelch = squelch;
|
|
send_config();
|
|
};
|
|
#endif
|
|
|
|
// Force sample rate to match baseband processor
|
|
receiver_model.set_sampling_rate(3072000);
|
|
|
|
receiver_model.enable();
|
|
|
|
audio::set_rate(audio::Rate::Hz_24000);
|
|
audio::output::start();
|
|
|
|
update_display();
|
|
|
|
#ifdef LOGGER
|
|
logger = std::make_unique<EPIRBLogger>();
|
|
if (logger) {
|
|
logger->append(logs_dir / "epirb_rx.txt");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
EPIRBAppView::~EPIRBAppView() {
|
|
// Remove tick second timer
|
|
rtc_time::signal_tick_second -= signal_token_tick_second;
|
|
// Stop audio processor
|
|
audio::output::stop();
|
|
// Stop receiver
|
|
receiver_model.disable();
|
|
baseband::shutdown();
|
|
// Cleanup ResourceManager
|
|
ResourceManager::destroy();
|
|
}
|
|
|
|
void EPIRBAppView::refresh() {
|
|
// Force map repaint
|
|
view_map.repaint();
|
|
}
|
|
|
|
void EPIRBAppView::focus() {
|
|
options_frequency.focus();
|
|
}
|
|
|
|
void EPIRBAppView::on_packet(Message* const p) {
|
|
const auto message = static_cast<const EPIRBPacketMessage*>(p);
|
|
const baseband::Packet& packet = message->packet;
|
|
|
|
// Decode the EPIRB packet
|
|
if (packet.size() > 64) {
|
|
// Actual beacon
|
|
Beacon& beacon = beacon_db.add_beacon();
|
|
// Get a new beacon from database and load it
|
|
decode_packet(packet, beacon);
|
|
beacons_received++;
|
|
|
|
// Track packet statistics
|
|
if (beacon.isFrameValid()) {
|
|
if (beacon.bch1Corrected || beacon.bch2Corrected) {
|
|
packets_corrected++;
|
|
} else {
|
|
packets_valid++;
|
|
}
|
|
} else {
|
|
packets_error++;
|
|
}
|
|
|
|
// Update timeout
|
|
timeout = (countdown * -1);
|
|
// Reset selection
|
|
beacon_db.set_current_beacon(0);
|
|
// Update display
|
|
on_beacon_change();
|
|
|
|
#ifdef LOGGER
|
|
// Log the beacon
|
|
if (logger) {
|
|
logger->on_packet(beacon);
|
|
}
|
|
#endif
|
|
|
|
view_list.set_dirty();
|
|
}
|
|
}
|
|
|
|
void EPIRBAppView::on_beacon_change() {
|
|
// Beacon selection has changed => get new selection
|
|
Beacon& cur_beacon = beacon_db.get_current_beacon();
|
|
// Update detail, qr and map tab
|
|
view_detail.set_beacon(cur_beacon);
|
|
view_qr.set_beacon(&cur_beacon);
|
|
update_map();
|
|
// Update display
|
|
update_display();
|
|
}
|
|
|
|
void EPIRBAppView::update_map() {
|
|
// Clear previously saved markers
|
|
view_map.clear_markers();
|
|
bool hide_map = true;
|
|
size_t size = beacon_db.size();
|
|
if (size > 0) {
|
|
// Check if current beacon has a valid location
|
|
Beacon& beacon = beacon_db.get_current_beacon();
|
|
size_t cur_index = beacon_db.get_current_beacon_index();
|
|
if (!beacon.location.isUnknown()) {
|
|
hide_map = false;
|
|
// Set new position
|
|
view_map.set_main_marker(std::string(beacon.getType()) + "-" + beacon.shortId(), beacon.location.latitude.getFloatValue(), beacon.location.longitude.getFloatValue());
|
|
// Add all beacons with valid locations as markers
|
|
for (size_t j = 0; j < size; j++) {
|
|
if (cur_index != j) {
|
|
Beacon& other_beacon = beacon_db.get_beacon(j);
|
|
if (!other_beacon.location.isUnknown()) {
|
|
ui::GeoMarker marker;
|
|
marker.lat = other_beacon.location.latitude.getFloatValue();
|
|
marker.lon = other_beacon.location.longitude.getFloatValue();
|
|
marker.angle = 0;
|
|
marker.tag = std::string(other_beacon.getType()) + "-" + other_beacon.shortId();
|
|
view_map.add_marker(marker);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
view_map.hide_map(hide_map);
|
|
view_map.repaint();
|
|
}
|
|
|
|
void EPIRBAppView::on_tick_second() {
|
|
timeout++;
|
|
// Limit to 3 digits
|
|
if (timeout > 999) timeout = 0;
|
|
text_timeout.set_content(to_string_dec_uint(abs(timeout)));
|
|
}
|
|
|
|
void EPIRBAppView::update_display() {
|
|
// We use a single TextArea for code size optimization
|
|
char buffer[128];
|
|
char* buffer_pointer = buffer;
|
|
buffer_pointer += sprintf(buffer_pointer, "%sListening... Beacon:%s%2d/%d\t", STR_COLOR_CYAN, STR_COLOR_WHITE, (beacon_db.get_current_beacon_index() + 1), beacons_received);
|
|
buffer_pointer += sprintf(buffer_pointer, "%sStats: %s%03dOK %s%03dCOR %s%03dERR\t", STR_COLOR_CYAN, STR_COLOR_GREEN, packets_valid, STR_COLOR_YELLOW, packets_corrected, STR_COLOR_RED, packets_error);
|
|
buffer_pointer += sprintf(buffer_pointer, "%sCurrent:%s ", STR_COLOR_CYAN, STR_COLOR_WHITE);
|
|
if (!beacon_db.empty()) {
|
|
beacon_db.get_current_beacon().formatSummary(buffer_pointer, false);
|
|
}
|
|
text_status.set_content(buffer);
|
|
}
|
|
|
|
void EPIRBAppView::send_config() {
|
|
// Send config to baseband
|
|
baseband::set_epirb_rx_config(epirb_rx_config_message);
|
|
}
|
|
|
|
} // namespace ui::external_app::epirb_rx
|