diff --git a/firmware/application/apps/ui_scanner.cpp b/firmware/application/apps/ui_scanner.cpp index 26c00728..1e68d081 100644 --- a/firmware/application/apps/ui_scanner.cpp +++ b/firmware/application/apps/ui_scanner.cpp @@ -26,6 +26,7 @@ #include "string_format.hpp" #include "audio.hpp" + using namespace portapack; namespace ui { @@ -38,6 +39,10 @@ ScannerThread::ScannerThread( } ScannerThread::~ScannerThread() { + stop(); +} + +void ScannerThread::stop() { if( thread ) { chThdTerminate(thread); chThdWait(thread); @@ -49,6 +54,18 @@ void ScannerThread::set_scanning(const bool v) { _scanning = v; } +bool ScannerThread::is_scanning() { + return _scanning; +} + +void ScannerThread::set_userpause(const bool v) { + _userpause = v; +} + +bool ScannerThread::is_userpause() { + return _userpause; +} + msg_t ScannerThread::static_fn(void* arg) { auto obj = static_cast(arg); obj->run(); @@ -56,36 +73,33 @@ msg_t ScannerThread::static_fn(void* arg) { } void ScannerThread::run() { - RetuneMessage message { }; - uint32_t frequency_index = 0; - - while( !chThdShouldTerminate() ) { - if (_scanning) { - // Retune - receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); + if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ... + RetuneMessage message { }; + uint32_t frequency_index = 0; + while( !chThdShouldTerminate() ) { + if (_scanning) { + // Retune + receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); + message.range = frequency_index; + EventDispatcher::send_message(message); - message.range = frequency_index; - EventDispatcher::send_message(message); - - - frequency_index++; - if (frequency_index >= frequency_list_.size()) - frequency_index = 0; + frequency_index++; + if (frequency_index >= frequency_list_.size()) + frequency_index = 0; + } + chThdSleepMilliseconds(50); //50 is enough for reception stabilization, increase for more precise scanning ? } - - chThdSleepMilliseconds(50); } } void ScannerView::handle_retune(uint32_t i) { - text_cycle.set( to_string_dec_uint(i) + "/" + - to_string_dec_uint(frequency_list.size()) + " : " + - to_string_dec_uint(frequency_list[i]) ); - desc_cycle.set( description_list[i] ); + big_display.set(frequency_list[i]); //Show the big Freq + text_cycle.set( to_string_dec_uint(i + 1,3) ); + if (description_list[i].size() > 0) desc_cycle.set( description_list[i] ); //If this is a new description: show } void ScannerView::focus() { - field_lna.focus(); + field_mode.focus(); } ScannerView::~ScannerView() { @@ -94,9 +108,16 @@ ScannerView::~ScannerView() { baseband::shutdown(); } +void ScannerView::show_max() { //show total number of freqs to scan + if (frequency_list.size() == MAX_DB_ENTRY) + text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)"); + else + text_max.set( "/ " + to_string_dec_uint(frequency_list.size())); +} + ScannerView::ScannerView( - NavigationView& -) + NavigationView& nav + ) : nav_ { nav } { add_children({ &labels, @@ -105,104 +126,190 @@ ScannerView::ScannerView( &field_rf_amp, &field_volume, &field_bw, - &field_trigger, &field_squelch, &field_wait, - //&record_view, + &rssi, &text_cycle, + &text_max, &desc_cycle, - //&waterfall, + &big_display, + &button_manual_start, + &button_manual_end, + &field_mode, + &step_mode, + &button_manual_scan, + &button_pause, + &button_audio_app }); - std::string scanner_file = "SCANNER"; - if (load_freqman_file(scanner_file, database)) { - for(auto& entry : database) { - // FIXME - if (entry.type == RANGE) { - for (uint32_t i=entry.frequency_a; i < entry.frequency_b; i+= 1000000) { - frequency_list.push_back(i); - description_list.push_back("RNG " + to_string_dec_uint(entry.frequency_a) + ">" + to_string_dec_uint(entry.frequency_b)); - } - } else { - frequency_list.push_back(entry.frequency_a); - description_list.push_back(entry.description); - } + def_step = change_mode(AM); //Start on AM + field_mode.set_by_value(AM); //Reflect the mode into the manual selector + + big_display.set_style(&style_green); //Start with green color + + button_manual_start.on_select = [this, &nav](Button& button) { + auto new_view = nav_.push(frequency_range.min); + new_view->on_changed = [this, &button](rf::Frequency f) { + frequency_range.min = f; + button_manual_start.set_text(to_string_short_freq(f)); + }; + }; + + button_manual_end.on_select = [this, &nav](Button& button) { + auto new_view = nav.push(frequency_range.max); + new_view->on_changed = [this, &button](rf::Frequency f) { + frequency_range.max = f; + button_manual_end.set_text(to_string_short_freq(f)); + }; + }; + + button_pause.on_select = [this](Button&) { + if (scan_thread->is_userpause()) { + timer = wait * 10; //Unlock timer pause on_statistics_update + button_pause.set_text("PAUSE"); //resume scanning (show button for pause) + scan_thread->set_userpause(false); + //scan_resume(); + } else { + scan_pause(); + scan_thread->set_userpause(true); + button_pause.set_text("RESUME"); //PAUSED, show resume } - } else { - // DEBUG - // TODO: Clean this - frequency_list.push_back(466025000); - description_list.push_back("POCSAG-France"); - frequency_list.push_back(466050000); - description_list.push_back("POCSAG-France"); - frequency_list.push_back(466075000); - description_list.push_back("POCSAG-France"); - frequency_list.push_back(466175000); - description_list.push_back("POCSAG-France"); - frequency_list.push_back(466206250); - description_list.push_back("POCSAG-France"); - frequency_list.push_back(466231250); - description_list.push_back("POCSAG-France"); - } - - field_bw.set_selected_index(2); - field_bw.on_change = [this](size_t n, OptionsField::value_t) { - receiver_model.set_nbfm_configuration(n); }; - field_wait.on_change = [this](int32_t v) { - wait = v; - }; - field_wait.set_value(5); - - field_trigger.on_change = [this](int32_t v) { - trigger = v; - }; - field_trigger.set_value(30); - - field_squelch.set_value(receiver_model.squelch_level()); - field_squelch.on_change = [this](int32_t v) { - squelch = v; - receiver_model.set_squelch_level(v); + button_audio_app.on_select = [this](Button&) { + if (scan_thread->is_scanning()) + scan_thread->set_scanning(false); + scan_thread->stop(); + nav_.pop(); + nav_.push(); }; + button_manual_scan.on_select = [this](Button&) { + if (!frequency_range.min || !frequency_range.max) { + nav_.display_modal("Error", "Both START and END freqs\nneed a value"); + } else if (frequency_range.min > frequency_range.max) { + nav_.display_modal("Error", "END freq\nis lower than START"); + } else { + scan_thread->stop(); //STOP SCANNER THREAD + frequency_list.clear(); + description_list.clear(); + def_step = step_mode.selected_index_value(); //Use def_step from manual selector + description_list.push_back( + "M:" + to_string_short_freq(frequency_range.min) + ">" + + to_string_short_freq(frequency_range.max) + " S:" + + to_string_short_freq(def_step) + ); + + rf::Frequency frequency = frequency_range.min; + while (frequency_list.size() < MAX_DB_ENTRY && frequency <= frequency_range.max) { //add manual range + frequency_list.push_back(frequency); + description_list.push_back(""); //If empty, will keep showing the last description + frequency+=def_step; + } + + show_max(); + start_scan_thread(); //RESTART SCANNER THREAD + } + }; + + field_mode.on_change = [this](size_t, OptionsField::value_t v) { + if (scan_thread->is_scanning()) + scan_thread->set_scanning(false); // WE STOP SCANNING + audio::output::stop(); + scan_thread->stop(); + receiver_model.disable(); + baseband::shutdown(); + chThdSleepMilliseconds(50); + change_mode(v); + start_scan_thread(); + }; + + //PRE-CONFIGURATION: + field_wait.on_change = [this](int32_t v) { wait = v; }; field_wait.set_value(5); + field_squelch.on_change = [this](int32_t v) { squelch = v; }; field_squelch.set_value(30); field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99); - field_volume.on_change = [this](int32_t v) { - this->on_headphone_volume_changed(v); - }; - - audio::output::start(); - - audio::output::mute(); - baseband::run_image(portapack::spi_flash::image_tag_nfm_audio); - receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); - receiver_model.set_sampling_rate(3072000); - receiver_model.set_baseband_bandwidth(1750000); - receiver_model.enable(); - receiver_model.set_squelch_level(0); - receiver_model.set_nbfm_configuration(field_bw.selected_index()); - audio::output::unmute(); - - // TODO: Scanning thread here - scan_thread = std::make_unique(frequency_list); + field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); }; + // LEARN FREQUENCIES + std::string scanner_txt = "SCANNER"; + if ( load_freqman_file(scanner_txt, database) ) { + for(auto& entry : database) { // READ LINE PER LINE + if (frequency_list.size() < MAX_DB_ENTRY) { //We got space! + if (entry.type == RANGE) { //RANGE + switch (entry.step) { + case AM_US: def_step = 10000; break ; + case AM_EUR:def_step = 9000; break ; + case NFM_1: def_step = 12500; break ; + case NFM_2: def_step = 6250; break ; + case FM_1: def_step = 100000; break ; + case FM_2: def_step = 50000; break ; + case N_1: def_step = 25000; break ; + case N_2: def_step = 250000; break ; + case AIRBAND:def_step= 8330; break ; + } + frequency_list.push_back(entry.frequency_a); //Store starting freq and description + description_list.push_back("R:" + to_string_short_freq(entry.frequency_a) + + ">" + to_string_short_freq(entry.frequency_b) + + " S:" + to_string_short_freq(def_step)); + while (frequency_list.size() < MAX_DB_ENTRY && entry.frequency_a <= entry.frequency_b) { //add the rest of the range + entry.frequency_a+=def_step; + frequency_list.push_back(entry.frequency_a); + description_list.push_back(""); //Token (keep showing the last description) + } + } else if ( entry.type == SINGLE) { + frequency_list.push_back(entry.frequency_a); + description_list.push_back("S: " + entry.description); + } + show_max(); + } + else + { + break; //No more space: Stop reading the txt file ! + } + } + } + else + { + desc_cycle.set(" NO SCANNER.TXT FILE ..." ); + } + audio::output::stop(); + step_mode.set_by_value(def_step); //Impose the default step into the manual step selector + start_scan_thread(); } void ScannerView::on_statistics_update(const ChannelStatistics& statistics) { - int32_t max_db = statistics.max_db; - - if (timer <= wait) - timer++; - - if (max_db < -trigger) { - if (timer == wait) { - //audio::output::stop(); - scan_thread->set_scanning(true); + if (!scan_thread->is_userpause()) + { + if (timer >= (wait * 10) ) + { + timer=0; + scan_resume(); + } + else if (!timer) + { + if (statistics.max_db > -squelch) { //There is something on the air... + scan_pause(); + timer++; + } + } + else + { + timer++; } - } else { - //audio::output::start(); - scan_thread->set_scanning(false); - timer = 0; + } +} + +void ScannerView::scan_pause() { + if (scan_thread->is_scanning()) { + scan_thread->set_scanning(false); // WE STOP SCANNING + audio::output::start(); + } +} + +void ScannerView::scan_resume() { + if (!scan_thread->is_scanning()) { + audio::output::stop(); + scan_thread->set_scanning(true); // WE RESCAN } } @@ -211,4 +318,60 @@ void ScannerView::on_headphone_volume_changed(int32_t v) { receiver_model.set_headphone_volume(new_volume); } -} /* namespace ui */ +size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thread->stop(); After this do a start_scan_thread() + using option_t = std::pair; + using options_t = std::vector; + options_t bw; + field_bw.on_change = [this](size_t n, OptionsField::value_t) { }; + + switch (new_mod) { + case NFM: //bw 16k (2) default + bw.emplace_back("8k5", 0); + bw.emplace_back("11k", 0); + bw.emplace_back("16k", 0); + field_bw.set_options(bw); + + baseband::run_image(portapack::spi_flash::image_tag_nfm_audio); + receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); + field_bw.set_selected_index(2); + receiver_model.set_nbfm_configuration(field_bw.selected_index()); + field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); }; + receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000); + break; + case AM: + bw.emplace_back("DSB", 0); + bw.emplace_back("USB", 0); + bw.emplace_back("LSB", 0); + field_bw.set_options(bw); + + baseband::run_image(portapack::spi_flash::image_tag_am_audio); + receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); + field_bw.set_selected_index(0); + receiver_model.set_am_configuration(field_bw.selected_index()); + field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); }; + receiver_model.set_sampling_rate(2000000);receiver_model.set_baseband_bandwidth(2000000); + break; + case WFM: + bw.emplace_back("16k", 0); + field_bw.set_options(bw); + + baseband::run_image(portapack::spi_flash::image_tag_wfm_audio); + receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio); + field_bw.set_selected_index(0); + receiver_model.set_wfm_configuration(field_bw.selected_index()); + field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); }; + receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(2000000); + break; + } + + return mod_step[new_mod]; + +} + +void ScannerView::start_scan_thread() { + receiver_model.enable(); + receiver_model.set_squelch_level(0); + scan_thread = std::make_unique(frequency_list); +} + +} /* namespace ui */ \ No newline at end of file diff --git a/firmware/application/apps/ui_scanner.hpp b/firmware/application/apps/ui_scanner.hpp index b5d17c22..354700af 100644 --- a/firmware/application/apps/ui_scanner.hpp +++ b/firmware/application/apps/ui_scanner.hpp @@ -20,20 +20,38 @@ * Boston, MA 02110-1301, USA. */ +#include "ui.hpp" #include "receiver_model.hpp" #include "ui_receiver.hpp" #include "ui_font_fixed_8x16.hpp" +#include "ui_spectrum.hpp" #include "freqman.hpp" +#include "log_file.hpp" +#include "analog_audio_app.hpp" + + +#define MAX_DB_ENTRY 500 namespace ui { +enum modulation_type { AM = 0,WFM,NFM }; + +string const mod_name[3] = {"AM", "WFM", "NFM"}; +size_t const mod_step[3] = {9000, 100000, 12500 }; + class ScannerThread { public: ScannerThread(std::vector frequency_list); ~ScannerThread(); - + void set_scanning(const bool v); + bool is_scanning(); + + void set_userpause(const bool v); + bool is_userpause(); + + void stop(); ScannerThread(const ScannerThread&) = delete; ScannerThread(ScannerThread&&) = delete; @@ -45,38 +63,64 @@ private: Thread* thread { nullptr }; bool _scanning { true }; - + bool _userpause { false }; static msg_t static_fn(void* arg); - void run(); }; class ScannerView : public View { public: - ScannerView(NavigationView&); + ScannerView(NavigationView& nav); ~ScannerView(); void focus() override; + + void big_display_freq(rf::Frequency f); + + const Style style_grey { // scanning + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::grey(), + }; - std::string title() const override { return "Scanner"; }; + const Style style_green { //Found signal + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::green(), + }; + + std::string title() const override { return "SCANNER"; }; + std::vector frequency_list{ }; + std::vector description_list { }; + +//void set_parent_rect(const Rect new_parent_rect) override; private: + NavigationView& nav_; + + void start_scan_thread(); + size_t change_mode(uint8_t mod_type); + void show_max(); + void scan_pause(); + void scan_resume(); + void on_statistics_update(const ChannelStatistics& statistics); void on_headphone_volume_changed(int32_t v); void handle_retune(uint32_t i); - - std::vector frequency_list { }; - std::vector description_list { }; - int32_t trigger { 0 }; + + jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual scan task too... int32_t squelch { 0 }; uint32_t timer { 0 }; uint32_t wait { 0 }; + size_t def_step { 0 }; freqman_db database { }; Labels labels { - { { 0 * 8, 0 * 16 }, "LNA: TRIGGER: /99 VOL:", Color::light_grey() }, - { { 0 * 8, 1 * 16 }, "VGA: SQUELCH: /99 AMP:", Color::light_grey() }, - { { 0 * 8, 2 * 16 }, " BW: WAIT:", Color::light_grey() }, + { { 0 * 8, 0 * 16 }, "LNA: VGA: AMP: VOL:", Color::light_grey() }, + { { 0 * 8, 1* 16 }, "BW: SQUELCH: /99 WAIT:", Color::light_grey() }, + { { 3 * 8, 10 * 16 }, "START END MANUAL", Color::light_grey() }, + { { 0 * 8, 14 * 16 }, "MODE:", Color::light_grey() }, + { { 11 * 8, 14 * 16 }, "STEP:", Color::light_grey() }, }; LNAGainField field_lna { @@ -84,15 +128,15 @@ private: }; VGAGainField field_vga { - { 4 * 8, 1 * 16 } + { 11 * 8, 0 * 16 } }; RFAmpField field_rf_amp { - { 28 * 8, 1 * 16 } + { 18 * 8, 0 * 16 } }; NumberField field_volume { - { 28 * 8, 0 * 16 }, + { 24 * 8, 0 * 16 }, 2, { 0, 99 }, 1, @@ -100,25 +144,13 @@ private: }; OptionsField field_bw { - { 4 * 8, 2 * 16 }, - 3, - { - { "8k5", 0 }, - { "11k", 0 }, - { "16k", 0 }, - } - }; + { 3 * 8, 1 * 16 }, + 4, + { } + }; - NumberField field_trigger { - { 16 * 8, 0 * 16 }, - 2, - { 0, 99 }, - 1, - ' ', - }; - NumberField field_squelch { - { 16 * 8, 1 * 16 }, + { 15 * 8, 1 * 16 }, 2, { 0, 99 }, 1, @@ -126,20 +158,84 @@ private: }; NumberField field_wait { - { 16 * 8, 2 * 16 }, + { 26 * 8, 1 * 16 }, 2, { 0, 99 }, 1, ' ', }; + RSSI rssi { + { 0 * 16, 2 * 16, 15 * 16, 8 }, + }; + Text text_cycle { - { 0, 5 * 16, 240, 16 }, - "--/--" + { 0, 3 * 16, 3 * 8, 16 }, }; + + Text text_max { + { 4 * 8, 3 * 16, 18 * 8, 16 }, + }; + Text desc_cycle { - {0, 6 * 16, 240, 16 }, - " " + {0, 4 * 16, 240, 16 }, + }; + + BigFrequency big_display { //Show frequency in glamour + { 4, 6 * 16, 28 * 8, 52 }, + 0 + }; + + Button button_manual_start { + { 0 * 8, 11 * 16, 11 * 8, 28 }, + "" + }; + + Button button_manual_end { + { 12 * 8, 11 * 16, 11 * 8, 28 }, + "" + }; + + Button button_manual_scan { + { 24 * 8, 11 * 16, 6 * 8, 28 }, + "SCAN" + }; + + OptionsField field_mode { + { 5 * 8, 14 * 16 }, + 6, + { + { " AM ", 0 }, + { " WFM ", 1 }, + { " NFM ", 2 }, + } + }; + + OptionsField step_mode { + { 17 * 8, 14 * 16 }, + 12, + { + { "5Khz (SA AM)", 5000 }, + { "9Khz (EU AM)", 9000 }, + { "10Khz(US AM)", 10000 }, + { "50Khz (FM1)", 50000 }, + { "100Khz(FM2)", 100000 }, + { "6.25khz(NFM)", 6250 }, + { "12.5khz(NFM)", 12500 }, + { "25khz (N1)", 25000 }, + { "250khz (N2)", 250000 }, + { "8.33khz(AIR)", 8330 } + } + }; + + Button button_pause { + { 12, 17 * 16, 96, 24 }, + "PAUSE" + }; + + Button button_audio_app { + { 124, 17 * 16, 96, 24 }, + "AUDIO APP" }; std::unique_ptr scan_thread { }; @@ -160,4 +256,4 @@ private: }; }; -} /* namespace ui */ +} /* namespace ui */ \ No newline at end of file diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index daaad1ef..dca1f364 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -48,11 +48,28 @@ enum freqman_entry_type { RANGE }; +//Entry step placed for AlainD freqman version (or any other enhanced version) +enum freqman_entry_step { + STEP_DEF = 0, // default + AM_US, // 10 Khz AM/CB + AM_EUR, // 9 Khz LW/MW + NFM_1, // 12,5 Khz (Analogic PMR 446) + NFM_2, // 6,25 Khz (Digital PMR 446) + FM_1, // 100 Khz + FM_2, // 50 Khz + N_1, // 25 Khz + N_2, // 250 Khz + AIRBAND, // AIRBAND 8,33 Khz + ERROR_STEP +}; + +// freqman_entry_step step added, as above, to provide compatibility / future enhancement. struct freqman_entry { rf::Frequency frequency_a { 0 }; rf::Frequency frequency_b { 0 }; std::string description { }; freqman_entry_type type { }; + freqman_entry_step step { }; }; using freqman_db = std::vector; diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index a62ce1ae..fd7fb1a3 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -113,7 +113,8 @@ std::string to_string_dec_int( } std::string to_string_short_freq(const uint64_t f) { - auto final_str = to_string_dec_int(f / 1000000, 4) + "." + to_string_dec_int((f / 100) % 10000, 4, '0'); + //was... to_string_dec_int(f / 1000000,4) + auto final_str = to_string_dec_int(f / 1000000) + "." + to_string_dec_int((f / 100) % 10000, 4, '0'); return final_str; }