Compare commits

...

29 Commits

Author SHA1 Message Date
Brumi-2021 695e6d19f4 Adding_BPF_selection_to_the_NOAA_APT_signal (#2675)
* Adding_BPF_selection_to_the_NOAA_APT_signal
* comments, spell mistake .
2025-06-02 14:40:13 +02:00
Totoo 6e9ecf8e7b jammer fix (#2676)
* jammer fix
2025-06-02 19:58:53 +08:00
RocketGod fd158e873a Jammer improvements (#2674)
* Add new jammer modes
Overview:
This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Fixed and made brutal.

This PR introduces user-focused improvements to the Jammer App in the HackRF PortaPack Mayhem Firmware, enhancing usability and flexibility. The changes address specific user requirements for a more intuitive default configuration, continuous waveform support, and dynamic setting adjustments during transmission.
2025-05-31 22:16:57 +02:00
Totoo f90bd44617 Detector RX ext app (#2673) 2025-05-28 19:19:47 +08:00
Totoo 2602c417be ui new coord system examples and macros (#2672) 2025-05-27 17:37:41 +08:00
Totoo aa1264cf91 getres cmd (#2671) 2025-05-27 15:18:33 +08:00
Brumi-2021 c79abccffd wfm_filters_GUI_name_std (#2668) 2025-05-25 20:04:08 +02:00
Brumi-2021 d3c9dc1f10 Storing_selected_NOAA_filter_in_settings_file (#2665)
* Storing_selected_NOAA_filter_in_settings_file

* format_issues
2025-05-24 18:18:24 +02:00
Totoo 3c8335c942 Audio to right (#2664)
* r.align
2025-05-23 12:27:57 +02:00
Totoo ac31f4781c screen max width fixes (#2663)
* max width fixes

* format
2025-05-23 08:42:19 +02:00
Totoo c80cae3638 fix png part 2025-05-23 00:23:35 +08:00
Totoo a1d7cf2b86 Prepare for display orientation part 1 (#2661) 2025-05-22 23:24:53 +08:00
zix 6f6d863a14 refactor the serial log logic of BLE Rx (#2660) 2025-05-21 07:07:26 -07:00
RocketGod 43a1bc0445 Jammer app add modes (#2659)
* Add new jammer modes

Overview

This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.

* Add new jammer modes

Overview

This PR enhances the PortaPack Jammer app by introducing eight new signal types, ported from my Flipper Zero RF Jammer app (https://github.com/RocketGod-git/flipper-zero-rf-jammer). These modes expand the app's capability to disrupt a wide range of RF communication protocols, from analog radios to modern digital systems. The implementation preserves the original app structure, resolves namespace conflicts, and ensures compatibility with the Mayhem firmware.

New Modes

The following modes have been added to the options_type in ui_jammer.hpp, with corresponding signal generation in proc_jammer.cpp:

Noise: Generates broadband white noise to interfere with analog and digital signals (e.g., Wi-Fi, Bluetooth, key fobs). Highly effective for overwhelming receivers across a frequency range.

Sine: Produces a continuous, unmodulated sine wave to jam narrowband receivers, ideal for analog FM/AM radios or telemetry systems.

Square: Emits a harmonic-rich square wave, disrupting digital protocols (e.g., OOK, ASK) and systems sensitive to sharp transitions, such as remote keyless entry.

Sawtooth (Experimental): Generates a sawtooth wave with a unique harmonic profile, useful for testing interference against PWM-based or niche analog systems.

Triangle (Experimental): Creates a triangle wave with minimal harmonics, suitable for exploratory jamming of narrowband systems or receiver linearity testing.

Chirp: Outputs a rapid frequency-sweeping chirp signal, effective against frequency-hopping and spread-spectrum systems (e.g., some Wi-Fi, Bluetooth, or military radios).

Gauss: Generates Gaussian noise to mimic natural interference, targeting digital systems like GPS or data links by degrading signal-to-noise ratios.

Brute (Experimental): Transmits a constant maximum-amplitude signal to saturate simple receiver front-ends, useful for brute-force jamming of basic analog devices.
2025-05-19 19:16:28 +02:00
OpenSourceSDRLab 0d02f431c7 Upload the PCB file of PortaPack H4 and update the schematic file (#2657)
* Create README.txt

* Update README.txt

* Add files via upload

The gerber files of the portapack h4.

* delete

* Upload the gerber files for H4

* Create README.txt

This is the V1.0 version PCB file of PortaPack H4.

* Upload the pcb file for H4

* Update README.txt

* Update LCD_TF_Schematic.pdf
2025-05-19 23:40:02 +12:00
Brumi-2021 322b06d670 Improve_RF_sensitivity_NOAA_signal (#2654) 2025-05-18 12:49:24 +02:00
gullradriel db187301ab adding 10Hz and 50Hz to freqman_steps (#2652) 2025-05-11 21:36:47 +02:00
horrordash 84cb32ee14 slightly improved ads-b receiver module (#2649)
* slightly improved ads-b receiver module:
* fix Heading, Speed and Vrate decoders
* decode more ModeS messages
* log all ModeS messages (except DF11)
* fix formatting (clang-style); advice on data alignment taken into account
* ADS-B module: convert Indicated AirSpeed to True AirSpeed if altitute is known
* ADS-B rx module: replacing floating point with integer arithmetic
2025-05-11 21:11:15 +02:00
Brumi-2021 61bd696b69 Explicit_naming_wefax_NOAA_and_small_addition (#2651) 2025-05-11 21:10:48 +02:00
Totoo db65ae192a Noaa apt decoder (#2648) 2025-05-11 20:24:52 +02:00
RocketGod 7553dafffc Update ui_tetris.cpp (#2650)
Start "I" tetromino a bit higher and block rotate if it will cause out of bounds collision.
2025-05-10 13:08:41 +02:00
sommermorgentraum cfdd1a4c22 remove dead code (#2647) 2025-05-07 06:17:12 +02:00
gullradriel d4342e01a4 going back to WFM as main baseband in main.cpp as NFM is now making the apps crashing. Looks like last additions to WFM made it bigger. (#2646) 2025-05-06 16:50:59 +08:00
Brumi-2021 4342f5c0ee Adding_WFM_AM_mode_to_Audio_App (#2644)
* Adding_WFM_AM_mode_to_Audio_App
* more precise values for cos and sin theta, fix sen_theta to sin_theta
* fix sen_theta to sin_theta
2025-05-05 17:48:01 +02:00
Richard bd781ce37b Fix for #2538 (#2635)
* Fix for #2538

Fix for #2538
Added on_bandwidth_changed Callback to ui_transmitter.hpp
Modified the field_bw.on_change lambda in the TransmitterView constructor to trigger the on_bandwidth_changed callback
Connected the Callback in ui_siggen.cpp

I am not a C++ programmer so this change was proposed by Gemini AI.

I have built and tested the App and it works as expected and I don't think the change will have any unexpected side effects.

* Fix clang issues

Fix clang issues

* Update ui_transmitter.cpp

typo

* Revised change

The proposed change mirrors the way a change to the frequency (on_edit_frequency) is triggered in ui_siggen by the tx_view.
The bw parameter is not passed because it is stored in _setting in the tx_view and will be read by update_config.
A change to the bw is not checked against auto_update to keep its behaviour consistent with a change to the gain, amplitude or frequency.

* Make changes to the channel_bandwidth dynamic whist playing

Behaviour of channel bandwidth is now consistent with frequency, amp and gain.

* comment edit

* revert hackrf submodule checkpoint to the repo

* comment

---------

Co-authored-by: zxkmm <zxkmm@hotmail.com>
2025-05-02 04:19:41 +00:00
Erwin Ried c2e05dea48 Update README.md
Links were dead for opensourcesdrlab
2025-04-25 13:01:54 +02:00
sommermorgentraum de3212cba5 spectrum cursor opt again (#2634)
* spectrum cursor opt

* fmt

* remove blink

* remove End event

* cleanup
2025-04-22 21:14:31 +08:00
sommermorgentraum 9aff3f4121 Fix bug that created by PR "Added menu group for transceivers" (#2630) 2025-04-17 07:42:05 +02:00
sommermorgentraum 6b6a00d511 Support IPS screen & brightness set for IPS screen (#2629)
* _

* format

* format

* format
2025-04-17 07:40:28 +02:00
174 changed files with 2947 additions and 776 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/ABDO6H) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/9UMBN5) or [upgrade](https://share.hackrf.app/30CQ55), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
+3
View File
@@ -188,6 +188,7 @@ void copy_to_radio_model(const AppSettings& settings) {
settings.am_config_index,
settings.nbfm_config_index,
settings.wfm_config_index,
settings.wfmam_config_index,
settings.squelch);
}
@@ -218,6 +219,7 @@ void copy_from_radio_model(AppSettings& settings) {
settings.am_config_index = receiver_model.am_configuration();
settings.nbfm_config_index = receiver_model.nbfm_configuration();
settings.wfm_config_index = receiver_model.wfm_configuration();
settings.wfmam_config_index = receiver_model.wfmam_configuration();
}
settings.step = receiver_model.frequency_step();
@@ -271,6 +273,7 @@ SettingsManager::SettingsManager(
bindings_.emplace_back("am_config_index"sv, &settings_.am_config_index);
bindings_.emplace_back("nbfm_config_index"sv, &settings_.nbfm_config_index);
bindings_.emplace_back("wfm_config_index"sv, &settings_.wfm_config_index);
bindings_.emplace_back("wfmam_config_index"sv, &settings_.wfmam_config_index);
bindings_.emplace_back("squelch"sv, &settings_.squelch);
}
+1
View File
@@ -139,6 +139,7 @@ struct AppSettings {
uint8_t am_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t wfmam_config_index = 0;
uint8_t squelch = 80;
uint8_t volume;
// NOTE: update COMMON_APP_SETTINGS_COUNT when adding to this
+1 -1
View File
@@ -210,7 +210,7 @@ class AISAppView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Channel channel{
{21 * 8, 5, 6 * 8, 4},
+34 -2
View File
@@ -114,6 +114,26 @@ WFMOptionsView::WFMOptionsView(
};
}
/* WFMAMAptOptionsView *******************************************************/
WFMAMAptOptionsView::WFMAMAptOptionsView(
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
add_children({
&label_config,
&options_config,
});
freqman_set_bandwidth_option(WFMAM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
options_config.set_by_value(receiver_model.wfmam_configuration());
options_config.on_change = [this](size_t, OptionsField::value_t n) {
receiver_model.set_wfmam_configuration(n);
};
}
/* AMFMAptOptionsView *********************************************************/
AMFMAptOptionsView::AMFMAptOptionsView(
@@ -415,6 +435,12 @@ void AnalogAudioView::on_show_options_modulation() {
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::WFMAudioAMApt:
widget = std::make_unique<WFMAMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(true);
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::AMAudioFMApt:
widget = std::make_unique<AMFMAptOptionsView>(this, options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
@@ -462,7 +488,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO pending to update it.
case ReceiverModel::Mode::WFMAudioAMApt:
image_tag = portapack::spi_flash::image_tag_wfm_audio;
break;
case ReceiverModel::Mode::AMAudioFMApt:
image_tag = portapack::spi_flash::image_tag_am_audio;
break;
case ReceiverModel::Mode::SpectrumAnalysis:
@@ -501,7 +530,10 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
sampling_rate = 48000;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
case ReceiverModel::Mode::WFMAudioAMApt:
sampling_rate = 12000;
break;
case ReceiverModel::Mode::AMAudioFMApt:
sampling_rate = 12000;
break;
default:
+25 -7
View File
@@ -74,7 +74,7 @@ class AMFMAptOptionsView : public View {
OptionsField options_config{
{3 * 8, 0 * 16},
6, // Max option length chars
17, // Max option length chars "USB+FM(Wefax Apt)"
{
// Using common messages from freqman_ui.cpp In HF USB , Here we only need USB Audio demod, + post-FM demod fsubcarrier FM tone to get APT signal.
}};
@@ -132,6 +132,23 @@ class WFMOptionsView : public View {
}};
};
class WFMAMAptOptionsView : public View {
public:
WFMAMAptOptionsView(Rect parent_rect, const Style* style);
private:
Text label_config{
{0 * 8, 0 * 16, 2 * 8, 1 * 16},
"BW",
};
OptionsField options_config{
{3 * 8, 0 * 16},
19, // Max option char length "80k-NOAA Apt LPF-2K" , example.
{
// Using common messages from freqman_ui.cpp
}};
};
class SPECOptionsView : public View {
public:
SPECOptionsView(AnalogAudioView* view, Rect parent_rect, const Style* style);
@@ -168,7 +185,7 @@ class SPECOptionsView : public View {
{19 * 8, 0 * 16, 11 * 8, 1 * 16}, // 18 (x col.) x char_size, 12 (length) x 8 blanking space to delete previous chars.
"Rx_IQ_CAL "};
NumberField field_rx_iq_phase_cal{
{28 * 8, 0 * 16},
{screen_width - 2 * 8, 0 * 16},
2,
{0, 63}, // 5 or 6 bits IQ CAL phase adjustment (range updated later)
1,
@@ -215,7 +232,7 @@ class AnalogAudioView : public View {
uint8_t zoom_factor_amfm{0}; // initial zoom factor in AMFM mode
uint8_t previous_AM_mode_option{0}; // GUI 5 AM modes : (0..4 ) (DSB9K, DSB6K, USB,LSB, CW). Used to select proper FIR filter (0..11) AM mode + offset 0 (zoom+1) or +6 (if zoom+2)
uint8_t previous_zoom{0}; // GUI ZOOM+1, ZOOM+2 , equivalent to two values offset 0 (zoom+1) or +6 (if zoom+2)
//
app_settings::SettingsManager settings_{
"rx_audio",
app_settings::Mode::RX,
@@ -227,7 +244,7 @@ class AnalogAudioView : public View {
{"previous_zoom"sv, &previous_zoom}, // we are saving and restoring AMFM ZOOM factor from Settings.
}};
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
size_t spec_bw_index = 0;
@@ -261,11 +278,12 @@ class AnalogAudioView : public View {
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"FMAM", toUType(ReceiverModel::Mode::WFMAudioAMApt)} // Added to handle SAT NOAA APT
}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Text text_ctcss{
{16 * 8, 1 * 16, 14 * 8, 1 * 16},
@@ -274,7 +292,7 @@ class AnalogAudioView : public View {
std::unique_ptr<Widget> options_widget{};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
{0 * 8, 2 * 16, screen_width, 1 * 16},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
+1 -1
View File
@@ -170,7 +170,7 @@ class BLECommView : public View {
"-"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, ui::screen_width, ui::screen_height - 80}};
std::string str_log{""};
bool logging{false};
+7 -4
View File
@@ -33,6 +33,7 @@
#include "portapack_persistent_memory.hpp"
#include "ui_fileman.hpp"
#include "ui_textentry.hpp"
#include "usb_serial_asyncmsg.hpp"
using namespace portapack;
using namespace modems;
@@ -446,6 +447,8 @@ BLERxView::BLERxView(NavigationView& nav)
&button_switch,
&recent_entries_view});
async_tx_states_when_entered = portapack::async_tx_enabled;
recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry);
};
@@ -453,9 +456,9 @@ BLERxView::BLERxView(NavigationView& nav)
check_serial_log.on_select = [this](Checkbox&, bool v) {
serial_logging = v;
if (v) {
usb_serial_thread = std::make_unique<UsbSerialThread>();
portapack::async_tx_enabled = true;
} else {
usb_serial_thread.reset();
portapack::async_tx_enabled = false;
}
};
check_serial_log.set_value(serial_logging);
@@ -755,8 +758,7 @@ void BLERxView::on_data(BlePacketData* packet) {
}
if (serial_logging) {
usb_serial_thread->serial_str = str_console + "\r\n";
usb_serial_thread->str_ready = true;
UsbSerialAsyncmsg::asyncmsg(str_console); // new line handled there, no need here.
}
str_console = "";
@@ -916,6 +918,7 @@ void BLERxView::set_parent_rect(const Rect new_parent_rect) {
}
BLERxView::~BLERxView() {
portapack::async_tx_enabled = async_tx_states_when_entered;
receiver_model.disable();
baseband::shutdown();
}
+5 -4
View File
@@ -219,6 +219,7 @@ class BLERxView : public View {
std::string filter{};
bool logging{false};
bool serial_logging{false};
bool async_tx_states_when_entered{false};
bool name_enable{true};
app_settings::SettingsManager settings_{
@@ -340,18 +341,18 @@ class BLERxView : public View {
true};
// Console console{
// {0, 10 * 8, 240, 240}};
// {0, 10 * 8, screen_height, screen_height-80}};
Button button_clear_list{
{2 * 8, 320 - (16 + 32), 7 * 8, 32},
{2 * 8, screen_height - (16 + 32), 7 * 8, 32},
"Clear"};
Button button_save_list{
{11 * 8, 320 - (16 + 32), 11 * 8, 32},
{11 * 8, screen_height - (16 + 32), 11 * 8, 32},
"Export CSV"};
Button button_switch{
{240 - 6 * 8, 320 - (16 + 32), 4 * 8, 32},
{screen_width - 6 * 8, screen_height - (16 + 32), 4 * 8, 32},
"Tx"};
std::string str_log{""};
+3 -3
View File
@@ -216,7 +216,7 @@ class BLETxView : public View {
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
@@ -288,10 +288,10 @@ class BLETxView : public View {
{{0 * 8, 9 * 16}, "Packet Data:", Theme::getInstance()->fg_light->foreground}};
Console console{
{0, 9 * 18, 240, 240}};
{0, 9 * 18, screen_width, screen_height - 80}};
TextViewer dataEditView{
{0, 9 * 18, 240, 240}};
{0, 9 * 18, screen_width, screen_height - 80}};
Button button_clear_marked{
{1 * 8, 14 * 16, 13 * 8, 3 * 8},
+1 -1
View File
@@ -109,7 +109,7 @@ class CaptureAppView : public View {
/*small*/ true};
RecordView record_view{
{0 * 8, 2 * 16, 30 * 8, 1 * 16},
{0 * 8, 2 * 16, screen_width, 1 * 16},
u"BBD_????.*",
captures_dir,
RecordView::FileType::RawS16,
+1 -1
View File
@@ -166,7 +166,7 @@ class ERTAppView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_packet{
Message::ID::ERTPacket,
+1 -1
View File
@@ -259,7 +259,7 @@ class POCSAGAppView : public View {
' ',
true /*wrap*/};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Image image_status{
{0 * 8 + 4, 1 * 16 + 2, 16, 16},
+24 -5
View File
@@ -91,10 +91,6 @@ void SoundBoardView::start_tx(const uint32_t id) {
auto reader = std::make_unique<WAVFileReader>();
uint32_t tone_key_index = options_tone_key.selected_index();
uint32_t sample_rate;
uint8_t bits_per_sample;
stop();
if (!reader->open(u"/WAV/" + file_list[id].native())) {
@@ -108,7 +104,9 @@ void SoundBoardView::start_tx(const uint32_t id) {
// button_play.set_bitmap(&bitmap_stop);
sample_rate = reader->sample_rate();
uint32_t sample_rate = reader->sample_rate();
tone_key_index = options_tone_key.selected_index();
bits_per_sample = reader->bits_per_sample();
replay_thread = std::make_unique<ReplayThread>(
@@ -155,6 +153,23 @@ void SoundBoardView::on_tx_progress(const uint32_t progress) {
progressbar.set_value(progress);
}
void SoundBoardView::update_config() {
// NB: this were called by the on_bandwidth_changed() callback,
// so other val would be updated too when bw changed. currently it's safe but be careful.
baseband::set_audiotx_config(
1536000 / 20, // Update vu-meter at 20Hz
transmitter_model.channel_bandwidth(),
0, // Gain is unused
8, // shift_bits_s16, default 8 bits, but also unused
bits_per_sample,
TONES_F2D(tone_key_frequency(tone_key_index), TONES_SAMPLERATE),
false, // AM
false, // DSB
false, // USB
false // LSB
);
}
void SoundBoardView::on_select_entry() {
tx_view.focus();
}
@@ -289,6 +304,10 @@ SoundBoardView::SoundBoardView(
};
};
tx_view.on_bandwidth_changed = [this]() {
update_config();
};
tx_view.on_start = [this]() {
start_tx(menu_view.highlighted_index());
};
+8 -5
View File
@@ -70,6 +70,8 @@ class SoundBoardView : public View {
uint32_t playing_id{};
uint32_t page = 1;
uint32_t c_page = 1;
uint32_t tone_key_index = 1;
uint8_t bits_per_sample = 1;
std::vector<std::filesystem::path> file_list{};
@@ -90,6 +92,7 @@ class SoundBoardView : public View {
void on_tx_progress(const uint32_t progress);
void refresh_list();
void on_select_entry();
void update_config();
Labels labels{
{{24 * 8, 180}, "Vol:", Theme::getInstance()->fg_light->foreground},
@@ -104,10 +107,10 @@ class SoundBoardView : public View {
"<="};
Text page_info{
{0, 29 * 8, 30 * 8, 16}};
{0, 29 * 8, screen_width, 16}};
MenuView menu_view{
{0, 0, 240, 175},
{0, 0, screen_width, 175},
true};
Text text_empty{
{7 * 8, 12 * 8, 16 * 8, 16},
@@ -128,9 +131,9 @@ class SoundBoardView : public View {
{}};
AudioVolumeField field_volume{
{28 * 8, 180}};
{screen_width - 2 * 8, 180}};
Text text_volume_disabled{
{28 * 8, 180, 3 * 8, 16},
{screen_width - 2 * 8, 180, 3 * 8, 16},
"--"};
Checkbox check_loop{
@@ -144,7 +147,7 @@ class SoundBoardView : public View {
"Random"};
ProgressBar progressbar{
{0 * 8, 31 * 8 + 2, 30 * 8, 4}};
{0 * 8, 31 * 8 + 2, screen_width, 4}};
TransmitterView tx_view{
16 * 16,
@@ -6,6 +6,10 @@
#include <cstdint>
/*
TODO: Now it is dyn width. There should be an algorithm to fill the menu based on it's size.
*/
namespace ui {
class AboutView : public View {
public:
@@ -22,11 +26,11 @@ class AboutView : public View {
uint16_t frame_sync_count{0};
void on_frame_sync();
MenuView menu_view{
{0, 0, 240, 264},
{0, 0, screen_width, screen_height - 56},
true};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, screen_height - 50, screen_width / 3, 24},
"OK",
};
+146 -36
View File
@@ -40,6 +40,8 @@ namespace pmem = portapack::persistent_memory;
namespace ui {
static const char speed_type_msg[][6] = {" Spd:", " IAS:", " TAS:"};
static std::string get_map_tag(const AircraftRecentEntry& entry) {
return trimr(entry.callsign.empty() ? entry.icao_str : entry.callsign);
}
@@ -70,18 +72,34 @@ void RecentEntriesTable<AircraftRecentEntries>::draw(
entry_string +=
(entry.callsign.empty() ? entry.icao_str + " " : entry.callsign + " ") +
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4) +
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4) + " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
to_string_dec_uint(entry.age, 4);
to_string_dec_uint((unsigned int)(entry.pos.altitude / 100), 4);
if (entry.velo.type == SPD_IAS && entry.pos.alt_valid) { // IAS can be converted to TAS
// It is generally accepted that for every thousand feet of altitude,
// true airspeed is approximately 2% higher than indicated airspeed.
// Since the application CPU has no floating point unit, we avoid floating point here
// tas = entry.velo.speed + (float)entry.pos.altitude / 1000.0 * 0.02 * entry.velo.speed;
unsigned int tas = entry.velo.speed + entry.pos.altitude * 2 * entry.velo.speed / 100000;
entry_string +=
to_string_dec_uint(tas, 4) + '*' +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 3);
} else {
entry_string +=
to_string_dec_uint((unsigned int)entry.velo.speed, 4) +
to_string_dec_uint((unsigned int)(entry.amp >> 9), 4);
}
entry_string += " " +
(entry.hits <= 999 ? to_string_dec_uint(entry.hits, 3) + " " : "1k+ ") +
to_string_dec_uint(entry.age, 4);
painter.draw_string(
target_rect.location(),
style,
entry_string);
if (entry.pos.valid)
if (entry.pos.pos_valid)
painter.draw_bitmap(target_rect.location() + Point(8 * 8, 0),
bitmap_target, target_color, style.background);
}
@@ -93,22 +111,31 @@ void ADSBLogger::log(const ADSBLogEntry& log_entry) {
log_line.reserve(100);
log_line = log_entry.raw_data;
log_line += "ICAO:" + log_entry.icao;
log_line += " ICAO:" + log_entry.icao;
if (log_entry.sqwk)
log_line += " Squawk:" + to_string_dec_uint(log_entry.sqwk, 4, '0');
if (!log_entry.callsign.empty())
log_line += " " + log_entry.callsign;
if (log_entry.pos.valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude) +
" Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
if (log_entry.pos.alt_valid)
log_line += " Alt:" + to_string_dec_int(log_entry.pos.altitude);
if (log_entry.pos.pos_valid)
log_line += " Lat:" + to_string_decimal(log_entry.pos.latitude, 7) +
" Lon:" + to_string_decimal(log_entry.pos.longitude, 7);
if (log_entry.vel.valid)
log_line += " Type:" + to_string_dec_uint(log_entry.vel_type) +
" Hdg:" + to_string_dec_uint(log_entry.vel.heading) +
" Spd: " + to_string_dec_int(log_entry.vel.speed);
speed_type_msg[log_entry.vel.type] +
to_string_dec_int(log_entry.vel.speed) +
" Vrate:" + to_string_dec_int(log_entry.vel.v_rate);
if (log_entry.sil != 0)
log_line += " Sil:" + to_string_dec_uint(log_entry.sil);
log_file.write_entry(log_line);
}
@@ -258,7 +285,7 @@ ADSBRxDetailsView::ADSBRxDetailsView(
get_map_tag(entry_),
entry_.pos.altitude,
GeoPos::alt_unit::FEET,
GeoPos::spd_unit::MPH,
GeoPos::spd_unit::HIDDEN,
entry_.pos.latitude,
entry_.pos.longitude,
entry_.velo.heading);
@@ -356,11 +383,12 @@ void ADSBRxDetailsView::refresh_ui() {
text_callsign.set(entry_.callsign);
text_infos.set(entry_.info_string);
std::string str_sil = (entry_.sil > 0) ? " Sil:" + to_string_dec_uint(entry_.sil) : "";
std::string str_sqw = (entry_.sqwk > 0) ? " Sqw:" + to_string_dec_uint(entry_.sqwk) : "";
if (entry_.velo.heading < 360 && entry_.velo.speed >= 0)
text_info2.set("Hdg:" + to_string_dec_uint(entry_.velo.heading) +
" Spd:" + to_string_dec_int(entry_.velo.speed) + str_sil);
speed_type_msg[entry_.velo.type] + to_string_dec_int(entry_.velo.speed) + str_sil + str_sqw);
else
text_info2.set(str_sil);
text_info2.set(str_sil + str_sqw);
text_frame_pos_even.set(to_string_hex_array(entry_.frame_pos_even.get_raw_data(), 14));
text_frame_pos_odd.set(to_string_hex_array(entry_.frame_pos_odd.get_raw_data(), 14));
@@ -381,7 +409,7 @@ ADSBRxView::ADSBRxView(NavigationView& nav) {
&status_good_frame,
&field_volume});
recent_entries_view.set_parent_rect({0, 16, 240, 272});
recent_entries_view.set_parent_rect({0, 16, screen_width, 272});
recent_entries_view.on_select = [this, &nav](const AircraftRecentEntry& entry) {
detail_key = entry.key();
details_view = nav.push<ADSBRxDetailsView>(entry);
@@ -421,14 +449,22 @@ void ADSBRxView::focus() {
void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
auto frame = message->frame;
uint32_t ICAO_address = frame.get_ICAO_address();
status_frame.toggle();
// Bad frame, skip it.
if (!frame.check_CRC() || ICAO_address == 0)
return;
uint32_t ICAO_address;
uint32_t crc = frame.check_CRC();
if (crc != 0) {
if (find(recent, crc) != recent.end())
ICAO_address = crc;
else
return; // Bad frame, skip it.
} else {
ICAO_address = frame.get_ICAO_address();
if (ICAO_address == 0)
return; // Bad frame, skip it.
}
ADSBLogEntry log_entry;
status_good_frame.toggle();
rtc::RTC datetime;
@@ -440,42 +476,68 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
entry.inc_hit();
entry.reset_age();
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
}
// Store smoothed amplitude on updates.
entry.amp = entry.hits == 0
? message->amp
: ((entry.amp * 15) + message->amp) >> 4;
log_entry.raw_data = to_string_hex_array(frame.get_raw_data(), 14);
uint8_t df = frame.get_DF();
if (df == 11) // do not log DF11, because messages arrive too frequently
return;
ADSBLogEntry log_entry;
uint8_t* raw_data = frame.get_raw_data();
if (df & 0x10) // 112 bits
log_entry.raw_data = to_string_hex_array(raw_data, 14);
else { // 56 bits
log_entry.raw_data = to_string_hex_array(raw_data, 7);
log_entry.raw_data.append(14, ' ');
}
log_entry.icao = entry.icao_str;
if (frame.get_DF() == DF_ADSB) {
// 17: // Extended squitter
// 18: // Extended squitter/non-transponder
if (df == DF_ADSB) {
uint8_t msg_type = frame.get_msg_type();
uint8_t msg_sub = frame.get_msg_sub();
uint8_t* raw_data = frame.get_raw_data();
// 4: // surveillance, altitude reply
if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
// transmitted when horizontal position information is not available but altitude information is available
if (msg_type == 0) {
// Q-bit must be present
if (raw_data[5] & 1) {
int altitude = ((((raw_data[5] & 0xFE) << 3) | ((raw_data[6] & 0xF0) >> 4)) * 25) - 1000;
log_entry.pos.altitude = entry.pos.altitude = altitude;
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
}
}
// 1-4: Aircraft identification
else if ((msg_type >= AIRCRAFT_ID_L) && (msg_type <= AIRCRAFT_ID_H)) {
entry.set_callsign(decode_frame_id(frame));
log_entry.callsign = entry.callsign;
}
// 9:
// 18: // Extended squitter/non-transponder
// 21: // Comm-B, identity reply
// 20: // Comm-B, altitude reply
// 9-18: Airborne position (w/Baro Altitude)
// 20-22: Airborne position (w/GNSS Height)
else if (((msg_type >= AIRBORNE_POS_BARO_L) && (msg_type <= AIRBORNE_POS_BARO_H)) ||
((msg_type >= AIRBORNE_POS_GPS_L) && (msg_type <= AIRBORNE_POS_GPS_H))) {
entry.set_frame_pos(frame, raw_data[6] & 4);
log_entry.pos = entry.pos;
if (entry.pos.valid) {
if (entry.pos.pos_valid) {
std::string str_info =
"Alt:" + to_string_dec_int(entry.pos.altitude) +
" Lat:" + to_string_decimal(entry.pos.latitude, 2) +
" Lon:" + to_string_decimal(entry.pos.longitude, 2);
entry.set_info_string(std::move(str_info));
}
// 19: Airborne velocities
} else if (msg_type == AIRBORNE_VEL && msg_sub >= VEL_GND_SUBSONIC && msg_sub <= VEL_AIR_SUPERSONIC) {
entry.set_frame_velo(frame);
log_entry.vel = entry.velo;
@@ -485,11 +547,59 @@ void ADSBRxView::on_frame(const ADSBFrameMessage* message) {
}
}
logger->log(log_entry);
// 4: // surveillance, altitude reply
// 20: // Comm-B, altitude reply
// 21: // Comm-B, identity reply
if (df == 0 || df == 4 || df == 20) { // Decode the 13 bit AC altitude field
uint8_t m_bit = raw_data[3] & (1 << 6);
uint8_t q_bit = raw_data[3] & (1 << 4);
int altitude = 0;
if (pmem::beep_on_packets()) {
baseband::request_audio_beep(1000, 24000, 60);
if (!m_bit) { // units -> FEET
if (q_bit) { // N is the 11 bit integer resulting from the removal of bit Q and M
int n = ((raw_data[2] & 31) << 6) |
((raw_data[3] & 0x80) >> 2) |
((raw_data[3] & 0x20) >> 1) |
(raw_data[3] & 15);
// The final altitude is due to the resulting number multiplied by 25, minus 1000.
altitude = 25 * n - 1000;
if (altitude < 0)
altitude = 0;
} // else N is an 11 bit Gillham coded altitude
}
log_entry.pos.altitude = entry.pos.altitude = altitude;
log_entry.pos.alt_valid = entry.pos.alt_valid = true;
}
if (df == 5 || df == 21 ||
(df == 17 && frame.get_msg_type() == 28 && frame.get_msg_sub() == 1)) { // Decode the squawk code
uint8_t* s = (df == 17) ? raw_data + 5 : raw_data + 2; // calc start of the code
uint16_t sqwk{0};
sqwk = ((s[1] & 0x80) >> 5) | ((s[0] & 0x02) >> 0) | ((s[0] & 0x08) >> 3); // A
sqwk *= 10;
sqwk += ((s[1] & 0x02) << 1) | ((s[1] & 0x08) >> 2) | ((s[1] & 0x20) >> 5); // B
sqwk *= 10;
sqwk += ((s[0] & 0x01) << 2) | ((s[0] & 0x04) >> 1) | ((s[0] & 0x10) >> 4); // C
sqwk *= 10;
sqwk += ((s[1] & 0x01) << 2) | ((s[1] & 0x04) >> 1) | ((s[1] & 0x10) >> 4); // D
log_entry.sqwk = entry.sqwk = sqwk;
}
if (df == 20 || df == 21) {
if (raw_data[4] == 0x20) { // try decode as BDS20
std::string callsign = decode_frame_id(frame);
if (callsign.find('#') == std::string::npos) { // all chars OK
entry.set_callsign(callsign);
log_entry.callsign = callsign;
}
}
}
logger->log(log_entry);
}
void ADSBRxView::on_tick_second() {
@@ -544,7 +654,7 @@ void ADSBRxView::refresh_ui() {
}
// NB: current entry also gets a marker so it shows up if map is panned.
if (map_needs_update && entry.pos.valid && entry.state <= ADSBAgeState::Recent) {
if (map_needs_update && entry.pos.pos_valid && entry.state <= ADSBAgeState::Recent) {
map_needs_update = details_view->add_map_marker(entry);
}
+17 -15
View File
@@ -95,8 +95,8 @@ struct AircraftRecentEntry {
ADSBAgeState state{ADSBAgeState::Invalid};
uint32_t age{0}; // In seconds
uint32_t amp{0};
adsb_pos pos{false, 0, 0, 0};
adsb_vel velo{false, 0, 999, 0};
adsb_pos pos{false, false, 0, 0, 0};
adsb_vel velo{false, SPD_GND, 0, 999, 0};
ADSBFrame frame_pos_even{};
ADSBFrame frame_pos_odd{};
@@ -105,6 +105,7 @@ struct AircraftRecentEntry {
std::string info_string{};
uint8_t sil{0}; // Surveillance integrity level
uint16_t sqwk{0};
AircraftRecentEntry(const uint32_t ICAO_address)
: ICAO_address{ICAO_address} {
@@ -152,8 +153,8 @@ struct AircraftRecentEntry {
age += delta;
if (age < ADSBAgeLimit::Current)
state = pos.valid ? ADSBAgeState::Invalid
: ADSBAgeState::Current;
state = pos.pos_valid ? ADSBAgeState::Current
: ADSBAgeState::Invalid;
else if (age < ADSBAgeLimit::Recent)
state = ADSBAgeState::Recent;
@@ -178,6 +179,7 @@ struct ADSBLogEntry {
adsb_vel vel{};
uint8_t vel_type{};
uint8_t sil{};
uint16_t sqwk{};
};
// TODO: Make logging optional.
@@ -228,7 +230,7 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_model{
{0 * 8, 6 * 16, 30 * 8, 16},
{0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_type{
@@ -236,19 +238,19 @@ class ADSBRxAircraftDetailsView : public View {
"-"};
Text text_number_of_engines{
{18 * 8, 8 * 16, 30 * 8, 16},
{18 * 8, 8 * 16, screen_width, 16},
"-"};
Text text_engine_type{
{0 * 8, 10 * 16, 30 * 8, 16},
{0 * 8, 10 * 16, screen_width, 16},
"-"};
Text text_owner{
{0 * 8, 12 * 16, 30 * 8, 16},
{0 * 8, 12 * 16, screen_width, 16},
"-"};
Text text_operator{
{0 * 8, 14 * 16, 30 * 8, 16},
{0 * 8, 14 * 16, screen_width, 16},
"-"};
Button button_close{
@@ -310,7 +312,7 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_airline{
{0 * 8, 4 * 16, 30 * 8, 16},
{0 * 8, 4 * 16, screen_width, 16},
"-"};
Text text_country{
@@ -318,18 +320,18 @@ class ADSBRxDetailsView : public View {
"-"};
Text text_infos{
{0 * 8, 6 * 16, 30 * 8, 16},
{0 * 8, 6 * 16, screen_width, 16},
"-"};
Text text_info2{
{0 * 8, 7 * 16, 30 * 8, 16},
{0 * 8, 7 * 16, screen_width, 16},
"-"};
Text text_frame_pos_even{
{0 * 8, 14 * 16, 30 * 8, 16},
{0 * 8, 14 * 16, screen_width, 16},
"-"};
Text text_frame_pos_odd{
{0 * 8, 16 * 16, 30 * 8, 16},
{0 * 8, 16 * 16, screen_width, 16},
"-"};
Button button_aircraft_details{
@@ -440,7 +442,7 @@ class ADSBRxView : public View {
};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
MessageHandlerRegistration message_handler_frame{
Message::ID::ADSBFrame,
+2 -2
View File
@@ -247,8 +247,8 @@ APRSTableView::APRSTableView(NavigationView& nav, Rect parent_rec)
details_view.hidden(true);
recent_entries_view.set_parent_rect({0, 0, 240, 280});
details_view.set_parent_rect({0, 0, 240, 280});
recent_entries_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
details_view.set_parent_rect({0, 0, screen_width, screen_width - 40});
recent_entries_view.on_select = [this](const APRSRecentEntry& entry) {
this->on_show_detail(entry);
+5 -5
View File
@@ -134,7 +134,7 @@ class APRSDetailsView : public View {
bool send_updates{false};
Console console{
{0, 0 * 16, 240, 224}};
{0, 0 * 16, screen_width, 224}};
Button button_done{
{160, 14 * 16, 8 * 8, 3 * 16},
@@ -220,7 +220,7 @@ class APRSRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
OptionsField options_region{
{0 * 8, 0 * 8},
@@ -241,7 +241,7 @@ class APRSRxView : public View {
// DEBUG
RecordView record_view{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
{0 * 8, 1 * 16, screen_width, 1 * 16},
u"AFS_????.WAV",
aprs_dir,
RecordView::FileType::WAV,
@@ -249,7 +249,7 @@ class APRSRxView : public View {
4};
Console console{
{0, 2 * 16, 240, 240}};
{0, 2 * 16, screen_width, screen_height - 80}};
std::unique_ptr<APRSLogger> logger{};
};
@@ -265,7 +265,7 @@ class APRSRXView : public View {
private:
NavigationView& nav_;
Rect view_rect = {0, 3 * 8, 240, 280};
Rect view_rect = {0, 3 * 8, screen_width, screen_height - 40};
APRSRxView view_stream{nav_, view_rect};
APRSTableView view_table{nav_, view_rect};
+1 -1
View File
@@ -91,7 +91,7 @@ class APRSTXView : public View {
' '};
Text text_payload{
{0 * 8, 5 * 16, 30 * 8, 16},
{0 * 8, 5 * 16, screen_width, 16},
"-"};
Button button_set{
{0 * 8, 6 * 16, 80, 32},
+1 -1
View File
@@ -90,7 +90,7 @@ class BattinfoView : public View {
"-"};
Text text_warn{
{1 * 8, 8 * 16, 30 * 8, 2 * 16},
{1 * 8, 8 * 16, screen_width, 2 * 16},
""}; */
Button button_mode{
+2 -2
View File
@@ -189,7 +189,7 @@ class BHTView : public View {
tx_modes tx_mode = IDLE;
Rect view_rect = {0, 3 * 8, 240, 176};
Rect view_rect = {0, 3 * 8, screen_width, 176};
XylosView view_xylos{view_rect};
EPARView view_EPAR{view_rect};
@@ -218,7 +218,7 @@ class BHTView : public View {
' '};
ProgressBar progressbar{
{0 * 8, 29 * 8, 30 * 8, 16},
{0 * 8, 29 * 8, screen_width, 16},
};
TransmitterView tx_view{
@@ -42,7 +42,7 @@ class BMPFileViewer : public View {
private:
NavigationView& nav_;
std::filesystem::path path_{};
BMPViewer bmp{{0, 0, 240, 320}};
BMPViewer bmp{{0, 0, screen_width, screen_height}};
};
} // namespace ui
+2 -2
View File
@@ -78,11 +78,11 @@ class BTLERxView : public View {
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, 1 * 16, 96, 24},
"Modem setup"};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
+3 -3
View File
@@ -202,10 +202,10 @@ RegistersView::RegistersView(
};
button_done.on_select = [&nav](Button&) { nav.pop(); };
registers_widget.set_parent_rect({0, 48, 240, 192});
registers_widget.set_parent_rect({0, 48, screen_width, 192});
registers_widget.set_page(0);
text_title.set_parent_rect({(240 - static_cast<int>(title.size()) * 8) / 2, 16,
text_title.set_parent_rect({(screen_width - static_cast<int>(title.size()) * 8) / 2, 16,
static_cast<int>(title.size()) * 8, 16});
text_title.set(title);
@@ -482,7 +482,7 @@ DebugPmemView::DebugPmemView(NavigationView& nav)
: registers_widget(RegistersWidgetConfig{CT_PMEM, PMEM_SIZE_BYTES, page_size, 8}) {
add_children({&registers_widget, &text_checksum, &text_checksum2, &button_ok});
registers_widget.set_parent_rect({0, 32, 240, 192});
registers_widget.set_parent_rect({0, 32, screen_width, 192});
text_checksum.set("Size: " + to_string_dec_uint(portapack::persistent_memory::data_size(), 3) + " CRC: " + to_string_hex(portapack::persistent_memory::pmem_stored_checksum(), 8));
text_checksum2.set("Calculated CRC: " + to_string_hex(portapack::persistent_memory::pmem_calculated_checksum(), 8));
+3 -3
View File
@@ -283,7 +283,7 @@ class DebugMemoryDumpView : public View {
"Write"};
Button button_done{
{128, 240, 96, 24},
{128, screen_height - 80, 96, 24},
"Done"};
Labels labels{
@@ -332,7 +332,7 @@ class DebugPmemView : public View {
Text text_checksum2{{16, 248, 208, 16}};
Button button_ok{
{240 / 3, 270, 240 / 3, 24},
{screen_width / 3, 270, screen_width / 3, 24},
"OK",
};
@@ -364,7 +364,7 @@ public:
private:
Console console {
{ 8, 16, 224, 240 }
{ 8, 16, 224, screen_height-80 }
};
Button button_exit {
+2 -2
View File
@@ -124,7 +124,7 @@ class EncodersConfigView : public View {
""};
Waveform waveform{
{0, 17 * 8, 240, 32},
{0, 17 * 8, screen_width, 32},
waveform_buffer,
0,
0,
@@ -201,7 +201,7 @@ class EncodersView : public View {
void start_tx(const bool scan);
void on_tx_progress(const uint32_t progress, const bool done);
Rect view_rect = {0, 4 * 8, 240, 168};
Rect view_rect = {0, 4 * 8, screen_width, 168};
EncodersConfigView view_config{nav_, view_rect};
EncodersScanView view_scan{nav_, view_rect};
@@ -84,7 +84,7 @@ class ExternalModuleView : public View {
Text text_app5_name{{24, 160, 200, 16}};
Button dummy{
{240, 0, 0, 0},
{screen_width, 0, 0, 0},
""};
SignalToken signal_token_tick_second{};
+1 -1
View File
@@ -455,7 +455,7 @@ FileLoadView::FileLoadView(
add_children({&menu_view});
// Resize menu view to fill screen
menu_view.set_parent_rect({0, 3 * 8, 240, 29 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 29 * 8});
refresh_list();
+3 -3
View File
@@ -125,7 +125,7 @@ class FileManBaseView : public View {
};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
Button button_exit{
@@ -172,7 +172,7 @@ private:
};
Text text_path {
{ 0 * 8, 2 * 16, 30 * 8, 16 },
{ 0 * 8, 2 * 16, screen_width, 16 },
"",
};
@@ -182,7 +182,7 @@ private:
};
Text text_name {
{ 0 * 8, 7 * 16, 30 * 8, 16 },
{ 0 * 8, 7 * 16, screen_width, 16 },
"",
};
@@ -80,7 +80,7 @@ FlashUtilityView::FlashUtilityView(NavigationView& nav)
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(apps_dir);
ensure_directory(firmware_dir);
@@ -60,7 +60,7 @@ class FlashUtilityView : public View {
{{4, 4}, "Select firmware to flash:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
std::filesystem::path extract_tar(std::filesystem::path::string_type path, ui::Painter& painter); // extracts the tar file, and returns the firmware.bin path from it. empty string if no fw
+1 -1
View File
@@ -45,7 +45,7 @@ using option_db_t = std::pair<std::string_view, int32_t>;
using options_db_t = std::vector<option_db_t>;
extern options_db_t freqman_modulations;
extern options_db_t freqman_bandwidths[5];
extern options_db_t freqman_bandwidths[6];
// extern options_db_t freqman_steps; // now included via ui_receiver.hpp
extern options_db_t freqman_steps_short;
+1 -1
View File
@@ -100,7 +100,7 @@ class FrequencySaveView : public FreqManBaseView {
{{0 * 8, 6 * 16}, "Description:", Theme::getInstance()->bg_darkest->foreground}};
TextField field_description{
{0 * 8, 7 * 16, 30 * 8, 1 * 16},
{0 * 8, 7 * 16, screen_width, 1 * 16},
""};
Button button_save{
+1
View File
@@ -34,6 +34,7 @@ namespace ui {
IQTrimView::IQTrimView(NavigationView& nav)
: nav_{nav} {
power_buckets_.resize(screen_width);
add_children({
&labels,
&field_path,
+2 -2
View File
@@ -96,7 +96,7 @@ class IQTrimView : public View {
std::filesystem::path path_{};
Optional<iq::CaptureInfo> info_{};
std::array<iq::PowerBuckets::Bucket, screen_width> power_buckets_{};
std::vector<iq::PowerBuckets::Bucket> power_buckets_{};
TrimProgressUI progress_ui{};
Labels labels{
@@ -112,7 +112,7 @@ class IQTrimView : public View {
};
TextField field_path{
{0 * 8, 1 * 16, 30 * 8, 1 * 16},
{0 * 8, 1 * 16, screen_width, 1 * 16},
"Open File..."};
Point pos_lines{0 * 8, 4 * 16};
@@ -94,10 +94,10 @@ void GlassView::get_max_power(const ChannelSpectrum& spectrum, uint8_t bin, uint
rf::Frequency GlassView::get_freq_from_bin_pos(uint8_t pos) {
rf::Frequency freq_at_pos = 0;
if (mode == LOOKING_GLASS_SINGLEPASS) {
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by SCREEN_W(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (SCREEN_W / 2);
// starting from the middle, minus 8 ignored bin on each side. Since pos is [-120,120] after the (pos - 120), it's divided by screen_width(240)/2 => 120
freq_at_pos = f_center_ini + ((pos - 120) * ((looking_glass_range - ((16 * looking_glass_range) / SPEC_NB_BINS)) / 2)) / (screen_width / 2);
} else
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / SCREEN_W;
freq_at_pos = f_min + (2 * offset * each_bin_size) + (pos * looking_glass_range) / screen_width;
return freq_at_pos;
}
@@ -122,7 +122,7 @@ void GlassView::reset_live_view() {
// Clear screen in peak mode.
if (live_frequency_view == 2)
display.fill_rectangle({{0, 108 + 16}, {SCREEN_W, SCREEN_H - (108 + 16)}}, {0, 0, 0});
display.fill_rectangle({{0, 108 + 16}, {screen_width, screen_height - (108 + 16)}}, {0, 0, 0});
}
void GlassView::add_spectrum_pixel(uint8_t power) {
@@ -130,7 +130,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
spectrum_data[pixel_index] = (live_frequency_integrate * spectrum_data[pixel_index] + power) / (live_frequency_integrate + 1); // smoothing
pixel_index++;
if (pixel_index == SCREEN_W) // got an entire waterfall line
if (pixel_index == screen_width) // got an entire waterfall line
{
if (live_frequency_view > 0) {
constexpr int rssi_sample_range = SPEC_NB_BINS;
@@ -140,22 +140,22 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
constexpr int raw_min = rssi_sample_range * rssi_voltage_min / adc_voltage_max;
constexpr int raw_max = rssi_sample_range * rssi_voltage_max / adc_voltage_max;
constexpr int raw_delta = raw_max - raw_min;
const range_t<int> y_max_range{0, 320 - (108 + 16)};
const range_t<int> y_max_range{0, screen_height - (108 + 16)};
// drawing and keeping track of max freq
for (uint16_t xpos = 0; xpos < SCREEN_W; xpos++) {
for (uint16_t xpos = 0; xpos < screen_width; xpos++) {
// save max powerwull freq
if (spectrum_data[xpos] > max_freq_power) {
max_freq_power = spectrum_data[xpos];
max_freq_hold = get_freq_from_bin_pos(xpos);
}
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (320 - (108 + 16))) / raw_delta);
int16_t point = y_max_range.clip(((spectrum_data[xpos] - raw_min) * (screen_height - (108 + 16))) / raw_delta);
uint8_t color_gradient = (point * 255) / 212;
// clear if not in peak view
if (live_frequency_view != 2) {
display.fill_rectangle({{xpos, 108 + 16}, {1, SCREEN_H - point}}, {0, 0, 0});
display.fill_rectangle({{xpos, 108 + 16}, {1, screen_height - point}}, {0, 0, 0});
}
display.fill_rectangle({{xpos, SCREEN_H - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
display.fill_rectangle({{xpos, screen_height - point}, {1, point}}, {color_gradient, 0, uint8_t(255 - color_gradient)});
}
if (last_max_freq != max_freq_hold) {
last_max_freq = max_freq_hold;
@@ -163,7 +163,7 @@ void GlassView::add_spectrum_pixel(uint8_t power) {
}
plot_marker(marker_pixel_index);
} else {
display.draw_pixels({{0, display.scroll(1)}, {SCREEN_W, 1}}, spectrum_row); // new line at top, one less var, speedier
display.draw_pixels({{0, display.scroll(1)}, {screen_width, 1}}, spectrum_row); // new line at top, one less var, speedier
}
pixel_index = 0; // Start New cascade line
}
@@ -199,7 +199,7 @@ bool GlassView::process_bins(uint8_t* powerlevel) {
void GlassView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
baseband::spectrum_streaming_stop();
// Convert bins of this spectrum slice into a representative max_power and when enough, into pixels
// we actually need SCREEN_W (240) of those bins
// we actually need screen_width (240) of those bins
for (uint8_t bin = 0; bin < bin_length; bin++) {
get_max_power(spectrum, bin, max_power);
if (max_power > range_max_power)
@@ -238,7 +238,7 @@ void GlassView::on_hide() {
}
void GlassView::on_show() {
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
baseband::spectrum_streaming_start();
}
@@ -253,11 +253,11 @@ void GlassView::on_range_changed() {
// if the view is done in one pass, show it like in analog_audio_app
mode = LOOKING_GLASS_SINGLEPASS;
offset = 2;
bin_length = SCREEN_W;
bin_length = screen_width;
ignore_dc = 0;
looking_glass_bandwidth = looking_glass_range;
looking_glass_sampling_rate = looking_glass_range;
each_bin_size = looking_glass_bandwidth / SCREEN_W;
each_bin_size = looking_glass_bandwidth / screen_width;
looking_glass_step = looking_glass_bandwidth;
f_center_ini = f_min + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
} else {
@@ -269,7 +269,7 @@ void GlassView::on_range_changed() {
if (mode == LOOKING_GLASS_FASTSCAN) {
offset = 2;
ignore_dc = 4;
bin_length = SCREEN_W;
bin_length = screen_width;
} else { // if( mode == LOOKING_GLASS_SLOWSCAN )
offset = 2;
bin_length = 80;
@@ -279,7 +279,7 @@ void GlassView::on_range_changed() {
f_center_ini = f_min - (offset * each_bin_size) + (looking_glass_bandwidth / 2); // Initial center frequency for sweep
}
search_span = looking_glass_range / MHZ_DIV;
marker_pixel_step = looking_glass_range / SCREEN_W; // Each pixel value in Hz
marker_pixel_step = looking_glass_range / screen_width; // Each pixel value in Hz
pixel_index = 0;
max_power = 0;
@@ -304,10 +304,10 @@ void GlassView::plot_marker(uint8_t pos) {
{
shift_y = 16;
}
portapack::display.fill_rectangle({0, 100 + shift_y, SCREEN_W, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
portapack::display.fill_rectangle({0, 100 + shift_y, screen_width, 8}, Theme::getInstance()->bg_darkest->background); // Clear old marker and whole marker rectangle btw
portapack::display.fill_rectangle({pos - 2, 100 + shift_y, 5, 3}, Theme::getInstance()->fg_red->foreground); // Red marker top
portapack::display.fill_rectangle({pos - 1, 103 + shift_y, 3, 3}, Theme::getInstance()->fg_red->foreground); // Red marker middle
portapack::display.fill_rectangle({pos, 106 + shift_y, 1, 2}, Theme::getInstance()->fg_red->foreground); // Red marker bottom
}
void GlassView::update_min(int32_t v) {
@@ -358,7 +358,8 @@ GlassView::GlassView(
NavigationView& nav)
: nav_(nav) {
baseband::run_image(portapack::spi_flash::image_tag_wideband_spectrum);
spectrum_row.resize(screen_width);
spectrum_data.resize(screen_width);
if (!gradient.load_file(default_gradient_file)) {
gradient.set_default();
}
@@ -437,11 +438,11 @@ GlassView::GlassView(
freq_stats.hidden(true);
button_jump.hidden(true);
button_rst.hidden(true);
display.scroll_set_area(109, 319); // Restart scroll on the correct coordinates.
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates.
break;
case 1: // LEVEL
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -451,7 +452,7 @@ GlassView::GlassView(
case 2: // PEAK
default:
display.fill_rectangle({{0, 108}, {SCREEN_W, 24}}, {0, 0, 0});
display.fill_rectangle({{0, 108}, {screen_width, 24}}, {0, 0, 0});
display.scroll_disable();
level_integration.hidden(false);
freq_stats.hidden(false);
@@ -491,9 +492,9 @@ GlassView::GlassView(
field_marker.on_encoder_change = [this](TextField&, EncoderEvent delta) {
if ((marker_pixel_index + delta) < 0)
marker_pixel_index = marker_pixel_index + delta + SCREEN_W;
else if ((marker_pixel_index + delta) > SCREEN_W)
marker_pixel_index = marker_pixel_index + delta - SCREEN_W;
marker_pixel_index = marker_pixel_index + delta + screen_width;
else if ((marker_pixel_index + delta) > screen_width)
marker_pixel_index = marker_pixel_index + delta - screen_width;
else
marker_pixel_index = marker_pixel_index + delta;
on_marker_change();
@@ -531,7 +532,7 @@ GlassView::GlassView(
};
set_spec_iq_phase_calibration_value(get_spec_iq_phase_calibration_value()); // initialize iq_phase_calibration in radio
display.scroll_set_area(109, 319);
display.scroll_set_area(109, screen_height - 1); // Restart scroll on the correct coordinates
// trigger:
// Discord User jteich: WidebandSpectrum::on_message to set the trigger value. In WidebandSpectrum::execute,
@@ -539,7 +540,7 @@ GlassView::GlassView(
// at which time it pushes the buffer up with channel_spectrum.feed
baseband::set_spectrum(looking_glass_bandwidth, trigger);
marker_pixel_index = SCREEN_W / 2;
marker_pixel_index = screen_width / 2;
on_range_changed(); // Force a UI update.
receiver_model.set_sampling_rate(looking_glass_sampling_rate); // 20mhz
@@ -51,9 +51,6 @@ namespace ui {
#define LOOKING_GLASS_SINGLEPASS 2
// one spectrum line number of bins
#define SPEC_NB_BINS 256
// screen dimensions
#define SCREEN_W 240
#define SCREEN_H 320
class GlassView : public View {
public:
@@ -155,8 +152,8 @@ class GlassView : public View {
uint8_t min_color_power{0}; // Filter cutoff level.
uint32_t pixel_index{0};
std::array<Color, SCREEN_W> spectrum_row{};
std::array<uint8_t, SCREEN_W> spectrum_data{};
std::vector<Color> spectrum_row{};
std::vector<uint8_t> spectrum_data{};
ChannelSpectrumFIFO* fifo{};
int32_t steps = 1;
@@ -168,7 +165,7 @@ class GlassView : public View {
rf::Frequency max_freq_hold = 0;
rf::Frequency last_max_freq = 0;
int16_t max_freq_power = -1000;
uint8_t bin_length = SCREEN_W;
uint8_t bin_length = screen_width;
uint8_t offset = 0;
uint8_t ignore_dc = 0;
@@ -222,7 +219,7 @@ class GlassView : public View {
{}};
ButtonWithEncoder button_beep_squelch{
{240 - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
{screen_width - 8 * 8, 2 * 16 + 4, 8 * 8, 1 * 8},
""};
TextField field_marker{
@@ -293,15 +290,15 @@ class GlassView : public View {
}};
Button button_jump{
{SCREEN_W - 4 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 4 * 8, 5 * 16, 4 * 8, 16},
"JMP"};
Button button_rst{
{SCREEN_W - 9 * 8, 5 * 16, 4 * 8, 16},
{screen_width - 9 * 8, 5 * 16, 4 * 8, 16},
"RST"};
Text freq_stats{
{0 * 8, 5 * 16, SCREEN_W - 10 * 8, 8},
{0 * 8, 5 * 16, screen_width - 10 * 8, 8},
""};
MessageHandlerRegistration message_handler_spectrum_config{
+2 -2
View File
@@ -202,7 +202,7 @@ void MicTXView::rxaudio(bool enable) {
receiver_model.set_modulation(ReceiverModel::Mode::NarrowbandFMAudio);
// receiver_model.set_nbfm_configuration(n); is called above, depending user's selection (8k5, 11k, 16k).
break;
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 40k)
case MIC_MOD_WFM: // WFM, BW 200Khz aprox, or the two new addional BW filters (180k, 80k)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
// receiver_model.set_wfm_configuration(n); is called above, depending user's selection (200k, 180k, 0k).
@@ -300,7 +300,7 @@ void MicTXView::update_receiver_rxbw(void) {
receiver_model.set_nbfm_configuration(rxbw_index); // we are in NFM/FM case, we need to select proper NFM/FM RX channel filter, NFM BW 8K5(0), NFM BW 11K(1), FM BW 16K (2)
break;
case MIC_MOD_WFM:
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 40K(2)
receiver_model.set_wfm_configuration(rxbw_index); // we are in WFM case, we need to select proper WFB RX BW filter, WFM BW 200K(0), WFM BW 180K(1), WFM BW 80K(2)
break;
case MIC_MOD_AM:
receiver_model.set_am_configuration(rxbw_index); // we are in AM TX mode, we need to select proper AM full path config AM-9K filter. 0+0 =>AM-9K(0), 0+1=1 =>AM-6K(1),
+1 -1
View File
@@ -350,7 +350,7 @@ class MicTXView : public View {
};
Button tx_button{
{10 * 8, 30 * 8, 10 * 8, 5 * 8},
{10 * 8, screen_width, 10 * 8, 5 * 8},
"PTT TX",
true};
+3 -3
View File
@@ -107,7 +107,7 @@ class PlaylistView : public View {
void handle_replay_thread_done(uint32_t return_code);
Text text_filename{
{0 * 8, 0 * 16, 30 * 8, 16}};
{0 * 8, 0 * 16, screen_width, 16}};
FrequencyField field_frequency{
{0 * 8, 1 * 16}};
@@ -139,13 +139,13 @@ class PlaylistView : public View {
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
Text text_track{
{0 * 8, 3 * 16, 30 * 8, 16}};
{0 * 8, 3 * 16, screen_width, 16}};
NewButton button_prev{
{2 * 8, 4 * 16, 4 * 8, 2 * 16},
+2 -2
View File
@@ -122,10 +122,10 @@ class POCSAGTXView : public View {
}};
Text text_message{
{0 * 8, 16 * 8, 30 * 8, 16},
{0 * 8, 16 * 8, screen_width, 16},
""};
Text text_message_l2{
{0 * 8, 18 * 8, 30 * 8, 16},
{0 * 8, 18 * 8, screen_width, 16},
""};
Button button_message{
+1 -1
View File
@@ -160,7 +160,7 @@ class RDSView : public View {
void start_tx();
Rect view_rect = {0, 8 * 8, 240, 192};
Rect view_rect = {0, 8 * 8, screen_width, 192};
RDSPSNView view_PSN{nav_, view_rect};
RDSRadioTextView view_radiotext{nav_, view_rect};
+6 -6
View File
@@ -335,7 +335,7 @@ ReconView::ReconView(NavigationView& nav)
tx_view.hidden(true);
// set record View
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1164,18 +1164,18 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
}
if (new_mod == SPEC_MODULATION) {
if (persistent_memory::recon_repeat_recorded()) {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"RECON_REPEAT.C16", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_as_is(true);
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_RAW", captures_dir,
RecordView::FileType::RawS16, 16384, 3);
record_view->set_filename_date_frequency(true);
}
} else {
record_view = std::make_unique<RecordView>(Rect{0, 0, 30 * 8, 1 * 16},
record_view = std::make_unique<RecordView>(Rect{0, 0, screen_width, 1 * 16},
u"AUTO_AUDIO", audio_dir,
RecordView::FileType::WAV, 4096, 4);
record_view->set_filename_date_frequency(true);
@@ -1404,8 +1404,8 @@ void ReconView::start_repeat() {
std::string delay_message = "TX DELAY: " + to_string_dec_uint(delay) + "s";
// update display information
p.fill_rectangle({0, (SCREEN_H / 2) - 16, SCREEN_W, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(SCREEN_W / 2) - 7 * 8, SCREEN_H / 2}, *Theme::getInstance()->fg_red, delay_message);
p.fill_rectangle({0, (screen_height / 2) - 16, screen_width, 64}, Theme::getInstance()->fg_light->foreground);
p.draw_string({(screen_width / 2) - 7 * 8, screen_height / 2}, *Theme::getInstance()->fg_red, delay_message);
// sleep 1 second
chThdSleepMilliseconds(1000);
+7 -7
View File
@@ -237,15 +237,15 @@ class ReconView : public View {
Text file_name{
// show file used
{0, 1 * 16, SCREEN_W, 16},
{0, 1 * 16, screen_width, 16},
};
Text desc_cycle{
{0, 2 * 16, SCREEN_W, 16},
{0, 2 * 16, screen_width, 16},
};
RSSI rssi{
{0 * 16, 3 * 16 + 2, SCREEN_W - 8 * 8 + 4, 12},
{0 * 16, 3 * 16 + 2, screen_width - 8 * 8 + 4, 12},
};
ButtonWithEncoder text_cycle{
@@ -285,15 +285,15 @@ class ReconView : public View {
// Button can be RECON or SCANNER
Button button_scanner_mode{
{SCREEN_W - 7 * 8, 3 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 3 * 16, 7 * 8, 28},
"RECON"};
Button button_loop_config{
{SCREEN_W - 7 * 8, 5 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 5 * 16, 7 * 8, 28},
"[LOOP]"};
Button button_config{
{SCREEN_W - 7 * 8, 7 * 16, 7 * 8, 28},
{screen_width - 7 * 8, 7 * 16, 7 * 8, 28},
"CONFIG"};
ButtonWithEncoder button_manual_start{
@@ -391,7 +391,7 @@ class ReconView : public View {
"<REMOVE>"};
ProgressBar progressbar{
{0 * 8, SCREEN_H / 2 - 16, SCREEN_W, 32}};
{0 * 8, screen_height / 2 - 16, screen_width, 32}};
TransmitterView2 tx_view{
{11 * 8, 2 * 16},
@@ -57,10 +57,6 @@
#define RECON_MIN_LOCK_DURATION 100 // have to be >= and a multiple of STATS_UPDATE_INTERVAL
#define RECON_DEF_WAIT_DURATION 1000 // will be incremented/decremented by STATS_UPDATE_INTERVAL steps
// screen size helper
#define SCREEN_W 240
// #define SCREEN_H 320
// recon settings nb params
#define RECON_SETTINGS_NB_PARAMS 7
@@ -219,7 +215,7 @@ class ReconSetupView : public View {
std::string input_file{"RECON"};
std::string output_file{"RECON_RESULTS"};
Rect view_rect{0, 3 * 8, SCREEN_W, 230};
Rect view_rect{0, 3 * 8, screen_width, 230};
ReconSetupViewMain viewMain{nav_, view_rect, input_file, output_file};
ReconSetupViewMore viewMore{nav_, view_rect};
+7 -8
View File
@@ -759,7 +759,7 @@ AppSettingsView::AppSettingsView(
add_children({&labels,
&menu_view});
menu_view.set_parent_rect({0, 3 * 8, 240, 33 * 8});
menu_view.set_parent_rect({0, 3 * 8, screen_width, 33 * 8});
ensure_directory(settings_dir);
@@ -810,30 +810,29 @@ SetDisplayView::SetDisplayView(NavigationView& nav) {
&field_fake_brightness,
&button_save,
&button_cancel,
&checkbox_invert_switch,
&checkbox_ips_screen_switch,
&checkbox_brightness_switch});
field_fake_brightness.set_by_value(pmem::fake_brightness_level());
checkbox_brightness_switch.set_value(pmem::apply_fake_brightness());
checkbox_invert_switch.set_value(pmem::config_lcd_inverted_mode());
checkbox_ips_screen_switch.set_value(pmem::config_lcd_normally_black());
button_save.on_select = [&nav, this](Button&) {
pmem::set_apply_fake_brightness(checkbox_brightness_switch.value());
pmem::set_fake_brightness_level(field_fake_brightness.selected_index_value());
if (checkbox_invert_switch.value() != pmem::config_lcd_inverted_mode()) {
display.set_inverted(checkbox_invert_switch.value());
pmem::set_lcd_inverted_mode(checkbox_invert_switch.value());
if (checkbox_ips_screen_switch.value() != pmem::config_lcd_normally_black()) {
pmem::set_lcd_normally_black(checkbox_ips_screen_switch.value());
}
send_system_refresh();
nav.pop();
};
// only enable invert OR fake brightness
checkbox_invert_switch.on_select = [this](Checkbox&, bool v) {
checkbox_ips_screen_switch.on_select = [this](Checkbox&, bool v) {
if (v) checkbox_brightness_switch.set_value(false);
};
checkbox_brightness_switch.on_select = [this](Checkbox&, bool v) {
if (v) checkbox_invert_switch.set_value(false);
if (v) checkbox_ips_screen_switch.set_value(false);
};
button_cancel.on_select = [&nav, this](Button&) {
+7 -5
View File
@@ -713,7 +713,7 @@ class AppSettingsView : public View {
{{0, 4}, "Select file to edit:", Theme::getInstance()->bg_darkest->foreground}};
MenuView menu_view{
{0, 2 * 8, 240, 26 * 8},
{0, 2 * 8, screen_width, 26 * 8},
true};
};
@@ -762,6 +762,8 @@ class SetDisplayView : public View {
{{1 * 8, 2 * 16}, "(has a small performance", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 3 * 16}, "impact when enabled).", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 8 * 16}, "Brightness:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 10 * 16}, "REBOOT TO APPLY SCREEN TYPE", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_fake_brightness{
@@ -776,10 +778,10 @@ class SetDisplayView : public View {
16,
"Enable brightness adjust"};
Checkbox checkbox_invert_switch{
{1 * 8, 10 * 16},
Checkbox checkbox_ips_screen_switch{
{1 * 8, 12 * 16},
23,
"Invert colors (For IPS)"};
"IPS Screen"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
@@ -945,7 +947,7 @@ class SetThemeView : public View {
OptionsField options{
{0 * 8, 4 * 16},
screen_width / 8,
(size_t)(screen_width / 8),
{
{"Default - Grey", 0},
{"Yellow", 1},
+2 -2
View File
@@ -57,7 +57,7 @@ void SIGFRXView::paint(Painter& painter) {
uint8_t i, xp;
// portapack::display.draw_bmp_from_bmp_hex_arr({0, 302-160}, fox_bmp);
portapack::display.fill_rectangle({0, 16, 240, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 16, screen_width, 160 - 16}, Theme::getInstance()->bg_darkest->foreground);
for (i = 0; i < 6; i++) {
xp = sigfrx_marks[i * 3];
painter.draw_string({(ui::Coord)sigfrx_marks[(i * 3) + 1], 144 - 20}, style_white, to_string_dec_uint(sigfrx_marks[(i * 3) + 2]));
@@ -66,7 +66,7 @@ void SIGFRXView::paint(Painter& painter) {
}
void SIGFRXView::on_channel_spectrum(const ChannelSpectrum& spectrum) {
portapack::display.fill_rectangle({0, 144, 240, 4}, Theme::getInstance()->bg_darkest->foreground);
portapack::display.fill_rectangle({0, 144, screen_width, 4}, Theme::getInstance()->bg_darkest->foreground);
uint8_t xmax = 0, imax = 0;
size_t i;
+8 -1
View File
@@ -154,6 +154,13 @@ SigGenView::SigGenView(
};
};
tx_view.on_bandwidth_changed = [this]() {
// we don't protect here with auto_update because other field of tx_view obj isn't protected too
// to remains the design logic same
update_config();
};
tx_view.on_start = [this]() {
start_tx();
tx_view.set_transmitting(true);
@@ -165,4 +172,4 @@ SigGenView::SigGenView(
};
}
} /* namespace ui */
} /* namespace ui */
+1 -1
View File
@@ -120,7 +120,7 @@ class SondeView : public View {
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
Checkbox check_log{
{22 * 8, 8 * 16},
+2 -2
View File
@@ -58,7 +58,7 @@ void ScreenshotViewer::paint(Painter& painter) {
}
// Screenshots from PNGWriter are all this size.
if (file.size() != 232383) {
if ((file.size() != 232383 && screen_width == 240) || (screen_width == 320 && file.size() != 463743)) {
show_invalid();
return;
}
@@ -66,7 +66,7 @@ void ScreenshotViewer::paint(Painter& painter) {
constexpr size_t read_chunk = 80; // NB: must be a factor of pixel_width.
constexpr size_t buffer_size = sizeof(ColorRGB888) * read_chunk;
uint8_t buffer[buffer_size];
std::array<Color, screen_width> pixel_data;
std::vector<Color> pixel_data(screen_width);
// Seek past all the headers.
file.seek(43);
+1 -1
View File
@@ -192,7 +192,7 @@ class SubGhzDRecentEntryDetailView : public View {
Text text_id{{6 * 8, 2 * 16, 10 * 8, 16}, "?"};
Console console{
{0, 4 * 16, 240, screen_height - (4 * 16) - 36}};
{0, 4 * 16, screen_width, screen_height - (4 * 16) - 36}};
Labels labels{
{{0 * 8, 0 * 16}, "Type:", Theme::getInstance()->fg_light->foreground},
+2 -2
View File
@@ -99,8 +99,8 @@ void TestView::on_packet(const testapp::Packet& packet) {
display.draw_pixel(Point(cur_x, 4 * 16 + (256 - ((raw_alt - cal_value) / 4))), Color::white());
cur_x++;
if (cur_x >= 240) {
display.fill_rectangle(Rect(0, 5 * 16, 240, 256), Color::black());
if (cur_x >= screen_width) {
display.fill_rectangle(Rect(0, 5 * 16, screen_width, 256), Color::black());
cur_x = 0;
}
+2 -2
View File
@@ -95,10 +95,10 @@ class TestView : public View {
};
Text text_debug_a{
{0 * 8, 4 * 16, 30 * 8, 16},
{0 * 8, 4 * 16, screen_width, 16},
"..."};
Text text_debug_b{
{0 * 8, 5 * 16, 30 * 8, 16},
{0 * 8, 5 * 16, screen_width, 16},
"..."};
Button button_cal{
@@ -149,7 +149,7 @@ class WeatherView : public View {
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
+20 -3
View File
@@ -93,9 +93,9 @@ void NBFMConfig::apply(const uint8_t squelch_level) const {
void WFMConfig::apply() const {
const WFMConfigureMessage message{
decim_0, // taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_40k_wfm_decim_0
decim_1, // taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_40k_wfm_decim_1
taps_64_lp_156_198,
decim_0, // Dynamic array 24 taps : taps_200k_decim_0 , taps_180k_wfm_decim_0, taps_80k_wfm_decim_0
decim_1, // Dynamic array 16 taps : taps_200k_decim_1 or taps_180k_wfm_decim_1, taps_80k_wfm_decim_1
taps_64_lp_156_198, // Fixed channel audio filter 15khz
75000,
audio_48k_hpf_30hz_config,
audio_48k_deemph_2122_6_config};
@@ -103,6 +103,18 @@ void WFMConfig::apply() const {
audio::set_rate(audio::Rate::Hz_48000);
}
void WFMAMConfig::apply() const {
const WFMAMConfigureMessage message{
decim_0, // Fixed 24 taps array : taps_16k0_decim_0
decim_1, // Dynamic 32 taps array : taps_80k_wfmam_decim_1, 38k_wfmam
taps_64_lp_bpf, // Dynamic 64 taps array , to filter modulated DSB AM 2k4 carrier before demod. AM .(LPF / BPF)
17000, // NOAA satellite tx , FM deviation = +-17Khz.
apt_audio_12k_notch_2k4_config,
apt_audio_12k_lpf_2000hz_config};
send_message(&message);
audio::set_rate(audio::Rate::Hz_12000);
}
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration) {
shared_memory.bb_data.tones_data.tone_defs[index].delta = delta;
shared_memory.bb_data.tones_data.tone_defs[index].duration = duration;
@@ -310,6 +322,11 @@ void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
send_message(&message);
}
void set_noaaapt_config() {
const NoaaAptRxConfigureMessage message{};
send_message(&message);
}
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
+10 -1
View File
@@ -56,12 +56,20 @@ struct NBFMConfig {
};
struct WFMConfig {
const fir_taps_real<24> decim_0; // To handle both WFM filters , 200k and 40K for NOAA APT
const fir_taps_real<24> decim_0; // To handle all 3 WFM filters , 200k, 180k and 80K-
const fir_taps_real<16> decim_1;
void apply() const;
};
struct WFMAMConfig {
const fir_taps_real<24> decim_0; // To handle WFM filter BW=40K for NOAA APT
const fir_taps_real<32> decim_1;
const fir_taps_real<64> taps_64_lp_bpf; // to handle dynamically LPF / BPF .
void apply() const;
};
void set_tone(const uint32_t index, const uint32_t delta, const uint32_t duration);
void set_tones_config(const uint32_t bw, const uint32_t pre_silence, const uint16_t tone_count, const bool dual_tone, const bool audio_out);
void kill_tone();
@@ -93,6 +101,7 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void set_noaaapt_config();
void request_roger_beep();
void request_rssi_beep();
+38
View File
@@ -2435,6 +2435,44 @@ static constexpr Bitmap bitmap_icon_new_file{
{16, 16},
bitmap_icon_new_file_data};
static constexpr uint8_t bitmap_icon_noaa_data[] = {
0x1C,
0x80,
0x3C,
0x40,
0x78,
0x18,
0xF0,
0x20,
0xE0,
0x26,
0x00,
0x0F,
0x80,
0x0F,
0xC0,
0x07,
0xE1,
0x1B,
0xC5,
0x39,
0x95,
0x78,
0x35,
0xF0,
0x09,
0xE0,
0x72,
0xC0,
0x04,
0x00,
0x78,
0x00,
};
static constexpr Bitmap bitmap_icon_noaa{
{16, 16},
bitmap_icon_noaa_data};
static constexpr uint8_t bitmap_icon_notepad_data[] = {
0x0C,
0x00,
+2 -2
View File
@@ -87,10 +87,10 @@ class ACARSAppView : public View {
true};
Console console{
{0, 3 * 16, 240, 256}};
{0, 3 * 16, screen_width, 256}};
AudioVolumeField field_volume{
{28 * 8, 1 * 16}};
{screen_width - 2 * 8, 1 * 16}};
std::unique_ptr<ACARSLogger> logger{};
+1 -1
View File
@@ -218,7 +218,7 @@ class ADSBTxView : public View {
void start_tx();
void generate_frames();
Rect view_rect = {0, 7 * 8, 240, 192};
Rect view_rect = {0, 7 * 8, screen_width, 192};
ADSBPositionView view_position{nav_, view_rect};
ADSBCallsignView view_callsign{nav_, view_rect};
+2 -2
View File
@@ -84,7 +84,7 @@ class AFSKRxView : public View {
{21 * 8, 5, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -105,7 +105,7 @@ class AFSKRxView : public View {
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
{0, 4 * 16, 240, screen_width}};
{0, 4 * 16, screen_width, screen_width}};
void on_data_afsk(const AFSKDataMessage& message);
+1 -1
View File
@@ -58,7 +58,7 @@ class AnalogTvView : public View {
app_settings::SettingsManager settings_{
"rx_tv", app_settings::Mode::RX};
const Rect options_view_rect{0 * 8, 1 * 16, 30 * 8, 1 * 16};
const Rect options_view_rect{0 * 8, 1 * 16, screen_width, 1 * 16};
const Rect nbfm_view_rect{0 * 8, 1 * 16, 18 * 8, 1 * 16};
RSSI rssi{
@@ -86,7 +86,7 @@ class WhipCalcView : public View {
{13 * 8, 4 * 16, 10 * 16, 16},
"-"};
Console console{
{0, 6 * 16, 240, 160}};
{0, 6 * 16, screen_width, 160}};
Button button_exit{
{72, 17 * 16, 96, 32},
+1 -1
View File
@@ -119,7 +119,7 @@ class BLESpamView : public View {
#ifdef BLESPMUSECONSOLE
Console console{
{0, 70, 240, 220}};
{0, 70, screen_width, 220}};
#endif
OptionsField options_atkmode{
{0 * 8, 2 * 8},
+12 -10
View File
@@ -12,8 +12,8 @@ namespace ui::external_app::breakout {
Ticker game_timer;
int paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
float ball_x = SCREEN_WIDTH / 2;
int paddle_x = 0;
float ball_x = 0;
float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float ball_dx = 1.5f;
float ball_dy = -2.0f;
@@ -105,7 +105,7 @@ void game_timer_check() {
}
void init_game() {
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
score = 0;
lives = 3;
level = 1;
@@ -157,7 +157,7 @@ void draw_screen() {
}
void draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
rect(0, GAME_AREA_TOP - 1, screen_width, GAME_AREA_TOP, COLOR_BORDER);
}
void draw_bricks() {
@@ -218,14 +218,14 @@ void move_paddle_left() {
}
void move_paddle_right() {
if (paddle_x < SCREEN_WIDTH - PADDLE_WIDTH) {
if (paddle_x < screen_width - PADDLE_WIDTH) {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_BACKGROUND);
if (ball_attached) {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
}
paddle_x += 10;
if (paddle_x > SCREEN_WIDTH - PADDLE_WIDTH) paddle_x = SCREEN_WIDTH - PADDLE_WIDTH;
if (paddle_x > screen_width - PADDLE_WIDTH) paddle_x = screen_width - PADDLE_WIDTH;
if (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
@@ -277,8 +277,8 @@ void update_game() {
if (ball_x < 0) {
ball_x = 0;
ball_dx = -ball_dx;
} else if (ball_x > SCREEN_WIDTH - BALL_SIZE) {
ball_x = SCREEN_WIDTH - BALL_SIZE;
} else if (ball_x > screen_width - BALL_SIZE) {
ball_x = screen_width - BALL_SIZE;
ball_dx = -ball_dx;
}
@@ -381,7 +381,7 @@ void init_menu() {
auto style_blue = *ui::Theme::getInstance()->fg_blue;
auto style_cyan = *ui::Theme::getInstance()->fg_cyan;
int16_t screen_width = 240;
int16_t screen_width = ui::screen_width;
int16_t title_x = (screen_width - 17 * 8) / 2;
int16_t divider_width = 24 * 8;
int16_t divider_x = (screen_width - divider_width) / 2;
@@ -428,7 +428,7 @@ void init_game_over() {
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
int16_t screen_width = 240;
// int16_t screen_width = screen_width;
int16_t title_width = 9 * 8;
int16_t title_x = (screen_width - title_width) / 2;
int16_t score_text_width = (16 + std::to_string(score).length()) * 8;
@@ -484,6 +484,8 @@ void reset_game() {
BreakoutView::BreakoutView(NavigationView& nav)
: nav_{nav} {
paddle_x = (screen_width - PADDLE_WIDTH) / 2;
ball_x = screen_width / 2;
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
+1 -3
View File
@@ -56,8 +56,6 @@ class Ticker {
void detach();
};
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define PADDLE_WIDTH 40
#define PADDLE_HEIGHT 10
#define BALL_SIZE 8
@@ -141,7 +139,7 @@ class BreakoutView : public View {
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
{screen_width, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
+2 -2
View File
@@ -53,7 +53,7 @@ class CVSSpamView : public View {
std::vector<std::filesystem::path> file_list{};
MenuView menu_view{
{0, 0, 240, 180},
{0, 0, screen_width, 180},
true};
Text text_empty{
@@ -84,7 +84,7 @@ class CVSSpamView : public View {
LanguageHelper::currentMessages[LANG_STOP]};
ProgressBar progressbar{
{0, 256, 240, 44}};
{0, 256, screen_width, 44}};
MessageHandlerRegistration message_handler_fifo_signal{
Message::ID::RequestSignal,
+4 -1
View File
@@ -80,7 +80,7 @@ bool DebugDumpView::debug_dump_func() {
pmem_dump_file.write_line("updown_converter: " + to_string_dec_int(config_updown_converter()));
pmem_dump_file.write_line("updown_frequency_rx_correction: " + to_string_dec_int(config_freq_rx_correction_updown()));
pmem_dump_file.write_line("updown_frequency_tx_correction: " + to_string_dec_int(config_freq_tx_correction_updown()));
pmem_dump_file.write_line("lcd_inverted_mode: " + to_string_dec_uint(config_lcd_inverted_mode()));
pmem_dump_file.write_line("lcd_normally_black: " + to_string_dec_uint(config_lcd_normally_black()));
pmem_dump_file.write_line("converter_frequency_offset: " + to_string_dec_int(config_converter_freq()));
pmem_dump_file.write_line("frequency_rx_correction: " + to_string_dec_uint(config_freq_rx_correction()));
pmem_dump_file.write_line("frequency_tx_correction: " + to_string_dec_uint(config_freq_tx_correction()));
@@ -157,6 +157,9 @@ bool DebugDumpView::debug_dump_func() {
case ReceiverModel::Mode::WidebandFMAudio:
pmem_dump_file.write_line("modulation: Mode::WidebandFMAudio");
break;
case ReceiverModel::Mode::WFMAudioAMApt:
pmem_dump_file.write_line("modulation: Mode::WFMAudioAMApt");
break;
case ReceiverModel::Mode::SpectrumAnalysis:
pmem_dump_file.write_line("modulation: Mode::SpectrumAnalysis");
break;
+1 -1
View File
@@ -44,7 +44,7 @@ class DebugDumpView : public View {
NavigationView& nav_;
Text dump_output{
{0 * 8, 19 * 8, 30 * 8, 16},
{0 * 8, 19 * 8, screen_width, 16},
""};
Button button_exit{
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 Bernd Herzog
*
* 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.hpp"
#include "ui_detector_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::detector_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<DetectorRxView>();
}
} // namespace ui::external_app::detector_rx
extern "C" {
__attribute__((section(".external_app.app_detector_rx.application_information"), used)) application_information_t _application_information_detector_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::detector_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Detector",
/*.bitmap_data = */
{
0x00,
0x00,
0x00,
0x00,
0x04,
0x20,
0x12,
0x48,
0x8A,
0x51,
0xCA,
0x53,
0xCA,
0x53,
0x8A,
0x51,
0x12,
0x48,
0x84,
0x21,
0xC0,
0x03,
0x40,
0x02,
0x60,
0x06,
0x20,
0x04,
0x30,
0x0C,
0xF0,
0x0F},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
// this has to be the biggest baseband used by the app. SPEC
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* 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_detector_rx.hpp"
#include "ui_fileman.hpp"
#include "ui_freqman.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "oversample.hpp"
#include "ui_font_fixed_8x16.hpp"
using namespace portapack;
using namespace tonekey;
using portapack::memory::map::backup_ram;
namespace ui::external_app::detector_rx {
// Function to map the value from one range to another
int32_t DetectorRxView::map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh) {
return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow);
}
void DetectorRxView::focus() {
field_lna.focus();
}
DetectorRxView::~DetectorRxView() {
// reset performance counters request to default
shared_memory.request_m4_performance_counter = 0;
receiver_model.disable();
audio::output::stop();
baseband::shutdown();
}
void DetectorRxView::on_timer() {
freq_index++;
if (freq_mode == 0 && freq_index >= tetra_uplink_monitoring_frequencies_hz.size()) freq_index = 0; // TETRA UP
if (freq_mode == 1 && freq_index >= lora_monitoring_frequencies_hz.size()) freq_index = 0; // Lora
if (freq_mode == 2 && freq_index >= remotes_monitoring_frequencies_hz.size()) freq_index = 0; // Remotes
if (freq_mode == 2)
freq_ = remotes_monitoring_frequencies_hz[freq_index];
else if (freq_mode == 1)
freq_ = lora_monitoring_frequencies_hz[freq_index];
else
freq_ = tetra_uplink_monitoring_frequencies_hz[freq_index];
receiver_model.set_target_frequency(freq_);
}
DetectorRxView::DetectorRxView(NavigationView& nav)
: nav_{nav} {
add_children({
&labels,
&field_lna,
&field_vga,
&field_rf_amp,
&field_volume,
&text_frequency,
&freq_stats_rssi,
&freq_stats_db,
&text_beep_squelch,
&field_beep_squelch,
&rssi,
&rssi_graph,
&field_mode,
});
// activate vertical bar mode
rssi.set_vertical_rssi(true);
freq_ = receiver_model.target_frequency();
field_beep_squelch.set_value(beep_squelch);
field_beep_squelch.on_change = [this](int32_t v) {
beep_squelch = v;
};
rssi_graph.set_nb_columns(256);
change_mode();
rssi.set_peak(true, 3000);
// FILL STEP OPTIONS
freq_stats_rssi.set_style(Theme::getInstance()->bg_darkest);
freq_stats_db.set_style(Theme::getInstance()->bg_darkest);
field_mode.on_change = [this](size_t, int32_t value) {
freq_mode = value;
freq_index = 0;
switch (value) {
case 1: // Lora
text_frequency.set(" 433, 868, 915 Mhz");
break;
case 2: // Remotes
text_frequency.set(" 433, 315 Mhz");
break;
default:
case 0: // TETRA UP
text_frequency.set(" 380-390 Mhz");
break;
}
};
}
void DetectorRxView::on_statistics_update(const ChannelStatistics& statistics) {
static int16_t last_max_db = 0;
static uint8_t last_min_rssi = 0;
static uint8_t last_avg_rssi = 0;
static uint8_t last_max_rssi = 0;
rssi_graph.add_values(rssi.get_min(), rssi.get_avg(), rssi.get_max(), statistics.max_db);
// refresh db
if (last_max_db != statistics.max_db) {
last_max_db = statistics.max_db;
freq_stats_db.set("Power: " + to_string_dec_int(statistics.max_db) + " db");
rssi.set_db(statistics.max_db);
}
// refresh rssi
if (last_min_rssi != rssi_graph.get_graph_min() || last_avg_rssi != rssi_graph.get_graph_avg() || last_max_rssi != rssi_graph.get_graph_max()) {
last_min_rssi = rssi_graph.get_graph_min();
last_avg_rssi = rssi_graph.get_graph_avg();
last_max_rssi = rssi_graph.get_graph_max();
freq_stats_rssi.set("RSSI: " + to_string_dec_uint(last_min_rssi) + "/" + to_string_dec_uint(last_avg_rssi) + "/" + to_string_dec_uint(last_max_rssi));
}
if (statistics.max_db > beep_squelch) {
baseband::request_audio_beep(map(statistics.max_db, -100, 20, 400, 2600), 24000, 150);
}
} /* on_statistic_updates */
size_t DetectorRxView::change_mode() {
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
audio_sampling_rate = audio::Rate::Hz_24000;
baseband::run_image(portapack::spi_flash::image_tag_capture);
receiver_model.set_modulation(ReceiverModel::Mode::Capture);
baseband::set_sample_rate(DETECTOR_BW, get_oversample_rate(DETECTOR_BW));
// The radio needs to know the effective sampling rate.
auto actual_sampling_rate = get_actual_sample_rate(DETECTOR_BW);
receiver_model.set_sampling_rate(actual_sampling_rate);
receiver_model.set_baseband_bandwidth(filter_bandwidth_for_sampling_rate(actual_sampling_rate));
audio::set_rate(audio_sampling_rate);
audio::output::start();
receiver_model.set_headphone_volume(receiver_model.headphone_volume()); // WM8731 hack.
receiver_model.enable();
return 0;
}
} // namespace ui::external_app::detector_rx
@@ -0,0 +1,201 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2018 Furrtek
* Copyright (C) 2023 gullradriel, Nilorea Studio Inc.
*
* 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.
*/
#ifndef _UI_DETECTOR_RX
#define _UI_DETECTOR_RX
#include "analog_audio_app.hpp"
#include "app_settings.hpp"
#include "audio.hpp"
#include "baseband_api.hpp"
#include "file.hpp"
#include "freqman_db.hpp"
#include "portapack_persistent_memory.hpp"
#include "radio_state.hpp"
#include "receiver_model.hpp"
#include "string_format.hpp"
#include "ui.hpp"
#include "ui_mictx.hpp"
#include "ui_receiver.hpp"
#include "ui_spectrum.hpp"
namespace ui::external_app::detector_rx {
#define DETECTOR_BW 750000
class DetectorRxView : public View {
public:
DetectorRxView(NavigationView& nav);
~DetectorRxView();
void focus() override;
std::string title() const override { return "Detector RX"; };
private:
NavigationView& nav_;
RxRadioState radio_state_{};
int32_t map(int32_t value, int32_t fromLow, int32_t fromHigh, int32_t toLow, int32_t toHigh);
size_t change_mode();
void on_statistics_update(const ChannelStatistics& statistics);
void set_display_freq(int64_t freq);
void on_timer();
uint8_t freq_index = 0;
rf::Frequency freq_ = {433920000};
int32_t beep_squelch = 0;
audio::Rate audio_sampling_rate = audio::Rate::Hz_48000;
uint8_t freq_mode = 0;
app_settings::SettingsManager settings_{
"rx_detector",
app_settings::Mode::RX,
{
{"beep_squelch"sv, &beep_squelch},
}};
Labels labels{
{{UI_POS_X(0), UI_POS_Y(0)}, "LNA: VGA: AMP: VOL: ", Theme::getInstance()->fg_light->foreground}};
LNAGainField field_lna{
{UI_POS_X(4), UI_POS_Y(0)}};
VGAGainField field_vga{
{UI_POS_X(11), UI_POS_Y(0)}};
RFAmpField field_rf_amp{
{UI_POS_X(18), UI_POS_Y(0)}};
AudioVolumeField field_volume{
{UI_POS_X(24), UI_POS_Y(0)}};
OptionsField field_mode{
{UI_POS_X(0), UI_POS_Y(1)},
9,
{
{"TETRA UP", 0},
{"Lora", 1},
{"Remotes", 2},
}};
Text text_frequency{
{UI_POS_X_RIGHT(20), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
""};
Text text_beep_squelch{
{UI_POS_X_RIGHT(9), UI_POS_Y(2), UI_POS_WIDTH(4), UI_POS_DEFAULT_HEIGHT},
"Bip>"};
NumberField field_beep_squelch{
{UI_POS_X_RIGHT(5), UI_POS_Y(2)},
4,
{-100, 20},
1,
' ',
};
// RSSI: XX/XX/XXX
Text freq_stats_rssi{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
// Power: -XXX db
Text freq_stats_db{
{UI_POS_X(0), UI_POS_Y(3), UI_POS_WIDTH(15), UI_POS_DEFAULT_HEIGHT},
};
RSSIGraph rssi_graph{
{UI_POS_X(0), UI_POS_Y(5), UI_POS_WIDTH_REMAINING(5), UI_POS_HEIGHT_REMAINING(6)},
};
RSSI rssi{
{UI_POS_X_RIGHT(5), UI_POS_Y(5), UI_POS_WIDTH(5), UI_POS_HEIGHT_REMAINING(6)},
};
MessageHandlerRegistration message_handler_stats{
Message::ID::ChannelStatistics,
[this](const Message* const p) {
this->on_statistics_update(static_cast<const ChannelStatisticsMessage*>(p)->statistics);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
const std::vector<uint32_t> remotes_monitoring_frequencies_hz = {
// Around 315 MHz (common for older remotes, key fobs in some regions)
// Window centered on 315 MHz, covers 314.625 - 315.375 MHz
315000000,
// Around 433.92 MHz (very common for remotes, sensors, key fobs globally)
// Window centered on 433.92 MHz, covers 433.545 - 434.295 MHz
433920000,
};
const std::vector<uint32_t> lora_monitoring_frequencies_hz = {
// EU433 Band (Europe, typically 433.05 MHz to 434.79 MHz)
// Scanning the approximate range 433.0 MHz to 434.8 MHz with 750kHz steps
433375000, // Covers 433.000 - 433.750 MHz
434125000, // Covers 433.750 - 434.500 MHz (includes 433.92 MHz)
434875000, // Covers 434.500 - 435.250 MHz (covers up to 434.79 MHz)
// EU868 Band (Europe, typically 863 MHz to 870 MHz, specific channels around 868 MHz)
// Targeting common LoRaWAN channel groups (approx 867.0 - 868.6 MHz) with 750kHz steps
867375000, // Covers 867.000 - 867.750 MHz
868125000, // Covers 867.750 - 868.500 MHz
868875000, // Covers 868.500 - 869.250 MHz (covers up to 868.6 MHz)
// US915 Band (North America, typically 902 MHz to 928 MHz, specific channels around 915 MHz)
// Providing a few sample windows around the 915 MHz area with 750kHz steps.
// This band is wide; a full scan would require many more frequencies.
914250000, // Covers 913.875 - 914.625 MHz
915000000, // Covers 914.625 - 915.375 MHz (Centered on 915 MHz)
915750000, // Covers 915.375 - 916.125 MHz
};
const std::vector<uint32_t> tetra_uplink_monitoring_frequencies_hz = {
// Band starts at 380,000,000 Hz, ends at 390,000,000 Hz.
// First center: 380,000,000 + 375,000 = 380,375,000 Hz
// Last center: 380,375,000 + 13 * 750,000 = 390,125,000 Hz (14 frequencies total for this band)
380375000, // Covers 380.000 - 380.750 MHz
381125000, // Covers 380.750 - 381.500 MHz
381875000, // Covers 381.500 - 382.250 MHz
382625000, // Covers 382.250 - 383.000 MHz
383375000, // Covers 383.000 - 383.750 MHz
384125000, // Covers 383.750 - 384.500 MHz
384875000, // Covers 384.500 - 385.250 MHz
385625000, // Covers 385.250 - 386.000 MHz
386375000, // Covers 386.000 - 386.750 MHz
387125000, // Covers 386.750 - 387.500 MHz
387875000, // Covers 387.500 - 388.250 MHz
388625000, // Covers 388.250 - 389.000 MHz
389375000, // Covers 389.000 - 389.750 MHz
390125000, // Covers 389.750 - 390.500 MHz
};
};
} // namespace ui::external_app::detector_rx
#endif
+10 -5
View File
@@ -12,11 +12,11 @@
namespace ui::external_app::doom {
//clang-format off
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define RENDER_HEIGHT 280
#define HALF_WIDTH (SCREEN_WIDTH / 2)
#define HALF_HEIGHT (RENDER_HEIGHT / 2)
int SCREEN_WIDTH = 0;
int SCREEN_HEIGHT = 0;
int RENDER_HEIGHT = 0;
int HALF_WIDTH = 0;
int HALF_HEIGHT = 0;
#define LEVEL_WIDTH_BASE 6
#define LEVEL_WIDTH (1 << LEVEL_WIDTH_BASE)
#define LEVEL_HEIGHT 57
@@ -1001,6 +1001,11 @@ void render_map(Painter& painter, bool full_clear, int16_t x_start = 0, int16_t
DoomView::DoomView(NavigationView& nav)
: nav_{nav} {
SCREEN_WIDTH = screen_width;
SCREEN_HEIGHT = screen_height;
RENDER_HEIGHT = screen_height - 40;
HALF_WIDTH = screen_width / 2;
HALF_HEIGHT = RENDER_HEIGHT / 2;
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
+1 -1
View File
@@ -70,7 +70,7 @@ class DoomView : public View {
private:
NavigationView& nav_;
Button dummy{{240, 0, 0, 0}, ""};
Button dummy{{screen_width, 0, 0, 0}, ""};
bool initialized{false};
bool prev_velocity_moving{false};
MessageHandlerRegistration message_handler_frame_sync{
+11
View File
@@ -111,6 +111,11 @@ set(EXTCPPSRC
external/wefax_rx/main.cpp
external/wefax_rx/ui_wefax_rx.cpp
#noaaapt_rx
external/noaaapt_rx/main.cpp
external/noaaapt_rx/ui_noaaapt_rx.cpp
#shoppingcart_lock
external/shoppingcart_lock/main.cpp
@@ -207,6 +212,10 @@ set(EXTCPPSRC
#gfxEQ
external/gfxeq/main.cpp
external/gfxeq/ui_gfxeq.cpp
#detector_rx
external/detector_rx/main.cpp
external/detector_rx/ui_detector_rx.cpp
)
set(EXTAPPLIST
@@ -239,6 +248,7 @@ set(EXTAPPLIST
ookbrute
ook_editor
wefax_rx
noaaapt_rx
shoppingcart_lock
flippertx
remote
@@ -260,4 +270,5 @@ set(EXTAPPLIST
scanner
level
gfxeq
detector_rx
)
+18
View File
@@ -73,6 +73,8 @@ MEMORY
ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k
ram_external_app_level (rwx) : org = 0xADE10000, len = 32k
ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k
ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k
ram_external_app_detector_rx (rwx) : org = 0xADE40000, len = 32k
}
SECTIONS
@@ -334,12 +336,19 @@ SECTIONS
KEEP(*(.external_app.app_stopwatch.application_information));
*(*ui*external_app*stopwatch*);
} > ram_external_app_stopwatch
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_wefax_rx.application_information));
*(*ui*external_app*wefax_rx*);
} > ram_external_app_wefax_rx
.external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_noaaapt_rx.application_information));
*(*ui*external_app*noaaapt_rx*);
} > ram_external_app_noaaapt_rx
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
@@ -375,4 +384,13 @@ SECTIONS
KEEP(*(.external_app.app_gfxeq.application_information));
*(*ui*external_app*gfxeq*);
} > ram_external_app_gfxeq
.external_app_detector_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_detector_rx.application_information));
*(*ui*external_app*detector_rx*);
} > ram_external_app_detector_rx
}
+1 -1
View File
@@ -60,7 +60,7 @@ class ExtSensorsView : public View {
{{0 * 8, 5 * 16}, "ORI:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "ENV:", Theme::getInstance()->fg_light->foreground}};
Text text_info{{0 * 8, 0 * 8, 30 * 8, 16 * 1}, "Connect a compatible module..."};
Text text_info{{0 * 8, 0 * 8, screen_width, 16 * 1}, "Connect a compatible module..."};
Text text_gps{{5 * 8, 3 * 16, 24 * 8, 16}, "-"};
Text text_orientation{{5 * 8, 5 * 16, 24 * 8, 16}, "-"};
Text text_envl1{{5 * 8, 7 * 16, 24 * 8, 16}, "-"};
+2 -2
View File
@@ -89,7 +89,7 @@ class FmRadioView : public View {
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -103,7 +103,7 @@ class FmRadioView : public View {
{21 * 8, 10, 6 * 8, 4}};
Waveform waveform{
{0, 20, 30 * 8, 2 * 16},
{0, 20, screen_width, 2 * 16},
audio_spectrum,
128,
0,
+3 -3
View File
@@ -62,7 +62,7 @@ class FoxhuntRxView : public View {
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{screen_width - 2 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
@@ -73,7 +73,7 @@ class FoxhuntRxView : public View {
{0 * 8, 2 * 16 + 4, 14 * 8, 14},
};
RSSIGraph rssi_graph{
{0, 50, 240, 30},
{0, 50, screen_width, 30},
};
Button clear_markers{
@@ -84,7 +84,7 @@ class FoxhuntRxView : public View {
{2, 18, 7 * 8, 16},
"Mark"};
GeoMap geomap{{0, 80, 240, 240}};
GeoMap geomap{{0, 80, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_gps{
Message::ID::GPSPosData,
+2 -2
View File
@@ -29,8 +29,8 @@ __attribute__((section(".external_app.app_gfxeq.application_information"), used)
ui::Color::green().v,
app_location_t::RX,
-1,
{'P', 'N', 'F', 'M'},
{'P', 'W', 'F', 'M'},
0x00000000,
};
} // namespace ui::external_app::gfxeq
} // namespace ui::external_app::gfxeq
+4 -4
View File
@@ -32,7 +32,7 @@ gfxEQView::gfxEQView(NavigationView& nav)
baseband::run_image(portapack::spi_flash::image_tag_wfm_audio);
receiver_model.set_modulation(ReceiverModel::Mode::WidebandFMAudio);
receiver_model.set_wfm_configuration(1); // 200k => 0 , 180k => 1 , 40k => 2. Set to 1 or 2 for better reception
receiver_model.set_wfm_configuration(1); // 200k => 0 , 180k => 1 , 80k => 2. Set to 1 or 2 for better reception
receiver_model.set_sampling_rate(3072000);
receiver_model.set_baseband_bandwidth(1750000);
@@ -165,12 +165,12 @@ void gfxEQView::render_equalizer(Painter& painter) {
if (prev_bar_heights[bar] > active_segments) {
int clear_height = (prev_bar_heights[bar] - active_segments) * SEGMENT_HEIGHT;
int clear_y = SCREEN_HEIGHT - prev_bar_heights[bar] * SEGMENT_HEIGHT;
int clear_y = screen_height - prev_bar_heights[bar] * SEGMENT_HEIGHT;
painter.fill_rectangle({x, clear_y, BAR_WIDTH, clear_height}, Color(0, 0, 0));
}
for (int seg = 0; seg < active_segments; seg++) {
int y = SCREEN_HEIGHT - (seg + 1) * SEGMENT_HEIGHT;
int y = screen_height - (seg + 1) * SEGMENT_HEIGHT;
if (y < header_height) break;
Color segment_color = (seg >= active_segments - 2 && seg < active_segments) ? theme.peak_color : theme.base_color;
@@ -183,7 +183,7 @@ void gfxEQView::render_equalizer(Painter& painter) {
void gfxEQView::paint(Painter& painter) {
if (needs_background_redraw) {
painter.fill_rectangle({0, header_height, SCREEN_WIDTH, RENDER_HEIGHT}, Color(0, 0, 0));
painter.fill_rectangle({0, header_height, screen_width, RENDER_HEIGHT}, Color(0, 0, 0));
needs_background_redraw = false;
}
render_equalizer(painter);
+2 -4
View File
@@ -40,12 +40,10 @@ class gfxEQView : public View {
private:
static constexpr ui::Dim header_height = 2 * 16;
static constexpr int SCREEN_WIDTH = 240;
static constexpr int SCREEN_HEIGHT = 320;
static constexpr int RENDER_HEIGHT = 288;
static constexpr int NUM_BARS = 11;
static constexpr int BAR_SPACING = 2;
static constexpr int BAR_WIDTH = (SCREEN_WIDTH - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
int BAR_WIDTH = (screen_width - (BAR_SPACING * (NUM_BARS - 1))) / NUM_BARS;
static constexpr int HORIZONTAL_OFFSET = 2;
static constexpr int SEGMENT_HEIGHT = 10;
@@ -101,7 +99,7 @@ class gfxEQView : public View {
LNAGainField field_lna{{15 * 8, 0 * 16}};
VGAGainField field_vga{{18 * 8, 0 * 16}};
Button button_mood{{21 * 8, 0, 6 * 8, 16}, "MOOD"};
AudioVolumeField field_volume{{28 * 8, 0 * 16}};
AudioVolumeField field_volume{{screen_width - 2 * 8, 0 * 16}};
rf::Frequency frequency_value{93100000};
+1 -1
View File
@@ -112,7 +112,7 @@ class GpsSimAppView : public View {
LanguageHelper::currentMessages[LANG_LOOP],
true};
ImageButton button_play{
{28 * 8, 2 * 16, 2 * 8, 1 * 16},
{screen_width - 2 * 8, 2 * 16, 2 * 8, 1 * 16},
&bitmap_play,
Theme::getInstance()->fg_green->foreground,
Theme::getInstance()->fg_green->background};
+130 -49
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
*
* This file is part of PortaPack.
*
@@ -36,7 +37,6 @@ void RangeView::focus() {
}
void RangeView::update_start(rf::Frequency f) {
// Change everything except max
frequency_range.min = f;
button_start.set_text(to_string_short_freq(f));
@@ -48,7 +48,6 @@ void RangeView::update_start(rf::Frequency f) {
}
void RangeView::update_stop(rf::Frequency f) {
// Change everything except min
frequency_range.max = f;
button_stop.set_text(to_string_short_freq(f));
@@ -60,7 +59,6 @@ void RangeView::update_stop(rf::Frequency f) {
}
void RangeView::update_center(rf::Frequency f) {
// Change min/max/center, keep width
center = f;
button_center.set_text(to_string_short_freq(center));
@@ -75,7 +73,6 @@ void RangeView::update_center(rf::Frequency f) {
}
void RangeView::update_width(uint32_t w) {
// Change min/max/width, keep center
width = w;
button_width.set_text(to_string_short_freq(width));
@@ -91,7 +88,6 @@ void RangeView::update_width(uint32_t w) {
}
void RangeView::paint(Painter&) {
// Draw lines and arrows
Rect r;
Point p;
Coord c;
@@ -162,7 +158,7 @@ RangeView::RangeView(NavigationView& nav) {
auto load_view = nav.push<FrequencyLoadView>();
load_view->on_frequency_loaded = [this](rf::Frequency value) {
update_center(value);
update_width(100000); // 100kHz default jamming bandwidth when loading unique frequency
update_width(100000);
};
load_view->on_range_loaded = [this](rf::Frequency start, rf::Frequency stop) {
update_start(start);
@@ -193,10 +189,37 @@ void JammerView::set_jammer_channel(uint32_t i, uint32_t width, uint64_t center,
jammer_channels[i].enabled = true;
jammer_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
jammer_channels[i].center = center;
jammer_channels[i].duration = 30720 * duration;
jammer_channels[i].duration = duration ? 30720 * duration : 3000;
}
void JammerView::start_tx() {
if (update_config()) {
jamming = true;
button_transmit.set_style(&style_cancel);
button_transmit.set_text("STOP");
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000);
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
mscounter = 0;
}
}
void JammerView::stop_tx() {
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
transmitter_model.disable();
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
jamming = false;
cooling = false;
seconds = 0;
mscounter = 0;
}
bool JammerView::update_config() {
uint32_t c, i = 0;
size_t num_channels;
rf::Frequency start_freq, range_bw, range_bw_sub, ch_width;
@@ -204,24 +227,19 @@ void JammerView::start_tx() {
size_t hop_value = options_hop.selected_index_value();
// Disable all channels by default
for (c = 0; c < JAMMER_MAX_CH; c++)
jammer_channels[c].enabled = false;
// Generate jamming channels with JAMMER_MAX_CH maximum width
// Convert ranges min/max to center/bw
for (size_t r = 0; r < 3; r++) {
if (range_views[r]->frequency_range.enabled) {
range_bw = abs(range_views[r]->frequency_range.max - range_views[r]->frequency_range.min);
// Get lower bound
if (range_views[r]->frequency_range.min < range_views[r]->frequency_range.max)
start_freq = range_views[r]->frequency_range.min;
else
start_freq = range_views[r]->frequency_range.max;
if (range_bw >= JAMMER_CH_WIDTH) {
// Split range in multiple channels
num_channels = 0;
range_bw_sub = range_bw;
@@ -241,7 +259,6 @@ void JammerView::start_tx() {
i++;
}
} else {
// Range fits in a single channel
if (i >= JAMMER_MAX_CH) {
out_of_ranges = true;
} else {
@@ -254,43 +271,24 @@ void JammerView::start_tx() {
if (!out_of_ranges && i) {
text_range_total.set("/" + to_string_dec_uint(i, 2));
jamming = true;
button_transmit.set_style(&style_cancel);
button_transmit.set_text("STOP");
transmitter_model.set_rf_amp(field_amp.value());
transmitter_model.set_tx_gain(field_gain.value());
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
transmitter_model.enable();
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
mscounter = 0; // euquiq: Reset internal ms counter for do_timer()
return true;
} else {
if (out_of_ranges)
nav_.display_modal("Error", "Jamming bandwidth too large.\nMust be less than 24MHz.");
else
nav_.display_modal("Error", "No range enabled.");
return false;
}
}
void JammerView::stop_tx() {
button_transmit.set_style(&style_val);
button_transmit.set_text("START");
transmitter_model.disable();
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
jamming = false;
cooling = false;
}
// called each 1/60th of second
void JammerView::on_timer() {
if (++mscounter == 60) {
if (++mscounter >= 60) {
mscounter = 0;
if (jamming) {
int32_t timepause = field_timepause.value();
if (cooling) {
if (++seconds >= field_timepause.value()) { // Re-start TX
transmitter_model.set_baseband_bandwidth(28'000'000); // Although tx is narrowband , let's use Max TX LPF .
if (timepause == 0 || ++seconds >= timepause) {
transmitter_model.set_baseband_bandwidth(28'000'000);
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
@@ -306,8 +304,7 @@ void JammerView::on_timer() {
seconds = 0;
}
} else {
if (++seconds >= field_timetx.value()) // Start cooling period:
{
if (timepause && ++seconds >= field_timetx.value()) {
transmitter_model.disable();
button_transmit.set_text("PAUSED");
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
@@ -327,11 +324,9 @@ void JammerView::on_timer() {
}
}
JammerView::JammerView(
NavigationView& nav)
JammerView::JammerView(NavigationView& nav)
: nav_{nav} {
Rect view_rect = {0, 3 * 8, 240, 80};
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
Rect view_rect = {0, 3 * 8, screen_width, 80};
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&tab_view,
@@ -355,15 +350,101 @@ JammerView::JammerView(
view_range_b.set_parent_rect(view_rect);
view_range_c.set_parent_rect(view_rect);
options_type.set_selected_index(3); // Rand CW
options_speed.set_selected_index(3); // 10kHz
options_hop.set_selected_index(1); // 50ms
button_transmit.set_style(&style_val);
view_range_a.check_enabled.set_value(true);
view_range_a.frequency_range.enabled = true;
view_range_a.update_center(315'000'000);
view_range_a.update_width(1'000'000);
options_type.set_selected_index(3);
options_speed.set_selected_index(3);
options_hop.set_selected_index(0);
field_timetx.set_value(30);
field_timepause.set_value(1);
field_timepause.set_value(0);
field_jitter.set_value(0);
field_gain.set_value(transmitter_model.tx_gain());
field_amp.set_value(transmitter_model.rf_amp());
button_transmit.set_style(&style_val);
options_type.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
};
options_speed.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
if (jamming && !cooling) baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
};
options_hop.on_change = [this](size_t, OptionsField::value_t) {
if (jamming) update_config();
};
field_timetx.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_timepause.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_jitter.on_change = [this](int32_t) {
if (jamming) update_config();
};
field_gain.on_change = [this](int32_t v) {
if (jamming) transmitter_model.set_tx_gain(v);
};
field_amp.on_change = [this](int32_t v) {
if (jamming) transmitter_model.set_rf_amp(v);
};
for (auto range_view : range_views) {
range_view->check_enabled.on_select = [this](Checkbox&, bool) {
if (jamming) update_config();
};
range_view->button_start.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.min);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_start(f);
if (jamming) update_config();
};
};
range_view->button_stop.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->frequency_range.max);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_stop(f);
if (jamming) update_config();
};
};
range_view->button_center.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->center);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_center(f);
if (jamming) update_config();
};
};
range_view->button_width.on_select = [this, range_view](Button&) {
auto new_view = nav_.push<FrequencyKeypadView>(range_view->width);
new_view->on_changed = [this, range_view](rf::Frequency f) {
range_view->update_width(f);
if (jamming) update_config();
};
};
range_view->button_load_range.on_select = [this, range_view](Button&) {
auto load_view = nav_.push<FrequencyLoadView>();
load_view->on_frequency_loaded = [this, range_view](rf::Frequency value) {
range_view->update_center(value);
range_view->update_width(100000);
if (jamming) update_config();
};
load_view->on_range_loaded = [this, range_view](rf::Frequency start, rf::Frequency stop) {
range_view->update_start(start);
range_view->update_stop(stop);
if (jamming) update_config();
};
};
}
button_transmit.on_select = [this](Button&) {
if (jamming || cooling)
+41 -31
View File
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2025 RocketGod - Added modes from my Flipper Zero RF Jammer App - https://betaskynet.com
*
* This file is part of PortaPack.
*
@@ -40,27 +41,12 @@ class RangeView : public View {
RangeView(NavigationView& nav);
void focus() override;
void paint(Painter&) override;
void paint(Painter& painter) override;
jammer_range_t frequency_range{false, 0, 0};
private:
void update_start(rf::Frequency f);
void update_stop(rf::Frequency f);
void update_center(rf::Frequency f);
void update_width(uint32_t w);
uint32_t width{};
rf::Frequency center{};
const Style& style_info = *Theme::getInstance()->fg_medium;
Labels labels{
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
Checkbox check_enabled{
{1 * 8, 4},
12,
@@ -73,15 +59,32 @@ class RangeView : public View {
Button button_start{
{0 * 8, 11 * 8, 11 * 8, 28},
""};
Button button_stop{
{19 * 8, 11 * 8, 11 * 8, 28},
""};
Button button_center{
{76, 4 * 15 - 4, 11 * 8, 28},
""};
Button button_width{
{76, 8 * 15, 11 * 8, 28},
""};
void update_start(rf::Frequency f);
void update_stop(rf::Frequency f);
void update_center(rf::Frequency f);
void update_width(uint32_t w);
private:
const Style& style_info = *Theme::getInstance()->fg_medium;
Labels labels{
{{2 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_START], Theme::getInstance()->fg_light->foreground},
{{23 * 8, 8 * 8 + 4}, LanguageHelper::currentMessages[LANG_STOP], Theme::getInstance()->fg_light->foreground},
{{12 * 8, 5 * 8 - 4}, "Center", Theme::getInstance()->fg_light->foreground},
{{12 * 8 + 4, 13 * 8}, "Width", Theme::getInstance()->fg_light->foreground}};
};
class JammerView : public View {
@@ -109,15 +112,16 @@ class JammerView : public View {
void start_tx();
void on_timer();
void stop_tx();
bool update_config();
void set_jammer_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
void on_retune(const rf::Frequency freq, const uint32_t range);
JammerChannel* jammer_channels = (JammerChannel*)shared_memory.bb_data.data;
bool jamming{false};
bool cooling{false}; // euquiq: Indicates jammer in cooldown
uint16_t seconds = 0; // euquiq: seconds counter for toggling tx / cooldown
int16_t mscounter = 0; // euquiq: Internal ms counter for do_timer()
lfsr_word_t lfsr_v = 1; // euquiq: Used to generate "random" Jitter
bool cooling{false};
uint16_t seconds{0};
int16_t mscounter{0};
lfsr_word_t lfsr_v{1};
const Style& style_val = *Theme::getInstance()->fg_green;
const Style& style_cancel = *Theme::getInstance()->fg_red;
@@ -139,8 +143,8 @@ class JammerView : public View {
{{1 * 8, 25 * 8}, "Speed:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 27 * 8}, "Hop:", Theme::getInstance()->fg_light->foreground},
{{4 * 8, 29 * 8}, "TX:", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 31 * 8}, "Sle3p:", Theme::getInstance()->fg_light->foreground}, // euquiq: Token of appreciation to TheSle3p, which made this ehnancement a reality with his bounty.
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground}, // Maybe the repository curator can keep the "mystype" for some versions.
{{1 * 8, 31 * 8}, "Sleep:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 33 * 8}, "Jitter:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 29 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 31 * 8}, "Secs.", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 33 * 8}, "/60", Theme::getInstance()->fg_light->foreground},
@@ -150,12 +154,17 @@ class JammerView : public View {
OptionsField options_type{
{7 * 8, 23 * 8},
8,
{
{"Rand FSK", 0},
{"FM tone", 1},
{"CW sweep", 2},
{"Rand CW", 3},
}};
{{"Rand FSK", 0},
{"FM tone", 1},
{"CW sweep", 2},
{"Noise", 3},
{"Sine", 4},
{"Square", 5},
{"Sawtooth", 6},
{"Triangle", 7},
{"Chirp", 8},
{"Gauss", 9},
{"Brute", 10}}};
Text text_range_number{
{16 * 8, 23 * 8, 2 * 8, 16},
@@ -176,7 +185,8 @@ class JammerView : public View {
OptionsField options_hop{
{7 * 8, 27 * 8},
5,
{{"10ms ", 1},
{{"Off ", 0},
{"10ms ", 1},
{"50ms ", 5},
{"100ms", 10},
{"1s ", 100},
@@ -195,7 +205,7 @@ class JammerView : public View {
NumberField field_timepause{
{8 * 8, 31 * 8},
2,
{1, 60},
{0, 60},
1,
' ',
};
@@ -203,7 +213,7 @@ class JammerView : public View {
NumberField field_jitter{
{8 * 8, 33 * 8},
2,
{1, 60},
{0, 60},
1,
' ',
};
+2 -2
View File
@@ -77,8 +77,8 @@ __attribute__((section(".external_app.app_level.application_information"), used)
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
// this has to be the biggest baseband used by the app. Level is using AM,WFM,NFM,AMFM,SPEC and NFM is the biggest
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'N', 'F', 'M'},
// this has to be the biggest baseband used by the app. Level is using AM,WFM,NFM,AMFM,SPEC and WFM is the biggest
/*.m4_app_tag = portapack::spi_flash::image_tag_nfm */ {'P', 'W', 'F', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+2 -2
View File
@@ -178,12 +178,12 @@ class LevelView : public View {
RSSIGraph rssi_graph{
// 240x320 =>
{0, 6 * 16 + 8, 240 - 5 * 8, 320 - (6 * 16)},
{0, 6 * 16 + 8, screen_width - 5 * 8, screen_height - (6 * 16)},
};
RSSI rssi{
// 240x320 =>
{240 - 5 * 8, 6 * 16 + 8, 5 * 8, 320 - (6 * 16)},
{screen_width - 5 * 8, 6 * 16 + 8, 5 * 8, screen_height - (6 * 16)},
};
void handle_coded_squelch(const uint32_t value);
+1 -1
View File
@@ -171,7 +171,7 @@ class LGEView : public View {
'0'};
Console console{
{0, 18 * 8, 30 * 8, 7 * 16}};
{0, 18 * 8, screen_width, 7 * 16}};
TransmitterView tx_view{
16 * 16,
@@ -72,16 +72,16 @@ class McuTemperatureView : public View {
private:
Text text_title{
{76, 16, 240, 16},
{76, 16, screen_width, 16},
"Temperature",
};
McuTemperatureWidget temperature_widget{
{0, 40, 240, 180},
{0, 40, screen_width, 180},
};
Button button_done{
{72, 264, 96, 24},
{72, screen_height - 56, 96, 24},
"Done"};
};
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2025 HTotoo
*
* 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.hpp"
#include "ui_noaaapt_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::noaaapt_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<NoaaAptRxView>();
}
} // namespace ui::external_app::noaaapt_rx
extern "C" {
__attribute__((section(".external_app.app_noaaapt_rx.application_information"), used)) application_information_t _application_information_noaaapt_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::noaaapt_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "NOAA APT",
/*.bitmap_data = */ {
0x1C,
0x80,
0x3C,
0x40,
0x78,
0x18,
0xF0,
0x20,
0xE0,
0x26,
0x00,
0x0F,
0x80,
0x0F,
0xC0,
0x07,
0xE1,
0x1B,
0xC5,
0x39,
0x95,
0x78,
0x35,
0xF0,
0x09,
0xE0,
0x72,
0xC0,
0x04,
0x00,
0x78,
0x00,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_noaaapt_rx */ {'P', 'N', 'O', 'A'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,155 @@
/*
* Copyright (C) 2025 HTotoo
*
* 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.
*/
/*
TODOS LATER:
- add load data from wav file (maybe to a separate app, not this)
- AGC?!?
- Auto doppler correction
- fix and enable sync detection
- auto start / stop bmp save on each image
*/
#include "ui_noaaapt_rx.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "lcd_ili9341.hpp"
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::noaaapt_rx {
void NoaaAptRxView::focus() {
field_frequency.focus();
}
NoaaAptRxView::NoaaAptRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&txt_status,
//&check_wav, // enable this or the record view, but not both. yet it has some error, says "Invalid object" a lot. so disabled it
//&record_view, //
&button_ss});
record_view.set_filename_date_frequency(true);
record_view.on_error = [&nav](std::string message) {
nav.display_modal("Error", message);
};
record_view.set_sampling_rate(12000);
field_frequency.set_step(100);
field_frequency.on_edit_shown = [this]() {
paused = true;
};
field_frequency.on_edit_hidden = [this]() {
paused = false;
};
audio::set_rate(audio::Rate::Hz_12000);
audio::output::start();
receiver_model.set_hidden_offset(NOAAAPT_FREQ_OFFSET);
receiver_model.set_baseband_bandwidth(1750000);
receiver_model.set_sampling_rate(3072000);
receiver_model.enable();
txt_status.set("Waiting for signal.");
button_ss.on_select = [this](Button&) {
if (bmp.is_loaded()) {
bmp.close();
button_ss.set_text(LanguageHelper::currentMessages[LANG_START]);
if (check_wav.value()) {
record_view.stop();
}
return;
}
if (check_wav.value()) {
record_view.start();
}
ensure_directory("/BMP");
bmp.create("/BMP/noaa_" + to_string_timestamp(rtc_time::now()) + ".bmp", NOAAAPT_PX_SIZE, 1);
button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]);
};
on_settings_changed();
}
NoaaAptRxView::~NoaaAptRxView() {
stopping = true;
receiver_model.set_hidden_offset(0);
bmp.close();
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
record_view.stop();
}
void NoaaAptRxView::on_settings_changed() {
baseband::set_noaaapt_config();
}
void NoaaAptRxView::on_status(NoaaAptRxStatusDataMessage msg) {
(void)msg;
std::string tmp = "";
if (msg.state == 0) {
tmp = "Waiting for signal.";
} else if (msg.state == 1) {
tmp = "Synced.";
} else if (msg.state == 2) {
tmp = "Image arriving.";
}
txt_status.set(tmp);
}
// this stores and displays the image. keep it as simple as you can. a bit more complexity will kill the sync
void NoaaAptRxView::on_image(NoaaAptRxImageDataMessage msg) {
if ((line_num) >= UI_POS_HEIGHT_REMAINING(NOAA_IMG_START_ROW)) line_num = 0; // for draw reset
for (uint16_t i = 0; i < msg.cnt; i += 1) {
Color pxl = {msg.image[i], msg.image[i], msg.image[i]};
bmp.write_next_px(pxl);
line_in_part++;
if (line_in_part == NOAAAPT_PX_SIZE) {
line_in_part = 0;
line_num++;
bmp.expand_y_delta(1);
}
uint16_t xpos = line_in_part / (NOAAAPT_PX_SIZE / 240);
if (xpos >= 240) xpos = 239;
line_buffer[xpos] = pxl;
if ((line_in_part == 0)) {
portapack::display.render_line({0, line_num + NOAA_IMG_START_ROW * 16}, 240, line_buffer);
}
}
}
} // namespace ui::external_app::noaaapt_rx
@@ -0,0 +1,139 @@
/*
* Copyright (C) 2025 HTotoo
*
* 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.
*/
#ifndef __UI_noaaapt_RX_H__
#define __UI_noaaapt_RX_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "ui_fileman.hpp"
#include "bmpfile.hpp"
#include "file_path.hpp"
using namespace ui;
namespace ui::external_app::noaaapt_rx {
#define NOAAAPT_PX_SIZE 2080
#define NOAAAPT_FREQ_OFFSET 0
#define NOAA_IMG_START_ROW 4
class NoaaAptRxView : public View {
public:
NoaaAptRxView(NavigationView& nav);
~NoaaAptRxView();
void focus() override;
std::string title() const override { return "NOAA APT"; };
private:
void on_settings_changed();
void on_status(NoaaAptRxStatusDataMessage msg);
void on_image(NoaaAptRxImageDataMessage msg);
bool stopping = false;
uint16_t line_num = 0; // nth line
uint16_t line_in_part = 0; // got multiple parts of a line, so keep track of it
uint8_t delayer = 0;
ui::Color line_buffer[240];
std::filesystem::path filetohandle = "";
bool paused = false; // when freq field is shown for example, we need to pause
BMPFile bmp{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_noaaapt",
app_settings::Mode::RX,
{}};
RFAmpField field_rf_amp{
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH(6), 4}};
AudioVolumeField field_volume{
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
RxFrequencyField field_frequency{
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
RecordView record_view{
{UI_POS_X(0), UI_POS_Y(2), UI_POS_MAXWIDTH, UI_POS_DEFAULT_HEIGHT},
u"AUD",
u"AUDIO",
RecordView::FileType::WAV,
4096,
4};
Checkbox check_wav{
{UI_POS_X(0), UI_POS_Y(2)},
12,
"Save WAV too",
true};
Text txt_status{
{UI_POS_X(0), UI_POS_Y(1), UI_POS_WIDTH(20), UI_POS_DEFAULT_HEIGHT},
};
Button button_ss{
{UI_POS_X_RIGHT(6), UI_POS_Y(1), UI_POS_WIDTH(5), UI_POS_DEFAULT_HEIGHT},
LanguageHelper::currentMessages[LANG_START]};
MessageHandlerRegistration message_handler_stats{
Message::ID::NoaaAptRxStatusData,
[this](const Message* const p) {
if (stopping || paused) return;
const auto message = *reinterpret_cast<const NoaaAptRxStatusDataMessage*>(p);
on_status(message);
}};
MessageHandlerRegistration message_handler_image{
Message::ID::NoaaAptRxImageData,
[this](const Message* const p) {
if (stopping || paused) return;
const auto message = *reinterpret_cast<const NoaaAptRxImageDataMessage*>(p);
on_image(message);
}};
};
} // namespace ui::external_app::noaaapt_rx
#endif /*__UI_noaaapt_RX_H__*/
+2 -2
View File
@@ -77,11 +77,11 @@ class NRFRxView : public View {
nav_};
Button button_modem_setup{
{240 - 12 * 8, 1 * 16, 96, 24},
{screen_width - 12 * 8, 1 * 16, 96, 24},
LanguageHelper::currentMessages[LANG_MODEM_SETUP]};
Console console{
{0, 4 * 16, 240, 240}};
{0, 4 * 16, screen_width, screen_height - 80}};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
+3 -3
View File
@@ -109,7 +109,7 @@ class OOKEditorAppView : public View {
Labels label_waveform{{{0, 188}, "Waveform:", Theme::getInstance()->fg_light->foreground}};
// Text field to display the various status message of the app
Text text_app_status{{0, 160, 30 * 8, 16}, ""};
Text text_app_status{{0, 160, screen_width, 16}, ""};
// OptionsField for selectable sample rates.
OptionsField field_sample_rate{{96, 20}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}};
@@ -121,7 +121,7 @@ class OOKEditorAppView : public View {
NumberField field_repeat{{210, 40}, 3, {1, 999}, 1, '0', false};
// Text field to display the payload data.
Text text_payload{{0 * 8, 100, 30 * 8, 16}, ""};
Text text_payload{{0 * 8, 100, screen_width, 16}, ""};
// Buttons for setting configurations, opening files, and starting transmission.
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
@@ -134,7 +134,7 @@ class OOKEditorAppView : public View {
ProgressBar progressbar{{2 * 8, 250, 208, 16}};
// Waveform display using waveform buffer and yellow theme color.
Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
Waveform waveform{{0, 208, screen_width, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
};
/******** bug key input view **********/
@@ -56,7 +56,7 @@ class PlaylistEditorView : public View {
"New"};
Text text_current_ppl_file{
{sizeof("PPL file:") * 8, 0 * 16, screen_width - sizeof("PPL file:") * 8, 16},
{sizeof("PPL file:") * 8, 0 * 16, screen_width - (int)sizeof("PPL file:") * 8, 16},
""};
MenuView menu_view{};
+16 -16
View File
@@ -60,29 +60,29 @@ class ProtoView : public View {
"rx_protoview", app_settings::Mode::RX};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
{UI_POS_X(13), UI_POS_Y(0)}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
{UI_POS_X(15), UI_POS_Y(0)}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
{UI_POS_X(18), UI_POS_Y(0)}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
{UI_POS_X(21), UI_POS_Y(0), UI_POS_WIDTH(6), 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
{UI_POS_X_RIGHT(2), UI_POS_Y(0)}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
{UI_POS_X(0), UI_POS_Y(0)},
nav_};
// need to seperate because label shift need to hide independently
Labels label_zoom{
{{0 * 8, 1 * 16}, "Zoom: ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(1)}, "Zoom: ", Theme::getInstance()->fg_light->foreground}};
Labels label_shift{
{{0 * 8, 2 * 16}, "Shift: ", Theme::getInstance()->fg_light->foreground}};
{{UI_POS_X(0), UI_POS_Y(2)}, "Shift: ", Theme::getInstance()->fg_light->foreground}};
OptionsField options_zoom{
{7 * 8, 1 * 16},
{UI_POS_X(7), UI_POS_Y(1)},
4,
{{"1", 1},
{"2", 2},
@@ -96,22 +96,22 @@ class ProtoView : public View {
{"1000", 1000}}};
NumberField number_shift{
{7 * 8, 2 * 16},
{UI_POS_X(7), UI_POS_Y(2)},
5,
{-MAXSIGNALBUFFER, MAXSIGNALBUFFER},
1,
' '};
Button button_reset{
{screen_width - 12 * 8, 1 * 16, 96, 24},
{UI_POS_X_RIGHT(12), UI_POS_Y(1), UI_POS_WIDTH(12), UI_POS_HEIGHT(1.5)},
LanguageHelper::currentMessages[LANG_RESET]};
Button button_pause{
{screen_width - 12 * 8, 1 * 16 + 24, 96, 24},
{UI_POS_X_RIGHT(12), UI_POS_Y(2.5), UI_POS_WIDTH(12), UI_POS_HEIGHT(1.5)},
LanguageHelper::currentMessages[LANG_PAUSE]};
Waveform waveform{
{0, 8 * 8, 240, 50},
{UI_POS_X(0), UI_POS_Y(4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
waveform_buffer,
0,
0,
@@ -119,7 +119,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform2{
{0, 8 * 8 + 55, 240, 50},
{UI_POS_X(0), UI_POS_Y(4) + (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF],
0,
0,
@@ -127,7 +127,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform3{
{0, 8 * 8 + 110, 240, 50},
{UI_POS_X(0), UI_POS_Y(4) + 2 * (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF * 2],
0,
0,
@@ -135,7 +135,7 @@ class ProtoView : public View {
Theme::getInstance()->fg_yellow->foreground};
Waveform waveform4{
{0, 8 * 8 + 165, 240, 50},
{UI_POS_X(0), UI_POS_Y(4) + 3 * (UI_POS_HEIGHT_REMAINING(5) / 4), UI_POS_MAXWIDTH, (UI_POS_HEIGHT_REMAINING(5) / 4) - 4},
&waveform_buffer[MAXDRAWCNTPERWF * 3],
0,
0,
@@ -203,7 +203,7 @@ class RandomPasswordView : public View {
' '};
OptionsField field_method{
{(screen_width / 2) + (sizeof("method:") - 1) * 8, 7 * 16 - 2},
{(screen_width / 2) + (int)(sizeof("method:") - 1) * 8, 7 * 16 - 2},
sizeof("R+L+R+H"),
{{"R+L+R", Method::RADIO_LCG_ROLL},
{"R+L+R+H", Method::RADIO_LCG_ROLL_HASH}}};
+7 -7
View File
@@ -292,12 +292,12 @@ class RemoteAppView : public View {
bool is_sending() const { return replay_thread_ != nullptr; }
void show_error(const std::string& msg) const;
static constexpr Dim button_rows = 4;
static constexpr Dim button_cols = 3;
static constexpr uint8_t max_buttons = button_rows * button_cols;
static constexpr Dim button_area_height = 200;
static constexpr Dim button_width = screen_width / button_cols;
static constexpr Dim button_height = button_area_height / button_rows;
Dim button_rows = 4;
Dim button_cols = 3;
uint8_t max_buttons = button_rows * button_cols;
Dim button_area_height = 200;
Dim button_width = screen_width / button_cols;
Dim button_height = button_area_height / button_rows;
// This value is mysterious... why?
static constexpr uint32_t baseband_bandwidth = 2'500'000;
@@ -328,7 +328,7 @@ class RemoteAppView : public View {
bool ready_signal_{}; // Used to signal ReplayThread ready.
TextField field_title{
{0 * 8, 0 * 16 + 2, 30 * 8, 1 * 16},
{0 * 8, 0 * 16 + 2, screen_width, 1 * 16},
{}};
TransmitterView2 tx_view{

Some files were not shown because too many files have changed in this diff Show More