Make detector use freqman (#3119)

* make detector list a freqman file
* add file loading ability, autoscan/pause, step in on pause on range, frequency value display
* Update firmware/application/external/detector_rx/ui_detector_rx.hpp
* Update firmware/application/external/detector_rx/ui_detector_rx.cpp
* add 750 kHz step
* revamp UI and add auto advance, auto scan, and only display 'RANGE SCAN...' when auto scan is on on ranges to avoid flicker of the hell.
* adjust as I can the ranges as they where before
* Update sdcard/FREQMAN/DETECTOR.TXT
* fix instance related copilot remark
This commit is contained in:
gullradriel
2026-04-02 08:26:45 +02:00
committed by GitHub
parent aee54e5b2d
commit 605c9efa0c
5 changed files with 255 additions and 118 deletions
+177 -46
View File
@@ -26,6 +26,7 @@
#include "ui_freqman.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "file_path.hpp"
#include "oversample.hpp"
#include "ui_font_fixed_8x16.hpp"
@@ -35,36 +36,112 @@ using portapack::memory::map::backup_ram;
namespace ui::external_app::detector_rx {
// Function to map the value from one range to another
int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
}
void DetectorRxView::focus() {
field_lna.focus();
button_index.focus();
}
DetectorRxView::~DetectorRxView() {
// reset performance counters request to default
shared_memory.request_m4_performance_counter = 0;
receiver_model.disable();
audio::output::stop();
baseband::shutdown();
}
void DetectorRxView::on_timer() {
freq_index++;
if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
std::string DetectorRxView::format_freq_mhz(int64_t freq_hz) {
int64_t mhz = freq_hz / 1000000;
int64_t khz_frac = (freq_hz % 1000000) / 1000;
return "< " + to_string_dec_uint(mhz) + "." + to_string_dec_uint(khz_frac, 3, '0') + " MHz >";
}
if (freq_mode == 2)
freq_ = remotes_monitoring_frequencies_hz[freq_index];
else if (freq_mode == 1)
freq_ = lora_monitoring_frequencies_hz[freq_index];
else
freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
receiver_model.set_target_frequency(freq_);
void DetectorRxView::load_freqman() {
freqman_load_options options{};
options.load_freqs = true;
options.load_ranges = true;
options.load_hamradios = false;
options.load_repeaters = false;
if (!load_freqman_file(freq_file_stem, frequency_list, options) || frequency_list.empty()) {
button_file.set_text("No file!");
button_index.set_text("");
text_entry_desc.set("");
button_freq.set_text("");
frequency_list.clear();
return;
}
current_index = 0;
init_current_entry();
button_file.set_text(freq_file_stem);
}
void DetectorRxView::init_current_entry() {
if (frequency_list.empty()) return;
auto& entry = *frequency_list[current_index];
if (entry.type == freqman_type::Range) {
minfreq = entry.frequency_a;
maxfreq = entry.frequency_b;
current_freq = minfreq;
current_step = is_valid(entry.step) ? freqman_entry_get_step_value(entry.step) : DETECTOR_BW;
} else {
current_freq = entry.frequency_a;
minfreq = current_freq;
maxfreq = current_freq;
current_step = DETECTOR_BW;
}
update_entry_display();
update_freq_display();
receiver_model.set_target_frequency(current_freq);
}
void DetectorRxView::update_entry_display() {
if (frequency_list.empty()) return;
auto& entry = *frequency_list[current_index];
button_index.set_text(
to_string_dec_uint(current_index + 1) + "/" +
to_string_dec_uint(frequency_list.size()));
text_entry_desc.set(entry.description);
}
void DetectorRxView::update_freq_display() {
if (auto_scan && (minfreq != maxfreq)) {
if (last_update_was_auto_range_type != 1) {
button_freq.set_text("RANGE SCAN...");
last_update_was_auto_range_type = 1;
}
} else {
button_freq.set_text(format_freq_mhz(current_freq));
last_update_was_auto_range_type = 0;
}
}
void DetectorRxView::on_timer() {
if (frequency_list.empty() || !auto_scan) return;
auto& entry = *frequency_list[current_index];
if (entry.type == freqman_type::Range) {
current_freq += current_step;
if (current_freq > maxfreq) {
if (auto_advance) {
current_index = (current_index + 1) % frequency_list.size();
init_current_entry();
return;
}
current_freq = minfreq;
}
receiver_model.set_target_frequency(current_freq);
update_freq_display();
} else if (auto_advance) {
// Single frequency: advance to next entry
current_index = (current_index + 1) % frequency_list.size();
init_current_entry();
}
}
DetectorRxView::DetectorRxView(NavigationView& nav)
@@ -75,21 +152,22 @@ DetectorRxView::DetectorRxView(NavigationView& nav)
&field_vga,
&field_rf_amp,
&field_volume,
&text_frequency,
&freq_stats_rssi,
&freq_stats_db,
&button_file,
&button_index,
&text_entry_desc,
&text_beep_squelch,
&field_beep_squelch,
&freq_stats_db,
&freq_stats_rssi,
&button_freq,
&button_auto_scan,
&button_auto_advance,
&rssi,
&rssi_graph,
&field_mode,
});
// activate vertical bar mode
rssi.set_vertical_rssi(true);
freq_ = receiver_model.target_frequency();
field_beep_squelch.set_value(beep_squelch);
field_beep_squelch.on_change = [this](int32_t v) {
beep_squelch = v;
@@ -97,29 +175,85 @@ DetectorRxView::DetectorRxView(NavigationView& nav)
rssi_graph.set_nb_columns(256);
// FILE button opens file picker
button_file.on_select = [this](Button&) {
auto open_view = nav_.push<FileLoadView>(".TXT");
open_view->push_dir(freqman_dir);
open_view->on_changed = [this](std::filesystem::path new_file_path) {
if (new_file_path.native().find((u"/" / freqman_dir).native()) != 0) {
button_file.set_text("Invalid file");
return;
}
freq_file_stem = new_file_path.stem().string();
load_freqman();
};
};
// Index encoder: rotate to change entry, click does nothing
button_index.on_change = [this]() {
if (frequency_list.empty()) return;
int32_t delta = button_index.get_encoder_delta();
button_index.set_encoder_delta(0);
if (delta == 0) return;
// Use signed arithmetic for index wraparound to avoid unsigned promotion issues.
const int32_t list_size = static_cast<int32_t>(frequency_list.size());
int32_t idx = static_cast<int32_t>(current_index);
if (delta > 0) {
idx = (idx + 1) % list_size;
} else {
idx = (idx - 1 + list_size) % list_size;
}
current_index = static_cast<size_t>(idx);
init_current_entry();
};
// Frequency encoder: rotate to step within range
button_freq.on_change = [this]() {
if (frequency_list.empty()) return;
auto& entry = *frequency_list[current_index];
if (entry.type != freqman_type::Range) {
button_freq.set_encoder_delta(0);
return;
}
int32_t delta = button_freq.get_encoder_delta();
button_freq.set_encoder_delta(0);
if (delta == 0) return;
if (delta > 0) {
current_freq += current_step;
if (current_freq > maxfreq) current_freq = minfreq;
} else {
current_freq -= current_step;
if (current_freq < minfreq) current_freq = maxfreq;
}
receiver_model.set_target_frequency(current_freq);
update_freq_display();
};
// Auto-scan toggle
button_auto_scan.on_select = [this](Button&) {
auto_scan = !auto_scan;
button_auto_scan.set_text(auto_scan ? "AUTOSCAN" : "NO SCAN");
update_freq_display();
};
button_auto_scan.set_text(auto_scan ? "AUTOSCAN" : "NO SCAN");
// Auto-advance toggle
button_auto_advance.on_select = [this](Button&) {
auto_advance = !auto_advance;
button_auto_advance.set_text(auto_advance ? "AUTOADV" : "NO ADV");
};
button_auto_advance.set_text(auto_advance ? "AUTOADV" : "NO ADV");
change_mode();
rssi.set_peak(true, 3000);
// FILL STEP OPTIONS
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
field_mode.on_change = [this](size_t, int32_t value) {
freq_mode = value;
freq_index = 0;
switch (value) {
case 1: // Lora
text_frequency.set(" 433, 868, 915 Mhz");
break;
case 2: // Remotes
text_frequency.set(" 433, 315 Mhz");
break;
default:
case 0: // TETRA UP
text_frequency.set(" 380-390 Mhz");
break;
}
};
load_freqman();
}
void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
@@ -130,25 +264,23 @@ void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
// refresh db
if (last_max_db != statistics.max_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
rssi.set_db(statistics.max_db);
}
// refresh rssi
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
last_min_rssi = rssi_graph.get_graph_min();
last_avg_rssi = rssi_graph.get_graph_avg();
last_max_rssi = rssi_graph.get_graph_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
freq_stats_rssi.set("RSSI:" + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
}
if (statistics.max_db > beep_squelch) {
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
}
} /* on_statistic_updates */
}
size_t DetectorRxView::change_mode() {
audio::output::stop();
@@ -160,14 +292,13 @@ size_t DetectorRxView::change_mode() {
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
// The radio needs to know the effective sampling rate.
auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
audio::set_rate(audio_sampling_rate);
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
receiver_model.set_headphone_volume(receiver_model.headphone_volume());
receiver_model.enable();
+53 -72
View File
@@ -29,6 +29,7 @@
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
@@ -60,20 +61,35 @@ class DetectorRxView : public View {
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
size_t change_mode();
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
void on_timer();
void load_freqman();
void init_current_entry();
void update_entry_display();
void update_freq_display();
std::string format_freq_mhz(int64_t freq_hz);
freqman_db frequency_list{};
size_t current_index{0};
int64_t current_freq{0};
int64_t minfreq{0};
int64_t maxfreq{0};
int32_t current_step{DETECTOR_BW};
std::string freq_file_stem{"DETECTOR"};
bool auto_scan{true};
bool auto_advance{false};
uint8_t last_update_was_auto_range_type = -1;
uint8_t freq_index = 0;
rf::Frequency freq_ = {433920000};
int32_t beep_squelch = 0;
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
uint8_t freq_mode = 0;
app_settings::SettingsManager settings_{
"rx_detector",
app_settings::Mode::RX,
{
{"beep_squelch"sv, &beep_squelch},
{"freq_file"sv, &freq_file_stem},
{"auto_scan"sv, &auto_scan},
{"auto_advance"sv, &auto_advance},
}};
Labels labels{
@@ -93,41 +109,55 @@ class DetectorRxView : public View {
AudioVolumeField field_volume{
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
OptionsField field_mode{
{UI_POS_X(0), UI_POS_Y(1)},
9,
{
{"TETRA UP", 0},
{"Lora", 1},
{"Remotes", 2},
}};
// Row 1: filename + auto advance
Button button_file{
{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
""};
Text text_frequency{
{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
Button button_auto_advance{
{UI_POS_X_RIGHT(9), UI_POS_Y(1), UI_POS_WIDTH(9), UI_POS_DEFAULT_HEIGHT},
"NO ADV"};
// Row 2: index encoder + description + auto scan
ButtonWithEncoder button_index{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
""};
Text text_entry_desc{
{UI_POS_X(4), UI_POS_Y(2), UI_POS_WIDTH(17), UI_POS_DEFAULT_HEIGHT},
""};
Button button_auto_scan{
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(9), UI_POS_DEFAULT_HEIGHT},
"AUTO SCAN"};
// Row 3: frequency encoder + bip level
ButtonWithEncoder button_freq{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
""};
Text text_beep_squelch{
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
{UI_POS_X_RIGHT(9), UI_POS_Y(3), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
"Bip>"};
NumberField field_beep_squelch{
{UI_POS_X_RIGHT(5), UI_POS_Y(2)},
{UI_POS_X_RIGHT(5), UI_POS_Y(3)},
4,
{-100, 20},
1,
' ',
};
// RSSI: XX/XX/XXX
Text freq_stats_rssi{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
// Power: -XXX db
// Row 4: Power + RSSI on same line
Text freq_stats_db{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
{UI_POS_X(0), UI_POS_Y(4), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
Text freq_stats_rssi{
{UI_POS_X(15), UI_POS_Y(4), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
// Row 5+: RSSI graph + vertical bar
RSSIGraph rssi_graph{
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
};
@@ -147,55 +177,6 @@ class DetectorRxView : public View {
[this](const Message* const) {
this->on_timer();
}};
const std::vector<uint32_t> remotes_monitoring_frequencies_hz = {
// Around 315 MHz (common for older remotes, key fobs in some regions)
// Window centered on 315 MHz, covers 314.625 - 315.375 MHz
315000000,
// Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
// Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
433920000,
};
const std::vector<uint32_t> lora_monitoring_frequencies_hz = {
// EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
// Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
433375000, // Covers 433.000 - 433.750 MHz
434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
// EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
// Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
867375000, // Covers 867.000 - 867.750 MHz
868125000, // Covers 867.750 - 868.500 MHz
868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
// US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
// Providing a few sample windows around the 915 MHz area with 750kHz steps.
// This band is wide; a full scan would require many more frequencies.
914250000, // Covers 913.875 - 914.625 MHz
915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
915750000, // Covers 915.375 - 916.125 MHz
};
const std::vector<uint32_t> tetra_uplink_monitoring_frequencies_hz = {
// Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
// First center: 380,000,000 + 375,000 = 380,375,000 Hz
// Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
380375000, // Covers 380.000 - 380.750 MHz
381125000, // Covers 380.750 - 381.500 MHz
381875000, // Covers 381.500 - 382.250 MHz
382625000, // Covers 382.250 - 383.000 MHz
383375000, // Covers 383.000 - 383.750 MHz
384125000, // Covers 383.750 - 384.500 MHz
384875000, // Covers 384.500 - 385.250 MHz
385625000, // Covers 385.250 - 386.000 MHz
386375000, // Covers 386.000 - 386.750 MHz
387125000, // Covers 386.750 - 387.500 MHz
387875000, // Covers 387.500 - 388.250 MHz
388625000, // Covers 388.250 - 389.000 MHz
389375000, // Covers 389.000 - 389.750 MHz
390125000, // Covers 389.750 - 390.500 MHz
};
};
} // namespace ui::external_app::detector_rx
+2
View File
@@ -134,6 +134,7 @@ options_t freqman_steps = {
{"100kHz (FM2)", 100000},
{"250kHz (N2)", 250000},
{"500kHz (WFM)", 500000},
{"750kHz ", 750000},
{"1MHz ", 1000000},
};
@@ -156,6 +157,7 @@ options_t freqman_steps_short = {
{"100kHz", 100000},
{"250kHz", 250000},
{"500kHz", 500000},
{"750kHz", 750000},
{"1MHz", 1000000},
};
+2
View File
@@ -102,6 +102,7 @@ enum class freqman_type : uint8_t {
_100kHz,
_250kHz,
_500kHz,
_750kHz,
_1MHz,
Unknown,
* };
@@ -130,6 +131,7 @@ enum class freqman_type : uint8_t {
freqman_step_info{ freqman_step::_100kHz, "100kHz", "100kHz (FM2)", 100'000 },
freqman_step_info{ freqman_step::_250kHz, "250kHz", "250kHz (N2)", 250'000 },
freqman_step_info{ freqman_step::_500kHz, "500kHz", "500kHz (WFM)", 500'000 },
freqman_step_info{ freqman_step::_750kHz, "750kHz", "750kHz ", 750'000 },
freqman_step_info{ freqman_step::_1MHz, "1MHz", "1MHz ", 1'000'000 },
freqman_step_info{ freqman_step::Unknown, "Unknown", "Unknown ", 0 },
* };
+21
View File
@@ -0,0 +1,21 @@
# Around 315 MHz (common for older remotes, key fobs in some regions)
# Window centered on 315 MHz, covers 314.625 - 315.375 MHz
f=315000000,d=Remotes 315M
# Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
# Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
f=433920000,d=Remotes 433.92M
# EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
# Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
a=433375000,b=434875000,s=750kHz,d=LoRa EU433
# EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
# Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
a=867375000,b=868875000,s=750kHz,d=LoRa EU868
# US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
a=902000000,b=928250000,s=750kHz,d=LoRa US915
# TETRA Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
a=380375000,b=390125000,s=750kHz,d=TETRA UP