From 223cd7c20d4fac3665c428ecf30486b920a7d88e Mon Sep 17 00:00:00 2001 From: GullCode Date: Sun, 11 Sep 2022 16:07:08 +0200 Subject: [PATCH] added Recon files --- firmware/application/apps/ui_recon.cpp | 1717 +++++++++++++++++ firmware/application/apps/ui_recon.hpp | 403 ++++ .../application/apps/ui_recon_settings.cpp | 301 +++ .../application/apps/ui_recon_settings.hpp | 194 ++ 4 files changed, 2615 insertions(+) create mode 100644 firmware/application/apps/ui_recon.cpp create mode 100644 firmware/application/apps/ui_recon.hpp create mode 100644 firmware/application/apps/ui_recon_settings.cpp create mode 100644 firmware/application/apps/ui_recon_settings.hpp diff --git a/firmware/application/apps/ui_recon.cpp b/firmware/application/apps/ui_recon.cpp new file mode 100644 index 00000000..89de6356 --- /dev/null +++ b/firmware/application/apps/ui_recon.cpp @@ -0,0 +1,1717 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2018 Furrtek + * + * 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_recon.hpp" +#include "ui_fileman.hpp" +#include "ui_recon_settings.hpp" +#include "file.hpp" + +// Id's for messages between ReconThread and ReconView +#define MSG_RECON_PAUSE 9999 // for handle_retune to know that recon thread triggered a pause. f is not important with that message +#define MSG_RECON_SET_MODULATION 10000 // for handle_retune to know that recon thread triggered a modulation change. f is the index of the modulation +#define MSG_RECON_SET_BANDWIDTH 20000 // for handle_retune to know that recon thread triggered a bandwidth change. f is the new bandwidth value index for current modulation +#define MSG_RECON_SET_STEP 30000 // for handle_retune to know that recon thread triggered a bandwidth change. f is the new bandwidth value index for current modulation +#define MSG_RECON_SET_RECEIVER_BANDWIDTH 40000 // for handle_retune to know that recon thread triggered a receiver bandwidth change. f is the new bandwidth in hz +#define MSG_RECON_SET_RECEIVER_SAMPLERATE 50000 // for handle_retune to know that recon thread triggered a receiver samplerate change. f is the new samplerate in hz/s + + +using namespace portapack; +using portapack::memory::map::backup_ram; + +namespace ui { + + ReconThread::ReconThread( freqman_db *database ) : frequency_list_ { *database } { + thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ReconThread::static_fn, this ); + } + + ReconThread::~ReconThread() { + stop(); + } + + void ReconThread::stop() { + if( thread ) { + chThdTerminate(thread); + chThdWait(thread); + thread = nullptr; + } + } + + + void ReconThread::set_recon(const bool v) { + _recon = v; + } + + void ReconThread::set_freq_delete(const bool v) { + _freq_delete = v; + } + + void ReconThread::set_stepper( const int64_t v){ + _stepper = v; + } + + void ReconThread::set_lock_duration( const uint32_t v ){ + _lock_duration = v; + } + + uint32_t ReconThread::get_lock_duration() { + return _lock_duration ; + } + + void ReconThread::set_lock_nb_match( const uint32_t v ){ + _lock_nb_match = v; + } + + uint32_t ReconThread::get_lock_nb_match() { + return _lock_nb_match ; + } + + bool ReconThread::is_recon() { + return _recon; + } + + void ReconThread::set_freq_lock(const uint32_t v) { + _freq_lock = v; + } + uint32_t ReconThread::is_freq_lock() { + return _freq_lock; + } + int64_t ReconThread::get_current_freq() { + return freq ; + } + + void ReconThread::change_recon_direction() { + _fwd = !_fwd; + // chThdSleepMilliseconds(300); //Give some pause after reversing recon direction + } + + bool ReconThread::get_recon_direction() { + return _fwd ; + } + void ReconThread::set_recon_direction( const bool v) { + _fwd = v ; + } + + void ReconThread::set_continuous(const bool v) { + _continuous = v; + } + + freqman_index_t ReconThread::get_current_modulation() { + return last_entry.modulation ; + } + + freqman_index_t ReconThread::get_current_bandwidth() { + return last_entry.bandwidth ; + } + + + void ReconThread::set_default_modulation( freqman_index_t index ) { + def_modulation = index ; + } + + void ReconThread::set_default_bandwidth( freqman_index_t index ) { + def_bandwidth = index ; + } + void ReconThread::set_default_step( freqman_index_t index ) { + def_step = index ; + } + void ReconThread::set_freq_index( int16_t index ) { + frequency_index = index ; + } + int16_t ReconThread::get_freq_index() { + return frequency_index ; + } + + msg_t ReconThread::static_fn( void* arg ) { + auto obj = static_cast(arg); + obj->run(); + return 0; + } + + void ReconThread::run() { + + if (frequency_list_.size() > 0 ) { //IF THERE IS A FREQUENCY LIST ... + int64_t minfreq = 0 ; + int64_t maxfreq = 0 ; + bool has_looped = false ; + bool entry_has_changed = false ; + RetuneMessage message { }; + + if( frequency_list_[ 0 ] . step >= 0 ) + step = freqman_entry_get_step_value( frequency_list_[ 0 ] . step ); + else + step = freqman_entry_get_step_value( def_step ); + + switch( frequency_list_[ 0 ] . type ){ + case SINGLE: + freq = frequency_list_[ 0 ] . frequency_a ; + break; + case RANGE: + minfreq = frequency_list_[ 0 ] . frequency_a ; + maxfreq = frequency_list_[ 0 ] . frequency_b ; + if( _fwd ) + { + freq = minfreq ; + } + else + { + freq = maxfreq ; + } + if( frequency_list_[ 0 ] . step >= 0 ) + step = freqman_entry_get_step_value( frequency_list_[ 0 ] . step ); + break; + case HAMRADIO: + minfreq = frequency_list_[ 0 ] . frequency_a ; + maxfreq = frequency_list_[ 0 ] . frequency_b ; + if( _fwd ) + { + freq = minfreq ; + } + else + { + freq = maxfreq ; + } + break; + default: + break; + } + last_entry . modulation = -1 ; + last_entry . bandwidth = -1 ; + last_entry . step = -1 ; + bool restart_recon = false; //Flag whenever scanning is restarting after a pause + + while( !chThdShouldTerminate() && frequency_list_.size() > 0 ) { + if( !_freq_delete ) + { + if( _recon || _stepper != 0 ) + { + if( _freq_lock == 0 || _stepper != 0 ) //normal recon (not performing freq_lock) + { + if( !restart_recon || _stepper != 0 ) + { + has_looped = false ; + entry_has_changed = false ; + + if( last_entry . frequency_a != freq ) + { + last_entry . frequency_a = freq ; + receiver_model.set_tuning_frequency( freq ); // Retune + message.freq = freq ; + message.range = frequency_index ; + EventDispatcher::send_message(message); + } + + // Set modulation if any + if( last_entry . modulation != frequency_list_[ frequency_index ] . modulation && frequency_list_[ frequency_index ] . modulation >= 0 ) + { + last_entry . modulation = frequency_list_[ frequency_index ]. modulation; + message.freq = last_entry . modulation ; + message.range = MSG_RECON_SET_MODULATION ; + EventDispatcher::send_message(message); + } + // Set bandwidth if any + if( last_entry . bandwidth != frequency_list_[ frequency_index ] . bandwidth && frequency_list_[ frequency_index ] . bandwidth >= 0 ) + { + last_entry . bandwidth = frequency_list_[ frequency_index ]. bandwidth; + message.freq = last_entry . bandwidth ; + message.range = MSG_RECON_SET_BANDWIDTH ; + EventDispatcher::send_message(message); + } + if( last_entry . step != frequency_list_[ frequency_index ] . step && frequency_list_[ frequency_index ] . step >= 0 ) + { + last_entry . step = frequency_list_[ frequency_index ]. step ; + message.freq = last_entry . step ; + message.range = MSG_RECON_SET_STEP ; + EventDispatcher::send_message(message); + step = freqman_entry_get_step_value( last_entry . step ); + } + + /* we are doing a range */ + if( frequency_list_[ frequency_index ] . type == RANGE ) { + + if ( ( _fwd && _stepper == 0 ) || _stepper > 0 ) { + //forward + freq += step ; + // if bigger than range max + if (freq > maxfreq ) { + // when going forward we already know that we can skip a whole range => two values in the list + frequency_index ++ ; + entry_has_changed = true ; + // looping + if( (uint32_t)frequency_index >= frequency_list_.size() ) + { + has_looped = true ; + frequency_index = 0 ; + } + } + } + else if( (!_fwd && _stepper == 0 ) || _stepper < 0 ) { + //reverse + freq -= step ; + // if lower than range min + if (freq < minfreq ) { + // when back we have to check one step at a time + frequency_index -- ; + entry_has_changed = true ; + // looping + if( frequency_index < 0 ) + { + has_looped = true ; + frequency_index = frequency_list_.size() - 1 ; + } + } + } + } + else if( frequency_list_[ frequency_index ] . type == SINGLE ) { + if ( (_fwd && _stepper == 0 ) || _stepper > 0 ) { //forward + frequency_index++; + entry_has_changed = true ; + // looping + if( (uint32_t)frequency_index >= frequency_list_.size() ) + { + has_looped = true ; + frequency_index = 0 ; + } + } + else if( (!_fwd && _stepper == 0 ) || _stepper < 0 ) { + //reverse + frequency_index--; + entry_has_changed = true ; + // if previous if under the list => go back from end + if( frequency_index < 0 ) + { + has_looped = true ; + frequency_index = frequency_list_.size() - 1 ; + } + } + } + else if( frequency_list_[ frequency_index ] . type == HAMRADIO ) + { + if ( (_fwd && _stepper == 0 ) || _stepper > 0 ) { //forward + if( ( minfreq != maxfreq ) && freq == minfreq ) + { + freq = maxfreq ; + } + else + { + frequency_index++; + entry_has_changed = true ; + // looping + if( (uint32_t)frequency_index >= frequency_list_.size() ) + { + has_looped = true ; + frequency_index = 0 ; + } + } + } + else if( (!_fwd && _stepper == 0 ) || _stepper < 0 ) { + //reverse + if( ( minfreq != maxfreq ) && freq == maxfreq ) + { + freq = minfreq ; + } + else + { + frequency_index--; + entry_has_changed = true ; + // if previous if under the list => go back from end + if( frequency_index < 0 ) + { + has_looped = true ; + frequency_index = frequency_list_.size() - 1 ; + } + } + } + } + // set index to boundary if !continuous + if( has_looped && !_continuous ) + { + entry_has_changed = true ; + /* prepare values for the next run, when user will resume */ + if( ( _fwd && _stepper == 0 ) || _stepper > 0 ) + { + frequency_index = 0 ; + } + else if( ( !_fwd && _stepper == 0 ) || _stepper < 0 ) + { + frequency_index = frequency_list_.size() - 1 ; + } + } + + } + else + { + restart_recon = false ; + } + } + // reload entry if changed + if( entry_has_changed ){ + switch( frequency_list_[ frequency_index ] . type ){ + case SINGLE: + freq = frequency_list_[ frequency_index ] . frequency_a ; + break; + case RANGE: + minfreq = frequency_list_[ frequency_index ] . frequency_a ; + maxfreq = frequency_list_[ frequency_index ] . frequency_b ; + if( ( _fwd && _stepper == 0 ) || _stepper > 0 ) + { + freq = minfreq ; + } + else if( ( !_fwd && _stepper == 0 ) || _stepper < 0 ) + { + freq = maxfreq ; + } + break; + case HAMRADIO: + minfreq = frequency_list_[ frequency_index ] . frequency_a ; + maxfreq = frequency_list_[ frequency_index ] . frequency_b ; + if( ( _fwd && _stepper == 0 ) || _stepper > 0 ) + { + freq = minfreq ; + } + else if( ( !_fwd && _stepper == 0 ) || _stepper < 0 ) + { + freq = maxfreq ; + } + break; + default: + break; + } + + } + + // send a pause message with the right freq + if( has_looped && !_continuous ) + { + // signal pause to handle_retune + receiver_model.set_tuning_frequency( freq ); // Retune to actual freq + message.freq = freq ; + message.range = MSG_RECON_PAUSE ; + EventDispatcher::send_message(message); + } + if( _stepper < 0 ) _stepper ++ ; + if( _stepper > 0 ) _stepper -- ; + } + else + { + restart_recon = true ; + } + } + chThdSleepMilliseconds( _lock_duration ); //Needed to (eventually) stabilize the receiver into new freq + } + } + } + + bool ReconView::check_sd_card() + { + return (sd_card::status() == sd_card::Status::Mounted) ? true : false; + } + + void ReconView::set_display_freq( int64_t freq ) + { + int64_t freqMHz = ( freq / 1000000 ); + int64_t freqMhzFloatingPart = ( freq - ( 1000000 * freqMHz ) ) / 100 ; + big_display.set( "FREQ: "+to_string_dec_int( freqMHz )+"."+to_string_dec_int( freqMhzFloatingPart )+" MHz" ); + } + + void ReconView::handle_retune( int64_t freq , uint32_t index ) { + + static int64_t last_freq = 0 ; + static uint32_t last_index = 999999 ; + + switch( index ) { + case MSG_RECON_PAUSE: + user_pause(); + if( update_ranges && current_index < frequency_list.size() ) + { + button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.min = frequency_list[ current_index ] . frequency_a ; + if( frequency_list[ current_index ] . frequency_b != 0 ) + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_b ; + } + else + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_a ; + } + } + return ; + break ; + case MSG_RECON_SET_MODULATION : + receiver_model.disable(); + baseband::shutdown(); + change_mode( freq ); + if ( !recon_thread->is_recon() ) //for some motive, audio output gets stopped. + audio::output::start(); //So if recon was stopped we resume audio + receiver_model.enable(); + field_mode.set_selected_index( freq ); + return ; + break ; + case MSG_RECON_SET_BANDWIDTH: + switch( recon_thread->get_current_modulation() ) + { + case AM_MODULATION: + receiver_model.set_am_configuration( freq ); + break ; + case NFM_MODULATION: + receiver_model.set_nbfm_configuration( freq ); + break ; + case WFM_MODULATION: + receiver_model.set_wfm_configuration( freq ); + default: + break ; + } + field_bw.set_selected_index( freq ); + return ; + break ; + case MSG_RECON_SET_STEP: + step_mode.set_selected_index( freq ); + return ; + break ; + } + + if( last_index != index ) + { + last_index = index ; + current_index = index ; + if( frequency_list.size() && index < frequency_list.size() && frequency_list[ index ] . type == RANGE ) + { + if( update_ranges ) + { + button_manual_start.set_text( to_string_short_freq( frequency_list[ index ] . frequency_a ) ); + frequency_range.min = frequency_list[ index ] . frequency_a ; + if( frequency_list[ index ] . frequency_b != 0 ) + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ index ] . frequency_b ) ); + frequency_range.max = frequency_list[ index ] . frequency_b ; + } + else + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ index ] . frequency_a ) ); + frequency_range.max = frequency_list[ index ] . frequency_a ; + } + } + } + } + + uint32_t freq_lock = recon_thread->is_freq_lock(); + + if( freq_lock == 0 ) { + //NO FREQ LOCK, ONGOING STANDARD SCANNING + if( index < 1000 && index < frequency_list.size() ) + { + text_cycle.set( to_string_dec_uint( index + 1 , 3 ) ); + if(frequency_list[index].description.size() > 0) desc_cycle.set( frequency_list[index].description ); //Show new description + } + big_display.set_style(&style_white); + } + else if( freq_lock == 1 && recon_lock_nb_match != 1 ) + { + //STARTING LOCK FREQ + big_display.set_style(&style_yellow); + } + else if( index < 1000 && freq_lock >= recon_thread -> get_lock_nb_match() ) + { + big_display.set_style( &style_green); + + //FREQ IS STRONG: GREEN and recon will pause when on_statistics_update() + if( (!scanner_mode) && autosave && last_freq != freq ) { + File recon_file; + std::string freq_file_path = "/FREQMAN/"+output_file+".TXT" ; + std::string frequency_to_add ; + + freqman_entry entry = frequency_list[ current_index ] ; + entry . frequency_a = recon_thread->get_current_freq(); + entry . frequency_b = recon_thread->get_current_freq(); + entry . modulation = recon_thread->get_current_modulation(); + entry . bandwidth = recon_thread->get_current_bandwidth(); + entry . type = SINGLE ; + + get_freq_string( entry , frequency_to_add ); + + auto result = recon_file.open(freq_file_path); //First recon if freq is already in txt + if (!result.is_valid()) { + 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 < recon_file.size();pointer++) { + recon_file.seek(pointer); + recon_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) { + result = recon_file.append(freq_file_path); //Second: append if it is not there + if( !result.is_valid() ) + { + recon_file.write_line( frequency_to_add ); + } + } + } + else { + result = recon_file.create( freq_file_path ); //First freq if no output file + if( !result.is_valid() ) + { + recon_file.write_line( frequency_to_add ); + } + } + } + last_freq = freq ; + } + set_display_freq( freq ); //UPDATE the big Freq after 0, 1 or recon_lock_nb_match (at least, for color synching) + } + + + void ReconView::focus() { + button_pause.focus(); + } + + ReconView::~ReconView() { + + ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() ); + + // save app settings + settings.save("recon", &app_settings); + + audio::output::stop(); + receiver_model.disable(); + baseband::shutdown(); + } + + void ReconView::show_max( bool refresh_display ) { //show total number of freqs to recon + static int32_t last_db = 999999 ; + static int32_t last_nb_match = 999999 ; + static int32_t last_timer = -1 ; + if( recon_thread && frequency_list.size() > 0 ) + { + int32_t nb_match = recon_thread->is_freq_lock(); + if( last_db != db ) + { + last_db = db ; + refresh_display = true ; + } + if( last_nb_match != nb_match ) + { + last_nb_match = nb_match ; + refresh_display = true ; + } + if( last_timer != timer ) + { + last_timer = timer ; + refresh_display = true ; + } + if( refresh_display ) + { + text_max.set( "/" + to_string_dec_uint( frequency_list.size() ) + " " + to_string_dec_int( db ) + " db " + to_string_dec_uint( nb_match ) + "/" + to_string_dec_uint( recon_thread->get_lock_nb_match() ) ); + freq_stats.set( "RSSI: "+to_string_dec_int( rssi.get_min() )+"/"+to_string_dec_int( rssi.get_avg() )+"/"+to_string_dec_int( rssi.get_max() )+" db" ); + text_timer.set( "TIMER: " + to_string_dec_int( timer ) ); + } + } + else + { + if( refresh_display ) + { + text_max.set( " " ); + freq_stats.set( "RSSI: " +to_string_dec_int( rssi.get_min() )+"/"+to_string_dec_int( rssi.get_avg() )+"/"+to_string_dec_int( rssi.get_max() )+" db" ); + text_timer.set( "TIMER: " + to_string_dec_int( timer ) ); + } + } + } + + ReconView::ReconView( NavigationView& nav) : nav_ { nav } { + + add_children( { + &labels, + &field_lna, + &field_vga, + &field_rf_amp, + &field_volume, + &field_bw, + &field_squelch, + &field_wait, + &field_lock_wait, + &button_recon_setup, + &button_scanner_mode, + &file_name, + &rssi, + &text_cycle, + &text_max, + &desc_cycle, + &big_display, + &freq_stats, + &text_timer, + &button_manual_start, + &button_manual_end, + &button_manual_recon, + &field_mode, + &step_mode, + &button_pause, + &button_audio_app, + &button_add, + &button_dir, + &button_restart, + &button_mic_app, + &button_remove + } ); + + // Recon directory + if( check_sd_card() ) { // Check to see if SD Card is mounted + make_new_directory( u"/RECON" ); + sd_card_mounted = true ; + } + + def_step = 0 ; + change_mode(AM_MODULATION); //Start on AM + field_mode.set_by_value(AM_MODULATION); //Reflect the mode into the manual selector + + //HELPER: Pre-setting a manual range, based on stored frequency + rf::Frequency stored_freq = persistent_memory::tuned_frequency(); + receiver_model.set_tuning_frequency( stored_freq ); + 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)); + // Loading settings + autostart = persistent_memory::recon_autostart_recon(); + autosave = persistent_memory::recon_autosave_freqs(); + continuous = persistent_memory::recon_continuous(); + filedelete = persistent_memory::recon_clear_output(); + load_freqs = persistent_memory::recon_load_freqs(); + load_ranges = persistent_memory::recon_load_ranges(); + load_hamradios = persistent_memory::recon_load_hamradios(); + update_ranges = persistent_memory::recon_update_ranges_when_recon(); + + //Loading input and output file from settings + ReconSetupLoadStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , volume ); + + field_volume.set_value( volume ); + + // load auto common app settings + if( sd_card_mounted ) + { + auto rc = settings.load("recon", &app_settings); + if(rc == SETTINGS_OK) { + field_lna.set_value(app_settings.lna); + field_vga.set_value(app_settings.vga); + field_rf_amp.set_value(app_settings.rx_amp); + receiver_model.set_rf_amp(app_settings.rx_amp); + } + } + button_scanner_mode.set_style( &style_blue ); + button_scanner_mode.set_text( "RECON" ); + file_name.set( "USE:" ); + + field_squelch.set_value( squelch ); + + button_manual_start.on_select = [this, &nav](ButtonWithEncoder& 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](ButtonWithEncoder& 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_manual_start.on_change = [this]() { + frequency_range.min = frequency_range.min + button_manual_start.get_encoder_delta() * freqman_entry_get_step_value( def_step ); + if( frequency_range.min < 1 ) + { + frequency_range.min = 1 ; + } + if( frequency_range.min > ( MAX_UFREQ - freqman_entry_get_step_value( def_step ) ) ) + { + frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value( def_step ); + } + if( frequency_range.min > (frequency_range.max - freqman_entry_get_step_value( def_step ) ) ) + { + frequency_range.max = frequency_range.min + freqman_entry_get_step_value( def_step ); + if( frequency_range.max > MAX_UFREQ ) + { + frequency_range.min = MAX_UFREQ - freqman_entry_get_step_value( def_step ); + frequency_range.max = MAX_UFREQ ; + } + } + button_manual_start.set_text( to_string_short_freq(frequency_range.min) ); + button_manual_end.set_text( to_string_short_freq(frequency_range.max) ); + button_manual_start.set_encoder_delta( 0 ); + }; + + button_manual_end.on_change = [this]() { + frequency_range.max = frequency_range.max + button_manual_end.get_encoder_delta() * freqman_entry_get_step_value( def_step ); + if( frequency_range.max < ( freqman_entry_get_step_value( def_step ) + 1 ) ) + { + frequency_range.max = ( freqman_entry_get_step_value( def_step ) + 1 ); + } + if( frequency_range.max > MAX_UFREQ ) + { + frequency_range.max = MAX_UFREQ ; + } + if( frequency_range.max < (frequency_range.min + freqman_entry_get_step_value( def_step ) ) ) + { + frequency_range.min = frequency_range.max - freqman_entry_get_step_value( def_step ); + if( frequency_range.max < ( freqman_entry_get_step_value( def_step ) + 1 ) ) + { + frequency_range.min = 1 ; + frequency_range.max = ( freqman_entry_get_step_value( def_step ) + 1 ) ; + } + } + button_manual_start.set_text( to_string_short_freq(frequency_range.min) ); + button_manual_end.set_text( to_string_short_freq(frequency_range.max) ); + button_manual_end.set_encoder_delta( 0 ); + }; + + + + button_pause.on_select = [this](ButtonWithEncoder&) { + if( recon_thread && frequency_list.size() > 0 ) + { + if( continuous_lock ) + { + if( fwd ) + { + recon_thread-> set_stepper( 1 ); + } + else + { + recon_thread-> set_stepper( -1 ); + } + timer = 0 ; + recon_resume(); + button_pause.set_text(""); //Show button for non continuous stop + continuous_lock = false ; + } + else + { + if( userpause ) + { + user_resume(); + } + else + { + user_pause(); + + if( update_ranges ) + { + button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.min = frequency_list[ current_index ] . frequency_a ; + if( frequency_list[ current_index ] . frequency_b != 0 ) + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_b ; + } + else + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_a ; + } + } + } + } + } + button_manual_start.set_encoder_delta( 0 ); + }; + + button_pause.on_change = [this]() { + if( recon_thread && frequency_list.size() > 0 ) + { + if( button_pause.get_encoder_delta() > 0 ) + { + fwd = true ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "FW>" ); + recon_thread->set_freq_lock( 0 ); + recon_thread-> set_stepper( 1 ); + } + else if( button_pause.get_encoder_delta() < 0 ) + { + fwd = false ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "set_freq_lock( 0 ); + recon_thread-> set_stepper( -1 ); + } + } + button_pause.set_encoder_delta( 0 ); + }; + + button_audio_app.on_select = [this](Button&) { + recon_thread->stop(); + nav_.pop(); + nav_.push(); + }; + + button_mic_app.on_select = [this](Button&) { + recon_thread->stop(); + nav_.pop(); + nav_.push(); + }; + + button_remove.on_select = [this](ButtonWithEncoder&) { + bool previous_is_recon = false ; + bool previous_userpause = userpause ; + if( recon_thread ) + { + previous_is_recon = recon_thread->is_recon(); + recon_thread->set_recon(false); + recon_thread->set_freq_delete(true); + chThdSleepMilliseconds( recon_lock_duration ); // give some time to Thread::Run to pause + } + if( scanner_mode ) + { + if(frequency_list.size() > 0 ) + { + if( current_index >= frequency_list.size() ) + { + current_index = frequency_list.size() - 1 ; + } + frequency_list.erase( frequency_list.begin()+ current_index ); + if( current_index >= frequency_list.size() ) + { + current_index = frequency_list.size() - 1 ; + } + if( frequency_list.size() > 0 ) + { + recon_thread->set_freq_index( current_index ); + if(frequency_list[current_index].description.size() > 0) + { + desc_cycle.set( frequency_list[current_index].description ); //Show new description + } + else + { + desc_cycle.set( "no description" ); //Show new description + show_max( true ); //UPDATE new list size on screen + } + text_cycle.set( to_string_dec_uint( current_index + 1 , 3 ) ); + + File freqman_file; + + std::string freq_file_path = "FREQMAN/" + output_file + ".TXT"; + + delete_file( freq_file_path ); + + auto result = freqman_file.create( freq_file_path ); + if ( !result.is_valid() ) + { + for (size_t n = 0; n < frequency_list.size(); n++) + { + std::string line ; + get_freq_string( frequency_list[ n ] , line ); + freqman_file.write_line( line ); + } + } + } + } + } + else // RECON MODE / MANUAL, only remove matching from output + { + File recon_file; + File tmp_recon_file; + std::string freq_file_path = "/FREQMAN/"+output_file+".TXT" ; + std::string tmp_freq_file_path = "/FREQMAN/"+output_file+"TMP.TXT" ; + std::string frequency_to_add ; + + freqman_entry entry = frequency_list[ current_index ] ; + entry . frequency_a = recon_thread->get_current_freq(); + entry . frequency_b = recon_thread->get_current_freq(); + entry . modulation = recon_thread->get_current_modulation(); + entry . bandwidth = recon_thread->get_current_bandwidth(); + entry . type = SINGLE ; + + get_freq_string( entry , frequency_to_add ); + + delete_file( tmp_freq_file_path ); + auto result = tmp_recon_file.create(tmp_freq_file_path); //First recon if freq is already in txt + // + if (!result.is_valid()) { + bool found=false; + result = recon_file.open(freq_file_path); //First recon if freq is already in txt + if (!result.is_valid()) { + char one_char[1]; //Read it char by char + std::string line; //and put read line in here + for (size_t pointer=0; pointer < recon_file.size();pointer++) { + recon_file.seek(pointer); + recon_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; + } + else + { + tmp_recon_file.write_line( frequency_to_add ); + } + line.clear(); //Ready for next textline + } + } + if( found) + { + delete_file( freq_file_path ); + rename_file( tmp_freq_file_path , freq_file_path ); + } + else + { + delete_file( tmp_freq_file_path ); + } + } + } + } + if( frequency_list.size() == 0 ) + { + text_cycle.set( " " ); + desc_cycle.set( "no entries in list" ); //Show new description + show_max( true ); //UPDATE new list size on screen + delete_file( "FREQMAN/"+output_file+".TXT" ); + } + + if( recon_thread ) + { + recon_thread->set_freq_index( current_index ); + timer = 0 ; + + if( previous_userpause ) + { + user_pause(); + } + else + { + user_resume(); + } + recon_thread->set_recon( previous_is_recon ); + recon_thread->set_freq_delete(false); + chThdSleepMilliseconds( recon_lock_duration ); // give some time to Thread::Run to pause + } + }; + + button_remove.on_change = [this]() { + if( recon_thread && frequency_list.size() > 0 ) + { + timer = 0 ; + if( button_remove.get_encoder_delta() > 0 ) + { + fwd = true ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "FW>" ); + recon_thread->set_freq_lock( 0 ); + recon_thread-> set_stepper( 1 ); + } + else if( button_remove.get_encoder_delta() < 0 ) + { + fwd = false ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "set_freq_lock( 0 ); + recon_thread-> set_stepper( -1 ); + } + } + button_remove.set_encoder_delta( 0 ); + }; + + button_manual_recon.on_select = [this](Button&) { + scanner_mode = false ; + manual_mode = true ; + 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(); + recon_thread->stop(); //STOP SCANNER THREAD + + frequency_list.clear(); + freqman_entry manual_freq_entry ; + + def_step = step_mode.selected_index(); // max range val + + + manual_freq_entry . type = RANGE ; + manual_freq_entry . description = + "R " + to_string_short_freq(frequency_range.min) + ">" + + to_string_short_freq(frequency_range.max) + " S" // current Manual range + + to_string_short_freq(freqman_entry_get_step_value(def_step)).erase(0,1) ; //euquiq: lame kludge to reduce spacing in step freq + manual_freq_entry . frequency_a = frequency_range.min ; // min range val + manual_freq_entry . frequency_b = frequency_range.max ; // max range val + manual_freq_entry . modulation = -1 ; + manual_freq_entry . bandwidth = -1 ; + manual_freq_entry . step = def_step ; + + frequency_list . push_back( manual_freq_entry ); + + big_display.set_style(&style_white); //Back to white color + set_display_freq( frequency_range.min ); + + freq_stats.set_style(&style_white); + freq_stats.set( "0/0/0" ); + + show_max(); /* display step information */ + text_cycle.set( "MANUAL SEARCH" ); + button_scanner_mode.set_style( &style_white ); + button_scanner_mode.set_text( "M-SEARCH" ); + file_name.set_style( &style_white ); + file_name.set( "USE: MANUAL RANGE" ); + + start_recon_thread(); + user_resume(); + } + }; + + field_mode.on_change = [this](size_t, OptionsField::value_t v) { + if( v != -1 ) + { + receiver_model.disable(); + baseband::shutdown(); + change_mode(v); + if ( !recon_thread->is_recon() ) //for some motive, audio output gets stopped. + audio::output::start(); //So if recon was stopped we resume audio + receiver_model.enable(); + } + }; + + button_dir.on_select = [this](Button&) { + recon_thread->change_recon_direction(); + fwd = recon_thread->get_recon_direction(); + if( fwd ) + { + button_dir.set_text( "FW>" ); + } + else + { + button_dir.set_text( " 0 ) + { + def_step = step_mode.selected_index(); //Use def_step from manual selector + frequency_file_load( true ); + if( recon_thread ) + { + recon_thread->set_lock_duration( recon_lock_duration ); + recon_thread->set_lock_nb_match( recon_lock_nb_match ); + } + if( fwd ) + { + button_dir.set_text( "FW>" ); + } + else + { + button_dir.set_text( "get_current_freq(); + bool found=false; + File recon_file; + std::string freq_file_path = "/FREQMAN/"+output_file+".TXT" ; + std::string frequency_to_add ; + + freqman_entry entry = frequency_list[ current_index ] ; + entry . frequency_a = recon_thread->get_current_freq(); + entry . frequency_b = recon_thread->get_current_freq(); + entry . modulation = recon_thread->get_current_modulation(); + entry . bandwidth = recon_thread->get_current_bandwidth(); + entry . type = SINGLE ; + + get_freq_string( entry , frequency_to_add ); + + auto result = recon_file.open(freq_file_path); //First recon if freq is already in txt + if (!result.is_valid()) { + char one_char[1]; //Read it char by char + std::string line; //and put read line in here + for (size_t pointer=0; pointer < recon_file.size();pointer++) { + recon_file.seek(pointer); + recon_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) { + result = recon_file.append(freq_file_path); //Second: append if it is not there + if( !result.is_valid() ) + { + recon_file.write_line( frequency_to_add ); + } + } + if (found) { + nav_.display_modal("Error", "Frequency already exists"); + set_display_freq( freq ); + } + } + else + { + auto result = recon_file.create(freq_file_path); //third: create if it is not there + if( !result.is_valid() ) + { + recon_file.write_line( frequency_to_add ); + } + } + } + } + }; + + button_add.on_change = [this]() { + if( recon_thread && frequency_list.size() > 0 ) + { + timer = 0 ; + if( button_add.get_encoder_delta() > 0 ) + { + fwd = true ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "FW>" ); + recon_thread-> set_stepper( 1 ); + recon_thread->set_freq_lock( 0 ); + } + else if( button_add.get_encoder_delta() < 0 ) + { + fwd = false ; + recon_thread -> set_recon_direction( fwd ); + button_dir.set_text( "set_freq_lock( 0 ); + recon_thread-> set_stepper( -1 ); + } + } + button_add.set_encoder_delta( 0 ); + }; + + + button_scanner_mode.on_select = [this,&nav](Button&) { + manual_mode = false ; + if( scanner_mode ) + { + scanner_mode = false ; + button_scanner_mode.set_style( &style_blue ); + button_scanner_mode.set_text( "RECON" ); + } + else + { + scanner_mode = true ; + button_scanner_mode.set_style( &style_red ); + button_scanner_mode.set_text( "SCANNER" ); + } + frequency_file_load( true ); + if( recon_thread ) + { + recon_thread->set_lock_duration( recon_lock_duration ); + recon_thread->set_lock_nb_match( recon_lock_nb_match ); + recon_thread->set_continuous( continuous ); + recon_thread->set_recon_direction( fwd ); + } + if( autostart ) + { + user_resume(); + } + else + { + user_pause(); + } + }; + + button_recon_setup.on_select = [this,&nav](Button&) { + + ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() ); + + user_pause(); + + auto open_view = nav.push(input_file,output_file,recon_lock_duration,recon_lock_nb_match,recon_match_mode); + open_view -> on_changed = [this](std::vector result) { + input_file = result[0]; + output_file = result[1]; + recon_lock_duration = strtol( result[2].c_str() , nullptr , 10 ); + recon_lock_nb_match = strtol( result[3].c_str() , nullptr , 10 ); + recon_match_mode = strtol( result[4].c_str() , nullptr , 10 ); + + ReconSetupSaveStrings( "RECON/RECON.CFG" , input_file , output_file , recon_lock_duration , recon_lock_nb_match , squelch , recon_match_mode , wait , recon_lock_duration , field_volume.value() ); + + autosave = persistent_memory::recon_autosave_freqs(); + autostart = persistent_memory::recon_autostart_recon(); + continuous = persistent_memory::recon_continuous(); + filedelete = persistent_memory::recon_clear_output(); + load_freqs = persistent_memory::recon_load_freqs(); + load_ranges = persistent_memory::recon_load_ranges(); + load_hamradios = persistent_memory::recon_load_hamradios(); + + update_ranges = persistent_memory::recon_update_ranges_when_recon(); + + frequency_file_load( true ); + if( recon_thread ) + { + recon_thread->set_lock_duration( recon_lock_duration ); + recon_thread->set_lock_nb_match( recon_lock_nb_match ); + recon_thread->set_continuous( continuous ); + recon_thread->set_recon_direction( fwd ); + } + if( autostart ) + { + user_resume(); + } + else + { + user_pause(); + } + + if( update_ranges ) + { + button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.min = frequency_list[ current_index ] . frequency_a ; + if( frequency_list[ current_index ] . frequency_b != 0 ) + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_b ; + } + else + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_a ; + } + } + + lock_wait = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) ) / 100 ; + lock_wait = lock_wait * 100 ; // poor man's rounding + if( lock_wait < 400 ) + lock_wait = 400 ; + field_lock_wait.set_value( lock_wait ); + show_max(); + return ; + }; + + if( userpause != true ) + { + timer = 0 ; + user_resume(); + } + else + { + RetuneMessage message { }; + message.freq = recon_thread->get_current_freq(); + message.range = current_index ; + EventDispatcher::send_message(message); + } + }; + + //PRE-CONFIGURATION: + field_wait.on_change = [this](int32_t v) + { + wait = v ; + if( wait == 0 ) + { + field_wait.set_style( &style_blue ); + } + else if( wait >= 500 ) + { + field_wait.set_style(&style_white); + } + else if( wait > -500 && wait < 500 ) + { + field_wait.set_style( &style_red ); + } + else if( wait <= -500 ) + { + field_wait.set_style( &style_green ); + } + }; + field_lock_wait.on_change = [this](int32_t v) + { + lock_wait = v ; + + uint32_t lock_white = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) ) / 100 ; + lock_white = lock_white * 100 ; + if( lock_white < 400 ) + lock_white = 400 ; + + uint32_t lock_yellow = 300 ; + + if( lock_wait >= lock_white ) + { + field_lock_wait.set_style( &style_white ); + } + else if( lock_wait >= lock_yellow ) + { + field_lock_wait.set_style( &style_yellow); + } + else + { + field_lock_wait.set_style(&style_red); + } + }; + + field_wait.set_value(wait); + lock_wait = ( 4 * ( recon_lock_duration * recon_lock_nb_match ) ); + lock_wait = lock_wait / 100 ; lock_wait = lock_wait * 100 ; // poor man's rounding + if( lock_wait < 400 ) + lock_wait = 400 ; + field_lock_wait.set_value(lock_wait); + + field_squelch.on_change = [this](int32_t v) { + squelch = v ; + }; + field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); }; + field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99); + + //FILL STEP OPTIONS + freqman_set_modulation_option( field_mode ); + freqman_set_step_option( step_mode ); + + if( filedelete ) + { + delete_file( "FREQMAN/"+output_file+".TXT" ); + } + + frequency_file_load( false ); /* do not stop all at start */ + if( recon_thread && frequency_list.size() > 0 ) + { + recon_thread->set_lock_duration( recon_lock_duration ); + recon_thread->set_lock_nb_match( recon_lock_nb_match ); + if( update_ranges ) + { + button_manual_start.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.min = frequency_list[ current_index ] . frequency_a ; + if( frequency_list[ current_index ] . frequency_b != 0 ) + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_b ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_b ; + } + else + { + button_manual_end.set_text( to_string_short_freq( frequency_list[ current_index ] . frequency_a ) ); + frequency_range.max = frequency_list[ current_index ] . frequency_a ; + } + } + + if( autostart ) + { + timer = 0 ; //Will trigger a recon_resume() on_statistics_update, also advancing to next freq. + user_resume(); + } + else + { + user_pause(); + } + show_max(); + } + } + + + void ReconView::frequency_file_load( bool stop_all_before) { + + // stop everything running now if required + if (stop_all_before) { + recon_thread->stop(); + } + audio::output::stop(); + + if( !scanner_mode) + { + def_step = step_mode.selected_index(); //Use def_step from manual selector + frequency_list.clear(); // clear the existing frequency list (expected behavior) + if( !load_freqman_file_ex( input_file , frequency_list, load_freqs, load_ranges, load_hamradios ) ) + { + desc_cycle.set(" NO " + input_file + ".TXT FILE ..." ); + file_name.set_style( &style_white ); + file_name.set( "USE: NO DATA" ); + } + else + { + file_name.set_style( &style_blue ); + file_name.set( "USE: "+input_file ); + } + step_mode.set_selected_index(def_step); //Impose the default step into the manual step selector + } + else + { + def_step = step_mode.selected_index(); //Use def_step from manual selector + frequency_list.clear(); // clear the existing frequency list (expected behavior) + if( !load_freqman_file_ex( output_file , frequency_list, load_freqs, load_ranges, load_hamradios ) ) + { + desc_cycle.set(" NO " + output_file + ".TXT FILE ..." ); + file_name.set_style( &style_white ); + file_name.set( "USE:" ); + } + else + { + file_name.set( "USE: "+output_file ); + file_name.set_style( &style_red ); + } + step_mode.set_selected_index(def_step); //Impose the default step into the manual step selector + } + + start_recon_thread(); + } + + void ReconView::on_statistics_update(const ChannelStatistics& statistics) { + + int32_t actual_db = statistics.max_db ; + bool update_stats = false ; + int32_t freq_lock = recon_thread->is_freq_lock(); + int32_t max_lock = recon_thread -> get_lock_nb_match(); + + // 0 recon , 1 locking , 2 locked + static int32_t status = -1 ; + + if( actual_db != 0 && db != actual_db ) + { + db = actual_db ; + update_stats = true ; + } + + if( !userpause ) + { + if( !timer ) + { + if( status != 0 ) + { + status = 0 ; + update_stats = true ; + continuous_lock = false ; + recon_thread->set_freq_lock( 0 ); //in lock period, still analyzing the signal + recon_resume(); // RESUME! + big_display.set_style(&style_white); + } + } + if( freq_lock >= max_lock ) // LOCKED + { + if( status != 2 ) + { + status = 2 ; + update_stats = true ; + + if( wait != 0 ) + { + audio::output::start(); + this->on_headphone_volume_changed( (receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99 ); + } + if( wait >= 0 ) + { + timer = wait ; + } + //Inform freq (for coloring purposes also!) + RetuneMessage message { }; + message.freq = recon_thread->get_current_freq() ; + message.range = current_index ; + EventDispatcher::send_message(message); + } + if( wait < 0 ) + { + if( actual_db > squelch ) //MATCHING LEVEL IN STAY X AFTER LAST ACTIVITY + { + timer = abs( wait ); + } + } + } + else // freq_lock < max_lock , LOCKING + { + if( actual_db > squelch ) //MATCHING LEVEL + { + if( status != 1 ) + { + status = 1 ; + continuous_lock = true ; + if( wait != 0 ) + { + audio::output::stop(); + } + timer = lock_wait ; + //Inform freq (for coloring purposes also!) + RetuneMessage message { }; + message.freq = recon_thread->get_current_freq() ; + current_index = recon_thread->get_freq_index() ; + message.range = current_index ; + EventDispatcher::send_message(message); + } + freq_lock ++ ; + recon_thread->set_freq_lock( freq_lock ); //in lock period, still analyzing the signal + update_stats = true ; + } + else + { + // continuous, direct cut it if not consecutive match + if( recon_match_mode == 0 ) + { + timer = 0 ; + update_stats = true ; + } + } + } + } + else + { + if( actual_db > squelch ) + { + if( status != 2 ) //MATCHING LEVEL + { + status = 2 ; + big_display.set_style(&style_yellow); + set_display_freq( recon_thread->get_current_freq() ); + } + } + else + { + if( status != 0 ) + { + status = 0 ; + big_display.set_style(&style_white); + set_display_freq( recon_thread->get_current_freq() ); + } + + } + } + + if( update_stats ) + { + show_max(); + } + + timer -= 50 ; + if( timer < 0 ) + { + timer = 0 ; + } + } /* on_statistic_updates */ + + void ReconView::recon_pause() { + if (recon_thread->is_recon()) + { + audio::output::start(); + this->on_headphone_volume_changed( (receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99 ); + recon_thread->set_freq_lock( 0 ); //in lock period, still analyzing the signal + recon_thread ->set_recon(false); // WE STOP SCANNING + } + } + + + void ReconView::recon_resume() { + audio::output::stop(); + if( !recon_thread->is_recon() ) + recon_thread->set_recon(true); // RESUME! + big_display.set_style(&style_white); //Back to grey color + } + + void ReconView::user_pause() { + timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq. + button_pause.set_text(""); //PAUSED, show resume + userpause=true; + continuous_lock=false; + recon_pause(); + } + + void ReconView::user_resume() { + timer = 0 ; // Will trigger a recon_resume() on_statistics_update, also advancing to next freq. + button_pause.set_text(""); //Show button for pause + userpause=false; // Resume recon + continuous_lock=false; + recon_resume(); + } + + void ReconView::on_headphone_volume_changed(int32_t v) { + const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max; + receiver_model.set_headphone_volume(new_volume); + } + + size_t ReconView::change_mode( freqman_index_t new_mod ) { //Before this, do a recon_thread->stop(); After this do a start_recon_thread() + + field_bw.on_change = [this](size_t n, OptionsField::value_t) { (void)n; }; + + switch( new_mod ) { + case AM_MODULATION: + freqman_set_bandwidth_option( new_mod , field_bw ); + //bw DSB (0) default + field_bw.set_selected_index(0); + baseband::run_image(portapack::spi_flash::image_tag_am_audio); + receiver_model.set_modulation(ReceiverModel::Mode::AMAudio); + 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: + freqman_set_bandwidth_option( new_mod , field_bw ); + //bw 16k (2) default + field_bw.set_selected_index(2); + baseband::run_image(portapack::spi_flash::image_tag_nfm_audio); + receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio); + 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_MODULATION: + freqman_set_bandwidth_option( new_mod , field_bw ); + //bw 16k (0) only/default + field_bw.set_selected_index(0); + baseband::run_image(portapack::spi_flash::image_tag_wfm_audio); + receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio); + 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(1750000); + break; + default: + break; + } + return freqman_entry_get_step_value( def_step ); + } + + void ReconView::start_recon_thread() { + receiver_model.enable(); + receiver_model.set_squelch_level(0); + recon_thread = std::make_unique(&frequency_list); + recon_thread->set_continuous( continuous ); + recon_thread->set_lock_duration( recon_lock_duration ); + recon_thread->set_lock_nb_match( recon_lock_nb_match ); + recon_thread->set_recon_direction( fwd ); + } + +} /* namespace ui */ diff --git a/firmware/application/apps/ui_recon.hpp b/firmware/application/apps/ui_recon.hpp new file mode 100644 index 00000000..15a914d7 --- /dev/null +++ b/firmware/application/apps/ui_recon.hpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2018 Furrtek + * + * 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_RECON +#define _UI_RECON + +#include "ui.hpp" +#include "receiver_model.hpp" +#include "ui_receiver.hpp" +#include "ui_font_fixed_8x16.hpp" +#include "freqman.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" +#include "app_settings.hpp" + + +namespace ui { + + class ReconThread { + public: + ReconThread(freqman_db *database ); + ~ReconThread(); + + void set_recon(const bool v); + void set_freq_delete(const bool v); + bool is_recon(); + + void set_lock_duration( const uint32_t v ); + uint32_t get_lock_duration(); + void set_lock_nb_match( const uint32_t v ); + void set_match_mode( const uint32_t v ); + uint32_t get_lock_nb_match(); + + void set_freq_lock(const uint32_t v); + uint32_t is_freq_lock(); + int64_t get_current_freq(); + + void set_stepper(const int64_t v); + + void change_recon_direction(); + bool get_recon_direction(); + void set_recon_direction( const bool v); + + void set_continuous(const bool v); + + void set_default_modulation( freqman_index_t index ); + freqman_index_t get_current_modulation(); + void set_default_bandwidth( freqman_index_t index ); + freqman_index_t get_current_bandwidth(); + void set_default_step( freqman_index_t index ); + void set_freq_index( int16_t index ); + int16_t get_freq_index(); + + void run(); + void stop(); + + ReconThread(const ReconThread&) = delete; + ReconThread(ReconThread&&) = delete; + ReconThread& operator=(const ReconThread&) = delete; + ReconThread& operator=(ReconThread&&) = delete; + + private: + freqman_db &frequency_list_ ; + Thread* thread { nullptr }; + int64_t freq = 0 ; + uint32_t step = 0 ; + freqman_index_t def_modulation = 0 ; + freqman_index_t def_bandwidth = 0 ; + freqman_index_t def_step = 0 ; + tone_index tone = 0 ; + freqman_entry last_entry = { } ; + int16_t frequency_index = 0 ; + + bool _recon { true }; + bool _freq_delete { false }; + bool _fwd { true }; + bool _continuous { true }; + int64_t _stepper { 0 }; + int32_t _freq_lock { 0 }; + uint32_t _lock_duration { 50 }; + uint32_t _lock_nb_match { 10 }; + static msg_t static_fn(void* arg); + }; + + class ReconView : public View { + public: + ReconView(NavigationView& nav); + ~ReconView(); + + void focus() override; + + void big_display_freq( int64_t f ); + + const Style style_grey { // recon + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::grey(), + }; + + const Style style_white { // recon + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::white(), + }; + + const Style style_yellow { //Found signal + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::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(), + }; + + const Style style_blue { // quick recon, wait == 0 + .font = font::fixed_8x16, + .background = Color::black(), + .foreground = Color::blue(), + }; + + std::string title() const override { return "Recon"; }; + + //void set_parent_rect(const Rect new_parent_rect) override; + + private: + NavigationView& nav_; + + void start_recon_thread(); + size_t change_mode( freqman_index_t mod_type); + void show_max( bool refresh_display = false ); + void recon_pause(); + void recon_resume(); + void user_pause(); + void user_resume(); + void frequency_file_load( bool stop_all_before = false); + void on_statistics_update(const ChannelStatistics& statistics); + void on_headphone_volume_changed(int32_t v); + void set_display_freq( int64_t freq ); + void handle_retune( int64_t freq , uint32_t index ); + bool check_sd_card(); + + jammer::jammer_range_t frequency_range { false, 0, 0 }; //perfect for manual recon task too... + int32_t squelch { 0 }; + int32_t db { 0 }; + int32_t timer { 0 }; + int32_t wait { 5000 }; // in msec. if > 0 wait duration after a lock, if < 0 duration is set to 'wait' unless there is no more activity + uint32_t lock_wait { 500 }; // in msec. Represent the maximum amount of time we will wait for a lock to complete before switching to next + int32_t def_step { 0 }; + freqman_db frequency_list = { }; + uint32_t current_index { 0 }; + bool userpause { false }; + bool continuous_lock { false }; + std::string input_file = { "RECON" }; + std::string output_file = { "RECON_RESULTS" }; + bool autosave = { true }; + bool autostart = { true }; + bool continuous = { true }; + bool filedelete = { true }; + bool load_freqs = { true }; + bool load_ranges = { true }; + bool load_hamradios = { true }; + bool update_ranges = { true }; + bool fwd = { true }; + // maximum usable freq + long long int MAX_UFREQ = { 7200000000 }; + uint32_t recon_lock_nb_match = { 10 }; + uint32_t recon_lock_duration = { 50 }; + uint32_t recon_match_mode = { 0 }; + bool scanner_mode { false }; + bool manual_mode { false }; + bool sd_card_mounted = false ; + int32_t volume = 40 ; + + Labels labels + { + { { 0 * 8 , 0 * 16 }, "LNA: VGA: AMP: VOL: ", Color::light_grey() }, + { { 0 * 8 , 1 * 16 }, "BW : SQ: W,L: , ", Color::light_grey() }, + { { 3 * 8 , 10 * 16 }, "START END MANUAL", Color::light_grey() }, + { { 0 * 8 , (26 * 8) + 4 }, "MODE:", Color::light_grey() }, + { { 11 * 8 , (26 * 8) + 4 }, "STEP:", Color::light_grey() }, + }; + + LNAGainField field_lna { + { 4 * 8, 0 * 16 } + }; + + VGAGainField field_vga { + { 11 * 8, 0 * 16 } + }; + + RFAmpField field_rf_amp { + { 18 * 8, 0 * 16 } + }; + + NumberField field_volume { + { 24 * 8, 0 * 16 }, + 2, + { 0, 99 }, + 1, + ' ', + }; + + OptionsField field_bw { + { 3 * 8, 1 * 16 }, + 4, + { } + }; + + NumberField field_squelch { + { 10 * 8, 1 * 16 }, + 3, + { -90, 20 }, + 1, + ' ', + }; + + NumberField field_wait { + { 18 * 8, 1 * 16 }, + 5, + { -9000, 9000 }, + 100, + ' ', + }; + + NumberField field_lock_wait { + { 24 * 8, 1 * 16 }, + 4, + { 100 , 9000 }, + 100, + ' ', + }; + + RSSI rssi { + { 0 * 16, 2 * 16, 15 * 16, 8 }, + }; + + Text text_cycle { + { 0, 3 * 16, 3 * 8, 16 }, + }; + + Text text_max { + { 3 * 8, 3 * 16, 20 * 8 , 16 }, + }; + + Text desc_cycle { + {0, 4 * 16, 240, 16 }, + }; + + /* BigFrequency big_display { //Show frequency in glamour + { 4, 7 * 16 - 8 , 28 * 8, 52 }, + 0 + }; */ + + Text big_display { //Show frequency in text mode + { 0, 5 * 16 , 28 * 8, 16 }, + }; + + Text freq_stats { //Show frequency stats in text mode + { 0, 6 * 16 , 28 * 8, 16 }, + }; + + Text text_timer { //Show frequency stats in text mode + { 0, 7 * 16 , 28 * 8, 16 }, + }; + + Button button_recon_setup { + { 25 * 8 , 2 * 16 + 8 , 4 * 8, 28 }, + "OPT" + }; + + Button button_scanner_mode { + { 21 * 8 , 8 * 16 , 9 * 8, 28 }, + "RECON" + }; + + Text file_name { //Show file used + { 0 , 8 * 16 + 4 , 20 * 8, 16 }, + }; + + + ButtonWithEncoder button_manual_start { + { 0 * 8, 11 * 16, 11 * 8, 28 }, + "" + }; + + ButtonWithEncoder button_manual_end { + { 12 * 8 - 6, 11 * 16, 11 * 8, 28 }, + "" + }; + + Button button_manual_recon { + { 23 * 8 - 3, 11 * 16, 7 * 8 , 28 }, + "SEARCH" + }; + + OptionsField field_mode { + { 5 * 8, (26 * 8) + 4 }, + 6, + { + } + }; + + OptionsField step_mode { + { 17 * 8, (26 * 8) + 4 }, + 12, + { + } + }; + + ButtonWithEncoder button_pause { + { 0, (15 * 16) - 4, 72, 28 }, + "PAUSE" + }; + + + Button button_audio_app { + { 84, (15 * 16) - 4, 72, 28 }, + "AUDIO" + }; + + ButtonWithEncoder button_add { + { 168, (15 * 16) - 4, 72, 28 }, + "" + }; + + Button button_dir { + { 0, (35 * 8) - 4, 34, 28 }, + "FW>" + }; + + Button button_restart { + { 38, (35 * 8) - 4, 34, 28 }, + "RST" + }; + + + Button button_mic_app { + { 84, (35 * 8) - 4, 72, 28 }, + "MIC TX" + }; + + ButtonWithEncoder button_remove { + { 168, (35 * 8) - 4, 72, 28 }, + "" + }; + + std::unique_ptr recon_thread { }; + + MessageHandlerRegistration message_handler_retune { + Message::ID::Retune, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->handle_retune(message.freq,message.range); + } + }; + + MessageHandlerRegistration message_handler_stats { + Message::ID::ChannelStatistics, + [this](const Message* const p) { + this->on_statistics_update(static_cast(p)->statistics); + } + }; + // app save settings + std::app_settings settings { }; + std::app_settings::AppSettings app_settings { }; + }; + +} /* namespace ui */ + +#endif diff --git a/firmware/application/apps/ui_recon_settings.cpp b/firmware/application/apps/ui_recon_settings.cpp new file mode 100644 index 00000000..2a36b485 --- /dev/null +++ b/firmware/application/apps/ui_recon_settings.cpp @@ -0,0 +1,301 @@ +/* + * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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_recon_settings.hpp" +#include "ui_navigation.hpp" +#include "ui_fileman.hpp" +#include "ui_textentry.hpp" + +#include "file.hpp" +#include "portapack.hpp" +#include "portapack_persistent_memory.hpp" + +using namespace std; +using namespace portapack; + +namespace ui { + + bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume ) + { + File settings_file; + size_t length, file_position = 0; + char * pos; + char * line_start; + char * line_end; + char file_data[257]; + + uint32_t it = 0 ; + uint32_t nb_params = 9 ; + std::string params[ 9 ]; + + bool check_sd_card = (sd_card::status() == sd_card::Status::Mounted) ? true : false ; + + if( check_sd_card ) + { + auto result = settings_file.open( source ); + if( !result.is_valid() ) + { + while( it < nb_params ) + { + // Read a 256 bytes block from file + settings_file.seek(file_position); + memset(file_data, 0, 257); + auto read_size = settings_file.read(file_data, 256); + if (read_size.is_error()) + break ; + file_position += 256; + // Reset line_start to beginning of buffer + line_start = file_data; + pos=line_start; + while ((line_end = strstr(line_start, "\x0A"))) { + length = line_end - line_start - 1 ; + params[ it ] = string( pos , length ); + it ++ ; + line_start = line_end + 1; + pos=line_start ; + if (line_start - file_data >= 256) + break; + if( it >= nb_params ) + break ; + } + if (read_size.value() != 256) + break; // End of file + + // Restart at beginning of last incomplete line + file_position -= (file_data + 256 - line_start); + } + } + + } + + if( it > 0 ) + input_file = params[ 0 ]; + else + input_file = "RECON" ; + + if( it > 1 ) + output_file= params[ 1 ]; + else + output_file = "RECON_RESULTS" ; + + if( it > 2 ) + recon_lock_duration = strtoll( params[ 2 ].c_str() , nullptr , 10 ); + else + recon_lock_duration = 50 ; + + if( it > 3 ) + recon_lock_nb_match = strtoll( params[ 3 ].c_str() , nullptr , 10 ); + else + recon_lock_nb_match = 10 ; + + if( it > 4 ) + recon_squelch_level = strtoll( params[ 4 ].c_str() , nullptr , 10 ); + else + recon_squelch_level = -14 ; + + if( it > 5 ) + recon_match_mode = strtoll( params[ 5 ].c_str() , nullptr , 10 ); + else + recon_match_mode = 0 ; + + if( it > 6 ) + wait = strtoll( params[ 6 ].c_str() , nullptr , 10 ); + else + wait = 5000 ; + + if( it > 7 ) + lock_wait = strtoll( params[ 7 ].c_str() , nullptr , 10 ); + else + lock_wait = 1000 ; + + if( it > 8 ) + volume = strtoll( params[ 8 ].c_str() , nullptr , 10 ); + else + volume = 40 ; + + if( it < nb_params ) + { + /* bad number of params, signal defaults */ + return false ; + } + return true ; + } + + bool ReconSetupSaveStrings( std::string dest, std::string input_file , std::string output_file , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume ) + { + File settings_file; + + auto result = settings_file.create( dest ); + if( result.is_valid() ) + return false ; + settings_file.write_line( input_file ); + settings_file.write_line( output_file ); + settings_file.write_line( to_string_dec_uint( recon_lock_duration ) ); + settings_file.write_line( to_string_dec_uint( recon_lock_nb_match ) ); + settings_file.write_line( to_string_dec_int( recon_squelch_level ) ); + settings_file.write_line( to_string_dec_uint( recon_match_mode ) ); + settings_file.write_line( to_string_dec_int( wait ) ); + settings_file.write_line( to_string_dec_uint( lock_wait ) ); + settings_file.write_line( to_string_dec_int( volume ) ); + return true ; + } + + ReconSetupViewMain::ReconSetupViewMain( NavigationView &nav , Rect parent_rect , std::string input_file , std::string output_file ) : View( parent_rect ) , _input_file { input_file } , _output_file { output_file } + { + hidden(true); + add_children({ + &button_load_freqs, + &text_input_file, + &button_save_freqs, + &button_output_file, + &checkbox_autosave_freqs, + &checkbox_autostart_recon, + &checkbox_continuous, + &checkbox_clear_output + }); + + checkbox_autosave_freqs.set_value( persistent_memory::recon_autosave_freqs() ); + checkbox_autostart_recon.set_value( persistent_memory::recon_autostart_recon() ); + checkbox_continuous.set_value( persistent_memory::recon_continuous() ); + checkbox_clear_output.set_value( persistent_memory::recon_clear_output() ); + + text_input_file.set( _input_file ); + button_output_file.set_text( _output_file ); + + button_load_freqs.on_select = [this, &nav](Button&) { + auto open_view = nav.push(".TXT"); + open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) { + std::string dir_filter = "FREQMAN/"; + std::string str_file_path = new_file_path.string(); + if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder + // get the filename without txt extension so we can use load_freqman_file fcn + _input_file = new_file_path.stem().string(); + text_input_file.set( _input_file ); + } else { + nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired."); + } + }; + }; + + button_save_freqs.on_select = [this, &nav](Button&) { + auto open_view = nav.push(".TXT"); + open_view->on_changed = [this,&nav](std::filesystem::path new_file_path) { + std::string dir_filter = "FREQMAN/"; + std::string str_file_path = new_file_path.string(); + if (str_file_path.find(dir_filter) != string::npos) { // assert file from the FREQMAN folder + _output_file = new_file_path.stem().string(); + button_output_file.set_text( _output_file ); + } else { + nav.display_modal("LOAD ERROR", "A valid file from\nFREQMAN directory is\nrequired."); + } + }; + }; + + button_output_file.on_select =[this, &nav](Button&) { + text_prompt( nav, _output_file , 28 , + [this](std::string& buffer) { + _output_file = buffer ; + button_output_file.set_text( _output_file ); + } ); + }; + }; + + void ReconSetupViewMain::Save( std::string &input_file , std::string &output_file ) { + persistent_memory::set_recon_autosave_freqs(checkbox_autosave_freqs.value()); + persistent_memory::set_recon_autostart_recon(checkbox_autostart_recon.value()); + persistent_memory::set_recon_continuous(checkbox_continuous.value()); + persistent_memory::set_recon_clear_output(checkbox_clear_output.value()); + input_file=_input_file ; + output_file=_output_file ; + }; + void ReconSetupViewMore::Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode ) { + persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value()); + persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value()); + persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value()); + persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value()); + recon_lock_duration = field_recon_lock_duration.value(); + recon_lock_nb_match = field_recon_lock_nb_match.value(); + recon_match_mode = field_recon_match_mode . selected_index_value() ; + }; + + void ReconSetupViewMain::focus() { + button_load_freqs.focus(); + } + + ReconSetupViewMore::ReconSetupViewMore( NavigationView &nav , Rect parent_rect , uint32_t recon_lock_duration , uint32_t recon_lock_nb_match , uint32_t recon_match_mode ) : View( parent_rect ), _recon_lock_duration { recon_lock_duration } , _recon_lock_nb_match { recon_lock_nb_match } , _recon_match_mode { recon_match_mode } + { + (void)nav; + hidden(true); + + add_children({ + &checkbox_load_freqs, + &checkbox_load_ranges, + &checkbox_load_hamradios, + &checkbox_update_ranges_when_recon, + &text_recon_lock_duration, + &field_recon_lock_duration, + &text_recon_lock_nb, + &field_recon_lock_nb_match, + &field_recon_match_mode, + }); + + checkbox_load_freqs.set_value( persistent_memory::recon_load_freqs() ); + checkbox_load_ranges.set_value( persistent_memory::recon_load_ranges() ); + checkbox_load_hamradios.set_value( persistent_memory::recon_load_hamradios() ); + checkbox_update_ranges_when_recon.set_value( persistent_memory::recon_update_ranges_when_recon() ); + field_recon_lock_duration.set_value( _recon_lock_duration ); + field_recon_lock_nb_match.set_value( _recon_lock_nb_match ); + field_recon_match_mode.set_by_value( _recon_match_mode ); + }; + + void ReconSetupViewMore::focus() { + checkbox_load_freqs.focus(); + } + + void ReconSetupView::focus() { + viewMain.focus(); + } + + ReconSetupView::ReconSetupView( + NavigationView& nav , std::string _input_file , std::string _output_file , uint32_t _recon_lock_duration , uint32_t _recon_lock_nb_match , uint32_t _recon_match_mode ) : nav_ { nav } , input_file { _input_file } , output_file { _output_file } , recon_lock_duration { _recon_lock_duration } , recon_lock_nb_match { _recon_lock_nb_match } , recon_match_mode { _recon_match_mode } + { + add_children({ + &tab_view, + &viewMain, + &viewMore, + &button_save + }); + + button_save.on_select = [this,&nav](Button&) { + viewMain.Save( input_file , output_file ); + viewMore.Save( recon_lock_duration , recon_lock_nb_match , recon_match_mode ); + std::vector messages ; + messages.push_back( input_file ); + messages.push_back( output_file ); + messages.push_back( to_string_dec_uint( recon_lock_duration ) ); + messages.push_back( to_string_dec_uint( recon_lock_nb_match ) ); + messages.push_back( to_string_dec_uint( recon_match_mode ) ); + on_changed( messages ); + nav.pop(); + }; + } +} /* namespace ui */ diff --git a/firmware/application/apps/ui_recon_settings.hpp b/firmware/application/apps/ui_recon_settings.hpp new file mode 100644 index 00000000..c475a634 --- /dev/null +++ b/firmware/application/apps/ui_recon_settings.hpp @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 "serializer.hpp" +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_tabview.hpp" +#include "ui_navigation.hpp" +#include "string_format.hpp" + +namespace ui { + + bool ReconSetupLoadStrings( std::string source, std::string &input_file , std::string &output_file , uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , int32_t &recon_squelch_level , uint32_t &recon_match_mode , int32_t &wait , uint32_t &lock_wait , int32_t &volume ); + bool ReconSetupSaveStrings( std::string dest, const std::string input_file , const std::string output_file , const uint32_t recon_lock_duration , const uint32_t recon_lock_nb_match , int32_t recon_squelch_level , uint32_t recon_match_mode , int32_t wait , uint32_t lock_wait , int32_t volume ); + + class ReconSetupViewMain : public View { + public: + ReconSetupViewMain( NavigationView& nav, Rect parent_rect , std::string input_file , std::string output_file ); + void Save( std::string &input_file , std::string &output_file ); + void focus() override; + + private: + std::string _input_file = { "RECON" }; + std::string _output_file = { "RECON_RESULTS" }; + + Button button_load_freqs { + { 1 * 8 , 12 , 18 * 8 , 22 }, + "select input file" + }; + Text text_input_file { + { 1 * 8 , 4 + 2 * 16, 18 * 8, 22 }, + "RECON" + }; + + Button button_save_freqs { + { 1 * 8 , 4 * 16 - 8 , 18 * 8 , 22 }, + "select output file" + }; + Button button_output_file { + { 1 * 8 , 5 * 16 - 2, 18 * 8, 22 }, + "RECON_RESULTS" + }; + + Checkbox checkbox_autosave_freqs { + { 1 * 8, 7 * 16 - 4 }, + 3, + "autosave freqs" + }; + + Checkbox checkbox_autostart_recon { + { 1 * 8, 9 * 16 - 4 }, + 3, + "autostart recon" + }; + + Checkbox checkbox_continuous { + { 1 * 8, 11 * 16 - 4 }, + 3, + "continuous" + }; + Checkbox checkbox_clear_output { + { 1 * 8, 13 * 16 - 4 }, + 3, + "clear output at start" + }; + }; + + class ReconSetupViewMore : public View { + public: + ReconSetupViewMore( NavigationView& nav, Rect parent_rect , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode ); + + void Save( uint32_t &recon_lock_duration , uint32_t &recon_lock_nb_match , uint32_t &recon_match_mode ); + + void focus() override; + + private: + + const uint32_t _recon_lock_duration = 50 ; + const uint32_t _recon_lock_nb_match = 10 ; + const uint32_t _recon_match_mode = 0 ; + + Checkbox checkbox_load_freqs { + { 1 * 8, 12 }, + 3, + "input: load freqs" + }; + + Checkbox checkbox_load_ranges { + { 1 * 8, 42 }, + 3, + "input: load ranges" + }; + + Checkbox checkbox_load_hamradios { + { 1 * 8, 72 }, + 3, + "input: load hamradios" + }; + + Checkbox checkbox_update_ranges_when_recon { + { 1 * 8, 102 }, + 3, + "auto update m-ranges" + }; + Text text_recon_lock_duration { + { 1 * 8 , 132 , 22 * 8 , 22 }, + " ms (lock duration)" + }; + NumberField field_recon_lock_duration { + { 1 * 8, 132 }, // position X , Y + 4, // number of displayed digits (even empty) + { 50 , 990 }, // range of number + 10, // rotary encoder increment + ' ', // filling character + false // can loop + }; + Text text_recon_lock_nb { + { 1 * 8 , 162 , 25 * 8 , 22 }, + " x (nb lock to match freq)" + }; + NumberField field_recon_lock_nb_match { + { 1 * 8, 162 }, + 4, + { 1, 99 }, + 1, + ' ', + false + }; + OptionsField field_recon_match_mode { + { 1 * 8, 192 }, + 20, // CONTINUOUS MATCH MODE / SPARSE TIMED MATCH MODE + { + { "SQL MATCH: CONTINOUS" , 0 }, + { "SQL MATCH: SPARSE" , 1 } + } + }; + }; + + + class ReconSetupView : public View { + public: + ReconSetupView( NavigationView& nav , std::string _input_file , std::string _output_file , const uint32_t _recon_lock_duration , const uint32_t _recon_lock_nb_match , const uint32_t _recon_match_mode ); + + std::function messages )> on_changed { }; + + void focus() override; + + std::string title() const override { return "Recon setup"; }; + + private: + + NavigationView& nav_ ; + + std::string input_file = { "RECON" }; + std::string output_file = { "RECON_RESULTS" }; + uint32_t recon_lock_duration = 50 ; + uint32_t recon_lock_nb_match = 10 ; + uint32_t recon_match_mode = 0 ; + + Rect view_rect = { 0, 3 * 8, 240, 230 }; + + ReconSetupViewMain viewMain{ nav_ , view_rect , input_file , output_file }; + ReconSetupViewMore viewMore{ nav_ , view_rect , recon_lock_duration , recon_lock_nb_match , recon_match_mode }; + + TabView tab_view { + { "Main", Color::cyan() , &viewMain }, + { "More", Color::green(), &viewMore } + }; + Button button_save { + { 9 * 8, 255, 14 * 8 , 40 }, + "SAVE" + }; + }; + +} /* namespace ui */