2022-09-11 16:07:08 +02:00
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
2023-05-22 22:17:28 +02:00
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
2022-09-11 16:07:08 +02: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 11:43:37 -07:00
#include "ui_freqman.hpp"
2023-07-07 22:21:48 +02:00
#include "capture_app.hpp"
2023-07-17 11:43:37 -07:00
#include "convert.hpp"
2023-07-08 13:04:12 -07:00
#include "file.hpp"
2023-07-17 11:43:37 -07:00
#include "file_reader.hpp"
2023-07-08 13:04:12 -07:00
#include "tone_key.hpp"
2023-12-28 11:25:53 +01:00
#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 16:07:08 +02:00
using namespace portapack ;
2023-07-08 13:04:12 -07:00
using namespace tonekey ;
2022-09-11 16:07:08 +02:00
using portapack :: memory :: map :: backup_ram ;
2023-07-17 11:43:37 -07:00
namespace fs = std :: filesystem ;
2022-09-11 16:07:08 +02:00
namespace ui {
2023-12-28 11:25:53 +01:00
void ReconView :: reload_restart_recon () {
2024-03-10 01:45:54 +01:00
// force reload of current
change_mode ( field_mode . selected_index_value ());
uint8_t previous_index = current_index ;
reset_indexes ();
2023-12-28 11:25:53 +01:00
frequency_file_load ();
2024-03-10 01:45:54 +01:00
current_index = previous_index ;
handle_retune ();
2023-12-28 11:25:53 +01:00
if ( frequency_list . size () > 0 ) {
if ( fwd ) {
button_dir . set_text ( "FW>" );
} else {
button_dir . set_text ( "<RW" );
}
recon_resume ();
}
if ( scanner_mode ) {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_red );
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_red );
2023-12-28 11:25:53 +01:00
button_scanner_mode . set_text ( "SCAN" );
} else {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_blue );
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_blue );
2023-12-28 11:25:53 +01:00
button_scanner_mode . set_text ( "RECON" );
}
if ( frequency_list . size () > FREQMAN_MAX_PER_FILE ) {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_yellow );
2023-12-28 11:25:53 +01:00
}
}
2023-08-15 18:29:16 +02:00
void ReconView :: check_update_ranges_from_current () {
2023-08-18 22:05:28 +02:00
if ( frequency_list . size () && current_is_valid () && current_entry (). type == freqman_type :: Range ) {
2023-08-15 18:29:16 +02: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 13:04:12 -07: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 07:50:04 +02:00
void ReconView :: set_loop_config ( bool v ) {
continuous = v ;
2024-05-27 21:02:52 +02:00
button_loop_config . set_style ( v ? Theme :: getInstance () -> fg_green : Theme :: getInstance () -> bg_darkest );
2023-06-16 07:50:04 +02:00
persistent_memory :: set_recon_continuous ( continuous );
}
2023-12-30 22:32:41 +01:00
void ReconView :: recon_stop_recording ( bool exiting ) {
2023-06-25 08:16:49 +02:00
if ( is_recording ) {
record_view -> stop ();
is_recording = false ;
2024-03-10 01:45:54 +01:00
if ( field_mode . selected_index_value () == SPEC_MODULATION ) {
button_audio_app . set_text ( "RAW" );
// repeater mode
if ( ! exiting && persistent_memory :: recon_repeat_recorded ()) {
start_repeat ();
}
} else {
button_audio_app . set_text ( "AUDIO" );
2023-12-28 11:25:53 +01:00
}
2024-05-27 21:02:52 +02:00
button_audio_app . set_style ( Theme :: getInstance () -> bg_darkest );
button_config . set_style ( Theme :: getInstance () -> bg_darkest );
2023-06-25 08:16:49 +02:00
}
}
2023-05-24 20:52:05 +02:00
void ReconView :: clear_freqlist_for_ui_action () {
2023-12-30 22:32:41 +01:00
recon_stop_recording ( false );
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-05-24 20:52:05 +02:00
// flag to detect and reload frequency_list
if ( ! manual_mode ) {
2023-07-08 13:04:12 -07:00
// Clear doesn't actually free, re-assign so destructor runs on previous instance.
frequency_list = freqman_db {};
2023-06-22 17:34:20 +02:00
} else
2023-05-26 18:17:47 +02:00
frequency_list . shrink_to_fit ();
2023-06-22 17:34:20 +02:00
freqlist_cleared_for_ui_action = true ;
2023-05-24 20:52:05 +02:00
}
void ReconView :: reset_indexes () {
2023-07-08 13:04:12 -07:00
last_entry . modulation = freqman_invalid_index ;
last_entry . bandwidth = freqman_invalid_index ;
last_entry . step = freqman_invalid_index ;
2023-05-24 20:52:05 +02:00
current_index = 0 ;
}
2023-07-08 13:04:12 -07: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 11:25:53 +01:00
case freqman_type :: Repeater :
description = "L: " ;
break ;
2023-07-08 13:04:12 -07:00
default :
description = "S: " ;
}
description += current_entry (). description ;
}
desc_cycle . set ( description );
}
2023-05-21 20:42:54 +02:00
void ReconView :: colorize_waits () {
// colorize wait on match
if ( wait == 0 ) {
2024-05-27 21:02:52 +02:00
field_wait . set_style ( Theme :: getInstance () -> fg_blue );
2023-05-21 20:42:54 +02:00
} else if ( wait >= 500 ) {
2024-05-27 21:02:52 +02:00
field_wait . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-21 20:42:54 +02:00
} else if ( wait > - 500 && wait < 500 ) {
2024-05-27 21:02:52 +02:00
field_wait . set_style ( Theme :: getInstance () -> fg_red );
2023-05-21 20:42:54 +02:00
} else if ( wait <= - 500 ) {
2024-05-27 21:02:52 +02:00
field_wait . set_style ( Theme :: getInstance () -> fg_green );
2023-05-21 20:42:54 +02: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 ) {
2024-05-27 21:02:52 +02:00
field_lock_wait . set_style ( Theme :: getInstance () -> fg_yellow );
2023-05-21 20:42:54 +02:00
} else {
2024-05-27 21:02:52 +02:00
field_lock_wait . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-21 20:42:54 +02:00
}
} else {
2024-05-27 21:02:52 +02:00
field_lock_wait . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-21 20:42:54 +02:00
}
}
2023-07-17 11:43:37 -07:00
bool ReconView :: recon_save_freq ( const fs :: path & path , size_t freq_index , bool warn_if_exists ) {
2023-07-08 13:04:12 -07:00
if ( frequency_list . size () == 0 || ! current_is_valid ())
2023-05-19 08:16:05 +12:00
return false ;
2023-07-17 11:43:37 -07:00
FreqmanDB freq_db ;
if ( ! freq_db . open ( path , /*create*/ true ))
return false ;
2023-07-08 13:04:12 -07:00
freqman_entry entry = * frequency_list [ freq_index ]; // Makes a copy.
2023-05-19 08:16:05 +12:00
2023-07-17 11:43:37 -07: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-19 08:16:05 +12:00
}
2023-07-17 11:43:37 -07: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-19 08:16:05 +12:00
return true ;
}
2023-08-20 13:28:02 -07: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 11:25:53 +01:00
load_repeaters = persistent_memory :: recon_load_repeaters ();
2023-08-20 13:28:02 -07:00
update_ranges = persistent_memory :: recon_update_ranges_when_recon ();
auto_record_locked = persistent_memory :: recon_auto_record_locked ();
2023-05-19 08:16:05 +12:00
}
void ReconView :: audio_output_start () {
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: start ();
2023-06-05 11:09:50 -07:00
receiver_model . set_headphone_volume ( receiver_model . headphone_volume ()); // WM8731 hack.
2023-05-19 08:16:05 +12: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 10:49:07 +02:00
big_display . set ( "FREQ:" + to_string_short_freq ( freq ) + " MHz" );
2023-05-19 08:16:05 +12:00
}
2023-05-22 10:49:07 +02:00
if ( last_nb_match != recon_lock_nb_match || last_freq_lock != freq_lock ) {
last_freq_lock = freq_lock ;
2023-05-19 08:16:05 +12:00
last_nb_match = recon_lock_nb_match ;
2023-05-22 10:49:07 +02:00
text_nb_locks . set ( to_string_dec_uint ( freq_lock ) + "/" + to_string_dec_uint ( recon_lock_nb_match ));
2023-05-19 08:16:05 +12:00
if ( freq_lock == 0 ) {
// NO FREQ LOCK, ONGOING STANDARD SCANNING
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-22 10:49:07 +02:00
if ( recon )
2023-05-19 08:16:05 +12: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
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> fg_yellow );
2023-05-19 08:16:05 +12:00
button_pause . set_text ( "<SKPLCK>" );
} else if ( freq_lock >= recon_lock_nb_match ) {
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> fg_green );
2023-05-19 08:16:05 +12:00
button_pause . set_text ( "<UNLOCK>" );
}
}
2023-05-22 10:49:07 +02: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-19 08:16:05 +12:00
}
void ReconView :: handle_retune () {
if ( last_freq != freq ) {
last_freq = freq ;
2023-06-11 11:47:13 -07:00
receiver_model . set_target_frequency ( freq ); // Retune
2023-05-19 08:16:05 +12:00
}
if ( frequency_list . size () > 0 ) {
2023-07-08 13:04:12 -07: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-19 08:16:05 +12:00
}
// Set bandwidth if any
2023-07-08 13:04:12 -07: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-19 08:16:05 +12:00
}
2023-07-08 13:04:12 -07:00
if ( last_entry . step != current_entry (). step && is_valid ( current_entry (). step )) {
last_entry . step = current_entry (). step ;
2023-05-19 08:16:05 +12: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 18:29:16 +02:00
check_update_ranges_from_current ();
2023-05-19 08:16:05 +12:00
text_cycle . set_text ( to_string_dec_uint ( current_index + 1 , 3 ));
2023-07-08 13:04:12 -07:00
update_description ();
2023-05-19 08:16:05 +12:00
}
}
}
void ReconView :: focus () {
button_pause . focus ();
}
ReconView ::~ ReconView () {
2023-12-30 22:32:41 +01:00
if ( recon_tx ) {
replay_thread . reset ();
}
recon_stop_recording ( true );
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-12-30 22:32:41 +01:00
transmitter_model . disable ();
2023-05-19 08:16:05 +12:00
receiver_model . disable ();
baseband :: shutdown ();
}
ReconView :: ReconView ( NavigationView & nav )
: nav_ { nav } {
2023-06-25 08:16:49 +02:00
chrono_start = chTimeNow ();
2023-12-28 11:25:53 +01:00
tx_view . hidden ( true );
// set record View
2025-05-23 08:42:19 +02:00
record_view = std :: make_unique < RecordView > ( Rect { 0 , 0 , screen_width , 1 * 16 },
2024-03-25 02:44:49 -05:00
u "AUTO_AUDIO" , audio_dir ,
2023-06-28 10:14:30 -07:00
RecordView :: FileType :: WAV , 4096 , 4 );
2023-12-28 11:25:53 +01: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-19 08:16:05 +12:00
add_children ({ & labels ,
& field_lna ,
& field_vga ,
& field_rf_amp ,
& field_volume ,
& field_bw ,
& field_squelch ,
2023-06-11 18:49:28 +02:00
& field_nblocks ,
2023-05-19 08:16:05 +12:00
& field_wait ,
& field_lock_wait ,
2023-05-24 20:52:05 +02:00
& button_config ,
2023-05-19 08:16:05 +12:00
& button_scanner_mode ,
2023-06-16 07:50:04 +02:00
& button_loop_config ,
2023-05-19 08:16:05 +12:00
& file_name ,
& rssi ,
& text_cycle ,
& text_max ,
2023-05-22 10:49:07 +02:00
& text_nb_locks ,
2023-05-19 08:16:05 +12: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 18:49:28 +02:00
& field_recon_match_mode ,
2023-05-19 08:16:05 +12:00
& step_mode ,
& button_pause ,
& button_audio_app ,
& button_add ,
& button_dir ,
& button_restart ,
& button_mic_app ,
2023-06-25 08:16:49 +02:00
& button_remove ,
2023-12-28 11:25:53 +01:00
record_view . get (),
& progressbar ,
& tx_view });
2023-05-19 08:16:05 +12:00
def_step = 0 ;
2023-08-20 13:28:02 -07:00
load_persisted_settings ();
2023-06-11 11:47:13 -07:00
2023-08-20 13:28:02 -07:00
// 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 );
}
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-05-19 08:16:05 +12:00
button_manual_start . on_select = [ this , & nav ]( ButtonWithEncoder & button ) {
2023-05-24 20:52:05 +02:00
clear_freqlist_for_ui_action ();
2023-05-19 08:16:05 +12: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 20:52:05 +02:00
clear_freqlist_for_ui_action ();
2023-05-19 08:16:05 +12: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 11:43:37 -07:00
// NB: This is using the freq keypad to select an index.
2023-05-19 08:16:05 +12: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 11:43:37 -07: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-19 08:16:05 +12:00
if ( frequency_range . min < 0 ) {
frequency_range . min = 0 ;
}
2023-07-17 11:43:37 -07:00
if ( frequency_range . min > ( MAX_UFREQ - step_val )) {
frequency_range . min = MAX_UFREQ - step_val ;
2023-05-19 08:16:05 +12:00
}
2023-07-17 11:43:37 -07:00
if ( frequency_range . min > ( frequency_range . max - step_val )) {
frequency_range . max = frequency_range . min + step_val ;
2023-05-19 08:16:05 +12:00
if ( frequency_range . max > MAX_UFREQ ) {
2023-07-17 11:43:37 -07:00
frequency_range . min = MAX_UFREQ - step_val ;
2023-05-19 08:16:05 +12: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 11:43:37 -07: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-19 08:16:05 +12:00
}
if ( frequency_range . max > MAX_UFREQ ) {
frequency_range . max = MAX_UFREQ ;
}
2023-07-17 11:43:37 -07: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-19 08:16:05 +12:00
frequency_range . min = 1 ;
2023-07-17 11:43:37 -07:00
frequency_range . max = ( step_val + 1 );
2023-05-19 08:16:05 +12: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 10:49:07 +02:00
if ( ! recon ) {
2023-05-19 08:16:05 +12:00
recon_resume ();
2023-06-11 18:49:28 +02:00
user_pause = false ;
2023-05-19 08:16:05 +12:00
} else {
recon_pause ();
2023-06-11 18:49:28 +02:00
user_pause = true ;
2023-05-19 08:16:05 +12: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 16:26:26 -07:00
auto settings = receiver_model . settings ();
settings . frequency_step = step_mode . selected_index_value ();
2023-07-07 22:21:48 +02:00
if ( field_mode . selected_index_value () == SPEC_MODULATION )
2024-03-30 12:57:43 -05:00
nav_ . replace < CaptureAppView > ( settings );
2023-07-07 22:21:48 +02:00
else
nav_ . replace < AnalogAudioView > ( settings );
2023-05-19 08:16:05 +12:00
};
2023-06-16 07:50:04 +02:00
button_loop_config . on_select = [ this ]( Button & ) {
set_loop_config ( ! continuous );
2023-05-19 08:16:05 +12:00
};
2023-06-16 07:50:04 +02:00
set_loop_config ( continuous );
2023-05-19 08:16:05 +12:00
rssi . set_peak ( true , 500 );
2023-06-11 11:47:13 -07:00
// TODO: *BUG* Both transmitter_model and receiver_model share the same pmem setting for target_frequency.
2023-05-19 08:16:05 +12:00
button_mic_app . on_select = [ this ]( Button & ) {
2023-06-02 07:53:15 +02:00
if ( frequency_list . size () > 0 && current_index >= 0 && ( unsigned ) current_index < frequency_list . size ()) {
2023-07-08 13:04:12 -07: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 07:53:15 +02:00
} else {
// it's single or range so we us actual tuned frequency
2023-06-11 11:47:13 -07:00
transmitter_model . set_target_frequency ( freq );
receiver_model . set_target_frequency ( freq );
2023-06-02 07:53:15 +02:00
}
}
2023-07-04 16:26:26 -07:00
2024-03-30 12:57:43 -05:00
// MicTX wants Frequency, Modulation and Bandwidth overrides, but that's only stored on the RX model.
2023-07-04 16:26:26 -07:00
nav_ . replace < MicTXView > ( receiver_model . settings ());
2023-05-19 08:16:05 +12:00
};
button_remove . on_select = [ this ]( ButtonWithEncoder & ) {
2023-07-17 11:43:37 -07:00
handle_remove_current_item ();
2023-05-19 08:16:05 +12:00
timer = 0 ;
freq_lock = 0 ;
2023-11-06 20:38:37 +01:00
recon_redraw ();
2023-05-19 08:16:05 +12: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 22:32:41 +01:00
button_remove . set_text ( "<DELETE>" );
2023-10-09 20:01:40 +02:00
button_add . hidden ( false );
2023-05-19 08:16:05 +12: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 18:29:16 +02: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 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-05-26 18:17:47 +02:00
2023-07-08 13:04:12 -07: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 > ());
2023-05-19 08:16:05 +12:00
2023-07-08 13:04:12 -07:00
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-19 08:16:05 +12:00
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> bg_darkest ); // Back to white color
2023-05-19 08:16:05 +12:00
2024-05-27 21:02:52 +02:00
freq_stats . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-19 08:16:05 +12:00
freq_stats . set ( "0/0/0" );
text_cycle . set_text ( "1" );
text_max . set ( "/1" );
2024-05-27 21:02:52 +02:00
button_scanner_mode . set_style ( Theme :: getInstance () -> bg_darkest );
2023-07-05 11:08:29 +02:00
button_scanner_mode . set_text ( "MANUAL" );
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> bg_darkest );
2023-10-08 22:21:00 +02:00
file_name . set ( "MANUAL => " + output_file );
2024-05-27 21:02:52 +02:00
desc_cycle . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-22 10:49:07 +02:00
2023-07-08 13:04:12 -07:00
last_entry . modulation = freqman_invalid_index ;
last_entry . bandwidth = freqman_invalid_index ;
last_entry . step = freqman_invalid_index ;
2023-05-22 10:49:07 +02:00
last_index = - 1 ;
2023-05-19 08:16:05 +12:00
2023-07-08 13:04:12 -07:00
freq = current_entry (). frequency_a ;
2023-05-19 08:16:05 +12: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 10:49:07 +02:00
if ( ! recon )
2023-05-19 08:16:05 +12:00
recon_resume ();
};
button_restart . on_select = [ this ]( Button & ) {
2023-12-28 11:25:53 +01:00
reload_restart_recon ();
2023-05-19 08:16:05 +12:00
};
2023-07-08 13:04:12 -07:00
button_add . on_select = [ this ]( ButtonWithEncoder & ) {
2023-05-19 08:16:05 +12:00
if ( ! scanner_mode ) {
2023-05-21 22:31:03 +02:00
recon_save_freq ( freq_file_path , current_index , true );
2023-05-19 08:16:05 +12: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 ;
2024-05-27 21:02:52 +02:00
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_blue );
2023-05-19 08:16:05 +12:00
button_scanner_mode . set_text ( "RECON" );
2023-12-30 22:32:41 +01:00
button_remove . set_text ( "<REMOVE>" );
2023-05-19 08:16:05 +12:00
} else {
scanner_mode = true ;
2024-05-27 21:02:52 +02:00
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_red );
2023-07-05 11:08:29 +02:00
button_scanner_mode . set_text ( "SCAN" );
2023-12-30 22:32:41 +01:00
button_remove . set_text ( "<DELETE>" );
2023-05-19 08:16:05 +12:00
}
2023-11-06 20:38:37 +01:00
frequency_file_load ();
2023-05-19 08:16:05 +12:00
if ( autostart ) {
recon_resume ();
} else {
recon_pause ();
}
2023-10-08 22:21:00 +02:00
button_add . hidden ( scanner_mode );
2023-10-09 20:01:40 +02:00
if ( scanner_mode ) // only needed when hiding, UI mayhem
set_dirty ();
2023-05-19 08:16:05 +12:00
};
2023-05-24 20:52:05 +02:00
button_config . on_select = [ this , & nav ]( Button & ) {
2023-06-25 11:33:44 +02:00
if ( is_recording ) // disabling config while recording
return ;
2023-10-08 22:21:00 +02:00
2023-05-24 20:52:05 +02:00
clear_freqlist_for_ui_action ();
2023-10-08 22:21:00 +02:00
2023-06-25 08:16:49 +02:00
freq_lock = 0 ;
timer = 0 ;
2023-06-11 18:49:28 +02:00
auto open_view = nav . push < ReconSetupView > ( input_file , output_file );
2023-05-19 08:16:05 +12:00
open_view -> on_changed = [ this ]( std :: vector < std :: string > result ) {
input_file = result [ 0 ];
output_file = result [ 1 ];
2023-07-11 13:48:36 -07:00
freq_file_path = get_freqman_path ( output_file ). string ();
2023-05-19 08:16:05 +12:00
2023-08-20 13:28:02 -07:00
load_persisted_settings ();
ui_settings . save ();
2023-05-19 08:16:05 +12:00
2023-11-06 20:38:37 +01:00
frequency_file_load ();
2023-06-22 17:34:20 +02:00
freqlist_cleared_for_ui_action = false ;
2023-05-22 10:49:07 +02:00
2023-05-19 08:16:05 +12:00
if ( autostart ) {
recon_resume ();
} else {
recon_pause ();
}
};
};
2023-06-11 18:49:28 +02:00
field_recon_match_mode . on_change = [ this ]( size_t , OptionsField :: value_t v ) {
recon_match_mode = v ;
colorize_waits ();
};
2023-05-19 08:16:05 +12:00
field_wait . on_change = [ this ]( int32_t v ) {
wait = v ;
2023-06-25 08:16:49 +02:00
// replacing -100 by 200 else it's freezing the device
if ( wait == - 100 )
wait = - 200 ;
2023-05-21 20:42:54 +02:00
colorize_waits ();
2023-05-19 08:16:05 +12:00
};
2023-06-11 18:49:28 +02: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-19 08:16:05 +12:00
field_lock_wait . on_change = [ this ]( uint32_t v ) {
recon_lock_duration = v ;
2023-05-21 20:42:54 +02:00
colorize_waits ();
2023-05-19 08:16:05 +12:00
};
field_squelch . on_change = [ this ]( int32_t v ) {
squelch = v ;
};
// PRE-CONFIGURATION:
2024-05-27 21:02:52 +02:00
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_blue );
2023-05-19 08:16:05 +12:00
button_scanner_mode . set_text ( "RECON" );
file_name . set ( "=>" );
// Loading input and output file from settings
2023-07-11 13:48:36 -07:00
freq_file_path = get_freqman_path ( output_file ). string ();
2023-05-19 08:16:05 +12:00
2023-06-11 18:49:28 +02:00
field_recon_match_mode . set_selected_index ( recon_match_mode );
2023-05-19 08:16:05 +12:00
field_squelch . set_value ( squelch );
field_wait . set_value ( wait );
field_lock_wait . set_value ( recon_lock_duration );
2023-06-11 18:49:28 +02:00
field_nblocks . set_value ( recon_lock_nb_match );
2023-05-21 20:42:54 +02:00
colorize_waits ();
2023-05-19 08:16:05 +12:00
// fill modulation and step options
freqman_set_modulation_option ( field_mode );
freqman_set_step_option ( step_mode );
// set radio
2023-06-11 11:47:13 -07:00
change_mode ( AM_MODULATION ); // start on AM.
field_mode . set_by_value ( AM_MODULATION ); // reflect the mode into the manual selector
2023-05-19 08:16:05 +12:00
2023-12-28 11:25:53 +01:00
// tx progress bar
progressbar . hidden ( true );
2023-05-19 08:16:05 +12:00
if ( filedelete ) {
delete_file ( freq_file_path );
}
2023-11-06 20:38:37 +01:00
frequency_file_load (); /* do not stop all at start */
2023-05-19 08:16:05 +12:00
if ( autostart ) {
recon_resume ();
} else {
recon_pause ();
}
recon_redraw ();
}
2023-11-06 20:38:37 +01:00
void ReconView :: frequency_file_load () {
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-05-19 08:16:05 +12:00
def_step = step_mode . selected_index (); // use def_step from manual selector
2023-06-22 17:34:20 +02:00
std :: string file_input = input_file ; // default recon mode
2023-05-19 08:16:05 +12:00
if ( scanner_mode ) {
file_input = output_file ;
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_red );
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_red );
desc_cycle . set_style ( Theme :: getInstance () -> fg_red );
2023-07-05 11:08:29 +02:00
button_scanner_mode . set_text ( "SCAN" );
2023-05-19 08:16:05 +12:00
} else {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_blue );
button_scanner_mode . set_style ( Theme :: getInstance () -> fg_blue );
desc_cycle . set_style ( Theme :: getInstance () -> fg_blue );
2023-05-19 08:16:05 +12:00
button_scanner_mode . set_text ( "RECON" );
}
2023-07-17 11:43:37 -07:00
2023-10-08 22:21:00 +02:00
file_name . set ( file_input + "=>" + output_file );
2023-07-08 13:04:12 -07:00
freqman_load_options options {
. load_freqs = load_freqs ,
. load_ranges = load_ranges ,
2023-12-28 11:25:53 +01:00
. load_hamradios = load_hamradios ,
. load_repeaters = load_repeaters };
2023-10-08 22:21:00 +02:00
if ( ! load_freqman_file ( file_input , frequency_list , options ) || frequency_list . empty ()) {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_red );
2023-10-08 22:21:00 +02:00
desc_cycle . set ( "...empty file..." );
frequency_list . clear ();
text_cycle . set_text ( " " );
return ;
2023-05-19 08:16:05 +12:00
}
2023-10-08 22:21:00 +02:00
if ( frequency_list . size () > FREQMAN_MAX_PER_FILE ) {
2024-05-27 21:02:52 +02:00
file_name . set_style ( Theme :: getInstance () -> fg_yellow );
2023-05-19 08:16:05 +12:00
}
2023-07-08 13:04:12 -07:00
2023-05-24 20:52:05 +02:00
reset_indexes ();
2023-07-08 13:04:12 -07: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-19 08:16:05 +12: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 13:04:12 -07:00
text_cycle . set_text ( to_string_dec_uint ( current_index + 1 , 3 ));
2023-08-15 18:29:16 +02:00
check_update_ranges_from_current ();
2023-07-08 13:04:12 -07:00
update_description ();
2023-05-19 08:16:05 +12:00
handle_retune ();
}
void ReconView :: on_statistics_update ( const ChannelStatistics & statistics ) {
2023-12-28 11:25:53 +01:00
if ( recon_tx )
return ;
if ( is_repeat_active ())
return ;
2023-07-05 21:54:28 +02:00
chrono_end = chTimeNow ();
2023-10-09 20:01:40 +02:00
systime_t time_interval = chrono_end - chrono_start ;
2023-07-05 21:54:28 +02:00
chrono_start = chrono_end ;
2023-06-25 08:16:49 +02:00
2023-05-24 20:52:05 +02: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 20:38:37 +01:00
frequency_file_load ();
2023-05-24 20:52:05 +02:00
}
2023-06-11 18:49:28 +02:00
if ( autostart && ! user_pause ) {
2023-05-24 20:52:05 +02:00
recon_resume ();
} else {
recon_pause ();
}
freqlist_cleared_for_ui_action = false ;
}
2023-05-19 08:16:05 +12:00
db = statistics . max_db ;
2023-05-22 10:49:07 +02:00
if ( recon ) {
2023-05-19 08:16:05 +12:00
if ( ! timer ) {
status = 0 ;
freq_lock = 0 ;
2023-10-09 20:01:40 +02:00
timer = recon_lock_duration ;
2023-05-19 08:16:05 +12:00
}
if ( freq_lock < recon_lock_nb_match ) // LOCKING
{
if ( status != 1 ) {
status = 1 ;
if ( wait != 0 ) {
2023-12-30 22:32:41 +01:00
recon_stop_recording ( false );
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-05-19 08:16:05 +12:00
}
}
if ( db > squelch ) // MATCHING LEVEL
{
freq_lock ++ ;
2023-08-17 17:28:12 +02:00
timer += time_interval ; // give some more time for next lock
2023-05-19 08:16:05 +12:00
} else {
// continuous, direct cut it if not consecutive match after 1 first match
if ( recon_match_mode == RECON_MATCH_CONTINUOUS ) {
2023-07-22 16:30:02 +02:00
freq_lock = 0 ;
timer = 0 ;
2023-05-19 08:16:05 +12:00
}
}
}
if ( freq_lock >= recon_lock_nb_match ) // LOCKED
{
if ( status != 2 ) {
status = 2 ;
2023-06-25 08:16:49 +02: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-19 08:16:05 +12:00
if ( wait != 0 ) {
2023-06-25 08:16:49 +02: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 ) {
2024-05-27 21:02:52 +02:00
button_audio_app . set_style ( Theme :: getInstance () -> fg_red );
2023-12-28 11:25:53 +01:00
if ( field_mode . selected_index_value () == SPEC_MODULATION ) {
2023-07-07 22:21:48 +02:00
button_audio_app . set_text ( "RAW REC" );
2023-12-28 11:25:53 +01:00
} else
2023-07-07 22:21:48 +02:00
button_audio_app . set_text ( "WAV REC" );
2023-06-25 08:16:49 +02:00
record_view -> start ();
2024-05-27 21:02:52 +02:00
button_config . set_style ( Theme :: getInstance () -> fg_light ); // disable config while recording as it's causing an IO error pop up at exit
2023-06-25 08:16:49 +02: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-19 08:16:05 +12: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 16:30:02 +02:00
if ( timer != 0 ) {
2023-08-17 17:28:12 +02:00
timer -= time_interval ;
2023-05-19 08:16:05 +12: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 13:04:12 -07:00
if ( current_entry (). type == freqman_type :: Range ) {
2023-05-19 08:16:05 +12: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 13:04:12 -07:00
} else if ( current_entry (). type == freqman_type :: Single ) {
2023-05-19 08:16:05 +12: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 13:04:12 -07:00
} else if ( current_entry (). type == freqman_type :: HamRadio ) {
2023-05-19 08:16:05 +12: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 11:25:53 +01: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-19 08:16:05 +12: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 08:16:49 +02:00
// for some motive, audio output gets stopped.
if ( ! recon && field_mode . selected_index_value () != SPEC_MODULATION )
2023-05-19 08:16:05 +12:00
audio_output_start ();
}
// reload entry if changed
if ( entry_has_changed ) {
timer = 0 ;
2023-07-08 13:04:12 -07:00
switch ( current_entry (). type ) {
2023-12-28 11:25:53 +01:00
case freqman_type :: Repeater :
2023-07-08 13:04:12 -07:00
case freqman_type :: Single :
freq = current_entry (). frequency_a ;
2023-05-19 08:16:05 +12:00
break ;
2023-07-08 13:04:12 -07:00
case freqman_type :: Range :
minfreq = current_entry (). frequency_a ;
maxfreq = current_entry (). frequency_b ;
2023-05-19 08:16:05 +12: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 13:04:12 -07:00
case freqman_type :: HamRadio :
minfreq = current_entry (). frequency_a ;
maxfreq = current_entry (). frequency_b ;
2023-05-19 08:16:05 +12: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 11:25:53 +01:00
} /* on_statistics_updates */
2023-05-19 08:16:05 +12:00
}
handle_retune ();
recon_redraw ();
}
void ReconView :: recon_pause () {
timer = 0 ;
freq_lock = 0 ;
recon = false ;
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio_output_start ();
2023-05-19 08:16:05 +12:00
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-19 08:16:05 +12:00
button_pause . set_text ( "<RESUME>" ); // PAUSED, show resume
}
void ReconView :: recon_resume () {
timer = 0 ;
freq_lock = 0 ;
recon = true ;
2023-06-25 08:16:49 +02:00
if ( field_mode . selected_index_value () != SPEC_MODULATION )
audio :: output :: stop ();
2023-05-19 08:16:05 +12:00
2024-05-27 21:02:52 +02:00
big_display . set_style ( Theme :: getInstance () -> bg_darkest );
2023-05-19 08:16:05 +12: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 10:49:07 +02:00
}
if ( v < 0 ) {
2023-05-19 08:16:05 +12: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 10:49:07 +02:00
}
if ( v < 0 ) {
2023-05-19 08:16:05 +12: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 ) {
2024-03-10 01:45:54 +01:00
if ( recon_tx || is_repeat_active () || is_recording )
2023-12-28 11:25:53 +01:00
return 0 ;
2023-05-19 08:16:05 +12:00
field_mode . on_change = [ this ]( size_t , OptionsField :: value_t ) {};
field_bw . on_change = [ this ]( size_t , OptionsField :: value_t ) {};
2023-12-30 22:32:41 +01:00
recon_stop_recording ( false );
2023-12-28 11:25:53 +01:00
if ( record_view != nullptr ) {
remove_child ( record_view . get ());
record_view . reset ();
}
2024-03-10 01:45:54 +01:00
if ( field_mode . selected_index_value () != SPEC_MODULATION ) {
2023-06-25 08:16:49 +02:00
audio :: output :: stop ();
2024-03-10 01:45:54 +01:00
}
if ( new_mod == SPEC_MODULATION ) {
if ( persistent_memory :: recon_repeat_recorded ()) {
2025-05-23 08:42:19 +02:00
record_view = std :: make_unique < RecordView > ( Rect { 0 , 0 , screen_width , 1 * 16 },
2024-03-25 02:44:49 -05:00
u "RECON_REPEAT.C16" , captures_dir ,
2024-03-10 01:45:54 +01:00
RecordView :: FileType :: RawS16 , 16384 , 3 );
record_view -> set_filename_as_is ( true );
} else {
2025-05-23 08:42:19 +02:00
record_view = std :: make_unique < RecordView > ( Rect { 0 , 0 , screen_width , 1 * 16 },
2024-03-25 02:44:49 -05:00
u "AUTO_RAW" , captures_dir ,
2024-03-10 01:45:54 +01:00
RecordView :: FileType :: RawS16 , 16384 , 3 );
record_view -> set_filename_date_frequency ( true );
}
2023-06-28 10:14:30 -07:00
} else {
2025-05-23 08:42:19 +02:00
record_view = std :: make_unique < RecordView > ( Rect { 0 , 0 , screen_width , 1 * 16 },
2024-03-25 02:44:49 -05:00
u "AUTO_AUDIO" , audio_dir ,
2023-06-28 10:14:30 -07:00
RecordView :: FileType :: WAV , 4096 , 4 );
2023-12-28 11:25:53 +01:00
record_view -> set_filename_date_frequency ( true );
2023-06-25 08:16:49 +02:00
}
2023-12-28 11:25:53 +01:00
record_view -> set_auto_trim ( false );
2023-06-28 22:21:13 +02:00
add_child ( record_view . get ());
2023-06-25 08:16:49 +02:00
record_view -> hidden ( true );
record_view -> on_error = [ this ]( std :: string message ) {
nav_ . display_modal ( "Error" , message );
};
2023-05-19 08:16:05 +12:00
receiver_model . disable ();
2023-12-28 11:25:53 +01:00
transmitter_model . disable ();
2023-05-19 08:16:05 +12:00
baseband :: shutdown ();
2023-06-25 08:16:49 +02:00
size_t recording_sampling_rate = 0 ;
2023-05-19 08:16:05 +12: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 11:46:08 -05: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 08:13:14 -07:00
// bw DSB (0) default
field_bw . set_by_value ( 0 );
2023-06-11 18:49:28 +02:00
text_ctcss . set ( " " );
2023-06-25 08:16:49 +02:00
recording_sampling_rate = 12000 ;
2023-05-19 08:16:05 +12: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 11:46:08 -05: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 08:13:14 -07:00
// bw 16k (2) default
field_bw . set_by_value ( 2 );
2023-06-25 08:16:49 +02:00
recording_sampling_rate = 24000 ;
2023-05-19 08:16:05 +12: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 11:46:08 -05: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 08:13:14 -07:00
// bw 200k (0) default
field_bw . set_by_value ( 0 );
2023-06-11 18:49:28 +02:00
text_ctcss . set ( " " );
2023-06-25 08:16:49 +02: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 08:13:14 -07: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 08:16:49 +02:00
};
2023-08-30 08:13:14 -07:00
// bw 12k5 (0) default
field_bw . set_by_value ( 0 );
2023-06-25 08:16:49 +02:00
text_ctcss . set ( " " );
2023-05-19 08:16:05 +12:00
break ;
default :
break ;
}
2023-07-05 21:54:28 +02:00
if ( new_mod != SPEC_MODULATION ) {
2023-07-07 22:21:48 +02:00
button_audio_app . set_text ( "AUDIO" );
2023-06-25 08:16:49 +02:00
record_view -> set_sampling_rate ( recording_sampling_rate );
2023-07-31 08:46:07 -07:00
// reset receiver model to fix bug when going from SPEC to audio, the sound is distorted
2023-07-05 21:54:28 +02:00
receiver_model . set_sampling_rate ( 3072000 );
receiver_model . set_baseband_bandwidth ( 1750000 );
2023-07-07 22:21:48 +02:00
} else {
button_audio_app . set_text ( "RAW" );
2023-07-05 21:54:28 +02:00
}
2023-05-19 08:16:05 +12:00
field_mode . set_selected_index ( new_mod );
field_mode . on_change = [ this ]( size_t , OptionsField :: value_t v ) {
2025-03-26 11:58:11 +01:00
// initialize to a value under SPEC
static freqman_index_t last_mode = WFM_MODULATION ;
if ( v > SPEC_MODULATION ) {
if ( last_mode == SPEC_MODULATION )
v = AM_MODULATION ;
else
v = SPEC_MODULATION ;
field_mode . set_selected_index ( v );
2023-05-19 08:16:05 +12:00
}
2025-03-26 11:58:11 +01:00
last_mode = v ;
change_mode ( v );
2023-05-19 08:16:05 +12:00
};
2023-06-25 08:16:49 +02:00
// for some motive, audio output gets stopped.
if ( ! recon && field_mode . selected_index_value () != SPEC_MODULATION )
2023-05-19 08:16:05 +12: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 18:53:51 -05: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-19 08:16:05 +12:00
}
2023-06-30 00:37:43 -05:00
2023-07-17 11:43:37 -07: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 22:21:00 +02: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 20:38:37 +01:00
entry = current_entry ();
freq = entry . frequency_a ;
2023-10-08 22:21:00 +02:00
} else {
current_index = 0 ;
text_cycle . set_text ( " " );
}
update_description ();
2023-07-17 11:43:37 -07:00
}
2023-12-28 11:25:53 +01: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.
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 20:10:38 +01: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 11:25:53 +01: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 22:32:41 +01: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
2025-05-22 17:24:53 +02:00
p . fill_rectangle ({ 0 , ( screen_height / 2 ) - 16 , screen_width , 64 }, Theme :: getInstance () -> fg_light -> foreground );
p . draw_string ({( screen_width / 2 ) - 7 * 8 , screen_height / 2 }, * Theme :: getInstance () -> fg_red , delay_message );
2023-12-30 22:32:41 +01:00
// sleep 1 second
chThdSleepMilliseconds ( 1000 );
// decre delay
if ( delay > 0 )
delay = delay - 1 ;
else
break ;
}
}
2023-12-28 11:25:53 +01:00
// ReplayThread starts immediately on construction; must be set before creating.
2023-12-30 22:32:41 +01:00
repeat_ready_signal = true ;
repeat_cur_rep ++ ;
2023-12-28 11:25:53 +01: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 ;
2024-03-10 01:45:54 +01:00
if ( persistent_memory :: recon_repeat_recorded_file_mode () == RECON_REPEAT_AND_KEEP ) {
// rename file here to keep
std :: filesystem :: path base_path = next_filename_matching_pattern ( repeat_rec_path / u "REC_????.*" );
rename_file ( rawfile , base_path . replace_extension ( u ".C16" ));
rename_file ( rawmeta , base_path . replace_extension ( u ".TXT" ));
}
2023-12-28 11:25:53 +01:00
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 );
2024-03-26 11:32:24 -05:00
repeat_file_error ( rawfile , "Can't open file to send." );
2023-12-28 11:25:53 +01:00
}
}
2022-09-11 16:07:08 +02:00
} /* namespace ui */