Recon raw repeat (#1658)

* UI for Recon Repeater config
* persistent recon repeat settings
* record_view: added possibility to force keep the provided record filename
* working auto record to same file
* Recon TX 
* added Repeater type
* adding yellow coloring on Repeater config+a modal, comments in the code
* default repeater values
This commit is contained in:
gullradriel 2023-12-28 11:25:53 +01:00 committed by GitHub
parent 1bf95e85a0
commit 794fece8cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 514 additions and 62 deletions

View File

@ -412,7 +412,8 @@ void FrequencyEditView::refresh_ui() {
auto is_range = entry_.type == freqman_type::Range; auto is_range = entry_.type == freqman_type::Range;
auto is_ham = entry_.type == freqman_type::HamRadio; auto is_ham = entry_.type == freqman_type::HamRadio;
auto has_freq_b = is_range || is_ham; auto is_repeater = entry_.type == freqman_type::Repeater;
auto has_freq_b = is_range || is_ham || is_repeater;
field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey); field_freq_b.set_style(has_freq_b ? &Styles::white : &Styles::grey);
field_step.set_style(is_range ? &Styles::white : &Styles::grey); field_step.set_style(is_range ? &Styles::white : &Styles::grey);

View File

@ -220,7 +220,8 @@ class FrequencyEditView : public View {
{"Single", 0}, {"Single", 0},
{"Range", 1}, {"Range", 1},
{"HamRadio", 2}, {"HamRadio", 2},
{"Raw", 3}, {"Repeater", 3},
{"Raw", 4},
}}; }};
FrequencyField field_freq_a{{13 * 8, 4 * 16}}; FrequencyField field_freq_a{{13 * 8, 4 * 16}};

View File

@ -22,13 +22,24 @@
*/ */
#include "ui_recon.hpp" #include "ui_recon.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp" #include "ui_freqman.hpp"
#include "capture_app.hpp" #include "capture_app.hpp"
#include "convert.hpp" #include "convert.hpp"
#include "file.hpp" #include "file.hpp"
#include "file_reader.hpp" #include "file_reader.hpp"
#include "tone_key.hpp" #include "tone_key.hpp"
#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"
using namespace portapack; using namespace portapack;
using namespace tonekey; using namespace tonekey;
@ -37,6 +48,30 @@ namespace fs = std::filesystem;
namespace ui { namespace ui {
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);
}
}
void ReconView::check_update_ranges_from_current() { void ReconView::check_update_ranges_from_current() {
if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) { if (frequency_list.size() && current_is_valid() && current_entry().type == freqman_type::Range) {
if (update_ranges && !manual_mode) { if (update_ranges && !manual_mode) {
@ -75,8 +110,12 @@ void ReconView::recon_stop_recording() {
button_audio_app.set_text("AUDIO"); button_audio_app.set_text("AUDIO");
button_audio_app.set_style(&Styles::white); button_audio_app.set_style(&Styles::white);
record_view->stop(); record_view->stop();
button_config.set_style(&Styles::white); // disable config while recording as it's causing an IO error pop up at exit button_config.set_style(&Styles::white);
is_recording = false; is_recording = false;
// repeater mode
if (persistent_memory::recon_repeat_recorded()) {
start_repeat();
}
} }
} }
@ -111,6 +150,9 @@ void ReconView::update_description() {
case freqman_type::HamRadio: case freqman_type::HamRadio:
description = "H: "; description = "H: ";
break; break;
case freqman_type::Repeater:
description = "L: ";
break;
default: default:
description = "S: "; description = "S: ";
} }
@ -182,6 +224,7 @@ void ReconView::load_persisted_settings() {
load_freqs = persistent_memory::recon_load_freqs(); load_freqs = persistent_memory::recon_load_freqs();
load_ranges = persistent_memory::recon_load_ranges(); load_ranges = persistent_memory::recon_load_ranges();
load_hamradios = persistent_memory::recon_load_hamradios(); load_hamradios = persistent_memory::recon_load_hamradios();
load_repeaters = persistent_memory::recon_load_repeaters();
update_ranges = persistent_memory::recon_update_ranges_when_recon(); update_ranges = persistent_memory::recon_update_ranges_when_recon();
auto_record_locked = persistent_memory::recon_auto_record_locked(); auto_record_locked = persistent_memory::recon_auto_record_locked();
} }
@ -275,9 +318,20 @@ ReconView::~ReconView() {
ReconView::ReconView(NavigationView& nav) ReconView::ReconView(NavigationView& nav)
: nav_{nav} { : nav_{nav} {
chrono_start = chTimeNow(); chrono_start = chTimeNow();
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16}, record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO", u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4); RecordView::FileType::WAV, 4096, 4);
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);
};
add_children({&labels, add_children({&labels,
&field_lna, &field_lna,
&field_vga, &field_vga,
@ -314,13 +368,9 @@ ReconView::ReconView(NavigationView& nav)
&button_restart, &button_restart,
&button_mic_app, &button_mic_app,
&button_remove, &button_remove,
record_view.get()}); record_view.get(),
&progressbar,
record_view->hidden(true); &tx_view});
record_view->set_filename_date_frequency(true);
record_view->on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
def_step = 0; def_step = 0;
load_persisted_settings(); load_persisted_settings();
@ -566,27 +616,7 @@ ReconView::ReconView(NavigationView& nav)
}; };
button_restart.on_select = [this](Button&) { button_restart.on_select = [this](Button&) {
frequency_file_load(); reload_restart_recon();
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);
}
}; };
button_add.on_select = [this](ButtonWithEncoder&) { button_add.on_select = [this](ButtonWithEncoder&) {
@ -704,6 +734,9 @@ ReconView::ReconView(NavigationView& nav)
change_mode(AM_MODULATION); // start on AM. change_mode(AM_MODULATION); // start on AM.
field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector field_mode.set_by_value(AM_MODULATION); // reflect the mode into the manual selector
// tx progress bar
progressbar.hidden(true);
if (filedelete) { if (filedelete) {
delete_file(freq_file_path); delete_file(freq_file_path);
} }
@ -741,7 +774,8 @@ void ReconView::frequency_file_load() {
freqman_load_options options{ freqman_load_options options{
.load_freqs = load_freqs, .load_freqs = load_freqs,
.load_ranges = load_ranges, .load_ranges = load_ranges,
.load_hamradios = load_hamradios}; .load_hamradios = load_hamradios,
.load_repeaters = load_repeaters};
if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) { if (!load_freqman_file(file_input, frequency_list, options) || frequency_list.empty()) {
file_name.set_style(&Styles::red); file_name.set_style(&Styles::red);
desc_cycle.set("...empty file..."); desc_cycle.set("...empty file...");
@ -776,6 +810,12 @@ void ReconView::frequency_file_load() {
} }
void ReconView::on_statistics_update(const ChannelStatistics& statistics) { void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (recon_tx)
return;
if (is_repeat_active())
return;
chrono_end = chTimeNow(); chrono_end = chTimeNow();
systime_t time_interval = chrono_end - chrono_start; systime_t time_interval = chrono_end - chrono_start;
chrono_start = chrono_end; chrono_start = chrono_end;
@ -835,9 +875,9 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
// contents of a possible recon_start_recording(), but not yet since it's only called once // contents of a possible recon_start_recording(), but not yet since it's only called once
if (auto_record_locked && !is_recording) { if (auto_record_locked && !is_recording) {
button_audio_app.set_style(&Styles::red); button_audio_app.set_style(&Styles::red);
if (field_mode.selected_index_value() == SPEC_MODULATION) if (field_mode.selected_index_value() == SPEC_MODULATION) {
button_audio_app.set_text("RAW REC"); button_audio_app.set_text("RAW REC");
else } else
button_audio_app.set_text("WAV REC"); button_audio_app.set_text("WAV REC");
record_view->start(); record_view->start();
button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit button_config.set_style(&Styles::light_grey); // disable config while recording as it's causing an IO error pop up at exit
@ -956,6 +996,26 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
} }
} }
} }
} 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;
}
}
} }
// set index to boundary if !continuous // set index to boundary if !continuous
if (has_looped && !continuous) { if (has_looped && !continuous) {
@ -984,6 +1044,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (entry_has_changed) { if (entry_has_changed) {
timer = 0; timer = 0;
switch (current_entry().type) { switch (current_entry().type) {
case freqman_type::Repeater:
case freqman_type::Single: case freqman_type::Single:
freq = current_entry().frequency_a; freq = current_entry().frequency_a;
break; break;
@ -1017,7 +1078,7 @@ void ReconView::on_statistics_update(const ChannelStatistics& statistics) {
if (stepper > 0) stepper--; if (stepper > 0) stepper--;
} // if( recon || stepper != 0 || index_stepper != 0 ) } // if( recon || stepper != 0 || index_stepper != 0 )
} // if (frequency_list.size() > 0 ) } // if (frequency_list.size() > 0 )
} /* on_statistic_updates */ } /* on_statistics_updates */
} }
handle_retune(); handle_retune();
recon_redraw(); recon_redraw();
@ -1080,28 +1141,39 @@ void ReconView::on_stepper_delta(int32_t v) {
} }
size_t ReconView::change_mode(freqman_index_t new_mod) { size_t ReconView::change_mode(freqman_index_t new_mod) {
if (recon_tx || is_repeat_active())
return 0;
field_mode.on_change = [this](size_t, OptionsField::value_t) {}; field_mode.on_change = [this](size_t, OptionsField::value_t) {};
field_bw.on_change = [this](size_t, OptionsField::value_t) {}; field_bw.on_change = [this](size_t, OptionsField::value_t) {};
recon_stop_recording(); recon_stop_recording();
if (record_view != nullptr) {
remove_child(record_view.get()); remove_child(record_view.get());
record_view.reset(); record_view.reset();
if (new_mod == SPEC_MODULATION) { }
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) {
audio::output::stop(); audio::output::stop();
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16}, record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_RAW_", u"CAPTURES", u"AUTO_RAW", u"CAPTURES",
RecordView::FileType::RawS16, 16384, 3); RecordView::FileType::RawS16, 16384, 3);
} else { } else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16}, record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
u"AUTO_AUDIO_", u"AUDIO", u"AUTO_AUDIO", u"AUDIO",
RecordView::FileType::WAV, 4096, 4); RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
} }
record_view->set_auto_trim(false);
add_child(record_view.get()); add_child(record_view.get());
record_view->hidden(true); record_view->hidden(true);
record_view->set_filename_date_frequency(true);
record_view->on_error = [this](std::string message) { record_view->on_error = [this](std::string message) {
nav_.display_modal("Error", message); nav_.display_modal("Error", message);
}; };
receiver_model.disable(); receiver_model.disable();
transmitter_model.disable();
baseband::shutdown(); baseband::shutdown();
size_t recording_sampling_rate = 0; size_t recording_sampling_rate = 0;
switch (new_mod) { switch (new_mod) {
@ -1144,7 +1216,6 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) { field_bw.on_change = [this](size_t, OptionsField::value_t sampling_rate) {
// record_view determines the correct oversampling to apply and returns the actual sample rate. // 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); auto actual_sampling_rate = record_view->set_sampling_rate(sampling_rate);
// The radio needs to know the effective sampling rate. // The radio needs to know the effective sampling rate.
receiver_model.set_sampling_rate(actual_sampling_rate); receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate)); receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
@ -1222,4 +1293,132 @@ void ReconView::handle_remove_current_item() {
update_description(); update_description();
} }
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
// set TX to repeater TX freq if entry is Repeater, else use recorded one
if (current_entry().type == freqman_type::Repeater) {
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;
}
repeat_ready_signal = true;
repeat_cur_rep++;
// ReplayThread starts immediately on construction; must be set before creating.
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.");
}
}
} /* namespace ui */ } /* namespace ui */

View File

@ -41,10 +41,20 @@
#include "app_settings.hpp" #include "app_settings.hpp"
#include "radio_state.hpp" #include "radio_state.hpp"
#include "ui_recon_settings.hpp" #include "ui_recon_settings.hpp"
#include "ui_transmitter.hpp"
#include "replay_thread.hpp"
#include "metadata_file.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_freq_field.hpp"
#include "ui_spectrum.hpp"
#include <string>
#include <memory>
namespace ui { namespace ui {
#define RECON_CFG_FILE "SETTINGS/recon.cfg" #define RECON_CFG_FILE u"SETTINGS/recon.cfg"
enum class recon_mode : uint8_t { enum class recon_mode : uint8_t {
Recon, Recon,
@ -66,10 +76,17 @@ class ReconView : public View {
private: private:
NavigationView& nav_; NavigationView& nav_;
RxRadioState radio_state_{}; RxRadioState rx_radio_state_{};
TxRadioState tx_radio_state_{
0 /* frequency */,
1750000 /* bandwidth */,
500000 /* sampling rate */
};
app_settings::SettingsManager settings_{ app_settings::SettingsManager settings_{
"rx_recon"sv, app_settings::Mode::RX}; "rx_tx_recon"sv, app_settings::Mode::RX_TX};
;
void reload_restart_recon();
void check_update_ranges_from_current(); void check_update_ranges_from_current();
void set_loop_config(bool v); void set_loop_config(bool v);
void clear_freqlist_for_ui_action(); void clear_freqlist_for_ui_action();
@ -125,6 +142,7 @@ class ReconView : public View {
bool load_freqs{true}; bool load_freqs{true};
bool load_ranges{true}; bool load_ranges{true};
bool load_hamradios{true}; bool load_hamradios{true};
bool load_repeaters{true};
bool update_ranges{true}; bool update_ranges{true};
bool fwd{true}; bool fwd{true};
bool recon{true}; bool recon{true};
@ -163,6 +181,25 @@ class ReconView : public View {
systime_t chrono_start{}; systime_t chrono_start{};
systime_t chrono_end{}; systime_t chrono_end{};
const std::filesystem::path repeat_rec_file = u"RECON_REPEAT.C16";
const std::filesystem::path repeat_rec_meta = u"RECON_REPEAT.TXT";
const std::filesystem::path repeat_rec_path = u"CAPTURES";
const size_t repeat_read_size{16384};
const size_t repeat_buffer_count{3};
int8_t repeat_cur_rep = 0;
int64_t repeat_sample_rate = 0;
static constexpr uint32_t repeat_bandwidth = 2500000;
void on_repeat_tx_progress(const uint32_t progress);
void start_repeat();
void stop_repeat(const bool do_loop);
bool is_repeat_active() const;
void handle_repeat_thread_done(const uint32_t return_code);
void repeat_file_error(const std::filesystem::path& path, const std::string& message);
std::filesystem::path repeat_file_path{};
std::unique_ptr<ReplayThread> replay_thread{};
bool repeat_ready_signal{false};
bool recon_tx{false};
// Persisted settings. // Persisted settings.
SettingsStore ui_settings{ SettingsStore ui_settings{
"recon"sv, "recon"sv,
@ -354,17 +391,47 @@ class ReconView : public View {
{168, (35 * 8) - 4, 72, 28}, {168, (35 * 8) - 4, 72, 28},
"<REMOVE>"}; "<REMOVE>"};
ProgressBar progressbar{
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
/*short_ui*/ true};
MessageHandlerRegistration message_handler_coded_squelch{ MessageHandlerRegistration message_handler_coded_squelch{
Message::ID::CodedSquelch, Message::ID::CodedSquelch,
[this](const Message* const p) { [this](const Message* const p) {
const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p); const auto message = *reinterpret_cast<const CodedSquelchMessage*>(p);
this->handle_coded_squelch(message.value); handle_coded_squelch(message.value);
}}; }};
MessageHandlerRegistration message_handler_stats{ MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics, Message::ID::ChannelStatistics,
[this](const Message* const p) { [this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics); on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
MessageHandlerRegistration message_handler_replay_thread_error{
Message::ID::ReplayThreadDone,
[this](const Message* p) {
auto message = *reinterpret_cast<const ReplayThreadDoneMessage*>(p);
handle_repeat_thread_done(message.return_code);
}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
[this](const Message* p) {
auto message = static_cast<const RequestSignalMessage*>(p);
if (message->signal == RequestSignalMessage::Signal::FillRequest) {
repeat_ready_signal = true;
}
}};
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* p) {
auto message = *reinterpret_cast<const TXProgressMessage*>(p);
on_repeat_tx_progress(message.progress);
}}; }};
}; };

View File

@ -99,8 +99,13 @@ void ReconSetupViewMore::save() {
persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value()); persistent_memory::set_recon_load_freqs(checkbox_load_freqs.value());
persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value()); persistent_memory::set_recon_load_ranges(checkbox_load_ranges.value());
persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value()); persistent_memory::set_recon_load_hamradios(checkbox_load_hamradios.value());
persistent_memory::set_recon_load_repeaters(checkbox_load_repeaters.value());
persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value()); persistent_memory::set_recon_update_ranges_when_recon(checkbox_update_ranges_when_recon.value());
persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value()); persistent_memory::set_recon_auto_record_locked(checkbox_auto_record_locked.value());
persistent_memory::set_recon_repeat_recorded(checkbox_repeat_recorded.value());
persistent_memory::set_recon_repeat_nb(field_repeat_nb.value());
persistent_memory::set_recon_repeat_amp(checkbox_repeat_amp.value());
persistent_memory::set_recon_repeat_gain(field_repeat_gain.value());
}; };
void ReconSetupViewMain::focus() { void ReconSetupViewMain::focus() {
@ -113,16 +118,50 @@ ReconSetupViewMore::ReconSetupViewMore(NavigationView& nav, Rect parent_rect)
hidden(true); hidden(true);
add_children({&checkbox_load_freqs, add_children({&checkbox_load_freqs,
&checkbox_load_repeaters,
&checkbox_load_ranges, &checkbox_load_ranges,
&checkbox_load_hamradios, &checkbox_load_hamradios,
&checkbox_update_ranges_when_recon, &checkbox_update_ranges_when_recon,
&checkbox_auto_record_locked}); &checkbox_auto_record_locked,
&checkbox_repeat_recorded,
&text_repeat_nb,
&field_repeat_nb,
&checkbox_repeat_amp,
&text_repeat_gain,
&field_repeat_gain});
// tx options have to be in yellow to inform the users that activating them will make the device transmit
checkbox_repeat_recorded.set_style(&Styles::yellow);
text_repeat_nb.set_style(&Styles::yellow);
field_repeat_nb.set_style(&Styles::yellow);
checkbox_repeat_amp.set_style(&Styles::yellow);
text_repeat_gain.set_style(&Styles::yellow);
field_repeat_gain.set_style(&Styles::yellow);
checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs()); checkbox_load_freqs.set_value(persistent_memory::recon_load_freqs());
checkbox_load_repeaters.set_value(persistent_memory::recon_load_repeaters());
checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges()); checkbox_load_ranges.set_value(persistent_memory::recon_load_ranges());
checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios()); checkbox_load_hamradios.set_value(persistent_memory::recon_load_hamradios());
checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon()); checkbox_update_ranges_when_recon.set_value(persistent_memory::recon_update_ranges_when_recon());
checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked()); checkbox_auto_record_locked.set_value(persistent_memory::recon_auto_record_locked());
checkbox_repeat_recorded.set_value(persistent_memory::recon_repeat_recorded());
checkbox_repeat_amp.set_value(persistent_memory::recon_repeat_amp());
field_repeat_nb.set_value(persistent_memory::recon_repeat_nb());
field_repeat_gain.set_value(persistent_memory::recon_repeat_gain());
// tx warning modal
checkbox_repeat_recorded.on_select = [this, &nav](Checkbox&, bool v) {
if (v) {
nav.display_modal(
"TX WARNING",
"This activate TX ability\nin Recon !",
YESNO,
[this, &nav](bool choice) {
if (!choice)
checkbox_repeat_recorded.set_value(choice);
});
}
};
}; };
void ReconSetupViewMore::focus() { void ReconSetupViewMore::focus() {

View File

@ -30,6 +30,7 @@
#include "ui_tabview.hpp" #include "ui_tabview.hpp"
#include "ui_navigation.hpp" #include "ui_navigation.hpp"
#include "string_format.hpp" #include "string_format.hpp"
#include "ui_styles.hpp"
// 1Mhz helper // 1Mhz helper
#ifdef OneMHz #ifdef OneMHz
@ -116,26 +117,70 @@ class ReconSetupViewMore : public View {
Checkbox checkbox_load_freqs{ Checkbox checkbox_load_freqs{
{1 * 8, 12}, {1 * 8, 12},
3, 3,
"input: load freqs"}; "load freq",
true};
Checkbox checkbox_load_repeaters{
{14 * 8, 12},
3,
"load repeater",
true};
Checkbox checkbox_load_ranges{ Checkbox checkbox_load_ranges{
{1 * 8, 42}, {1 * 8, 42},
3, 3,
"input: load ranges"}; "load range",
true};
Checkbox checkbox_load_hamradios{ Checkbox checkbox_load_hamradios{
{1 * 8, 72}, {1 * 8, 72},
3, 3,
"input: load hamradios"}; "load hamradio",
true};
Checkbox checkbox_update_ranges_when_recon{ Checkbox checkbox_update_ranges_when_recon{
{1 * 8, 102}, {1 * 8, 102},
3, 3,
"auto update m-ranges"}; "auto update m-ranges"};
Checkbox checkbox_auto_record_locked{ Checkbox checkbox_auto_record_locked{
{1 * 8, 132}, {1 * 8, 132},
3, 3,
"record locked periods"}; "record locked periods"};
Checkbox checkbox_repeat_recorded{
{1 * 8, 162},
3,
"repeater,"};
Text text_repeat_nb{
{14 * 8, 165, 3 * 8, 22},
"nb:"};
NumberField field_repeat_nb{
{18 * 8, 165},
2,
{1, 99},
1,
' ',
};
Checkbox checkbox_repeat_amp{
{1 * 8, 192},
3,
"AMP,"};
Text text_repeat_gain{
{10 * 8, 196, 5 * 8, 22},
"GAIN:"};
NumberField field_repeat_gain{
{16 * 8, 196},
2,
{0, 47},
1,
' ',
};
}; };
class ReconSetupView : public View { class ReconSetupView : public View {

View File

@ -564,6 +564,7 @@ void ScannerView::frequency_file_load(const fs::path& path) {
def_step_index = entry.step; def_step_index = entry.step;
switch (entry.type) { switch (entry.type) {
case freqman_type::Repeater:
case freqman_type::Single: case freqman_type::Single:
entries.push_back({entry.frequency_a, entry.description}); entries.push_back({entry.frequency_a, entry.description});
break; break;

View File

@ -185,6 +185,8 @@ bool operator==(const freqman_entry& lhs, const freqman_entry& rhs) {
} else if (lhs.type == freqman_type::HamRadio) { } else if (lhs.type == freqman_type::HamRadio) {
equal = lhs.frequency_b == rhs.frequency_b && equal = lhs.frequency_b == rhs.frequency_b &&
lhs.tone == rhs.tone; lhs.tone == rhs.tone;
} else if (lhs.type == freqman_type::Repeater) {
equal = lhs.frequency_b == rhs.frequency_b;
} }
return equal; return equal;
@ -248,6 +250,10 @@ std::string pretty_string(const freqman_entry& entry, size_t max_length) {
str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" + str = "R:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description; to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
break; break;
case freqman_type::Repeater:
str = "L:" + to_string_rounded_freq(entry.frequency_a, 1) + "M,T:" +
to_string_rounded_freq(entry.frequency_b, 1) + "M: " + entry.description;
break;
case freqman_type::Raw: case freqman_type::Raw:
str = entry.description; str = entry.description;
break; break;
@ -292,6 +298,10 @@ std::string to_freqman_string(const freqman_entry& entry) {
if (is_valid(entry.tone)) if (is_valid(entry.tone))
append_field("c", tonekey::tone_key_value_string(entry.tone)); append_field("c", tonekey::tone_key_value_string(entry.tone));
break; break;
case freqman_type::Repeater:
append_field("l", to_string_dec_uint(entry.frequency_a));
append_field("t", to_string_dec_uint(entry.frequency_b));
break;
case freqman_type::Raw: case freqman_type::Raw:
return entry.description; return entry.description;
default: default:
@ -372,16 +382,18 @@ bool parse_freqman_entry(std::string_view str, freqman_entry& entry) {
parse_int(value, entry.frequency_a); parse_int(value, entry.frequency_a);
} else if (key == "m") { } else if (key == "m") {
entry.modulation = find_by_name(freqman_modulations, value); entry.modulation = find_by_name(freqman_modulations, value);
} else if (key == "r") { } else if (key == "r") { // HamRadio relay receive freq
entry.type = freqman_type::HamRadio; entry.type = freqman_type::HamRadio;
parse_int(value, entry.frequency_a); parse_int(value, entry.frequency_a);
} else if (key == "l") { // Portapack Repeater mode listen freq. Used as a single freq if Repeater mode isn't active
entry.type = freqman_type::Repeater;
parse_int(value, entry.frequency_a);
} else if (key == "s") { } else if (key == "s") {
entry.step = find_by_name(freqman_steps_short, value); entry.step = find_by_name(freqman_steps_short, value);
} else if (key == "t") { } else if (key == "t") { // Tx freq: scanned as a single freq in HamRadio mode, used as TX freq in Repeater mode and ignored by the scanner
parse_int(value, entry.frequency_b); parse_int(value, entry.frequency_b);
} }
} }
return is_valid(entry); return is_valid(entry);
} }
@ -400,7 +412,8 @@ bool parse_freqman_file(const fs::path& path, freqman_db& db, freqman_load_optio
if (entry.type == freqman_type::Unknown || if (entry.type == freqman_type::Unknown ||
(entry.type == freqman_type::Single && !options.load_freqs) || (entry.type == freqman_type::Single && !options.load_freqs) ||
(entry.type == freqman_type::Range && !options.load_ranges) || (entry.type == freqman_type::Range && !options.load_ranges) ||
(entry.type == freqman_type::HamRadio && !options.load_hamradios)) { (entry.type == freqman_type::HamRadio && !options.load_hamradios) ||
(entry.type == freqman_type::Repeater && !options.load_repeaters)) {
continue; continue;
} }
@ -433,8 +446,8 @@ bool is_valid(const freqman_entry& entry) {
if (entry.frequency_a == 0) if (entry.frequency_a == 0)
return false; return false;
// Frequency B must be set for type Range or Ham Radio // Frequency B must be set for type Range or HamRadio or Repeater
if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio) { if (entry.type == freqman_type::Range || entry.type == freqman_type::HamRadio || entry.type == freqman_type::Repeater) {
if (entry.frequency_b == 0) if (entry.frequency_b == 0)
return false; return false;
} }

View File

@ -56,6 +56,7 @@ enum class freqman_type : uint8_t {
Single, // f= Single, // f=
Range, // a=,b= Range, // a=,b=
HamRadio, // r=,t= HamRadio, // r=,t=
Repeater, // l=,t=
Raw, // line content in description Raw, // line content in description
Unknown, Unknown,
}; };
@ -167,6 +168,7 @@ struct freqman_load_options {
bool load_freqs{true}; bool load_freqs{true};
bool load_ranges{true}; bool load_ranges{true};
bool load_hamradios{true}; bool load_hamradios{true};
bool load_repeaters{true};
}; };
using freqman_entry_ptr = std::unique_ptr<freqman_entry>; using freqman_entry_ptr = std::unique_ptr<freqman_entry>;

View File

@ -145,6 +145,15 @@ OversampleRate RecordView::get_oversample_rate(uint32_t sample_rate) {
// Setter for datetime and frequency filename // Setter for datetime and frequency filename
void RecordView::set_filename_date_frequency(bool set) { void RecordView::set_filename_date_frequency(bool set) {
filename_date_frequency = set; filename_date_frequency = set;
if (set)
filename_as_is = false;
}
// Setter for leaving the filename untouched
void RecordView::set_filename_as_is(bool set) {
filename_as_is = set;
if (set)
filename_date_frequency = false;
} }
bool RecordView::is_active() const { bool RecordView::is_active() const {
@ -186,9 +195,11 @@ void RecordView::start() {
base_path = filename_stem_pattern.string() + "_" + date_time + "_" + base_path = filename_stem_pattern.string() + "_" + date_time + "_" +
trim(to_string_freq(receiver_model.target_frequency())) + "Hz"; trim(to_string_freq(receiver_model.target_frequency())) + "Hz";
base_path = folder / base_path; base_path = folder / base_path;
} else { } else if (filename_as_is) {
base_path = filename_stem_pattern.string();
base_path = folder / base_path;
} else
base_path = next_filename_matching_pattern(folder / filename_stem_pattern); base_path = next_filename_matching_pattern(folder / filename_stem_pattern);
}
if (base_path.empty()) { if (base_path.empty()) {
return; return;

View File

@ -73,6 +73,7 @@ class RecordView : public View {
bool is_active() const; bool is_active() const;
void set_filename_date_frequency(bool set); void set_filename_date_frequency(bool set);
void set_filename_as_is(bool set);
private: private:
void toggle(); void toggle();
@ -91,6 +92,7 @@ class RecordView : public View {
// Time Stamp // Time Stamp
bool filename_date_frequency = false; bool filename_date_frequency = false;
bool filename_as_is = false;
rtc::RTC datetime{}; rtc::RTC datetime{};
const std::filesystem::path filename_stem_pattern; const std::filesystem::path filename_stem_pattern;

View File

@ -195,6 +195,8 @@ struct data_t {
// Recon App // Recon App
uint64_t recon_config; uint64_t recon_config;
int8_t recon_repeat_nb;
int8_t recon_repeat_gain;
// enable or disable converter // enable or disable converter
bool converter; bool converter;
@ -257,7 +259,10 @@ struct data_t {
tone_mix(tone_mix_reset_value), tone_mix(tone_mix_reset_value),
hardware_config(0), hardware_config(0),
recon_config(0), recon_config(0),
recon_repeat_nb(0),
recon_repeat_gain(0),
converter(false), converter(false),
updown_converter(false), updown_converter(false),
@ -391,10 +396,15 @@ void defaults() {
set_recon_continuous(true); set_recon_continuous(true);
set_recon_clear_output(false); set_recon_clear_output(false);
set_recon_load_freqs(true); set_recon_load_freqs(true);
set_recon_load_repeaters(true);
set_recon_load_ranges(true); set_recon_load_ranges(true);
set_recon_update_ranges_when_recon(true); set_recon_update_ranges_when_recon(true);
set_recon_load_hamradios(true); set_recon_load_hamradios(true);
set_recon_match_mode(0); set_recon_match_mode(0);
set_recon_repeat_recorded(false);
set_recon_repeat_amp(false);
set_recon_repeat_gain(35);
set_recon_repeat_nb(3);
set_config_sdcard_high_speed_io(false, true); set_config_sdcard_high_speed_io(false, true);
} }
@ -747,6 +757,21 @@ bool recon_match_mode() {
bool recon_auto_record_locked() { bool recon_auto_record_locked() {
return (data->recon_config & 0x00400000UL) ? true : false; return (data->recon_config & 0x00400000UL) ? true : false;
} }
bool recon_repeat_recorded() {
return (data->recon_config & 0x00200000UL) ? true : false;
}
int8_t recon_repeat_nb() {
return data->recon_repeat_nb;
}
int8_t recon_repeat_gain() {
return data->recon_repeat_gain;
}
bool recon_repeat_amp() {
return (data->recon_config & 0x00100000UL) ? true : false;
}
bool recon_load_repeaters() {
return (data->recon_config & 0x00080000UL) ? true : false;
}
void set_recon_autosave_freqs(const bool v) { void set_recon_autosave_freqs(const bool v) {
data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31); data->recon_config = (data->recon_config & ~0x80000000UL) | (v << 31);
@ -778,6 +803,21 @@ void set_recon_match_mode(const bool v) {
void set_recon_auto_record_locked(const bool v) { void set_recon_auto_record_locked(const bool v) {
data->recon_config = (data->recon_config & ~0x00400000UL) | (v << 22); data->recon_config = (data->recon_config & ~0x00400000UL) | (v << 22);
} }
void set_recon_repeat_recorded(const bool v) {
data->recon_config = (data->recon_config & ~0x00200000UL) | (v << 21);
}
void set_recon_repeat_nb(const int8_t v) {
data->recon_repeat_nb = v;
}
void set_recon_repeat_gain(const int8_t v) {
data->recon_repeat_gain = v;
}
void set_recon_repeat_amp(const bool v) {
data->recon_config = (data->recon_config & ~0x00100000UL) | (v << 20);
}
void set_recon_load_repeaters(const bool v) {
data->recon_config = (data->recon_config & ~0x00080000UL) | (v << 19);
}
/* UI Config 2 */ /* UI Config 2 */
bool ui_hide_speaker() { bool ui_hide_speaker() {

View File

@ -245,9 +245,14 @@ bool recon_autostart_recon();
bool recon_continuous(); bool recon_continuous();
bool recon_clear_output(); bool recon_clear_output();
bool recon_load_freqs(); bool recon_load_freqs();
bool recon_load_repeaters();
bool recon_load_ranges(); bool recon_load_ranges();
bool recon_update_ranges_when_recon(); bool recon_update_ranges_when_recon();
bool recon_auto_record_locked(); bool recon_auto_record_locked();
bool recon_repeat_recorded();
int8_t recon_repeat_nb();
int8_t recon_repeat_gain();
bool recon_repeat_amp();
bool recon_load_hamradios(); bool recon_load_hamradios();
bool recon_match_mode(); bool recon_match_mode();
void set_recon_autosave_freqs(const bool v); void set_recon_autosave_freqs(const bool v);
@ -258,7 +263,12 @@ void set_recon_load_freqs(const bool v);
void set_recon_load_ranges(const bool v); void set_recon_load_ranges(const bool v);
void set_recon_update_ranges_when_recon(const bool v); void set_recon_update_ranges_when_recon(const bool v);
void set_recon_auto_record_locked(const bool v); void set_recon_auto_record_locked(const bool v);
void set_recon_repeat_recorded(const bool v);
void set_recon_repeat_nb(const int8_t v);
void set_recon_repeat_gain(const int8_t v);
void set_recon_repeat_amp(const bool v);
void set_recon_load_hamradios(const bool v); void set_recon_load_hamradios(const bool v);
void set_recon_load_repeaters(const bool v);
void set_recon_match_mode(const bool v); void set_recon_match_mode(const bool v);
/* UI Config 2 */ /* UI Config 2 */

View File

@ -59,6 +59,17 @@ TEST_CASE("It can parse basic ham radio freq entry.") {
CHECK_EQ(e.type, freqman_type::HamRadio); CHECK_EQ(e.type, freqman_type::HamRadio);
} }
TEST_CASE("It can parse repeater entry.") {
freqman_entry e;
REQUIRE(
parse_freqman_entry(
"l=123000000,t=123000500,d=This is the description.", e));
CHECK_EQ(e.frequency_a, 123'000'000);
CHECK_EQ(e.frequency_b, 123'000'500);
CHECK_EQ(e.description, "This is the description.");
CHECK_EQ(e.type, freqman_type::Repeater);
}
TEST_CASE("It can parse modulation") { TEST_CASE("It can parse modulation") {
freqman_entry e; freqman_entry e;
REQUIRE( REQUIRE(
@ -195,6 +206,16 @@ TEST_CASE("It can serialize basic HamRadio entry") {
CHECK(str == "r=123456000,t=423456000,d=Foobar"); CHECK(str == "r=123456000,t=423456000,d=Foobar");
} }
TEST_CASE("It can serialize basic Repeater entry") {
auto str = to_freqman_string(freqman_entry{
.frequency_a = 123'456'000,
.frequency_b = 423'456'000,
.description = "Foobar",
.type = freqman_type::Repeater,
});
CHECK(str == "l=123456000,t=423456000,d=Foobar");
}
// New tables for a future PR. // New tables for a future PR.
/* /*
TEST_CASE("It can parse modulation") { TEST_CASE("It can parse modulation") {