2022-09-11 10:07:08 -04:00
/*
* Copyright ( C ) 2015 Jared Boone , ShareBrained Technology , Inc .
* Copyright ( C ) 2018 Furrtek
2023-05-22 16:17:28 -04:00
* Copyright ( C ) 2023 gullradriel , Nilorea Studio Inc .
2022-09-11 10:07:08 -04:00
*
* 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"
2023-07-17 14:43:37 -04:00
# include "ui_freqman.hpp"
2023-07-07 16:21:48 -04:00
# include "capture_app.hpp"
2023-07-17 14:43:37 -04:00
# include "convert.hpp"
2023-07-08 16:04:12 -04:00
# include "file.hpp"
2023-07-17 14:43:37 -04:00
# include "file_reader.hpp"
2023-07-08 16:04:12 -04:00
# include "tone_key.hpp"
2023-12-28 05:25:53 -05:00
# include "replay_app.hpp"
# include "string_format.hpp"
# include "ui_fileman.hpp"
# include "io_file.hpp"
# include "io_convert.hpp"
# include "oversample.hpp"
# include "baseband_api.hpp"
# include "metadata_file.hpp"
# include "portapack.hpp"
# include "portapack_persistent_memory.hpp"
# include "utility.hpp"
# include "replay_thread.hpp"
2022-09-11 10:07:08 -04:00
using namespace portapack ;
2023-07-08 16:04:12 -04:00
using namespace tonekey ;
2022-09-11 10:07:08 -04:00
using portapack : : memory : : map : : backup_ram ;
2023-07-17 14:43:37 -04:00
namespace fs = std : : filesystem ;
2022-09-11 10:07:08 -04:00
namespace ui {
2023-12-28 05:25:53 -05:00
void ReconView : : reload_restart_recon ( ) {
frequency_file_load ( ) ;
if ( frequency_list . size ( ) > 0 ) {
if ( fwd ) {
button_dir . set_text ( " FW> " ) ;
} else {
button_dir . set_text ( " <RW " ) ;
}
recon_resume ( ) ;
}
if ( scanner_mode ) {
file_name . set_style ( & Styles : : red ) ;
button_scanner_mode . set_style ( & Styles : : red ) ;
button_scanner_mode . set_text ( " SCAN " ) ;
} else {
file_name . set_style ( & Styles : : blue ) ;
button_scanner_mode . set_style ( & Styles : : blue ) ;
button_scanner_mode . set_text ( " RECON " ) ;
}
if ( frequency_list . size ( ) > FREQMAN_MAX_PER_FILE ) {
file_name . set_style ( & Styles : : yellow ) ;
}
}
2023-08-15 12:29:16 -04:00
void ReconView : : check_update_ranges_from_current ( ) {
2023-08-18 16:05:28 -04:00
if ( frequency_list . size ( ) & & current_is_valid ( ) & & current_entry ( ) . type = = freqman_type : : Range ) {
2023-08-15 12:29:16 -04:00
if ( update_ranges & & ! manual_mode ) {
button_manual_start . set_text ( to_string_short_freq ( current_entry ( ) . frequency_a ) ) ;
frequency_range . min = current_entry ( ) . frequency_a ;
if ( current_entry ( ) . frequency_b ! = 0 ) {
button_manual_end . set_text ( to_string_short_freq ( current_entry ( ) . frequency_b ) ) ;
frequency_range . max = current_entry ( ) . frequency_b ;
} else {
button_manual_end . set_text ( to_string_short_freq ( current_entry ( ) . frequency_a ) ) ;
frequency_range . max = current_entry ( ) . frequency_a ;
}
}
}
}
2023-07-08 16:04:12 -04:00
bool ReconView : : current_is_valid ( ) {
return ( unsigned ) current_index < frequency_list . size ( ) ;
}
freqman_entry & ReconView : : current_entry ( ) {
return * frequency_list [ current_index ] ;
}
2023-06-16 01:50:04 -04:00
void ReconView : : set_loop_config ( bool v ) {
continuous = v ;
button_loop_config . set_style ( v ? & Styles : : green : & Styles : : white ) ;
persistent_memory : : set_recon_continuous ( continuous ) ;
}
2023-12-30 16:32:41 -05:00
void ReconView : : recon_stop_recording ( bool exiting ) {
2023-06-25 02:16:49 -04:00
if ( is_recording ) {
2023-07-07 16:21:48 -04:00
if ( field_mode . selected_index_value ( ) = = SPEC_MODULATION )
button_audio_app . set_text ( " RAW " ) ;
else
button_audio_app . set_text ( " AUDIO " ) ;
2023-06-25 02:16:49 -04:00
button_audio_app . set_style ( & Styles : : white ) ;
record_view - > stop ( ) ;
2023-12-28 05:25:53 -05:00
button_config . set_style ( & Styles : : white ) ;
2023-06-25 02:16:49 -04:00
is_recording = false ;
2023-12-28 05:25:53 -05:00
// repeater mode
2023-12-30 16:32:41 -05:00
if ( ! exiting & & persistent_memory : : recon_repeat_recorded ( ) ) {
2023-12-28 05:25:53 -05:00
start_repeat ( ) ;
}
2023-06-25 02:16:49 -04:00
}
}
2023-05-24 14:52:05 -04:00
void ReconView : : clear_freqlist_for_ui_action ( ) {
2023-12-30 16:32:41 -05:00
recon_stop_recording ( false ) ;
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-05-24 14:52:05 -04:00
// flag to detect and reload frequency_list
if ( ! manual_mode ) {
2023-07-08 16:04:12 -04:00
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
frequency_list = freqman_db { } ;
2023-06-22 11:34:20 -04:00
} else
2023-05-26 12:17:47 -04:00
frequency_list . shrink_to_fit ( ) ;
2023-06-22 11:34:20 -04:00
freqlist_cleared_for_ui_action = true ;
2023-05-24 14:52:05 -04:00
}
void ReconView : : reset_indexes ( ) {
2023-07-08 16:04:12 -04:00
last_entry . modulation = freqman_invalid_index ;
last_entry . bandwidth = freqman_invalid_index ;
last_entry . step = freqman_invalid_index ;
2023-05-24 14:52:05 -04:00
current_index = 0 ;
}
2023-07-08 16:04:12 -04:00
void ReconView : : update_description ( ) {
if ( frequency_list . empty ( ) | | current_entry ( ) . description . empty ( ) ) {
description = " ...no description... " ;
} else {
switch ( current_entry ( ) . type ) {
case freqman_type : : Range :
description = " R: " ;
break ;
case freqman_type : : HamRadio :
description = " H: " ;
break ;
2023-12-28 05:25:53 -05:00
case freqman_type : : Repeater :
description = " L: " ;
break ;
2023-07-08 16:04:12 -04:00
default :
description = " S: " ;
}
description + = current_entry ( ) . description ;
}
desc_cycle . set ( description ) ;
}
2023-05-21 14:42:54 -04:00
void ReconView : : colorize_waits ( ) {
// colorize wait on match
if ( wait = = 0 ) {
2023-06-07 11:33:32 -04:00
field_wait . set_style ( & Styles : : blue ) ;
2023-05-21 14:42:54 -04:00
} else if ( wait > = 500 ) {
2023-06-07 11:33:32 -04:00
field_wait . set_style ( & Styles : : white ) ;
2023-05-21 14:42:54 -04:00
} else if ( wait > - 500 & & wait < 500 ) {
2023-06-07 11:33:32 -04:00
field_wait . set_style ( & Styles : : red ) ;
2023-05-21 14:42:54 -04:00
} else if ( wait < = - 500 ) {
2023-06-07 11:33:32 -04:00
field_wait . set_style ( & Styles : : green ) ;
2023-05-21 14:42:54 -04:00
}
// colorize lock time if in SPARSE mode as in continuous the lock_wait time is disarmed at first lock count
if ( recon_match_mode = = RECON_MATCH_SPARSE ) {
if ( ( recon_lock_duration / STATS_UPDATE_INTERVAL ) < = recon_lock_nb_match ) {
2023-06-07 11:33:32 -04:00
field_lock_wait . set_style ( & Styles : : yellow ) ;
2023-05-21 14:42:54 -04:00
} else {
2023-06-07 11:33:32 -04:00
field_lock_wait . set_style ( & Styles : : white ) ;
2023-05-21 14:42:54 -04:00
}
} else {
2023-06-07 11:33:32 -04:00
field_lock_wait . set_style ( & Styles : : white ) ;
2023-05-21 14:42:54 -04:00
}
}
2023-07-17 14:43:37 -04:00
bool ReconView : : recon_save_freq ( const fs : : path & path , size_t freq_index , bool warn_if_exists ) {
2023-07-08 16:04:12 -04:00
if ( frequency_list . size ( ) = = 0 | | ! current_is_valid ( ) )
2023-05-18 16:16:05 -04:00
return false ;
2023-07-17 14:43:37 -04:00
FreqmanDB freq_db ;
if ( ! freq_db . open ( path , /*create*/ true ) )
return false ;
2023-07-08 16:04:12 -04:00
freqman_entry entry = * frequency_list [ freq_index ] ; // Makes a copy.
2023-07-17 14:43:37 -04:00
// For ranges, save the current frequency instead.
if ( entry . type = = freqman_type : : Range ) {
entry . frequency_a = freq ;
entry . frequency_b = 0 ;
entry . type = freqman_type : : Single ;
entry . modulation = last_entry . modulation ;
entry . bandwidth = last_entry . bandwidth ;
entry . step = freqman_invalid_index ;
2023-05-18 16:16:05 -04:00
}
2023-07-17 14:43:37 -04:00
auto it = freq_db . find_entry ( entry ) ;
auto found = ( it ! = freq_db . end ( ) ) ;
if ( found & & warn_if_exists )
nav_ . display_modal ( " Error " , " Frequency already exists " ) ;
if ( ! found )
freq_db . append_entry ( entry ) ;
2023-05-18 16:16:05 -04:00
return true ;
}
2023-08-20 16:28:02 -04:00
void ReconView : : load_persisted_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 ( ) ;
2023-12-28 05:25:53 -05:00
load_repeaters = persistent_memory : : recon_load_repeaters ( ) ;
2023-08-20 16:28:02 -04:00
update_ranges = persistent_memory : : recon_update_ranges_when_recon ( ) ;
auto_record_locked = persistent_memory : : recon_auto_record_locked ( ) ;
2023-05-18 16:16:05 -04:00
}
void ReconView : : audio_output_start ( ) {
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : start ( ) ;
2023-06-05 14:09:50 -04:00
receiver_model . set_headphone_volume ( receiver_model . headphone_volume ( ) ) ; // WM8731 hack.
2023-05-18 16:16:05 -04:00
}
void ReconView : : recon_redraw ( ) {
if ( last_rssi_min ! = rssi . get_min ( ) | | last_rssi_med ! = rssi . get_avg ( ) | | last_rssi_max ! = rssi . get_max ( ) ) {
last_rssi_min = rssi . get_min ( ) ;
last_rssi_med = rssi . get_avg ( ) ;
last_rssi_max = rssi . get_max ( ) ;
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 " ) ;
}
if ( last_entry . frequency_a ! = freq ) {
last_entry . frequency_a = freq ;
2023-05-22 04:49:07 -04:00
big_display . set ( " FREQ: " + to_string_short_freq ( freq ) + " MHz " ) ;
2023-05-18 16:16:05 -04:00
}
2023-05-22 04:49:07 -04:00
if ( last_nb_match ! = recon_lock_nb_match | | last_freq_lock ! = freq_lock ) {
last_freq_lock = freq_lock ;
2023-05-18 16:16:05 -04:00
last_nb_match = recon_lock_nb_match ;
2023-05-22 04:49:07 -04:00
text_nb_locks . set ( to_string_dec_uint ( freq_lock ) + " / " + to_string_dec_uint ( recon_lock_nb_match ) ) ;
2023-05-18 16:16:05 -04:00
if ( freq_lock = = 0 ) {
// NO FREQ LOCK, ONGOING STANDARD SCANNING
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : white ) ;
2023-05-22 04:49:07 -04:00
if ( recon )
2023-05-18 16:16:05 -04:00
button_pause . set_text ( " <PAUSE> " ) ;
else
button_pause . set_text ( " <RESUME> " ) ;
} else if ( freq_lock = = 1 & & recon_lock_nb_match ! = 1 ) {
// STARTING LOCK FREQ
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : yellow ) ;
2023-05-18 16:16:05 -04:00
button_pause . set_text ( " <SKPLCK> " ) ;
} else if ( freq_lock > = recon_lock_nb_match ) {
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : green ) ;
2023-05-18 16:16:05 -04:00
button_pause . set_text ( " <UNLOCK> " ) ;
}
}
2023-05-22 04:49:07 -04:00
if ( last_db ! = db | | last_list_size ! = frequency_list . size ( ) ) {
last_list_size = frequency_list . size ( ) ;
last_db = db ;
text_max . set ( " / " + to_string_dec_uint ( frequency_list . size ( ) ) + " " + to_string_dec_int ( db ) + " db " ) ;
}
2023-05-18 16:16:05 -04:00
}
void ReconView : : handle_retune ( ) {
if ( last_freq ! = freq ) {
last_freq = freq ;
2023-06-11 14:47:13 -04:00
receiver_model . set_target_frequency ( freq ) ; // Retune
2023-05-18 16:16:05 -04:00
}
if ( frequency_list . size ( ) > 0 ) {
2023-07-08 16:04:12 -04:00
if ( last_entry . modulation ! = current_entry ( ) . modulation & & is_valid ( current_entry ( ) . modulation ) ) {
last_entry . modulation = current_entry ( ) . modulation ;
field_mode . set_selected_index ( current_entry ( ) . modulation ) ;
last_entry . bandwidth = freqman_invalid_index ;
2023-05-18 16:16:05 -04:00
}
// Set bandwidth if any
2023-07-08 16:04:12 -04:00
if ( last_entry . bandwidth ! = current_entry ( ) . bandwidth & & is_valid ( current_entry ( ) . bandwidth ) ) {
last_entry . bandwidth = current_entry ( ) . bandwidth ;
field_bw . set_selected_index ( current_entry ( ) . bandwidth ) ;
2023-05-18 16:16:05 -04:00
}
2023-07-08 16:04:12 -04:00
if ( last_entry . step ! = current_entry ( ) . step & & is_valid ( current_entry ( ) . step ) ) {
last_entry . step = current_entry ( ) . step ;
2023-05-18 16:16:05 -04:00
step = freqman_entry_get_step_value ( last_entry . step ) ;
step_mode . set_selected_index ( step ) ;
}
if ( last_index ! = current_index ) {
last_index = current_index ;
2023-08-15 12:29:16 -04:00
check_update_ranges_from_current ( ) ;
2023-05-18 16:16:05 -04:00
text_cycle . set_text ( to_string_dec_uint ( current_index + 1 , 3 ) ) ;
2023-07-08 16:04:12 -04:00
update_description ( ) ;
2023-05-18 16:16:05 -04:00
}
}
}
void ReconView : : focus ( ) {
button_pause . focus ( ) ;
}
ReconView : : ~ ReconView ( ) {
2023-12-30 16:32:41 -05:00
if ( recon_tx ) {
replay_thread . reset ( ) ;
}
recon_stop_recording ( true ) ;
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-12-30 16:32:41 -05:00
transmitter_model . disable ( ) ;
2023-05-18 16:16:05 -04:00
receiver_model . disable ( ) ;
baseband : : shutdown ( ) ;
}
ReconView : : ReconView ( NavigationView & nav )
: nav_ { nav } {
2023-06-25 02:16:49 -04:00
chrono_start = chTimeNow ( ) ;
2023-12-28 05:25:53 -05:00
tx_view . hidden ( true ) ;
// set record View
2023-06-28 13:14:30 -04:00
record_view = std : : make_unique < RecordView > ( Rect { 0 , 0 , 30 * 8 , 1 * 16 } ,
2023-12-28 05:25:53 -05:00
u " AUTO_AUDIO " , u " AUDIO " ,
2023-06-28 13:14:30 -04:00
RecordView : : FileType : : WAV , 4096 , 4 ) ;
2023-12-28 05:25:53 -05:00
record_view - > set_filename_date_frequency ( true ) ;
record_view - > set_auto_trim ( false ) ;
record_view - > hidden ( true ) ;
record_view - > on_error = [ & nav ] ( std : : string message ) {
nav . display_modal ( " Error " , message ) ;
} ;
2023-05-18 16:16:05 -04:00
add_children ( { & labels ,
& field_lna ,
& field_vga ,
& field_rf_amp ,
& field_volume ,
& field_bw ,
& field_squelch ,
2023-06-11 12:49:28 -04:00
& field_nblocks ,
2023-05-18 16:16:05 -04:00
& field_wait ,
& field_lock_wait ,
2023-05-24 14:52:05 -04:00
& button_config ,
2023-05-18 16:16:05 -04:00
& button_scanner_mode ,
2023-06-16 01:50:04 -04:00
& button_loop_config ,
2023-05-18 16:16:05 -04:00
& file_name ,
& rssi ,
& text_cycle ,
& text_max ,
2023-05-22 04:49:07 -04:00
& text_nb_locks ,
2023-05-18 16:16:05 -04:00
& desc_cycle ,
& big_display ,
& freq_stats ,
& text_timer ,
& text_ctcss ,
& button_manual_start ,
& button_manual_end ,
& button_manual_recon ,
& field_mode ,
2023-06-11 12:49:28 -04:00
& field_recon_match_mode ,
2023-05-18 16:16:05 -04:00
& step_mode ,
& button_pause ,
& button_audio_app ,
& button_add ,
& button_dir ,
& button_restart ,
& button_mic_app ,
2023-06-25 02:16:49 -04:00
& button_remove ,
2023-12-28 05:25:53 -05:00
record_view . get ( ) ,
& progressbar ,
& tx_view } ) ;
2023-05-18 16:16:05 -04:00
def_step = 0 ;
2023-08-20 16:28:02 -04:00
load_persisted_settings ( ) ;
// When update_ranges is set or range invalid, use the rx model frequency instead of the saved values.
if ( update_ranges | | frequency_range . max = = 0 ) {
rf : : Frequency stored_freq = receiver_model . target_frequency ( ) ;
frequency_range . min = clip < rf : : Frequency > ( stored_freq - OneMHz , 0 , MAX_UFREQ ) ;
frequency_range . max = clip < rf : : Frequency > ( stored_freq + OneMHz , 0 , MAX_UFREQ ) ;
}
2023-05-18 16:16:05 -04:00
button_manual_start . set_text ( to_string_short_freq ( frequency_range . min ) ) ;
button_manual_end . set_text ( to_string_short_freq ( frequency_range . max ) ) ;
2023-06-11 14:47:13 -04:00
2023-05-18 16:16:05 -04:00
button_manual_start . on_select = [ this , & nav ] ( ButtonWithEncoder & button ) {
2023-05-24 14:52:05 -04:00
clear_freqlist_for_ui_action ( ) ;
2023-05-18 16:16:05 -04:00
auto new_view = nav_ . push < FrequencyKeypadView > ( 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 ) {
2023-05-24 14:52:05 -04:00
clear_freqlist_for_ui_action ( ) ;
2023-05-18 16:16:05 -04:00
auto new_view = nav . push < FrequencyKeypadView > ( 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 ) ) ;
} ;
} ;
text_cycle . on_select = [ this , & nav ] ( ButtonWithEncoder & button ) {
if ( frequency_list . size ( ) > 0 ) {
auto new_view = nav_ . push < FrequencyKeypadView > ( current_index ) ;
new_view - > on_changed = [ this , & button ] ( rf : : Frequency f ) {
2023-07-17 14:43:37 -04:00
// NB: This is using the freq keypad to select an index.
2023-05-18 16:16:05 -04:00
f = f / OneMHz ;
if ( f > = 1 & & f < = frequency_list . size ( ) ) {
index_stepper = f - 1 - current_index ;
freq_lock = 0 ;
}
} ;
}
} ;
button_manual_start . on_change = [ this ] ( ) {
2023-07-17 14:43:37 -04:00
auto step_val = freqman_entry_get_step_value ( def_step ) ;
frequency_range . min = frequency_range . min + button_manual_start . get_encoder_delta ( ) * step_val ;
2023-05-18 16:16:05 -04:00
if ( frequency_range . min < 0 ) {
frequency_range . min = 0 ;
}
2023-07-17 14:43:37 -04:00
if ( frequency_range . min > ( MAX_UFREQ - step_val ) ) {
frequency_range . min = MAX_UFREQ - step_val ;
2023-05-18 16:16:05 -04:00
}
2023-07-17 14:43:37 -04:00
if ( frequency_range . min > ( frequency_range . max - step_val ) ) {
frequency_range . max = frequency_range . min + step_val ;
2023-05-18 16:16:05 -04:00
if ( frequency_range . max > MAX_UFREQ ) {
2023-07-17 14:43:37 -04:00
frequency_range . min = MAX_UFREQ - step_val ;
2023-05-18 16:16:05 -04:00
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 ] ( ) {
2023-07-17 14:43:37 -04:00
auto step_val = freqman_entry_get_step_value ( def_step ) ;
frequency_range . max = frequency_range . max + button_manual_end . get_encoder_delta ( ) * step_val ;
if ( frequency_range . max < ( step_val + 1 ) ) {
frequency_range . max = ( step_val + 1 ) ;
2023-05-18 16:16:05 -04:00
}
if ( frequency_range . max > MAX_UFREQ ) {
frequency_range . max = MAX_UFREQ ;
}
2023-07-17 14:43:37 -04:00
if ( frequency_range . max < ( frequency_range . min + step_val ) ) {
frequency_range . min = frequency_range . max - step_val ;
if ( frequency_range . max < ( step_val + 1 ) ) {
2023-05-18 16:16:05 -04:00
frequency_range . min = 1 ;
2023-07-17 14:43:37 -04:00
frequency_range . max = ( step_val + 1 ) ;
2023-05-18 16:16:05 -04:00
}
}
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 ) ;
} ;
text_cycle . on_change = [ this ] ( ) {
on_index_delta ( text_cycle . get_encoder_delta ( ) ) ;
text_cycle . set_encoder_delta ( 0 ) ;
} ;
button_pause . on_select = [ this ] ( ButtonWithEncoder & ) {
if ( frequency_list . size ( ) > 0 ) {
if ( freq_lock > 0 ) {
if ( fwd ) {
on_stepper_delta ( 1 ) ;
} else {
on_stepper_delta ( - 1 ) ;
}
button_pause . set_text ( " <PAUSE> " ) ; // Show button for non continuous stop
} else {
2023-05-22 04:49:07 -04:00
if ( ! recon ) {
2023-05-18 16:16:05 -04:00
recon_resume ( ) ;
2023-06-11 12:49:28 -04:00
user_pause = false ;
2023-05-18 16:16:05 -04:00
} else {
recon_pause ( ) ;
2023-06-11 12:49:28 -04:00
user_pause = true ;
2023-05-18 16:16:05 -04:00
}
}
}
} ;
button_pause . on_change = [ this ] ( ) {
on_stepper_delta ( button_pause . get_encoder_delta ( ) ) ;
button_pause . set_encoder_delta ( 0 ) ;
} ;
button_audio_app . on_select = [ this ] ( Button & ) {
2023-07-04 19:26:26 -04:00
auto settings = receiver_model . settings ( ) ;
settings . frequency_step = step_mode . selected_index_value ( ) ;
2023-07-07 16:21:48 -04:00
if ( field_mode . selected_index_value ( ) = = SPEC_MODULATION )
nav_ . replace < CaptureAppView > ( ) ;
else
nav_ . replace < AnalogAudioView > ( settings ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-06-16 01:50:04 -04:00
button_loop_config . on_select = [ this ] ( Button & ) {
set_loop_config ( ! continuous ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-06-16 01:50:04 -04:00
set_loop_config ( continuous ) ;
2023-05-18 16:16:05 -04:00
rssi . set_focusable ( true ) ;
rssi . set_peak ( true , 500 ) ;
rssi . on_select = [ this ] ( RSSI & ) {
2023-09-27 15:03:02 -04:00
nav_ . replace < LevelView > ( ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-06-11 14:47:13 -04:00
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
2023-05-18 16:16:05 -04:00
button_mic_app . on_select = [ this ] ( Button & ) {
2023-06-02 01:53:15 -04:00
if ( frequency_list . size ( ) > 0 & & current_index > = 0 & & ( unsigned ) current_index < frequency_list . size ( ) ) {
2023-07-08 16:04:12 -04:00
if ( current_entry ( ) . type = = freqman_type : : HamRadio ) {
// if it's a HamRadio entry, then frequency_a is the freq at which the repeater receives, so we have to set it in transmit in mic app
transmitter_model . set_target_frequency ( current_entry ( ) . frequency_a ) ;
// if it's a HamRadio entry, then frequency_b is the freq at which the repeater transmits, so we have to set it in receive in mic app
receiver_model . set_target_frequency ( current_entry ( ) . frequency_b ) ;
2023-06-02 01:53:15 -04:00
} else {
// it's single or range so we us actual tuned frequency
2023-06-11 14:47:13 -04:00
transmitter_model . set_target_frequency ( freq ) ;
receiver_model . set_target_frequency ( freq ) ;
2023-06-02 01:53:15 -04:00
}
}
2023-07-04 19:26:26 -04:00
// MicTX wants Modulation and Bandwidth overrides, but that's only stored on the RX model.
nav_ . replace < MicTXView > ( receiver_model . settings ( ) ) ;
2023-05-18 16:16:05 -04:00
} ;
button_remove . on_select = [ this ] ( ButtonWithEncoder & ) {
2023-07-17 14:43:37 -04:00
handle_remove_current_item ( ) ;
2023-05-18 16:16:05 -04:00
timer = 0 ;
freq_lock = 0 ;
2023-11-06 14:38:37 -05:00
recon_redraw ( ) ;
2023-05-18 16:16:05 -04:00
} ;
button_remove . on_change = [ this ] ( ) {
on_stepper_delta ( button_remove . get_encoder_delta ( ) ) ;
button_remove . set_encoder_delta ( 0 ) ;
} ;
button_manual_recon . on_select = [ this ] ( Button & ) {
2023-12-30 16:32:41 -05:00
button_remove . set_text ( " <DELETE> " ) ;
2023-10-09 14:01:40 -04:00
button_add . hidden ( false ) ;
2023-05-18 16:16:05 -04:00
scanner_mode = false ;
manual_mode = true ;
recon_pause ( ) ;
if ( ! frequency_range . min | | ! frequency_range . max ) {
nav_ . display_modal ( " Error " , " Both START and END freqs \n need a value " ) ;
} else {
2023-08-15 12:29:16 -04:00
if ( frequency_range . min > frequency_range . max ) {
std : : swap ( frequency_range . min , frequency_range . max ) ;
button_manual_start . set_text ( to_string_short_freq ( frequency_range . min ) ) ;
button_manual_end . set_text ( to_string_short_freq ( frequency_range . max ) ) ;
}
// no need to stop audio in SPEC
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-05-18 16:16:05 -04:00
2023-07-08 16:04:12 -04:00
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
frequency_list = freqman_db { } ;
current_index = 0 ;
frequency_list . push_back ( std : : make_unique < freqman_entry > ( ) ) ;
def_step = step_mode . selected_index ( ) ;
current_entry ( ) . type = freqman_type : : Range ;
current_entry ( ) . description =
to_string_short_freq ( frequency_range . min ) . erase ( 0 , 1 ) + " > " + // euquiq: lame kludge to reduce spacing in step freq
to_string_short_freq ( frequency_range . max ) . erase ( 0 , 1 ) + " S: " +
freqman_entry_get_step_string_short ( def_step ) ;
current_entry ( ) . frequency_a = frequency_range . min ;
current_entry ( ) . frequency_b = frequency_range . max ;
current_entry ( ) . modulation = freqman_invalid_index ;
current_entry ( ) . bandwidth = freqman_invalid_index ;
current_entry ( ) . step = def_step ;
2023-05-18 16:16:05 -04:00
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : white ) ; // Back to white color
2023-05-18 16:16:05 -04:00
2023-06-07 11:33:32 -04:00
freq_stats . set_style ( & Styles : : white ) ;
2023-05-18 16:16:05 -04:00
freq_stats . set ( " 0/0/0 " ) ;
text_cycle . set_text ( " 1 " ) ;
text_max . set ( " /1 " ) ;
2023-06-07 11:33:32 -04:00
button_scanner_mode . set_style ( & Styles : : white ) ;
2023-07-05 05:08:29 -04:00
button_scanner_mode . set_text ( " MANUAL " ) ;
2023-06-07 11:33:32 -04:00
file_name . set_style ( & Styles : : white ) ;
2023-10-08 16:21:00 -04:00
file_name . set ( " MANUAL => " + output_file ) ;
2023-06-07 11:33:32 -04:00
desc_cycle . set_style ( & Styles : : white ) ;
2023-05-22 04:49:07 -04:00
2023-07-08 16:04:12 -04:00
last_entry . modulation = freqman_invalid_index ;
last_entry . bandwidth = freqman_invalid_index ;
last_entry . step = freqman_invalid_index ;
2023-05-22 04:49:07 -04:00
last_index = - 1 ;
2023-05-18 16:16:05 -04:00
2023-07-08 16:04:12 -04:00
freq = current_entry ( ) . frequency_a ;
2023-05-18 16:16:05 -04:00
handle_retune ( ) ;
recon_redraw ( ) ;
recon_resume ( ) ;
}
} ;
button_dir . on_select = [ this ] ( Button & ) {
if ( fwd ) {
fwd = false ;
button_dir . set_text ( " <RW " ) ;
} else {
fwd = true ;
button_dir . set_text ( " FW> " ) ;
}
timer = 0 ;
2023-05-22 04:49:07 -04:00
if ( ! recon )
2023-05-18 16:16:05 -04:00
recon_resume ( ) ;
} ;
button_restart . on_select = [ this ] ( Button & ) {
2023-12-28 05:25:53 -05:00
reload_restart_recon ( ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-07-08 16:04:12 -04:00
button_add . on_select = [ this ] ( ButtonWithEncoder & ) {
2023-05-18 16:16:05 -04:00
if ( ! scanner_mode ) {
2023-05-21 16:31:03 -04:00
recon_save_freq ( freq_file_path , current_index , true ) ;
2023-05-18 16:16:05 -04:00
}
} ;
button_add . on_change = [ this ] ( ) {
on_stepper_delta ( button_add . get_encoder_delta ( ) ) ;
button_add . set_encoder_delta ( 0 ) ;
} ;
button_scanner_mode . on_select = [ this , & nav ] ( Button & ) {
manual_mode = false ;
if ( scanner_mode ) {
scanner_mode = false ;
2023-06-07 11:33:32 -04:00
button_scanner_mode . set_style ( & Styles : : blue ) ;
2023-05-18 16:16:05 -04:00
button_scanner_mode . set_text ( " RECON " ) ;
2023-12-30 16:32:41 -05:00
button_remove . set_text ( " <REMOVE> " ) ;
2023-05-18 16:16:05 -04:00
} else {
scanner_mode = true ;
2023-06-07 11:33:32 -04:00
button_scanner_mode . set_style ( & Styles : : red ) ;
2023-07-05 05:08:29 -04:00
button_scanner_mode . set_text ( " SCAN " ) ;
2023-12-30 16:32:41 -05:00
button_remove . set_text ( " <DELETE> " ) ;
2023-05-18 16:16:05 -04:00
}
2023-11-06 14:38:37 -05:00
frequency_file_load ( ) ;
2023-05-18 16:16:05 -04:00
if ( autostart ) {
recon_resume ( ) ;
} else {
recon_pause ( ) ;
}
2023-10-08 16:21:00 -04:00
button_add . hidden ( scanner_mode ) ;
2023-10-09 14:01:40 -04:00
if ( scanner_mode ) // only needed when hiding, UI mayhem
set_dirty ( ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-05-24 14:52:05 -04:00
button_config . on_select = [ this , & nav ] ( Button & ) {
2023-06-25 05:33:44 -04:00
if ( is_recording ) // disabling config while recording
return ;
2023-10-08 16:21:00 -04:00
2023-05-24 14:52:05 -04:00
clear_freqlist_for_ui_action ( ) ;
2023-10-08 16:21:00 -04:00
2023-06-25 02:16:49 -04:00
freq_lock = 0 ;
timer = 0 ;
2023-06-11 12:49:28 -04:00
auto open_view = nav . push < ReconSetupView > ( input_file , output_file ) ;
2023-05-18 16:16:05 -04:00
open_view - > on_changed = [ this ] ( std : : vector < std : : string > result ) {
input_file = result [ 0 ] ;
output_file = result [ 1 ] ;
2023-07-11 16:48:36 -04:00
freq_file_path = get_freqman_path ( output_file ) . string ( ) ;
2023-05-18 16:16:05 -04:00
2023-08-20 16:28:02 -04:00
load_persisted_settings ( ) ;
ui_settings . save ( ) ;
2023-05-18 16:16:05 -04:00
2023-11-06 14:38:37 -05:00
frequency_file_load ( ) ;
2023-06-22 11:34:20 -04:00
freqlist_cleared_for_ui_action = false ;
2023-05-22 04:49:07 -04:00
2023-05-18 16:16:05 -04:00
if ( autostart ) {
recon_resume ( ) ;
} else {
recon_pause ( ) ;
}
} ;
} ;
2023-06-11 12:49:28 -04:00
field_recon_match_mode . on_change = [ this ] ( size_t , OptionsField : : value_t v ) {
recon_match_mode = v ;
colorize_waits ( ) ;
} ;
2023-05-18 16:16:05 -04:00
field_wait . on_change = [ this ] ( int32_t v ) {
wait = v ;
2023-06-25 02:16:49 -04:00
// replacing -100 by 200 else it's freezing the device
if ( wait = = - 100 )
wait = - 200 ;
2023-05-21 14:42:54 -04:00
colorize_waits ( ) ;
2023-05-18 16:16:05 -04:00
} ;
2023-06-11 12:49:28 -04:00
field_nblocks . on_change = [ this ] ( int32_t v ) {
recon_lock_nb_match = v ;
if ( ( unsigned ) v < freq_lock )
freq_lock = v ;
colorize_waits ( ) ;
} ;
2023-05-18 16:16:05 -04:00
field_lock_wait . on_change = [ this ] ( uint32_t v ) {
recon_lock_duration = v ;
2023-05-21 14:42:54 -04:00
colorize_waits ( ) ;
2023-05-18 16:16:05 -04:00
} ;
field_squelch . on_change = [ this ] ( int32_t v ) {
squelch = v ;
} ;
// PRE-CONFIGURATION:
2023-06-07 11:33:32 -04:00
button_scanner_mode . set_style ( & Styles : : blue ) ;
2023-05-18 16:16:05 -04:00
button_scanner_mode . set_text ( " RECON " ) ;
file_name . set ( " => " ) ;
// Loading input and output file from settings
2023-07-11 16:48:36 -04:00
freq_file_path = get_freqman_path ( output_file ) . string ( ) ;
2023-05-18 16:16:05 -04:00
2023-06-11 12:49:28 -04:00
field_recon_match_mode . set_selected_index ( recon_match_mode ) ;
2023-05-18 16:16:05 -04:00
field_squelch . set_value ( squelch ) ;
field_wait . set_value ( wait ) ;
field_lock_wait . set_value ( recon_lock_duration ) ;
2023-06-11 12:49:28 -04:00
field_nblocks . set_value ( recon_lock_nb_match ) ;
2023-05-21 14:42:54 -04:00
colorize_waits ( ) ;
2023-05-18 16:16:05 -04:00
// fill modulation and step options
freqman_set_modulation_option ( field_mode ) ;
freqman_set_step_option ( step_mode ) ;
// set radio
2023-06-11 14:47:13 -04:00
change_mode ( AM_MODULATION ) ; // start on AM.
field_mode . set_by_value ( AM_MODULATION ) ; // reflect the mode into the manual selector
2023-05-18 16:16:05 -04:00
2023-12-28 05:25:53 -05:00
// tx progress bar
progressbar . hidden ( true ) ;
2023-05-18 16:16:05 -04:00
if ( filedelete ) {
delete_file ( freq_file_path ) ;
}
2023-11-06 14:38:37 -05:00
frequency_file_load ( ) ; /* do not stop all at start */
2023-05-18 16:16:05 -04:00
if ( autostart ) {
recon_resume ( ) ;
} else {
recon_pause ( ) ;
}
recon_redraw ( ) ;
}
2023-11-06 14:38:37 -05:00
void ReconView : : frequency_file_load ( ) {
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-05-18 16:16:05 -04:00
def_step = step_mode . selected_index ( ) ; // use def_step from manual selector
2023-06-22 11:34:20 -04:00
std : : string file_input = input_file ; // default recon mode
2023-05-18 16:16:05 -04:00
if ( scanner_mode ) {
file_input = output_file ;
2023-06-07 11:33:32 -04:00
file_name . set_style ( & Styles : : red ) ;
button_scanner_mode . set_style ( & Styles : : red ) ;
2023-06-25 02:16:49 -04:00
desc_cycle . set_style ( & Styles : : red ) ;
2023-07-05 05:08:29 -04:00
button_scanner_mode . set_text ( " SCAN " ) ;
2023-05-18 16:16:05 -04:00
} else {
2023-06-07 11:33:32 -04:00
file_name . set_style ( & Styles : : blue ) ;
button_scanner_mode . set_style ( & Styles : : blue ) ;
2023-06-25 02:16:49 -04:00
desc_cycle . set_style ( & Styles : : blue ) ;
2023-05-18 16:16:05 -04:00
button_scanner_mode . set_text ( " RECON " ) ;
}
2023-07-17 14:43:37 -04:00
2023-10-08 16:21:00 -04:00
file_name . set ( file_input + " => " + output_file ) ;
2023-07-08 16:04:12 -04:00
freqman_load_options options {
. load_freqs = load_freqs ,
. load_ranges = load_ranges ,
2023-12-28 05:25:53 -05:00
. load_hamradios = load_hamradios ,
. load_repeaters = load_repeaters } ;
2023-10-08 16:21:00 -04:00
if ( ! load_freqman_file ( file_input , frequency_list , options ) | | frequency_list . empty ( ) ) {
2023-06-07 11:33:32 -04:00
file_name . set_style ( & Styles : : red ) ;
2023-10-08 16:21:00 -04:00
desc_cycle . set ( " ...empty file... " ) ;
frequency_list . clear ( ) ;
text_cycle . set_text ( " " ) ;
return ;
2023-05-18 16:16:05 -04:00
}
2023-10-08 16:21:00 -04:00
if ( frequency_list . size ( ) > FREQMAN_MAX_PER_FILE ) {
file_name . set_style ( & Styles : : yellow ) ;
2023-05-18 16:16:05 -04:00
}
2023-07-08 16:04:12 -04:00
2023-05-24 14:52:05 -04:00
reset_indexes ( ) ;
2023-07-08 16:04:12 -04:00
step = freqman_entry_get_step_value (
is_valid ( current_entry ( ) . step ) ? current_entry ( ) . step : def_step ) ;
if ( current_entry ( ) . type = = freqman_type : : Single ) {
freq = current_entry ( ) . frequency_a ;
} else if ( current_entry ( ) . type ! = freqman_type : : Unknown ) {
minfreq = current_entry ( ) . frequency_a ;
maxfreq = current_entry ( ) . frequency_b ;
freq = fwd ? minfreq : maxfreq ;
}
2023-05-18 16:16:05 -04:00
step_mode . set_selected_index ( def_step ) ; // Impose the default step into the manual step selector
receiver_model . enable ( ) ;
receiver_model . set_squelch_level ( 0 ) ;
2023-07-08 16:04:12 -04:00
text_cycle . set_text ( to_string_dec_uint ( current_index + 1 , 3 ) ) ;
2023-08-15 12:29:16 -04:00
check_update_ranges_from_current ( ) ;
2023-07-08 16:04:12 -04:00
update_description ( ) ;
2023-05-18 16:16:05 -04:00
handle_retune ( ) ;
}
void ReconView : : on_statistics_update ( const ChannelStatistics & statistics ) {
2023-12-28 05:25:53 -05:00
if ( recon_tx )
return ;
if ( is_repeat_active ( ) )
return ;
2023-07-05 15:54:28 -04:00
chrono_end = chTimeNow ( ) ;
2023-10-09 14:01:40 -04:00
systime_t time_interval = chrono_end - chrono_start ;
2023-07-05 15:54:28 -04:00
chrono_start = chrono_end ;
2023-06-25 02:16:49 -04:00
2023-05-24 14:52:05 -04:00
// hack to reload the list if it was cleared by going into CONFIG
if ( freqlist_cleared_for_ui_action ) {
if ( ! manual_mode ) {
2023-11-06 14:38:37 -05:00
frequency_file_load ( ) ;
2023-05-24 14:52:05 -04:00
}
2023-06-11 12:49:28 -04:00
if ( autostart & & ! user_pause ) {
2023-05-24 14:52:05 -04:00
recon_resume ( ) ;
} else {
recon_pause ( ) ;
}
freqlist_cleared_for_ui_action = false ;
}
2023-05-18 16:16:05 -04:00
db = statistics . max_db ;
2023-05-22 04:49:07 -04:00
if ( recon ) {
2023-05-18 16:16:05 -04:00
if ( ! timer ) {
status = 0 ;
freq_lock = 0 ;
2023-10-09 14:01:40 -04:00
timer = recon_lock_duration ;
2023-05-18 16:16:05 -04:00
}
if ( freq_lock < recon_lock_nb_match ) // LOCKING
{
if ( status ! = 1 ) {
status = 1 ;
if ( wait ! = 0 ) {
2023-12-30 16:32:41 -05:00
recon_stop_recording ( false ) ;
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-05-18 16:16:05 -04:00
}
}
if ( db > squelch ) // MATCHING LEVEL
{
freq_lock + + ;
2023-08-17 11:28:12 -04:00
timer + = time_interval ; // give some more time for next lock
2023-05-18 16:16:05 -04:00
} else {
// continuous, direct cut it if not consecutive match after 1 first match
if ( recon_match_mode = = RECON_MATCH_CONTINUOUS ) {
2023-07-22 10:30:02 -04:00
freq_lock = 0 ;
timer = 0 ;
2023-05-18 16:16:05 -04:00
}
}
}
if ( freq_lock > = recon_lock_nb_match ) // LOCKED
{
if ( status ! = 2 ) {
status = 2 ;
2023-06-25 02:16:49 -04:00
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ( ( ! scanner_mode ) & & autosave & & frequency_list . size ( ) > 0 ) {
recon_save_freq ( freq_file_path , current_index , false ) ;
}
2023-05-18 16:16:05 -04:00
if ( wait ! = 0 ) {
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio_output_start ( ) ;
// contents of a possible recon_start_recording(), but not yet since it's only called once
if ( auto_record_locked & & ! is_recording ) {
button_audio_app . set_style ( & Styles : : red ) ;
2023-12-28 05:25:53 -05:00
if ( field_mode . selected_index_value ( ) = = SPEC_MODULATION ) {
2023-07-07 16:21:48 -04:00
button_audio_app . set_text ( " RAW REC " ) ;
2023-12-28 05:25:53 -05:00
} else
2023-07-07 16:21:48 -04:00
button_audio_app . set_text ( " WAV REC " ) ;
2023-06-25 02:16:49 -04:00
record_view - > start ( ) ;
2023-06-25 05:33:44 -04:00
button_config . set_style ( & Styles : : light_grey ) ; // disable config while recording as it's causing an IO error pop up at exit
2023-06-25 02:16:49 -04:00
is_recording = true ;
}
// FREQ IS STRONG: GREEN and recon will pause when on_statistics_update()
if ( ( ! scanner_mode ) & & autosave & & frequency_list . size ( ) > 0 ) {
recon_save_freq ( freq_file_path , current_index , false ) ;
}
2023-05-18 16:16:05 -04:00
}
if ( wait > = 0 ) {
timer = wait ;
}
}
if ( wait < 0 ) {
if ( db > squelch ) // MATCHING LEVEL IN STAY X AFTER LAST ACTIVITY
{
timer = abs ( wait ) ;
}
}
}
}
if ( last_timer ! = timer ) {
last_timer = timer ;
text_timer . set ( " TIMER: " + to_string_dec_int ( timer ) ) ;
}
2023-07-22 10:30:02 -04:00
if ( timer ! = 0 ) {
2023-08-17 11:28:12 -04:00
timer - = time_interval ;
2023-05-18 16:16:05 -04:00
if ( timer < 0 ) {
timer = 0 ;
}
}
if ( recon | | stepper ! = 0 | | index_stepper ! = 0 ) {
if ( ! timer | | stepper ! = 0 | | index_stepper ! = 0 ) {
// IF THERE IS A FREQUENCY LIST ...
if ( frequency_list . size ( ) > 0 ) {
has_looped = false ;
entry_has_changed = false ;
if ( recon | | stepper ! = 0 | | index_stepper ! = 0 ) {
if ( index_stepper = = 0 ) {
/* we are doing a range */
2023-07-08 16:04:12 -04:00
if ( current_entry ( ) . type = = freqman_type : : Range ) {
2023-05-18 16:16:05 -04:00
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
current_index + + ;
entry_has_changed = true ;
// looping
if ( ( uint32_t ) current_index > = frequency_list . size ( ) ) {
has_looped = true ;
current_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
current_index - - ;
entry_has_changed = true ;
// looping
if ( current_index < 0 ) {
has_looped = true ;
current_index = frequency_list . size ( ) - 1 ;
}
}
}
2023-07-08 16:04:12 -04:00
} else if ( current_entry ( ) . type = = freqman_type : : Single ) {
2023-05-18 16:16:05 -04:00
if ( ( fwd & & stepper = = 0 ) | | stepper > 0 ) { // forward
current_index + + ;
entry_has_changed = true ;
// looping
if ( ( uint32_t ) current_index > = frequency_list . size ( ) ) {
has_looped = true ;
current_index = 0 ;
}
} else if ( ( ! fwd & & stepper = = 0 ) | | stepper < 0 ) {
// reverse
current_index - - ;
entry_has_changed = true ;
// if previous if under the list => go back from end
if ( current_index < 0 ) {
has_looped = true ;
current_index = frequency_list . size ( ) - 1 ;
}
}
2023-07-08 16:04:12 -04:00
} else if ( current_entry ( ) . type = = freqman_type : : HamRadio ) {
2023-05-18 16:16:05 -04:00
if ( ( fwd & & stepper = = 0 ) | | stepper > 0 ) { // forward
if ( ( minfreq ! = maxfreq ) & & freq = = minfreq ) {
freq = maxfreq ;
} else {
current_index + + ;
entry_has_changed = true ;
// looping
if ( ( uint32_t ) current_index > = frequency_list . size ( ) ) {
has_looped = true ;
current_index = 0 ;
}
}
} else if ( ( ! fwd & & stepper = = 0 ) | | stepper < 0 ) {
// reverse
if ( ( minfreq ! = maxfreq ) & & freq = = maxfreq ) {
freq = minfreq ;
} else {
current_index - - ;
entry_has_changed = true ;
// if previous if under the list => go back from end
if ( current_index < 0 ) {
has_looped = true ;
current_index = frequency_list . size ( ) - 1 ;
}
}
}
2023-12-28 05:25:53 -05:00
} else if ( current_entry ( ) . type = = freqman_type : : Repeater ) {
// repeater is like single, we only listen on frequency_a and then jump to next entry
if ( ( fwd & & stepper = = 0 ) | | stepper > 0 ) { // forward
current_index + + ;
entry_has_changed = true ;
// looping
if ( ( uint32_t ) current_index > = frequency_list . size ( ) ) {
has_looped = true ;
current_index = 0 ;
}
} else if ( ( ! fwd & & stepper = = 0 ) | | stepper < 0 ) {
// reverse
current_index - - ;
entry_has_changed = true ;
// if previous if under the list => go back from end
if ( current_index < 0 ) {
has_looped = true ;
current_index = frequency_list . size ( ) - 1 ;
}
}
2023-05-18 16:16:05 -04:00
}
// 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 ) {
current_index = 0 ;
} else if ( ( ! fwd & & stepper = = 0 ) | | stepper < 0 ) {
current_index = frequency_list . size ( ) - 1 ;
}
}
} else {
current_index + = index_stepper ;
if ( current_index < 0 )
current_index + = frequency_list . size ( ) ;
if ( ( unsigned ) current_index > = frequency_list . size ( ) )
current_index - = frequency_list . size ( ) ;
entry_has_changed = true ;
2023-06-25 02:16:49 -04:00
// for some motive, audio output gets stopped.
if ( ! recon & & field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
2023-05-18 16:16:05 -04:00
audio_output_start ( ) ;
}
// reload entry if changed
if ( entry_has_changed ) {
timer = 0 ;
2023-07-08 16:04:12 -04:00
switch ( current_entry ( ) . type ) {
2023-12-28 05:25:53 -05:00
case freqman_type : : Repeater :
2023-07-08 16:04:12 -04:00
case freqman_type : : Single :
freq = current_entry ( ) . frequency_a ;
2023-05-18 16:16:05 -04:00
break ;
2023-07-08 16:04:12 -04:00
case freqman_type : : Range :
minfreq = current_entry ( ) . frequency_a ;
maxfreq = current_entry ( ) . frequency_b ;
2023-05-18 16:16:05 -04:00
if ( ( fwd & & ! stepper & & ! index_stepper ) | | stepper > 0 | | index_stepper > 0 ) {
freq = minfreq ;
} else if ( ( ! fwd & & ! stepper & & ! index_stepper ) | | stepper < 0 | | index_stepper < 0 ) {
freq = maxfreq ;
}
break ;
2023-07-08 16:04:12 -04:00
case freqman_type : : HamRadio :
minfreq = current_entry ( ) . frequency_a ;
maxfreq = current_entry ( ) . frequency_b ;
2023-05-18 16:16:05 -04:00
if ( ( fwd & & ! stepper & & ! index_stepper ) | | stepper > 0 | | index_stepper > 0 ) {
freq = minfreq ;
} else if ( ( ! fwd & & ! stepper & & ! index_stepper ) | | stepper < 0 | | index_stepper < 0 ) {
freq = maxfreq ;
}
break ;
default :
break ;
}
}
if ( has_looped & & ! continuous ) {
recon_pause ( ) ;
}
index_stepper = 0 ;
if ( stepper < 0 ) stepper + + ;
if ( stepper > 0 ) stepper - - ;
} // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 )
2023-12-28 05:25:53 -05:00
} /* on_statistics_updates */
2023-05-18 16:16:05 -04:00
}
handle_retune ( ) ;
recon_redraw ( ) ;
}
void ReconView : : recon_pause ( ) {
timer = 0 ;
freq_lock = 0 ;
recon = false ;
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio_output_start ( ) ;
2023-05-18 16:16:05 -04:00
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : white ) ;
2023-05-18 16:16:05 -04:00
button_pause . set_text ( " <RESUME> " ) ; // PAUSED, show resume
}
void ReconView : : recon_resume ( ) {
timer = 0 ;
freq_lock = 0 ;
recon = true ;
2023-06-25 02:16:49 -04:00
if ( field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
audio : : output : : stop ( ) ;
2023-05-18 16:16:05 -04:00
2023-06-07 11:33:32 -04:00
big_display . set_style ( & Styles : : white ) ;
2023-05-18 16:16:05 -04:00
button_pause . set_text ( " <PAUSE> " ) ;
}
void ReconView : : on_index_delta ( int32_t v ) {
if ( v > 0 ) {
fwd = true ;
button_dir . set_text ( " FW> " ) ;
2023-05-22 04:49:07 -04:00
}
if ( v < 0 ) {
2023-05-18 16:16:05 -04:00
fwd = false ;
button_dir . set_text ( " <RW " ) ;
}
if ( frequency_list . size ( ) > 0 )
index_stepper = v ;
freq_lock = 0 ;
timer = 0 ;
}
void ReconView : : on_stepper_delta ( int32_t v ) {
if ( v > 0 ) {
fwd = true ;
button_dir . set_text ( " FW> " ) ;
2023-05-22 04:49:07 -04:00
}
if ( v < 0 ) {
2023-05-18 16:16:05 -04:00
fwd = false ;
button_dir . set_text ( " <RW " ) ;
}
if ( frequency_list . size ( ) > 0 )
stepper = v ;
freq_lock = 0 ;
timer = 0 ;
}
size_t ReconView : : change_mode ( freqman_index_t new_mod ) {
2023-12-28 05:25:53 -05:00
if ( recon_tx | | is_repeat_active ( ) )
return 0 ;
2023-05-18 16:16:05 -04:00
field_mode . on_change = [ this ] ( size_t , OptionsField : : value_t ) { } ;
field_bw . on_change = [ this ] ( size_t , OptionsField : : value_t ) { } ;
2023-12-30 16:32:41 -05:00
recon_stop_recording ( false ) ;
2023-12-28 05:25:53 -05:00
if ( record_view ! = nullptr ) {
remove_child ( record_view . get ( ) ) ;
record_view . reset ( ) ;
}
if ( persistent_memory : : recon_repeat_recorded ( ) ) {
record_view = std : : make_unique < RecordView > ( Rect { 0 , 0 , 30 * 8 , 1 * 16 } ,
u " RECON_REPEAT.C16 " , u " CAPTURES " ,
RecordView : : FileType : : RawS16 , 16384 , 3 ) ;
record_view - > set_filename_as_is ( true ) ;
} else if ( new_mod = = SPEC_MODULATION ) {
2023-06-25 02:16:49 -04:00
audio : : output : : stop ( ) ;
2023-06-28 13:14:30 -04:00
record_view = std : : make_unique < RecordView > ( Rect { 0 , 0 , 30 * 8 , 1 * 16 } ,
2023-12-28 05:25:53 -05:00
u " AUTO_RAW " , u " CAPTURES " ,
2023-06-28 13:14:30 -04:00
RecordView : : FileType : : RawS16 , 16384 , 3 ) ;
} else {
record_view = std : : make_unique < RecordView > ( Rect { 0 , 0 , 30 * 8 , 1 * 16 } ,
2023-12-28 05:25:53 -05:00
u " AUTO_AUDIO " , u " AUDIO " ,
2023-06-28 13:14:30 -04:00
RecordView : : FileType : : WAV , 4096 , 4 ) ;
2023-12-28 05:25:53 -05:00
record_view - > set_filename_date_frequency ( true ) ;
2023-06-25 02:16:49 -04:00
}
2023-12-28 05:25:53 -05:00
record_view - > set_auto_trim ( false ) ;
2023-06-28 16:21:13 -04:00
add_child ( record_view . get ( ) ) ;
2023-06-25 02:16:49 -04:00
record_view - > hidden ( true ) ;
record_view - > on_error = [ this ] ( std : : string message ) {
nav_ . display_modal ( " Error " , message ) ;
} ;
2023-05-18 16:16:05 -04:00
receiver_model . disable ( ) ;
2023-12-28 05:25:53 -05:00
transmitter_model . disable ( ) ;
2023-05-18 16:16:05 -04:00
baseband : : shutdown ( ) ;
2023-06-25 02:16:49 -04:00
size_t recording_sampling_rate = 0 ;
2023-05-18 16:16:05 -04:00
switch ( new_mod ) {
case AM_MODULATION :
freqman_set_bandwidth_option ( new_mod , field_bw ) ;
baseband : : run_image ( portapack : : spi_flash : : image_tag_am_audio ) ;
receiver_model . set_modulation ( ReceiverModel : : Mode : : AMAudio ) ;
2023-06-06 12:46:08 -04:00
receiver_model . set_am_configuration ( field_bw . selected_index_value ( ) ) ;
field_bw . on_change = [ this ] ( size_t , OptionsField : : value_t n ) { receiver_model . set_am_configuration ( n ) ; } ;
2023-08-30 11:13:14 -04:00
// bw DSB (0) default
field_bw . set_by_value ( 0 ) ;
2023-06-11 12:49:28 -04:00
text_ctcss . set ( " " ) ;
2023-06-25 02:16:49 -04:00
recording_sampling_rate = 12000 ;
2023-05-18 16:16:05 -04:00
break ;
case NFM_MODULATION :
freqman_set_bandwidth_option ( new_mod , field_bw ) ;
baseband : : run_image ( portapack : : spi_flash : : image_tag_nfm_audio ) ;
receiver_model . set_modulation ( ReceiverModel : : Mode : : NarrowbandFMAudio ) ;
2023-06-06 12:46:08 -04:00
receiver_model . set_nbfm_configuration ( field_bw . selected_index_value ( ) ) ;
field_bw . on_change = [ this ] ( size_t , OptionsField : : value_t n ) { receiver_model . set_nbfm_configuration ( n ) ; } ;
2023-08-30 11:13:14 -04:00
// bw 16k (2) default
field_bw . set_by_value ( 2 ) ;
2023-06-25 02:16:49 -04:00
recording_sampling_rate = 24000 ;
2023-05-18 16:16:05 -04:00
break ;
case WFM_MODULATION :
freqman_set_bandwidth_option ( new_mod , field_bw ) ;
baseband : : run_image ( portapack : : spi_flash : : image_tag_wfm_audio ) ;
receiver_model . set_modulation ( ReceiverModel : : Mode : : WidebandFMAudio ) ;
2023-06-06 12:46:08 -04:00
receiver_model . set_wfm_configuration ( field_bw . selected_index_value ( ) ) ;
field_bw . on_change = [ this ] ( size_t , OptionsField : : value_t n ) { receiver_model . set_wfm_configuration ( n ) ; } ;
2023-08-30 11:13:14 -04:00
// bw 200k (0) default
field_bw . set_by_value ( 0 ) ;
2023-06-11 12:49:28 -04:00
text_ctcss . set ( " " ) ;
2023-06-25 02:16:49 -04:00
recording_sampling_rate = 48000 ;
break ;
case SPEC_MODULATION :
freqman_set_bandwidth_option ( new_mod , field_bw ) ;
baseband : : run_image ( portapack : : spi_flash : : image_tag_capture ) ;
receiver_model . set_modulation ( ReceiverModel : : Mode : : Capture ) ;
field_bw . on_change = [ this ] ( size_t , OptionsField : : value_t sampling_rate ) {
2023-08-30 11:13:14 -04:00
// record_view determines the correct oversampling to apply and returns the actual sample rate.
auto actual_sampling_rate = record_view - > set_sampling_rate ( sampling_rate ) ;
// The radio needs to know the effective sampling rate.
receiver_model . set_sampling_rate ( actual_sampling_rate ) ;
receiver_model . set_baseband_bandwidth ( filter_bandwidth_for_sampling_rate ( actual_sampling_rate ) ) ;
2023-06-25 02:16:49 -04:00
} ;
2023-08-30 11:13:14 -04:00
// bw 12k5 (0) default
field_bw . set_by_value ( 0 ) ;
2023-06-25 02:16:49 -04:00
text_ctcss . set ( " " ) ;
2023-05-18 16:16:05 -04:00
break ;
default :
break ;
}
2023-07-05 15:54:28 -04:00
if ( new_mod ! = SPEC_MODULATION ) {
2023-07-07 16:21:48 -04:00
button_audio_app . set_text ( " AUDIO " ) ;
2023-06-25 02:16:49 -04:00
record_view - > set_sampling_rate ( recording_sampling_rate ) ;
2023-07-31 11:46:07 -04:00
// reset receiver model to fix bug when going from SPEC to audio, the sound is distorted
2023-07-05 15:54:28 -04:00
receiver_model . set_sampling_rate ( 3072000 ) ;
receiver_model . set_baseband_bandwidth ( 1750000 ) ;
2023-07-07 16:21:48 -04:00
} else {
button_audio_app . set_text ( " RAW " ) ;
2023-07-05 15:54:28 -04:00
}
2023-05-18 16:16:05 -04:00
field_mode . set_selected_index ( new_mod ) ;
field_mode . on_change = [ this ] ( size_t , OptionsField : : value_t v ) {
if ( v ! = - 1 ) {
change_mode ( v ) ;
}
} ;
2023-06-25 02:16:49 -04:00
// for some motive, audio output gets stopped.
if ( ! recon & & field_mode . selected_index_value ( ) ! = SPEC_MODULATION )
2023-05-18 16:16:05 -04:00
audio : : output : : start ( ) ; // so if recon was stopped we resume audio
receiver_model . enable ( ) ;
return freqman_entry_get_step_value ( def_step ) ;
}
void ReconView : : handle_coded_squelch ( const uint32_t value ) {
2023-07-02 19:53:51 -04:00
if ( field_mode . selected_index ( ) = = NFM_MODULATION )
text_ctcss . set ( tone_key_string_by_value ( value , text_ctcss . parent_rect ( ) . width ( ) / 8 ) ) ;
else
text_ctcss . set ( " " ) ;
2023-05-18 16:16:05 -04:00
}
2023-06-30 01:37:43 -04:00
2023-07-17 14:43:37 -04:00
void ReconView : : handle_remove_current_item ( ) {
if ( frequency_list . empty ( ) | | ! current_is_valid ( ) )
return ;
auto entry = current_entry ( ) ; // Copy the current entry.
// In Scanner or Recon modes, remove from the in-memory list.
if ( mode ( ) ! = recon_mode : : Manual ) {
if ( current_is_valid ( ) ) {
frequency_list . erase ( frequency_list . begin ( ) + current_index ) ;
}
}
// In Scanner or Manual mode, remove the entry from the output file.
if ( mode ( ) ! = recon_mode : : Recon ) {
FreqmanDB freq_db ;
if ( ! freq_db . open ( freq_file_path ) )
return ;
freq_db . delete_entry ( entry ) ;
}
2023-10-08 16:21:00 -04:00
// Clip
if ( frequency_list . size ( ) > 0 ) {
current_index = clip < int32_t > ( current_index , 0u , frequency_list . size ( ) - 1 ) ;
text_cycle . set_text ( to_string_dec_uint ( current_index + 1 , 3 ) ) ;
2023-11-06 14:38:37 -05:00
entry = current_entry ( ) ;
freq = entry . frequency_a ;
2023-10-08 16:21:00 -04:00
} else {
current_index = 0 ;
text_cycle . set_text ( " " ) ;
}
update_description ( ) ;
2023-07-17 14:43:37 -04:00
}
2023-12-28 05:25:53 -05:00
void ReconView : : on_repeat_tx_progress ( const uint32_t progress ) {
progressbar . set_value ( progress ) ;
}
void ReconView : : repeat_file_error ( const std : : filesystem : : path & path , const std : : string & message ) {
nav_ . display_modal ( " Error " , " Error opening file \n " + path . string ( ) + " \n " + message ) ;
}
bool ReconView : : is_repeat_active ( ) const {
return replay_thread ! = nullptr ;
}
void ReconView : : start_repeat ( ) {
// Prepare to send a file.
std : : filesystem : : path rawfile = u " / " + repeat_rec_path + u " / " + repeat_rec_file ;
std : : filesystem : : path rawmeta = u " / " + repeat_rec_path + u " / " + repeat_rec_meta ;
if ( recon_tx = = false ) {
recon_tx = true ;
if ( record_view ! = nullptr ) {
record_view - > stop ( ) ;
remove_child ( record_view . get ( ) ) ;
record_view . reset ( ) ;
}
receiver_model . disable ( ) ;
transmitter_model . disable ( ) ;
baseband : : shutdown ( ) ;
baseband : : run_image ( portapack : : spi_flash : : image_tag_replay ) ;
size_t rawsize = 0 ;
{
File capture_file ;
auto error = capture_file . open ( rawfile ) ;
if ( error ) {
repeat_file_error ( rawfile , " Can't open file to send for size " ) ;
return ;
}
rawsize = capture_file . size ( ) ;
}
// Reset the transmit progress bar.
uint8_t sample_size = std : : filesystem : : capture_file_sample_size ( rawfile ) ;
progressbar . set_value ( 0 ) ;
progressbar . set_max ( rawsize * sizeof ( complex16_t ) / sample_size ) ;
progressbar . hidden ( false ) ;
auto metadata = read_metadata_file ( rawmeta ) ;
// If no metadata found, fallback to the TX frequency.
if ( ! metadata ) {
metadata = { freq , 500'000 } ;
repeat_file_error ( rawmeta , " Can't open file to read meta, using default rx_freq,500'000 " ) ;
}
// Update the sample rate in proc_replay baseband.
baseband : : set_sample_rate ( metadata - > sample_rate ,
get_oversample_rate ( metadata - > sample_rate ) ) ;
transmitter_model . set_sampling_rate ( get_actual_sample_rate ( metadata - > sample_rate ) ) ;
transmitter_model . set_baseband_bandwidth ( metadata - > sample_rate < = 500'000 ? 1'750'000 : 2'500'000 ) ; // TX LPF min 1M75 for SR <=500K, and 2M5 (by experimental test) for SR >500K
2023-12-28 14:10:38 -05:00
// set TX to repeater TX freq if entry is Repeater and have a valid freq, else use recorded one
if ( current_entry ( ) . type = = freqman_type : : Repeater & & current_entry ( ) . frequency_b ! = 0 ) {
2023-12-28 05:25:53 -05:00
transmitter_model . set_target_frequency ( current_entry ( ) . frequency_b ) ;
} else {
transmitter_model . set_target_frequency ( metadata - > center_frequency ) ;
}
// set TX powers and enable transmitter
transmitter_model . set_tx_gain ( persistent_memory : : recon_repeat_gain ( ) ) ;
transmitter_model . set_rf_amp ( persistent_memory : : recon_repeat_amp ( ) ) ;
transmitter_model . enable ( ) ;
}
// clear replay thread and set reader
replay_thread . reset ( ) ;
auto reader = std : : make_unique < FileConvertReader > ( ) ;
auto error = reader - > open ( rawfile ) ;
if ( error ) {
repeat_file_error ( rawfile , " Can't open file to send to thread " ) ;
return ;
}
2023-12-30 16:32:41 -05:00
// wait for TX if needed (hackish, direct screen update since the UI will be blocked)
if ( persistent_memory : : recon_repeat_delay ( ) > 0 ) {
uint8_t delay = persistent_memory : : recon_repeat_delay ( ) ;
Painter p ;
while ( delay > 0 ) {
std : : string delay_message = " TX DELAY: " + to_string_dec_uint ( delay ) + " s " ;
// update display information
p . fill_rectangle ( { 0 , ( SCREEN_H / 2 ) - 16 , SCREEN_W , 64 } , Color : : light_grey ( ) ) ;
p . draw_string ( { ( SCREEN_W / 2 ) - 7 * 8 , SCREEN_H / 2 } , Styles : : red , delay_message ) ;
// sleep 1 second
chThdSleepMilliseconds ( 1000 ) ;
// decre delay
if ( delay > 0 )
delay = delay - 1 ;
else
break ;
}
}
2023-12-28 05:25:53 -05:00
// ReplayThread starts immediately on construction; must be set before creating.
2023-12-30 16:32:41 -05:00
repeat_ready_signal = true ;
repeat_cur_rep + + ;
2023-12-28 05:25:53 -05:00
replay_thread = std : : make_unique < ReplayThread > (
std : : move ( reader ) ,
/* read_size */ repeat_read_size ,
/* buffer_count */ repeat_buffer_count ,
& repeat_ready_signal ,
[ ] ( uint32_t return_code ) {
ReplayThreadDoneMessage message { return_code } ;
EventDispatcher : : send_message ( message ) ;
} ) ;
}
void ReconView : : stop_repeat ( const bool do_loop ) {
repeat_ready_signal = false ;
if ( is_repeat_active ( ) ) {
replay_thread . reset ( ) ;
transmitter_model . disable ( ) ;
}
// repeat transmit if current number of repetitions (repeat_cur_rep) is < recon configured number of repetitions (recon_repeat_nb)
if ( do_loop & & repeat_cur_rep < persistent_memory : : recon_repeat_nb ( ) ) {
start_repeat ( ) ;
} else {
repeat_cur_rep = 0 ;
recon_tx = false ;
reload_restart_recon ( ) ;
progressbar . hidden ( true ) ;
set_dirty ( ) ; // fix progressbar no hiding
}
}
void ReconView : : handle_repeat_thread_done ( const uint32_t return_code ) {
if ( return_code = = ReplayThread : : END_OF_FILE ) {
stop_repeat ( true ) ;
} else if ( return_code = = ReplayThread : : READ_ERROR ) {
stop_repeat ( false ) ;
repeat_file_error ( u " / " + repeat_rec_path + u " / " + repeat_rec_file , " Can't open file to send. " ) ;
}
}
2022-09-11 10:07:08 -04:00
} /* namespace ui */