Compare commits

...

26 Commits

Author SHA1 Message Date
gullradriel 1df318355b Wefax warning fix modulation fix (#2543)
* changed order of modulations, changed case to avoid capture
* added missing AMAudioFMApt mode to dump pmem
* reorder demod, adding missing ones, fix warnings
* removed uneeded 'previous_modulation', renamed WFAX to AMFM to match other places
* removing uneeded 'previous_modulation' uneeded check in change_modulation
* move capture at the end so AMAUdioFMApt is matching the other arrays for position 4
* added AMFM to Recon Level and Scanner

* clang + more details in some comments

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-03-07 17:28:11 +01:00
RocketGod a17995fd2b Moved games to new game menu (#2544)
* Moved games to new game menu
* There's enough games to have a menu now and I plan to make more. Having them in "Utilities" made no sense.
2025-03-06 22:27:25 +01:00
sommermorgentraum ee472e1ed2 waveform fix 3 and trivial change (#2540) 2025-03-05 17:53:41 +01:00
sommermorgentraum e933c8b498 Trivial textual change about missing SD content (#2542)
* _

* _
2025-03-05 15:37:53 +01:00
RocketGod 18bebbfb6d Breakout - The Portapack remake game rises from the pirate's lair (#2541)
* Breakout - The Portapack remake game rises from the pirate's lair

* Fixes

* Added a signature
2025-03-05 14:43:30 +08:00
Brumi-2021 52c3760e90 Adding Wefax demodulation mode inside Audio App (#2539)
* Adding_new_WFAX_GUI_mode_Audio_App

* Wefax_APT_demodulation_structure

* Solving REC Apt signal.wav from WFAX

* clang format issues

* correcting comments
2025-03-05 11:27:18 +08:00
Davide Rovelli b6e498a6d3 Added different modulations in signal generator
* Added DSB, AM 100% mod index and AM 50% mod index. 
* Changed UI.
* Added pulsed CW
2025-03-03 22:17:56 +01:00
sommermorgentraum ddf7f7ccb5 Allow disable/enable waveform in Audio app to remove decoding problem on some frequencies 2025-03-03 22:16:03 +01:00
sommermorgentraum fcdccdea85 add cursor to font viewer app (#2528) 2025-02-22 13:37:06 +01:00
sommermorgentraum 5e55444f19 Support Bug Key AKA Auto Key for OOK Editor app (#2523)
* _

* some final tune

* textual

* rename following gull's suggestion
2025-02-19 13:51:09 +08:00
sommermorgentraum 73f7f84718 Playlist editor (#2506)
* make both exist
* format
* fix focusing issue
* add example hopper payload
* fix compiler err
* clean up
* correct linker script addr
* lint
* PoC
* unknown: write_line issue
* clean up
* merge
* fix read line
* remove debug code
* fix english
* support new file
* support enter delay
* fix crash
* remove debug code
* some final tune
2025-02-18 22:05:40 +01:00
sommermorgentraum 7ad4ad99dd Add widget preview tool (#2520)
* PoC

* opt

* opt
2025-02-16 23:42:06 +08:00
sommermorgentraum 200f10397b Add new app "hopper" app. (#2482)
* make both jammer and hopper exist
* add example hopper payload
* example files
* swap scanner and recon app location
2025-02-14 20:10:01 +01:00
E.T. 9b352f45a2 fix docker build warnings in dockerfile-nogit (#2518) 2025-02-13 07:29:20 +01:00
Lerold 51c445054e Moved country-specific FREQMAN files to the separate repository set up for it. (#2517) 2025-02-10 11:02:55 +01:00
RocketGod 02586101e6 Add modal to turn off screen when charging is detected (#2514) 2025-02-08 22:09:40 +01:00
Gaurav Chaturvedi b66d8b1aa1 Removing vim swap files (#2503)
* Removing vim swap files
* Added vim swap file to .gitignore
2025-01-28 16:31:11 +01:00
gullradriel 8dc3851b55 externalize wipe sdcard (#2502) 2025-01-27 22:10:33 +01:00
gullradriel aef7c2be93 fix baseband (#2501) 2025-01-26 22:23:29 +01:00
gullradriel 4e38dc4d3e put back app in 'Utilities' (#2500)
Co-authored-by: gullradriel <gullradriel@no-mail.com>
2025-01-26 22:15:02 +01:00
Davide Rovelli 520ad97f78 Added different modulations in signal generator (#2492)
* Added DSB, AM 100% mod index and AM 50% mod index. Changed UI.
2025-01-26 21:41:11 +01:00
Lucas C. Villa Real 5bd208bca1 APRS: add frequency settings for Brazil (#2494)
* Add frequency settings for Brazil, named 'BR' that tunes to 145.570 MHz.
* Also added: Japan: 144.640 MHz (JAP), Thailand: 144.900 MHz (THA), Philippines: 144.740 MHz (PHI)
* Reordered list by increasing frequency
Co-authored-by: gullradriel <3157857+gullradriel@users.noreply.github.com>
2025-01-26 21:39:38 +01:00
gullradriel a65ef3ce2e Externalize antenna calc and wav view (#2498)
* externalize antenna calc and wav view
* Added a tool to check if all the pictures in graphics are used in internal apps
2025-01-26 21:36:21 +01:00
gullradriel b2bb37af74 Flipper tx: use file_path, example file (#2496)
* added subghz_dir
* use subghz_dir from file_path
* example file
2025-01-26 17:46:43 +00:00
gullradriel 54ad82a423 fix fallthrough warning (#2497) 2025-01-26 16:27:29 +01:00
gullradriel e463405c77 fix cmake_minimum_required to 3.16 (#2499) 2025-01-26 16:26:02 +01:00
104 changed files with 3826 additions and 771 deletions
+1
View File
@@ -61,6 +61,7 @@ cmake-build-debug/
*.sublime-workspace
.vscode
.idea
*.swp
# VSCodium extensions
.history
+1 -1
View File
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
cmake_policy(SET CMP0005 NEW)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/firmware/toolchain-arm-cortex-m.cmake)
+3 -3
View File
@@ -3,7 +3,7 @@ FROM ubuntu:noble
# Set location to download ARM toolkit from.
# This will need to be changed over time or replaced with a static link to the latest release.
ENV ARMBINURL=https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2019q4/gcc-arm-none-eabi-9-2019-q4-major-x86_64-linux.tar.bz2?revision=108bd959-44bd-4619-9c19-26187abf5225&la=en&hash=E788CE92E5DFD64B2A8C246BBA91A249CB8E2D2D \
PATH=$HOME/bin:$PATH:/opt/build/armbin/bin
PATH=$PATH:/opt/build/armbin/bin
#Create volume /havoc/bin for compiled firmware binaries
VOLUME /havoc
@@ -15,8 +15,8 @@ RUN apt-get update \
&& apt-get -qy autoremove \
&& rm -rf /var/lib/apt/lists/*
ENV LANG C.UTF-8
ENV LC_ALL C.UTF-8
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8
# Grab the GNU ARM toolchain from arm.com
# Then extract contents to /opt/build/armbin/
+1 -1
View File
@@ -18,7 +18,7 @@
# Boston, MA 02110-1301, USA.
#
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
project(firmware)
-3
View File
@@ -305,7 +305,6 @@ set(CPPSRC
apps/ui_recon.cpp
apps/ui_scanner.cpp
apps/ui_sd_over_usb.cpp
apps/ui_sd_wipe.cpp
apps/ui_search.cpp
apps/ui_settings.cpp
apps/ui_siggen.cpp
@@ -317,9 +316,7 @@ set(CPPSRC
apps/ui_text_editor.cpp
apps/ui_touch_calibration.cpp
apps/ui_touchtunes.cpp
apps/ui_view_wav.cpp
apps/ui_weatherstation.cpp
apps/ui_whipcalc.cpp
protocols/aprs.cpp
protocols/ax25.cpp
protocols/bht.cpp
+32 -5
View File
@@ -102,6 +102,24 @@ WFMOptionsView::WFMOptionsView(
};
}
/* AMFMAptOptionsView *********************************************************/
AMFMAptOptionsView::AMFMAptOptionsView(
Rect parent_rect,
const Style* style)
: View{parent_rect} {
set_style(style);
add_children({
&label_config,
&options_config,
});
freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
options_config.set_by_value(receiver_model.amfm_configuration());
}
/* SPECOptionsView *******************************************************/
SPECOptionsView::SPECOptionsView(
@@ -175,8 +193,9 @@ AnalogAudioView::AnalogAudioView(
};
auto modulation = receiver_model.modulation();
// This app doesn't handle "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
if (modulation == ReceiverModel::Mode::Capture)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
options_modulation.set_by_value(toUType(modulation));
@@ -267,10 +286,6 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
}
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
// This app doesn't know what to do with "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
baseband::spectrum_streaming_stop();
update_modulation(modulation);
on_show_options_modulation();
@@ -346,6 +361,12 @@ void AnalogAudioView::on_show_options_modulation() {
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::AMAudioFMApt:
widget = std::make_unique<AMFMAptOptionsView>(options_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
text_ctcss.hidden(true);
break;
case ReceiverModel::Mode::SpectrumAnalysis:
widget = std::make_unique<SPECOptionsView>(this, nbfm_view_rect, Theme::getInstance()->option_active);
waterfall.show_audio_spectrum_view(false);
@@ -387,6 +408,9 @@ 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.
image_tag = portapack::spi_flash::image_tag_am_audio;
break;
case ReceiverModel::Mode::SpectrumAnalysis:
image_tag = portapack::spi_flash::image_tag_wideband_spectrum;
break;
@@ -421,6 +445,9 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
case ReceiverModel::Mode::WidebandFMAudio:
sampling_rate = 48000;
break;
case ReceiverModel::Mode::AMAudioFMApt: // TODO Wefax mode.
sampling_rate = 12000;
break;
default:
break;
}
@@ -53,6 +53,24 @@ class AMOptionsView : public View {
}};
};
class AMFMAptOptionsView : public View {
public:
AMFMAptOptionsView(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},
6, // Max option length
{
// 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.
}};
};
class NBFMOptionsView : public View {
public:
NBFMOptionsView(Rect parent_rect, const Style* style);
@@ -211,6 +229,7 @@ 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
}};
AudioVolumeField field_volume{
+2 -1
View File
@@ -175,7 +175,8 @@ void SoundBoardView::refresh_list() {
for (auto& c : entry_extension)
c = toupper(c);
if (entry_extension == ".WAV") {
if (entry_extension == ".WAV" && entry.path().string().find("shopping_cart") == std::string::npos) {
/* ^ because the shopping cart lock app using the speaker to send the LF signal, it's meaningless to be here */
if (reader->open(wav_dir / entry.path())) {
if ((reader->channels() == 1) && ((reader->bits_per_sample() == 8) || (reader->bits_per_sample() == 16))) {
// sounds[c].ms_duration = reader->ms_duration();
+14 -6
View File
@@ -99,13 +99,21 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect)
field_frequency.set_value(aprs_rx_freq);
} else if (i == 1) { // NA - North America - is also the default
field_frequency.set_value(144390000);
} else if (i == 2) { // EUR
field_frequency.set_value(144800000);
} else if (i == 3) { // AUS
field_frequency.set_value(145175000);
} else if (i == 4) { // NZ
} else if (i == 2) { // NZ
field_frequency.set_value(144575000);
} else if (i == 5) { // ISS
} else if (i == 3) { // JAP
field_frequency.set_value(144640000);
} else if (i == 4) { // PHI
field_frequency.set_value(144740000);
} else if (i == 5) { // EUR
field_frequency.set_value(144800000);
} else if (i == 6) { // THA
field_frequency.set_value(144900000);
} else if (i == 7) { // AUS
field_frequency.set_value(145175000);
} else if (i == 8) { // BR
field_frequency.set_value(145570000);
} else if (i == 9) { // ISS
field_frequency.set_value(145825000);
}
options_region_id = i;
+8 -4
View File
@@ -227,10 +227,14 @@ class APRSRxView : public View {
3,
{{"MAN", 0},
{"NA ", 1},
{"EUR", 2},
{"AUS", 3},
{"NZ ", 4},
{"ISS", 5}}};
{"NZ ", 2},
{"JAP", 3},
{"PHI", 4},
{"EUR", 5},
{"THA", 6},
{"AUS", 7},
{"BR ", 8},
{"ISS", 9}}};
FrequencyField field_frequency{
{3 * 8, 0 * 16}};
@@ -101,6 +101,9 @@ void ExternalModuleView::on_tick_second() {
case app_location_t::HOME:
btnText += " (Home)";
break;
case app_location_t::GAMES:
btnText += " (Games)";
break;
}
switch (i) {
@@ -122,4 +125,5 @@ void ExternalModuleView::on_tick_second() {
}
}
}
} // namespace ui
+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[4];
extern options_db_t freqman_bandwidths[5];
extern options_db_t freqman_steps;
extern options_db_t freqman_steps_short;
+9
View File
@@ -277,6 +277,15 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
field_bw.set_by_value(0);
field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_wfm_configuration(n); };
break;
case AMFM_MODULATION:
audio_sampling_rate = audio::Rate::Hz_12000;
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
field_bw.set_by_value(0);
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
break;
case SPEC_MODULATION:
audio_sampling_rate = audio::Rate::Hz_24000;
freqman_set_bandwidth_option(new_mod, field_bw);
+10
View File
@@ -1227,6 +1227,16 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
text_ctcss.set(" ");
recording_sampling_rate = 48000;
break;
case AMFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
field_bw.set_by_value(0);
text_ctcss.set(" ");
recording_sampling_rate = 12000;
break;
case SPEC_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_capture);
+8
View File
@@ -725,6 +725,14 @@ void ScannerView::change_mode(freqman_index_t new_mod) {
field_bw.set_by_value(receiver_model.wfm_configuration());
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
break;
case AMFM_MODULATION:
freqman_set_bandwidth_option(new_mod, field_bw);
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
receiver_model.set_amfm_configuration(5);
field_bw.set_by_value(0);
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
break;
default:
break;
}
+33 -6
View File
@@ -44,9 +44,9 @@ SigGenView::~SigGenView() {
void SigGenView::update_config() {
if (checkbox_stop.value())
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), field_stop.value());
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), field_stop.value());
else
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), options_shape.selected_index_value(), 0);
baseband::set_siggen_config(transmitter_model.channel_bandwidth(), (options_mod.selected_index_value() << 4) + options_shape.selected_index_value(), 0);
}
void SigGenView::update_tone() {
@@ -78,6 +78,7 @@ SigGenView::SigGenView(
baseband::run_image(portapack::spi_flash::image_tag_siggen);
add_children({&labels,
&options_mod,
&options_shape,
&text_shape,
&symfield_tone,
@@ -87,22 +88,48 @@ SigGenView::SigGenView(
&field_stop,
&tx_view});
symfield_tone.hidden(1); // At first launch , by default we are in CW Shape has NO MOD , we are not using Tone modulation.
symfield_tone.hidden(true); // At first launch , by default we are in CW: Shape ignored, we are not using Tone modulation.
options_shape.hidden(true);
text_shape.hidden(true);
symfield_tone.set_value(1000); // Default: 1000 Hz
options_shape.on_change = [this](size_t, OptionsField::value_t v) {
text_shape.set(shape_strings[v]);
if (auto_update)
update_config();
if ((v == 0) || (v == 6)) { // In Shapes Options (CW & Pseudo Random Noise) we are not using Tone modulation freq.
symfield_tone.hidden(1);
if (v == 5) { // In Shape Pseudo Random Noise we are not using Tone modulation freq.
symfield_tone.hidden(true);
} else {
symfield_tone.hidden(0);
symfield_tone.hidden(false);
}
set_dirty();
};
options_shape.set_selected_index(0);
text_shape.set(shape_strings[0]);
options_mod.on_change = [this](size_t, OptionsField::value_t v) {
if (auto_update)
update_config();
if (v == 0) { // In Modulation Options CW we are not using Tone modulation freq.
symfield_tone.hidden(true);
} else {
symfield_tone.hidden(false);
}
if ((v == 0) || (v == 2) || (v == 3) || (v == 7)) { // In Modulation Options CW, QPSK, BPSK, Pulsed CW we are not using Shapes.
options_shape.hidden(true);
text_shape.hidden(true);
} else {
options_shape.hidden(false);
text_shape.hidden(false);
}
set_dirty();
};
options_mod.set_selected_index(0);
field_stop.set_value(1);
symfield_tone.set_value(1000); // Default: 1000 Hz
+36 -30
View File
@@ -58,68 +58,74 @@ class SigGenView : public View {
app_settings::SettingsManager settings_{
"tx_siggen", app_settings::Mode::TX};
const std::string shape_strings[9] = {
"CW (No mod.) ",
"Sine mod. FM",
"Triangle mod.FM", // max 15 character text space.
"Saw up mod. FM",
"Saw down mod.FM",
"Square mod. FM",
"Pseudo Noise FM", // using 16 bits LFSR register, 16 order polynomial feedback.
"BPSK 0,1,0,1...",
"QPSK 00-01-10.."};
const std::string shape_strings[6] = {// max 15 character text space.
"Sine",
"Triangle",
"Saw up",
"Saw down",
"Square",
"Pseudo Noise"};
bool auto_update{false};
Labels labels{
{{3 * 8, 4 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
{{22 * 8, 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground},
{{8 * 8, 20 * 8}, "Modulation: FM", Theme::getInstance()->fg_light->foreground}};
{{3 * 8, 2 * 8}, "Modulation:", Theme::getInstance()->fg_light->foreground},
{{3 * 8, 3 * 8 + 8 + 10}, "Shape:", Theme::getInstance()->fg_light->foreground},
{{6 * 8, 2 * 8 + 7 * 8}, "Tone: Hz", Theme::getInstance()->fg_light->foreground},
{{22 * 8, 2 * 8 + 15 * 8 + 4}, "s.", Theme::getInstance()->fg_light->foreground}};
ImageOptionsField options_shape{
{10 * 8, 4, 32, 32},
{10 * 8, 3 * 8 + 8, 32, 32},
Theme::getInstance()->bg_darkest->foreground,
Theme::getInstance()->bg_darkest->background,
{{&bitmap_sig_cw, 0},
{&bitmap_sig_sine, 1},
{&bitmap_sig_tri, 2},
{&bitmap_sig_saw_up, 3},
{&bitmap_sig_saw_down, 4},
{&bitmap_sig_square, 5},
{&bitmap_sig_noise, 6},
{&bitmap_sig_noise, 7}, // Pending to add a correct BPSK icon.
{&bitmap_sig_noise, 8}}}; // Pending to add a correct QPSK icon.
{{&bitmap_sig_sine, 0},
{&bitmap_sig_tri, 1},
{&bitmap_sig_saw_up, 2},
{&bitmap_sig_saw_down, 3},
{&bitmap_sig_square, 4},
{&bitmap_sig_noise, 5}}};
Text text_shape{
{15 * 8, 4 + 10, 15 * 8, 16},
{15 * 8, 3 * 8 + 8 + 10, 15 * 8, 16},
""};
SymField symfield_tone{
{12 * 8, 7 * 8},
{12 * 8, 2 * 8 + 7 * 8},
5};
Button button_update{
{5 * 8, 10 * 8, 8 * 8, 3 * 8},
{5 * 8, 2 * 8 + 10 * 8, 8 * 8, 3 * 8},
"Update"};
Checkbox checkbox_auto{
{15 * 8, 10 * 8},
{15 * 8, 2 * 8 + 10 * 8},
4,
"Auto"};
Checkbox checkbox_stop{
{5 * 8, 15 * 8},
{5 * 8, 2 * 8 + 15 * 8},
10,
"Stop after"};
NumberField field_stop{
{20 * 8, 15 * 8 + 4},
{20 * 8, 2 * 8 + 15 * 8 + 4},
2,
{1, 99},
1,
' '};
OptionsField options_mod{
{15 * 8, 2 * 8},
12,
{{"CW (No mod.)", 0},
{"FM", 1},
{"BPSK", 2},
{"QPSK", 3},
{"DSB", 4},
{"AM 100% dep.", 5},
{"AM 50% depth", 6},
{"Pulse CW 25%", 7}}};
TransmitterView tx_view{
16 * 16,
10000,
+6 -6
View File
@@ -66,12 +66,12 @@ static void send_message(const Message* const message) {
void AMConfig::apply() const {
const AMConfigureMessage message{
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 5 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW)
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 5 x AM mod. types.
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k all rest AM modes)
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW)
modulation, // var parameter .
audio_12k_hpf_300hz_config};
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, AMFM-WFAX)
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 6 x AM mod. types. (")
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes)
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,AMFM-WFAX)
modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
audio_12k_iir_filter_config}; // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf)
send_message(&message);
audio::set_rate(audio::Rate::Hz_12000);
}
+2 -1
View File
@@ -36,9 +36,10 @@
namespace baseband {
struct AMConfig {
const fir_taps_real<32> decim_2; // added to handle two types decim_2 9k, 6k
const fir_taps_real<32> decim_2; // added to handle two var types decim_2 9k, 6k
const fir_taps_complex<64> channel;
const AMConfigureMessage::Modulation modulation;
const iir_biquad_config_t audio_12k_iir_filter_config; // added to handle two var IIR filter types : 300 hpf(as before) , 1500Hz lpf for Wefax.
void apply() const;
};
+77 -76
View File
@@ -105,6 +105,44 @@ static constexpr Bitmap bitmap_icon_utilities{
{16, 16},
bitmap_icon_utilities_data};
static constexpr uint8_t bitmap_icon_games_data[] = {
0x0C,
0x0C,
0x0C,
0x0C,
0x3F,
0x3C,
0xFF,
0x7E,
0xFF,
0xFF,
0xFF,
0xFF,
0xCC,
0xCC,
0xCC,
0xCC,
0xFF,
0xFF,
0xE7,
0x73,
0xE7,
0x73,
0x33,
0x33,
0x03,
0x30,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_games{
{16, 16},
bitmap_icon_games_data};
static constexpr uint8_t bitmap_stripes_data[] = {
0xFF,
0x03,
@@ -987,44 +1025,6 @@ static constexpr Bitmap bitmap_icon_previous{
{16, 16},
bitmap_icon_previous_data};
static constexpr uint8_t bitmap_icon_tools_antenna_data[] = {
0x38,
0x3E,
0x10,
0x22,
0x10,
0x26,
0x10,
0x22,
0x10,
0x2E,
0x10,
0x22,
0x10,
0x26,
0x10,
0x22,
0x38,
0x2E,
0x38,
0x22,
0x38,
0x26,
0x38,
0x22,
0x38,
0x2E,
0x38,
0x22,
0x38,
0x3E,
0x00,
0x00,
};
static constexpr Bitmap bitmap_icon_tools_antenna{
{16, 16},
bitmap_icon_tools_antenna_data};
static constexpr uint8_t bitmap_icon_batt_text_data[] = {
0x00,
0x00,
@@ -1799,6 +1799,45 @@ static constexpr uint8_t bitmap_icon_lge_data[] = {
0x00,
0x00,
};
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
0xFC,
0x03,
0x04,
0x06,
0x04,
0x0E,
0x04,
0x1E,
0x04,
0x3E,
0x04,
0x20,
0x04,
0x20,
0x04,
0x21,
0x44,
0x25,
0x54,
0x25,
0xF4,
0x2F,
0xA4,
0x2A,
0x84,
0x22,
0x04,
0x22,
0x04,
0x20,
0xFC,
0x3F,
};
static constexpr Bitmap bitmap_icon_file_iq{
{16, 16},
bitmap_icon_file_iq_data};
static constexpr Bitmap bitmap_icon_lge{
{16, 16},
bitmap_icon_lge_data};
@@ -2713,44 +2752,6 @@ static constexpr Bitmap bitmap_icon_touchtunes{
{16, 16},
bitmap_icon_touchtunes_data};
static constexpr uint8_t bitmap_icon_file_iq_data[] = {
0xFC,
0x03,
0x04,
0x06,
0x04,
0x0E,
0x04,
0x1E,
0x04,
0x3E,
0x04,
0x20,
0x04,
0x20,
0x04,
0x21,
0x44,
0x25,
0x54,
0x25,
0xF4,
0x2F,
0xA4,
0x2A,
0x84,
0x22,
0x04,
0x22,
0x04,
0x20,
0xFC,
0x3F,
};
static constexpr Bitmap bitmap_icon_file_iq{
{16, 16},
bitmap_icon_file_iq_data};
static constexpr uint8_t bitmap_icon_sd_data[] = {
0x00,
0x00,
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 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_whipcalc.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::antenna_length {
void initialize_app(ui::NavigationView& nav) {
nav.push<WhipCalcView>();
}
} // namespace ui::external_app::antenna_length
extern "C" {
__attribute__((section(".external_app.app_antenna_length.application_information"), used)) application_information_t _application_information_antenna_length = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::antenna_length::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Antenna Length",
/*.bitmap_data = */ {0x38, 0x3E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x10, 0x2E, 0x10, 0x22, 0x10, 0x26, 0x10, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x26, 0x38, 0x22, 0x38, 0x2E, 0x38, 0x22, 0x38, 0x3E, 0x00, 0x00},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -32,8 +32,9 @@
#include <cstring>
using namespace portapack;
using namespace ui;
namespace ui {
namespace ui::external_app::antenna_length {
void WhipCalcView::focus() {
field_frequency.focus();
@@ -176,4 +177,4 @@ void WhipCalcView::load_antenna_db() {
void WhipCalcView::add_default_antenna() {
antenna_db.push_back({"ANT500", {185, 315, 450, 586, 724, 862}}); // store a default ant500
}
} // namespace ui
} // namespace ui::external_app::antenna_length
@@ -31,7 +31,10 @@
#include "string_format.hpp"
#include <vector>
namespace ui {
using namespace ui;
namespace ui::external_app::antenna_length {
class WhipCalcView : public View {
public:
WhipCalcView(NavigationView& nav);
@@ -90,6 +93,6 @@ class WhipCalcView : public View {
"Back"};
};
} /* namespace ui */
} // namespace ui::external_app::antenna_length
#endif /*__UI_WHIPCALC__*/
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2024 Mark Thompson
*
* 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.
*/
// dummy include file to avoid changing original source
#ifndef __UI_Arial12x12_H__
#define __UI_Arial12x12_H__
#define Arial12x12 (0)
#endif /*__UI_Arial12x12_H__*/
@@ -0,0 +1,68 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_SPI_TFT_ILI9341_H__
#define __UI_SPI_TFT_ILI9341_H__
ui::Painter painter;
static int bg_color;
enum {
White,
Blue,
Yellow,
Purple,
Green,
Red,
Maroon,
Orange,
Black,
};
static const Color pp_colors[] = {
Color::white(),
Color::blue(),
Color::yellow(),
Color::purple(),
Color::green(),
Color::red(),
Color::magenta(),
Color::orange(),
Color::black(),
};
static void claim(__FILE* x) {
(void)x;
};
static void cls() {
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
};
static void background(int color) {
bg_color = color;
};
static void set_orientation(int x) {
(void)x;
};
static void set_font(unsigned char* x) {
(void)x;
};
static void fillrect(int x1, int y1, int x2, int y2, int color) {
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
static void rect(int x1, int y1, int x2, int y2, int color) {
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
};
#endif /*__UI_SPI_TFT_ILI9341_H__*/
+429
View File
@@ -0,0 +1,429 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "mbed.h"
#include "SPI_TFT_ILI9341.h"
#include "Arial12x12.h"
#include "ui.hpp"
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define PADDLE_WIDTH 40
#define PADDLE_HEIGHT 10
#define BALL_SIZE 8
#define BRICK_WIDTH 20
#define BRICK_HEIGHT 10
#define BRICK_ROWS 5
#define BRICK_COLS 10
#define BRICK_GAP 2
#define GAME_AREA_TOP 50
#define GAME_AREA_BOTTOM 310
#define PADDLE_Y (GAME_AREA_BOTTOM - PADDLE_HEIGHT)
#define BALL_SPEED_INCREASE 0.1f
#define STATE_MENU 0
#define STATE_PLAYING 1
#define STATE_GAME_OVER 3
#define COLOR_BACKGROUND Black
#define COLOR_PADDLE Blue
#define COLOR_BALL White
#define COLOR_BORDER White
#define COLOR_BRICK_COLORS \
{ Red, Orange, Yellow, Green, Purple }
Ticker game_timer;
int paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
float ball_x = SCREEN_WIDTH / 2;
float ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float ball_dx = 1.5f;
float ball_dy = -2.0f;
int score = 0;
int lives = 3;
int level = 1;
int game_state = STATE_MENU;
bool initialized = false;
bool ball_attached = true;
unsigned int brick_count = 0;
bool bricks[BRICK_ROWS][BRICK_COLS];
int brick_colors[BRICK_ROWS];
extern ui::Painter painter;
void init_game();
void init_level();
void draw_screen();
void draw_bricks();
void draw_paddle();
void draw_ball();
void draw_score();
void draw_lives();
void draw_level();
void draw_borders();
void move_paddle_left();
void move_paddle_right();
void launch_ball();
void update_game();
void check_collisions();
bool check_brick_collision(int row, int col);
void handle_game_over();
void show_menu();
void show_game_over();
bool check_level_complete();
void next_level();
void reset_game();
void game_timer_check() {
if (game_state == STATE_PLAYING) {
update_game();
}
}
void init_game() {
claim(stdout);
set_orientation(2);
set_font((unsigned char*)Arial12x12);
paddle_x = (SCREEN_WIDTH - PADDLE_WIDTH) / 2;
score = 0;
lives = 3;
level = 1;
brick_colors[0] = Red;
brick_colors[1] = Orange;
brick_colors[2] = Yellow;
brick_colors[3] = Green;
brick_colors[4] = Purple;
init_level();
game_state = STATE_MENU;
show_menu();
}
void init_level() {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
ball_dx = (ball_dx > 0 ? 1.5f : -1.5f) * speed_multiplier;
ball_dy = -2.0f * speed_multiplier;
ball_attached = true;
brick_count = 0;
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
bricks[row][col] = true;
brick_count++;
}
}
}
void draw_screen() {
cls();
background(COLOR_BACKGROUND);
draw_borders();
draw_bricks();
draw_paddle();
draw_ball();
draw_score();
draw_lives();
draw_level();
}
void draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
}
void draw_bricks() {
for (int row = 0; row < BRICK_ROWS; row++) {
for (int col = 0; col < BRICK_COLS; col++) {
if (bricks[row][col]) {
int x = col * (BRICK_WIDTH + BRICK_GAP);
int y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
fillrect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, brick_colors[row]);
rect(x, y, x + BRICK_WIDTH, y + BRICK_HEIGHT, Black);
}
}
}
}
void draw_paddle() {
fillrect(paddle_x, PADDLE_Y, paddle_x + PADDLE_WIDTH, PADDLE_Y + PADDLE_HEIGHT, COLOR_PADDLE);
}
void draw_ball() {
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BALL);
}
void draw_score() {
auto style = *ui::Theme::getInstance()->fg_green;
painter.draw_string({5, 10}, style, "Score: " + std::to_string(score));
}
void draw_lives() {
auto style = *ui::Theme::getInstance()->fg_red;
painter.draw_string({5, 30}, style, "Lives: " + std::to_string(lives));
}
void draw_level() {
auto style = *ui::Theme::getInstance()->fg_yellow;
painter.draw_string({80, 30}, style, "Level: " + std::to_string(level));
}
void move_paddle_left() {
if (paddle_x > 0) {
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 < 0) paddle_x = 0;
if (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
}
draw_paddle();
if (ball_attached) {
draw_ball();
}
}
}
void move_paddle_right() {
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 (ball_attached) {
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
}
draw_paddle();
if (ball_attached) {
draw_ball();
}
}
}
void launch_ball() {
if (ball_attached) {
ball_attached = false;
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
float speed_multiplier = (level == 1) ? 1.0f : 1.0f + ((level - 1) * BALL_SPEED_INCREASE);
ball_dx = 1.5f * speed_multiplier;
ball_dy = -2.0f * speed_multiplier;
}
}
void update_game() {
if (ball_attached) {
return;
}
fillrect(ball_x, ball_y, ball_x + BALL_SIZE, ball_y + BALL_SIZE, COLOR_BACKGROUND);
float next_ball_y = ball_y + ball_dy;
if (next_ball_y > GAME_AREA_BOTTOM) {
lives--;
draw_lives();
if (lives <= 0) {
handle_game_over();
} else {
ball_attached = true;
ball_x = paddle_x + (PADDLE_WIDTH / 2) - (BALL_SIZE / 2);
ball_y = GAME_AREA_BOTTOM - PADDLE_HEIGHT - BALL_SIZE - 1;
draw_ball();
}
return;
}
ball_x += ball_dx;
ball_y = next_ball_y;
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;
ball_dx = -ball_dx;
}
if (ball_y < GAME_AREA_TOP) {
ball_y = GAME_AREA_TOP;
ball_dy = -ball_dy;
}
if (ball_y + BALL_SIZE >= PADDLE_Y && ball_y <= PADDLE_Y + PADDLE_HEIGHT) {
if (ball_x + BALL_SIZE >= paddle_x && ball_x <= paddle_x + PADDLE_WIDTH) {
ball_y = PADDLE_Y - BALL_SIZE;
float hit_position = (ball_x + (BALL_SIZE / 2)) - paddle_x;
float angle = (hit_position / PADDLE_WIDTH) - 0.5f;
ball_dx = angle * 4.0f;
if (ball_dx > -0.5f && ball_dx < 0.5f) {
ball_dx = (ball_dx > 0) ? 0.5f : -0.5f;
}
ball_dy = -ball_dy;
}
}
check_collisions();
draw_ball();
if (check_level_complete()) {
next_level();
}
}
void check_collisions() {
int grid_x = ball_x / (BRICK_WIDTH + BRICK_GAP);
int grid_y = (ball_y - GAME_AREA_TOP - 5) / (BRICK_HEIGHT + BRICK_GAP);
for (int row = grid_y - 1; row <= grid_y + 1; row++) {
for (int col = grid_x - 1; col <= grid_x + 1; col++) {
if (row >= 0 && row < BRICK_ROWS && col >= 0 && col < BRICK_COLS) {
if (bricks[row][col] && check_brick_collision(row, col)) {
return;
}
}
}
}
}
bool check_brick_collision(int row, int col) {
int brick_x = col * (BRICK_WIDTH + BRICK_GAP);
int brick_y = GAME_AREA_TOP + row * (BRICK_HEIGHT + BRICK_GAP) + 5;
if (ball_x + BALL_SIZE >= brick_x && ball_x <= brick_x + BRICK_WIDTH &&
ball_y + BALL_SIZE >= brick_y && ball_y <= brick_y + BRICK_HEIGHT) {
fillrect(brick_x, brick_y, brick_x + BRICK_WIDTH, brick_y + BRICK_HEIGHT, COLOR_BACKGROUND);
bricks[row][col] = false;
brick_count--;
score += (5 - row) * 10;
draw_score();
float center_x = brick_x + BRICK_WIDTH / 2;
float center_y = brick_y + BRICK_HEIGHT / 2;
float ball_center_x = ball_x + BALL_SIZE / 2;
float ball_center_y = ball_y + BALL_SIZE / 2;
float dx = std::abs(ball_center_x - center_x);
float dy = std::abs(ball_center_y - center_y);
if (dx * BRICK_HEIGHT > dy * BRICK_WIDTH) {
ball_dx = -ball_dx;
} else {
ball_dy = -ball_dy;
}
return true;
}
return false;
}
bool check_level_complete() {
return brick_count == 0;
}
void next_level() {
level++;
init_level();
draw_screen();
}
void handle_game_over() {
game_state = STATE_GAME_OVER;
show_game_over();
}
void show_menu() {
cls();
background(COLOR_BACKGROUND);
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_white = *ui::Theme::getInstance()->fg_light;
auto style_green = *ui::Theme::getInstance()->fg_green;
auto style_red = *ui::Theme::getInstance()->fg_red;
painter.draw_string({0, 40}, style_yellow, "* * * BREAKOUT * * *");
painter.draw_string({0, 70}, style_white, "========================");
painter.draw_string({0, 120}, style_green, "| ROTARY: MOVE PADDLE |");
painter.draw_string({0, 150}, style_green, "| SELECT: START/LAUNCH |");
painter.draw_string({0, 190}, style_white, "========================");
painter.draw_string({24, 230}, style_red, "* PRESS SELECT *");
}
void show_game_over() {
cls();
background(COLOR_BACKGROUND);
auto style_red = *ui::Theme::getInstance()->fg_red;
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_green = *ui::Theme::getInstance()->fg_green;
painter.draw_string({72, 120}, style_red, "GAME OVER");
painter.draw_string({12, 160}, style_yellow, "FINAL SCORE: " + std::to_string(score));
painter.draw_string({0, 200}, style_green, "PRESS SELECT TO RESTART");
wait(1);
}
void reset_game() {
level = 1;
score = 0;
lives = 3;
game_state = STATE_PLAYING;
init_level();
draw_screen();
}
int main() {
if (!initialized) {
initialized = true;
game_timer.attach(&game_timer_check, 1.0 / 60.0);
init_game();
}
while (1) {
if (but_SELECT && game_state == STATE_MENU) {
game_state = STATE_PLAYING;
reset_game();
}
if (but_SELECT && game_state == STATE_GAME_OVER) {
reset_game();
}
if (but_SELECT && game_state == STATE_PLAYING && ball_attached) {
launch_ball();
}
if (but_LEFT && game_state == STATE_PLAYING) {
move_paddle_left();
}
if (but_RIGHT && game_state == STATE_PLAYING) {
move_paddle_right();
}
}
}
+70
View File
@@ -0,0 +1,70 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_breakout.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::breakout {
void initialize_app(ui::NavigationView& nav) {
nav.push<BreakoutView>();
}
} // namespace ui::external_app::breakout
extern "C" {
__attribute__((section(".external_app.app_breakout.application_information"), used)) application_information_t _application_information_breakout = {
/*.memory_location = */ (uint8_t*)0x00000000, // will be filled at compile time
/*.externalAppEntry = */ ui::external_app::breakout::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Breakout",
/*.bitmap_data = */ {
0xFF,
0xFF,
0x01,
0x01,
0x01,
0x01,
0x01,
0x01,
0xFF,
0xFF,
0x80,
0x80,
0x80,
0x80,
0x80,
0x80,
0xFF,
0xFF,
0x01,
0x01,
0x01,
0x01,
0x01,
0x01,
0xFF,
0xFF,
0x80,
0x80,
0x80,
0x80,
0x80,
0x80,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+105
View File
@@ -0,0 +1,105 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_mbed_H__
#define __UI_mbed_H__
using Callback = void (*)(void);
#define wait_us(x) (void)0
#define wait(x) chThdSleepMilliseconds(x * 1000)
#define PullUp 1
#include "ui_navigation.hpp"
enum {
dp0,
dp1,
dp2,
dp3,
dp4,
dp5,
dp6,
dp7,
dp8,
dp9,
dp10,
dp11,
dp12,
dp13,
dp14,
dp15,
dp16,
dp17,
dp18,
dp19,
dp20,
dp21,
dp22,
dp23,
dp24,
dp25,
};
static bool but_RIGHT;
static bool but_LEFT;
static bool but_SELECT;
class Timer {
public:
Timer() { (void)0; };
void reset() { (void)0; };
void start() { (void)0; }
uint32_t read_ms() { return 1000; };
private:
};
static Callback game_update_callback;
static uint32_t game_update_timeout;
static uint32_t game_update_counter;
static void check_game_timer() {
if (game_update_callback) {
if (++game_update_counter >= game_update_timeout) {
game_update_counter = 0;
game_update_callback();
}
}
}
class Ticker {
public:
Ticker() { (void)0; };
void attach(Callback func, double delay_sec) {
game_update_callback = func;
game_update_timeout = delay_sec * 60;
}
void detach() {
game_update_callback = nullptr;
}
private:
};
static Callback button_callback;
class InterruptIn {
public:
InterruptIn(int reg) {
(void)reg;
};
void fall(Callback func) { button_callback = func; };
void mode(int v) { (void)v; };
private:
};
#endif /*__UI_mbed_H__*/
+79
View File
@@ -0,0 +1,79 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_breakout.hpp"
namespace ui::external_app::breakout {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Weffc++"
#include "breakout.cpp"
#pragma GCC diagnostic pop
BreakoutView::BreakoutView(NavigationView& nav)
: nav_{nav} {
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 60.0);
}
void BreakoutView::on_show() {
}
void BreakoutView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
init_game();
}
}
void BreakoutView::frame_sync() {
check_game_timer();
set_dirty();
}
bool BreakoutView::on_encoder(const EncoderEvent delta) {
if (game_state == STATE_PLAYING) {
if (delta > 0) {
move_paddle_right();
set_dirty();
} else if (delta < 0) {
move_paddle_left();
set_dirty();
}
}
return true;
}
bool BreakoutView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (game_state == STATE_MENU) {
game_state = STATE_PLAYING;
reset_game();
} else if (game_state == STATE_PLAYING && ball_attached) {
launch_ball();
} else if (game_state == STATE_GAME_OVER) {
reset_game();
}
} else if (key == KeyEvent::Left) {
if (game_state == STATE_PLAYING) {
move_paddle_left();
}
} else if (key == KeyEvent::Right) {
if (game_state == STATE_PLAYING) {
move_paddle_right();
}
}
set_dirty();
return true;
}
} // namespace ui::external_app::breakout
+53
View File
@@ -0,0 +1,53 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_BREAKOUT_H__
#define __UI_BREAKOUT_H__
#include "ui_navigation.hpp"
#include "event_m0.hpp"
#include "message.hpp"
#include "irq_controls.hpp"
#include "random.hpp"
#include "lpc43xx_cpp.hpp"
#include "limits.h"
#include "ui_widget.hpp"
namespace ui::external_app::breakout {
class BreakoutView : public View {
public:
BreakoutView(NavigationView& nav);
void on_show() override;
std::string title() const override { return "Breakout"; };
void focus() override { dummy.focus(); };
void paint(Painter& painter) override;
void frame_sync();
bool on_encoder(const EncoderEvent event) override;
bool on_key(KeyEvent key) override;
private:
bool initialized = false;
NavigationView& nav_;
Button dummy{
{240, 0, 0, 0},
""};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::breakout
#endif /*__UI_BREAKOUT_H__*/
+31 -1
View File
@@ -4,6 +4,10 @@ set(EXTCPPSRC
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#tetris
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
@@ -150,6 +154,26 @@ set(EXTCPPSRC
#app_manager
external/app_manager/main.cpp
external/app_manager/ui_app_manager.cpp
#hopper
external/hopper/main.cpp
external/hopper/ui_hopper.cpp
# whip calculator
external/antenna_length/main.cpp
external/antenna_length/ui_whipcalc.cpp
# wav viewer
external/wav_view/main.cpp
external/wav_view/ui_view_wav.cpp
# wipe sdcard
external/sd_wipe/main.cpp
external/sd_wipe/ui_sd_wipe.cpp
# playlist editor
external/playlist_editor/main.cpp
external/playlist_editor/ui_playlist_editor.cpp
)
set(EXTAPPLIST
@@ -167,6 +191,7 @@ set(EXTAPPLIST
spainter
keyfob
tetris
breakout
extsensors
foxhunt_rx
audio_test
@@ -189,4 +214,9 @@ set(EXTAPPLIST
tuner
metronome
app_manager
)
hopper
antenna_length
view_wav
sd_wipe
playlist_editor
)
+42
View File
@@ -38,6 +38,7 @@ MEMORY
ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
ram_external_app_breakout(rwx) : org = 0xADE00000, len = 32k
ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k
ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k
ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k
@@ -59,6 +60,11 @@ MEMORY
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k
ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k
ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k
ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k
ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k
}
SECTIONS
@@ -147,6 +153,12 @@ SECTIONS
*(*ui*external_app*tetris*);
} > ram_external_app_tetris
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
*(*ui*external_app*breakout*);
} > ram_external_app_breakout
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_extsensors.application_information));
@@ -279,4 +291,34 @@ SECTIONS
KEEP(*(.external_app.app_app_manager.application_information));
*(*ui*external_app*app_manager*);
} > ram_external_app_app_manager
.external_app_hopper : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_hopper.application_information));
*(*ui*external_app*hopper*);
} > ram_external_app_hopper
.external_app_antenna_length : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_antenna_length.application_information));
*(*ui*external_app*antenna_length*);
} > ram_external_app_antenna_length
.external_app_view_wav : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_view_wav.application_information));
*(*ui*external_app*view_wav*);
} > ram_external_app_view_wav
.external_app_sd_wipe : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_sd_wipe.application_information));
*(*ui*external_app*sd_wipe*);
} > ram_external_app_sd_wipe
.external_app_playlist_editor : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_playlist_editor.application_information));
*(*ui*external_app*playlist_editor*);
} > ram_external_app_playlist_editor
}
@@ -89,8 +89,6 @@ class FlipperTxView : public View {
std::unique_ptr<FlipperPlayThread> replay_thread{};
Optional<flippersub_metadata> submeta{};
const std::filesystem::path subghz_dir = u"subghz";
MessageHandlerRegistration message_handler_tx_progress{
Message::ID::TXProgress,
[this](const Message* const p) {
+2 -1
View File
@@ -108,7 +108,8 @@ class FmRadioView : public View {
128,
0,
false,
Theme::getInstance()->bg_darkest->foreground};
Theme::getInstance()->bg_darkest->foreground,
true};
Button btn_fav_0{{2, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
Button btn_fav_1{{2 + 15 * 8, FMR_BTNGRID_TOP + 0 * 34, 10 * 8, 28}, "---"};
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
* copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -32,7 +33,7 @@ namespace ui::external_app::font_viewer {
/* DebugFontsView *******************************************************/
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name) {
uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font) {
auto char_width{font_style->font.char_width()};
auto char_height{font_style->font.line_height()};
auto cpl{((screen_width / char_width) - 6) & 0xF8}; // Display a multiple of 8 characters per line
@@ -47,7 +48,15 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
if ((c % cpl) == 0)
painter.draw_string({0, line_pos}, *font_style, "Ox" + to_string_hex(c + 0x20, 2));
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
Style highlight_style_big = *Theme::getInstance()->fg_red;
Style highlight_style_small = *Theme::getInstance()->bg_important_small;
if (c == field_cursor.value()) {
painter.draw_char({((c % cpl) + 5) * char_width, line_pos},
is_big_font ? highlight_style_big : highlight_style_small,
(char)(c + 0x20));
} else {
painter.draw_char({((c % cpl) + 5) * char_width, line_pos}, *font_style, (char)(c + 0x20));
}
}
return line_pos + char_height;
@@ -56,13 +65,29 @@ uint16_t DebugFontsView::display_font(Painter& painter, uint16_t y_offset, const
void DebugFontsView::paint(Painter& painter) {
int16_t line_pos;
line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16");
display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8");
line_pos = display_font(painter, 32, Theme::getInstance()->bg_darkest, "Fixed 8x16", true);
display_font(painter, line_pos + 16, Theme::getInstance()->bg_darkest_small, "Fixed 5x8", false);
}
void DebugFontsView::update_address_text() {
uint8_t ascii_value = field_cursor.value() + 0x20;
text_address.set("0x" + to_string_hex(ascii_value, 2));
}
DebugFontsView::DebugFontsView(NavigationView& nav)
: nav_{nav} {
add_children({&field_cursor,
&text_address});
set_focusable(true);
field_cursor.on_change = [&](int32_t) {
update_address_text();
set_dirty();
};
}
void DebugFontsView::focus() {
field_cursor.focus();
}
} /* namespace ui::external_app::font_viewer */
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2023 Mark Thompson
* copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -39,10 +40,23 @@ class DebugFontsView : public View {
public:
DebugFontsView(NavigationView& nav);
void paint(Painter& painter) override;
void focus() override;
std::string title() const override { return "Fonts"; };
private:
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name);
uint16_t display_font(Painter& painter, uint16_t y_offset, const Style* font_style, std::string_view font_name, bool is_big_font);
void update_address_text();
NumberField field_cursor{
{0 * 8, 0 * 8},
1,
{0, 1000},
1,
' '};
Text text_address{
{screen_width / 2, 0 * 16, screen_width / 2, 16},
"0x20",
};
NavigationView& nav_;
};
+83
View File
@@ -0,0 +1,83 @@
/*
* 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_hopper.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::hopper {
void initialize_app(ui::NavigationView& nav) {
nav.push<HopperView>();
}
} // namespace ui::external_app::hopper
extern "C" {
__attribute__((section(".external_app.app_hopper.application_information"), used)) application_information_t _application_information_hopper = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::hopper::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Hopper",
/*.bitmap_data = */ {
0xE0,
0x07,
0xF8,
0x1F,
0x1C,
0x38,
0x0E,
0x78,
0x06,
0x7C,
0x03,
0xCE,
0x03,
0xC7,
0x83,
0xC3,
0xC3,
0xC1,
0xE3,
0xC0,
0x73,
0xC0,
0x3E,
0x60,
0x1E,
0x70,
0x1C,
0x38,
0xF8,
0x1F,
0xE0,
0x07,
},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_jammer */ {'P', 'J', 'A', 'M'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+346
View File
@@ -0,0 +1,346 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2020 euquiq
* copyleft mr.r0b0t from the F society
*
* 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_hopper.hpp"
#include "ui_receiver.hpp"
#include "ui_freqman.hpp"
#include "ui_fileman.hpp"
#include "file_path.hpp"
#include "file_reader.hpp"
#include "convert.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
using namespace portapack;
namespace ui::external_app::hopper {
void HopperView::focus() {
button_load_list.focus();
}
HopperView::~HopperView() {
transmitter_model.disable();
baseband::shutdown();
}
void HopperView::update_freq_list_menu_view() {
menu_freq_list.clear();
uint8_t list_count = freq_list.size();
if (list_count == 0) {
menu_freq_list.add_item({"Add freq or load list",
Theme::getInstance()->bg_darkest->foreground,
nullptr,
nullptr});
}
for (uint8_t i = 0; i < list_count; i++) {
menu_freq_list.add_item({to_string_rounded_freq(freq_list[i], INT8_MAX),
Theme::getInstance()->bg_darkest->foreground,
nullptr,
nullptr});
}
set_dirty();
}
void HopperView::on_retune(const rf::Frequency freq, const uint32_t range) {
if (freq) {
transmitter_model.set_target_frequency(freq);
text_range_number.set(to_string_dec_uint(range, 2));
}
}
void HopperView::set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration) {
hopper_channels[i].enabled = true;
hopper_channels[i].width = (width * 0xFFFFFFULL) / 1536000;
hopper_channels[i].center = center;
hopper_channels[i].duration = 30720 * duration;
}
void HopperView::start_tx() {
uint32_t c, i = 0;
bool out_of_ranges = false;
size_t hop_value = options_hop.selected_index_value();
// Disable all channels by default
for (c = 0; c < JAMMER_MAX_CH; c++)
hopper_channels[c].enabled = false;
uint8_t channel_count = freq_list.size();
if (channel_count <= JAMMER_MAX_CH) {
for (c = 0; c < channel_count; c++) {
if (i >= JAMMER_MAX_CH) {
out_of_ranges = true;
break;
}
set_hopper_channel(i, JAMMER_CH_WIDTH, freq_list[c], hop_value);
i++;
}
} else {
out_of_ranges = true;
}
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()
} else {
if (out_of_ranges)
nav_.display_modal("Error", "Jam freq too much.");
}
}
void HopperView::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 HopperView::on_timer() {
if (++mscounter == 60) {
mscounter = 0;
if (jamming) {
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 .
transmitter_model.enable();
button_transmit.set_text("STOP");
baseband::set_jammer(true, (JammerType)options_type.selected_index(), options_speed.selected_index_value());
int32_t jitter_amount = field_jitter.value();
if (jitter_amount) {
lfsr_v = lfsr_iterate(lfsr_v);
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
mscounter += jitter_amount;
}
cooling = false;
seconds = 0;
}
} else {
if (++seconds >= field_timetx.value()) // Start cooling period:
{
transmitter_model.disable();
button_transmit.set_text("PAUSED");
baseband::set_jammer(false, JammerType::TYPE_FSK, 0);
int32_t jitter_amount = field_jitter.value();
if (jitter_amount) {
lfsr_v = lfsr_iterate(lfsr_v);
jitter_amount = (jitter_amount / 2) - (lfsr_v & jitter_amount);
mscounter += jitter_amount;
}
cooling = true;
seconds = 0;
}
}
}
}
}
void HopperView::load_list() {
freq_list.clear();
auto open_view = nav_.push<FileLoadView>(".PHOP");
open_view->push_dir(hopper_dir);
open_view->on_changed = [this](std::filesystem::path path) {
File f;
auto error = f.open(path);
if (error) {
nav_.display_modal("Err", "Can't open.");
update_freq_list_menu_view();
return;
}
freq_list.clear();
auto reader = FileLineReader(f);
for (const auto& line : reader) {
if (line.length() == 0 || line[0] == '#')
continue;
rf::Frequency freq;
if (parse_int(line, freq)) {
if (freq_list.size() >= JAMMER_MAX_CH) {
break;
}
freq_list.push_back(freq);
}
}
update_freq_list_menu_view();
};
update_freq_list_menu_view();
}
void HopperView::save_list() {
if (freq_list.empty()) {
nav_.display_modal("Err", "Nothing to save");
return;
}
ensure_directory(hopper_dir);
filename_buffer = "";
text_prompt(
nav_,
filename_buffer,
64,
[this](std::string& value) {
auto path = hopper_dir / (value + ".PHOP");
File f;
auto error = f.create(path);
if (error) {
nav_.display_modal("Err", "Create fail.");
return;
}
for (const auto& freq : freq_list) {
auto freq_str = to_string_dec_uint(freq) + "\n";
f.write(freq_str.c_str(), freq_str.length());
}
text_range_number.set("Saved: " + path.filename().string());
});
}
HopperView::HopperView(
NavigationView& nav)
: nav_{nav} {
// baseband::run_image(portapack::spi_flash::image_tag_jammer);
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&menu_freq_list,
&button_load_list,
&button_add_freq,
&button_delete_freq,
&button_save_list,
&button_clear,
&labels,
&options_type,
&text_range_number,
&text_range_total,
&options_speed,
&options_hop,
&field_timetx,
&field_timepause,
&field_jitter,
&field_gain,
&field_amp,
&button_transmit});
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);
field_timetx.set_value(30);
field_timepause.set_value(1);
field_gain.set_value(transmitter_model.tx_gain());
field_amp.set_value(transmitter_model.rf_amp());
button_load_list.on_select = [this]() {
load_list();
update_freq_list_menu_view();
};
button_add_freq.on_select = [this]() {
if (freq_list.size() < JAMMER_MAX_CH) {
auto kb_view = nav_.push<FrequencyKeypadView>(0);
kb_view->on_changed = [this](rf::Frequency freq) {
freq_list.push_back(freq);
update_freq_list_menu_view();
};
} else {
nav_.display_modal("Err", "No more.");
}
update_freq_list_menu_view();
};
button_delete_freq.on_select = [this]() {
auto i = menu_freq_list.highlighted_index();
if (i < freq_list.size()) {
freq_list.erase(freq_list.begin() + i);
}
update_freq_list_menu_view();
};
button_save_list.on_select = [this]() {
save_list();
update_freq_list_menu_view();
};
button_clear.on_select = [this]() {
// clang-format off
nav_.display_modal("Del:", "Clean all?\n", YESNO, [this](bool choice) {
if (choice){
freq_list.clear();
update_freq_list_menu_view();
} }, TRUE);
// clang-format on
update_freq_list_menu_view();
};
button_transmit.on_select = [this](Button&) {
if (jamming || cooling)
stop_tx();
else
start_tx();
};
menu_freq_list.on_left = [this]() {
button_load_list.focus();
};
menu_freq_list.on_right = [this]() {
button_load_list.focus();
};
update_freq_list_menu_view();
}
} // namespace ui::external_app::hopper
+233
View File
@@ -0,0 +1,233 @@
/*
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
* Copyright (C) 2016 Furrtek
* Copyright (C) 2020 euquiq
* copyleft mr.r0b0t from the F society
*
* 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_language.hpp"
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "ui_tabview.hpp"
#include "transmitter_model.hpp"
#include "message.hpp"
#include "jammer.hpp"
#include "lfsr_random.hpp"
#include "radio_state.hpp"
using namespace jammer;
namespace ui::external_app::hopper {
class HopperView : public View {
public:
HopperView(NavigationView& nav);
~HopperView();
HopperView(const HopperView&) = delete;
HopperView(HopperView&&) = delete;
HopperView& operator=(const HopperView&) = delete;
HopperView& operator=(HopperView&&) = delete;
void focus() override;
void update_freq_list_menu_view();
std::string title() const override { return "Hopper"; };
std::vector<rf::Frequency> freq_list{315000000, 433920000};
private:
NavigationView& nav_;
TxRadioState radio_state_{
0 /* frequency */,
3500000 /* bandwidth */,
3072000 /* sampling rate */
};
void start_tx();
void on_timer();
void stop_tx();
void set_hopper_channel(uint32_t i, uint32_t width, uint64_t center, uint32_t duration);
void on_retune(const rf::Frequency freq, const uint32_t range);
void load_list();
void save_list();
void on_file_save_write(const std::string& value);
HopperChannel* hopper_channels = (HopperChannel*)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
std::string filename_buffer = "";
const Style& style_val = *Theme::getInstance()->fg_green;
const Style& style_cancel = *Theme::getInstance()->fg_red;
MenuView menu_freq_list{
{0, 0, screen_width, 8 * 16},
false};
NewButton button_load_list{
{0 * 8, 9 * 16 + 4, 4 * 8, 32},
{},
&bitmap_icon_load,
Color::dark_blue(),
/*vcenter*/ true};
NewButton button_save_list{
{4 * 8, 9 * 16 + 4, 4 * 8, 32},
{},
&bitmap_icon_save,
Color::dark_blue(),
/*vcenter*/ true};
NewButton button_add_freq{
{8 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
{},
&bitmap_icon_add,
Color::dark_green(),
/*vcenter*/ true};
NewButton button_delete_freq{
{12 * 8 + 4, 9 * 16 + 4, 4 * 8, 32},
{},
&bitmap_icon_trash,
Color::dark_red(),
/*vcenter*/ true};
NewButton button_clear{
{screen_width - 4 * 8, 9 * 16 + 4, 4 * 8, 32},
{},
&bitmap_icon_tools_wipesd,
Color::red(),
/*vcenter*/ true};
Labels labels{
{{2 * 8, 23 * 8}, "Type:", Theme::getInstance()->fg_light->foreground},
{{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.
{{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},
{{2 * 8, 35 * 8}, "Gain:", Theme::getInstance()->fg_light->foreground},
{{11 * 8, 35 * 8}, "A:", Theme::getInstance()->fg_light->foreground}};
OptionsField options_type{
{7 * 8, 23 * 8},
8,
{
{"Rand FSK", 0},
{"FM tone", 1},
{"CW sweep", 2},
{"Rand CW", 3},
}};
Text text_range_number{
{16 * 8, 23 * 8, 2 * 8, 16},
"--"};
Text text_range_total{
{18 * 8, 23 * 8, 3 * 8, 16},
"/--"};
OptionsField options_speed{
{7 * 8, 25 * 8},
6,
{{"10Hz ", 10},
{"100Hz ", 100},
{"1kHz ", 1000},
{"10kHz ", 10000},
{"100kHz", 100000}}};
OptionsField options_hop{
{7 * 8, 27 * 8},
5,
{{"10ms ", 1},
{"50ms ", 5},
{"100ms", 10},
{"1s ", 100},
{"2s ", 200},
{"5s ", 500},
{"10s ", 1000}}};
NumberField field_timetx{
{7 * 8, 29 * 8},
3,
{1, 180},
1,
' ',
};
NumberField field_timepause{
{8 * 8, 31 * 8},
2,
{1, 60},
1,
' ',
};
NumberField field_jitter{
{8 * 8, 33 * 8},
2,
{1, 60},
1,
' ',
};
NumberField field_gain{
{8 * 8, 35 * 8},
2,
{0, 47},
1,
' ',
};
NumberField field_amp{
{13 * 8, 35 * 8},
1,
{0, 1},
1,
' ',
};
Button button_transmit{
{148, 216, 80, 80},
LanguageHelper::currentMessages[LANG_START]};
MessageHandlerRegistration message_handler_retune{
Message::ID::Retune,
[this](Message* const p) {
const auto message = static_cast<const RetuneMessage*>(p);
this->on_retune(message->freq, message->range);
}};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->on_timer();
}};
};
} // namespace ui::external_app::hopper
+155 -1
View File
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
* copyleft Elliot Alderson from F society
* copyleft Darlene Alderson from F society
*
* This file is part of PortaPack.
*
@@ -178,7 +180,8 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
&label_waveform,
&waveform,
&button_open,
&button_save});
&button_save,
&button_bug_key});
// Initialize default values for controls
field_symbol_rate.set_value(100);
@@ -267,6 +270,16 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
});
};
button_bug_key.on_select = [&](Button&) {
auto bug_key_input_view = nav_.push<OOKEditorBugKeyView>(ook_data.payload);
bug_key_input_view->on_save = [this](std::string p) {
ook_data.payload = p;
text_payload.set(ook_data.payload);
draw_waveform();
};
};
// Configure button to start or stop the transmission
button_send_stop.on_select = [this](Button&) {
if (!is_transmitting) {
@@ -280,4 +293,145 @@ OOKEditorAppView::OOKEditorAppView(NavigationView& nav)
// initial waveform drawing (should be a single line)
draw_waveform();
}
/*************** bug key view ****************/
OOKEditorBugKeyView::OOKEditorBugKeyView(NavigationView& nav, std::string payload)
: nav_{nav},
payload_{payload} {
add_children({&labels,
&field_primary_step,
&field_secondary_step,
&console,
&button_insert_high_level_long,
&button_insert_high_level_short,
&button_insert_low_level_long,
&button_insert_low_level_short,
&button_delete,
&button_save});
button_insert_low_level_short.on_select = [this](Button&) {
on_insert(InsertType::LOW_LEVEL_SHORT);
};
button_insert_low_level_long.on_select = [this](Button&) {
on_insert(InsertType::LOW_LEVEL_LONG);
};
button_insert_high_level_short.on_select = [this](Button&) {
on_insert(InsertType::HIGH_LEVEL_SHORT);
};
button_insert_high_level_long.on_select = [this](Button&) {
on_insert(InsertType::HIGH_LEVEL_LONG);
};
button_delete.on_select = [this](Button&) {
on_delete();
};
button_save.on_select = [this](Button&) {
if (on_save) on_save(build_payload());
nav_.pop();
};
auto update_step_buttons = [this](int32_t value, Button& btnLow, Button& btnHigh) {
std::string low_level_btn_str;
std::string high_level_btn_str;
if (value <= 14) { // the button width allow max 14 chars
for (int i = 0; i < value; i++) {
low_level_btn_str.push_back('0');
high_level_btn_str.push_back('1');
}
} else {
low_level_btn_str = to_string_dec_int(value) + " * \"0\"";
high_level_btn_str = to_string_dec_int(value) + " * \"1\"";
}
btnLow.set_text(" "); // set_dirty broken console. this is work around
btnHigh.set_text(" ");
btnLow.set_text(low_level_btn_str);
btnHigh.set_text(high_level_btn_str);
};
field_primary_step.on_change = [&](int32_t) {
update_step_buttons(field_primary_step.value(),
button_insert_low_level_short,
button_insert_high_level_short);
update_console();
};
field_secondary_step.on_change = [&](int32_t) {
update_step_buttons(field_secondary_step.value(),
button_insert_low_level_long,
button_insert_high_level_long);
update_console();
};
field_primary_step.set_value(1);
field_secondary_step.set_value(2);
update_step_buttons(field_primary_step.value(),
button_insert_low_level_short,
button_insert_high_level_short);
update_console();
}
void OOKEditorBugKeyView::on_insert(InsertType type) {
auto promise_length = 0;
std::string promose_level = "0";
switch (type) {
case InsertType::LOW_LEVEL_SHORT:
promise_length = field_primary_step.value();
promose_level = "0";
break;
case InsertType::LOW_LEVEL_LONG:
promise_length = field_secondary_step.value();
promose_level = "0";
break;
case InsertType::HIGH_LEVEL_SHORT:
promise_length = field_primary_step.value();
promose_level = "1";
break;
case InsertType::HIGH_LEVEL_LONG:
promise_length = field_secondary_step.value();
promose_level = "1";
break;
}
for (auto i = 0; i < promise_length; i++) {
payload_ += promose_level;
}
update_console();
}
void OOKEditorBugKeyView::on_delete() {
// I'm aware that if user inputted like: [long high][long high][short high], this will delete a pile of them
// but this doesnt matter because:
// 1. they should not do it, high or low shoudl cross each other, don't repeat
// 2. don't have too much RAM to trach the input trace
if (payload_.length() > 0) {
size_t len = payload_.length();
char last_char = payload_[len - 1];
size_t pos = len - 1;
while (pos > 0 && payload_[pos - 1] == last_char) {
pos--;
}
payload_.erase(pos);
update_console();
}
}
void OOKEditorBugKeyView::update_console() {
console.clear(true);
console.write(payload_);
}
std::string OOKEditorBugKeyView::build_payload() {
return payload_;
}
void OOKEditorBugKeyView::focus() {
button_save.focus();
}
} // namespace ui::external_app::ook_editor
@@ -1,5 +1,7 @@
/*
* Copyright (C) 2024 Samir Sánchez Garnica @sasaga92
* copyleft Elliot Alderson from F society
* copyleft Darlene Alderson from F society
*
* This file is part of PortaPack.
*
@@ -29,6 +31,8 @@ namespace ui::external_app::ook_editor {
#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz).
#define WAVEFORM_BUFFER_SIZE 550
/*****************Editor View ******************/
class OOKEditorAppView : public View {
public:
void focus() override;
@@ -123,6 +127,7 @@ class OOKEditorAppView : public View {
Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]};
Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]};
Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]};
Button button_bug_key{{0, 125 + 28 + 3, screen_width, 28}, "Bug Key"};
Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]};
// Progress bar to display transmission progress.
@@ -132,6 +137,82 @@ class OOKEditorAppView : public View {
Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground};
};
/******** bug key input view **********/
enum InsertType {
LOW_LEVEL_SHORT,
LOW_LEVEL_LONG,
HIGH_LEVEL_SHORT,
HIGH_LEVEL_LONG
};
class OOKEditorBugKeyView : public View {
public:
std::function<void(std::string)> on_save{};
OOKEditorBugKeyView(NavigationView& nav, std::string payload);
std::string title() const override { return "Bug.K"; };
void focus() override;
private:
NavigationView& nav_;
std::string payload_ = "";
std::string path_ = "";
uint32_t delay_{0};
std::string delay_str{""}; // needed by text_prompt
void on_insert(InsertType type);
void on_delete();
void update_console();
std::string build_payload();
Labels labels{
{{0 * 8, 0 * 16}, "Primary Step", Theme::getInstance()->fg_light->foreground},
{{(screen_width / 2), 0 * 16}, "Secondary Step", Theme::getInstance()->fg_light->foreground}};
NumberField field_primary_step{
{0 * 8, 1 * 16},
3,
{0, 550},
1,
' '};
NumberField field_secondary_step{
{(screen_width / 2), 1 * 16},
3,
{0, 550},
1,
' '};
Console console{
{0, 3 * 16, screen_width, screen_height - 10 * 16}};
Button button_insert_low_level_long{
{0 * 8, 13 * 16, screen_width / 2, 2 * 16},
"00"};
Button button_insert_low_level_short{
{0 * 8, 15 * 16, screen_width / 2, 2 * 16},
"0"};
Button button_insert_high_level_long{
{(screen_width / 2), 13 * 16, screen_width / 2, 2 * 16},
"11"};
Button button_insert_high_level_short{
{(screen_width / 2), 15 * 16, screen_width / 2, 2 * 16},
"1"};
Button button_delete{
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"<Backspace"};
Button button_save{
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"Save"};
};
}; // namespace ui::external_app::ook_editor
#endif /*__UI_OOK_EDITOR_H__*/
+84
View File
@@ -0,0 +1,84 @@
/*
* Copyright (C) 2024 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_playlist_editor.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::playlist_editor {
void initialize_app(ui::NavigationView& nav) {
nav.push<PlaylistEditorView>();
}
} // namespace ui::external_app::playlist_editor
extern "C" {
__attribute__((section(".external_app.app_playlist_editor.application_information"), used)) application_information_t _application_information_playlist_editor = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::playlist_editor::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "PlaylistEdit",
/*.bitmap_data = */ {
0x03,
0x00,
0x00,
0x00,
0x03,
0x00,
0x00,
0x00,
0x0F,
0x00,
0x00,
0x00,
0x03,
0x01,
0x80,
0x01,
0xC3,
0x00,
0xE0,
0xFF,
0xEF,
0xFF,
0xC0,
0x00,
0x83,
0x01,
0x00,
0x01,
0x03,
0x00,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::cyan().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -0,0 +1,367 @@
/*
* copyleft Elliot Alderson from F society
* copyleft Darlene Alderson from F society
*
* 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_playlist_editor.hpp"
#include "ui_navigation.hpp"
#include "ui_external_items_menu_loader.hpp"
#include "file.hpp"
#include "ui_fileman.hpp"
#include "file_path.hpp"
#include "string_format.hpp"
namespace fs = std::filesystem;
#include "string_format.hpp"
#include "file_reader.hpp"
using namespace portapack;
namespace ui::external_app::playlist_editor {
/*********menu**********/
PlaylistEditorView::PlaylistEditorView(NavigationView& nav)
: nav_{nav} {
portapack::async_tx_enabled = true;
add_children({&labels,
&button_new,
&text_current_ppl_file,
&menu_view,
&text_hint,
&button_open_playlist,
&button_edit,
&button_insert,
&button_save_playlist});
menu_view.set_parent_rect({0, 2 * 8, screen_width, 24 * 8});
menu_view.on_highlight = [this]() {
text_hint.set("Edit:" +
playlist[menu_view.highlighted_index()].substr(playlist[menu_view.highlighted_index()].find_last_of('/') + 1,
playlist[menu_view.highlighted_index()].find(',') -
playlist[menu_view.highlighted_index()].find_last_of('/') - 1));
};
button_new.on_select = [this](Button&) {
if (on_create_ppl()) {
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
refresh_interface();
}
};
button_open_playlist.on_select = [this](Button&) {
open_file();
};
button_edit.on_select = [this](Button&) {
on_edit_item();
};
button_insert.on_select = [this](Button&) {
on_insert_item();
};
button_save_playlist.on_select = [this](Button&) {
save_ppl();
};
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON);
}
void PlaylistEditorView::focus() {
menu_view.focus();
}
void PlaylistEditorView::open_file() {
auto open_view = nav_.push<FileLoadView>(".PPL");
open_view->push_dir(playlist_dir);
open_view->on_changed = [this](fs::path new_file_path) {
current_ppl_path = new_file_path;
on_file_changed(new_file_path);
};
}
void PlaylistEditorView::swap_opened_file_or_new_button(DisplayFilenameOrNewButton d) {
if (d == DisplayFilenameOrNewButton::DISPLAY_NEW_BUTTON) {
button_new.hidden(false);
text_current_ppl_file.hidden(true);
} else {
button_new.hidden(true);
text_current_ppl_file.hidden(false);
text_current_ppl_file.set(current_ppl_name_buffer);
}
refresh_interface();
}
/*
NB: same name would became as "open file"
*/
bool PlaylistEditorView::on_create_ppl() {
bool success = false;
text_prompt(
nav_,
current_ppl_name_buffer,
100,
[&](std::string& s) {
current_ppl_name_buffer = s;
success = true;
current_ppl_name_buffer += ".PPL";
current_ppl_path = playlist_dir / std::filesystem::path(current_ppl_name_buffer);
File f;
f.open(current_ppl_path, true, true); // prob safer here as standalone obj as read only and then open again in process func
f.close();
on_file_changed(current_ppl_path);
});
return success;
}
void PlaylistEditorView::on_file_changed(const fs::path& new_file_path) {
File playlist_file;
auto error = playlist_file.open(new_file_path.string());
if (error) return;
menu_view.clear();
auto reader = FileLineReader(playlist_file);
for (const auto& line : reader) {
playlist.push_back(line);
}
for (auto& line : playlist) {
// remove empty lines
if (line == "\n" || line == "\r\n" || line == "\r") {
playlist.erase(std::remove(playlist.begin(), playlist.end(), line), playlist.end());
}
// remove line end \n etc
if (line.length() > 0 && (line[line.length() - 1] == '\n' || line[line.length() - 1] == '\r')) {
line = line.substr(0, line.length() - 1);
}
}
text_hint.set("Highlight an entry");
text_current_ppl_file.set(new_file_path.string());
ever_opened = true;
swap_opened_file_or_new_button(DisplayFilenameOrNewButton::DISPLAY_FILENAME);
refresh_menu_view();
}
void PlaylistEditorView::refresh_menu_view() {
menu_view.clear();
for (const auto& line : playlist) {
if (line.length() == 0 || line[0] == '#') {
menu_view.add_item({line,
ui::Color::grey(),
&bitmap_icon_notepad,
[this](KeyEvent) {
button_insert.focus();
}});
} else {
const auto filename = line.substr(line.find_last_of('/') + 1, line.find(',') - line.find_last_of('/') - 1);
menu_view.add_item({filename,
ui::Color::white(),
&bitmap_icon_cwgen,
[this](KeyEvent) {
button_edit.focus();
}});
}
}
}
void PlaylistEditorView::on_edit_item() {
if (!ever_opened || playlist.empty()) {
nav_.display_modal("Err", "No entry");
return;
}
auto edit_view = nav_.push<PlaylistItemEditView>(
playlist[menu_view.highlighted_index()]);
edit_view->set_on_delete([this]() {
playlist.erase(playlist.begin() + menu_view.highlighted_index());
refresh_interface();
});
edit_view->on_save = [this](std::string new_item) {
playlist[menu_view.highlighted_index()] = new_item;
refresh_interface();
};
}
void PlaylistEditorView::on_insert_item() {
// if (current_ppl_path.empty() || current_ppl_path.string().find_first_not_of(" \t\n\r") == std::string::npos) {
if (!ever_opened) { // TODO: this is a workaround because the above line is not working and I took one hour and didn't find the issue
nav_.display_modal("Err", "No playlist file loaded");
return;
}
auto edit_view = nav_.push<PlaylistItemEditView>(
"");
edit_view->on_save = [&](std::string new_item) {
if (playlist.empty()) {
playlist.push_back(new_item);
} else {
playlist.insert(playlist.begin() + menu_view.highlighted_index() + 1, new_item);
}
refresh_interface();
};
}
void PlaylistEditorView::refresh_interface() {
const auto previous_index = menu_view.highlighted_index();
refresh_menu_view();
set_dirty();
menu_view.set_highlighted(previous_index);
}
void PlaylistEditorView::save_ppl() {
if (current_ppl_path.empty()) {
nav_.display_modal("Err", "No playlist file loaded");
return;
} else if (playlist.empty()) {
nav_.display_modal("Err", "List is empty");
return;
}
File playlist_file;
auto error = playlist_file.open(current_ppl_path.string(), false, false);
if (error) {
nav_.display_modal("Err", "open err");
return;
}
// clear file
playlist_file.seek(0);
playlist_file.truncate();
// write new data
for (const auto& entry : playlist) {
playlist_file.write_line(entry);
}
nav_.display_modal("Save", "Saved playlist\n" + current_ppl_path.string());
}
/*********edit**********/
PlaylistItemEditView::PlaylistItemEditView(
NavigationView& nav,
std::string item)
: nav_{nav},
original_item_{item} {
add_children({&labels,
&field_path,
&field_delay,
&button_browse,
&button_input_delay,
&button_delete,
&button_save});
button_browse.on_select = [this, &nav](Button&) {
auto open_view = nav.push<FileLoadView>(".C16");
open_view->push_dir(captures_dir);
open_view->on_changed = [this](fs::path path) {
field_path.set_text(path.string());
path_ = path.string();
};
field_delay.on_change = [&](auto) {
delay_ = field_delay.value();
};
};
button_input_delay.on_select = [this](Button&) {
delay_str = to_string_dec_uint(delay_);
if (delay_str == "0") {
delay_str = "";
}
text_prompt(
nav_,
delay_str,
100,
[&](std::string& s) {
delay_ = atoi(s.c_str());
field_delay.set_value(delay_);
refresh_ui();
});
};
button_delete.on_select = [this](Button&) {
if (on_delete) on_delete();
nav_.pop();
};
button_save.on_select = [&](Button&) {
if (path_.empty()) {
nav_.display_modal("Err", "Select a file\n or press back to cancel");
return;
}
if (on_save) on_save(build_item());
nav_.pop();
};
if (!on_delete) {
button_delete.hidden(true);
}
parse_item(item);
refresh_ui();
}
void PlaylistItemEditView::focus() {
button_save.focus();
}
void PlaylistItemEditView::refresh_ui() {
field_path.set_text(path_);
field_delay.set_value(delay_);
}
void PlaylistItemEditView::parse_item(std::string item) {
// Parse format: path,delay
if (item.empty()) {
return;
}
auto parts = split_string(item, ',');
if (parts.size() >= 1) {
path_ = std::string{parts[0]};
}
if (parts.size() >= 2) {
delay_ = atoi(std::string{parts[1]}.c_str());
}
}
std::string PlaylistItemEditView::build_item() const {
const auto v = path_ + "," + to_string_dec_uint(field_delay.value());
return path_ + "," + to_string_dec_uint(field_delay.value());
}
} // namespace ui::external_app::playlist_editor
@@ -0,0 +1,159 @@
/*
* copyleft Elliot Alderson from F society
* copyleft Darlene Alderson from F society
*
* 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_PLAYLIST_EDITOR_H__
#define __UI_PLAYLIST_EDITOR_H__
#include "ui_navigation.hpp"
namespace fs = std::filesystem;
namespace ui::external_app::playlist_editor {
enum DisplayFilenameOrNewButton {
DISPLAY_FILENAME,
DISPLAY_NEW_BUTTON
};
class PlaylistEditorView : public View {
public:
PlaylistEditorView(NavigationView& nav);
std::string title() const override { return "PPL Edit"; };
private:
NavigationView& nav_;
void focus() override;
std::vector<std::string> playlist = {};
fs::path current_ppl_path = "";
std::string current_ppl_name_buffer = ""; // this is because text_prompt needs it. TODO: this is so annoying, shoudl refactor that func
bool ever_opened = false;
Labels labels{
{{0 * 8, 0 * 16}, "PPL file:", Theme::getInstance()->fg_light->foreground}};
Button button_new{
{(sizeof("PPL file:") + 1) * 8, 0 * 16, 8 * 5, 16},
"New"};
Text text_current_ppl_file{
{sizeof("PPL file:") * 8, 0 * 16, screen_width - sizeof("PPL file:") * 8, 16},
""};
MenuView menu_view{};
Text text_hint{
{0, 27 * 8, screen_width, 16},
"Open a PPL file"};
Text text_ppl_name{
{0, 27 * 8, screen_width, 16},
"Highlight an app"};
Button button_open_playlist{
{0, 29 * 8, screen_width / 2 - 1, 32},
"Open PPL"};
Button button_edit{
{screen_width / 2 + 2, 29 * 8, screen_width / 2 - 2, 32},
"Edit Item"};
Button button_insert{
{0, screen_height - 32 - 16, screen_width / 2 - 1, 32},
"Ins. After"};
Button button_save_playlist{
{screen_width / 2 + 2, screen_height - 32 - 16, screen_width / 2 - 2, 32},
"Save PPL"};
void open_file();
void swap_opened_file_or_new_button(DisplayFilenameOrNewButton d);
void on_file_changed(const fs::path& path);
bool on_create_ppl();
void refresh_interface();
void on_edit_item();
void on_insert_item();
void refresh_menu_view();
void save_ppl();
};
class PlaylistItemEditView : public View {
public:
std::function<void(std::string)> on_save{};
std::function<void()> on_delete{};
PlaylistItemEditView(NavigationView& nav, std::string item);
std::string title() const override { return "Edit Item"; };
void focus() override;
void set_on_delete(std::function<void()> callback) {
on_delete = callback;
button_delete.hidden(false);
}
private:
NavigationView& nav_;
std::string original_item_ = "";
std::string path_ = "";
uint32_t delay_{0};
std::string delay_str{""}; // needed by text_prompt
void refresh_ui();
void parse_item(std::string item);
std::string build_item() const;
Labels labels{
{{0 * 8, 1 * 16}, "Path:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Delay(ms):", Theme::getInstance()->fg_light->foreground}};
TextField field_path{
{0, 2 * 16, screen_width, 16},
"empty"};
NumberField field_delay{
{11 * 8, 5 * 16},
5,
{0, 99999},
10,
' '};
Button button_browse{
{2 * 8, 8 * 16, 8 * 8, 3 * 16},
"Browse"};
Button button_input_delay{
{12 * 8, 8 * 16, sizeof("Input Delay") * 8, 3 * 16},
"Input Delay"};
Button button_delete{
{1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"Delete"};
Button button_save{
{1 + screen_width / 2 + 1, 17 * 16, screen_width / 2 - 4, 2 * 16},
"Save"};
};
} // namespace ui::external_app::playlist_editor
#endif // __UI_PLAYLIST_EDITOR_H__
+1 -1
View File
@@ -74,7 +74,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
0x01,
},
/*.icon_color = */ ui::Color::yellow().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'F', 'R'},
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 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_sd_wipe.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::sd_wipe {
void initialize_app(ui::NavigationView& nav) {
nav.push<WipeSDView>();
}
} // namespace ui::external_app::sd_wipe
extern "C" {
__attribute__((section(".external_app.app_sd_wipe.application_information"), used)) application_information_t _application_information_sd_wipe = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::sd_wipe::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Wipe SD card",
/*.bitmap_data = */ {0xF0, 0x3F, 0x58, 0x35, 0x5C, 0x35, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0x3C, 0x1C, 0xBC, 0xC9, 0xBC, 0xE3, 0x2C, 0x77, 0x5C, 0x3E, 0xAC, 0x1C, 0x5C, 0x3E, 0x2C, 0x77, 0x9C, 0xE3, 0xAC, 0xC1},
/*.icon_color = */ ui::Color::red().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -22,7 +22,9 @@
#include "ui_sd_wipe.hpp"
namespace ui {
using namespace ui;
namespace ui::external_app::sd_wipe {
Thread* WipeSDView::thread{nullptr};
@@ -63,4 +65,4 @@ void WipeSDView::focus() {
}
}
} /* namespace ui */
} // namespace ui::external_app::sd_wipe
@@ -30,7 +30,9 @@
#include <cstdint>
namespace ui {
using namespace ui;
namespace ui::external_app::sd_wipe {
class WipeSDView : public View {
public:
@@ -87,6 +89,6 @@ class WipeSDView : public View {
""};
};
} /* namespace ui */
} // namespace ui::external_app::sd_wipe
#endif /*__UI_SD_WIPE_H__*/
+1 -1
View File
@@ -74,7 +74,7 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
0xF1,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
+50
View File
@@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 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_view_wav.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::view_wav {
void initialize_app(ui::NavigationView& nav) {
nav.push<ViewWavView>();
}
} // namespace ui::external_app::view_wav
extern "C" {
__attribute__((section(".external_app.app_view_wav.application_information"), used)) application_information_t _application_information_view_wav = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::view_wav::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "WAV Viewer",
/*.bitmap_data = */ {0xF0, 0x0F, 0x1C, 0x18, 0x17, 0x38, 0x15, 0x78, 0x15, 0xF8, 0x15, 0x82, 0x15, 0x8B, 0xD5, 0x83, 0xD5, 0xBB, 0xD5, 0x83, 0x15, 0x8B, 0x15, 0x92, 0x15, 0xA0, 0x17, 0x80, 0x1C, 0x80, 0xF0, 0xFF},
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {'P', 'A', 'T', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
@@ -28,8 +28,9 @@
#include "string_format.hpp"
using namespace portapack;
using namespace ui;
namespace ui {
namespace ui::external_app::view_wav {
void ViewWavView::update_scale(int32_t new_scale) {
scale = new_scale;
@@ -345,4 +346,4 @@ ViewWavView::~ViewWavView() {
baseband::shutdown();
}
} /* namespace ui */
} /* namespace ui::external_app::view_wav */
@@ -28,7 +28,9 @@
#include "ui_receiver.hpp"
#include "replay_thread.hpp"
namespace ui {
using namespace ui;
namespace ui::external_app::view_wav {
class ViewWavView : public View {
public:
@@ -202,4 +204,4 @@ class ViewWavView : public View {
}};
};
} /* namespace ui */
} /* namespace ui::external_app::view_wav */
+4
View File
@@ -50,6 +50,10 @@ Optional<File::Error> File::open_fatfs(const std::filesystem::path& filename, BY
}
}
/*
* @param read_only: open in readonly mode
* @param create: create if it doesnt exist
*/
Optional<File::Error> File::open(const std::filesystem::path& filename, bool read_only, bool create) {
BYTE mode = read_only ? FA_READ : FA_READ | FA_WRITE;
if (create)
+2
View File
@@ -49,3 +49,5 @@ const std::filesystem::path sstv_dir = u"SSTV";
const std::filesystem::path wav_dir = u"WAV";
const std::filesystem::path whipcalc_dir = u"WHIPCALC";
const std::filesystem::path ook_editor_dir = u"OOKFILES";
const std::filesystem::path hopper_dir = u"HOPPER";
const std::filesystem::path subghz_dir = u"SUBGHZ";
+2
View File
@@ -51,5 +51,7 @@ extern const std::filesystem::path sstv_dir;
extern const std::filesystem::path wav_dir;
extern const std::filesystem::path whipcalc_dir;
extern const std::filesystem::path ook_editor_dir;
extern const std::filesystem::path hopper_dir;
extern const std::filesystem::path subghz_dir;
#endif /* __FILE_PATH_H__ */
+2 -1
View File
@@ -41,7 +41,8 @@ enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
SPEC_MODULATION
SPEC_MODULATION,
AMFM_MODULATION // Added for Wefax.
};
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
+6 -1
View File
@@ -49,9 +49,10 @@ options_t freqman_modulations = {
{"NFM", 1},
{"WFM", 2},
{"SPEC", 3},
{"AMFM", 4},
};
options_t freqman_bandwidths[4] = {
options_t freqman_bandwidths[5] = {
{
// AM
{"DSB 9k", 0},
@@ -100,6 +101,10 @@ options_t freqman_bandwidths[4] = {
{"5000k", 5500000},
{"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC).
},
{
// AMFM for Wefax-
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones.
},
};
// TODO: these should be indexes.
+26 -6
View File
@@ -39,13 +39,14 @@ using namespace portapack;
namespace {
static constexpr std::array<baseband::AMConfig, 5> am_configs{{
static constexpr std::array<baseband::AMConfig, 6> am_configs{{
// we config here all the non COMMON parameters to each AM modulation type in RX.
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
{taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 2K8 (+ 2K8)
{taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB}, // SSB LSB BW 2K8 (- 2K8)
{taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB}, // SSB USB BW 0K7 (+ 0K7) used to get audio tone from CW Morse, assuming tx shifted +700hz aprox
{taps_9k0_decim_2, taps_9k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 9khz (+-4k5) commercial EU bandwidth .
{taps_6k0_decim_2, taps_6k0_dsb_channel, AMConfigureMessage::Modulation::DSB, audio_12k_hpf_300hz_config}, // AM DSB-C BW 6khz (+-3k0) narrow AM , ham equipments.
{taps_6k0_decim_2, taps_2k8_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 2K8 (+ 2K8) SSB ham equipments.
{taps_6k0_decim_2, taps_2k8_lsb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB LSB BW 2K8 (- 2K8) SSB ham equipments.
{taps_6k0_decim_2, taps_0k7_usb_channel, AMConfigureMessage::Modulation::SSB, audio_12k_hpf_300hz_config}, // SSB USB BW 0K7 (+ 0K7) To get audio tone from CW Morse, assuming tx shifted +700hz aprox
{taps_6k0_decim_2, taps_2k6_usb_wefax_channel, AMConfigureMessage::Modulation::SSB_FM, audio_12k_lpf_1500hz_config}, // SSB USB+FM to demod. Subcarrier FM Audio Tones to get APT Weather Fax.
}};
static constexpr std::array<baseband::NBFMConfig, 3> nbfm_configs{{
@@ -145,6 +146,17 @@ void ReceiverModel::set_am_configuration(uint8_t n) {
}
}
uint8_t ReceiverModel::amfm_configuration() const {
return settings_.amfm_config_index;
}
void ReceiverModel::set_amfm_configuration(uint8_t n) {
if (n < am_configs.size()) {
settings_.amfm_config_index = n;
update_modulation();
}
}
uint8_t ReceiverModel::nbfm_configuration() const {
return settings_.nbfm_config_index;
}
@@ -303,6 +315,10 @@ void ReceiverModel::update_modulation() {
update_am_configuration();
break;
case Mode::AMAudioFMApt: // Wefax , first step , USB demodulation from the AMAudio group, index 2 (USB+3K), TODO +FM subcarrier demod ?
update_amfm_configuration();
break;
case Mode::NarrowbandFMAudio:
update_nbfm_configuration();
break;
@@ -321,6 +337,10 @@ void ReceiverModel::update_am_configuration() {
am_configs[am_configuration()].apply();
}
void ReceiverModel::update_amfm_configuration() {
am_configs[amfm_configuration()].apply(); // update with different index for Wefax.
}
void ReceiverModel::update_nbfm_configuration() {
nbfm_configs[nbfm_configuration()].apply(squelch_level());
}
+7 -1
View File
@@ -41,7 +41,8 @@ class ReceiverModel {
NarrowbandFMAudio = 1,
WidebandFMAudio = 2,
SpectrumAnalysis = 3,
Capture = 4
AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
Capture = 5,
};
struct settings_t {
@@ -54,6 +55,7 @@ class ReceiverModel {
bool rf_amp = false;
Mode mode = Mode::NarrowbandFMAudio;
uint8_t am_config_index = 0;
uint8_t amfm_config_index = 0;
uint8_t nbfm_config_index = 0;
uint8_t wfm_config_index = 0;
uint8_t squelch_level = 80;
@@ -87,6 +89,9 @@ class ReceiverModel {
uint8_t am_configuration() const;
void set_am_configuration(uint8_t n);
uint8_t amfm_configuration() const;
void set_amfm_configuration(uint8_t n);
uint8_t nbfm_configuration() const;
void set_nbfm_configuration(uint8_t n);
@@ -140,6 +145,7 @@ class ReceiverModel {
void update_modulation();
void update_am_configuration();
void update_amfm_configuration();
void update_nbfm_configuration();
void update_wfm_configuration();
+2 -1
View File
@@ -64,7 +64,8 @@ class AudioSpectrumView : public View {
128,
0,
false,
Theme::getInstance()->bg_darkest->foreground};
Theme::getInstance()->bg_darkest->foreground,
true};
};
class FrequencyScale : public Widget {
+2 -1
View File
@@ -67,7 +67,8 @@ class TimeScopeView : public View {
128,
0,
false,
Theme::getInstance()->bg_darkest->foreground};
Theme::getInstance()->bg_darkest->foreground,
true};
};
class TVView : public Widget {
+44 -18
View File
@@ -54,7 +54,6 @@
#include "ui_recon.hpp"
#include "ui_scanner.hpp"
#include "ui_sd_over_usb.hpp"
#include "ui_sd_wipe.hpp"
#include "ui_search.hpp"
#include "ui_settings.hpp"
#include "ui_siggen.hpp"
@@ -63,10 +62,8 @@
// #include "ui_test.hpp"
#include "ui_text_editor.hpp"
#include "ui_touchtunes.hpp"
#include "ui_view_wav.hpp"
#include "ui_weatherstation.hpp"
#include "ui_subghzd.hpp"
#include "ui_whipcalc.hpp"
#include "ui_battinfo.hpp"
#include "ui_external_items_menu_loader.hpp"
@@ -125,10 +122,11 @@ const NavigationView::AppList NavigationView::appList = {
{nullptr, "Transmit", HOME, Color::cyan(), &bitmap_icon_transmit, new ViewFactory<TransmittersMenuView>()},
{"capture", "Capture", HOME, Color::red(), &bitmap_icon_capture, new ViewFactory<CaptureAppView>()},
{"replay", "Replay", HOME, Color::green(), &bitmap_icon_replay, new ViewFactory<PlaylistView>()},
{"scanner", "Scanner", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
{"recon", "Recon", HOME, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
{nullptr, "Games", HOME, Color::purple(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
/* RX ********************************************************************/
@@ -142,7 +140,7 @@ const NavigationView::AppList NavigationView::appList = {
{"level", "Level", RX, Color::green(), &bitmap_icon_options_radio, new ViewFactory<LevelView>()},
{"pocsag", "POCSAG", RX, Color::green(), &bitmap_icon_pocsag, new ViewFactory<POCSAGAppView>()},
{"radiosonde", "Radiosnde", RX, Color::green(), &bitmap_icon_sonde, new ViewFactory<SondeView>()},
{"recon", "Recon", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ReconView>()},
{"scanner", "Scanner", RX, Color::green(), &bitmap_icon_scanner, new ViewFactory<ScannerView>()},
{"search", "Search", RX, Color::yellow(), &bitmap_icon_search, new ViewFactory<SearchView>()},
{"subghzd", "SubGhzD", RX, Color::yellow(), &bitmap_icon_remote, new ViewFactory<SubGhzDView>()},
{"weather", "Weather", RX, Color::green(), &bitmap_icon_thermometer, new ViewFactory<WeatherView>()},
@@ -153,7 +151,6 @@ const NavigationView::AppList NavigationView::appList = {
//{"sstv", "SSTV", RX, Color::dark_grey(), &bitmap_icon_sstv, new ViewFactory<NotImplementedView>()},
//{"tetra", "TETRA", RX, Color::dark_grey(), &bitmap_icon_tetra, new ViewFactory<NotImplementedView>()},
/* TX ********************************************************************/
//{"adsbtx", "ADS-B TX", TX, ui::Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBTxView>()},
{"aprstx", "APRS TX", TX, ui::Color::green(), &bitmap_icon_aprs, new ViewFactory<APRSTXView>()},
{"bht", "BHT Xy/EP", TX, ui::Color::green(), &bitmap_icon_bht, new ViewFactory<BHTView>()},
{"bletx", "BLE Tx", TX, ui::Color::green(), &bitmap_icon_btle, new ViewFactory<BLETxView>()},
@@ -162,20 +159,16 @@ const NavigationView::AppList NavigationView::appList = {
{"rdstx", "RDS", TX, ui::Color::green(), &bitmap_icon_rds, new ViewFactory<RDSView>()},
{"soundbrd", "Soundbrd", TX, ui::Color::green(), &bitmap_icon_soundboard, new ViewFactory<SoundBoardView>()},
{"touchtune", "TouchTune", TX, ui::Color::green(), &bitmap_icon_touchtunes, new ViewFactory<TouchTunesView>()},
{"signalgen", "Signal Gen", TX, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
/* UTILITIES *************************************************************/
{"antennalength", "Antenna Length", UTILITIES, Color::green(), &bitmap_icon_tools_antenna, new ViewFactory<WhipCalcView>()},
{"filemanager", "File Manager", UTILITIES, Color::green(), &bitmap_icon_dir, new ViewFactory<FileManagerView>()},
{"freqman", "Freq. Manager", UTILITIES, Color::green(), &bitmap_icon_freqman, new ViewFactory<FrequencyManagerView>()},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
{"signalgen", "Signal Gen", UTILITIES, Color::green(), &bitmap_icon_cwgen, new ViewFactory<SigGenView>()},
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
{"wavview", "Wav View", UTILITIES, Color::yellow(), &bitmap_icon_soundboard, new ViewFactory<ViewWavView>()},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
{nullptr, "Wipe SD card", UTILITIES, Color::red(), &bitmap_icon_tools_wipesd, new ViewFactory<WipeSDView>()},
};
const NavigationView::AppMap NavigationView::appMap = generate_app_map(NavigationView::appList);
@@ -359,12 +352,29 @@ void SystemStatusView::on_battery_data(const BatteryStateMessage* msg) {
batt_was_inited = true;
refresh();
}
// Check if charging state changed to charging
static bool was_charging = false;
if (msg->on_charger && !was_charging) {
// Only show charging modal when transitioning to charging state
nav_.display_modal(
"CHARGING",
"Screen on while charging?",
YESNO,
[this](bool keep_screen_on) {
if (!keep_screen_on) {
EventDispatcher::set_display_sleep(true);
}
});
}
was_charging = msg->on_charger;
if (!pmem::ui_hide_numeric_battery()) {
battery_text.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
}
if (!pmem::ui_hide_battery_icon()) {
battery_icon.set_battery(msg->valid_mask, msg->percent, msg->on_charger);
};
}
}
void SystemStatusView::refresh() {
@@ -784,9 +794,9 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
[&nav]() {
nav.display_modal(
"Notice",
"External app directory empty;\n"
"see Mayhem wiki and copy apps\n"
"to " + apps_dir.string() + " folder of SD card.");
"Can't read external apps\n"
"Check SD card\n"
"Update SD card content\n");
}},
error_tile_pos);
} else {
@@ -812,7 +822,7 @@ void add_external_items(NavigationView& nav, app_location_t location, BtnGridVie
bool verify_sdcard_format() {
FATFS* fs = &sd_card::fs;
return (fs->fs_type == FS_FAT32 || fs->fs_type == FS_EXFAT) || !(sd_card::status() == sd_card::Status::Mounted);
/* ^ to satisfy those users that not use an sd*/
/* ^ to satisfy those users that not use an sd*/
}
/* ReceiversMenuView *****************************************************/
@@ -859,6 +869,22 @@ void UtilitiesMenuView::on_populate() {
add_external_items(nav_, app_location_t::UTILITIES, *this, return_icon ? 1 : 0);
}
/* GamesMenuView ********************************************************/
GamesMenuView::GamesMenuView(NavigationView& nav)
: nav_(nav) {
set_max_rows(2);
}
void GamesMenuView::on_populate() {
bool return_icon = pmem::show_gui_return_icon();
if (return_icon) {
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }});
}
add_apps(nav_, *this, GAMES);
add_external_items(nav_, app_location_t::GAMES, *this, return_icon ? 1 : 0);
}
/* SystemMenuView ********************************************************/
void SystemMenuView::hackrf_mode(NavigationView& nav) {
@@ -882,7 +908,7 @@ SystemMenuView::SystemMenuView(NavigationView& nav)
void SystemMenuView::on_populate() {
if (!verify_sdcard_format()) {
add_item({"SDCard Error", Theme::getInstance()->error_dark->foreground, nullptr, [this]() {
nav_.display_modal("Error", "SD Card is not exFAT/FAT32,\nformat to exFAT or FAT32 on PC");
nav_.display_modal("Error", "SD Card is not exFAT/FAT32");
}});
}
add_apps(nav_, *this, HOME);
+10
View File
@@ -389,6 +389,16 @@ class UtilitiesMenuView : public BtnGridView {
void on_populate() override;
};
class GamesMenuView : public BtnGridView {
public:
GamesMenuView(NavigationView& nav);
std::string title() const override { return "Games"; };
private:
NavigationView& nav_;
void on_populate() override;
};
class SystemMenuView : public BtnGridView {
public:
SystemMenuView(NavigationView& nav);
+1 -1
View File
@@ -72,7 +72,7 @@ void AudioOutput::on_block(const buffer_f32_t& audio) {
if (do_processing) {
const auto audio_present_now = squelch.execute(audio);
hpf.execute_in_place(audio);
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef"
deemph.execute_in_place(audio);
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
+29 -6
View File
@@ -24,6 +24,8 @@
#include "complex.hpp"
#include "fxpt_atan2.hpp"
#include "utility_m4.hpp"
#include "dsp_hilbert.hpp"
#include "dsp_modulate.hpp"
#include <hal.h>
@@ -63,12 +65,7 @@ buffer_f32_t SSB::execute(
return {dst.p, src.count, src.sampling_rate};
}
/*
static inline float angle_approx_4deg0(const complex32_t t) {
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
return 16384.0f * x;
}
*/
static inline float angle_approx_0deg27(const complex32_t t) {
if (t.real()) {
const auto x = static_cast<float>(t.imag()) / static_cast<float>(t.real());
@@ -82,6 +79,32 @@ static inline float angle_precise(const complex32_t t) {
return atan2f(t.imag(), t.real());
}
buffer_f32_t SSB_FM::execute( // Added to handle AMFM (WFAX, HF weather map )
const buffer_c16_t& src, // input arg , pointer Complex c16 i,q buffer.
const buffer_f32_t& dst) { // input arg , pointer f32 buffer audio demodulated
complex16_t* src_p = src.p; // removed const ; init src_p pointer with the mem address pointed by src.p.
const auto src_end = &src.p[src.count];
auto dst_p = dst.p;
float mag_sq_lpf_norm;
while (src_p < src_end) {
// FM APT audio tone demod: real part (USB-differentiator) and AM tone demodulation + lpf (to remove the subcarrier.)
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm; // already normalized/32.768f and clipped to +1.0f for the wav file.
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
real_to_complex.execute((src_p++)->real(), mag_sq_lpf_norm);
*(dst_p++) = mag_sq_lpf_norm;
}
return {dst.p, src.count, src.sampling_rate};
}
buffer_f32_t FM::execute(
const buffer_c16_t& src,
const buffer_f32_t& dst) {
+12
View File
@@ -23,6 +23,7 @@
#define __DSP_DEMODULATE_H__
#include "dsp_types.hpp"
#include "dsp_hilbert.hpp"
namespace dsp {
namespace demodulate {
@@ -47,6 +48,17 @@ class SSB {
static constexpr float k = 1.0f / 32768.0f;
};
class SSB_FM { // Added to handle AMFM for WFAX
public:
buffer_f32_t execute(
const buffer_c16_t& src,
const buffer_f32_t& dst);
private:
static constexpr float k = 1.0f / 32768.0f;
dsp::Real_to_Complex real_to_complex{}; // It is a member variable of SSB_FM.
};
class FM {
public:
buffer_f32_t execute(
+85
View File
@@ -21,6 +21,7 @@
#include "dsp_hilbert.hpp"
#include "dsp_sos_config.hpp"
#include "utility_m4.hpp"
namespace dsp {
@@ -83,4 +84,88 @@ void HilbertTransform::execute(float in, float& out_i, float& out_q) {
n = (n + 1) % 4;
}
Real_to_Complex::Real_to_Complex() {
// No need to call a separate configuration method like "Real_to_Complex()" externally before using the execute() method
// This is the constructor for the Real_to_Complex class.
// It initializes the member variables and calls the configure function for the sos_input, sos_i, and sos_q filters.
// to ensure the object is ready to use right after instantiation.
n = 0;
sos_input.configure(full_band_lpf_config);
sos_i.configure(full_band_lpf_config);
sos_q.configure(full_band_lpf_config);
sos_mag_sq.configure(quarter_band_lpf_config); // for APT LPF subcarrier filter. (1/4 Nyquist fs/2 = 1/4 * 12Khz/2 = 1.5khz)
}
void Real_to_Complex::execute(float in, float& out_mag_sq_lpf) {
// Full_band LPF means a LP filter with f_cut_off = fs/2; Full band = Full max band = 1/2 * fs_max = 1.0 x f_Nyquist = 1 * fs/2 = fs/2
float a = 0, b = 0;
float out_i = 0, out_q = 0, out_mag_sq = 0;
// int32_t packed;
float in_filtered = sos_input.execute(in) * 1.0f; // Anti-aliasing full band LPF, fc = fs/2= 6k, audio filter front-end.
switch (n) {
case 0:
a = in_filtered;
b = 0;
break;
case 1:
a = 0;
b = -in_filtered;
break;
case 2:
a = -in_filtered;
b = 0;
break;
case 3:
a = 0;
b = in_filtered;
break;
}
float i = sos_i.execute(a) * 1.0f; // better keep <1.0f to minimize recorded APT(t) black level artifacts.-
float q = sos_q.execute(b) * 1.0f;
switch (n) { // shifting down -fs4 (fs = 12khz , fs/4 = 3khz)
case 0:
out_i = i;
out_q = q;
break;
case 1:
out_i = -q;
out_q = i;
break;
case 2:
out_i = -i;
out_q = -q;
break;
case 3:
out_i = q;
out_q = -i;
break;
}
n = (n + 1) % 4;
/* res = __smuad(val1,val2); p1 = val1[15:0] × val2[15:0]
p2 = val1[31:16] × val2[31:16]
res[31:0] = p1 + p2
return res; */
// Not strict Magnitude complex calculation, it is a cross multiplication (lower 16 bit real x lower 16 imag) + 0 (higher 16 bits comp),
// but better visual results comparing real magnitude calculation, (better map diagonal lines reproduction, and less artifacts in APT signal(t)
out_mag_sq = __SMUAD(out_i, out_q); // "cross-magnitude" of the complex (out_i + j out_q)
out_mag_sq_lpf = sos_mag_sq.execute((out_mag_sq)) * 2.0f; // LPF quater band = 1.5khz APT signal
out_mag_sq_lpf /= 32768.0f; // normalize ;
// Compress clipping positive APT signal [-1.5 ..1.5] input , converted to [-1.0 ...1.0] with "S" compressor gain shape.
if (out_mag_sq_lpf > 1.0f) {
out_mag_sq_lpf = 1.0f; // clipped signal at +1.0f, APT signal is positive, no need to clip -1.0
} else {
out_mag_sq_lpf = out_mag_sq_lpf * (1.5f - ((out_mag_sq_lpf * out_mag_sq_lpf) / 2.0f));
}
}
} /* namespace dsp */
+13
View File
@@ -39,6 +39,19 @@ class HilbertTransform {
SOSFilter<5> sos_q = {};
};
class Real_to_Complex {
public:
Real_to_Complex(); // Additional initialization
void execute(float in, float& out_mag_sq_lpf);
private:
uint8_t n = 0;
SOSFilter<5> sos_input = {};
SOSFilter<5> sos_i = {};
SOSFilter<5> sos_q = {};
SOSFilter<5> sos_mag_sq = {};
};
} /* namespace dsp */
#endif /*__DSP_HILBERT_H__*/
+23 -7
View File
@@ -27,6 +27,7 @@
#include "event_m4.hpp"
#include <array>
#include "dsp_hilbert.hpp"
void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
if (!configured) {
@@ -44,16 +45,30 @@ void NarrowbandAMAudio::execute(const buffer_c8_t& buffer) {
// TODO: Feed channel_stats post-decimation data?
feed_channel_stats(channel_out);
auto audio = demodulate(channel_out);
auto audio = demodulate(channel_out); // now 3 AM demodulation types : demod_am, demod_ssb, demod_ssb_fm (for Wefax)
audio_compressor.execute_in_place(audio);
audio_output.write(audio);
}
buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) {
if (modulation_ssb) {
return demod_ssb.execute(channel, audio_buffer);
} else {
return demod_am.execute(channel, audio_buffer);
switch (modulation_ssb) { // enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
case (int)(AMConfigureMessage::Modulation::DSB):
return demod_am.execute(channel, audio_buffer);
break;
case (int)(AMConfigureMessage::Modulation::SSB):
return demod_ssb.execute(channel, audio_buffer);
break;
case (int)(AMConfigureMessage::Modulation::SSB_FM): // Added to handle Weather Fax mode.
// chDbgPanic("case SSB_FM demodulation"); // Debug.
return demod_ssb_fm.execute(channel, audio_buffer); // Calling a derivative of demod_ssb (USB) , but with different FIR taps + FM audio tones demod.
break;
// return demod am as a default
default:
return demod_am.execute(channel, audio_buffer);
break;
}
}
@@ -98,8 +113,9 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
channel_filter_high_f = message.channel_filter.high_frequency_normalized * channel_filter_input_fs;
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
channel_spectrum.set_decimation_factor(1.0f);
modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB);
audio_output.configure(message.audio_hpf_config);
// modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); // originally we had just 2 AM types of demod. (DSB , SSB)
modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
configured = true;
}
+3 -1
View File
@@ -63,9 +63,11 @@ class NarrowbandAMAudio : public BasebandProcessor {
int32_t channel_filter_transition = 0;
bool configured{false};
bool modulation_ssb = false;
// bool modulation_ssb = false; // Origianlly we only had 2 AM demod types {DSB = 0, SSB = 1} , and we could handle it with bool var , 1 bit.
int8_t modulation_ssb = 0; // Now we have 3 AM demod types we will send now index integer {DSB = 0, SSB = 1, SSB_FM = 2}
dsp::demodulate::AM demod_am{};
dsp::demodulate::SSB demod_ssb{};
dsp::demodulate::SSB_FM demod_ssb_fm{}; // added for Wfax mode.
FeedForwardCompressor audio_compressor{};
AudioOutput audio_output{};
+72 -48
View File
@@ -38,28 +38,72 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
} else
sample_count--;
if (tone_shape == 0) {
if (modulation == 0) {
// CW
re = 127; // max. signed 8 bits value . (-128 ...+127), max. amplitude , static phasor at 0º
im = 0;
} else {
if (tone_shape == 1) {
} else if (modulation == 2) {
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
im = 0;
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
} else if (modulation == 3) {
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment.
switch (((tone_phase & 0xFF000000) >> 24)) {
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
/* "00" */
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
break;
case 64 ... 127:
/* "01" */
re = (sine_table_i8[96]); // symbol-phasor 135º
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
break;
case 128 ... 191:
/* "10" */
re = (sine_table_i8[159]); // symbol-phasor 225º
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
break;
case 192 ... 255:
/* "11" */
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
break;
default:
break;
}
tone_phase += tone_delta; // In BPSK-QSPK we are using to calculate each 1/4 of the periode.
} else if (modulation == 7) {
// Pulsed CW, 25% duty cycle.
if (tone_phase < 1073741824) // 1073741824 = 2^32*(25/100)
re = 127;
else
re = 0;
im = 0;
tone_phase += tone_delta; // In Pulsed CW we are using to calculate each periode.
} else { // Other modulations: FM, DSB, AM
if (tone_shape == 0) {
// Sine
sample = (sine_table_i8[(tone_phase & 0xFF000000) >> 24]);
} else if (tone_shape == 2) {
} else if (tone_shape == 1) {
// Triangle
int8_t a = (tone_phase & 0xFF000000) >> 24;
sample = (a & 0x80) ? ((a << 1) ^ 0xFF) - 0x80 : (a << 1) + 0x80;
} else if (tone_shape == 3) {
} else if (tone_shape == 2) {
// Saw up
sample = ((tone_phase & 0xFF000000) >> 24);
} else if (tone_shape == 4) {
} else if (tone_shape == 3) {
// Saw down
sample = ((tone_phase & 0xFF000000) >> 24) ^ 0xFF;
} else if (tone_shape == 5) {
} else if (tone_shape == 4) {
// Square
sample = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128;
} else if (tone_shape == 6) {
} else if (tone_shape == 5) {
// Noise generator, pseudo random noise generator, 16 bits linear-feedback shift register (LFSR) algorithm, variant Fibonacci.
// https://en.wikipedia.org/wiki/Linear-feedback_shift_register
// 16 bits LFSR .taps: 16, 15, 13, 4 ;feedback polynomial: x^16 + x^15 + x^13 + x^4 + 1
@@ -79,49 +123,13 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
if (counter == 15) {
counter = 0;
}
} else if (tone_shape == 7) {
// Digital BPSK consecutive 0,1,0,...continuous cycle, 1 bit/symbol, at rate of 2 symbols / Freq Tone Periode... without any Pulse shape at the moment .
re = (((tone_phase & 0xFF000000) >> 24) & 0x80) ? 127 : -128; // Sending 2 bits by Periode T of the GUI tone, alternative static phasor to 0, -180º , 0º
im = 0;
} else if (tone_shape == 8) {
// Digital QPSK consecutive 00, 01, 10, 11,00, ...continuous cycle ,2 bits/symbol, at rate of 4 symbols / Freq Tone Periode. not random., without any Pulse shape at the moment .
switch (((tone_phase & 0xFF000000) >> 24)) {
case 0 ... 63: // equivalent to 1/4 of total 360º degrees.
/* "00" */
re = (sine_table_i8[32]); // we are sending symbol-phasor 45º during 1/4 of the total periode
im = (sine_table_i8[32 + 64]); // 32 index = rounded (45º/360º * 255 total sin table steps) = 31,875
break;
case 64 ... 127:
/* "01" */
re = (sine_table_i8[96]); // symbol-phasor 135º
im = (sine_table_i8[96 + 64]); // 96 index = 32 + 256/4
break;
break;
case 128 ... 191:
/* "10" */
re = (sine_table_i8[159]); // symbol-phasor 225º
im = (sine_table_i8[159 + 64]); // 159 rounded index = 96 + 256/4 = 159.3
break;
case 192 ... 255:
/* "11" */
re = (sine_table_i8[223]); // symbol-phasor 315º ; 223 rounded index = (315/360) * 255 =223.125
im = (sine_table_i8[((223 + 64) & 0xFF)]); // Max index 255, circular periodic conversion.
break;
default:
break;
}
}
if (tone_shape != 6) { //(all except Pseudo Random White Noise). We are in (1):periodic signals or (2):BPSK/QPSK , in both cases ,we need Tone updated acum sum phases to modulate in FM / or control phasor phase (BPSK & QPSK.)
tone_phase += tone_delta; // In periodic signals(Sine/triangle/square) we are using to FM mod. in BPSK-QSPK we are using to calculate each 1/4 of the periode.
if (tone_shape != 5) { // All periodic except Pseudo Random White Noise.
tone_phase += tone_delta; // In periodic signals we are using phase to generate the tone to be modulated.
}
if (tone_shape < 7) { // All Option shape signals except BPSK(7) & QPSK(8) we are modulating in FM. (Those two has phase shift modulation XPSK , not FM )
if (modulation == 1) {
// Do FM modulation
delta = sample * fm_delta;
@@ -130,6 +138,21 @@ void SigGenProcessor::execute(const buffer_c8_t& buffer) {
re = (sine_table_i8[(sphase & 0xFF000000) >> 24]); // sin LUT is not dealing with decimals , output range [-128 ,...127]
im = (sine_table_i8[(phase & 0xFF000000) >> 24]);
} else if (modulation == 4) {
// Do Double Side Band modulation
re = sample;
im = 0;
} else if (modulation == 5) {
// Do AM modulation (100% mod index)
re = 64 + (sample >> 1); // 64 = 127 - (127 >> 1): carrier level without modulating signal
im = 0;
} else if (modulation == 6) {
// Do AM modulation (50% mod index)
re = 96 + (sample >> 2); // 96 = 127 - (127 >> 2): carrier level without modulating signal
im = 0;
}
}
@@ -154,7 +177,8 @@ void SigGenProcessor::on_message(const Message* const msg) {
auto_off = false;
fm_delta = message.bw * (0xFFFFFFULL / 1536000);
tone_shape = message.shape;
tone_shape = message.shape & 0xF;
modulation = (message.shape & 0xF0) >> 4;
// lfsr = seed_value ; // Finally not used , init lfsr 8 bits.
lfsr_16 = seed_value_16; // init lfsr 16 bits.
+1 -1
View File
@@ -36,7 +36,7 @@ class SigGenProcessor : public BasebandProcessor {
bool configured{false};
uint32_t tone_delta{0}, fm_delta{}, tone_phase{0};
uint8_t tone_shape{};
uint8_t tone_shape{}, modulation{};
uint32_t sample_count{0};
bool auto_off{};
int32_t phase{0}, sphase{0}, delta{0}; // they may have sign in the pseudo random sample generation.
@@ -1060,6 +1060,7 @@ DWORD get_fat ( /* 0xFFFFFFFF:Disk error, 1:Internal error, 2..0x7FFFFFFF:Cluste
}
}
/* go to default */
__attribute__ ((fallthrough));
#endif
default:
val = 1; /* Internal error */
+86 -10
View File
@@ -553,14 +553,14 @@ constexpr fir_taps_real<32> taps_6k0_decim_2{
}},
};
// IFIR prototype filter fs=48000 ; pass=4500 (cutt off -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000
// IFIR prototype filter fs=48000 ; pass=4500 (cutoff -3dBs) , stop=8000 (<-60dBs), decim=4, fout=12000
// For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivated from taps_6k0_decim_2 )
// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cut_off = 5150. sample_rate = 48000 # Hz,
// Created with h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple.
// Pre-decimate LPF FIR filter design Created with SciPy Python with the "window method", num_taps = 32, cutoff = 5150. sample_rate = 48000 # Hz,
// Created with h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving good STOP band plot < -60 dB's with some ripple.
// post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15) and similar H(f)gain
constexpr fir_taps_real<32> taps_9k0_decim_2{
.low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutt off freq -3dB (real achieved data ,in the plot and measurements)
.high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutt off freq -3dB (idem)
.low_frequency_normalized = -4500.0f / 48000.0f, // Negative -cutoff freq -3dB (real achieved data ,in the plot and measurements)
.high_frequency_normalized = 4500.0f / 48000.0f, // Positive +cutoff freq -3dB (idem)
.transition_normalized = 3500.0f / 48000.0f, // 3500 Hz = (8000 Hz - 4500 Hz) (both from plot H(f) curve plot)
.taps = {{-53, -30, 47, 198, 355, 372, 89, -535,
-1307, -1771, -1353, 370, 3384, 7109, 10535, 12591,
@@ -644,14 +644,14 @@ constexpr fir_taps_complex<64> taps_6k0_dsb_channel{
}},
};
// Channel filter: fs=12000, pass=4500 (cutt off -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve)
// Channel filter: fs=12000, pass=4500 (cutoff -3dBs), stop=4940 (<-60dBs), decim=1, fout=12000 (*1) real frec pass / stop , based on plotted H(f) curve)
// For Europe AM commercial broadcasting stations in LF/MF/HF, Emissions Designator 9K00A3E Bandwidth: 9.00 kHz (derivative from taps_6k0_dsb_channel)
// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cut_off = 4575. sample_rate = 12000 # Hz,
// Created with : h = signal.firwin(num_taps, cut_off, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs.
// FIR filter design created with SciPy Python using "window method"; selected design parameters: num_taps = 64, cutoff = 4575. sample_rate = 12000 # Hz,
// Created with : h = signal.firwin(num_taps, cutoff, nyq=sample_rate/2, window=('chebwin',50)) , achieving real plot curve (*1) with peak stop band ripple -60dBs.
// post-scaled h taps to avoid decimals , targeting <= similar int values as previous taps_6k0_dsb_channel peak < 32.767 (2 exp 15), (29625) and similar H(f)gain
constexpr fir_taps_complex<64> taps_9k0_dsb_channel{
.low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutt off freq -3dB (in the H(f) curve plot)
.high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutt off freq -3dB (in the H(f) curve plot)
.low_frequency_normalized = -4500.0f / 12000.0f, // Negative -cutoff freq -3dB (in the H(f) curve plot)
.high_frequency_normalized = 4500.0f / 12000.0f, // Positive +cutoff freq -3dB (in the H(f) curve plot)
.transition_normalized = 440.0f / 12000.0f, // 440Hz = (4940 Hz -4500 Hz) cut-3dB's (both data comes from H(f) curve plot and confirmed by measurements )
.taps = {{
{2, 0},
@@ -945,6 +945,82 @@ constexpr fir_taps_complex<64> taps_0k7_usb_channel{
}},
};
// USB AM+FM for Wefax (Weather fax RX) , based USB AM with truncated Differentiator band limmited cuttoff 2.400Hz for Audio Tones FM dem. ///////////////////
// IFIR prototype filter: fs=12000, pass=2600, stop=3200, decim=1, fout=12000 // stop band minimum att < -48 dB's (+3300 Hz min atten peak) , rest <50 to -60dB's
constexpr fir_taps_complex<64> taps_2k6_usb_wefax_channel{
.low_frequency_normalized = 0,
.high_frequency_normalized = 2600.0f / 12000.0f,
.transition_normalized = 600.0f / 12000.0f,
.taps = {{{-14 + 2},
{-11 - 5},
{-2 - 8},
{6 - 5},
{13 + 1},
{15 + 14},
{0 + 26},
{-22 + 13},
{-13 - 11},
{7 - 1},
{-20 + 17},
{-47 - 37},
{33 - 89},
{122 + 8},
{19 + 131},
{-124 + 26},
{1 - 123},
{158 + 52},
{-94 + 245},
{-363 - 91},
{36 - 468},
{524 - 37},
{67 + 531},
{-552 + 5},
{136 - 686},
{1013 + 258},
{-204 + 1527},
{-2104 + 168},
{-900 - 2529},
{2577 - 1881},
{2868 + 2122},
{-1209 + 3570},
{-3768 - 52},
{-1043 - 3412},
{2634 - 1801},
{2083 + 1693},
{-861 + 1927},
{-1507 - 318},
{95 - 1041},
{692 + 100},
{-189 + 519},
{-478 - 241},
{210 - 481},
{454 + 122},
{-35 + 372},
{-262 + 7},
{4 - 166},
{116 + 40},
{-66 + 108},
{-117 - 62},
{33 - 117},
{95 - 2},
{19 + 57},
{-23 + 13},
{3 - 7},
{6 + 16},
{-20 + 16},
{-25 - 9},
{-8 - 19},
{4 - 12},
{7 - 4},
{7 + 4},
{2 + 12},
{-7 + 13}
}}
};
// WFM 200KF8E emission type //////////////////////////////////////////////
// IFIR image-reject filter: fs=3072000, pass=100000, stop=484000, decim=4, fout=768000
+6
View File
@@ -55,6 +55,12 @@ constexpr iir_biquad_config_t audio_12k_hpf_300hz_config{
{0.89485861f, -1.78971721f, 0.89485861f},
{1.00000000f, -1.77863178f, 0.80080265f}};
// scipy.signal.butter(2, 1500 / 6000.0, 'low', analog=False)
constexpr iir_biquad_config_t audio_12k_lpf_1500hz_config{
// Added to lpf the audio in wefax mode , before sending to SD card or spk.
{0.09763107f, 0.19526215f, 0.09763107f},
{1.00000000f, -0.94280904f, 0.33333333f}};
// scipy.signal.butter(2, 300 / 4000.0, 'highpass', analog=False)
constexpr iir_biquad_config_t audio_8k_hpf_300hz_config{
{0.84645925f, -1.69291851f, 0.84645925f},
+19 -1
View File
@@ -25,7 +25,7 @@
#include "dsp_iir.hpp"
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.5, btype = 'lowpass', output="sos")
// 3khz cutofff @fs:12Khz , used in Hilbert
constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
{0.02339042f, 0.0411599f, 0.02339042f, 1.0f, -0.95317621f, 0.33446485f},
{1.0f, 0.82196114f, 1.0f, 1.0f, -0.50327735f, 0.63611027f},
@@ -33,4 +33,22 @@ constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
{1.0f, 0.14394122f, 1.0f, 1.0f, -0.04368236f, 0.94798064f},
{1.0f, 0.08720754, 1.0f, 1.0f, 0.00220944f, 0.98743139f}};
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.99, btype = 'lowpass', output="sos")
// 6khz cutofff @fs:12Khz , used in AMFM demod for WFAX
constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = {
{0.88095275f, 1.76184993f, 0.88095275f, 1.0f, 1.89055677f, 0.89616378f},
{1.0f, 1.99958798f, 1.0f, 1.0f, 1.9781807f, 0.98002549f},
{1.0f, 1.99928911f, 1.0f, 1.0f, 1.99328036f, 0.99447816f},
{1.0f, 1.99914562f, 1.0f, 1.0f, 1.997254f, 0.99828526f},
{1.0f, 1.99909558f, 1.0f, 1.0f, 1.9986187f, 0.99960319f}};
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5 , rs = 60.0, Wn = 0.25, btype = 'lowpass', output="sos")
// 1.5khz cutofff @fs:12Khz, used in AMFM demod for WFAX
constexpr iir_biquad_df2_config_t quarter_band_lpf_config[5] = {
{0.00349312f, 0.00319397f, 0.00349312f, 1.0f, -1.53025211f, 0.6203438f},
{1.0f, -0.83483341f, 1.0f, 1.0f, -1.47619047f, 0.77120659f},
{1.0f, -1.23050154f, 1.0f, 1.0f, -1.43058949f, 0.9000896f},
{1.0f, -1.33837384f, 1.0f, 1.0f, -1.41007744f, 0.96349953f},
{1.0f, -1.36921549f, 1.0f, 1.0f, -1.40680439f, 0.9910884f}};
#endif /*__DSP_SOS_CONFIG_H__*/
+4 -3
View File
@@ -585,6 +585,7 @@ class AMConfigureMessage : public Message {
enum class Modulation : int32_t {
DSB = 0,
SSB = 1,
SSB_FM = 2, // Added new for RX Wefax mode, to demodulate APT signal ,FM modulated inside audio subcarrier tones, and then up broadcasted in SSB USB .
};
constexpr AMConfigureMessage(
@@ -593,14 +594,14 @@ class AMConfigureMessage : public Message {
const fir_taps_real<32> decim_2_filter,
const fir_taps_complex<64> channel_filter,
const Modulation modulation,
const iir_biquad_config_t audio_hpf_config)
const iir_biquad_config_t audio_hpf_lpf_config)
: Message{ID::AMConfigure},
decim_0_filter(decim_0_filter),
decim_1_filter(decim_1_filter),
decim_2_filter(decim_2_filter),
channel_filter(channel_filter),
modulation{modulation},
audio_hpf_config(audio_hpf_config) {
audio_hpf_lpf_config(audio_hpf_lpf_config) {
}
const fir_taps_real<24> decim_0_filter;
@@ -608,7 +609,7 @@ class AMConfigureMessage : public Message {
const fir_taps_real<32> decim_2_filter;
const fir_taps_complex<64> channel_filter;
const Modulation modulation;
const iir_biquad_config_t audio_hpf_config;
const iir_biquad_config_t audio_hpf_lpf_config;
};
// TODO: Put this somewhere else, or at least the implementation part.
@@ -1329,6 +1329,9 @@ bool debug_dump() {
case ReceiverModel::Mode::Capture:
pmem_dump_file.write_line("modulation: Mode::Capture");
break;
case ReceiverModel::Mode::AMAudioFMApt:
pmem_dump_file.write_line("modulation: Mode::AMAudioFMApt");
break;
default:
pmem_dump_file.write_line("modulation: !!unknown mode!!");
break;
+11 -1
View File
@@ -35,6 +35,13 @@ struct JammerChannel {
uint32_t duration;
};
struct HopperChannel {
bool enabled;
uint64_t center;
uint32_t width;
uint32_t duration;
};
struct ToneDef {
uint32_t delta;
uint32_t duration;
@@ -61,7 +68,10 @@ struct SharedMemory {
union {
ToneData tones_data;
JammerChannel jammer_channels[24];
struct {
JammerChannel jammer_channels[24];
HopperChannel hopper_channels[24];
} dummy_seperate;
uint8_t data[512];
} bb_data{{{{0, 0}}, 0, {0}}};
+2 -1
View File
@@ -72,7 +72,8 @@ enum app_location_t : uint32_t {
TX,
DEBUG,
HOME,
SETTINGS
SETTINGS,
GAMES
};
struct standalone_application_information_t {
+118 -4
View File
@@ -2606,15 +2606,19 @@ Waveform::Waveform(
uint32_t length,
uint32_t offset,
bool digital,
Color color)
Color color,
bool clickable)
: Widget{parent_rect},
data_{data},
length_{length},
offset_{offset},
digital_{digital},
color_{color} {
// set_focusable(false);
// previous_data.resize(length_, 0);
color_{color},
clickable_{clickable} {
if (clickable) {
set_focusable(true);
// previous_data.resize(length_, 0);
}
}
void Waveform::set_cursor(const uint32_t i, const int16_t position) {
@@ -2641,9 +2645,112 @@ void Waveform::set_length(const uint32_t new_length) {
}
}
bool Waveform::is_paused() const {
return paused_;
}
void Waveform::set_paused(bool paused) {
paused_ = paused;
if (!paused) {
if_ever_painted_pause = false;
}
set_dirty();
}
bool Waveform::is_clickable() const {
return clickable_;
}
void Waveform::getAccessibilityText(std::string& result) {
// no idea what this is in use in any places, but others have it
result = paused_ ? "paused waveform" : "waveform";
}
void Waveform::getWidgetName(std::string& result) {
result = "Waveform";
}
bool Waveform::on_key(const KeyEvent key) {
if (!clickable_) return false;
if (key == KeyEvent::Select) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool Waveform::on_keyboard(const KeyboardEvent key) {
// no idea what this is for, but others have it
if (!clickable_) return false;
if (key == 32 || key == 10) {
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
}
return false;
}
bool Waveform::on_touch(const TouchEvent event) {
if (!clickable_) return false;
switch (event.type) {
case TouchEvent::Type::Start:
focus();
return true;
case TouchEvent::Type::End:
set_paused(!paused_);
if (on_select) {
on_select(*this);
}
return true;
default:
return false;
}
}
void Waveform::paint(Painter& painter) {
// previously it's upside down , low level is up and high level is down, which doesn't make sense,
// if that was made for a reason, feel free to revert.
if (paused_) {
// TODO: this is bad: that it still enter this func and still consume resources.
// even do a if(paused_) return; comsume too, but not that much.
if (dirty() && !if_ever_painted_pause) {
// clear
painter.fill_rectangle_unrolled8(screen_rect(), Theme::getInstance()->bg_darkest->background);
// draw "WF HIDDEN" text
const auto r = screen_rect();
painter.draw_string(
{r.center().x() - 24, r.center().y() - 8},
style(),
"WF HIDDEN");
if_ever_painted_pause = true;
}
if (show_cursors) {
for (uint32_t n = 0; n < 2; n++) {
painter.draw_vline(
Point(std::min(screen_rect().size().width(), (int)cursors[n]), screen_rect().location().y()),
screen_rect().size().height(),
cursor_colors[n]);
}
}
return;
}
// not paused
size_t n;
Coord y, y_offset = screen_rect().location().y();
Coord prev_x = screen_rect().location().x(), prev_y;
@@ -2701,6 +2808,13 @@ void Waveform::paint(Painter& painter) {
cursor_colors[n]);
}
}
// focused highlight border
if (clickable_ && has_focus()) {
painter.draw_rectangle(
screen_rect(),
Theme::getInstance()->fg_light->foreground);
}
}
/* VuMeter **************************************************************/
+16 -1
View File
@@ -974,7 +974,9 @@ class SymField : public Widget {
class Waveform : public Widget {
public:
Waveform(Rect parent_rect, int16_t* data, uint32_t length, uint32_t offset, bool digital, Color color);
std::function<void(Waveform&)> on_select{};
Waveform(Rect parent_rect, int16_t* data, uint32_t length, uint32_t offset, bool digital, Color color, bool clickable = false);
Waveform(const Waveform&) = delete;
Waveform(Waveform&&) = delete;
@@ -985,7 +987,17 @@ class Waveform : public Widget {
void set_length(const uint32_t new_length);
void set_cursor(const uint32_t i, const int16_t position);
bool is_paused() const;
void set_paused(bool paused);
bool is_clickable() const;
void paint(Painter& painter) override;
bool on_key(const KeyEvent key) override;
bool on_touch(const TouchEvent event) override;
bool on_keyboard(const KeyboardEvent event) override;
void getAccessibilityText(std::string& result) override;
void getWidgetName(std::string& result) override;
private:
const Color cursor_colors[2] = {Theme::getInstance()->fg_cyan->foreground, Theme::getInstance()->fg_magenta->foreground};
@@ -997,6 +1009,9 @@ class Waveform : public Widget {
Color color_;
int16_t cursors[2]{};
bool show_cursors{false};
bool paused_{false};
bool clickable_{false};
bool if_ever_painted_pause{false}; // for prevent the "hidden" label keeps painting and being expensive
};
class VuMeter : public Widget {
+1 -1
View File
@@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.16)
project(standalone_apps)
+1 -1
View File
@@ -65,7 +65,7 @@ __attribute__((section(".standalone_application_information"), used)) standalone
0x00,
},
/*.icon_color = 16 bit: 5R 6G 5B*/ 0x0000FFE0,
/*.menu_location = */ app_location_t::UTILITIES,
/*.menu_location = */ app_location_t::GAMES,
/*.initialize_app = */ initialize,
/*.on_event = */ on_event,
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
for fname in $(ls ../graphics/); do
fname=$(echo "$fname" | awk -F'.' '{print $1}')
out=$(grep -r --exclude="bitmap.hpp" "$fname" ../* )
ret=$?
if [[ "$ret" -ne 0 ]]; then
echo "$fname not found"
fi
done
+275
View File
@@ -0,0 +1,275 @@
#
# copyleft Elliot Alderson from F society
# copyleft Darlene Alderson from F society
#
# 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.
#
import re
import tkinter as tk
from typing import Dict, List, Tuple
import argparse
from dataclasses import dataclass
"""
TODO: Multile class in one file seperation
TODO: Add more widget type
widget compatible guide:
1. add the preview color in the "widgets = {" table
(note that this color is only for easier to distinguish)
2. add the widget type and also regex in the "parsers" table
(note that your regex should apply all the possible constructor overload (re-impl))
"""
# pp hard coded vars
screen_width = 240
SCREEN_W = 240
screen_height = 320
CHARACTER_WIDTH = 8
LINE_HEIGHT = 16
topbar_offset = 16
# widgets actually drew after the top bar,
# for example if Y = 0 then Y on screen actually is 16
# scale factor
scale = 2
widgets = {
"Button": "lightgray",
"NewButton": "lightyellow",
"Text": "lightblue",
"Rectangle": "lightgreen",
"Image": "pink",
"ImageButton": "lightpink",
"NumberField": "peachpuff",
"ProgressBar": "lightcoral",
"Console": "wheat",
"Checkbox": "plum",
"Label": "lavender",
"TextField": "paleturquoise",
"OptionsField": "palegreen",
"VuMeter": "sandybrown",
"BigFrequency": "khaki"
}
@dataclass
class Widget:
name: str
widget_type: str
x: int
y: int
width: int
height: int
text: str = ""
class WidgetParser:
def __init__(self):
self.parsers: Dict[str, re.Pattern] = {
'Button': re.compile( # TODO: fix those that using language helper
r'Button\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
re.MULTILINE
),
'NewButton': re.compile(
r'NewButton\s+(\w+)\s*\{\s*(?:\{([^}]+)\}|{}),\s*(?:"([^"]*)"|\{\}),\s*(?:[^,}]+(?:,\s*[^}]+)*|\{\})\};',
re.MULTILINE
),
'Text': re.compile(
r'Text\s+(\w+)\s*\{\s*\{([^}]+)\}(?:\s*,\s*"([^"]*)")?\s*\};',
re.MULTILINE
),
'ProgressBar': re.compile(
r'ProgressBar\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
re.MULTILINE
),
'Console': re.compile(
r'Console\s+(\w+)\s*\{\s*\{([^}]+)\}\s*\};',
re.MULTILINE
),
'Label': re.compile(
r'Label\s+(\w+)\s*\{\s*\{([^}]+)\},\s*"([^"]+)"\s*\};',
re.MULTILINE
),
'VuMeter': re.compile(
r'VuMeter\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+,\s*(?:true|false)\s*\};',
re.MULTILINE
),
'BigFrequency': re.compile(
r'BigFrequency\s+(\w+)\s*\{\s*\{([^}]+)\},\s*\d+\s*\};',
re.MULTILINE
)
}
def parse_coordinates(self, coord_str: str) -> Tuple[int, int, int, int]:
"""Parse coordinate string like '2 * 8, 8 * 16, 8 * 8, 3 * 16'"""
if not coord_str or coord_str.isspace():
return (0, 0, 0, 0) # for empty constructor fallback
# split and evaluate each coordinate expression
coords = [eval(x.strip()) for x in coord_str.split(',')]
# ensure we have exactly 4 coordinates, pad with zeros if needed
while len(coords) < 4:
coords.append(0)
# only take the first 4 coordinates if there are more
return tuple(coords[:4])
def parse_file(self, filepath: str) -> List[Widget]:
with open(filepath, 'r') as f:
content = f.read()
widgets = []
for widget_type, pattern in self.parsers.items():
matches = pattern.finditer(content)
for match in matches:
name = match.group(1) # Widget name is always in group 1
coords = self.parse_coordinates(match.group(2) if match.group(2) else "")
# Only try to get text if the pattern has 3 or more groups
text = ""
try:
if match.lastindex >= 3:
text = match.group(3) if match.group(3) else ""
except IndexError:
pass
widgets.append(Widget(
name=name,
widget_type=widget_type,
x=coords[0],
y=coords[1],
width=coords[2],
height=coords[3],
text=text
))
return widgets
class WidgetPreview(tk.Tk):
def __init__(self, widgets: List[Widget]):
super().__init__()
self.title("Widget Preview")
self.canvas = tk.Canvas(self, width=screen_width * scale, height=screen_height * scale, bg='white')
self.canvas.pack(padx=10, pady=10)
self.all_text_elements = []
self.draw_widgets(widgets)
def draw_widgets(self, widgets: List[Widget]):
draw_top_bar(self)
for widget in widgets:
self.draw_widget(widget)
def draw_widget(self, widget: Widget):
print(f"Drawing widget: {widget.name} ({widget.widget_type})")
x1 = widget.x * scale
y1 = (widget.y + topbar_offset) * scale
x2 = x1 + (widget.width * scale)
y2 = y1 + (widget.height * scale)
print(f"Coordinates: ({x1}, {y1}), ({x2}, {y2})")
if widget.widget_type == "VuMeter":
segment_height = widget.height / 8 * scale
for i in range(8):
self.canvas.create_rectangle(
x1, y1 + (i * segment_height),
x2, y1 + ((i+1) * segment_height),
fill=widgets[widget.widget_type],
outline="gray"
)
elif widget.widget_type == "BigFrequency":
# Draw 7-segment style display
self.canvas.create_rectangle(
x1, y1, x2, y2,
fill=widgets[widget.widget_type],
outline="gray"
)
self.canvas.create_text(
(x1 + x2) / 2,
(y1 + y2) / 2,
text="433.92" # placeholder text
)
else:
# defualt rendering
rect_id = self.canvas.create_rectangle(
x1, y1, x2, y2,
fill=widgets[widget.widget_type]
)
type_text_id = self.canvas.create_text(
(x1 + x2) // 2,
(y1 + y2) // 2,
text=widget.widget_type
)
detail_text_id = self.canvas.create_text(
(x1 + x2) // 2,
(y1 + y2) // 2,
text=f"{widget.widget_type}|{widget.name}|{widget.text}",
state='hidden'
)
widget_texts = {
'type': type_text_id,
'detail': detail_text_id
}
self.all_text_elements.append(widget_texts)
# hover handlers
def on_enter(event):
for texts in self.all_text_elements:
self.canvas.itemconfig(texts['type'], state='hidden')
self.canvas.itemconfig(texts['detail'], state='hidden')
self.canvas.itemconfig(detail_text_id, state='normal')
self.canvas.tag_raise(detail_text_id)
def on_leave(event):
for texts in self.all_text_elements:
self.canvas.itemconfig(texts['type'], state='normal')
self.canvas.tag_raise(texts['type'])
self.canvas.itemconfig(texts['detail'], state='hidden')
for item_id in [rect_id, type_text_id, detail_text_id]:
self.canvas.tag_bind(item_id, '<Enter>', on_enter)
self.canvas.tag_bind(item_id, '<Leave>', on_leave)
def draw_top_bar(self):
self.canvas.create_rectangle(0, 0, screen_width * scale, topbar_offset * scale, fill='lightblue')
self.canvas.create_text(screen_width * scale // 2, topbar_offset * scale // 2, text='I\'m Top Bar, hover mouse on items to check details', fill='black')
def main():
parser = argparse.ArgumentParser(description='Preview UI widgets from hpp files')
parser.add_argument('file', help='Path to the hpp file')
args = parser.parse_args()
widget_parser = WidgetParser()
widgets = widget_parser.parse_file(args.file)
app = WidgetPreview(widgets)
app.mainloop()
if __name__ == "__main__":
main()
-11
View File
@@ -1,11 +0,0 @@
f=124700000,m=AM,bw=DSB 6k,d=BNE Approach NW
f=123500000,m=AM,bw=DSB 6k,d=BNE Approach 2
f=125600000,m=AM,bw=DSB 6k,d=BNE Approach 3
f=120500000,m=AM,bw=DSB 6k,d=BNE RWY 19L/01R
f=118000000,m=AM,bw=DSB 6k,d=BNE RWY 19R/01L
f=113200000,m=AM,bw=DSB 6k,d=BNE ATIS 1
f=125500000,m=AM,bw=DSB 6k,d=BNE ATIS 2
f=121700000,m=AM,bw=DSB 6k,d=BNE GRND 1
f=122250000,m=AM,bw=DSB 6k,d=BNE GRND 2
f=124050000,m=AM,bw=DSB 6k,d=BNE GRND 3
f=125700000,m=AM,bw=DSB 6k,d=OOL Approach E
-28
View File
@@ -1,28 +0,0 @@
f=132475000,m=AM,bw=DSB 6k,d=ATIS - Civil
f=130550000,m=AM,bw=DSB 6k,d=AbelagUnicom - Civil
f=118250000,m=AM,bw=DSB 6k,d=Approach - Civil
f=120100000,m=AM,bw=DSB 6k,d=Approach - Civil
f=122500000,m=AM,bw=DSB 6k,d=Approach Military Crossing
f=121950000,m=AM,bw=DSB 6k,d=Clearance Delivery - Civil
f=126625000,m=AM,bw=DSB 6k,d=Departure - Civil
f=127575000,m=AM,bw=DSB 6k,d=Final 25L - Civil
f=129725000,m=AM,bw=DSB 6k,d=Final 25R - Civil
f=118050000,m=AM,bw=DSB 6k,d=Ground - Civil
f=121700000,m=AM,bw=DSB 6k,d=Ground - Civil
f=121875000,m=AM,bw=DSB 6k,d=Ground - Civil
f=118600000,m=AM,bw=DSB 6k,d=Tower VHF - Civil
f=120775000,m=AM,bw=DSB 6k,d=Tower - Civil
f=127150000,m=AM,bw=DSB 6k,d=Tower Spare - Civil
f=240400000,m=AM,bw=DSB 6k,d=Air to Air - Military
f=241000000,m=AM,bw=DSB 6k,d=Air to Air - Military
f=289400000,m=AM,bw=DSB 6k,d=Military Approach
f=362300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
f=389300000,m=AM,bw=DSB 6k,d=Approach Military Crossing - Military
f=138250000,m=AM,bw=DSB 6k,d=Inflight Safety - Military
f=140575000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
f=367950000,m=AM,bw=DSB 6k,d=Melsbroek Ops - Military
f=257800000,m=AM,bw=DSB 6k,d=Tower - Military
f=388500000,m=AM,bw=DSB 6k,d=Tower - Military
f=388525000,m=AM,bw=DSB 6k,d=Tower - Military
f=121725000,m=AM,bw=DSB 6k,d=Wing Ops - Military
f=336500000,m=AM,bw=DSB 6k,d=Wing Ops UHF - Military
-56
View File
@@ -1,56 +0,0 @@
f=87800000,m=WFM,bw=200k,d=France Inter
f=87600000,m=WFM,bw=200k,d=France Inter
f=88200000,m=WFM,bw=200k,d=Generations
f=88600000,m=WFM,bw=200k,d=Radio Soleil
f=89000000,m=WFM,bw=200k,d=RFI
f=89400000,m=WFM,bw=200k,d=Radio Libertaire
f=89900000,m=WFM,bw=200k,d=TSF Jazz
f=90400000,m=WFM,bw=200k,d=Nostalgie
f=90900000,m=WFM,bw=200k,d=Chante France
f=91300000,m=WFM,bw=200k,d=Cherie FM
f=91700000,m=WFM,bw=200k,d=France Musique
f=92100000,m=WFM,bw=200k,d=Mouv'
f=92600000,m=WFM,bw=200k,d=Tropiques FM
f=93100000,m=WFM,bw=200k,d=Cause Commune
f=93100000,m=WFM,bw=200k,d=Aligre FM
f=93500000,m=WFM,bw=200k,d=France Culture
f=93900000,m=WFM,bw=200k,d=Vivre FM
f=93900000,m=WFM,bw=200k,d=Radio Campus
f=94300000,m=WFM,bw=200k,d=Radio Orient
f=94800000,m=WFM,bw=200k,d=Radio Shalom
f=94800000,m=WFM,bw=200k,d=Radio J
f=94800000,m=WFM,bw=200k,d=RCJ
f=95200000,m=WFM,bw=200k,d=Ici Et Maintenant
f=95200000,m=WFM,bw=200k,d=Neo
f=95600000,m=WFM,bw=200k,d=Radio Courtoisie
f=96000000,m=WFM,bw=200k,d=Skyrock
f=96400000,m=WFM,bw=200k,d=BFM Business
f=96900000,m=WFM,bw=200k,d=Voltage
f=97400000,m=WFM,bw=200k,d=Rire Et Chansons
f=97800000,m=WFM,bw=200k,d=Ado
f=98200000,m=WFM,bw=200k,d=Radio FG
f=98600000,m=WFM,bw=200k,d=Radio Alfa
f=99000000,m=WFM,bw=200k,d=Latina
f=99500000,m=WFM,bw=200k,d=AYP FM
f=99500000,m=WFM,bw=200k,d=France Maghreb 2
f=99900000,m=WFM,bw=200k,d=Sud Radio +
f=100300000,m=WFM,bw=200k,d=NRJ
f=100700000,m=WFM,bw=200k,d=Frequence Protestante
f=100700000,m=WFM,bw=200k,d=Radio Notre-Dame
f=101100000,m=WFM,bw=200k,d=Radio Classique
f=101500000,m=WFM,bw=200k,d=Radio Nova
f=101900000,m=WFM,bw=200k,d=Fun Radio
f=102300000,m=WFM,bw=200k,d=Oui FM
f=102700000,m=WFM,bw=200k,d=M Radio
f=103100000,m=WFM,bw=200k,d=RMC
f=103500000,m=WFM,bw=200k,d=Europe 2
f=103900000,m=WFM,bw=200k,d=RFM
f=104300000,m=WFM,bw=200k,d=RTL
f=104700000,m=WFM,bw=200k,d=Europe 1
f=105100000,m=WFM,bw=200k,d=FIP
f=105500000,m=WFM,bw=200k,d=France Info
f=105900000,m=WFM,bw=200k,d=RTL2
f=106300000,m=WFM,bw=200k,d=Frequence Paris Plurielle
f=106700000,m=WFM,bw=200k,d=Beur FM
f=107100000,m=WFM,bw=200k,d=France Bleu
f=107500000,m=WFM,bw=200k,d=Africa Radio
-4
View File
@@ -1,4 +0,0 @@
f=92500000,m=WFM,bw=200k,d=NRK Oslo
f=94800000,m=WFM,bw=200k,d=NRK Bergen
f=97200000,m=WFM,bw=200k,d=NRK Melhus
f=98600000,m=WFM,bw=200k,d=NRK Arendal
-58
View File
@@ -1,58 +0,0 @@
f=88200000,m=WFM,bw=200k,d=RMF Polkowice
f=88200000,m=WFM,bw=200k,d=RMF Kielce
f=89200000,m=WFM,bw=200k,d=RMF Bielsko-Biala
f=89300000,m=WFM,bw=200k,d=RMF Lublin
f=89300000,m=WFM,bw=200k,d=RMF Koszalin
f=90600000,m=WFM,bw=200k,d=RMF Warsaw
f=91000000,m=WFM,bw=200k,d=RMF Warsaw
f=91300000,m=WFM,bw=200k,d=RMF Łobez
f=91500000,m=WFM,bw=200k,d=RMF Ostroleka
f=91700000,m=WFM,bw=200k,d=RMF Wagrowiec
f=91900000,m=WFM,bw=200k,d=RMF Siedlce
f=92600000,m=WFM,bw=200k,d=RMF Elbląg
f=92900000,m=WFM,bw=200k,d=RMF Wroclaw
f=93000000,m=WFM,bw=200k,d=RMF Katowice
f=93300000,m=WFM,bw=200k,d=RMF Bydgoszcz
f=93500000,m=WFM,bw=200k,d=RMF Lodz
f=93600000,m=WFM,bw=200k,d=RMF Ryki
f=93800000,m=WFM,bw=200k,d=RMF Luban
f=94300000,m=WFM,bw=200k,d=RMF Płock
f=94600000,m=WFM,bw=200k,d=RMF Poznan
f=94800000,m=WFM,bw=200k,d=RMF Zagan
f=95100000,m=WFM,bw=200k,d=RMF Suwałki
f=95300000,m=WFM,bw=200k,d=RMF Olsztyn
f=95400000,m=WFM,bw=200k,d=RMF Tarnow
f=96000000,m=WFM,bw=200k,d=RMF Krakow
f=96100000,m=WFM,bw=200k,d=RMF Legnica
f=96100000,m=WFM,bw=200k,d=RMF Gorzow Wielkopolski
f=96400000,m=WFM,bw=200k,d=RMF Bialogard
f=96600000,m=WFM,bw=200k,d=RMF Walcz
f=97100000,m=WFM,bw=200k,d=RMF Włoszczowa
f=98000000,m=WFM,bw=200k,d=RMF Kalisz
f=98400000,m=WFM,bw=200k,d=RMF Gdansk
f=98900000,m=WFM,bw=200k,d=RMF Konin
f=99300000,m=WFM,bw=200k,d=RMF Opole
f=99500000,m=WFM,bw=200k,d=RMF Kluczbork
f=100100000,m=WFM,bw=200k,d=RMF Rzeszow
f=100200000,m=WFM,bw=200k,d=RMF Bialystok
f=100800000,m=WFM,bw=200k,d=RMF Jelenia Gora
f=100900000,m=WFM,bw=200k,d=RMF Slupsk
f=101100000,m=WFM,bw=200k,d=RMF Solina
f=101200000,m=WFM,bw=200k,d=RMF Swinoujscie
f=101600000,m=WFM,bw=200k,d=RMF Klodzko
f=101800000,m=WFM,bw=200k,d=RMF Zakopane
f=101800000,m=WFM,bw=200k,d=RMF Leżajsk
f=102000000,m=WFM,bw=200k,d=RMF Gizycko
f=102900000,m=WFM,bw=200k,d=RMF Walbrzych
f=103200000,m=WFM,bw=200k,d=RMF Szczawnica
f=103400000,m=WFM,bw=200k,d=RMF Przemyśl
f=103400000,m=WFM,bw=200k,d=RMF Lebork
f=104700000,m=WFM,bw=200k,d=RMF Rabka
f=104900000,m=WFM,bw=200k,d=RMF Koszalin
f=105900000,m=WFM,bw=200k,d=RMF Krynica
f=105900000,m=WFM,bw=200k,d=RMF Czestochowa
f=106400000,m=WFM,bw=200k,d=RMF Zielona Gora
f=106500000,m=WFM,bw=200k,d=RMF Elk
f=106700000,m=WFM,bw=200k,d=RMF Szczecin
f=107400000,m=WFM,bw=200k,d=RMF Iława
f=107700000,m=WFM,bw=200k,d=RMF Zamosc
-28
View File
@@ -1,28 +0,0 @@
f=88000000,m=WFM,bw=200k,d=Radio Impuls
f=88500000,m=WFM,bw=200k,d=Radio Trinitas
f=89000000,m=WFM,bw=200k,d=Radio ZU
f=89500000,m=WFM,bw=200k,d=Dance FM
f=90800000,m=WFM,bw=200k,d=Magic FM
f=91700000,m=WFM,bw=200k,d=Național FM
f=92100000,m=WFM,bw=200k,d=One World Radio
f=93500000,m=WFM,bw=200k,d=RFI România
f=94200000,m=WFM,bw=200k,d=Radio Vocea Evangheliei
f=94800000,m=WFM,bw=200k,d=Radio Guerrilla
f=96100000,m=WFM,bw=200k,d=Kiss FM
f=96900000,m=WFM,bw=200k,d=Gold FM
f=97900000,m=WFM,bw=200k,d=Digi FM
f=98300000,m=WFM,bw=200k,d=București FM
f=99300000,m=WFM,bw=200k,d=Radio Itsy Bitsy
f=100200000,m=WFM,bw=200k,d=Virgin Radio
f=100600000,m=WFM,bw=200k,d=Rock FM
f=101300000,m=WFM,bw=200k,d=Radio România Cultural
f=101900000,m=WFM,bw=200k,d=Romantic FM
f=102800000,m=WFM,bw=200k,d=PRO FM
f=103400000,m=WFM,bw=200k,d=Radio Seven
f=103800000,m=WFM,bw=200k,d=Digi 24 FM
f=104800000,m=WFM,bw=200k,d=Radio România Muzical
f=105300000,m=WFM,bw=200k,d=Radio România Actualități
f=105800000,m=WFM,bw=200k,d=Sport Total FM
f=106200000,m=WFM,bw=200k,d=3 FM
f=106700000,m=WFM,bw=200k,d=Europa FM
f=107300000,m=WFM,bw=200k,d=Smart FM
-94
View File
@@ -1,94 +0,0 @@
f=91200000,m=WFM,bw=200k,d=Radio Slovensko Nitra
f=92200000,m=WFM,bw=200k,d=Radio Slovensko Poprad
f=96600000,m=WFM,bw=200k,d=Radio Slovensko Bratislava
f=96600000,m=WFM,bw=200k,d=Radio Slovensko Kosice
f=103500000,m=WFM,bw=200k,d=Radio Slovensko Zilina
f=99300000,m=WFM,bw=200k,d=Radio Regina Bratislava
f=100100000,m=WFM,bw=200k,d=Radio Regina Zilina
f=100300000,m=WFM,bw=200k,d=Radio Regina Kosice
f=96200000,m=WFM,bw=200k,d=Radio Devin Kosice
f=104400000,m=WFM,bw=200k,d=Radio Devin Bratislava
f=106700000,m=WFM,bw=200k,d=Radio Devin Presov
f=89300000,m=WFM,bw=200k,d=Radio _FM Bratislava
f=94500000,m=WFM,bw=200k,d=Radio _FM Zilina
f=101200000,m=WFM,bw=200k,d=Radio _FM Kosice
f=101500000,m=WFM,bw=200k,d=Radio _FM Presov
f=105400000,m=WFM,bw=200k,d=Radio _FM Banska Bystrica
f=98900000,m=WFM,bw=200k,d=Radio Patria Bratislava
f=102400000,m=WFM,bw=200k,d=Radio Patria Senec
f=98900000,m=WFM,bw=200k,d=Radio Slovakia International Bratislava
f=107600000,m=WFM,bw=200k,d=Radio Expres Bratislava
f=96500000,m=WFM,bw=200k,d=Radio Expres Zilina
f=93100000,m=WFM,bw=200k,d=Radio Expres Martin
f=93600000,m=WFM,bw=200k,d=Radio Expres Donovaly
f=95100000,m=WFM,bw=200k,d=Radio Expres Banska Stiavnica
f=95200000,m=WFM,bw=200k,d=Radio Expres Kosice
f=105200000,m=WFM,bw=200k,d=Radio Expres Presov
f=99500000,m=WFM,bw=200k,d=Radio Expres Vychodna
f=104800000,m=WFM,bw=200k,d=Europa 2 Bratislava
f=93500000,m=WFM,bw=200k,d=Europa 2 Martin
f=97200000,m=WFM,bw=200k,d=Europa 2 Presov
f=102000000,m=WFM,bw=200k,d=Europa 2 Kosice
f=106600000,m=WFM,bw=200k,d=Radio Melody Bratislava
f=88800000,m=WFM,bw=200k,d=Radio Melody Nitra
f=89800000,m=WFM,bw=200k,d=Radio Rock Presov
f=97600000,m=WFM,bw=200k,d=Radio Rock Bratislava
f=100300000,m=WFM,bw=200k,d=Radio Rock Trencin
f=94000000,m=WFM,bw=200k,d=Fun radio Nitra
f=94300000,m=WFM,bw=200k,d=Fun radio Bratislava
f=102900000,m=WFM,bw=200k,d=Fun radio Kosice
f=94800000,m=WFM,bw=200k,d=Radio Vlna Kosice
f=96100000,m=WFM,bw=200k,d=Radio Vlna Nitra
f=101800000,m=WFM,bw=200k,d=Radio Vlna Bratislava
f=104800000,m=WFM,bw=200k,d=Radio Vlna Poprad
f=104900000,m=WFM,bw=200k,d=Radio Vlna Martin
f=105100000,m=WFM,bw=200k,d=Radio Vlna Banska Stiavnica
f=105800000,m=WFM,bw=200k,d=Radio Vlna Presov
f=95600000,m=WFM,bw=200k,d=Best FM Bratislava
f=93800000,m=WFM,bw=200k,d=Best FM Kosice
f=88200000,m=WFM,bw=200k,d=Best FM Nitra
f=94000000,m=WFM,bw=200k,d=Best FM Presov
f=98900000,m=WFM,bw=200k,d=Best FM Trencin
f=93000000,m=WFM,bw=200k,d=Best FM Trnava
f=98000000,m=WFM,bw=200k,d=Best FM Zilina
f=89400000,m=WFM,bw=200k,d=Radio Lumen Martin
f=92900000,m=WFM,bw=200k,d=Radio Lumen Presov
f=93800000,m=WFM,bw=200k,d=Radio Lumen Bratislava
f=94400000,m=WFM,bw=200k,d=Radio Lumen Kosice
f=98100000,m=WFM,bw=200k,d=Radio Lumen Trnava
f=91700000,m=WFM,bw=200k,d=Radio 7 Bratislava
f=88600000,m=WFM,bw=200k,d=Radio 7 Kosice
f=103600000,m=WFM,bw=200k,d=Radio 7 Nitra
f=96400000,m=WFM,bw=200k,d=Radio 7 Poprad
f=107500000,m=WFM,bw=200k,d=Radio 7 Ruzomberok
f=101000000,m=WFM,bw=200k,d=Radio 7 Zilina
f=104100000,m=WFM,bw=200k,d=Dobre radio Presov
f=104200000,m=WFM,bw=200k,d=Dobre radio Domasa
f=104500000,m=WFM,bw=200k,d=Dobre radio Kosice
f=107000000,m=WFM,bw=200k,d=Dobre radio Bratislava
f=89700000,m=WFM,bw=200k,d=Radio Beta Zvolen
f=104300000,m=WFM,bw=200k,d=Radio Beta Trencin
f=96200000,m=WFM,bw=200k,d=Radio Frontinus Martin
f=104600000,m=WFM,bw=200k,d=Radio Frontinus Zilina
f=99600000,m=WFM,bw=200k,d=Radio Rebeca Martin
f=88600000,m=WFM,bw=200k,d=Radio Rebeca Zilina
f=96000000,m=WFM,bw=200k,d=Radio SiTy Bratislava
f=97700000,m=WFM,bw=200k,d=Sky Radio Kosice
f=91400000,m=WFM,bw=200k,d=Sky Radio Presov
f=106600000,m=WFM,bw=200k,d=Sky Radio Poprad
f=106200000,m=WFM,bw=200k,d=Trnavske radio Trnava
f=107900000,m=WFM,bw=200k,d=Trnavske radio Piestany
f=93300000,m=WFM,bw=200k,d=Radio VIVA Bratislava
f=101000000,m=WFM,bw=200k,d=Radio VIVA Trnava
f=93600000,m=WFM,bw=200k,d=Radio WOW Nitra
f=106700000,m=WFM,bw=200k,d=Radio WOW Trencin
f=89200000,m=WFM,bw=200k,d=Zahoracke radio Skalica
f=101500000,m=WFM,bw=200k,d=Zahoracke radio Senica
f=92400000,m=WFM,bw=200k,d=Radio Goldies Martin
f=92800000,m=WFM,bw=200k,d=Radio Goldies Trencin
f=104100000,m=WFM,bw=200k,d=Radio Kiss Presov
f=90100000,m=WFM,bw=200k,d=Radio Kosice Bratislava
f=90400000,m=WFM,bw=200k,d=Radio Kosice Presov
f=91700000,m=WFM,bw=200k,d=Radio Kosice Kosice
f=106400000,m=WFM,bw=200k,d=Radio Liptov Liptovsky Mikulas
f=106100000,m=WFM,bw=200k,d=Radio Modra
-28
View File
@@ -1,28 +0,0 @@
f=129400000,m=AM,bw=DSB 6k,d=Bucharest Information (LRBB_FSS)
f=134380000,m=AM,bw=DSB 6k,d=Bucharest Radar [sector consolidat] (LRBB_CTR)
f=119415000,m=AM,bw=DSB 6k,d=Bucharest Approach (LROP_APP)
f=127155000,m=AM,bw=DSB 6k,d=Bucharest Director (LROP_D_APP)
f=122905000,m=AM,bw=DSB 6k,d=Constanta Approach (LRCK_APP)
f=126430000,m=AM,bw=DSB 6k,d=NAPOC Approach (LRCL_APP)
f=126430000,m=AM,bw=DSB 6k,d=NAPOC North Approach (LRCL_N_APP)
f=119680000,m=AM,bw=DSB 6k,d=NAPOC South Approach (LRCL_S_APP)
f=123530000,m=AM,bw=DSB 6k,d=Arad Approach (LRAR_APP)
f=118230000,m=AM,bw=DSB 6k,d=Arad (LRAR_TWR)
f=120980000,m=AM,bw=DSB 6k,d=Bacau (LRBC_TWR)
f=118855000,m=AM,bw=DSB 6k,d=Baia Mare (LRBM_TWR)
f=125205000,m=AM,bw=DSB 6k,d=Baneasa (LRBS_TWR)
f=118705000,m=AM,bw=DSB 6k,d=Cluj (LRCL_TWR)
f=120030000,m=AM,bw=DSB 6k,d=Constanta (LRCK_TWR)
f=129530000,m=AM,bw=DSB 6k,d=Craiova (LRCV_TWR)
f=119955000,m=AM,bw=DSB 6k,d=Iasi (LRIA_TWR)
f=118805000,m=AM,bw=DSB 6k,d=Otopeni (LROP_TWR)
f=118455000,m=AM,bw=DSB 6k,d=Oradea (LROD_TWR)
f=119655000,m=AM,bw=DSB 6k,d=Satu Mare (LRSM_TWR)
f=121305000,m=AM,bw=DSB 6k,d=Sibiu (LRSB_TWR)
f=129955000,m=AM,bw=DSB 6k,d=Suceava (LRSV_TWR)
f=120105000,m=AM,bw=DSB 6k,d=Timisoara (LRTR_TWR)
f=119180000,m=AM,bw=DSB 6k,d=Targu Mures (LRTM_TWR)
f=119755000,m=AM,bw=DSB 6k,d=Tulcea (LRTC_TWR)
f=129950000,m=AM,bw=DSB 6k,d=Baneasa (LRBS_GND)
f=121855000,m=AM,bw=DSB 6k,d=Otopeni (LROP_GND)
f=121600000,m=AM,bw=DSB 6k,d=Timisoara (LRTR_GND)
-62
View File
@@ -1,62 +0,0 @@
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
# Lower Paging Bands - Paired
f=152030000,d=USA LPBP-FA1
f=152060000,d=USA LPBP-FB1
f=152090000,d=USA LPBP-FC1
f=152120000,d=USA LPBP-FD1
f=152150000,d=USA LPBP-FE1
f=152180000,d=USA LPBP-FF1
f=152210000,d=USA LPBP-FG1
f=152510000,d=USA LPBP-FH1
f=152540000,d=USA LPBP-FI1
f=152570000,d=USA LPBP-FJ1
f=152600000,d=USA LPBP-FK1
f=152630000,d=USA LPBP-FL1
f=152660000,d=USA LPBP-FM1
f=152690000,d=USA LPBP-FN1
f=152720000,d=USA LPBP-FO1
f=152750000,d=USA LPBP-FP1
f=152780000,d=USA LPBP-FQ1
f=152810000,d=USA LPBP-FR1
f=158490000,d=USA LPBP-FA2
f=158520000,d=USA LPBP-FB2
f=158550000,d=USA LPBP-FC2
f=158580000,d=USA LPBP-FD2
f=158610000,d=USA LPBP-FE2
f=158640000,d=USA LPBP-FF2
f=158670000,d=USA LPBP-FG2
f=157770000,d=USA LPBP-FH2
f=157800000,d=USA LPBP-FI2
f=157830000,d=USA LPBP-FJ2
f=157860000,d=USA LPBP-FK2
f=157890000,d=USA LPBP-FL2
f=157920000,d=USA LPBP-FM2
f=157950000,d=USA LPBP-FN2
f=157980000,d=USA LPBP-FO2
f=158010000,d=USA LPBP-FP2
f=158040000,d=USA LPBP-FQ2
f=158070000,d=USA LPBP-FR2
f=454025000,d=USA LPBP-GA1
f=454050000,d=USA LPBP-GB1
f=454075000,d=USA LPBP-GC1
f=454100000,d=USA LPBP-GD1
f=454125000,d=USA LPBP-GE1
f=454150000,d=USA LPBP-GF1
f=454175000,d=USA LPBP-GG1
f=454200000,d=USA LPBP-GH1
f=454225000,d=USA LPBP-GI1
f=454250000,d=USA LPBP-GJ1
f=454275000,d=USA LPBP-GK1
f=454300000,d=USA LPBP-GL1
f=454325000,d=USA LPBP-GM1
f=152570000,d=USA LPBP-GN1
f=152600000,d=USA LPBP-GO1
f=152630000,d=USA LPBP-GP1
f=152660000,d=USA LPBP-GQ1
f=152690000,d=USA LPBP-GR1
f=152720000,d=USA LPBP-GS1
f=152750000,d=USA LPBP-GT1
f=152780000,d=USA LPBP-GU1
f=152810000,d=USA LPBP-GV1
f=152720000,d=USA LPBP-GX1
f=152750000,d=USA LPBP-GY1
-28
View File
@@ -1,28 +0,0 @@
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
# Lower Paging Bands - Paired
f=152810000,d=USA LPBP-GZ1
f=459025000,d=USA LPBP-GA2
f=459050000,d=USA LPBP-GB2
f=459075000,d=USA LPBP-GC2
f=459100000,d=USA LPBP-GD2
f=459125000,d=USA LPBP-GE2
f=459150000,d=USA LPBP-GF2
f=459175000,d=USA LPBP-GG2
f=459200000,d=USA LPBP-GH2
f=459225000,d=USA LPBP-GI2
f=459250000,d=USA LPBP-GJ2
f=459275000,d=USA LPBP-GK2
f=459300000,d=USA LPBP-GL2
f=459325000,d=USA LPBP-GM2
f=157830000,d=USA LPBP-GN2
f=157860000,d=USA LPBP-GO2
f=157890000,d=USA LPBP-GP2
f=157920000,d=USA LPBP-GQ2
f=157950000,d=USA LPBP-GR2
f=157980000,d=USA LPBP-GS2
f=158010000,d=USA LPBP-GT2
f=158040000,d=USA LPBP-GU2
f=158070000,d=USA LPBP-GV2
f=157980000,d=USA LPBP-GX2
f=158010000,d=USA LPBP-GY2
f=158070000,d=USA LPBP-GZ2
-38
View File
@@ -1,38 +0,0 @@
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
# Lower Paging Bands - Unpaired
f=35200000,d=USA LPBU-CA
f=35220000,d=USA LPBU-CB
f=35240000,d=USA LPBU-CC
f=35260000,d=USA LPBU-CD
f=35300000,d=USA LPBU-CE
f=35340000,d=USA LPBU-CF
f=35380000,d=USA LPBU-CG
f=35420000,d=USA LPBU-CH
f=35460000,d=USA LPBU-CI
f=35500000,d=USA LPBU-CJ
f=35540000,d=USA LPBU-CK
f=35560000,d=USA LPBU-CL
f=35580000,d=USA LPBU-CM
f=35600000,d=USA LPBU-CN
f=35620000,d=USA LPBU-CO
f=35660000,d=USA LPBU-CP
f=43200000,d=USA LPBU-DA
f=43220000,d=USA LPBU-DB
f=43240000,d=USA LPBU-DC
f=43260000,d=USA LPBU-DD
f=43300000,d=USA LPBU-DE
f=43340000,d=USA LPBU-DF
f=43380000,d=USA LPBU-DG
f=43420000,d=USA LPBU-DH
f=43460000,d=USA LPBU-DI
f=43500000,d=USA LPBU-DJ
f=43540000,d=USA LPBU-DK
f=43560000,d=USA LPBU-DL
f=43580000,d=USA LPBU-DM
f=43600000,d=USA LPBU-DN
f=43620000,d=USA LPBU-DO
f=43660000,d=USA LPBU-DP
f=152240000,d=USA LPBU-EA
f=152800000,d=USA LPBU-EB
f=158100000,d=USA LPBU-EC
f=158700000,d=USA LPBU-ED
-52
View File
@@ -1,52 +0,0 @@
# https://www.fcc.gov/wireless/bureau-divisions/mobility-division/paging
# Upper Paging Bands - Private Carrier Paging
f=929012500,d=USA PCP-A
f=929112500,d=USA PCP-B
f=929237500,d=USA PCP-C
f=929312500,d=USA PCP-D
f=929387500,d=USA PCP-E
f=929437500,d=USA PCP-F
f=929462500,d=USA PCP-G
f=929637500,d=USA PCP-H
f=929687500,d=USA PCP-I
f=929787500,d=USA PCP-J
f=929912500,d=USA PCP-K
f=929962500,d=USA PCP-L
# Upper Paging Bands - Common Carrier Paging
f=931012500,d=USA CCP-AA
f=931037500,d=USA CCP-AB
f=931062500,d=USA CCP-AC
f=931087500,d=USA CCP-AD
f=931112500,d=USA CCP-AE
f=931137500,d=USA CCP-AF
f=931162500,d=USA CCP-AG
f=931187500,d=USA CCP-AH
f=931212500,d=USA CCP-AI
f=931237500,d=USA CCP-AJ
f=931262500,d=USA CCP-AK
f=931287500,d=USA CCP-AL
f=931312500,d=USA CCP-AM
f=929462500,d=USA CCP-AN
f=929637500,d=USA CCP-AO
f=929687500,d=USA CCP-AP
f=929787500,d=USA CCP-AQ
f=929912500,d=USA CCP-AR
f=929962500,d=USA CCP-AS
f=929462500,d=USA CCP-AT
f=929637500,d=USA CCP-AU
f=929687500,d=USA CCP-AV
f=929787500,d=USA CCP-AW
f=929912500,d=USA CCP-AX
f=929962500,d=USA CCP-AY
f=929962500,d=USA CCP-AZ
f=931662500,d=USA CCP-BA
f=931687500,d=USA CCP-BB
f=931712500,d=USA CCP-BC
f=931737500,d=USA CCP-BD
f=931762500,d=USA CCP-BE
f=931787500,d=USA CCP-BF
f=931812500,d=USA CCP-BG
f=931837500,d=USA CCP-BH
f=931862500,d=USA CCP-BI
f=931962500,d=USA CCP-BJ
f=931987500,d=USA CCP-BK
+3
View File
@@ -0,0 +1,3 @@
2402000000
2426000000
2480000000
+10
View File
@@ -0,0 +1,10 @@
433920000
315000000
418000000
303000000
310000000
330000000
350000000
370000000
390000000
430500000

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