diff --git a/firmware/application/apps/ui_scanner.cpp b/firmware/application/apps/ui_scanner.cpp index fc6365638..08e736e5e 100644 --- a/firmware/application/apps/ui_scanner.cpp +++ b/firmware/application/apps/ui_scanner.cpp @@ -27,15 +27,15 @@ using namespace portapack; namespace ui { -ScannerThread::ScannerThread( - std::vector frequency_list -) : frequency_list_ { std::move(frequency_list) } +ScannerThread::ScannerThread(std::vector frequency_list) : frequency_list_ { std::move(frequency_list) } { + _manual_search = false; create_thread(); } -ScannerThread::ScannerThread(const jammer::jammer_range_t& frequency_range, size_t def_step) : frequency_range_(frequency_range), def_step_(def_step) +ScannerThread::ScannerThread(const jammer::jammer_range_t& frequency_range, size_t def_step_hz) : frequency_range_(frequency_range), def_step_hz_(def_step_hz) { + _manual_search = true; create_thread(); } @@ -56,6 +56,7 @@ void ScannerThread::stop() { } } +//Set by "userpause" void ScannerThread::set_scanning(const bool v) { _scanning = v; } @@ -72,14 +73,26 @@ uint32_t ScannerThread::is_freq_lock() { return _freq_lock; } -void ScannerThread::set_freq_del(const uint32_t v) { +//Delete an entry from frequency list +//Caller must pause scan_thread AND can't delete a second one until this field is cleared +void ScannerThread::set_freq_del(const rf::Frequency v) { _freq_del = v; } -void ScannerThread::change_scanning_direction() { - _fwd = !_fwd; - chThdSleepMilliseconds(300); //Give some pause after reversing scanning direction +//Force a one-time forward or reverse frequency index change; OK to do this without pausing scan thread +//(used when rotary encoder is turned) +void ScannerThread::set_index_stepper(const int32_t v) { + _index_stepper = v; +} +//Set scanning direction; OK to do this without pausing scan_thread +void ScannerThread::set_scanning_direction(bool fwd) { + int32_t new_stepper = fwd? 1 : -1; + + if (_stepper != new_stepper) { + _stepper = new_stepper; + chThdSleepMilliseconds(300); //Give some pause after reversing scanning direction + } } msg_t ScannerThread::static_fn(void* arg) { @@ -89,117 +102,136 @@ msg_t ScannerThread::static_fn(void* arg) { } void ScannerThread::run() { - if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ... - RetuneMessage message { }; - uint32_t frequency_index = frequency_list_.size(); - bool restart_scan = false; //Flag whenever scanning is restarting after a pause - while( !chThdShouldTerminate() ) { - 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; + RetuneMessage message { }; - } 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 + if (!_manual_search && frequency_list_.size()) { //IF NOT MANUAL MODE AND THERE IS A FREQUENCY LIST ... + int32_t size = frequency_list_.size(); + int32_t frequency_index = (_stepper > 0)? size : 0; //Forcing wraparound to starting frequency on 1st pass + + while( !chThdShouldTerminate() ) { + bool force_one_step = (_index_stepper != 0); + int32_t step = force_one_step? _index_stepper : _stepper; //_index_stepper direction takes priority + + if (_scanning || force_one_step) { //Scanning, or paused and using rotary encoder + if ((_freq_lock == 0) || force_one_step) { //normal scanning (not performing freq_lock) + frequency_index += step; + if (frequency_index >= size) //Wrap + frequency_index = 0; + else if (frequency_index < 0) + frequency_index = size-1; + + if (force_one_step) + _index_stepper = 0; + + receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); // Retune } message.freq = frequency_list_[frequency_index]; message.range = frequency_index; //Inform freq (for coloring purposes also!) - EventDispatcher::send_message(message); - } - else { //NOT scanning - if (_freq_del != 0) { //There is a frequency to delete - for (uint32_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 - } + EventDispatcher::send_message(message); + } + else if (_freq_del != 0) { //There is a frequency to delete + for (int32_t i = 0; i < size; i++) { //Search for the freq to delete + if (frequency_list_[i] == _freq_del) + { //found: Erase it + frequency_list_.erase(frequency_list_.begin() + i); + size = frequency_list_.size(); + break; + } + } + _freq_del = 0; //deleted. } - chThdSleepMilliseconds(50); //Needed to (eventually) stabilize the receiver into new freq - } - }else if(def_step_ > 0) // manual mode - { - RetuneMessage message { }; - int64_t frequency_index = 0; - int64_t size = (frequency_range_.max - frequency_range_.min) / def_step_; - bool restart_scan = false; //Flag whenever scanning is restarting after a pause - while( !chThdShouldTerminate() ) { - 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 >= size) - frequency_index = 0; - } else { //reverse - if (frequency_index < 1) - frequency_index = size; - frequency_index--; - } - receiver_model.set_tuning_frequency(frequency_range_.min + frequency_index * def_step_); // Retune - } - else - restart_scan=false; //Effectively skipping first retuning, giving system time + chThdSleepMilliseconds(SCANNER_SLEEP_MS); //Needed to (eventually) stabilize the receiver into new freq + } + }else if(_manual_search && (def_step_hz_ > 0)) // manual search range mode + { + int64_t size = (frequency_range_.max - frequency_range_.min) / def_step_hz_; + int64_t frequency_index = (_stepper > 0)? size : 0; //Forcing wraparound to starting frequency on 1st pass + + while( !chThdShouldTerminate() ) { + bool force_one_step = (_index_stepper != 0); + int32_t step = force_one_step? _index_stepper : _stepper; //_index_stepper direction takes priority + + if (_scanning || force_one_step) { //Scanning, or paused and using rotary encoder + if ((_freq_lock == 0) || force_one_step) { //normal scanning (not performing freq_lock) + frequency_index += step; + if (frequency_index >= size) //Wrap + frequency_index = 0; + else if (frequency_index < 0) + frequency_index = size-1; + + if (force_one_step) + _index_stepper = 0; + + receiver_model.set_tuning_frequency(frequency_range_.min + frequency_index * def_step_hz_); // Retune } - message.freq = frequency_range_.min + frequency_index * def_step_; - message.range = 0; //Inform freq (for coloring purposes also!) + message.freq = frequency_range_.min + frequency_index * def_step_hz_; + message.range = 0; //Inform freq (for coloring purposes also!) EventDispatcher::send_message(message); } - else { //NOT scanning - restart_scan=true; //Flag the need for skipping a cycle when restarting scan - } - chThdSleepMilliseconds(50); //Needed to (eventually) stabilize the receiver into new freq + + chThdSleepMilliseconds(SCANNER_SLEEP_MS); //Needed to (eventually) stabilize the receiver into new freq + } + } +} + +void ScannerView::bigdisplay_update(int32_t v) { + if (v != bigdisplay_current_color) { + if (v!=-1) + bigdisplay_current_color = v; // -1 means refresh display but keep current color + + switch (bigdisplay_current_color) { + case BDC_GREY: big_display.set_style(&style_grey); break; + case BDC_YELLOW:big_display.set_style(&style_yellow); break; + case BDC_GREEN: big_display.set_style(&style_green); break; + case BDC_RED: big_display.set_style(&style_red); break; + default: break; + } + + // update frequency display + bigdisplay_current_frequency = current_frequency; + big_display.set(bigdisplay_current_frequency); + } else { + + // no style change, but update frequency display if it's changed + if (current_frequency != bigdisplay_current_frequency) { + bigdisplay_current_frequency = current_frequency; + big_display.set(bigdisplay_current_frequency); } } } void ScannerView::handle_retune(int64_t freq, uint32_t freq_idx) { - current_index = freq_idx; //since it is an ongoing scan, this is a new index current_frequency = freq; - switch (scan_thread->is_freq_lock()) - { - case 0: //NO FREQ LOCK, ONGOING STANDARD SCANNING + if (scan_thread) { + switch (scan_thread->is_freq_lock()) + { + case 0: //NO FREQ LOCK, ONGOING STANDARD SCANNING + bigdisplay_update(BDC_GREY); + break; + case 1: //STARTING LOCK FREQ + bigdisplay_update(BDC_YELLOW); + break; + case MAX_FREQ_LOCK: //FREQ IS STRONG: GREEN and scanner will pause when on_statistics_update() + bigdisplay_update(BDC_GREEN); + break; + default: //freq lock is checking the signal, do not update display + return; + } + } + + if (!manual_search) { if (frequency_list.size() > 0) { - text_cycle.set( to_string_dec_uint(freq_idx + 1,3) ); + text_current_index.set( to_string_dec_uint(freq_idx + 1, 3) ); } - if (freq_idx < description_list.size() && description_list[freq_idx].size() > 0) { - desc_cycle.set( description_list[freq_idx] ); //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; + if (freq_idx < description_list.size() && description_list[freq_idx].size() > 1) + desc_current_index.set( description_list[freq_idx] ); //Show description from file + else + desc_current_index.set(desc_freq_list_scan); //Show Scan file name (no description in file) } - big_display.set(freq); //UPDATE the big Freq after 0, 1 or MAX_FREQ_LOCK (at least, for color synching) } void ScannerView::focus() { @@ -214,14 +246,16 @@ ScannerView::~ScannerView() { baseband::shutdown(); } -void ScannerView::show_max() { //show total number of freqs to scan +void ScannerView::show_max_index() { //show total number of freqs to scan + text_current_index.set("---"); + if (frequency_list.size() == FREQMAN_MAX_PER_FILE ) { - text_max.set_style(&style_red); - text_max.set( "/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE ) + " (DB MAX!)"); + text_max_index.set_style(&style_red); + text_max_index.set( "/ " + to_string_dec_uint(FREQMAN_MAX_PER_FILE ) + " (DB MAX!)"); } else { - text_max.set_style(&style_grey); - text_max.set( "/ " + to_string_dec_uint(frequency_list.size())); + text_max_index.set_style(&style_grey); + text_max_index.set( "/ " + to_string_dec_uint(frequency_list.size())); } } @@ -237,18 +271,20 @@ ScannerView::ScannerView( &field_volume, &field_bw, &field_squelch, - &field_wait, + &field_browse_wait, + &field_lock_wait, &button_load, + &button_clear, &rssi, - &text_cycle, - &text_max, - &desc_cycle, + &text_current_index, + &text_max_index, + &desc_current_index, &big_display, &button_manual_start, &button_manual_end, &field_mode, - &step_mode, - &button_manual_scan, + &field_step, + &button_manual_search, &button_pause, &button_dir, &button_audio_app, @@ -258,9 +294,16 @@ ScannerView::ScannerView( }); - def_step = change_mode(AM); //Start on AM - field_mode.set_by_value(AM); //Reflect the mode into the manual selector + //Populate option text for these fields + freqman_set_modulation_option( field_mode ); + freqman_set_step_option( field_step ); + // Default starting modulation (these may be overridden in SCANNER.TXT) + change_mode(AM_MODULATION); //Default modulation + field_mode.set_by_value(AM_MODULATION); //Reflect the mode into the manual selector + field_step.set_by_value(9000); //Default step interval (Hz) + + //FUTURE: perhaps additional settings should be stored in persistent memory vs using defaults //HELPER: Pre-setting a manual range, based on stored frequency rf::Frequency stored_freq = persistent_memory::tuned_frequency(); frequency_range.min = stored_freq - 1000000; @@ -268,8 +311,8 @@ ScannerView::ScannerView( frequency_range.max = stored_freq + 1000000; button_manual_end.set_text(to_string_short_freq(frequency_range.max)); + // Button to load txt files from the FREQMAN folder button_load.on_select = [this, &nav](Button&) { - // load txt files from the FREQMAN folder auto open_view = nav.push(".TXT"); open_view->on_changed = [this](std::filesystem::path new_file_path) { @@ -287,6 +330,23 @@ ScannerView::ScannerView( }; }; + // Button to clear in-memory frequency list + button_clear.on_select = [this, &nav](Button&) { + if (scan_thread && frequency_list.size()) { + scan_thread->stop(); //STOP SCANNER THREAD + frequency_list.clear(); + description_list.clear(); + + show_max_index(); //UPDATE new list size on screen + text_current_index.set(""); + desc_current_index.set(desc_freq_list_scan); + scan_thread->set_freq_lock(0); //Reset the scanner lock + + //FUTURE: Consider switching to manual search mode automatically after clear (but would need to validate freq range) + } + }; + + // Button to configure starting frequency for a manual range search 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) { @@ -295,6 +355,7 @@ ScannerView::ScannerView( }; }; + // Button to configure ending frequency for a manual range search 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) { @@ -303,86 +364,128 @@ ScannerView::ScannerView( }; }; - button_pause.on_select = [this](Button&) { + // Button to pause/resume scan (note that some other buttons will trigger resume also) + button_pause.on_select = [this](ButtonWithEncoder&) { if ( userpause ) user_resume(); else { scan_pause(); - button_pause.set_text("RESUME"); //PAUSED, show resume + button_pause.set_text(""); //PAUSED, show resume userpause=true; } }; + // Encoder dial causes frequency change when focus is on pause button + button_pause.on_change = [this]() { + int32_t encoder_delta { (button_pause.get_encoder_delta()>0)? 1 : -1 }; + + if (scan_thread) + scan_thread->set_index_stepper(encoder_delta); + + //Restart browse timer when frequency changes + if (browse_timer != 0) + browse_timer = 1; + + button_pause.set_encoder_delta( 0 ); + }; + + // Button to switch to Audio app button_audio_app.on_select = [this](Button&) { - scan_thread->stop(); + if (scan_thread) + scan_thread->stop(); nav_.pop(); nav_.push(); }; + // Button to switch to Mic app button_mic_app.on_select = [this](Button&) { - scan_thread->stop(); + if (scan_thread) + scan_thread->stop(); nav_.pop(); nav_.push(); }; + // Button to delete current frequency from scan Freq List 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); + if (scan_thread && (frequency_list.size() > current_index)) { + scan_thread->set_scanning(false); //PAUSE Scanning if necessary + + // Remove frequency from the Freq List in memory (it is not removed from the file) 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) + + show_max_index(); //UPDATE new list size on screen + desc_current_index.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"); + // Button to toggle between Manual Search and Freq List Scan modes + button_manual_search.on_select = [this](Button&) { + if (!manual_search) { + 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 { + manual_search = true; //Switch to Manual Search mode + } } 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 - - show_max(); - desc_cycle.set(""); - text_max.set(""); - text_cycle.set(""); - - if ( userpause ) //If user-paused, resume - user_resume(); - big_display.set_style(&style_grey); //Back to grey color - start_scan_thread(frequency_range, def_step); //RESTART SCANNER THREAD + manual_search = false; // Switch to List Scan mode } + + audio::output::stop(); + + if (scan_thread) + scan_thread->stop(); //STOP SCANNER THREAD + + if ( userpause ) //If user-paused, resume + user_resume(); + + start_scan_thread(); //RESTART SCANNER THREAD in selected mode }; + // Mode field was changed (AM/NFM/WFM) field_mode.on_change = [this](size_t, OptionsField::value_t v) { receiver_model.disable(); baseband::shutdown(); - change_mode(v); - if ( !scan_thread->is_scanning() ) //for some motive, audio output gets stopped. - audio::output::start(); //So if scan was stopped we resume audio + change_mode((freqman_index_t)v); + if ( scan_thread && !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 + // Step field was changed (Hz) -- only affects manual Search mode + field_step.on_change = [this](size_t, OptionsField::value_t v) { + (void)v; // prevent compiler Unused warning + + if ( manual_search && scan_thread ) { + //Restart scan thread with new step value + scan_thread->stop(); //STOP SCANNER THREAD + + //Resuming pause automatically + //FUTURE: Consider whether we should stay in Pause mode... + if ( userpause ) //If user-paused, resume + user_resume(); + + start_scan_thread(); //RESTART SCANNER THREAD in Manual Search mode + } }; + // Button to toggle Forward/Reverse + button_dir.on_select = [this](Button&) { + fwd = !fwd; + if (scan_thread) + scan_thread->set_scanning_direction(fwd); + if ( userpause ) //If user-paused, resume + user_resume(); + button_dir.set_text(fwd? "REVERSE":"FORWARD"); + bigdisplay_update(BDC_GREY); //Back to grey color + }; - button_add.on_select = [this](Button&) { // current_frequency - + // Button to add current frequency (found during Search) to the Scan Frequency List + button_add.on_select = [this](Button&) { File scanner_file; const std::string freq_file_path = "FREQMAN/" + loaded_file_name + ".TXT"; auto result = scanner_file.open(freq_file_path); //First search if freq is already in txt @@ -429,22 +532,38 @@ ScannerView::ScannerView( if (found) { nav_.display_modal("Error", "Frequency already exists"); - big_display.set(current_frequency); //After showing an error + bigdisplay_update(-1); //After showing an error } else { scanner_file.append(freq_file_path); //Second: append if it is not there - scanner_file.write_line(frequency_to_add + ",d=ADD FQ"); + scanner_file.write_line(frequency_to_add); + + // Add to frequency_list in memory too, since we can now switch back from manual mode + // Note that we are allowing freqs to be added to file (code above) that exceed the max count we can load into memory + if (frequency_list.size() < FREQMAN_MAX_PER_FILE) { + frequency_list.push_back(current_frequency); + description_list.push_back(""); + + show_max_index(); //Display updated frequency list size + } } } else { nav_.display_modal("Error", "Cannot open " + loaded_file_name + ".TXT\nfor appending freq."); - big_display.set(current_frequency); //After showing an error + bigdisplay_update(-1); //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(-10); + field_browse_wait.on_change = [this](int32_t v) { browse_wait = v; }; + field_browse_wait.set_value(5); + + field_lock_wait.on_change = [this](int32_t v) { lock_wait = v; }; + field_lock_wait.set_value(2); + + 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); }; @@ -455,50 +574,69 @@ ScannerView::ScannerView( void ScannerView::frequency_file_load(std::string file_name, bool stop_all_before) { + bool found_range { false }; + bool found_single { false }; + freqman_index_t def_mod_index { -1 }; + freqman_index_t def_bw_index { -1 }; + freqman_index_t def_step_index { -1 }; + // stop everything running now if required if (stop_all_before) { scan_thread->stop(); frequency_list.clear(); // clear the existing frequency list (expected behavior) description_list.clear(); - def_step = step_mode.selected_index_value(); //Use def_step from manual selector } if ( load_freqman_file(file_name, database) ) { loaded_file_name = file_name; // keep loaded filename in memory for(auto& entry : database) { // READ LINE PER LINE - if (frequency_list.size() < FREQMAN_MAX_PER_FILE ) { //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 ; - case ERROR_STEP: - case STEP_DEF: - default: - def_step = step_mode.selected_index_value(); //Use def_step from manual selector - break ; + if (frequency_list.size() < FREQMAN_MAX_PER_FILE) { //We got space! + // + // Get modulation & bw & step from file if specified + // Note these values could be different for each line in the file, but we only look at the first one + // + // Note that freqman requires a very specific string for these parameters, + // so check syntax in frequency file if specified value isn't being loaded + // + if (def_mod_index == -1) + def_mod_index = entry.modulation; + + if (def_bw_index == -1) + def_bw_index = entry.bandwidth; + + if (def_step_index == -1) + def_step_index = entry.step; + + // Get frequency + if (entry.type == RANGE) { + if (!found_range) { + // Set Start & End Search Range instead of populating the small in-memory frequency table + // NOTE: There may be multiple single frequencies in file, but only one search range is supported. + found_range = true; + frequency_range.min = entry.frequency_a; + button_manual_start.set_text(to_string_short_freq(frequency_range.min)); + frequency_range.max = entry.frequency_b; + button_manual_end.set_text(to_string_short_freq(frequency_range.max)); } - 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).erase(0,1) //euquiq: lame kludge to reduce spacing in step freq - ); - while (frequency_list.size() < FREQMAN_MAX_PER_FILE && 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 if ( entry.type == SINGLE ) { + found_single = true; + frequency_list.push_back(entry.frequency_a); + description_list.push_back(entry.description); + } + else if ( entry.type == HAMRADIO ) { + // For HAM repeaters, add both receive & transmit frequencies to scan list and modify description + // (FUTURE fw versions might handle these differently) + found_single = true; + frequency_list.push_back(entry.frequency_a); + description_list.push_back("R:" + entry.description); + + if ( (entry.frequency_a != entry.frequency_b) && + (frequency_list.size() < FREQMAN_MAX_PER_FILE) ) { + frequency_list.push_back(entry.frequency_b); + description_list.push_back("T:" + entry.description); + } + } } else { @@ -509,63 +647,122 @@ void ScannerView::frequency_file_load(std::string file_name, bool stop_all_befor else { loaded_file_name = "SCANNER"; // back to the default frequency file - desc_cycle.set(" NO " + file_name + ".TXT FILE ..." ); + desc_current_index.set(" NO " + file_name + ".TXT FILE ..." ); } + + desc_freq_list_scan = loaded_file_name+".TXT"; + if (desc_freq_list_scan.length() > 23) { // Truncate description and add ellipses if long file name + desc_freq_list_scan.resize(20); + desc_freq_list_scan = desc_freq_list_scan + "..."; + } + + if ((def_mod_index != -1) && (def_mod_index != (freqman_index_t)field_mode.selected_index_value())) + field_mode.set_by_value( def_mod_index ); //Update mode (also triggers a change callback that disables & reenables RF background) + + if (def_bw_index != -1) //Update BW if specified in file + field_bw.set_selected_index( def_bw_index ); + + if (def_step_index != -1) //Update step if specified in file + field_step.set_selected_index( def_step_index ); + audio::output::stop(); - step_mode.set_by_value(def_step); //Impose the default step into the manual step selector + + if ( userpause ) //If user-paused, resume + user_resume(); + + // Scan list if we found one, otherwise do manual range search + manual_search = !found_single; + start_scan_thread(); } +void ScannerView::update_squelch_while_paused(int32_t max_db) { + //Update audio & color based on signal level even if paused + if (++color_timer > 2) { //Counter to reduce color toggling when weak signal + if (max_db > squelch) { + audio::output::start(); //Re-enable audio when signal goes above squelch + on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause + bigdisplay_update(BDC_GREEN); + } else { + audio::output::stop(); //Silence audio when signal drops below squelch + bigdisplay_update(BDC_GREY); //Back to grey color + } + + color_timer = 0; + } +} + void ScannerView::on_statistics_update(const ChannelStatistics& statistics) { - if ( !userpause ) //Scanning not user-paused + + if (userpause) { + update_squelch_while_paused(statistics.max_db); + } + else if ( scan_thread ) //Scanning not user-paused { - if (timer >= (wait * 10) ) - { - timer = 0; - scan_resume(); + //Resume regardless of signal strength if browse time reached + if ((browse_wait != 0) && (browse_timer >= (browse_wait * STATISTICS_UPDATES_PER_SEC)) ) { + browse_timer = 0; + scan_resume(); //Resume scanning } - else if (!timer) + else { 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++; + if (scan_thread->is_freq_lock() >= MAX_FREQ_LOCK) { //Pause scanning when signal checking time reached + if (!browse_timer) //Don't bother pausing if already paused + scan_pause(); + browse_timer++; // browse_timer!=0 is also an indication that we've paused the scan + update_squelch_while_paused(statistics.max_db); } else { scan_thread->set_freq_lock( scan_thread->is_freq_lock() + 1 ); //in lock period, still analyzing the signal + if (browse_timer) //Continue incrementing browse timer while paused + browse_timer++; } + lock_timer = 0; //Keep resetting lock timer while signal remains } 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 - } + if (!browse_timer) { + // Signal lost and scan was never paused + if (scan_thread->is_freq_lock() > 0) { //But are we already in freq_lock ? + bigdisplay_update(BDC_GREY); //Back to grey color + scan_thread->set_freq_lock(0); //Reset the scanner lock, since there is no sig + } + } else { + //Signal lost and scan is still paused + lock_timer++; //Bump paused time + if (lock_timer >= (lock_wait * STATISTICS_UPDATES_PER_SEC)) { //Stay on freq until lock_wait time elapses + browse_timer = 0; + scan_resume(); + } else { + browse_timer++; //Bump browse time too (may hit that limit before lock_timer reached) + update_squelch_while_paused(statistics.max_db); + } + } } } - else //Ongoing wait time - { - timer++; - } } } void ScannerView::scan_pause() { - if (scan_thread->is_scanning()) { + if (scan_thread && 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(); - on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause + scan_thread->set_scanning(false); // WE STOP SCANNING } + audio::output::start(); + on_headphone_volume_changed(field_volume.value()); // quick fix to make sure WM8731S chips don't stay silent after pause } void ScannerView::scan_resume() { audio::output::stop(); - big_display.set_style(&style_grey); //Back to grey color - if (!scan_thread->is_scanning()) - scan_thread->set_scanning(true); // RESUME! + bigdisplay_update(BDC_GREY); //Back to grey color + + if (scan_thread) { + scan_thread->set_index_stepper(fwd? 1 : -1); + 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 + browse_timer = browse_wait * STATISTICS_UPDATES_PER_SEC; //Will trigger a scan_resume() on_statistics_update, also advancing to next freq. + button_pause.set_text(""); //Show button for pause, arrows indicate rotary encoder enabled for freq change userpause=false; //Resume scanning } @@ -574,7 +771,7 @@ void ScannerView::on_headphone_volume_changed(int32_t v) { receiver_model.set_headphone_volume(new_volume); } -size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thread->stop(); After this do a start_scan_thread() +void ScannerView::change_mode(freqman_index_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; @@ -583,39 +780,26 @@ size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thre }; 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 9k" , 0 ); - bw.emplace_back("DSB 6k" , 1 ); - bw.emplace_back("USB+3k" , 2 ); - bw.emplace_back("LSB-3k" , 3 ); - bw.emplace_back("CW " , 4 ); - - field_bw.set_options(bw); - + case AM_MODULATION: + freqman_set_bandwidth_option( new_mod , field_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(3072000); receiver_model.set_baseband_bandwidth(1750000); + break; + case NFM_MODULATION: //bw 16k (2) default + freqman_set_bandwidth_option( new_mod , field_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 WFM: - bw.emplace_back("200k ", 0); - field_bw.set_options(bw); - + case WFM_MODULATION: + freqman_set_bandwidth_option( new_mod , field_bw ); baseband::run_image(portapack::spi_flash::image_tag_wfm_audio); receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio); field_bw.set_selected_index(0); @@ -623,21 +807,29 @@ size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thre 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; + default: + 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); -} + show_max_index(); -void ScannerView::start_scan_thread(const jammer::jammer_range_t& frequency_range, size_t def_step) { - receiver_model.enable(); - receiver_model.set_squelch_level(0); - scan_thread = std::make_unique(frequency_range, def_step); + //Start Scanner Thread + //FUTURE: Consider passing additional control flags (fwd,userpause,etc) to scanner thread at start (perhaps in a data structure) + if (manual_search) { + button_manual_search.set_text("SCAN"); //Update meaning of Manual Scan button + desc_current_index.set(desc_freq_range_search); + scan_thread = std::make_unique(frequency_range, field_step.selected_index_value() ); + } else { + button_manual_search.set_text("SRCH"); //Update meaning of Manual Scan button + desc_current_index.set(desc_freq_list_scan); + scan_thread = std::make_unique(frequency_list); + } + + scan_thread->set_scanning_direction(fwd); } } /* namespace ui */ diff --git a/firmware/application/apps/ui_scanner.hpp b/firmware/application/apps/ui_scanner.hpp index b7160b43a..4d17843cd 100644 --- a/firmware/application/apps/ui_scanner.hpp +++ b/firmware/application/apps/ui_scanner.hpp @@ -33,20 +33,16 @@ #include "string_format.hpp" #include "file.hpp" - -#define MAX_FREQ_LOCK 10 //50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous +#define SCANNER_SLEEP_MS 50 //ms that Scanner Thread sleeps per loop +#define STATISTICS_UPDATES_PER_SEC 10 +#define MAX_FREQ_LOCK 10 //# of 50ms cycles scanner locks into freq when signal detected, to verify signal is not spureous 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(const jammer::jammer_range_t& frequency_range, size_t def_step); + ScannerThread(const jammer::jammer_range_t& frequency_range, size_t def_step_hz); ~ScannerThread(); void set_scanning(const bool v); @@ -55,9 +51,9 @@ public: 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 set_freq_del(const rf::Frequency v); + void set_index_stepper(const int32_t v); + void set_scanning_direction(bool fwd); void stop(); @@ -69,13 +65,16 @@ public: private: std::vector frequency_list_ { }; jammer::jammer_range_t frequency_range_ {false, 0, 0}; - size_t def_step_ { 0 }; + size_t def_step_hz_ { 0 }; Thread* thread { nullptr }; bool _scanning { true }; - bool _fwd { true }; + bool _manual_search { false }; uint32_t _freq_lock { 0 }; - uint32_t _freq_del { 0 }; + rf::Frequency _freq_del { 0 }; + uint32_t _freq_idx { 0 }; + int32_t _stepper { 1 }; + int32_t _index_stepper { 0 }; static msg_t static_fn(void* arg); void run(); void create_thread(); @@ -122,33 +121,50 @@ private: NavigationView& nav_; void start_scan_thread(); - void start_scan_thread(const jammer::jammer_range_t& frequency_range, size_t def_step); - size_t change_mode(uint8_t mod_type); - void show_max(); + void change_mode(freqman_index_t mod_type); + void show_max_index(); void scan_pause(); void scan_resume(); void user_resume(); void frequency_file_load(std::string file_name, bool stop_all_before = false); - + void bigdisplay_update(int32_t); + void update_squelch_while_paused(int32_t max_db); void on_statistics_update(const ChannelStatistics& statistics); void on_headphone_volume_changed(int32_t v); void handle_retune(int64_t freq, uint32_t freq_idx); 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 }; + uint32_t browse_timer { 0 }; + uint32_t lock_timer { 0 }; + uint32_t color_timer { 0 }; + int32_t bigdisplay_current_color { -2 }; + rf::Frequency bigdisplay_current_frequency { 0 }; + uint32_t browse_wait { 0 }; + uint32_t lock_wait { 0 }; freqman_db database { }; std::string loaded_file_name; uint32_t current_index { 0 }; rf::Frequency current_frequency { 0 }; bool userpause { false }; + bool manual_search { false }; + bool fwd { true }; //to preserve direction setting even if scan_thread restarted + + enum bigdisplay_color_type { + BDC_GREY, + BDC_YELLOW, + BDC_GREEN, + BDC_RED + }; + std::string desc_freq_range_search = "SEARCHING..."; + std::string desc_freq_list_scan = ""; + Labels labels { { { 0 * 8, 0 * 16 }, "LNA: VGA: AMP: VOL:", Color::light_grey() }, - { { 0 * 8, 1* 16 }, "BW: SQLCH: db WAIT:", Color::light_grey() }, - { { 3 * 8, 10 * 16 }, "START END MANUAL", Color::light_grey() }, + { { 0 * 8, 1 * 16 }, "BW: SQ: Wsa: Wsl:", Color::light_grey() }, + { { 0 * 8, 10 * 16 }, "SRCH START SEARCH END SWITCH", Color::light_grey() }, + { { 0 * 8, (26 * 8) + 4 }, "MODE:", Color::light_grey() }, { { 11 * 8, (26 * 8) + 4 }, "STEP:", Color::light_grey() }, }; @@ -175,20 +191,28 @@ private: OptionsField field_bw { { 3 * 8, 1 * 16 }, - 4, + 6, { } }; NumberField field_squelch { - { 18 * 8, 1 * 16 }, + { 13 * 8, 1 * 16 }, 3, { -90, 20 }, 1, ' ', }; - NumberField field_wait { - { 27 * 8, 1 * 16 }, + NumberField field_browse_wait { //Signal-Active wait timer - time to wait before moving on even when signal locked + { 21 * 8, 1 * 16 }, + 2, + { 0, 99 }, + 1, + ' ', + }; + + NumberField field_lock_wait { //Signal-Lost wait timer - time to wait before moving on after losing signal lock + { 28 * 8, 1 * 16 }, 2, { 0, 99 }, 1, @@ -199,16 +223,16 @@ private: { 0 * 16, 2 * 16, 15 * 16, 8 }, }; - Text text_cycle { + Text text_current_index { { 0, 3 * 16, 3 * 8, 16 }, }; - Text text_max { + Text text_max_index { { 4 * 8, 3 * 16, 18 * 8, 16 }, }; - Text desc_cycle { - {0, 4 * 16, 240, 16 }, + Text desc_current_index { + {0, 4 * 16, 240 - 6 * 8, 16 }, }; BigFrequency big_display { //Show frequency in glamour @@ -226,46 +250,31 @@ private: "" }; - Button button_manual_scan { + Button button_manual_search { { 24 * 8, 11 * 16, 6 * 8, 28 }, - "SCAN" + "" }; OptionsField field_mode { { 5 * 8, (26 * 8) + 4 }, 6, - { - { " AM ", 0 }, - { " WFM ", 1 }, - { " NFM ", 2 }, - } + { } //Text strings get filled by freqman_set_modulation_option() }; - OptionsField step_mode { + OptionsField field_step { { 17 * 8, (26 * 8) + 4 }, 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 } - } + { } //Text strings get filled by freqman_set_step_option() }; - Button button_pause { + ButtonWithEncoder button_pause { { 0, (15 * 16) - 4, 72, 28 }, - "PAUSE" + "" }; Button button_dir { { 0, (35 * 8) - 4, 72, 28 }, - "FW>