mirror of
https://github.com/eried/portapack-mayhem.git
synced 2025-01-27 06:47:13 -05:00
Scanner: Added last locked frequencies list
Added back squelch to NFM receiver Scanner: cleanup Widgets: VU-meter cleanup
This commit is contained in:
parent
bebec9ccf7
commit
38e14b1e30
@ -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
|
||||||
|
@ -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 */
|
||||||
|
@ -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 */
|
||||||
|
@ -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
|
||||||
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 {
|
||||||
|
@ -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>(); } },
|
||||||
|
417
firmware/application/ui_scanner.cpp
Normal file
417
firmware/application/ui_scanner.cpp
Normal 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 */
|
249
firmware/application/ui_scanner.hpp
Normal file
249
firmware/application/ui_scanner.hpp
Normal 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 */
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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__*/
|
||||||
|
@ -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(
|
||||||
|
@ -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 */
|
||||||
|
@ -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.
Loading…
x
Reference in New Issue
Block a user