mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-14 12:08:40 +00:00
1b2c68b3c0
Squelch value now goes from -90 to +20 and it's directly compared against the max_db parameter returned from each freq scanned by the radio subsystem, with no adjusts or manipulation (you adjust the number as will be used). Less squelch means weaker signals will trigger it. (as expected). There was a tiny cosmetic bug when you deleted a frequency from the scanning memory: The description was not erased from screen and you could see it while the scan did not resume. There was another bug on the pause button: If you asked for another manual scan range when paused, the button kept the text "RESUME" (its text was not reset to "PAUSE" again).
519 lines
17 KiB
C++
519 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
|
* Copyright (C) 2018 Furrtek
|
|
*
|
|
* This file is part of PortaPack.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include "ui_scanner.hpp"
|
|
|
|
using namespace portapack;
|
|
|
|
namespace ui {
|
|
|
|
ScannerThread::ScannerThread(
|
|
std::vector<rf::Frequency> frequency_list
|
|
) : frequency_list_ { std::move(frequency_list) }
|
|
{
|
|
thread = chThdCreateFromHeap(NULL, 1024, NORMALPRIO + 10, ScannerThread::static_fn, this);
|
|
}
|
|
|
|
ScannerThread::~ScannerThread() {
|
|
stop();
|
|
}
|
|
|
|
void ScannerThread::stop() {
|
|
if( thread ) {
|
|
chThdTerminate(thread);
|
|
chThdWait(thread);
|
|
thread = nullptr;
|
|
}
|
|
}
|
|
|
|
void ScannerThread::set_scanning(const bool v) {
|
|
_scanning = v;
|
|
}
|
|
|
|
bool ScannerThread::is_scanning() {
|
|
return _scanning;
|
|
}
|
|
|
|
void ScannerThread::set_freq_lock(const uint32_t v) {
|
|
_freq_lock = v;
|
|
}
|
|
|
|
uint32_t ScannerThread::is_freq_lock() {
|
|
return _freq_lock;
|
|
}
|
|
|
|
void ScannerThread::set_freq_del(const uint32_t v) {
|
|
_freq_del = v;
|
|
}
|
|
|
|
void ScannerThread::change_scanning_direction() {
|
|
_fwd = !_fwd;
|
|
chThdSleepMilliseconds(300); //Give some pause after reversing scanning direction
|
|
|
|
}
|
|
|
|
msg_t ScannerThread::static_fn(void* arg) {
|
|
auto obj = static_cast<ScannerThread*>(arg);
|
|
obj->run();
|
|
return 0;
|
|
}
|
|
|
|
void ScannerThread::run() {
|
|
if (frequency_list_.size()) { //IF THERE IS A FREQUENCY LIST ...
|
|
RetuneMessage message { };
|
|
uint32_t frequency_index = frequency_list_.size();
|
|
bool restart_scan = false; //Flag whenever scanning is restarting after a pause
|
|
while( !chThdShouldTerminate() ) {
|
|
if (_scanning) { //Scanning
|
|
if (_freq_lock == 0) { //normal scanning (not performing freq_lock)
|
|
if (!restart_scan) { //looping at full speed
|
|
if (_fwd) { //forward
|
|
frequency_index++;
|
|
if (frequency_index >= frequency_list_.size())
|
|
frequency_index = 0;
|
|
|
|
} else { //reverse
|
|
if (frequency_index < 1)
|
|
frequency_index = frequency_list_.size();
|
|
frequency_index--;
|
|
}
|
|
receiver_model.set_tuning_frequency(frequency_list_[frequency_index]); // Retune
|
|
}
|
|
else
|
|
restart_scan=false; //Effectively skipping first retuning, giving system time
|
|
}
|
|
message.range = frequency_index; //Inform freq (for coloring purposes also!)
|
|
EventDispatcher::send_message(message);
|
|
}
|
|
else { //NOT scanning
|
|
if (_freq_del != 0) { //There is a frequency to delete
|
|
for (uint16_t i = 0; i < frequency_list_.size(); i++) { //Search for the freq to delete
|
|
if (frequency_list_[i] == _freq_del)
|
|
{ //found: Erase it
|
|
frequency_list_.erase(frequency_list_.begin() + i);
|
|
if (i==0) //set scan index one place back to compensate
|
|
i=frequency_list_.size();
|
|
else
|
|
i--;
|
|
break;
|
|
}
|
|
}
|
|
_freq_del = 0; //deleted.
|
|
}
|
|
else {
|
|
restart_scan=true; //Flag the need for skipping a cycle when restarting scan
|
|
}
|
|
}
|
|
chThdSleepMilliseconds(50); //Needed to (eventually) stabilize the receiver into new freq
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScannerView::handle_retune(uint32_t i) {
|
|
switch (scan_thread->is_freq_lock())
|
|
{
|
|
case 0: //NO FREQ LOCK, ONGOING STANDARD SCANNING
|
|
text_cycle.set( to_string_dec_uint(i + 1,3) );
|
|
current_index = i; //since it is an ongoing scan, this is a new index
|
|
if (description_list[current_index].size() > 0) desc_cycle.set( description_list[current_index] ); //Show new description
|
|
break;
|
|
case 1: //STARTING LOCK FREQ
|
|
big_display.set_style(&style_yellow);
|
|
break;
|
|
case MAX_FREQ_LOCK: //FREQ IS STRONG: GREEN and scanner will pause when on_statistics_update()
|
|
big_display.set_style(&style_green);
|
|
break;
|
|
default: //freq lock is checking the signal, do not update display
|
|
return;
|
|
}
|
|
big_display.set(frequency_list[current_index]); //UPDATE the big Freq after 0, 1 or MAX_FREQ_LOCK (at least, for color synching)
|
|
}
|
|
|
|
void ScannerView::focus() {
|
|
field_mode.focus();
|
|
}
|
|
|
|
ScannerView::~ScannerView() {
|
|
audio::output::stop();
|
|
receiver_model.disable();
|
|
baseband::shutdown();
|
|
}
|
|
|
|
void ScannerView::show_max() { //show total number of freqs to scan
|
|
if (frequency_list.size() == MAX_DB_ENTRY) {
|
|
text_max.set_style(&style_red);
|
|
text_max.set( "/ " + to_string_dec_uint(MAX_DB_ENTRY) + " (DB MAX!)");
|
|
}
|
|
else {
|
|
text_max.set_style(&style_grey);
|
|
text_max.set( "/ " + to_string_dec_uint(frequency_list.size()));
|
|
}
|
|
}
|
|
|
|
ScannerView::ScannerView(
|
|
NavigationView& nav
|
|
) : nav_ { nav }
|
|
{
|
|
add_children({
|
|
&labels,
|
|
&field_lna,
|
|
&field_vga,
|
|
&field_rf_amp,
|
|
&field_volume,
|
|
&field_bw,
|
|
&field_squelch,
|
|
&field_wait,
|
|
&rssi,
|
|
&text_cycle,
|
|
&text_max,
|
|
&desc_cycle,
|
|
&big_display,
|
|
&button_manual_start,
|
|
&button_manual_end,
|
|
&field_mode,
|
|
&step_mode,
|
|
&button_manual_scan,
|
|
&button_pause,
|
|
&button_dir,
|
|
&button_audio_app,
|
|
&button_mic_app,
|
|
&button_add,
|
|
&button_remove
|
|
|
|
});
|
|
|
|
def_step = change_mode(AM); //Start on AM
|
|
field_mode.set_by_value(AM); //Reflect the mode into the manual selector
|
|
|
|
//HELPER: Pre-setting a manual range, based on stored frequency
|
|
rf::Frequency stored_freq = persistent_memory::tuned_frequency();
|
|
frequency_range.min = stored_freq - 1000000;
|
|
button_manual_start.set_text(to_string_short_freq(frequency_range.min));
|
|
frequency_range.max = stored_freq + 1000000;
|
|
button_manual_end.set_text(to_string_short_freq(frequency_range.max));
|
|
|
|
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 ( userpause )
|
|
user_resume();
|
|
else {
|
|
scan_pause();
|
|
button_pause.set_text("RESUME"); //PAUSED, show resume
|
|
userpause=true;
|
|
}
|
|
};
|
|
|
|
button_audio_app.on_select = [this](Button&) {
|
|
scan_thread->stop();
|
|
nav_.pop();
|
|
nav_.push<AnalogAudioView>();
|
|
};
|
|
|
|
button_mic_app.on_select = [this](Button&) {
|
|
scan_thread->stop();
|
|
nav_.pop();
|
|
nav_.push<MicTXView>();
|
|
};
|
|
|
|
button_remove.on_select = [this](Button&) {
|
|
if (frequency_list.size() > current_index) {
|
|
if (scan_thread->is_scanning()) //STOP Scanning if necessary
|
|
scan_thread->set_scanning(false);
|
|
scan_thread->set_freq_del(frequency_list[current_index]);
|
|
description_list.erase(description_list.begin() + current_index);
|
|
frequency_list.erase(frequency_list.begin() + current_index);
|
|
show_max(); //UPDATE new list size on screen
|
|
desc_cycle.set(" "); //Clean up description (cosmetic detail)
|
|
scan_thread->set_freq_lock(0); //Reset the scanner lock
|
|
if ( userpause ) //If user-paused, resume
|
|
user_resume();
|
|
}
|
|
};
|
|
|
|
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 {
|
|
audio::output::stop();
|
|
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();
|
|
if ( userpause ) //If user-paused, resume
|
|
user_resume();
|
|
big_display.set_style(&style_grey); //Back to grey color
|
|
start_scan_thread(); //RESTART SCANNER THREAD
|
|
}
|
|
};
|
|
|
|
field_mode.on_change = [this](size_t, OptionsField::value_t v) {
|
|
receiver_model.disable();
|
|
baseband::shutdown();
|
|
change_mode(v);
|
|
if ( !scan_thread->is_scanning() ) //for some motive, audio output gets stopped.
|
|
audio::output::start(); //So if scan was stopped we resume audio
|
|
receiver_model.enable();
|
|
};
|
|
|
|
button_dir.on_select = [this](Button&) {
|
|
scan_thread->change_scanning_direction();
|
|
if ( userpause ) //If user-paused, resume
|
|
user_resume();
|
|
big_display.set_style(&style_grey); //Back to grey color
|
|
};
|
|
|
|
button_add.on_select = [this](Button&) { //frequency_list[current_index]
|
|
File scanner_file;
|
|
auto result = scanner_file.open("FREQMAN/SCANNER.TXT"); //First search if freq is already in txt
|
|
if (!result.is_valid()) {
|
|
std::string frequency_to_add = "f="
|
|
+ to_string_dec_uint(frequency_list[current_index] / 1000)
|
|
+ to_string_dec_uint(frequency_list[current_index] % 1000UL, 3, '0');
|
|
char one_char[1]; //Read it char by char
|
|
std::string line; //and put read line in here
|
|
bool found=false;
|
|
for (size_t pointer=0; pointer < scanner_file.size();pointer++) {
|
|
|
|
scanner_file.seek(pointer);
|
|
scanner_file.read(one_char, 1);
|
|
if ((int)one_char[0] > 31) { //ascii space upwards
|
|
line += one_char[0]; //Add it to the textline
|
|
}
|
|
else if (one_char[0] == '\n') { //New Line
|
|
if (line.compare(0, frequency_to_add.size(),frequency_to_add) == 0) {
|
|
found=true;
|
|
break;
|
|
}
|
|
line.clear(); //Ready for next textline
|
|
}
|
|
}
|
|
if (found) {
|
|
nav_.display_modal("Error", "Frequency already exists");
|
|
big_display.set(frequency_list[current_index]); //After showing an error
|
|
}
|
|
else {
|
|
auto result = scanner_file.append("FREQMAN/SCANNER.TXT"); //Second: append if it is not there
|
|
scanner_file.write_line(frequency_to_add + ",d=ADD FQ");
|
|
}
|
|
} else
|
|
{
|
|
nav_.display_modal("Error", "Cannot open SCANNER.TXT\nfor appending freq.");
|
|
big_display.set(frequency_list[current_index]); //After showing an error
|
|
}
|
|
};
|
|
|
|
//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(-10);
|
|
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); };
|
|
// 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) {
|
|
if ( !userpause ) //Scanning not user-paused
|
|
{
|
|
if (timer >= (wait * 10) )
|
|
{
|
|
timer = 0;
|
|
scan_resume();
|
|
}
|
|
else if (!timer)
|
|
{
|
|
if (statistics.max_db > squelch ) { //There is something on the air...(statistics.max_db > -squelch)
|
|
if (scan_thread->is_freq_lock() >= MAX_FREQ_LOCK) { //checking time reached
|
|
scan_pause();
|
|
timer++;
|
|
} else {
|
|
scan_thread->set_freq_lock( scan_thread->is_freq_lock() + 1 ); //in lock period, still analyzing the signal
|
|
}
|
|
} else { //There is NOTHING on the air
|
|
if (scan_thread->is_freq_lock() > 0) { //But are we already in freq_lock ?
|
|
big_display.set_style(&style_grey); //Back to grey color
|
|
scan_thread->set_freq_lock(0); //Reset the scanner lock, since there is no signal
|
|
}
|
|
}
|
|
}
|
|
else //Ongoing wait time
|
|
{
|
|
timer++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScannerView::scan_pause() {
|
|
if (scan_thread->is_scanning()) {
|
|
scan_thread->set_freq_lock(0); //Reset the scanner lock (because user paused, or MAX_FREQ_LOCK reached) for next freq scan
|
|
scan_thread->set_scanning(false); // WE STOP SCANNING
|
|
audio::output::start();
|
|
}
|
|
}
|
|
|
|
void ScannerView::scan_resume() {
|
|
audio::output::stop();
|
|
big_display.set_style(&style_grey); //Back to grey color
|
|
if (!scan_thread->is_scanning())
|
|
scan_thread->set_scanning(true); // RESUME!
|
|
}
|
|
|
|
void ScannerView::user_resume() {
|
|
timer = wait * 10; //Will trigger a scan_resume() on_statistics_update, also advancing to next freq.
|
|
button_pause.set_text("PAUSE"); //Show button for pause
|
|
userpause=false; //Resume scanning
|
|
}
|
|
|
|
void ScannerView::on_headphone_volume_changed(int32_t v) {
|
|
const auto new_volume = volume_t::decibel(v - 99) + audio::headphone::volume_range().max;
|
|
receiver_model.set_headphone_volume(new_volume);
|
|
}
|
|
|
|
size_t 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 */ |