scanner-enhanced-version

New ui_scanner, inspired on AlainD's (alain00091) PR: https://github.com/eried/portapack-mayhem/pull/80

It includes the following:

1) A big frequency numbers display.

2) A Manual scan section (you can input a frequency range (START / END), choose a STEP value from an available of standard frequency intervals, and press SCAN button.

3) An AM / WFM / NFM scan mode selector, changing "on the fly".

4) A PAUSE / RESUME button, which will make the scanner to stop upon you listening something of interest

5) AUDIO APP button, a quick shortcut into the analog audio visualizing / recording app, with the mode, frequency, amp, LNA, VGA settings already in tune with the scanner.

6) Two enums are added to freqman.hpp, reserved for compatibility with AlainD's proposed freqman's app and / or further enhancement. More on this topic:

ORIGINAL scanner just used one frequency step, when creating scanning frequency ranges, which was unacceptable.  AlainD enhanced freqman in order to pass different steppings along with ranges.  This seems an excellent idea, and I preserved that aspect on my current implementation of thisscanner, while adding those enums into the freqman just to keep the door open for AlainD's freqman in the future.

7) I did eliminate the extra blank spaces added by function to_string_short_freq() which created unnecessary spacing in every app where there is need for a SHORT string, from a frequency number. (SHORT!, no extra spaces!!)

8) I also maintained AlainD idea of capping the number of frequencies which are dynamically created for each range and stored inside a memory based db. While AlainD capped the number into 400 frequencies, I was able to up that value a bit more, into 500.

Cheers!
This commit is contained in:
euquiq 2020-07-20 16:43:24 -03:00
parent f21e26eaa3
commit 27f566be8f
4 changed files with 421 additions and 144 deletions

View file

@ -26,6 +26,7 @@
#include "string_format.hpp"
#include "audio.hpp"
using namespace portapack;
namespace ui {
@ -38,6 +39,10 @@ ScannerThread::ScannerThread(
}
ScannerThread::~ScannerThread() {
stop();
}
void ScannerThread::stop() {
if( thread ) {
chThdTerminate(thread);
chThdWait(thread);
@ -49,6 +54,18 @@ void ScannerThread::set_scanning(const bool v) {
_scanning = v;
}
bool ScannerThread::is_scanning() {
return _scanning;
}
void ScannerThread::set_userpause(const bool v) {
_userpause = v;
}
bool ScannerThread::is_userpause() {
return _userpause;
}
msg_t ScannerThread::static_fn(void* arg) {
auto obj = static_cast<ScannerThread*>(arg);
obj->run();
@ -56,36 +73,33 @@ msg_t ScannerThread::static_fn(void* arg) {
}
void ScannerThread::run() {
RetuneMessage message { };
uint32_t frequency_index = 0;
while( !chThdShouldTerminate() ) {
if (_scanning) {
// Retune
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]);
if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ...
RetuneMessage message { };
uint32_t frequency_index = 0;
while( !chThdShouldTerminate() ) {
if (_scanning) {
// Retune
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]);
message.range = frequency_index;
EventDispatcher::send_message(message);
message.range = frequency_index;
EventDispatcher::send_message(message);
frequency_index++;
if (frequency_index >= frequency_list_.size())
frequency_index = 0;
frequency_index++;
if (frequency_index >= frequency_list_.size())
frequency_index = 0;
}
chThdSleepMilliseconds(50); //50 is enough for reception stabilization, increase for more precise scanning ?
}
chThdSleepMilliseconds(50);
}
}
void ScannerView::handle_retune(uint32_t i) {
text_cycle.set( to_string_dec_uint(i) + "/" +
to_string_dec_uint(frequency_list.size()) + " : " +
to_string_dec_uint(frequency_list[i]) );
desc_cycle.set( description_list[i] );
big_display.set(frequency_list[i]); //Show the big Freq
text_cycle.set( to_string_dec_uint(i + 1,3) );
if (description_list[i].size() > 0) desc_cycle.set( description_list[i] ); //If this is a new description: show
}
void ScannerView::focus() {
field_lna.focus();
field_mode.focus();
}
ScannerView::~ScannerView() {
@ -94,9 +108,16 @@ ScannerView::~ScannerView() {
baseband::shutdown();
}
void ScannerView::show_max() { //show total number of freqs to scan
if (frequency_list.size() == MAX_DB_ENTRY)
text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)");
else
text_max.set( "/ " + to_string_dec_uint(frequency_list.size()));
}
ScannerView::ScannerView(
NavigationView&
)
NavigationView& nav
) : nav_ { nav }
{
add_children({
&labels,
@ -105,104 +126,190 @@ ScannerView::ScannerView(
&field_rf_amp,
&field_volume,
&field_bw,
&field_trigger,
&field_squelch,
&field_wait,
//&record_view,
&rssi,
&text_cycle,
&text_max,
&desc_cycle,
//&waterfall,
&big_display,
&button_manual_start,
&button_manual_end,
&field_mode,
&step_mode,
&button_manual_scan,
&button_pause,
&button_audio_app
});
std::string scanner_file = "SCANNER";
if (load_freqman_file(scanner_file, database)) {
for(auto& entry : database) {
// FIXME
if (entry.type == RANGE) {
for (uint32_t i=entry.frequency_a; i < entry.frequency_b; i+= 1000000) {
frequency_list.push_back(i);
description_list.push_back("RNG " + to_string_dec_uint(entry.frequency_a) + ">" + to_string_dec_uint(entry.frequency_b));
}
} else {
frequency_list.push_back(entry.frequency_a);
description_list.push_back(entry.description);
}
def_step = change_mode(AM); //Start on AM
field_mode.set_by_value(AM); //Reflect the mode into the manual selector
big_display.set_style(&style_green); //Start with green color
button_manual_start.on_select = [this, &nav](Button& button) {
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](Button& button) {
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));
};
};
button_pause.on_select = [this](Button&) {
if (scan_thread->is_userpause()) {
timer = wait * 10; //Unlock timer pause on_statistics_update
button_pause.set_text("PAUSE"); //resume scanning (show button for pause)
scan_thread->set_userpause(false);
//scan_resume();
} else {
scan_pause();
scan_thread->set_userpause(true);
button_pause.set_text("RESUME"); //PAUSED, show resume
}
} else {
// DEBUG
// TODO: Clean this
frequency_list.push_back(466025000);
description_list.push_back("POCSAG-France");
frequency_list.push_back(466050000);
description_list.push_back("POCSAG-France");
frequency_list.push_back(466075000);
description_list.push_back("POCSAG-France");
frequency_list.push_back(466175000);
description_list.push_back("POCSAG-France");
frequency_list.push_back(466206250);
description_list.push_back("POCSAG-France");
frequency_list.push_back(466231250);
description_list.push_back("POCSAG-France");
}
field_bw.set_selected_index(2);
field_bw.on_change = [this](size_t n, OptionsField::value_t) {
receiver_model.set_nbfm_configuration(n);
};
field_wait.on_change = [this](int32_t v) {
wait = v;
};
field_wait.set_value(5);
field_trigger.on_change = [this](int32_t v) {
trigger = v;
};
field_trigger.set_value(30);
field_squelch.set_value(receiver_model.squelch_level());
field_squelch.on_change = [this](int32_t v) {
squelch = v;
receiver_model.set_squelch_level(v);
button_audio_app.on_select = [this](Button&) {
if (scan_thread->is_scanning())
scan_thread->set_scanning(false);
scan_thread->stop();
nav_.pop();
nav_.push<AnalogAudioView>();
};
button_manual_scan.on_select = [this](Button&) {
if (!frequency_range.min || !frequency_range.max) {
nav_.display_modal("Error", "Both START and END freqs\nneed a value");
} else if (frequency_range.min > frequency_range.max) {
nav_.display_modal("Error", "END freq\nis lower than START");
} else {
scan_thread->stop(); //STOP SCANNER THREAD
frequency_list.clear();
description_list.clear();
def_step = step_mode.selected_index_value(); //Use def_step from manual selector
description_list.push_back(
"M:" + to_string_short_freq(frequency_range.min) + ">"
+ to_string_short_freq(frequency_range.max) + " S:"
+ to_string_short_freq(def_step)
);
rf::Frequency frequency = frequency_range.min;
while (frequency_list.size() < MAX_DB_ENTRY && frequency <= frequency_range.max) { //add manual range
frequency_list.push_back(frequency);
description_list.push_back(""); //If empty, will keep showing the last description
frequency+=def_step;
}
show_max();
start_scan_thread(); //RESTART SCANNER THREAD
}
};
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
if (scan_thread->is_scanning())
scan_thread->set_scanning(false); // WE STOP SCANNING
audio::output::stop();
scan_thread->stop();
receiver_model.disable();
baseband::shutdown();
chThdSleepMilliseconds(50);
change_mode(v);
start_scan_thread();
};
//PRE-CONFIGURATION:
field_wait.on_change = [this](int32_t v) { wait = v; }; field_wait.set_value(5);
field_squelch.on_change = [this](int32_t v) { squelch = v; }; field_squelch.set_value(30);
field_volume.set_value((receiver_model.headphone_volume() - audio::headphone::volume_range().max).decibel() + 99);
field_volume.on_change = [this](int32_t v) {
this->on_headphone_volume_changed(v);
};
audio::output::start();
audio::output::mute();
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.enable();
receiver_model.set_squelch_level(0);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
audio::output::unmute();
// TODO: Scanning thread here
scan_thread = std::make_unique<ScannerThread>(frequency_list);
field_volume.on_change = [this](int32_t v) { this->on_headphone_volume_changed(v); };
// LEARN FREQUENCIES
std::string scanner_txt = "SCANNER";
if ( load_freqman_file(scanner_txt, database) ) {
for(auto& entry : database) { // READ LINE PER LINE
if (frequency_list.size() < MAX_DB_ENTRY) { //We got space!
if (entry.type == RANGE) { //RANGE
switch (entry.step) {
case AM_US: def_step = 10000; break ;
case AM_EUR:def_step = 9000; break ;
case NFM_1: def_step = 12500; break ;
case NFM_2: def_step = 6250; break ;
case FM_1: def_step = 100000; break ;
case FM_2: def_step = 50000; break ;
case N_1: def_step = 25000; break ;
case N_2: def_step = 250000; break ;
case AIRBAND:def_step= 8330; break ;
}
frequency_list.push_back(entry.frequency_a); //Store starting freq and description
description_list.push_back("R:" + to_string_short_freq(entry.frequency_a)
+ ">" + to_string_short_freq(entry.frequency_b)
+ " S:" + to_string_short_freq(def_step));
while (frequency_list.size() < MAX_DB_ENTRY && entry.frequency_a <= entry.frequency_b) { //add the rest of the range
entry.frequency_a+=def_step;
frequency_list.push_back(entry.frequency_a);
description_list.push_back(""); //Token (keep showing the last description)
}
} else if ( entry.type == SINGLE) {
frequency_list.push_back(entry.frequency_a);
description_list.push_back("S: " + entry.description);
}
show_max();
}
else
{
break; //No more space: Stop reading the txt file !
}
}
}
else
{
desc_cycle.set(" NO SCANNER.TXT FILE ..." );
}
audio::output::stop();
step_mode.set_by_value(def_step); //Impose the default step into the manual step selector
start_scan_thread();
}
void ScannerView::on_statistics_update(const ChannelStatistics& statistics) {
int32_t max_db = statistics.max_db;
if (timer <= wait)
timer++;
if (max_db < -trigger) {
if (timer == wait) {
//audio::output::stop();
scan_thread->set_scanning(true);
if (!scan_thread->is_userpause())
{
if (timer >= (wait * 10) )
{
timer=0;
scan_resume();
}
else if (!timer)
{
if (statistics.max_db > -squelch) { //There is something on the air...
scan_pause();
timer++;
}
}
else
{
timer++;
}
} else {
//audio::output::start();
scan_thread->set_scanning(false);
timer = 0;
}
}
void ScannerView::scan_pause() {
if (scan_thread->is_scanning()) {
scan_thread->set_scanning(false); // WE STOP SCANNING
audio::output::start();
}
}
void ScannerView::scan_resume() {
if (!scan_thread->is_scanning()) {
audio::output::stop();
scan_thread->set_scanning(true); // WE RESCAN
}
}
@ -211,4 +318,60 @@ void ScannerView::on_headphone_volume_changed(int32_t v) {
receiver_model.set_headphone_volume(new_volume);
}
} /* namespace ui */
size_t ScannerView::change_mode(uint8_t new_mod) { //Before this, do a scan_thread->stop(); After this do a start_scan_thread()
using option_t = std::pair<std::string, int32_t>;
using options_t = std::vector<option_t>;
options_t bw;
field_bw.on_change = [this](size_t n, OptionsField::value_t) { };
switch (new_mod) {
case NFM: //bw 16k (2) default
bw.emplace_back("8k5", 0);
bw.emplace_back("11k", 0);
bw.emplace_back("16k", 0);
field_bw.set_options(bw);
baseband::run_image(portapack::spi_flash::image_tag_nfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
field_bw.set_selected_index(2);
receiver_model.set_nbfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_nbfm_configuration(n); };
receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(1750000);
break;
case AM:
bw.emplace_back("DSB", 0);
bw.emplace_back("USB", 0);
bw.emplace_back("LSB", 0);
field_bw.set_options(bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudio);
field_bw.set_selected_index(0);
receiver_model.set_am_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_am_configuration(n); };
receiver_model.set_sampling_rate(2000000);receiver_model.set_baseband_bandwidth(2000000);
break;
case WFM:
bw.emplace_back("16k", 0);
field_bw.set_options(bw);
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
field_bw.set_selected_index(0);
receiver_model.set_wfm_configuration(field_bw.selected_index());
field_bw.on_change = [this](size_t n, OptionsField::value_t) { receiver_model.set_wfm_configuration(n); };
receiver_model.set_sampling_rate(3072000); receiver_model.set_baseband_bandwidth(2000000);
break;
}
return mod_step[new_mod];
}
void ScannerView::start_scan_thread() {
receiver_model.enable();
receiver_model.set_squelch_level(0);
scan_thread = std::make_unique<ScannerThread>(frequency_list);
}
} /* namespace ui */