diff --git a/firmware/application/external/detector_rx/main.cpp b/firmware/application/external/detector_rx/main.cpp new file mode 100644 index 000000000..2745f970e --- /dev/null +++ b/firmware/application/external/detector_rx/main.cpp @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * 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 "ui.hpp" +#include "ui_detector_rx.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::detector_rx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::detector_rx + +extern "C" { + +__attribute__((section(".external_app.app_detector_rx.application_information"), used)) application_information_t _application_information_detector_rx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::detector_rx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "Detector", + /*.bitmap_data = */ + { + 0x00, + 0x00, + 0x00, + 0x00, + 0x04, + 0x20, + 0x12, + 0x48, + 0x8A, + 0x51, + 0xCA, + 0x53, + 0xCA, + 0x53, + 0x8A, + 0x51, + 0x12, + 0x48, + 0x84, + 0x21, + 0xC0, + 0x03, + 0x40, + 0x02, + 0x60, + 0x06, + 0x20, + 0x04, + 0x30, + 0x0C, + 0xF0, + 0x0F}, + /*.icon_color = */ ui::Color::orange().v, + /*.menu_location = */ app_location_t::RX, + /*.desired_menu_position = */ -1, + + // this has to be the biggest baseband used by the app. SPEC + /*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/detector_rx/ui_detector_rx.cpp b/firmware/application/external/detector_rx/ui_detector_rx.cpp new file mode 100644 index 000000000..a1dd3b935 --- /dev/null +++ b/firmware/application/external/detector_rx/ui_detector_rx.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2018 Furrtek + * Copyright (C) 2023 gullradriel, Nilorea Studio Inc. + * + * 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 "ui_detector_rx.hpp" +#include "ui_fileman.hpp" +#include "ui_freqman.hpp" +#include "baseband_api.hpp" +#include "file.hpp" +#include "oversample.hpp" +#include "ui_font_fixed_8x16.hpp" + +using namespace portapack; +using namespace tonekey; +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(); +} + +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 + + 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_); +} + +DetectorRxView::DetectorRxView(NavigationView& nav) + : nav_{nav} { + add_children({ + &labels, + &field_lna, + &field_vga, + &field_rf_amp, + &field_volume, + &text_frequency, + &freq_stats_rssi, + &freq_stats_db, + &text_beep_squelch, + &field_beep_squelch, + &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; + }; + + rssi_graph.set_nb_columns(256); + + 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; + } + }; +} + +void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) { + static int16_t last_max_db = 0; + static uint8_t last_min_rssi = 0; + static uint8_t last_avg_rssi = 0; + static uint8_t last_max_rssi = 0; + + 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)); + } + + 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(); + receiver_model.disable(); + baseband::shutdown(); + + audio_sampling_rate = audio::Rate::Hz_24000; + baseband::run_image(portapack::spi_flash::image_tag_capture); + 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.enable(); + + return 0; +} + +} // namespace ui::external_app::detector_rx diff --git a/firmware/application/external/detector_rx/ui_detector_rx.hpp b/firmware/application/external/detector_rx/ui_detector_rx.hpp new file mode 100644 index 000000000..4814f297f --- /dev/null +++ b/firmware/application/external/detector_rx/ui_detector_rx.hpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2018 Furrtek + * Copyright (C) 2023 gullradriel, Nilorea Studio Inc. + * + * 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 _UI_DETECTOR_RX +#define _UI_DETECTOR_RX + +#include "analog_audio_app.hpp" +#include "app_settings.hpp" +#include "audio.hpp" +#include "baseband_api.hpp" +#include "file.hpp" +#include "freqman_db.hpp" +#include "portapack_persistent_memory.hpp" +#include "radio_state.hpp" +#include "receiver_model.hpp" +#include "string_format.hpp" +#include "ui.hpp" +#include "ui_mictx.hpp" +#include "ui_receiver.hpp" +#include "ui_spectrum.hpp" + +namespace ui::external_app::detector_rx { + +#define DETECTOR_BW 750000 + +class DetectorRxView : public View { + public: + DetectorRxView(NavigationView& nav); + ~DetectorRxView(); + + void focus() override; + + std::string title() const override { return "Detector RX"; }; + + private: + NavigationView& nav_; + + RxRadioState radio_state_{}; + + 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(); + + 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}, + }}; + + Labels labels{ + {{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground}}; + + LNAGainField field_lna{ + {UI_POS_X(4), UI_POS_Y(0)}}; + + VGAGainField field_vga{ + {UI_POS_X(11), UI_POS_Y(0)}}; + + RFAmpField field_rf_amp{ + {UI_POS_X(18), UI_POS_Y(0)}}; + + AudioVolumeField field_volume{ + {UI_POS_X(24), UI_POS_Y(0)}}; + + OptionsField field_mode{ + {UI_POS_X(0), UI_POS_Y(1)}, + 9, + { + {"TETRA UP", 0}, + {"Lora", 1}, + {"Remotes", 2}, + }}; + + Text text_frequency{ + {UI_POS_X_RIGHT(20), UI_POS_Y(1), 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}, + "Bip>"}; + + NumberField field_beep_squelch{ + {UI_POS_X_RIGHT(5), UI_POS_Y(2)}, + 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 + Text freq_stats_db{ + {UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT}, + }; + + RSSIGraph rssi_graph{ + {UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)}, + }; + + RSSI rssi{ + {UI_POS_X_RIGHT(5), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(6)}, + }; + + MessageHandlerRegistration message_handler_stats{ + Message::ID::ChannelStatistics, + [this](const Message* const p) { + this->on_statistics_update(static_cast(p)->statistics); + }}; + + MessageHandlerRegistration message_handler_frame_sync{ + Message::ID::DisplayFrameSync, + [this](const Message* const) { + this->on_timer(); + }}; + + const std::vector 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 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 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 + +#endif diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 2ab738950..02e10e123 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -212,6 +212,10 @@ set(EXTCPPSRC #gfxEQ external/gfxeq/main.cpp external/gfxeq/ui_gfxeq.cpp + + #detector_rx + external/detector_rx/main.cpp + external/detector_rx/ui_detector_rx.cpp ) set(EXTAPPLIST @@ -266,4 +270,5 @@ set(EXTAPPLIST scanner level gfxeq + detector_rx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index ff7c3c189..10ecd03c6 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -74,6 +74,7 @@ MEMORY ram_external_app_level (rwx) : org = 0xADE10000, len = 32k ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k + ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k } SECTIONS @@ -383,4 +384,13 @@ SECTIONS KEEP(*(.external_app.app_gfxeq.application_information)); *(*ui*external_app*gfxeq*); } > ram_external_app_gfxeq + + .external_app_detector_rx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_detector_rx.application_information)); + *(*ui*external_app*detector_rx*); + } > ram_external_app_detector_rx + + } + diff --git a/firmware/common/ui.hpp b/firmware/common/ui.hpp index edec2d404..49e52c84e 100644 --- a/firmware/common/ui.hpp +++ b/firmware/common/ui.hpp @@ -31,6 +31,8 @@ namespace ui { // default font height #define UI_POS_DEFAULT_HEIGHT 16 +// small height +#define UI_POS_DEFAULT_HEIGHT_SMALL 8 // default font width #define UI_POS_DEFAULT_WIDTH 8 // px position of the linenum-th character (Y)