diff --git a/firmware/application/apps/ui_scanner.cpp b/firmware/application/apps/ui_scanner.cpp index 1e68d081d..65c4788df 100644 --- a/firmware/application/apps/ui_scanner.cpp +++ b/firmware/application/apps/ui_scanner.cpp @@ -22,11 +22,6 @@ #include "ui_scanner.hpp" -#include "baseband_api.hpp" -#include "string_format.hpp" -#include "audio.hpp" - - using namespace portapack; namespace ui { @@ -58,12 +53,22 @@ bool ScannerThread::is_scanning() { return _scanning; } -void ScannerThread::set_userpause(const bool v) { - _userpause = v; +void ScannerThread::set_freq_lock(const uint32_t v) { + _freq_lock = v; } -bool ScannerThread::is_userpause() { - return _userpause; +uint32_t ScannerThread::is_freq_lock() { + return _freq_lock; +} + +void ScannerThread::set_freq_del(const uint32_t v) { + _freq_del = v; +} + +void ScannerThread::change_scanning_direction() { + _fwd = !_fwd; + chThdSleepMilliseconds(300); //Give some pause after reversing scanning direction + } msg_t ScannerThread::static_fn(void* arg) { @@ -73,29 +78,74 @@ msg_t ScannerThread::static_fn(void* arg) { } void ScannerThread::run() { - if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ... + if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ... RetuneMessage message { }; - uint32_t frequency_index = 0; + uint32_t frequency_index = frequency_list_.size(); + bool restart_scan = false; //Flag whenever scanning is restarting after a pause while( !chThdShouldTerminate() ) { - if (_scanning) { - // Retune - receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); - message.range = frequency_index; + if (_scanning) { //Scanning + if (_freq_lock == 0) { //normal scanning (not performing freq_lock) + if (!restart_scan) { //looping at full speed + if (_fwd) { //forward + frequency_index++; + if (frequency_index >= frequency_list_.size()) + frequency_index = 0; + + } else { //reverse + if (frequency_index < 1) + frequency_index = frequency_list_.size(); + frequency_index--; + } + receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); // Retune + } + else + restart_scan=false; //Effectively skipping first retuning, giving system time + } + message.range = frequency_index; //Inform freq (for coloring purposes also!) EventDispatcher::send_message(message); - - frequency_index++; - if (frequency_index >= frequency_list_.size()) - frequency_index = 0; + } + else { //NOT scanning + if (_freq_del != 0) { //There is a frequency to delete + for (uint16_t i = 0; i < frequency_list_.size(); i++) { //Search for the freq to delete + if (frequency_list_[i] == _freq_del) + { //found: Erase it + frequency_list_.erase(frequency_list_.begin() + i); + if (i==0) //set scan index one place back to compensate + i=frequency_list_.size(); + else + i--; + break; + } + } + _freq_del = 0; //deleted. + } + else { + restart_scan=true; //Flag the need for skipping a cycle when restarting scan + } } - chThdSleepMilliseconds(50); //50 is enough for reception stabilization, increase for more precise scanning ? + chThdSleepMilliseconds(50); //Needed to (eventually) stabilize the receiver into new freq } } } void ScannerView::handle_retune(uint32_t 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 + switch (scan_thread->is_freq_lock()) + { + case 0: //NO FREQ LOCK, ONGOING STANDARD SCANNING + text_cycle.set( to_string_dec_uint(i + 1,3) ); + current_index = i; //since it is an ongoing scan, this is a new index + if (description_list[current_index].size() > 0) desc_cycle.set( description_list[current_index] ); //Show new description + break; + case 1: //STARTING LOCK FREQ + big_display.set_style(&style_yellow); + break; + case MAX_FREQ_LOCK: //FREQ IS STRONG: GREEN and scanner will pause when on_statistics_update() + big_display.set_style(&style_green); + break; + default: //freq lock is checking the signal, do not update display + return; + } + big_display.set(frequency_list[current_index]); //UPDATE the big Freq after 0, 1 or MAX_FREQ_LOCK (at least, for color synching) } void ScannerView::focus() { @@ -109,10 +159,14 @@ ScannerView::~ScannerView() { } void ScannerView::show_max() { //show total number of freqs to scan - if (frequency_list.size() == MAX_DB_ENTRY) + if (frequency_list.size() == MAX_DB_ENTRY) { + text_max.set_style(&style_red); text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)"); - else + } + else { + text_max.set_style(&style_grey); text_max.set( "/ " + to_string_dec_uint(frequency_list.size())); + } } ScannerView::ScannerView( @@ -139,13 +193,23 @@ ScannerView::ScannerView( &step_mode, &button_manual_scan, &button_pause, - &button_audio_app + &button_dir, + &button_audio_app, + &button_mic_app, + &button_add, + &button_remove + }); 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 + //HELPER: Pre-setting a manual range, based on stored frequency + rf::Frequency stored_freq = persistent_memory::tuned_frequency(); + frequency_range.min = stored_freq - 1000000; + button_manual_start.set_text(to_string_short_freq(frequency_range.min)); + frequency_range.max = stored_freq + 1000000; + button_manual_end.set_text(to_string_short_freq(frequency_range.max)); button_manual_start.on_select = [this, &nav](Button& button) { auto new_view = nav_.push(frequency_range.min); @@ -164,39 +228,56 @@ ScannerView::ScannerView( }; 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 { + if ( userpause ) + user_resume(); + else { scan_pause(); - scan_thread->set_userpause(true); - button_pause.set_text("RESUME"); //PAUSED, show resume + button_pause.set_text("RESUME"); //PAUSED, show resume + userpause=true; } }; 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_mic_app.on_select = [this](Button&) { + scan_thread->stop(); + nav_.pop(); + nav_.push(); + }; + + button_remove.on_select = [this](Button&) { + if (frequency_list.size() > current_index) { + if (scan_thread->is_scanning()) //STOP Scanning if necessary + scan_thread->set_scanning(false); + scan_thread->set_freq_del(frequency_list[current_index]); + description_list.erase(description_list.begin() + current_index); + frequency_list.erase(frequency_list.begin() + current_index); + show_max(); //UPDATE new list size on screen + desc_cycle.set(" "); //Clean up description (cosmetic detail) + scan_thread->set_freq_lock(0); //Reset the scanner lock + if ( userpause ) //If user-paused, resume + user_resume(); + } + }; + 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 { + audio::output::stop(); 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) + ">" + "M:" + to_string_short_freq(frequency_range.min) + " >" + to_string_short_freq(frequency_range.max) + " S:" + to_string_short_freq(def_step) ); @@ -207,27 +288,73 @@ ScannerView::ScannerView( description_list.push_back(""); //If empty, will keep showing the last description frequency+=def_step; } - show_max(); + if ( userpause ) //If user-paused, resume + user_resume(); + big_display.set_style(&style_grey); //Back to grey color 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(); + if ( !scan_thread->is_scanning() ) //for some motive, audio output gets stopped. + audio::output::start(); //So if scan was stopped we resume audio + receiver_model.enable(); + }; + + button_dir.on_select = [this](Button&) { + scan_thread->change_scanning_direction(); + if ( userpause ) //If user-paused, resume + user_resume(); + big_display.set_style(&style_grey); //Back to grey color + }; + + button_add.on_select = [this](Button&) { //frequency_list[current_index] + File scanner_file; + auto result = scanner_file.open("FREQMAN/SCANNER.TXT"); //First search if freq is already in txt + if (!result.is_valid()) { + std::string frequency_to_add = "f=" + + to_string_dec_uint(frequency_list[current_index] / 1000) + + to_string_dec_uint(frequency_list[current_index] % 1000UL, 3, '0'); + char one_char[1]; //Read it char by char + std::string line; //and put read line in here + bool found=false; + for (size_t pointer=0; pointer < scanner_file.size();pointer++) { + + scanner_file.seek(pointer); + scanner_file.read(one_char, 1); + if ((int)one_char[0] > 31) { //ascii space upwards + line += one_char[0]; //Add it to the textline + } + else if (one_char[0] == '\n') { //New Line + if (line.compare(0, frequency_to_add.size(),frequency_to_add) == 0) { + found=true; + break; + } + line.clear(); //Ready for next textline + } + } + if (found) { + nav_.display_modal("Error", "Frequency already exists"); + big_display.set(frequency_list[current_index]); //After showing an error + } + else { + auto result = scanner_file.append("FREQMAN/SCANNER.TXT"); //Second: append if it is not there + scanner_file.write_line(frequency_to_add + ",d=ADD FQ"); + } + } else + { + nav_.display_modal("Error", "Cannot open SCANNER.TXT\nfor appending freq."); + big_display.set(frequency_list[current_index]); //After showing an error + } }; //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_squelch.on_change = [this](int32_t v) { squelch = v; }; field_squelch.set_value(-10); 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); }; // LEARN FREQUENCIES @@ -249,7 +376,7 @@ ScannerView::ScannerView( } 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) + + " >" + 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; @@ -278,21 +405,30 @@ ScannerView::ScannerView( } void ScannerView::on_statistics_update(const ChannelStatistics& statistics) { - if (!scan_thread->is_userpause()) + if ( !userpause ) //Scanning not user-paused { if (timer >= (wait * 10) ) { - timer=0; + timer = 0; scan_resume(); } else if (!timer) { - if (statistics.max_db > -squelch) { //There is something on the air... - scan_pause(); - timer++; - } + if (statistics.max_db > squelch ) { //There is something on the air...(statistics.max_db > -squelch) + if (scan_thread->is_freq_lock() >= MAX_FREQ_LOCK) { //checking time reached + scan_pause(); + timer++; + } else { + scan_thread->set_freq_lock( scan_thread->is_freq_lock() + 1 ); //in lock period, still analyzing the signal + } + } else { //There is NOTHING on the air + if (scan_thread->is_freq_lock() > 0) { //But are we already in freq_lock ? + big_display.set_style(&style_grey); //Back to grey color + scan_thread->set_freq_lock(0); //Reset the scanner lock, since there is no signal + } + } } - else + else //Ongoing wait time { timer++; } @@ -301,16 +437,23 @@ void ScannerView::on_statistics_update(const ChannelStatistics& statistics) { void ScannerView::scan_pause() { if (scan_thread->is_scanning()) { + scan_thread->set_freq_lock(0); //Reset the scanner lock (because user paused, or MAX_FREQ_LOCK reached) for next freq scan 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 - } + audio::output::stop(); + big_display.set_style(&style_grey); //Back to grey color + if (!scan_thread->is_scanning()) + scan_thread->set_scanning(true); // RESUME! +} + +void ScannerView::user_resume() { + timer = wait * 10; //Will trigger a scan_resume() on_statistics_update, also advancing to next freq. + button_pause.set_text("PAUSE"); //Show button for pause + userpause=false; //Resume scanning } void ScannerView::on_headphone_volume_changed(int32_t v) { @@ -365,7 +508,6 @@ size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thre } return mod_step[new_mod]; - } void ScannerView::start_scan_thread() { diff --git a/firmware/application/apps/ui_scanner.hpp b/firmware/application/apps/ui_scanner.hpp index 354700af7..d2befbfe9 100644 --- a/firmware/application/apps/ui_scanner.hpp +++ b/firmware/application/apps/ui_scanner.hpp @@ -22,16 +22,20 @@ #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" +#include "audio.hpp" +#include "ui_mictx.hpp" +#include "portapack_persistent_memory.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "file.hpp" #define MAX_DB_ENTRY 500 +#define MAX_FREQ_LOCK 10 //50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous namespace ui { @@ -48,8 +52,12 @@ public: void set_scanning(const bool v); bool is_scanning(); - void set_userpause(const bool v); - bool is_userpause(); + void set_freq_lock(const uint32_t v); + uint32_t is_freq_lock(); + + void set_freq_del(const uint32_t v); + + void change_scanning_direction(); void stop(); @@ -63,7 +71,9 @@ private: Thread* thread { nullptr }; bool _scanning { true }; - bool _userpause { false }; + bool _fwd { true }; + uint32_t _freq_lock { 0 }; + uint32_t _freq_del { 0 }; static msg_t static_fn(void* arg); void run(); }; @@ -83,12 +93,24 @@ public: .foreground = Color::grey(), }; + const Style style_yellow { //Found signal + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::dark_yellow(), + }; + const Style style_green { //Found signal .font = font::fixed_8x16, .background = Color::black(), .foreground = Color::green(), }; + const Style style_red { //erasing freq + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::red(), + }; + std::string title() const override { return "SCANNER"; }; std::vector frequency_list{ }; std::vector description_list { }; @@ -103,6 +125,7 @@ private: void show_max(); void scan_pause(); void scan_resume(); + void user_resume(); void on_statistics_update(const ChannelStatistics& statistics); void on_headphone_volume_changed(int32_t v); @@ -114,13 +137,15 @@ private: uint32_t wait { 0 }; size_t def_step { 0 }; freqman_db database { }; + uint32_t current_index { 0 }; + bool userpause { false }; Labels labels { { { 0 * 8, 0 * 16 }, "LNA: VGA: AMP: VOL:", Color::light_grey() }, - { { 0 * 8, 1* 16 }, "BW: SQUELCH: /99 WAIT:", Color::light_grey() }, + { { 0 * 8, 1* 16 }, "BW: SQUELCH: db 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() }, + { { 0 * 8, (26 * 8) + 4 }, "MODE:", Color::light_grey() }, + { { 11 * 8, (26 * 8) + 4 }, "STEP:", Color::light_grey() }, }; LNAGainField field_lna { @@ -151,8 +176,8 @@ private: NumberField field_squelch { { 15 * 8, 1 * 16 }, - 2, - { 0, 99 }, + 3, + { -90, 20 }, 1, ' ', }; @@ -202,7 +227,7 @@ private: }; OptionsField field_mode { - { 5 * 8, 14 * 16 }, + { 5 * 8, (26 * 8) + 4 }, 6, { { " AM ", 0 }, @@ -212,7 +237,7 @@ private: }; OptionsField step_mode { - { 17 * 8, 14 * 16 }, + { 17 * 8, (26 * 8) + 4 }, 12, { { "5Khz (SA AM)", 5000 }, @@ -229,13 +254,33 @@ private: }; Button button_pause { - { 12, 17 * 16, 96, 24 }, + { 0, (15 * 16) - 4, 72, 28 }, "PAUSE" }; + Button button_dir { + { 0, (35 * 8) - 4, 72, 28 }, + "FW> scan_thread { };