Scanner: Added last locked frequencies list

Added back squelch to NFM receiver
Scanner: cleanup
Widgets: VU-meter cleanup
This commit is contained in:
furrtek 2017-05-18 11:06:11 +01:00
parent bebec9ccf7
commit 38e14b1e30
18 changed files with 788 additions and 631 deletions

View File

@ -162,7 +162,7 @@ set(CPPSRC
ui_baseband_stats_view.cpp ui_baseband_stats_view.cpp
ui_bht_tx.cpp ui_bht_tx.cpp
ui_channel.cpp ui_channel.cpp
ui_closecall.cpp ui_scanner.cpp
ui_coasterp.cpp ui_coasterp.cpp
ui_cw.cpp ui_cw.cpp
ui_debug.cpp ui_debug.cpp

View File

@ -77,8 +77,10 @@ NBFMOptionsView::NBFMOptionsView(
/* AnalogAudioView *******************************************************/ /* AnalogAudioView *******************************************************/
AnalogAudioView::AnalogAudioView( AnalogAudioView::AnalogAudioView(
NavigationView& nav NavigationView& nav,
) { bool eos
) : nav_ (nav)
{
add_children({ add_children({
&rssi, &rssi,
&channel, &channel,
@ -91,6 +93,8 @@ AnalogAudioView::AnalogAudioView(
&record_view, &record_view,
&waterfall, &waterfall,
}); });
exit_on_squelch = eos;
field_frequency.set_value(receiver_model.tuning_frequency()); field_frequency.set_value(receiver_model.tuning_frequency());
field_frequency.set_step(receiver_model.frequency_step()); field_frequency.set_step(receiver_model.frequency_step());
@ -311,4 +315,8 @@ void AnalogAudioView::update_modulation(const ReceiverModel::Mode modulation) {
} }
} }
void AnalogAudioView::squelched() {
if (exit_on_squelch) nav_.pop();
}
} /* namespace ui */ } /* namespace ui */

View File

@ -82,7 +82,7 @@ private:
class AnalogAudioView : public View { class AnalogAudioView : public View {
public: public:
AnalogAudioView(NavigationView& nav); AnalogAudioView(NavigationView& nav, bool eos);
~AnalogAudioView(); ~AnalogAudioView();
void on_hide() override; void on_hide() override;
@ -96,6 +96,9 @@ private:
const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }; const Rect options_view_rect { 0 * 8, 1 * 16, 30 * 8, 1 * 16 };
NavigationView& nav_;
bool exit_on_squelch { false };
RSSI rssi { RSSI rssi {
{ 21 * 8, 0, 6 * 8, 4 }, { 21 * 8, 0, 6 * 8, 4 },
}; };
@ -163,6 +166,17 @@ private:
void set_options_widget(std::unique_ptr<Widget> new_widget); void set_options_widget(std::unique_ptr<Widget> new_widget);
void update_modulation(const ReceiverModel::Mode modulation); void update_modulation(const ReceiverModel::Mode modulation);
void squelched();
MessageHandlerRegistration message_handler_squelch_signal {
Message::ID::RequestSignal,
[this](const Message* const p) {
(void)p;
this->squelched();
}
};
}; };
} /* namespace ui */ } /* namespace ui */

View File

@ -23,14 +23,15 @@
// Color bitmaps generated with: // Color bitmaps generated with:
// Gimp image > indexed colors (16), then "xxd -i *.bmp" // Gimp image > indexed colors (16), then "xxd -i *.bmp"
//TODO: Waveform widget as FFT view in scanner //TODO: Scanner multiple slices (buggy)
//TODO: Use to_string_short_freq
//TODO: Add auto-rounding to 12.5kHz channel option in scanner
//TODO: Waveform widget as FFT view in scanner ?
//BUG: Replay freezes when SD card not present //BUG: Replay freezes when SD card not present
//BUG: RDS doesn't stop baseband when stopping tx ? //BUG: RDS doesn't stop baseband when stopping tx ?
//BUG: Check AFSK transmit end, skips last bits ? //TEST: Check AFSK transmit end, skips last bits ?
//TEST: Imperial in whipcalc //TEST: Imperial in whipcalc
//TODO: IQ replay
//TODO: Optimize (and group ?) CTCSS tone gen code //TODO: Optimize (and group ?) CTCSS tone gen code
//TODO: Morse use prosigns //TODO: Morse use prosigns
//TODO: Morse live keying mode ? //TODO: Morse live keying mode ?
@ -41,39 +42,35 @@ Continuous (Fox-oring)
60s transmit, 360s space (Classic 1/7 min) 60s transmit, 360s space (Classic 1/7 min)
*/ */
//TODO: Use transmittermodel bw setting //TODO: Use transmittermodel bw setting
//TODO: Use Labels widget wherever possible //TODO: Use TransmitterView in TEDI/LCR, Numbers, ...
//TODO: Use TransmitterView in TEDI/LCR, Numbers, whistle, ...
//TODO: FreqMan: Add and rename categories //TODO: FreqMan: Add and rename categories
//TODO: FreqMan: Sort by category in edit screen //TODO: FreqMan: Sort by category in edit screen
//TODO: FreqMan: Cap entry count per category (only done for total entries right now) //TODO: FreqMan: Cap entry count per category (only done for total entries right now)
//TODO: Script engine ?
//TODO: Close Call multiple slices (buggy)
//TODO: Wav visualizer //TODO: Wav visualizer
//TODO: File browser view ? //TODO: File browser view ?
//TODO: Mousejack ? //TODO: Mousejack ?
//TODO: Move frequencykeypad from ui_receiver to ui_widget (used everywhere) //TODO: Move frequencykeypad from ui_receiver to ui_widget (used everywhere)
//TODO: ADS-B draw trajectory + GPS coordinates + scale, and playback //TODO: ADS-B draw trajectory + GPS coordinates + scale, and playback
//TODO: Analog TV tx with camcorder font character generator
//TODO: Make Whistle use proc_tones
//TODO: RDS multiple groups (sequence) //TODO: RDS multiple groups (sequence)
//TODO: Use ModalMessageView confirmation for TX ? //TODO: Use ModalMessageView confirmation for TX ?
//TODO: Show address/data bit fields in OOK TX
//TODO: Scan for OOK TX
//TODO: Check more OOK encoders
//TODO: Use msgpack for settings, lists... on sd card //TODO: Use msgpack for settings, lists... on sd card
//Multimon-style stuff: // Multimon-style stuff:
//TODO: AFSK receiver
//TODO: CTCSS detector //TODO: CTCSS detector
//TODO: DMR detector //TODO: DMR detector
//TODO: GSM channel detector //TODO: GSM channel detector
//TODO: SIGFOX RX/TX //TODO: SIGFOX RX/TX
//TODO: Bodet :)
//TODO: LCR full message former (see norm)
//TODO: AFSK NRZI
//TODO: Playdead amnesia and login //TODO: Playdead amnesia and login
//TODO: Setup: Play dead by default ? Enable/disable ? //TODO: Setup: Play dead by default ? Enable/disable ?
// Old or low-priority stuff:
//TODO: Bodet :)
//TODO: Analog TV tx with camcorder font character generator
//TODO: Show address/data bit fields in OOK TX
//TODO: Scan for OOK TX
//TODO: Script engine ?
//TODO: AFSK receiver
//TODO: Check more OOK encoders
//BUG (fixed ?): No audio in about when shown second time //BUG (fixed ?): No audio in about when shown second time
//TODO: Show MD5 mismatches for modules not found, etc... //TODO: Show MD5 mismatches for modules not found, etc...
//TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule //TODO: Module name/filename in modules.hpp to indicate requirement in case it's not found ui_loadmodule

View File

@ -1,371 +0,0 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 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_closecall.hpp"
#include "rtc_time.hpp"
#include "event_m0.hpp"
#include "portapack.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include <cstring>
#include <stdio.h>
#include <algorithm>
using namespace portapack;
namespace ui {
void CloseCallView::focus() {
field_frequency_min.focus();
}
CloseCallView::~CloseCallView() {
rtc_time::signal_tick_second -= signal_token_tick_second;
receiver_model.disable();
baseband::shutdown();
}
void CloseCallView::do_detection() {
uint8_t xmax = 0;
int16_t imax = 0;
uint16_t iraw = 0, c;
uint8_t power;
rf::Frequency freq_low, freq_high;
mean /= (CC_BIN_NB_NO_DC * slices_max);
// Find max value over threshold for all slices
for (c = 0; c < slices_max; c++) {
power = slicemax_pow[c];
if (power >= min_threshold) {
if ((power - min_threshold >= mean) && (power > xmax)) {
xmax = power;
imax = slicemax_idx[c] + (c * CC_BIN_NB);
iraw = slicemax_idx[c];
}
}
}
// Lock / release
if ((imax >= last_channel - 2) && (imax <= last_channel + 2) && (imax)) {
// Staying around the same frequency
if (detect_counter >= (5 / slices_max)) { // Todo: ugly, change. Should depend on refresh rate.
if ((imax != locked_imax) || (!locked)) {
std::string finalstr;
if (locked) {
// Already locked, adjust
if (weight < 16) {
frequency_acc += (slice_frequency + (CC_BIN_WIDTH * (imax - 120))); // Average
weight++;
}
} else {
// Lost, locking
frequency_acc = slice_frequency + (CC_BIN_WIDTH * (imax - 120)); // Init
if ((frequency_acc >= f_min) && (frequency_acc <= f_max)) {
text_infos.set("Locked !");
big_display.set_style(&style_locked);
// Approximation/error display
freq_low = (frequency_acc - 4883) / 1000;
freq_high = (frequency_acc + 4883) / 1000;
finalstr = "~9.8kHz: " + to_string_dec_uint(freq_low / 1000) + "." + to_string_dec_uint(freq_low % 1000);
finalstr += "/" + to_string_dec_uint(freq_high / 1000) + "." + to_string_dec_uint(freq_high % 1000);
text_precision.set(finalstr);
locked = true;
weight = 1;
locked_imax = imax;
}
}
resolved_frequency = frequency_acc / weight;
big_display.set(resolved_frequency);
}
release_counter = 0;
} else {
detect_counter++;
}
} else {
detect_counter = 0;
if (locked) {
if (release_counter == 6) {
locked = false;
text_infos.set("Lost");
big_display.set_style(&style_grey);
//big_display.set(resolved_frequency);
} else {
release_counter++;
}
}
}
last_channel = imax;
scan_counter++;
portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::black());
if (iraw < 120)
iraw += 2;
else
iraw -= 0;
last_pos = (ui::Coord)iraw;
portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::red());
}
void CloseCallView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
uint8_t xmax = 0;
int16_t imax = 0;
uint8_t power;
size_t i, m;
std::array<Color, 240> pixel_row;
baseband::spectrum_streaming_stop();
// Draw spectrum line (for debug)
for (i = 0; i < 120; i++) {
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[134 + i]]; // 134~253 in 0~119
pixel_row[i] = pixel_color;
}
for (i = 120; i < 240; i++) {
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[i - 118]]; // 2~121 in 120~239
pixel_row[i] = pixel_color;
}
display.draw_pixels(
{ { 0, 96 + slices_counter * 4 }, { pixel_row.size(), 1 } },
pixel_row
);
// Find max for this slice:
// Check if left of slice needs to be trimmed (masked)
//if (slices_counter == 0)
// i = slice_trim;
//else
i = 0;
for ( ; i < 120; i++) {
power = spectrum.db[134 + i];
mean += power;
if (power > xmax) {
xmax = power;
imax = i - 2;
}
}
// Check if right of slice needs to be trimmed (masked)
//if (slices_counter == (slices_max - 1))
// m = 240 - slice_trim;
//else
m = 240;
for (i = 120; i < m; i++) {
power = spectrum.db[i - 118];
mean += power;
if (power > xmax) {
xmax = power;
imax = i + 2;
}
}
slicemax_pow[slices_counter] = xmax;
slicemax_idx[slices_counter] = imax;
// Slice update
if (slicing) {
if (slices_counter >= (slices_max - 1)) {
do_detection();
mean = 0;
slices_counter = 0;
} else {
slices_counter++;
}
slice_frequency = slice_start + (slices_counter * CC_SLICE_WIDTH);
receiver_model.set_tuning_frequency(slice_frequency);
} else {
do_detection();
}
baseband::spectrum_streaming_start();
}
void CloseCallView::on_show() {
baseband::spectrum_streaming_start();
}
void CloseCallView::on_hide() {
baseband::spectrum_streaming_stop();
}
void CloseCallView::on_range_changed() {
rf::Frequency slices_span;
rf::Frequency slice_width;
int64_t offset;
f_max = field_frequency_max.value();
f_min = field_frequency_min.value();
scan_span = abs(f_max - f_min);
if (scan_span > CC_SLICE_WIDTH) {
// ex: 100~115 (15): 102.5(97.5~107.5) -> 112.5(107.5~117.5) = 2.5 lost left and right
slices_max = (scan_span + CC_SLICE_WIDTH - 1) / CC_SLICE_WIDTH;
slices_span = slices_max * CC_SLICE_WIDTH;
offset = ((scan_span - slices_span) / 2) + (CC_SLICE_WIDTH / 2);
slice_start = std::min(f_min, f_max) + offset;
slice_trim = 0;
slicing = true;
// Todo: trims
} else {
slice_frequency = (f_max + f_min) / 2;
receiver_model.set_tuning_frequency(slice_frequency);
slice_width = abs(f_max - f_min);
slice_trim = (240 - (slice_width * 240 / CC_SLICE_WIDTH)) / 2;
portapack::display.fill_rectangle({0, 97, 240, 4}, Color::black());
portapack::display.fill_rectangle({0, 97, slice_trim, 4}, Color::orange());
portapack::display.fill_rectangle({240 - slice_trim, 97, slice_trim, 4}, Color::orange());
slices_max = 1;
slices_counter = 0;
slicing = false;
}
/*
f_min = field_frequency_min.value();
scan_span = 3000000;
slice_frequency = (f_min + 1500000);
slice_start = slice_frequency;
receiver_model.set_tuning_frequency(slice_frequency);
slice_trim = 0;
slices_max = 1;
slices_counter = 0;
slicing = false;
field_frequency_max.set_value(f_min + 3000000);
*/
text_slices.set(to_string_dec_int(slices_max));
slices_counter = 0;
}
void CloseCallView::on_lna_changed(int32_t v_db) {
receiver_model.set_lna(v_db);
}
void CloseCallView::on_vga_changed(int32_t v_db) {
receiver_model.set_vga(v_db);
}
void CloseCallView::on_tick_second() {
// Update scan rate indication
text_rate.set(to_string_dec_uint(scan_counter, 3));
scan_counter = 0;
}
CloseCallView::CloseCallView(
NavigationView& nav
)
{
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({
&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_slices,
&text_rate,
&text_mhz,
&text_infos,
&text_precision,
&text_debug,
&big_display,
&button_exit
});
text_slices.set_style(&style_grey);
text_rate.set_style(&style_grey);
text_mhz.set_style(&style_grey);
big_display.set_style(&style_grey);
baseband::set_spectrum(CC_SLICE_WIDTH, 32);
field_threshold.set_value(80);
field_threshold.on_change = [this](int32_t v) {
min_threshold = v;
};
field_frequency_min.set_value(receiver_model.tuning_frequency());
field_frequency_min.set_step(100000);
field_frequency_min.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_min.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
//this->on_range_changed();
this->field_frequency_min.set_value(f);
};
};
field_frequency_max.set_value(receiver_model.tuning_frequency() + 2000000);
field_frequency_max.set_step(100000);
field_frequency_max.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_max.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
//this->on_range_changed();
this->field_frequency_max.set_value(f);
};
};
field_lna.set_value(receiver_model.lna());
field_lna.on_change = [this](int32_t v) {
this->on_lna_changed(v);
};
field_vga.set_value(receiver_model.vga());
field_vga.on_change = [this](int32_t v_db) {
this->on_vga_changed(v_db);
};
on_range_changed();
button_exit.on_select = [&nav](Button&){
nav.pop();
};
signal_token_tick_second = rtc_time::signal_tick_second += [this]() {
this->on_tick_second();
};
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(CC_SLICE_WIDTH);
receiver_model.set_baseband_bandwidth(2500000);
receiver_model.enable();
}
} /* namespace ui */

View File

@ -1,192 +0,0 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 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 "receiver_model.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
namespace ui {
#define CC_SLICE_WIDTH 2500000 // Radio bandwidth
#define CC_BIN_NB 256 // Total power bins (skip 4 at center, 2*6 on sides)
#define CC_BIN_NB_NO_DC (CC_BIN_NB - 16)
#define CC_BIN_WIDTH (CC_SLICE_WIDTH / CC_BIN_NB)
class CloseCallView : public View {
public:
CloseCallView(NavigationView& nav);
~CloseCallView();
CloseCallView(const CloseCallView&) = delete;
CloseCallView(CloseCallView&&) = delete;
CloseCallView& operator=(const CloseCallView&) = delete;
CloseCallView& operator=(CloseCallView&&) = delete;
void on_show() override;
void on_hide() override;
void focus() override;
std::string title() const override { return "Close Call"; };
private:
const Style style_grey { // For labels and lost signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_locked {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
rf::Frequency f_min { 0 }, f_max { 0 };
Coord last_pos { 0 };
ChannelSpectrumFIFO* fifo { nullptr };
uint8_t detect_counter { 0 }, release_counter { 0 };
uint8_t slice_trim { 0 };
uint32_t mean { 0 };
uint32_t min_threshold { 80 }; // Todo: Put this in persistent / settings
rf::Frequency slice_start { 0 };
rf::Frequency slice_frequency { 0 };
uint8_t slices_max { 0 };
uint8_t slices_counter { 0 };
int16_t last_channel { 0 };
uint32_t weight { 0 };
int64_t frequency_acc { 0 };
rf::Frequency scan_span { 0 }, resolved_frequency { 0 };
uint16_t locked_imax { 0 };
uint8_t slicemax_pow[32]; // Todo: Cap max slices !
int16_t slicemax_idx[32];
uint8_t scan_counter { 0 };
SignalToken signal_token_tick_second { };
bool ignore { true };
bool slicing { false };
bool locked { false };
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void on_range_changed();
void do_detection();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
void on_tick_second();
/* |012345678901234567890123456789|
* | Min: Max: LNA VGA |
* | 0000.0000 0000.0000 00 00 |
* | Threshold: 000 |
* | Slices: 00 Rate: 00Hz |
* |
* */
Labels labels {
{ { 1 * 8, 0 }, "Min: Max: LNA VGA", Color::light_grey() },
{ { 1 * 8, 4 * 8 }, "Threshold:", Color::light_grey() },
{ { 1 * 8, 6 * 8 }, "Slices: Rate: Hz", Color::light_grey() }
};
NumberField field_threshold {
{ 12 * 8, 2 * 16 },
3,
{ 5, 250 },
5,
' '
};
FrequencyField field_frequency_min {
{ 1 * 8, 1 * 16 },
};
FrequencyField field_frequency_max {
{ 11 * 8, 1 * 16 },
};
LNAGainField field_lna {
{ 22 * 8, 1 * 16 }
};
VGAGainField field_vga {
{ 26 * 8, 1 * 16 }
};
Text text_slices {
{ 9 * 8, 3 * 16, 2 * 8, 16 },
"--"
};
Text text_rate {
{ 24 * 8, 3 * 16, 2 * 8, 16 },
"--"
};
Text text_infos {
{ 1 * 8, 6 * 16, 28 * 8, 16 },
"..."
};
BigFrequency big_display {
{ 4, 8 * 16, 28 * 8, 32 },
0
};
Text text_mhz {
{ 26 * 8, 12 * 16 - 4, 3 * 8, 16 },
"MHz"
};
Text text_precision {
{ 1 * 8, 13 * 16, 28 * 8, 16 },
"..."
};
Text text_debug {
{ 1 * 8, 14 * 16, 28 * 8, 16 },
"DEBUG: -"
};
Button button_exit {
{ 92, 264, 56, 32 },
"Exit"
};
MessageHandlerRegistration message_handler_spectrum_config {
Message::ID::ChannelSpectrumConfig,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
this->fifo = message.fifo;
}
};
MessageHandlerRegistration message_handler_frame_sync {
Message::ID::DisplayFrameSync,
[this](const Message* const) {
if( this->fifo ) {
ChannelSpectrum channel_spectrum;
while( fifo->out(channel_spectrum) ) {
this->on_channel_spectrum(channel_spectrum);
}
}
}
};
};
} /* namespace ui */

View File

@ -90,7 +90,8 @@ private:
VuMeter vumeter { VuMeter vumeter {
{ 1 * 8, 2 * 8, 5 * 8, 26 * 8 }, { 1 * 8, 2 * 8, 5 * 8, 26 * 8 },
16 16,
false
}; };
OptionsField options_gain { OptionsField options_gain {

View File

@ -32,7 +32,6 @@
#include "ui_about.hpp" #include "ui_about.hpp"
#include "ui_adsbtx.hpp" #include "ui_adsbtx.hpp"
#include "ui_bht_tx.hpp" #include "ui_bht_tx.hpp"
#include "ui_closecall.hpp"
#include "ui_coasterp.hpp" #include "ui_coasterp.hpp"
#include "ui_cw.hpp" #include "ui_cw.hpp"
#include "ui_debug.hpp" #include "ui_debug.hpp"
@ -48,6 +47,7 @@
#include "ui_pocsag_tx.hpp" #include "ui_pocsag_tx.hpp"
#include "ui_rds.hpp" #include "ui_rds.hpp"
#include "ui_sd_wipe.hpp" #include "ui_sd_wipe.hpp"
#include "ui_scanner.hpp"
#include "ui_setup.hpp" #include "ui_setup.hpp"
#include "ui_soundboard.hpp" #include "ui_soundboard.hpp"
#include "ui_sstvtx.hpp" #include "ui_sstvtx.hpp"
@ -293,7 +293,7 @@ TranspondersMenuView::TranspondersMenuView(NavigationView& nav) {
ReceiverMenuView::ReceiverMenuView(NavigationView& nav) { ReceiverMenuView::ReceiverMenuView(NavigationView& nav) {
add_items<6>({ { add_items<6>({ {
// { "AFSK", ui::Color::grey(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } }, // AFSKRXView // { "AFSK", ui::Color::grey(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } }, // AFSKRXView
{ "Audio", ui::Color::green(), nullptr, [&nav](){ nav.push<AnalogAudioView>(); } }, { "Audio", ui::Color::green(), nullptr, [&nav](){ nav.push<AnalogAudioView>(false); } },
{ "CCIR", ui::Color::grey(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } }, { "CCIR", ui::Color::grey(), nullptr, [&nav](){ nav.push<NotImplementedView>(); } },
{ "Nordic/BTLE", ui::Color::grey(), &bitmap_icon_nordic, [&nav](){ nav.push<NotImplementedView>(); } }, { "Nordic/BTLE", ui::Color::grey(), &bitmap_icon_nordic, [&nav](){ nav.push<NotImplementedView>(); } },
{ "POCSAG", ui::Color::cyan(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } }, { "POCSAG", ui::Color::cyan(), &bitmap_icon_pocsag, [&nav](){ nav.push<POCSAGAppView>(); } },
@ -365,7 +365,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav) {
{ "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push<TransmitterAudioMenuView>(); } }, { "Audio transmitters", ui::Color::green(), &bitmap_icon_audiotx, [&nav](){ nav.push<TransmitterAudioMenuView>(); } },
{ "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push<TransmitterCodedMenuView>(); } }, { "Code transmitters", ui::Color::green(), &bitmap_icon_codetx, [&nav](){ nav.push<TransmitterCodedMenuView>(); } },
{ "SSTV transmitter", ui::Color::dark_green(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } }, { "SSTV transmitter", ui::Color::dark_green(), &bitmap_icon_sstv, [&nav](){ nav.push<SSTVTXView>(); } },
{ "Close Call", ui::Color::cyan(), &bitmap_icon_closecall, [&nav](){ nav.push<CloseCallView>(); } }, { "Scanner/search", ui::Color::cyan(), &bitmap_icon_closecall, [&nav](){ nav.push<ScannerView>(); } },
{ "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } }, { "Jammer", ui::Color::orange(),&bitmap_icon_jammer, [&nav](){ nav.push<JammerView>(); } },
{ "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push<UtilitiesView>(); } }, { "Utilities", ui::Color::purple(),&bitmap_icon_utilities, [&nav](){ nav.push<UtilitiesView>(); } },
{ "Setup", ui::Color::white(), &bitmap_icon_setup, [&nav](){ nav.push<SetupMenuView>(); } }, { "Setup", ui::Color::white(), &bitmap_icon_setup, [&nav](){ nav.push<SetupMenuView>(); } },

View File

@ -0,0 +1,417 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 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"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace ui {
template<>
void RecentEntriesTable<ScannerRecentEntries>::draw(
const Entry& entry,
const Rect& target_rect,
Painter& painter,
const Style& style
) {
std::string str_duration = "";
if (entry.duration < 600)
str_duration = to_string_dec_uint(entry.duration / 10) + "." + to_string_dec_uint(entry.duration % 10) + "s";
else
str_duration = to_string_dec_uint(entry.duration / 600) + "m" + to_string_dec_uint((entry.duration / 10) % 60) + "s";
str_duration.resize(target_rect.width() / 8, ' ');
painter.draw_string(target_rect.location(), style, to_string_short_freq(entry.frequency) + " " + entry.time + " " + str_duration);
}
void ScannerView::focus() {
field_frequency_min.focus();
}
ScannerView::~ScannerView() {
receiver_model.disable();
baseband::shutdown();
}
void ScannerView::do_detection() {
uint8_t power_max = 0;
int32_t bin_max = -1;
uint32_t bin_max_pixel = 0;
uint8_t power;
rtc::RTC datetime;
std::string str_approx, str_timestamp;
mean_power = mean_acc / (SCAN_BIN_NB_NO_DC * slices_nb);
mean_acc = 0;
overall_power_max = 0;
// Find max power over threshold for all slices
for (size_t slice = 0; slice < slices_nb; slice++) {
power = slices[slice].max_power;
if (power > overall_power_max)
overall_power_max = power;
if ((power >= mean_power + power_threshold) && (power > power_max)) {
power_max = power;
bin_max_pixel = slices[slice].max_index;
bin_max = bin_max_pixel + (slice * SCAN_BIN_NB);
}
}
// Lock / release
if ((bin_max >= last_bin - 2) && (bin_max <= last_bin + 2) && (bin_max > -1)) {
// Staying around the same bin
if (detect_timer >= DETECT_DELAY) {
if ((bin_max != locked_bin) || (!locked)) {
if (!locked) {
resolved_frequency = slices[slice_counter].center_frequency + (SCAN_BIN_WIDTH * (bin_max - 120)); // Init
// Check range
if ((resolved_frequency >= f_min) && (resolved_frequency <= f_max)) {
duration = 0;
auto& entry = ::on_packet(recent, resolved_frequency);
rtcGetTime(&RTCD1, &datetime);
str_timestamp = to_string_dec_uint(datetime.hour(), 2, '0') + ":" +
to_string_dec_uint(datetime.minute(), 2, '0') + ":" +
to_string_dec_uint(datetime.second(), 2, '0');
entry.set_time(str_timestamp);
recent_entries_view.set_dirty();
text_infos.set("Locked ! ");
big_display.set_style(&style_locked);
// Approximation/error display
str_approx = "." + to_string_dec_uint(((resolved_frequency - 4883) / 1000) % 10000);
str_approx += "~." + to_string_dec_uint(((resolved_frequency + 4883) / 1000) % 10000);
text_approx.set(str_approx);
locked = true;
locked_bin = bin_max;
// TODO
/*nav_.pop();
receiver_model.disable();
baseband::shutdown();
nav_.pop();*/
/*if (options_goto.selected_index() == 1)
nav_.push<AnalogAudioView>(false);
else if (options_goto.selected_index() == 2)
nav_.push<POCSAGAppView>();
*/
}
}
big_display.set(resolved_frequency);
}
}
release_timer = 0;
} else {
detect_timer = 0;
if (locked) {
if (release_timer >= RELEASE_DELAY) {
locked = false;
auto& entry = ::on_packet(recent, resolved_frequency);
entry.set_duration(duration);
recent_entries_view.set_dirty();
text_infos.set("Listening");
big_display.set_style(&style_grey);
}
}
}
last_bin = bin_max;
scan_counter++;
// Refresh red tick
portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::black());
if (bin_max > -1) {
if (bin_max_pixel < 120)
bin_max_pixel += 2;
//else
// bin_max_pixel -= 0;
last_pos = (ui::Coord)bin_max_pixel;
portapack::display.fill_rectangle({last_pos, 90, 1, 6}, Color::red());
}
}
void ScannerView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
uint8_t power_max = 0;
int16_t bin_max = 0;
uint8_t power;
size_t bin;
std::array<Color, 240> pixel_row;
baseband::spectrum_streaming_stop();
// Draw spectrum line
for (bin = 0; bin < 120; bin++) {
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[134 + bin]]; // 134~253 in 0~119
pixel_row[bin] = pixel_color;
}
for (bin = 120; bin < 240; bin++) {
const auto pixel_color = spectrum_rgb3_lut[spectrum.db[bin - 118]]; // 2~121 in 120~239
pixel_row[bin] = pixel_color;
}
display.draw_pixels(
{ { 0, 88 + slice_counter * 2 }, { pixel_row.size(), 1 } },
pixel_row
);
// Find max power for this slice
for (bin = 0 ; bin < 120; bin++) {
power = spectrum.db[134 + bin];
mean_acc += power;
if (power > power_max) {
power_max = power;
bin_max = bin - 2;
}
}
for (bin = 120; bin < 240; bin++) {
power = spectrum.db[bin - 118];
mean_acc += power;
if (power > power_max) {
power_max = power;
bin_max = bin + 2;
}
}
slices[slice_counter].max_power = power_max;
slices[slice_counter].max_index = bin_max;
// Slice update
if (slices_nb > 1) {
slice_counter++;
if (slice_counter >= slices_nb) {
do_detection();
slice_counter = 0;
}
receiver_model.set_tuning_frequency(slices[slice_counter].center_frequency);
} else {
do_detection();
}
baseband::spectrum_streaming_start();
}
void ScannerView::on_show() {
baseband::spectrum_streaming_start();
}
void ScannerView::on_hide() {
baseband::spectrum_streaming_stop();
}
void ScannerView::on_range_changed() {
rf::Frequency slices_span, center_frequency;
int64_t offset;
size_t slice;
f_min = field_frequency_min.value();
f_max = field_frequency_max.value();
scan_span = abs(f_max - f_min);
if (scan_span > SCAN_SLICE_WIDTH) {
// ex: 100M~115M (15M span):
// slices_nb = (115M-100M)/2.5M = 6
slices_nb = (scan_span + SCAN_SLICE_WIDTH - 1) / SCAN_SLICE_WIDTH;
if (slices_nb > 32) {
text_slices.set("!!");
slices_nb = 32;
} else {
text_slices.set(to_string_dec_uint(slices_nb));
}
// slices_span = 6 * 2.5M = 15M
slices_span = slices_nb * SCAN_SLICE_WIDTH;
// offset = 0 + 2.5/2 = 1.25M
offset = ((scan_span - slices_span) / 2) + (SCAN_SLICE_WIDTH / 2);
// slice_start = 100M + 1.25M = 101.25M
center_frequency = std::min(f_min, f_max) + offset;
for (slice = 0; slice < slices_nb; slice++) {
slices[slice].center_frequency = center_frequency;
center_frequency += SCAN_SLICE_WIDTH;
}
} else {
slices[0].center_frequency = (f_max + f_min) / 2;
receiver_model.set_tuning_frequency(slices[0].center_frequency);
slices_nb = 1;
text_slices.set("1");
}
slice_counter = 0;
}
void ScannerView::on_lna_changed(int32_t v_db) {
receiver_model.set_lna(v_db);
}
void ScannerView::on_vga_changed(int32_t v_db) {
receiver_model.set_vga(v_db);
}
void ScannerView::do_timers() {
if (timing_div >= 60) {
// ~1Hz
timing_div = 0;
// Update scan rate
text_rate.set(to_string_dec_uint(scan_counter, 3));
scan_counter = 0;
}
if (timing_div % 12 == 0) {
// ~5Hz
// Update power levels
text_mean.set(to_string_dec_uint(mean_power, 3));
vu_max.set_value(overall_power_max);
vu_max.set_mark(mean_power + power_threshold);
}
if (timing_div % 6 == 0) {
// ~10Hz
// Update timing indicator
if (locked) {
progress_timers.set_max(RELEASE_DELAY);
progress_timers.set_value(RELEASE_DELAY - release_timer);
} else {
progress_timers.set_max(DETECT_DELAY);
progress_timers.set_value(detect_timer);
}
// Increment timers
if (detect_timer < DETECT_DELAY) detect_timer++;
if (release_timer < RELEASE_DELAY) release_timer++;
if (locked) duration++;
}
timing_div++;
}
ScannerView::ScannerView(
NavigationView& nav
) : nav_ (nav)
{
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
add_children({
&labels,
&field_frequency_min,
&field_frequency_max,
&field_lna,
&field_vga,
&field_threshold,
&text_mean,
&text_slices,
&text_rate,
&text_infos,
&vu_max,
&progress_timers,
//&check_goto,
//&options_goto,
&big_display,
&recent_entries_view,
&text_approx
});
recent_entries_view.set_parent_rect({ 0, 28 * 8, 240, 12 * 8 });
text_mean.set_style(&style_grey);
text_slices.set_style(&style_grey);
text_rate.set_style(&style_grey);
progress_timers.set_style(&style_grey);
big_display.set_style(&style_grey);
baseband::set_spectrum(SCAN_SLICE_WIDTH, 32);
//options_goto.set_selected_index(0); // Nothing
field_threshold.set_value(80);
field_threshold.on_change = [this](int32_t value) {
power_threshold = value;
};
field_frequency_min.set_value(receiver_model.tuning_frequency() - 1000000);
field_frequency_min.set_step(100000);
field_frequency_min.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_min.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_min.set_value(f);
};
};
field_frequency_max.set_value(receiver_model.tuning_frequency() + 1000000);
field_frequency_max.set_step(100000);
field_frequency_max.on_change = [this](rf::Frequency) {
this->on_range_changed();
};
field_frequency_max.on_edit = [this, &nav]() {
auto new_view = nav.push<FrequencyKeypadView>(receiver_model.tuning_frequency());
new_view->on_changed = [this](rf::Frequency f) {
this->field_frequency_max.set_value(f);
};
};
field_lna.set_value(receiver_model.lna());
field_lna.on_change = [this](int32_t v) {
this->on_lna_changed(v);
};
field_vga.set_value(receiver_model.vga());
field_vga.on_change = [this](int32_t v_db) {
this->on_vga_changed(v_db);
};
progress_timers.set_max(DETECT_DELAY);
on_range_changed();
receiver_model.set_modulation(ReceiverModel::Mode::SpectrumAnalysis);
receiver_model.set_sampling_rate(SCAN_SLICE_WIDTH);
receiver_model.set_baseband_bandwidth(2500000);
receiver_model.enable();
}
} /* namespace ui */

View File

@ -0,0 +1,249 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 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 "receiver_model.hpp"
#include "spectrum_color_lut.hpp"
#include "ui_receiver.hpp"
#include "ui_font_fixed_8x16.hpp"
#include "recent_entries.hpp"
namespace ui {
#define SCAN_SLICE_WIDTH 2500000 // Scan slice bandwidth
#define SCAN_BIN_NB 256 // FFT power bins (skip 4 at center, 2*6 on sides)
#define SCAN_BIN_NB_NO_DC (SCAN_BIN_NB - 16) // Bins after trimming
#define SCAN_BIN_WIDTH (SCAN_SLICE_WIDTH / SCAN_BIN_NB)
#define DETECT_DELAY 5 // In 100ms units
#define RELEASE_DELAY 6
struct ScannerRecentEntry {
using Key = rf::Frequency;
static constexpr Key invalid_key = 0xffffffff;
rf::Frequency frequency;
uint32_t duration { 0 }; // In 100ms units
std::string time { "" };
ScannerRecentEntry(
) : ScannerRecentEntry { 0 }
{
}
ScannerRecentEntry(
const rf::Frequency frequency
) : frequency { frequency }
{
}
Key key() const {
return frequency;
}
void set_time(std::string& new_time) {
time = new_time;
}
void set_duration(uint32_t new_duration) {
duration = new_duration;
}
};
using ScannerRecentEntries = RecentEntries<ScannerRecentEntry>;
class ScannerView : public View {
public:
ScannerView(NavigationView& nav);
~ScannerView();
ScannerView(const ScannerView&) = delete;
ScannerView(ScannerView&&) = delete;
ScannerView& operator=(const ScannerView&) = delete;
ScannerView& operator=(ScannerView&&) = delete;
void on_show() override;
void on_hide() override;
void focus() override;
std::string title() const override { return "Close Call"; };
private:
NavigationView& nav_;
const Style style_grey { // For informations and lost signal
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::grey(),
};
const Style style_locked {
.font = font::fixed_8x16,
.background = Color::black(),
.foreground = Color::green(),
};
struct slice_t {
rf::Frequency center_frequency;
uint8_t max_power;
int16_t max_index;
uint8_t power;
int16_t index;
} slices[32];
ChannelSpectrumFIFO* fifo { nullptr };
rf::Frequency f_min { 0 }, f_max { 0 };
uint8_t detect_timer { 0 }, release_timer { 0 }, timing_div { 0 };
uint8_t overall_power_max { 0 };
uint32_t mean_power { 0 }, mean_acc { 0 };
uint32_t duration { 0 };
uint32_t power_threshold { 80 }; // Todo: Put this in persistent / settings
rf::Frequency slice_start { 0 };
uint8_t slices_nb { 0 };
uint8_t slice_counter { 0 };
int16_t last_bin { 0 };
Coord last_pos { 0 };
rf::Frequency scan_span { 0 }, resolved_frequency { 0 };
uint16_t locked_bin { 0 };
uint8_t scan_counter { 0 };
bool locked { false };
void on_channel_spectrum(const ChannelSpectrum& spectrum);
void on_range_changed();
void do_detection();
void on_lna_changed(int32_t v_db);
void on_vga_changed(int32_t v_db);
void do_timers();
const RecentEntriesColumns columns { {
{ "Frequency", 9 },
{ "Time", 8 },
{ "Duration", 11 }
} };
ScannerRecentEntries recent { };
RecentEntriesView<RecentEntries<ScannerRecentEntry>> recent_entries_view { columns, recent };
Labels labels {
{ { 1 * 8, 0 }, "Min: Max: LNA VGA", Color::light_grey() },
{ { 1 * 8, 4 * 8 }, "Trig: /255 Mean: /255", Color::light_grey() },
{ { 1 * 8, 6 * 8 }, "Slices: /32 Rate: Hz", Color::light_grey() },
{ { 6 * 8, 10 * 8 }, "Timer Status", Color::light_grey() },
{ { 1 * 8, 25 * 8 }, "+/-4.9kHz:", Color::light_grey() },
{ { 26 * 8, 25 * 8 }, "MHz", Color::light_grey() }
};
NumberField field_threshold {
{ 6 * 8, 2 * 16 },
3,
{ 5, 255 },
5,
' '
};
FrequencyField field_frequency_min {
{ 1 * 8, 1 * 16 },
};
FrequencyField field_frequency_max {
{ 11 * 8, 1 * 16 },
};
LNAGainField field_lna {
{ 22 * 8, 1 * 16 }
};
VGAGainField field_vga {
{ 26 * 8, 1 * 16 }
};
Text text_mean {
{ 22 * 8, 2 * 16, 3 * 8, 16 },
"---"
};
Text text_slices {
{ 8 * 8, 3 * 16, 2 * 8, 16 },
"--"
};
Text text_rate {
{ 24 * 8, 3 * 16, 3 * 8, 16 },
"---"
};
VuMeter vu_max {
{ 1 * 8, 11 * 8 - 4, 3 * 8, 48 },
16,
false
};
ProgressBar progress_timers {
{ 6 * 8, 12 * 8, 5 * 8, 16 }
};
Text text_infos {
{ 13 * 8, 12 * 8, 15 * 8, 16 },
"Listening"
};
Checkbox check_goto {
{ 6 * 8, 15 * 8 },
8,
"On lock:",
true
};
OptionsField options_goto {
{ 17 * 8, 15 * 8 },
7,
{
{ "Nothing", 0 },
{ "NFM RX ", 1 },
{ "POCSAG ", 2 }
}
};
BigFrequency big_display {
{ 4, 9 * 16, 28 * 8, 52 },
0
};
Text text_approx {
{ 11 * 8, 25 * 8, 11 * 8, 16 },
"..."
};
MessageHandlerRegistration message_handler_spectrum_config {
Message::ID::ChannelSpectrumConfig,
[this](const Message* const p) {
const auto message = *reinterpret_cast<const ChannelSpectrumConfigMessage*>(p);
this->fifo = message.fifo;
}
};
MessageHandlerRegistration message_handler_frame_sync {
Message::ID::DisplayFrameSync,
[this](const Message* const) {
if( this->fifo ) {
ChannelSpectrum channel_spectrum;
while( fifo->out(channel_spectrum) ) {
this->on_channel_spectrum(channel_spectrum);
}
}
this->do_timers();
}
};
};
} /* namespace ui */

View File

@ -76,8 +76,6 @@ void AudioOutput::write(
void AudioOutput::on_block( void AudioOutput::on_block(
const buffer_f32_t& audio const buffer_f32_t& audio
) { ) {
bool audio_present;
if (do_processing) { if (do_processing) {
const auto audio_present_now = squelch.execute(audio); const auto audio_present_now = squelch.execute(audio);
@ -98,6 +96,10 @@ void AudioOutput::on_block(
fill_audio_buffer(audio, audio_present); fill_audio_buffer(audio, audio_present);
} }
bool AudioOutput::is_squelched() {
return ~audio_present;
}
void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo) { void AudioOutput::fill_audio_buffer(const buffer_f32_t& audio, const bool send_to_fifo) {
std::array<int16_t, 32> audio_int; std::array<int16_t, 32> audio_int;

View File

@ -51,6 +51,8 @@ public:
void set_stream(std::unique_ptr<StreamInput> new_stream) { void set_stream(std::unique_ptr<StreamInput> new_stream) {
stream = std::move(new_stream); stream = std::move(new_stream);
} }
bool is_squelched();
private: private:
static constexpr float k = 32768.0f; static constexpr float k = 32768.0f;
@ -68,6 +70,7 @@ private:
uint64_t audio_present_history = 0; uint64_t audio_present_history = 0;
bool audio_present = false;
bool do_processing = true; bool do_processing = true;
void on_block(const buffer_f32_t& audio); void on_block(const buffer_f32_t& audio);

View File

@ -21,6 +21,7 @@
*/ */
#include "proc_nfm_audio.hpp" #include "proc_nfm_audio.hpp"
#include "portapack_shared_memory.hpp"
#include "event_m4.hpp" #include "event_m4.hpp"
@ -28,6 +29,8 @@
#include <cstddef> #include <cstddef>
void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) { void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
bool new_state;
if( !configured ) { if( !configured ) {
return; return;
} }
@ -56,6 +59,13 @@ void NarrowbandFMAudio::execute(const buffer_c8_t& buffer) {
} }
audio_output.write(pwmrssi_audio_buffer); audio_output.write(pwmrssi_audio_buffer);
new_state = audio_output.is_squelched();
if (new_state && !old_state)
shared_memory.application_queue.push(sig_message);
old_state = new_state;
} }
} }
@ -102,7 +112,8 @@ void NarrowbandFMAudio::configure(const NBFMConfigureMessage& message) {
channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs; channel_filter_pass_f = message.channel_filter.pass_frequency_normalized * channel_filter_input_fs;
channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs; channel_filter_stop_f = message.channel_filter.stop_frequency_normalized * channel_filter_input_fs;
channel_spectrum.set_decimation_factor(std::floor(channel_filter_output_fs / (channel_filter_pass_f + channel_filter_stop_f))); channel_spectrum.set_decimation_factor(std::floor(channel_filter_output_fs / (channel_filter_pass_f + channel_filter_stop_f)));
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config); // , 0.8f // TODO: Configurable squelch threshold
audio_output.configure(message.audio_hpf_config, message.audio_deemph_config, 0.8f);
synth_acc = 0; synth_acc = 0;

View File

@ -73,6 +73,7 @@ private:
dsp::demodulate::FM demod { }; dsp::demodulate::FM demod { };
AudioOutput audio_output { }; AudioOutput audio_output { };
bool old_state { };
SpectrumCollector channel_spectrum { }; SpectrumCollector channel_spectrum { };
@ -85,6 +86,8 @@ private:
void pwmrssi_config(const PWMRSSIConfigureMessage& message); void pwmrssi_config(const PWMRSSIConfigureMessage& message);
void configure(const NBFMConfigureMessage& message); void configure(const NBFMConfigureMessage& message);
void capture_config(const CaptureConfigMessage& message); void capture_config(const CaptureConfigMessage& message);
RequestSignalMessage sig_message { RequestSignalMessage::Signal::Squelched };
}; };
#endif/*__PROC_NFM_AUDIO_H__*/ #endif/*__PROC_NFM_AUDIO_H__*/

View File

@ -821,11 +821,13 @@ public:
}; };
// TODO: use streaming buffer instead // TODO: use streaming buffer instead
// TODO: rename (not only used for requests)
class RequestSignalMessage : public Message { class RequestSignalMessage : public Message {
public: public:
enum class Signal : char { enum class Signal : char {
FillRequest = 1, FillRequest = 1,
BeepRequest = 2, BeepRequest = 2,
Squelched = 3
}; };
constexpr RequestSignalMessage( constexpr RequestSignalMessage(

View File

@ -457,12 +457,16 @@ ProgressBar::ProgressBar(
} }
void ProgressBar::set_max(const uint32_t max) { void ProgressBar::set_max(const uint32_t max) {
if (max == _max) return;
_value = 0; _value = 0;
_max = max; _max = max;
set_dirty(); set_dirty();
} }
void ProgressBar::set_value(const uint32_t value) { void ProgressBar::set_value(const uint32_t value) {
if (value == _value) return;
if (value > _max) if (value > _max)
_value = _max; _value = _max;
else else
@ -1450,37 +1454,45 @@ void Waveform::paint(Painter& painter) {
VuMeter::VuMeter( VuMeter::VuMeter(
Rect parent_rect, Rect parent_rect,
uint32_t LEDs uint32_t LEDs,
bool show_max
) : Widget { parent_rect }, ) : Widget { parent_rect },
LEDs_ { LEDs }, LEDs_ { LEDs },
height { parent_rect.size().height() } show_max_ { show_max }
{ {
//set_focusable(false); //set_focusable(false);
LED_height = height / LEDs; LED_height = parent_rect.size().height() / LEDs;
split = 256 / LEDs; split = 256 / LEDs;
} }
void VuMeter::set_value(const uint8_t new_value) { void VuMeter::set_value(const uint32_t new_value) {
value_ = new_value; if ((new_value != value_) && (new_value < 256)) {
set_dirty(); value_ = new_value;
set_dirty();
}
} }
void VuMeter::set_mark(const uint8_t new_mark) { void VuMeter::set_mark(const uint32_t new_mark) {
if (new_mark != mark) { if ((new_mark != mark) && (new_mark < 256)) {
mark = new_mark; mark = new_mark;
set_dirty(); set_dirty();
} }
} }
void VuMeter::paint(Painter& painter) { void VuMeter::paint(Painter& painter) {
uint32_t bar;
Color color;
bool lit = false;
uint32_t bar_level;
Point pos = screen_rect().location(); Point pos = screen_rect().location();
Dim width = screen_rect().size().width() - 4; Dim width = screen_rect().size().width() - 4;
Dim height = screen_rect().size().height();
Dim bottom = pos.y() + height;
Coord marks_x = pos.x() + width;
if (value_ != prev_value) { if (value_ != prev_value) {
uint32_t bar; bar_level = LEDs_ - ((value_ + 1) / split);
Color color;
bool lit = false;
uint32_t bar_level = LEDs_ - ((value_ + 1) / split);
// Draw LEDs // Draw LEDs
for (bar = 0; bar < LEDs_; bar++) { for (bar = 0; bar < LEDs_; bar++) {
if (bar >= bar_level) if (bar >= bar_level)
@ -1501,33 +1513,34 @@ void VuMeter::paint(Painter& painter) {
} }
// Update max level // Update max level
if (value_ > max) { if (show_max_) {
max = value_; if (value_ > max) {
hold_timer = 30; // 0.5s @ 60Hz max = value_;
} else { hold_timer = 30; // 0.5s @ 60Hz
if (hold_timer) {
hold_timer--;
} else { } else {
if (max) max--; // Let it drop if (hold_timer) {
hold_timer--;
} else {
if (max) max--; // Let it drop
}
}
// Draw max level
if (max != prev_max) {
painter.draw_hline({ marks_x, bottom - (height * prev_max) / 256 }, 8, Color::black());
painter.draw_hline({ marks_x, bottom - (height * max) / 256 }, 8, Color::white());
if (prev_max == mark)
prev_mark = 0; // Force mark refresh
prev_max = max;
} }
}
// Draw max level
if (max != prev_max) {
painter.draw_hline({ pos.x() + width, pos.y() + height - (height * prev_max) / 256 }, 8, Color::black());
painter.draw_hline({ pos.x() + width, pos.y() + height - (height * max) / 256 }, 8, Color::white());
if (prev_max == mark)
prev_mark = 0; // Force mark refresh
} }
// Draw mark // Draw mark
if ((mark != prev_mark) && (mark)) { if ((mark != prev_mark) && (mark)) {
painter.draw_hline({ pos.x() + width, pos.y() + height - (height * prev_mark) / 256 }, 8, Color::black()); painter.draw_hline({ marks_x, bottom - (height * prev_mark) / 256 }, 8, Color::black());
painter.draw_hline({ pos.x() + width, pos.y() + height - (height * mark) / 256 }, 8, Color::grey()); painter.draw_hline({ marks_x, bottom - (height * mark) / 256 }, 8, Color::grey());
prev_mark = mark;
} }
prev_max = max;
prev_mark = mark;
} }
} /* namespace ui */ } /* namespace ui */

View File

@ -575,19 +575,19 @@ private:
class VuMeter : public Widget { class VuMeter : public Widget {
public: public:
VuMeter(Rect parent_rect, uint32_t LEDs); VuMeter(Rect parent_rect, uint32_t LEDs, bool show_max);
void set_value(const uint8_t new_value); void set_value(const uint32_t new_value);
void set_mark(const uint8_t new_mark); void set_mark(const uint32_t new_mark);
void paint(Painter& painter) override; void paint(Painter& painter) override;
private: private:
uint32_t LEDs_, LED_height { 0 }; uint32_t LEDs_, LED_height { 0 };
uint32_t value_ { 0 }, prev_value { 0 }; uint32_t value_ { 0 }, prev_value { 255 }; // Forces painting on first display
uint32_t split { 0 }; uint32_t split { 0 };
uint16_t max { 0 }, prev_max { 0 }, hold_timer { 0 }, mark { 0 }, prev_mark { 0 }; uint16_t max { 0 }, prev_max { 0 }, hold_timer { 0 }, mark { 0 }, prev_mark { 0 };
int height; bool show_max_;
}; };
} /* namespace ui */ } /* namespace ui */

Binary file not shown.