diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 80fc9c56..c9e3beac 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -270,6 +270,9 @@ set(CPPSRC apps/ui_playlist.cpp apps/gps_sim_app.cpp apps/soundboard_app.cpp + apps/ui_recon.cpp + apps/ui_recon_settings.cpp + apps/tpms_app.cpp apps/tpms_app.cpp protocols/aprs.cpp protocols/ax25.cpp 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 */ diff --git a/firmware/application/freqman.cpp b/firmware/application/freqman.cpp index c172d158..37c1020d 100644 --- a/firmware/application/freqman.cpp +++ b/firmware/application/freqman.cpp @@ -23,166 +23,574 @@ #include "freqman.hpp" #include +using option_t = std::pair; +using options_t = std::vector; + +options_t freqman_entry_modulations = { + { "AM", 0 }, + { "NFM", 1 }, + { "WFM", 2 } +}; + +options_t freqman_entry_bandwidths[ 4 ] = { + { //AM + { "DSB", 0 }, + { "USB", 1 }, + { "LSB", 2 }, + { "CW" , 3 } + }, + { //NFM + { "8k5" , 0 }, + { "11k" , 1 }, + { "16k" , 2 } + }, + { //WFM + { "16k" , 0 }, + } +}; + +options_t freqman_entry_steps = { + { "5KHz (SA AM)" , 5000 }, + { "6.25KHz(NFM)" , 6250 }, + { "8.33KHz(AIR)" , 8330 }, + { "9KHz (EU AM)" , 9000 }, + { "10KHz(US AM)" , 10000 }, + { "12.5KHz(NFM)" , 12500 }, + { "15KHz (HFM)" , 15000 }, + { "25KHz (N1)" , 25000 }, + { "50KHz (FM1)" , 50000 }, + { "100KHz (FM2)" , 100000 }, + { "250KHz (N2)" , 250000 }, + { "500KHz " , 500000 }, + { "1MHz " , 1000000 } +}; + +options_t freqman_entry_steps_short = { + { "5KHz" , 5000 }, + { "6.25KHz" , 6250 }, + { "8.33KHz" , 8330 }, + { "9KHz" , 9000 }, + { "10KHz" , 10000 }, + { "12.5KHz" , 12500 }, + { "15KHz" , 15000 }, + { "25KHz" , 25000 }, + { "50KHz" , 50000 }, + { "100KHz" , 100000 }, + { "250KHz" , 250000 }, + { "500KHz" , 500000 }, + { "1MHz" , 1000000 } +}; + std::vector get_freqman_files() { - std::vector file_list; - - auto files = scan_root_files(u"FREQMAN", u"*.TXT"); - - for (auto file : files) { - std::string file_name = file.stem().string(); - // don't propose tmp / hidden files in freqman's list - if (file_name.length() && file_name[0] != '.') { - file_list.emplace_back(file_name); - } - } - - return file_list; + std::vector file_list; + + auto files = scan_root_files(u"FREQMAN", u"*.TXT"); + + for (auto file : files) { + std::string file_name = file.stem().string(); + // don't propose tmp / hidden files in freqman's list + if (file_name.length() && file_name[0] != '.') { + file_list.emplace_back(file_name); + } + } + + return file_list; }; bool load_freqman_file(std::string& file_stem, freqman_db &db) { - File freqman_file; - size_t length, n = 0, file_position = 0; - char * pos; - char * line_start; - char * line_end; - std::string description; - rf::Frequency frequency_a, frequency_b; - char file_data[257]; - freqman_entry_type type; - - db.clear(); - - auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT"); - if (result.is_valid()) - return false; - - while (1) { - // Read a 256 bytes block from file - freqman_file.seek(file_position); - - memset(file_data, 0, 257); - auto read_size = freqman_file.read(file_data, 256); - if (read_size.is_error()) - return false; // Read error - - file_position += 256; - - // Reset line_start to beginning of buffer - line_start = file_data; - - if (!strstr(file_data, "f=") && !strstr(file_data, "a=")) - break; - - // Look for complete lines in buffer - while ((line_end = strstr(line_start, "\x0A"))) { - // Read frequency - pos = strstr(line_start, "f="); - frequency_b = 0; - type = SINGLE; - if (pos) { - pos += 2; - frequency_a = strtoll(pos, nullptr, 10); - } else { - // ...or range - pos = strstr(line_start, "a="); - if (pos) { - pos += 2; - frequency_a = strtoll(pos, nullptr, 10); - type = RANGE; - pos = strstr(line_start, "b="); - if (pos) { - pos += 2; - frequency_b = strtoll(pos, nullptr, 10); - } else - frequency_b = 0; - } else - frequency_a = 0; - } - - // Read description until , or LF - pos = strstr(line_start, "d="); - if (pos) { - pos += 2; - length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN); - description = string(pos, length); - } else - description = "-"; - - db.push_back({ frequency_a, frequency_b, description, type }); - n++; - - if (n >= FREQMAN_MAX_PER_FILE) return true; - - line_start = line_end + 1; - if (line_start - file_data >= 256) break; - } - - if (read_size.value() != 256) - break; // End of file - - // Restart at beginning of last incomplete line - file_position -= (file_data + 256 - line_start); - } - - return true; + return load_freqman_file_ex( file_stem , db , true , true , true ); + /* File freqman_file; + size_t length, n = 0, file_position = 0; + char * pos; + char * line_start; + char * line_end; + std::string description; + rf::Frequency frequency_a, frequency_b; + char file_data[257]; + freqman_entry_type type; + + db.clear(); + + auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT"); + if (result.is_valid()) + return false; + + while (1) { + // Read a 256 bytes block from file + freqman_file.seek(file_position); + + memset(file_data, 0, 257); + auto read_size = freqman_file.read(file_data, 256); + if (read_size.is_error()) + return false; // Read error + + file_position += 256; + + // Reset line_start to beginning of buffer + line_start = file_data; + + if (!strstr(file_data, "f=") && !strstr(file_data, "a=")) + break; + + // Look for complete lines in buffer + while ((line_end = strstr(line_start, "\x0A"))) { + // Read frequency + pos = strstr(line_start, "f="); + frequency_b = 0; + type = SINGLE; + if (pos) { + pos += 2; + frequency_a = strtoll(pos, nullptr, 10); + } else { + // ...or range + pos = strstr(line_start, "a="); + if (pos) { + pos += 2; + frequency_a = strtoll(pos, nullptr, 10); + type = RANGE; + pos = strstr(line_start, "b="); + if (pos) { + pos += 2; + frequency_b = strtoll(pos, nullptr, 10); + } else + frequency_b = 0; + } else + frequency_a = 0; + } + + // Read description until , or LF + pos = strstr(line_start, "d="); + if (pos) { + pos += 2; + length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN); + description = string(pos, length); + } else + description = "-"; + + db.push_back({ frequency_a, frequency_b, description, type , -1 , -1 , -1 , -1 }); + n++; + + if (n >= FREQMAN_MAX_PER_FILE) return true; + + line_start = line_end + 1; + if (line_start - file_data >= 256) break; } -bool save_freqman_file(std::string& file_stem, freqman_db &db) { - File freqman_file; - std::string item_string; - rf::Frequency frequency_a, frequency_b; - - if (!create_freqman_file(file_stem, freqman_file)) - return false; - - for (size_t n = 0; n < db.size(); n++) { - auto& entry = db[n]; +if (read_size.value() != 256) + break; // End of file - frequency_a = entry.frequency_a; - - if (entry.type == SINGLE) { - // Single - - // TODO: Make to_string_dec_uint be able to return uint64_t's - // Please forgive me... - item_string = "f=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); - - } else { - // Range - frequency_b = entry.frequency_b; - - item_string = "a=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); - item_string += ",b=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0'); - } - - if (entry.description.size()) - item_string += ",d=" + entry.description; - - freqman_file.write_line(item_string); - } - - return true; + // Restart at beginning of last incomplete line + file_position -= (file_data + 256 - line_start); + } +return true; +*/ +} + +bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs , bool load_ranges , bool load_hamradios ) { + File freqman_file; + size_t length, n = 0, file_position = 0; + char * pos; + char * line_start; + char * line_end; + std::string description; + rf::Frequency frequency_a, frequency_b; + char file_data[257]; + freqman_entry_type type; + freqman_index_t modulation = 0 ; + freqman_index_t bandwidth = 0 ; + freqman_index_t step = 0 ; + freqman_index_t tone = 0 ; + + + db.clear(); + + auto result = freqman_file.open("FREQMAN/" + file_stem + ".TXT"); + if (result.is_valid()) + return false; + + while (1) { + // Read a 256 bytes block from file + freqman_file.seek(file_position); + + memset(file_data, 0, 257); + auto read_size = freqman_file.read(file_data, 256); + if (read_size.is_error()) + return false; // Read error + + file_position += 256; + + // Reset line_start to beginning of buffer + line_start = file_data; + + if (!strstr(file_data, "f=") && !strstr(file_data, "a=") && !strstr(file_data, "r=") ) + break; + + // Look for complete lines in buffer + while ((line_end = strstr(line_start, "\x0A"))) { + + modulation = -1 ; + bandwidth = -1 ; + step = -1 ; + tone = -1 ; + type=SINGLE ; + + frequency_a = frequency_b = 0; + // Read frequency + pos = strstr(line_start, "f="); + if(pos) { + pos += 2; + frequency_a = strtoll(pos, nullptr, 10); + } else { + // ...or range + pos = strstr(line_start, "a="); + if (pos) { + pos += 2; + frequency_a = strtoll(pos, nullptr, 10); + type = RANGE; + pos = strstr(line_start, "b="); + if (pos) { + pos += 2; + frequency_b = strtoll(pos, nullptr, 10); + } else + frequency_b = 0; + }else { + // ... or hamradio + pos = strstr(line_start, "r="); + if (pos) { + pos += 2; + frequency_a = strtoll(pos, nullptr, 10); + type = HAMRADIO; + pos = strstr(line_start, "t="); + if (pos) { + pos += 2; + frequency_b = strtoll(pos, nullptr, 10); + } else + frequency_b = frequency_a ; + } else + frequency_a = 0; + } + } + // modulation if any + pos = strstr(line_start, "m="); + if (pos) { + pos += 2; + modulation = freqman_entry_get_modulation_from_str( pos ); + } + // bandwidth if any + pos = strstr(line_start, "bw="); + if (pos) { + pos += 3; + bandwidth = freqman_entry_get_bandwidth_from_str( modulation , pos ); + } + // step if any + pos = strstr(line_start, "s="); + if (pos) { + pos += 2; + step = freqman_entry_get_step_from_str_short( pos ); + } + // ctcss tone if any + /* disabled until better form + pos = strstr(line_start, "c="); + if (pos) { + pos += 2; + tone = tone_key_index_by_value( strtoll( pos , nullptr , 10 ) ); + } */ + // Read description until , or LF + pos = strstr(line_start, "d="); + if (pos) { + pos += 2; + length = std::min(strcspn(pos, ",\x0A"), (size_t)FREQMAN_DESC_MAX_LEN); + description = string(pos, length); + } else + description = "-"; + if( (type == SINGLE && load_freqs) || (type == RANGE && load_ranges) || (type == HAMRADIO && load_hamradios) ) + { + db.push_back({ frequency_a, frequency_b, description, type , modulation , bandwidth , step , tone }); + n++; + if (n >= FREQMAN_MAX_PER_FILE) return true; + } + + line_start = line_end + 1; + if (line_start - file_data >= 256) break; + } + + if (read_size.value() != 256) + break; // End of file + + // Restart at beginning of last incomplete line + file_position -= (file_data + 256 - line_start); + } + + /* populate implicitly specified modulation / bandwidth */ + if( db.size() > 2 ) + { + modulation = db[ 0 ] . modulation; + bandwidth = db[ 0 ] . bandwidth; + + for( unsigned int it = 1 ; it < db.size() ; it ++ ) + { + if( db[ it ] . modulation < 0 ) + { + db[ it ] . modulation = modulation ; + } + else + { + modulation = db[ it ] . modulation ; + } + if( db[ it ] . bandwidth < 0 ) + { + db[ it ] . bandwidth = bandwidth ; + } + else + { + modulation = db[ it ] . bandwidth ; + } + } + } + return true; +} + +bool get_freq_string( freqman_entry &entry , std::string &item_string ) +{ + rf::Frequency frequency_a, frequency_b; + + frequency_a = entry.frequency_a; + if (entry.type == SINGLE) { + // Single + item_string = "f=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); + } else if( entry.type == RANGE ) { + // Range + frequency_b = entry.frequency_b; + item_string = "a=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); + item_string += ",b=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0'); + if( entry.step >= 0 ) + { + item_string += ",s=" + freqman_entry_get_step_string_short( entry.step ); + } + } else if( entry.type == HAMRADIO ) { + frequency_b = entry.frequency_b; + item_string = "r=" + to_string_dec_uint(frequency_a / 1000) + to_string_dec_uint(frequency_a % 1000UL, 3, '0'); + item_string += ",t=" + to_string_dec_uint(frequency_b / 1000) + to_string_dec_uint(frequency_b % 1000UL, 3, '0'); + if( entry.tone >= 0 ) + { + item_string += ",c=" + tone_key_string( entry.tone ); + } + } + if( entry.modulation >= 0 && (unsigned)entry.modulation < freqman_entry_modulations . size() ) + { + item_string += ",m=" + freqman_entry_get_modulation_string( entry.modulation ); + if( entry.bandwidth >= 0 && (unsigned)entry.bandwidth < freqman_entry_bandwidths[ entry.modulation ] . size() ) + { + item_string += ",bw=" + freqman_entry_get_bandwidth_string( entry.modulation , entry.bandwidth ); + } + } + if (entry.description.size()) + item_string += ",d=" + entry.description; + + return true ; +} + +bool save_freqman_file(std::string &file_stem, freqman_db &db) { + + File freqman_file; + + std::string freq_file_path = "FREQMAN/" + file_stem + ".TXT"; + std::string tmp_freq_file_path = "FREQMAN/" + file_stem + ".TXT.TMP"; + + if( !db.size() ) + { + delete_file( "FREQMAN/"+file_stem+".TXT" ); + return true ; + } + + delete_file( tmp_freq_file_path ); + auto result = freqman_file.open( tmp_freq_file_path ); + if ( !result.is_valid() ) { + for (size_t n = 0; n < db.size(); n++) { + std::string item_string; + auto& entry = db[n]; + get_freq_string( entry , item_string ); + freqman_file.write_line( item_string ); + delete &item_string; + } + delete_file( freq_file_path ); + rename_file( tmp_freq_file_path , freq_file_path ); + return true; + } + return false ; } bool create_freqman_file(std::string& file_stem, File& freqman_file) { - auto result = freqman_file.create("FREQMAN/" + file_stem + ".TXT"); - if (result.is_valid()) - return false; - - return true; + + auto result = freqman_file.create( "FREQMAN/" + file_stem + ".TXT" ); + + if (result.is_valid()) + return false; + + return true; } std::string freqman_item_string(freqman_entry &entry, size_t max_length) { - std::string item_string; + std::string item_string; - if (entry.type == SINGLE) { - item_string = to_string_short_freq(entry.frequency_a) + "M: " + entry.description; - } else { - item_string = "Range: " + entry.description; - } - - if (item_string.size() > max_length) - return item_string.substr(0, max_length - 3) + "..."; - - return item_string; + switch( entry.type ){ + case SINGLE: + item_string = to_string_short_freq(entry.frequency_a) + "M: " + entry.description; + break; + case RANGE: + item_string = "R: " + entry.description; + break; + case HAMRADIO: + item_string = "H: " + entry.description; + break; + default: + item_string = "!UNKNOW TYPE " + entry.description; + break; + } + + if (item_string.size() > max_length) + return item_string.substr(0, max_length - 3) + "..."; + + return item_string; +} + +void freqman_set_modulation_option( OptionsField &option ) +{ + option.set_options( freqman_entry_modulations ); +} + +void freqman_set_bandwidth_option( freqman_index_t modulation , OptionsField &option ) +{ + option.set_options( freqman_entry_bandwidths[ modulation ] ); +} + +void freqman_set_step_option( OptionsField &option ) +{ + option.set_options( freqman_entry_steps ); +} + +void freqman_set_step_option_short( OptionsField &option ) +{ + option.set_options( freqman_entry_steps_short ); +} + +std::string freqman_entry_get_modulation_string( freqman_index_t modulation ) +{ + if( modulation < 0 || (unsigned)modulation >= freqman_entry_modulations . size() ) + { + return std::string( "" ); // unknown modulation + } + return freqman_entry_modulations[ modulation ] . first ; +} + +std::string freqman_entry_get_bandwidth_string( freqman_index_t modulation , freqman_index_t bandwidth ) +{ + if( modulation < 0 || (unsigned)modulation >= freqman_entry_modulations . size() ) + { + return std::string( "" ); // unknown modulation + } + if( bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[ modulation ] . size() ) + { + return std::string( "" ); // unknown modulation + } + return freqman_entry_bandwidths[ modulation ][ bandwidth ] . first ; +} + +std::string freqman_entry_get_step_string( freqman_index_t step ) +{ + if( step < 0 || (unsigned)step >= freqman_entry_steps . size() ) + { + return std::string( "" ); // unknown modulation + } + return freqman_entry_steps[ step ] . first ; +} + +std::string freqman_entry_get_step_string_short( freqman_index_t step ) +{ + if( step < 0 || (unsigned)step >= freqman_entry_steps_short . size() ) + { + return std::string( "" ); // unknown modulation + } + return freqman_entry_steps_short[ step ] . first ; +} + +int32_t freqman_entry_get_modulation_value( freqman_index_t modulation ) +{ + if( modulation < 0 || (unsigned)modulation >= freqman_entry_modulations . size() ) + { + return -1 ; // unknown modulation + } + return freqman_entry_modulations[ modulation ] . second ; +} + +int32_t freqman_entry_get_bandwidth_value( freqman_index_t modulation , freqman_index_t bandwidth ) +{ + if( modulation < 0 || (unsigned)modulation >= freqman_entry_modulations . size() ) + { + return -1 ; // unknown modulation + } + if( bandwidth < 0 || (unsigned)bandwidth > freqman_entry_bandwidths[ modulation ] . size() ) + { + return -1 ; // unknown bandwidth for modulation + } + return freqman_entry_bandwidths[ modulation ][ bandwidth ] . second ; +} + +int32_t freqman_entry_get_step_value( freqman_index_t step ) +{ + if( step < 0 || (unsigned)step >= freqman_entry_steps . size() ) + { + return -1 ; // unknown modulation + } + return freqman_entry_steps[ step ] . second ; +} + +freqman_index_t freqman_entry_get_modulation_from_str( char *str ) +{ + if( !str ) + return -1 ; + for( freqman_index_t index = 0 ; (unsigned)index < freqman_entry_modulations . size() ; index ++ ) + { + if( strncmp( freqman_entry_modulations[ index ] . first . c_str() , str , freqman_entry_modulations[ index ] . first . size() ) == 0 ) + return index ; + } + return -1 ; +} + +freqman_index_t freqman_entry_get_bandwidth_from_str( freqman_index_t modulation , char *str ) +{ + if( !str ) + return -1 ; + if( modulation < 0 || (unsigned)modulation >= freqman_entry_modulations . size() ) + return -1 ; + for( freqman_index_t index = 0 ; (unsigned)index < freqman_entry_bandwidths[ modulation ] . size() ; index ++ ) + { + if( strncmp( freqman_entry_bandwidths[ modulation ][ index ] . first . c_str() , str , freqman_entry_bandwidths[ modulation ][ index ] . first . size() ) == 0 ) + return index ; + } + return -1 ; +} + +freqman_index_t freqman_entry_get_step_from_str( char *str ) +{ + if( !str ) + return -1 ; + for( freqman_index_t index = 0 ; (unsigned)index < freqman_entry_steps . size() ; index ++ ) + { + if( strncmp( freqman_entry_steps[ index ] . first . c_str() , str , freqman_entry_steps[ index ] . first . size() ) == 0 ) + return index ; + } + return -1 ; +} + +freqman_index_t freqman_entry_get_step_from_str_short( char *str ) +{ + if( !str ) + return -1 ; + for( freqman_index_t index = 0 ; (unsigned)index < freqman_entry_steps_short . size() ; index ++ ) + { + if( strncmp( freqman_entry_steps_short[ index ] . first . c_str() , str , freqman_entry_steps_short[ index ] . first . size() ) == 0 ) + return index ; + } + return -1 ; } diff --git a/firmware/application/freqman.hpp b/firmware/application/freqman.hpp index 34c08d10..8e9b2069 100644 --- a/firmware/application/freqman.hpp +++ b/firmware/application/freqman.hpp @@ -20,14 +20,16 @@ * Boston, MA 02110-1301, USA. */ +#ifndef __FREQMAN_H__ +#define __FREQMAN_H__ + #include #include #include "file.hpp" #include "ui_receiver.hpp" +#include "tone_key.hpp" #include "string_format.hpp" - -#ifndef __FREQMAN_H__ -#define __FREQMAN_H__ +#include "ui_widget.hpp" #define FREQMAN_DESC_MAX_LEN 30 #define FREQMAN_MAX_PER_FILE 99 @@ -35,6 +37,10 @@ using namespace ui; using namespace std; +using namespace tonekey; + +// needs to be signed as -1 means not set +typedef int8_t freqman_index_t ; enum freqman_error { NO_ERROR = 0, @@ -44,13 +50,23 @@ enum freqman_error { }; enum freqman_entry_type { - SINGLE = 0, - RANGE + SINGLE = 0, //f= + RANGE, //a=,b= + HAMRADIO, //r=,t= + ERROR_TYPE +}; + +enum freqman_entry_modulation { + AM_MODULATION = 0 , + NFM_MODULATION , + WFM_MODULATION , + MODULATION_DEF , + ERROR_MODULATION }; //Entry step placed for AlainD freqman version (or any other enhanced version) enum freqman_entry_step { - STEP_DEF = 0, // default + STEP_DEF = -1, // default AM_US, // 10 kHz AM/CB AM_EUR, // 9 kHz LW/MW NFM_1, // 12,5 kHz (Analogic PMR 446) @@ -63,21 +79,46 @@ enum freqman_entry_step { ERROR_STEP }; -// freqman_entry_step step added, as above, to provide compatibility / future enhancement. struct freqman_entry { - rf::Frequency frequency_a { 0 }; - rf::Frequency frequency_b { 0 }; - std::string description { }; - freqman_entry_type type { }; - freqman_entry_step step { }; + rf::Frequency frequency_a { 0 }; // 'f=freq' or 'a=freq_start' or 'r=recv_freq' + rf::Frequency frequency_b { 0 }; // 'b=freq_end' or 't=tx_freq' + std::string description { }; // 'd=desc' + freqman_entry_type type { }; // SINGLE,RANGE,HAMRADIO + freqman_index_t modulation { }; // AM,NFM,WFM + freqman_index_t bandwidth { }; // AM_DSB, ... + freqman_index_t step { }; // 5Khz (SA AM,... + tone_index tone { }; // 0XZ, 11 1ZB,... }; using freqman_db = std::vector; std::vector get_freqman_files(); bool load_freqman_file(std::string& file_stem, freqman_db& db); +bool load_freqman_file_ex(std::string& file_stem, freqman_db& db, bool load_freqs , bool load_ranges , bool load_hamradios ); +bool get_freq_string( freqman_entry &entry , std::string &item_string ); bool save_freqman_file(std::string& file_stem, freqman_db& db); bool create_freqman_file(std::string& file_stem, File& freqman_file); + std::string freqman_item_string(freqman_entry &item, size_t max_length); +void freqman_set_bandwidth_option( freqman_index_t modulation , OptionsField &option ); +void freqman_set_modulation_option( OptionsField &option ); +void freqman_set_step_option( OptionsField &option ); +void freqman_set_step_option_short( OptionsField &option ); +void freqman_set_tone_option( OptionsField &option ); + +std::string freqman_entry_get_modulation_string( freqman_index_t modulation ); +std::string freqman_entry_get_bandwidth_string( freqman_index_t modulation , freqman_index_t bandwidth ); +std::string freqman_entry_get_step_string( freqman_index_t step ); +std::string freqman_entry_get_step_string_short( freqman_index_t step ); + +int32_t freqman_entry_get_modulation_value( freqman_index_t modulation ); +int32_t freqman_entry_get_bandwidth_value( freqman_index_t modulation , freqman_index_t bandwidth ); +int32_t freqman_entry_get_step_value( freqman_index_t step ); + +freqman_index_t freqman_entry_get_modulation_from_str( char *str ); +freqman_index_t freqman_entry_get_bandwidth_from_str( freqman_index_t modulation , char *str ); +freqman_index_t freqman_entry_get_step_from_str( char *str ); +freqman_index_t freqman_entry_get_step_from_str_short( char *str ); + #endif/*__FREQMAN_H__*/ diff --git a/firmware/application/tone_key.cpp b/firmware/application/tone_key.cpp index a208c96e..5b5aed6f 100644 --- a/firmware/application/tone_key.cpp +++ b/firmware/application/tone_key.cpp @@ -104,8 +104,30 @@ void tone_keys_populate(OptionsField& field) { field.set_options(tone_key_options); } -float tone_key_frequency(const uint32_t index) { +float tone_key_frequency(const tone_index index) { return tone_keys[index].second; } +std::string tone_key_string( tone_index index ) { + if( index < 0 || (unsigned)index >= tone_keys . size() ) + return std::string( "" ); + return tone_keys[ index ] .first ; +} + +tone_index tone_key_index_by_string( char *str ) { + if( !str ) + return -1 ; + for( tone_index index = 0 ; (unsigned)index < tone_keys . size() ; index ++ ) + { + if( tone_keys[ index ] . first . compare( str ) >= 0 ) + return index ; + } + return -1 ; +} + +/* tone_index tone_key_index_by_value( int32_t freq ) +{ + return -1 ; +} */ + } diff --git a/firmware/application/tone_key.hpp b/firmware/application/tone_key.hpp index 70438d86..0f5e4f24 100644 --- a/firmware/application/tone_key.hpp +++ b/firmware/application/tone_key.hpp @@ -30,12 +30,18 @@ using namespace ui; namespace tonekey { +typedef int16_t tone_index ; + using tone_key_t = std::vector>; extern const tone_key_t tone_keys; void tone_keys_populate(OptionsField& field); -float tone_key_frequency(const uint32_t index); +float tone_key_frequency(const tone_index index); + +std::string tone_key_string( const tone_index index ); +tone_index tone_key_index_by_string( char *str ); +// tone_index tone_key_index_by_value( int32_t freq ); } diff --git a/firmware/application/ui/ui_rssi.cpp b/firmware/application/ui/ui_rssi.cpp index 84487b16..d99795ec 100644 --- a/firmware/application/ui/ui_rssi.cpp +++ b/firmware/application/ui/ui_rssi.cpp @@ -80,6 +80,21 @@ void RSSI::paint(Painter& painter) { } } +int32_t RSSI::get_min() +{ + return min_ ; +} + +int32_t RSSI::get_avg() +{ + return avg_ ; +} + +int32_t RSSI::get_max() +{ + return max_ ; +} + void RSSI::set_pitch_rssi(bool enabled) { pitch_rssi_enabled = enabled; if (!enabled) baseband::set_pitch_rssi(0, false); diff --git a/firmware/application/ui/ui_rssi.hpp b/firmware/application/ui/ui_rssi.hpp index e22a61af..f792e793 100644 --- a/firmware/application/ui/ui_rssi.hpp +++ b/firmware/application/ui/ui_rssi.hpp @@ -46,6 +46,9 @@ public: } void paint(Painter& painter) override; + int32_t get_min(); + int32_t get_avg(); + int32_t get_max(); private: int32_t min_; diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 65641d75..78246808 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -57,6 +57,7 @@ #include "ui_remote.hpp" #include "ui_scanner.hpp" #include "ui_search.hpp" +#include "ui_recon.hpp" #include "ui_sd_wipe.hpp" #include "ui_settings.hpp" #include "ui_siggen.hpp" @@ -482,6 +483,7 @@ ReceiversMenuView::ReceiversMenuView(NavigationView& nav) { { "POCSAG", ui::Color::green(), &bitmap_icon_pocsag, [&nav](){ nav.push(); } }, { "Radiosnde", ui::Color::green(), &bitmap_icon_sonde, [&nav](){ nav.push(); } }, { "TPMS Cars", ui::Color::green(), &bitmap_icon_tpms, [&nav](){ nav.push(); } }, + { "Recon", ui::Color::green(), &bitmap_icon_scanner, [&nav](){ nav.push(); } }, { "APRS", ui::Color::green(), &bitmap_icon_aprs, [&nav](){ nav.push(); } } /* { "DMR", ui::Color::dark_grey(), &bitmap_icon_dmr, [&nav](){ nav.push(); } }, diff --git a/firmware/common/portapack_persistent_memory.cpp b/firmware/common/portapack_persistent_memory.cpp index e226b4b7..ad6e60ab 100644 --- a/firmware/common/portapack_persistent_memory.cpp +++ b/firmware/common/portapack_persistent_memory.cpp @@ -285,6 +285,9 @@ struct data_t { // Hardware uint32_t hardware_config; + // Recon App + uint64_t recon_config; + constexpr data_t() : structure_version(data_structure_version_enum::VERSION_CURRENT), tuned_frequency(tuned_frequency_reset_value), @@ -311,7 +314,8 @@ struct data_t { tone_mix(tone_mix_reset_value), - hardware_config(0) + hardware_config(0), + recon_config(0) { } }; @@ -663,6 +667,61 @@ void set_clkout_freq(uint32_t freq) { data->ui_config.set_clkout_freq(freq); } +bool recon_autosave_freqs() { + return (data->recon_config & 0x80000000UL) ? true : false; +} +bool recon_autostart_recon() { + return (data->recon_config & 0x40000000UL) ? true : false; +} +bool recon_continuous() { + return (data->recon_config & 0x20000000UL) ? true : false; +} +bool recon_clear_output() { + return (data->recon_config & 0x10000000UL) ? true : false; +} +bool recon_load_freqs() { + return (data->recon_config & 0x08000000UL) ? true : false; +} +bool recon_load_ranges() { + return (data->recon_config & 0x04000000UL) ? true : false; +} +bool recon_update_ranges_when_recon() { + return (data->recon_config & 0x02000000UL) ? true : false; +} +bool recon_load_hamradios() { + return (data->recon_config & 0x01000000UL) ? true : false; +} +bool recon_match_mode() { + return (data->recon_config & 0x00800000UL) ? true : false; +} + +void set_recon_autosave_freqs(const bool v ){ + data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31); +} +void set_recon_autostart_recon(const bool v ){ + data->recon_config = (data->recon_config & ~0x40000000UL) | (v << 30); +} +void set_recon_continuous(const bool v ){ + data->recon_config = (data->recon_config & ~0x20000000UL) | (v << 29); +} +void set_recon_clear_output(const bool v ){ + data->recon_config = (data->recon_config & ~0x10000000UL) | (v << 28); +} +void set_recon_load_freqs(const bool v ){ + data->recon_config = (data->recon_config & ~0x08000000UL) | (v << 27); +} +void set_recon_load_ranges(const bool v ){ + data->recon_config = (data->recon_config & ~0x04000000UL) | (v << 26); +} +void set_recon_update_ranges_when_recon(const bool v ){ + data->recon_config = (data->recon_config & ~0x02000000UL) | (v << 25); +} +void set_recon_load_hamradios(const bool v ){ + data->recon_config = (data->recon_config & ~0x01000000UL) | (v << 24); +} +void set_recon_match_mode(const bool v ) { + data->recon_config = (data->recon_config & ~0x00800000UL) | (v << 23); +} } /* namespace persistent_memory */ } /* namespace portapack */ diff --git a/firmware/common/portapack_persistent_memory.hpp b/firmware/common/portapack_persistent_memory.hpp index f498bcae..038911cb 100644 --- a/firmware/common/portapack_persistent_memory.hpp +++ b/firmware/common/portapack_persistent_memory.hpp @@ -188,6 +188,26 @@ void set_clkout_enabled(bool v); uint32_t clkout_freq(); void set_clkout_freq(uint32_t freq); +/* Recon app */ + bool recon_autosave_freqs(); + bool recon_autostart_recon(); + bool recon_continuous(); + bool recon_clear_output(); + bool recon_load_freqs(); + bool recon_load_ranges(); + bool recon_update_ranges_when_recon(); + bool recon_load_hamradios(); + bool recon_match_mode(); + void set_recon_autosave_freqs(const bool v); + void set_recon_autostart_recon(const bool v); + void set_recon_continuous(const bool v); + void set_recon_clear_output(const bool v); + void set_recon_load_freqs(const bool v); + void set_recon_load_ranges(const bool v); + void set_recon_update_ranges_when_recon(const bool v); + void set_recon_load_hamradios(const bool v ); + void set_recon_match_mode( const bool v ); + } /* namespace persistent_memory */ } /* namespace portapack */ diff --git a/firmware/common/ui_widget.cpp b/firmware/common/ui_widget.cpp index 79cd7cd4..1d8d5265 100644 --- a/firmware/common/ui_widget.cpp +++ b/firmware/common/ui_widget.cpp @@ -984,6 +984,159 @@ bool Button::on_touch(const TouchEvent event) { #endif } + +/* ButtonWithEncoder ****************************************************************/ + +ButtonWithEncoder::ButtonWithEncoder( + Rect parent_rect, + std::string text, + bool instant_exec +) : Widget { parent_rect }, + text_ { text }, + instant_exec_ { instant_exec } +{ + set_focusable(true); +} + +void ButtonWithEncoder::set_text(const std::string value) { + text_ = value; + set_dirty(); +} +int32_t ButtonWithEncoder::get_encoder_delta() { + return encoder_delta ; +} +void ButtonWithEncoder::set_encoder_delta( const int32_t delta ) +{ + encoder_delta = delta ; +} + +std::string ButtonWithEncoder::text() const { + return text_; +} + +void ButtonWithEncoder::paint(Painter& painter) { + Color bg, fg; + const auto r = screen_rect(); + + if (has_focus() || highlighted()) { + bg = style().foreground; + fg = Color::black(); + } else { + bg = Color::grey(); + fg = style().foreground; + } + + const Style paint_style = { style().font, bg, fg }; + + painter.draw_rectangle({r.location(), {r.size().width(), 1}}, Color::light_grey()); + painter.draw_rectangle({r.location().x(), r.location().y() + r.size().height() - 1, r.size().width(), 1}, Color::dark_grey()); + painter.draw_rectangle({r.location().x() + r.size().width() - 1, r.location().y(), 1, r.size().height()}, Color::dark_grey()); + + painter.fill_rectangle( + { r.location().x(), r.location().y() + 1, r.size().width() - 1, r.size().height() - 2 }, + paint_style.background + ); + + const auto label_r = paint_style.font.size_of(text_); + painter.draw_string( + { r.location().x() + (r.size().width() - label_r.width()) / 2, r.location().y() + (r.size().height() - label_r.height()) / 2 }, + paint_style, + text_ + ); +} + +void ButtonWithEncoder::on_focus() { + if( on_highlight ) + on_highlight(*this); +} + +bool ButtonWithEncoder::on_key(const KeyEvent key) { + if( key == KeyEvent::Select ) { + if( on_select ) { + on_select(*this); + return true; + } + } else { + if( on_dir ) { + return on_dir(*this, key); + } + } + + return false; +} + +bool ButtonWithEncoder::on_touch(const TouchEvent event) { + switch(event.type) { + case TouchEvent::Type::Start: + set_highlighted(true); + set_dirty(); + if( on_touch_press) { + on_touch_press(*this); + } + if( on_select && instant_exec_ ) { + on_select(*this); + } + return true; + + + case TouchEvent::Type::End: + set_highlighted(false); + set_dirty(); + if( on_touch_release) { + on_touch_release(*this); + } + if( on_select && !instant_exec_ ) { + on_select(*this); + } + return true; + + default: + return false; + } +#if 0 + switch(event.type) { + case TouchEvent::Type::Start: + flags.highlighted = true; + set_dirty(); + return true; + + case TouchEvent::Type::Move: + { + const bool new_highlighted = screen_rect().contains(event.point); + if( flags.highlighted != new_highlighted ) { + flags.highlighted = new_highlighted; + set_dirty(); + } + } + return true; + + case TouchEvent::Type::End: + if( flags.highlighted ) { + flags.highlighted = false; + set_dirty(); + if( on_select ) { + on_select(*this); + } + } + return true; + + default: + return false; + } +#endif +} +bool ButtonWithEncoder::on_encoder(const EncoderEvent delta) { + if( delta != 0 ) + { + encoder_delta += delta ; + delta_change = true ; + on_change(); + } + else + delta_change = 0 ; + return true ; +} + /* NewButton ****************************************************************/ NewButton::NewButton( diff --git a/firmware/common/ui_widget.hpp b/firmware/common/ui_widget.hpp index 18ff7309..41d638e4 100644 --- a/firmware/common/ui_widget.hpp +++ b/firmware/common/ui_widget.hpp @@ -414,6 +414,51 @@ private: bool instant_exec_ { false }; }; + +class ButtonWithEncoder : public Widget { +public: + std::function on_select { }; + std::function on_touch_release { }; // Executed when releasing touch, after on_select. + std::function on_touch_press { }; // Executed when touching, before on_select. + std::function on_dir { }; + std::function on_highlight { }; + + ButtonWithEncoder(Rect parent_rect, std::string text, bool instant_exec); // instant_exec: Execute on_select when you touching instead of releasing + ButtonWithEncoder( + Rect parent_rect, + std::string text + ) : ButtonWithEncoder { parent_rect, text, false } + { + } + + ButtonWithEncoder( + ) : ButtonWithEncoder { { }, { } } + { + } + + std::function on_change { }; + + void set_text(const std::string value); + int32_t get_encoder_delta(); + void set_encoder_delta( const int32_t delta ); + std::string text() const; + + void paint(Painter& painter) override; + + void on_focus() override; + bool on_key(const KeyEvent key) override; + bool on_touch(const TouchEvent event) override; + bool on_encoder(const EncoderEvent delta) override; + +private: + std::string text_; + int32_t encoder_delta = 0 ; + bool delta_change = 0 ; + bool instant_exec_ { false }; +}; + + + class NewButton : public Widget { public: std::function on_select { }; diff --git a/sdcard/RECON/RECON.CFG b/sdcard/RECON/RECON.CFG new file mode 100644 index 00000000..e69de29b