Compare commits

..

23 Commits

Author SHA1 Message Date
Totoo d73db884f0 Added wefax offset to audio app too. (#2572)
* added wefax offset to audio app too.

* moved from head to cpp
2025-03-19 23:03:04 +01:00
Benjamin Møller ff2f27e297 Add icon for the doom game (#2574)
* Add icon for the doom game
* Add b/w .png to convert as doom icon
2025-03-19 17:58:34 +00:00
gullradriel 443ffd8ad3 Add WEFAX freqman file (#2567)
* Add WEFAX freqman file
* fix bad escaped spaces and unicode characters
2025-03-19 18:45:35 +01:00
sommermorgentraum 403c362702 fix external app address list (#2573) 2025-03-19 18:45:11 +01:00
RocketGod 6aa29e265b Update Doom main.cpp for better icon (#2575) 2025-03-19 18:30:11 +01:00
RocketGod 40cf2b3f8c Doom - Mayhem Edition (#2570)
* Doom - Mini Mayhem version

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Update ui_doom.cpp

* Doom - Mayhem Edition

Made the Doom - Mayhem Edition game. Some little bugs but good enough for nightly.

* Code formatting. Forgot like always.

* give more initial ammo until i put ammo around the maze to collect
2025-03-19 14:16:12 +08:00
Totoo 4aa5fc1fbe WeFax rx ext app (#2566)
* wf3

* Ookbrute (#2354)

* Revert "Ookbrute (#2354)"

This reverts commit abb8143eec.

* fix

* test edition

* re enable ble

* re enable ert

* steal amfm stuff

* something happens

* save bmp on start btn

* kinda works

* exit crash fixed

* redline, remove some hardcoded

* removed cpu killer red line, and some fixes

* simplify #1

* seems ok. time to improve

* added hidden freq offset to receiver model, so wefax can be set to the "correct" freq without users needs to substract 300 hz

* badly implemented sync detection, and disabled it.

* fix for fix

* fixes

* fix offset to real life off

* no line on freq enter

* fixes
2025-03-18 18:31:40 -05:00
Erwin Ried 717d615f4f Update README.md
Other link broken
2025-03-18 10:46:19 +01:00
Erwin Ried dce4e91e41 Update README.md
Fixing opesourcesdrlab link
2025-03-18 10:36:34 +01:00
Brumi-2021 dc2f3260f3 zooming_spectrum_AMFM_mode (#2565) 2025-03-16 18:35:03 +01:00
sommermorgentraum 2d85e73f0d OokBrute app opt (#2561) 2025-03-11 21:17:47 +01:00
Mark Thompson 6ee7270db7 Setting for faster Button Repeat delays (#2559)
* Setting for faster Button Repeat delays

* Tweak fast delay times

* Tweak delay times

* Added description line and tweaked delay again
2025-03-11 09:13:41 -05:00
Mark Thompson b4112f0c04 Remove deprecated QR Code code (#2558) 2025-03-10 19:13:57 +00:00
Mark Thompson e920932886 Snake icon change (#2557) 2025-03-10 12:37:30 -05:00
Mark Thompson 081b11ad41 Breakout icon change (#2556)
- Changed Breakout icon
- Changed Tetris icon color to green
2025-03-10 17:25:49 +01:00
gullradriel adc23315a2 updated bitmaps for speaker icon (#2555)
* updated bitmaps for speaker icon
* removed opera cake icon that was added by mistake
* adding missing Game menu source png
* cyan for Game menu
* regenerated icons
2025-03-10 12:33:33 +01:00
Mark Thompson 084b88564b Stopwatch external app (#2553) 2025-03-09 22:03:41 +01:00
dark-juju 24e9504688 Moved speaker 1px to the right to match muted icon variant. (#2554) 2025-03-09 21:02:48 +00:00
sommermorgentraum c670ca59a8 rename rand pwd app (#2552)
* _

* remove wip code that forgot to add in old branch
2025-03-08 21:38:12 -06:00
RocketGod 2ca8ea6342 Nested Debug menu into Utilities menu to tidy Home screen (#2551) 2025-03-08 18:05:33 -06:00
RocketGod eb50b790c2 Snake (#2549)
* Create the Shopping Cart Lock app

Will demonstrate tomorrow. Don't merge until I do 😁

* Fixes for HTotoo's comments 😎

* Improved audio the best I can.

If nobody has any ideas to further improve high frequencies of the audio, the hardware may not be capable. I still need to check with line-out to better speaker to make sure it's not just the speaker, but it shouldn't be.

* Compared against baseband_api.cpp - matched some things better but still playback seems to be missing higher fq sounds

* renamed wav files to a more specific / less generic name

* indentation + using variables instead of litteral names for wav files to use

* indentation

* Made a Snake game - enjoy

* Code formatting. I always forget.

* move to keep sort order

* Update external.ld

Sorry I should have also asked if there was any reason that address ranges 0xADDA0000--0xADDD0000 were skipped in external.ld.  I assumed there wasn't so I changed it to be consecutive using the same 0x10000 step as the other modules.  If there is any reason to skip them then we should add a comment to note it.  Of course these are all just temporary address values used for linking and get overwritten by a kludgy "search & replace" during the build process.

Resolves enhancement request #764

---------

Co-authored-by: gullradriel <gullradriel@no-mail.com>
Co-authored-by: Mark Thompson <129641948+NotherNgineer@users.noreply.github.com>
2025-03-08 08:33:27 -06:00
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
76 changed files with 9089 additions and 5370 deletions
+2 -2
View File
@@ -26,11 +26,11 @@ This repository expands upon the previous work by many people and aims to consta
## What to buy?
<!---not direct to h4m but to opensourcesdrlab https://share.hackrf.app/TUOLYI--->
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/HR60Q4) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: ![Static Badge](https://img.shields.io/badge/NEW-yellow) The fabulous H4M [complete](https://share.hackrf.app/ABDO6H) or [upgrade](https://share.hackrf.app/FPLM1H), featuring numerous improvements and accessories. Sold by our friends at [OpenSourceSDRLab](https://share.hackrf.app/99SAMT). Join their giveaways on discord (check the badge on top). _EU customers_ can purchase via [Lab401](https://share.hackrf.app/0CI2CR).
:heavy_check_mark: A recommended one is this [PortaPack H2](https://www.ebay.com/itm/116382397447), that includes everything you need with the plastic case "inspired" on [this](https://github.com/portapack-mayhem/mayhem-firmware/wiki/3d-printed-enclosure).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/9FJFJE), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:heavy_check_mark: Some individuals lean towards the [H2 with a metal enclosure](https://share.hackrf.app/24T3TO), but its advantages remain debated. Share your insights on our [wiki](https://github.com/portapack-mayhem/mayhem-firmware/wiki/Hardware-overview).
:warning: Be cautious , *ask* the seller about compatibility with the latest releases. Look out for the description of the item, if they provide the firmware files for an older version or they have custom setup instructions, this means it might be **NOT compatible**, for example:
@@ -116,8 +116,8 @@ AMFMAptOptionsView::AMFMAptOptionsView(
});
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());
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.
}
/* SPECOptionsView *******************************************************/
@@ -193,9 +193,10 @@ AnalogAudioView::AnalogAudioView(
};
auto modulation = receiver_model.modulation();
// This app doesn't handle "Capture" mode.
if (modulation > ReceiverModel::Mode::SpectrumAnalysis) // This two should be together in the last index position : SpectrumAnalysis = 4, and Capture = 5
modulation = ReceiverModel::Mode::SpectrumAnalysis; // For sw simplicity , Wefax_mode, should NOT be added between.
if (modulation == ReceiverModel::Mode::Capture)
modulation = ReceiverModel::Mode::SpectrumAnalysis;
options_modulation.set_by_value(toUType(modulation));
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
@@ -264,6 +265,7 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
}
AnalogAudioView::~AnalogAudioView() {
receiver_model.set_hidden_offset(0);
audio::output::stop();
receiver_model.disable();
baseband::shutdown();
@@ -285,10 +287,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();
@@ -434,6 +432,8 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
receiver_model.enable();
// TODO: This doesn't belong here! There's a better way.
@@ -224,11 +224,13 @@ class AnalogAudioView : public View {
OptionsField options_modulation{
{0 * 8, 0 * 16},
4,
{{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
{"WFAX", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}}};
{
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
{"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{
{28 * 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;
}
+31 -27
View File
@@ -674,32 +674,6 @@ void SetAudioView::focus() {
button_save.focus();
}
/* SetQRCodeView *****************************************/
SetQRCodeView::SetQRCodeView(NavigationView& nav) {
add_children({
&labels,
&checkbox_bigger_qr,
&button_save,
&button_cancel,
});
checkbox_bigger_qr.set_value(pmem::show_bigger_qr_code());
button_save.on_select = [&nav, this](Button&) {
pmem::set_show_bigger_qr_code(checkbox_bigger_qr.value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetQRCodeView::focus() {
button_save.focus();
}
/* SetEncoderDialView ************************************/
SetEncoderDialView::SetEncoderDialView(NavigationView& nav) {
@@ -747,6 +721,36 @@ void SetEncoderDialView::focus() {
button_save.focus();
}
/* SetButtonsView ************************************/
SetButtonsView::SetButtonsView(NavigationView& nav) {
add_children({&labels,
&button_save,
&button_cancel,
&field_repeat_delay,
&field_repeat_speed,
&field_long_press_delay});
field_repeat_delay.set_by_value(pmem::ui_button_repeat_delay());
field_repeat_speed.set_by_value(pmem::ui_button_repeat_speed());
field_long_press_delay.set_by_value(pmem::ui_button_long_press_delay());
button_save.on_select = [&nav, this](Button&) {
pmem::set_ui_button_repeat_delay(field_repeat_delay.selected_index_value());
pmem::set_ui_button_repeat_speed(field_repeat_speed.selected_index_value());
pmem::set_ui_button_long_press_delay(field_long_press_delay.selected_index_value());
nav.pop();
};
button_cancel.on_select = [&nav, this](Button&) {
nav.pop();
};
}
void SetButtonsView::focus() {
button_save.focus();
}
/* AppSettingsView ************************************/
AppSettingsView::AppSettingsView(
@@ -1093,12 +1097,12 @@ void SettingsMenuView::on_populate() {
{"Converter", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetConverterSettingsView>(); }},
{"Date/Time", ui::Color::dark_cyan(), &bitmap_icon_options_datetime, [this]() { nav_.push<SetDateTimeView>(); }},
{"Encoder Dial", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetEncoderDialView>(); }},
{"Button Speed", ui::Color::dark_cyan(), &bitmap_icon_controls, [this]() { nav_.push<SetButtonsView>(); }},
{"Freq. Correct", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetFrequencyCorrectionView>(); }},
{"P.Memory Mgmt", ui::Color::dark_cyan(), &bitmap_icon_memory, [this]() { nav_.push<SetPersistentMemoryView>(); }},
{"Radio", ui::Color::dark_cyan(), &bitmap_icon_options_radio, [this]() { nav_.push<SetRadioView>(); }},
{"SD Card", ui::Color::dark_cyan(), &bitmap_icon_sdcard, [this]() { nav_.push<SetSDCardView>(); }},
{"User Interface", ui::Color::dark_cyan(), &bitmap_icon_options_ui, [this]() { nav_.push<SetUIView>(); }},
//{"QR Code", ui::Color::dark_cyan(), &bitmap_icon_qr_code, [this]() { nav_.push<SetQRCodeView>(); }},
{"Display", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetDisplayView>(); }},
{"Menu Color", ui::Color::dark_cyan(), &bitmap_icon_brightness, [this]() { nav_.push<SetMenuColorView>(); }},
{"Theme", ui::Color::dark_cyan(), &bitmap_icon_setup, [this]() { nav_.push<SetThemeView>(); }},
+43 -29
View File
@@ -545,35 +545,6 @@ class SetAudioView : public View {
};
};
class SetQRCodeView : public View {
public:
SetQRCodeView(NavigationView& nav);
void focus() override;
std::string title() const override { return "QR Code"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Change the size of the QR", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "code shown in Radiosonde.", Theme::getInstance()->fg_light->foreground},
};
Checkbox checkbox_bigger_qr{
{3 * 8, 4 * 16},
20,
"Show large QR code"};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
using portapack::persistent_memory::encoder_dial_direction;
using portapack::persistent_memory::encoder_dial_sensitivity;
using portapack::persistent_memory::encoder_rate_multiplier;
@@ -644,6 +615,49 @@ class SetEncoderDialView : public View {
};
};
class SetButtonsView : public View {
public:
SetButtonsView(NavigationView& nav);
void focus() override;
std::string title() const override { return "Button Speed"; };
private:
Labels labels{
{{1 * 8, 1 * 16}, "Adjusts response time when a", Theme::getInstance()->fg_light->foreground},
{{1 * 8, 2 * 16}, "button is held down.", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 5 * 16}, "Repeat delay:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 7 * 16}, "Repeat speed:", Theme::getInstance()->fg_light->foreground},
{{2 * 8, 9 * 16}, "Long press delay:", Theme::getInstance()->fg_light->foreground},
};
OptionsField field_repeat_delay{
{20 * 8, 5 * 16},
6,
{{"NORMAL", false},
{"FAST", true}}};
OptionsField field_repeat_speed{
{20 * 8, 7 * 16},
6,
{{"NORMAL", false},
{"FAST", true}}};
OptionsField field_long_press_delay{
{20 * 8, 9 * 16},
6,
{{"NORMAL", false},
{"FAST", true}}};
Button button_save{
{2 * 8, 16 * 16, 12 * 8, 32},
"Save"};
Button button_cancel{
{16 * 8, 16 * 16, 12 * 8, 32},
"Cancel",
};
};
class SetPersistentMemoryView : public View {
public:
SetPersistentMemoryView(NavigationView& nav);
+7 -2
View File
@@ -66,10 +66,10 @@ 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 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, WFAX)
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,WFAX)
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);
@@ -304,6 +304,11 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger) {
send_message(&message);
}
void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
const WeFaxRxConfigureMessage message{lpm, ioc};
send_message(&message);
}
void set_siggen_tone(const uint32_t tone) {
const SigGenToneMessage message{
TONES_F2D(tone, TONES_SAMPLERATE)};
+1
View File
@@ -90,6 +90,7 @@ void set_siggen_tone(const uint32_t tone);
void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t duration);
void set_spectrum_painter_config(const uint16_t width, const uint16_t height, bool update, int32_t bw);
void set_subghzd_config(uint8_t modulation, uint32_t sampling_rate);
void set_wefax_config(uint8_t lpm, uint8_t ioc);
void request_roger_beep();
void request_rssi_beep();
File diff suppressed because it is too large Load Diff
+33 -33
View File
@@ -27,41 +27,41 @@ __attribute__((section(".external_app.app_breakout.application_information"), us
/*.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,
0x00,
0x00,
0x7F,
0x7F,
0x7F,
0x7F,
0x7F,
0x7F,
0x00,
0x00,
0xF7,
0xF7,
0xF7,
0xF7,
0xF7,
0xF7,
0x00,
0x00,
0x7F,
0x7F,
0x7F,
0x7F,
0x7F,
0x7F,
0x00,
0x00,
0xF7,
0xF7,
0xF7,
0xF7,
0xF7,
0xF7,
},
/*.icon_color = */ ui::Color::green().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},
+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__*/
+30
View File
@@ -0,0 +1,30 @@
#ifndef __UI_SPI_TFT_ILI9341_H__
#define __UI_SPI_TFT_ILI9341_H__
ui::Painter painter;
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(),
};
#endif /*__UI_SPI_TFT_ILI9341_H__*/
+67
View File
@@ -0,0 +1,67 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_doom.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::doom {
void initialize_app(ui::NavigationView& nav) {
nav.push<DoomView>();
}
} // namespace ui::external_app::doom
extern "C" {
__attribute__((section(".external_app.app_doom.application_information"), used)) application_information_t _application_information_doom = {
(uint8_t*)0x00000000,
ui::external_app::doom::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Doom",
{
0x00,
0x00,
0x00,
0x00,
0x77,
0xDF,
0xFF,
0xDF,
0xD9,
0xFD,
0x89,
0xF8,
0x89,
0xE8,
0x89,
0xA8,
0x89,
0xA8,
0xD9,
0xAD,
0x79,
0xAF,
0x2D,
0xAA,
0x07,
0xA8,
0x03,
0xA0,
0x01,
0x80,
0x00,
0x00,
},
ui::Color::red().v,
app_location_t::GAMES,
-1,
{0, 0, 0, 0},
0x00000000,
};
}
+81
View File
@@ -0,0 +1,81 @@
#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,
};
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;
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__*/
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_DOOM_H__
#define __UI_DOOM_H__
#include "ui_widget.hpp"
#include "ui_navigation.hpp"
#include "message.hpp"
namespace ui::external_app::doom {
class DoomView : public View {
public:
DoomView(NavigationView& nav);
void on_show() override;
std::string title() const override { return "Doom"; }
void focus() override { dummy.focus(); }
void paint(Painter& painter) override;
void frame_sync();
bool on_key(const KeyEvent key) override;
private:
NavigationView& nav_;
Button dummy{{240, 0, 0, 0}, ""};
bool initialized{false};
bool prev_velocity_moving{false};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::doom
#endif /*__UI_DOOM_H__*/
+38 -17
View File
@@ -1,13 +1,5 @@
set(EXTCPPSRC
#tetris
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
@@ -63,6 +55,10 @@ set(EXTCPPSRC
external/keyfob/ui_keyfob.cpp
external/keyfob/ui_keyfob.hpp
#tetris
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#extsensors
external/extsensors/main.cpp
external/extsensors/ui_extsensors.cpp
@@ -111,9 +107,14 @@ set(EXTCPPSRC
external/acars_rx/main.cpp
external/acars_rx/acars_app.cpp
#wefax_rx
external/wefax_rx/main.cpp
external/wefax_rx/ui_wefax_rx.cpp
#shoppingcart_lock
external/shoppingcart_lock/main.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
external/shoppingcart_lock/shoppingcart_lock.cpp
#ookbrute
external/ookbrute/main.cpp
@@ -125,8 +126,8 @@ set(EXTCPPSRC
#cvs_spam
external/cvs_spam/main.cpp
external/cvs_spam/cvs_spam.cpp
external/cvs_spam/cvs_spam.cpp
#flippertx
external/flippertx/main.cpp
external/flippertx/ui_flippertx.cpp
@@ -138,15 +139,15 @@ set(EXTCPPSRC
#mcu_temperature
external/mcu_temperature/main.cpp
external/mcu_temperature/mcu_temperature.cpp
#fmradio
external/fmradio/main.cpp
external/fmradio/ui_fmradio.cpp
#tuner
external/tuner/main.cpp
external/tuner/ui_tuner.cpp
#metronome
external/metronome/main.cpp
external/metronome/ui_metronome.cpp
@@ -158,8 +159,8 @@ set(EXTCPPSRC
#hopper
external/hopper/main.cpp
external/hopper/ui_hopper.cpp
# whip calculator
# whip calculator
external/antenna_length/main.cpp
external/antenna_length/ui_whipcalc.cpp
@@ -174,6 +175,22 @@ set(EXTCPPSRC
# playlist editor
external/playlist_editor/main.cpp
external/playlist_editor/ui_playlist_editor.cpp
#snake
external/snake/main.cpp
external/snake/ui_snake.cpp
#stopwatch
external/stopwatch/main.cpp
external/stopwatch/ui_stopwatch.cpp
#breakout
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#doom
external/doom/main.cpp
external/doom/ui_doom.cpp
)
set(EXTAPPLIST
@@ -191,7 +208,6 @@ set(EXTAPPLIST
spainter
keyfob
tetris
breakout
extsensors
foxhunt_rx
audio_test
@@ -206,6 +222,7 @@ set(EXTAPPLIST
#acars_rx
ookbrute
ook_editor
wefax_rx
shoppingcart_lock
flippertx
remote
@@ -219,4 +236,8 @@ set(EXTAPPLIST
view_wav
sd_wipe
playlist_editor
snake
stopwatch
breakout
doom
)
+75 -47
View File
@@ -26,45 +26,49 @@ MEMORY
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
ram_external_app_blespam(rwx) : org = 0xADB40000, len = 32k
ram_external_app_analogtv(rwx) : org = 0xADB50000, len = 32k
ram_external_app_nrf_rx(rwx) : org = 0xADB60000, len = 32k
ram_external_app_coasterp(rwx) : org = 0xADB70000, len = 32k
ram_external_app_lge(rwx) : org = 0xADB80000, len = 32k
ram_external_app_lcr(rwx) : org = 0xADB90000, len = 32k
ram_external_app_jammer(rwx) : org = 0xADBA0000, len = 32k
ram_external_app_gpssim(rwx) : org = 0xADBB0000, len = 32k
ram_external_app_spainter(rwx) : org = 0xADBC0000, len = 32k
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_blespam (rwx) : org = 0xADB40000, len = 32k
ram_external_app_analogtv (rwx) : org = 0xADB50000, len = 32k
ram_external_app_nrf_rx (rwx) : org = 0xADB60000, len = 32k
ram_external_app_coasterp (rwx) : org = 0xADB70000, len = 32k
ram_external_app_lge (rwx) : org = 0xADB80000, len = 32k
ram_external_app_lcr (rwx) : org = 0xADB90000, len = 32k
ram_external_app_jammer (rwx) : org = 0xADBA0000, len = 32k
ram_external_app_gpssim (rwx) : org = 0xADBB0000, len = 32k
ram_external_app_spainter (rwx) : org = 0xADBC0000, len = 32k
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_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
ram_external_app_tpmsrx(rwx) : org = 0xADC30000, len = 32k
ram_external_app_protoview(rwx) : org = 0xADC40000, len = 32k
ram_external_app_adsbtx(rwx) : org = 0xADC50000, len = 32k
ram_external_app_morse_tx(rwx) : org = 0xADC60000, len = 32k
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k
ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k
ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
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
ram_external_app_tpmsrx (rwx) : org = 0xADC30000, len = 32k
ram_external_app_protoview (rwx) : org = 0xADC40000, len = 32k
ram_external_app_adsbtx (rwx) : org = 0xADC50000, len = 32k
ram_external_app_morse_tx (rwx) : org = 0xADC60000, len = 32k
ram_external_app_sstvtx (rwx) : org = 0xADC70000, len = 32k
ram_external_app_random_password(rwx): org = 0xADC80000, len = 32k
ram_external_app_acars_rx (rwx) : org = 0xADC90000, len = 32k
ram_external_app_shoppingcart_lock(rwx): org = 0xADCA0000, len = 32k
ram_external_app_cvs_spam (rwx) : org = 0xADCB0000, len = 32k
ram_external_app_ookbrute (rwx) : org = 0xADCC0000, len = 32k
ram_external_app_flippertx (rwx) : org = 0xADCD0000, len = 32k
ram_external_app_ook_editor (rwx) : org = 0xADCE0000, len = 32k
ram_external_app_remote (rwx) : org = 0xADCF0000, len = 32k
ram_external_app_mcu_temperature(rwx): org = 0xADD00000, len = 32k
ram_external_app_fmradio (rwx) : org = 0xADD10000, len = 32k
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
ram_external_app_snake (rwx) : org = 0xADDA0000, len = 32k
ram_external_app_stopwatch (rwx) : org = 0xADDB0000, len = 32k
ram_external_app_wefax_rx (rwx) : org = 0xADDC0000, len = 32k
ram_external_app_breakout (rwx) : org = 0xADDD0000, len = 32k
ram_external_app_doom (rwx) : org = 0xADDE0000, len = 32k
}
SECTIONS
@@ -153,12 +157,6 @@ 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));
@@ -201,7 +199,6 @@ SECTIONS
*(*ui*external_app*adsbtx*);
} > ram_external_app_adsbtx
.external_app_morse_tx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_morse_tx.application_information));
@@ -267,13 +264,13 @@ SECTIONS
KEEP(*(.external_app.app_mcu_temperature.application_information));
*(*ui*external_app*mcu_temperature*);
} > ram_external_app_mcu_temperature
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_fmradio.application_information));
*(*ui*external_app*fmradio*);
} > ram_external_app_fmradio
.external_app_tuner : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_tuner.application_information));
@@ -321,4 +318,35 @@ SECTIONS
KEEP(*(.external_app.app_playlist_editor.application_information));
*(*ui*external_app*playlist_editor*);
} > ram_external_app_playlist_editor
.external_app_snake : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_snake.application_information));
*(*ui*external_app*snake*);
} > ram_external_app_snake
.external_app_stopwatch : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_stopwatch.application_information));
*(*ui*external_app*stopwatch*);
} > ram_external_app_stopwatch
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_wefax_rx.application_information));
*(*ui*external_app*wefax_rx*);
} > ram_external_app_wefax_rx
.external_app_breakout : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_breakout.application_information));
*(*ui*external_app*breakout*);
} > ram_external_app_breakout
.external_app_doom : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_doom.application_information));
*(*ui*external_app*doom*);
} > ram_external_app_doom
}
+39
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -25,6 +26,7 @@
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "ui_textentry.hpp"
using namespace portapack;
using namespace ui;
@@ -39,6 +41,9 @@ OOKBruteView::OOKBruteView(NavigationView& nav)
: nav_{nav} {
add_children({
&button_startstop,
&button_input_stop_position,
&button_input_start_position,
&labels,
&field_frequency,
&tx_view,
&options_atkmode,
@@ -66,6 +71,40 @@ OOKBruteView::OOKBruteView(NavigationView& nav)
field_stop.on_change = [this](int32_t) {
validate_start_stop();
};
button_input_start_position.on_select = [this](Button&) {
stop(); // prevent mess count var up
text_input_buffer = to_string_dec_uint(field_start.value());
if (text_input_buffer == "0") {
text_input_buffer = "";
}
text_prompt(
nav_,
text_input_buffer,
8, // currently longest is princeton
[this](std::string& buffer) {
field_start.set_value(atoi(buffer.c_str()));
validate_start_stop();
});
};
button_input_stop_position.on_select = [this](Button&) {
stop(); // prevent mess count var up
text_input_buffer = to_string_dec_uint(field_stop.value());
if (text_input_buffer == "0") {
text_input_buffer = "";
}
text_prompt(
nav_,
text_input_buffer,
8, // currently longest is princeton
[this](std::string& buffer) {
field_stop.set_value(atoi(buffer.c_str()));
validate_start_stop();
});
};
update_start_stop(0);
}
+21 -4
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2024 HTotoo
* copyleft Whiterose of the Dark Army
*
* This file is part of PortaPack.
*
@@ -52,12 +53,25 @@ class OOKBruteView : public View {
app_settings::SettingsManager settings_{
"tx_ookbrute", app_settings::Mode::TX};
Labels labels{
{{0 * 8, 2 * 16}, "Start Position:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "Stop Position:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 13 * 16}, "Encoder Type:", Theme::getInstance()->fg_light->foreground}};
Button button_startstop{
{0, 3 * 16, 96, 24},
{8, screen_height - 48 - 16, screen_width - 2 * 8, 48},
LanguageHelper::currentMessages[LANG_START]};
Button button_input_start_position{
{8, 4 * 16, screen_width - 2 * 8, 24},
"Input Start Pos"};
Button button_input_stop_position{
{8, 9 * 16, screen_width - 2 * 8, 24},
"Input Stop Pos"};
NumberField field_start{
{0 * 8, 1 * 16},
{0 * 8, 3 * 16},
8,
{0, 2500},
1,
@@ -65,15 +79,16 @@ class OOKBruteView : public View {
true};
NumberField field_stop{
{11 * 8, 1 * 16},
{0, 8 * 16},
9,
{0, 2500},
1,
' ',
true};
// NB: when add new encoder here you should also change the char count limit for input range buttons by it's digits in DEC
OptionsField options_atkmode{
{0 * 8, 2 * 16},
{0, 14 * 16},
12,
{{"Came12", 0},
{"Came24", 1},
@@ -86,6 +101,8 @@ class OOKBruteView : public View {
uint32_t counter = 0; // for packet change
std::string text_input_buffer{}; // this is needed by the text_prompt func
void start();
void stop();
+1 -1
View File
@@ -38,7 +38,7 @@ __attribute__((section(".external_app.app_random_password.application_informatio
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Random passwd",
/*.app_name = */ "Rand Pwd",
/*.bitmap_data = */ {
0xC0,
0x03,
+1 -1
View File
@@ -57,7 +57,7 @@ __attribute__((section(".external_app.app_shoppingcart_lock.application_informat
0x00,
},
/*.icon_color = */ ui::Color::red().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.menu_location = */ app_location_t::TX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_afsk_rx */ {'P', 'A', 'T', 'X'},
@@ -225,6 +225,9 @@ ShoppingCartLock::ShoppingCartLock(NavigationView& nav)
log_event("[+] INITIALIZATION COMPLETE");
log_event("[+] PORTAPACK ARMED");
log_event("[*] STATUS: READY");
log_event("This app use speaker to");
log_event("produce LF signal, but");
log_event("also trigger radio TX");
}
ShoppingCartLock::~ShoppingCartLock() {
+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__*/
+68
View File
@@ -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__*/
+68
View File
@@ -0,0 +1,68 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui.hpp"
#include "ui_snake.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::snake {
void initialize_app(ui::NavigationView& nav) {
nav.push<SnakeView>();
}
} // namespace ui::external_app::snake
extern "C" {
__attribute__((section(".external_app.app_snake.application_information"), used)) application_information_t _application_information_snake = {
(uint8_t*)0x00000000,
ui::external_app::snake::initialize_app,
CURRENT_HEADER_VERSION,
VERSION_MD5,
"Snake",
{
0x00,
0x00,
0x00,
0x00,
0xE0,
0x09,
0x70,
0xC7,
0xFC,
0xC9,
0x06,
0x00,
0x06,
0x00,
0x0C,
0x00,
0xF0,
0x01,
0x00,
0x3E,
0x00,
0x40,
0xFC,
0x40,
0x02,
0x3F,
0x02,
0x00,
0x7C,
0x80,
0x80,
0x7F,
},
ui::Color::green().v,
app_location_t::GAMES,
-1,
{0, 0, 0, 0},
0x00000000,
};
}
+98
View File
@@ -0,0 +1,98 @@
#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,
};
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
static bool but_RIGHT;
static bool but_LEFT;
#pragma GCC diagnostic pop
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__*/
+234
View File
@@ -0,0 +1,234 @@
/*
* ------------------------------------------------------------
* | 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"
#include "random.hpp"
extern int game_state;
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
#define SNAKE_SIZE 10
#define INFO_BAR_HEIGHT 25
#define GAME_AREA_TOP (INFO_BAR_HEIGHT + 1)
#define GAME_AREA_HEIGHT (SCREEN_HEIGHT - INFO_BAR_HEIGHT - 2)
#define GRID_WIDTH ((SCREEN_WIDTH - 2) / SNAKE_SIZE)
#define GRID_HEIGHT (GAME_AREA_HEIGHT / SNAKE_SIZE)
#define STATE_MENU 0
#define STATE_PLAYING 1
#define STATE_GAME_OVER 2
#define COLOR_BACKGROUND Black
#define COLOR_SNAKE Green
#define COLOR_FOOD Red
#define COLOR_BORDER White
Ticker game_timer;
int snake_x[GRID_WIDTH * GRID_HEIGHT];
int snake_y[GRID_WIDTH * GRID_HEIGHT];
int snake_length = 1;
int snake_dx = 1, snake_dy = 0;
int food_x, food_y;
int score = 0;
int game_state = STATE_MENU;
bool initialized = false;
extern ui::Painter painter;
void init_game();
void update_game();
void draw_screen();
void draw_snake();
void draw_full_snake();
void erase_tail(int x, int y);
void draw_food();
void erase_food();
void draw_score();
void draw_borders();
void spawn_food();
bool check_collision();
void show_menu();
void show_game_over();
void game_timer_check() {
if (game_state == STATE_PLAYING) {
update_game();
}
}
void init_game() {
claim(stdout);
set_orientation(2);
set_font((unsigned char*)Arial12x12);
snake_x[0] = GRID_WIDTH / 2;
snake_y[0] = GRID_HEIGHT / 2;
snake_length = 1;
snake_dx = 1;
snake_dy = 0;
score = 0;
spawn_food();
if (game_state == STATE_MENU) {
show_menu();
} else if (game_state == STATE_PLAYING) {
draw_screen();
}
}
void spawn_food() {
bool valid;
do {
food_x = rand() % GRID_WIDTH;
food_y = rand() % GRID_HEIGHT;
valid = true;
for (int i = 0; i < snake_length; i++) {
if (snake_x[i] == food_x && snake_y[i] == food_y) {
valid = false;
break;
}
}
} while (!valid);
}
void update_game() {
int new_x = snake_x[0] + snake_dx;
int new_y = snake_y[0] + snake_dy;
bool ate_food = (new_x == food_x && new_y == food_y);
int tail_x = snake_x[snake_length - 1];
int tail_y = snake_y[snake_length - 1];
for (int i = snake_length - 1; i > 0; i--) {
snake_x[i] = snake_x[i - 1];
snake_y[i] = snake_y[i - 1];
}
snake_x[0] = new_x;
snake_y[0] = new_y;
if (ate_food) {
snake_x[snake_length] = tail_x;
snake_y[snake_length] = tail_y;
snake_length++;
score += 10;
spawn_food();
draw_food();
} else {
erase_tail(tail_x, tail_y);
}
draw_snake();
draw_score();
if (check_collision()) {
draw_borders();
game_state = STATE_GAME_OVER;
show_game_over();
return;
}
}
bool check_collision() {
if (snake_x[0] < 0 || snake_x[0] >= GRID_WIDTH || snake_y[0] < 0 || snake_y[0] >= GRID_HEIGHT) {
return true;
}
for (int i = 1; i < snake_length; i++) {
if (snake_x[0] == snake_x[i] && snake_y[0] == snake_y[i]) {
return true;
}
}
return false;
}
void draw_screen() {
cls();
background(COLOR_BACKGROUND);
draw_borders();
draw_full_snake();
draw_food();
draw_score();
}
void draw_snake() {
fillrect(1 + snake_x[0] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE,
1 + snake_x[0] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
}
void draw_full_snake() {
for (int i = 0; i < snake_length; i++) {
fillrect(1 + snake_x[i] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE,
1 + snake_x[i] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
}
}
void erase_tail(int x, int y) {
fillrect(1 + x * SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE,
1 + x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
}
void draw_food() {
fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_FOOD);
}
void erase_food() {
fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
}
void draw_score() {
auto style = *ui::Theme::getInstance()->fg_blue;
painter.draw_string({5, 5}, style, "Score: " + std::to_string(score));
}
void draw_borders() {
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
rect(0, GAME_AREA_TOP, SCREEN_WIDTH, SCREEN_HEIGHT, COLOR_BORDER);
}
void show_menu() {
cls();
background(COLOR_BACKGROUND);
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
auto style_green = *ui::Theme::getInstance()->fg_green;
auto style_blue = *ui::Theme::getInstance()->fg_blue;
painter.draw_string({50, 40}, style_yellow, "* * * SNAKE * * *");
painter.draw_string({0, 120}, style_blue, "USE THE D-PAD TO MOVE");
painter.draw_string({0, 150}, style_blue, "EAT THE RED SQUARES TO GROW");
painter.draw_string({0, 180}, style_blue, "DON'T HIT THE WALLS OR SELF");
painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **");
}
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({75, 90}, style_red, "GAME OVER");
painter.draw_string({74, 150}, style_yellow, "SCORE: " + std::to_string(score));
painter.draw_string({20, 220}, style_green, "PRESS SELECT TO RESTART");
wait(1);
}
int main() {
if (!initialized) {
initialized = true;
game_timer.attach(&game_timer_check, 1.0 / 5.0);
init_game();
}
while (1) {
if (but_SELECT && (game_state == STATE_MENU || game_state == STATE_GAME_OVER)) {
game_state = STATE_PLAYING;
init_game();
}
}
}
+71
View File
@@ -0,0 +1,71 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#include "ui_snake.hpp"
namespace ui::external_app::snake {
#include "snake.cpp"
SnakeView::SnakeView(NavigationView& nav)
: nav_{nav} {
add_children({&dummy});
game_timer.attach(&game_timer_check, 1.0 / 5.0);
}
void SnakeView::on_show() {
}
void SnakeView::paint(Painter& painter) {
(void)painter;
if (!initialized) {
initialized = true;
std::srand(LPC_RTC->CTIME0);
init_game();
}
}
void SnakeView::frame_sync() {
check_game_timer();
set_dirty();
}
bool SnakeView::on_key(const KeyEvent key) {
if (key == KeyEvent::Select) {
if (game_state == STATE_MENU || game_state == STATE_GAME_OVER) {
game_state = STATE_PLAYING;
init_game();
}
} else if (game_state == STATE_PLAYING) {
if (key == KeyEvent::Left) {
if (snake_dx == 0) {
snake_dx = -1;
snake_dy = 0;
}
} else if (key == KeyEvent::Right) {
if (snake_dx == 0) {
snake_dx = 1;
snake_dy = 0;
}
} else if (key == KeyEvent::Up) {
if (snake_dy == 0) {
snake_dx = 0;
snake_dy = -1;
}
} else if (key == KeyEvent::Down) {
if (snake_dy == 0) {
snake_dx = 0;
snake_dy = 1;
}
}
}
set_dirty();
return true;
}
} // namespace ui::external_app::snake
+48
View File
@@ -0,0 +1,48 @@
/*
* ------------------------------------------------------------
* | Made by RocketGod |
* | Find me at https://betaskynet.com |
* | Argh matey! |
* ------------------------------------------------------------
*/
#ifndef __UI_SNAKE_H__
#define __UI_SNAKE_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::snake {
class SnakeView : public View {
public:
SnakeView(NavigationView& nav);
void on_show() override;
std::string title() const override { return "Snake"; };
void focus() override { dummy.focus(); };
void paint(Painter& painter) override;
void frame_sync();
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::snake
#endif /*__UI_SNAKE_H__*/
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2025 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.
*/
#include "ui.hpp"
#include "ui_stopwatch.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::stopwatch {
void initialize_app(ui::NavigationView& nav) {
nav.push<StopwatchView>();
}
} // namespace ui::external_app::stopwatch
extern "C" {
__attribute__((section(".external_app.app_stopwatch.application_information"), used)) application_information_t _application_information_stopwatch = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::stopwatch::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "Stopwatch",
/*.bitmap_data = */ {
0x00,
0x00,
0xC0,
0x01,
0x80,
0x00,
0x80,
0x20,
0x60,
0x13,
0x10,
0x0C,
0x88,
0x08,
0x84,
0x10,
0x84,
0x10,
0xC2,
0x21,
0x84,
0x10,
0x04,
0x10,
0x08,
0x08,
0x10,
0x04,
0x60,
0x03,
0x80,
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
};
}
+101
View File
@@ -0,0 +1,101 @@
/*
* Copyright 2025 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.
*/
#include "ui_stopwatch.hpp"
#include "portapack.hpp"
#include "ch.h"
using namespace portapack;
namespace ui::external_app::stopwatch {
StopwatchView::StopwatchView(NavigationView& nav) {
add_children({
&labels,
&button_run_stop,
&button_reset_lap,
&button_done,
&big_display,
&lap_display,
});
button_run_stop.on_select = [this](Button&) {
if (running)
stop();
else
run();
};
button_reset_lap.on_select = [this](Button&) {
if (running)
lap();
else
reset();
};
button_done.on_select = [&nav](Button&) {
nav.pop();
};
}
void StopwatchView::focus() {
button_run_stop.focus();
}
void StopwatchView::run() {
running = true;
start_time = chTimeNow() - previously_elapsed;
button_run_stop.set_text("STOP");
button_reset_lap.set_text("LAP");
}
void StopwatchView::stop() {
running = false;
end_time = chTimeNow();
previously_elapsed = end_time - start_time;
button_run_stop.set_text("START");
button_reset_lap.set_text("RESET");
}
void StopwatchView::reset() {
lap_time = end_time = start_time = previously_elapsed = 0;
big_display.set(0);
lap_display.set(0);
}
void StopwatchView::lap() {
lap_time = chTimeNow();
lap_display.set((lap_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget
}
void StopwatchView::paint(Painter& painter) {
(void)painter;
if (running) {
end_time = chTimeNow();
big_display.set((end_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget
}
}
void StopwatchView::frame_sync() {
set_dirty();
}
} // namespace ui::external_app::stopwatch
@@ -0,0 +1,83 @@
/*
* Copyright 2025 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.
*/
#ifndef __UI_STOPWATCH_H__
#define __UI_STOPWATCH_H__
#include "ui_navigation.hpp"
namespace ui::external_app::stopwatch {
class StopwatchView : public View {
public:
StopwatchView(NavigationView& nav);
void focus() override;
void paint(Painter& painter) override;
void frame_sync();
std::string title() const override { return "Stopwatch"; };
private:
void run();
void stop();
void reset();
void lap();
bool running{false};
long long start_time{0};
long long end_time{0};
long long lap_time{0};
long long previously_elapsed{0};
Labels labels{
{{0 * 8, 1 * 16}, "TOTAL:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "LAP:", Theme::getInstance()->fg_light->foreground},
};
BigFrequency big_display{
{4, 2 * 16 + 4, 28 * 8, 52},
0};
BigFrequency lap_display{
{4, 8 * 16 + 4, 28 * 8, 52},
0};
Button button_run_stop{
{72, 210, 96, 24},
"START"};
Button button_reset_lap{
{72, 240, 96, 24},
"RESET"};
Button button_done{
{72, 270, 96, 24},
"EXIT"};
MessageHandlerRegistration message_handler_frame_sync{
Message::ID::DisplayFrameSync,
[this](const Message* const) {
this->frame_sync();
}};
};
} // namespace ui::external_app::stopwatch
#endif /*__UI_STOPWATCH_H__*/
+2 -2
View File
@@ -73,8 +73,8 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
0xFF,
0xF1,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::UTILITIES,
/*.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},
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2025 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui.hpp"
#include "ui_wefax_rx.hpp"
#include "ui_navigation.hpp"
#include "external_app.hpp"
namespace ui::external_app::wefax_rx {
void initialize_app(ui::NavigationView& nav) {
nav.push<WeFaxRxView>();
}
} // namespace ui::external_app::wefax_rx
extern "C" {
__attribute__((section(".external_app.app_wefax_rx.application_information"), used)) application_information_t _application_information_wefax_rx = {
/*.memory_location = */ (uint8_t*)0x00000000,
/*.externalAppEntry = */ ui::external_app::wefax_rx::initialize_app,
/*.header_version = */ CURRENT_HEADER_VERSION,
/*.app_version = */ VERSION_MD5,
/*.app_name = */ "WeFax",
/*.bitmap_data = */ {
0x00,
0x00,
0xFC,
0x07,
0x04,
0x0C,
0x04,
0x1C,
0x04,
0x3C,
0x84,
0x21,
0x84,
0x21,
0x84,
0x21,
0xF4,
0x2F,
0xF4,
0x2F,
0x84,
0x21,
0x84,
0x21,
0x84,
0x21,
0x04,
0x20,
0xFC,
0x3F,
0x00,
0x00,
},
/*.icon_color = */ ui::Color::orange().v,
/*.menu_location = */ app_location_t::RX,
/*.desired_menu_position = */ -1,
/*.m4_app_tag = portapack::spi_flash::image_tag_wefaxrx */ {'P', 'W', 'F', 'X'},
/*.m4_app_offset = */ 0x00000000, // will be filled at compile time
};
}
+152
View File
@@ -0,0 +1,152 @@
/*
* Copyright (C) 2025 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
/*
TODOS LATER:
- add load data from wav file (maybe to a separate app, not this)
- AGC?!?
- fix and enable sync detection
- auto start / stop bmp save on each image
*/
#include "ui_wefax_rx.hpp"
#include "audio.hpp"
#include "rtc_time.hpp"
#include "baseband_api.hpp"
#include "string_format.hpp"
#include "portapack_persistent_memory.hpp"
#include "lcd_ili9341.hpp"
using namespace portapack;
using namespace modems;
using namespace ui;
namespace ui::external_app::wefax_rx {
void WeFaxRxView::focus() {
field_frequency.focus();
}
WeFaxRxView::WeFaxRxView(NavigationView& nav)
: nav_{nav} {
baseband::run_prepared_image(portapack::memory::map::m4_code.base());
add_children({&rssi,
&field_rf_amp,
&field_lna,
&field_vga,
&field_volume,
&field_frequency,
&txt_status,
&labels,
&options_lpm,
&options_ioc,
&button_ss});
options_lpm.on_change = [this](size_t index, int32_t v) {
lpm_index = (uint8_t)index;
(void)v;
on_settings_changed();
};
options_ioc.on_change = [this](size_t index, int32_t v) {
ioc_index = (uint8_t)index;
(void)v;
on_settings_changed();
};
field_frequency.set_step(100);
field_frequency.on_edit_shown = [this]() {
paused = true;
};
field_frequency.on_edit_hidden = [this]() {
paused = false;
};
audio::output::start();
receiver_model.set_hidden_offset(WEFAX_FREQ_OFFSET);
receiver_model.enable();
txt_status.set("Waiting for signal.");
button_ss.on_select = [this](Button&) {
if (bmp.is_loaded()) {
bmp.close();
button_ss.set_text(LanguageHelper::currentMessages[LANG_START]);
return;
}
ensure_directory("/BMP");
bmp.create("/BMP/wefax_" + to_string_timestamp(rtc_time::now()) + ".bmp", WEFAX_PX_SIZE, 1);
button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]);
};
options_lpm.set_selected_index(lpm_index, false);
options_ioc.set_selected_index(ioc_index, true);
}
WeFaxRxView::~WeFaxRxView() {
stopping = true;
receiver_model.set_hidden_offset(0);
bmp.close();
receiver_model.disable();
baseband::shutdown();
audio::output::stop();
}
void WeFaxRxView::on_settings_changed() {
baseband::set_wefax_config(options_lpm.selected_index_value(), options_ioc.selected_index_value());
}
void WeFaxRxView::on_status(WeFaxRxStatusDataMessage msg) {
(void)msg;
std::string tmp = "";
if (msg.state == 0) {
tmp = "Waiting for signal.";
} else if (msg.state == 1) {
tmp = "Synced.";
} else if (msg.state == 2) {
tmp = "Image arriving.";
}
txt_status.set(tmp);
}
// this stores and displays the image. keep it as simple as you can. a bit more complexity will kill the sync
void WeFaxRxView::on_image(WeFaxRxImageDataMessage msg) {
if ((line_num) >= 320 - 4 * 16) line_num = 0; // for draw reset
for (uint16_t i = 0; i < msg.cnt; i += 1) {
Color pxl = {msg.image[i], msg.image[i], msg.image[i]};
bmp.write_next_px(pxl);
line_in_part++;
if (line_in_part == WEFAX_PX_SIZE) {
line_in_part = 0;
line_num++;
bmp.expand_y_delta(1);
}
uint16_t xpos = line_in_part / (WEFAX_PX_SIZE / 240);
if (xpos >= 240) xpos = 239;
line_buffer[xpos] = pxl;
if ((line_in_part == 0)) {
portapack::display.render_line({0, line_num + 4 * 16}, 240, line_buffer);
}
}
}
} // namespace ui::external_app::wefax_rx
+153
View File
@@ -0,0 +1,153 @@
/*
* Copyright (C) 2025 HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_WEFAX_RX_H__
#define __UI_WEFAX_RX_H__
#include "ui.hpp"
#include "ui_language.hpp"
#include "ui_navigation.hpp"
#include "ui_receiver.hpp"
#include "ui_geomap.hpp"
#include "ui_freq_field.hpp"
#include "ui_record_view.hpp"
#include "app_settings.hpp"
#include "radio_state.hpp"
#include "log_file.hpp"
#include "utility.hpp"
#include "ui_fileman.hpp"
#include "bmpfile.hpp"
#include "file_path.hpp"
using namespace ui;
namespace ui::external_app::wefax_rx {
#define WEFAX_PX_SIZE 840
// 1.900Hz is the usual center FM freq. , but as I added 300-Hz margin, + the truncated filter extended effect , in our case, we got better S/N if we use -2200 offset area. @Brumi
#define WEFAX_FREQ_OFFSET -2200
class WeFaxRxView : public View {
public:
WeFaxRxView(NavigationView& nav);
~WeFaxRxView();
void focus() override;
std::string title() const override { return "WeFax"; };
private:
void on_settings_changed();
void on_status(WeFaxRxStatusDataMessage msg);
void on_image(WeFaxRxImageDataMessage msg);
bool stopping = false;
uint8_t ioc_index{0};
uint8_t lpm_index{0};
uint16_t line_num = 0; // nth line
uint16_t line_in_part = 0; // got multiple parts of a line, so keep track of it
uint8_t delayer = 0;
ui::Color line_buffer[240];
std::filesystem::path filetohandle = "";
bool paused = false; // when freq field is shown for example, we need to pause
BMPFile bmp{};
NavigationView& nav_;
RxRadioState radio_state_{};
app_settings::SettingsManager settings_{
"rx_wefax",
app_settings::Mode::RX,
{
{"ioc_index"sv, &ioc_index},
{"lpm_index"sv, &lpm_index},
}};
RFAmpField field_rf_amp{
{13 * 8, 0 * 16}};
LNAGainField field_lna{
{15 * 8, 0 * 16}};
VGAGainField field_vga{
{18 * 8, 0 * 16}};
RSSI rssi{
{21 * 8, 0, 6 * 8, 4}};
AudioVolumeField field_volume{
{28 * 8, 0 * 16}};
RxFrequencyField field_frequency{
{0 * 8, 0 * 16},
nav_};
Labels labels{
{{1 * 8, 1 * 16}, "LPM:", Theme::getInstance()->fg_light->foreground},
{{13 * 8, 1 * 16}, "IOC:", Theme::getInstance()->fg_light->foreground},
};
OptionsField options_lpm{
{6 * 8, 1 * 16},
4,
{
{"60", 60},
{"90", 90},
{"100", 100},
{"120", 120},
{"180", 180},
{"240", 240},
}};
OptionsField options_ioc{
{18 * 8, 1 * 16},
4,
{
{"576", 0},
{"228", 1},
}};
Text txt_status{
{0 * 8, 2 * 16, 20 * 8, 16},
};
Button button_ss{
{190, 2 * 16, 5 * 8, 16},
LanguageHelper::currentMessages[LANG_START]};
MessageHandlerRegistration message_handler_stats{
Message::ID::WeFaxRxStatusData,
[this](const Message* const p) {
if (stopping || paused) return;
const auto message = *reinterpret_cast<const WeFaxRxStatusDataMessage*>(p);
on_status(message);
}};
MessageHandlerRegistration message_handler_image{
Message::ID::WeFaxRxImageData,
[this](const Message* const p) {
if (stopping || paused) return;
const auto message = *reinterpret_cast<const WeFaxRxImageDataMessage*>(p);
on_image(message);
}};
};
} // namespace ui::external_app::wefax_rx
#endif /*__UI_WEFAX_RX_H__*/
+2 -2
View File
@@ -41,8 +41,8 @@ enum freqman_entry_modulation : uint8_t {
AM_MODULATION = 0,
NFM_MODULATION,
WFM_MODULATION,
AMFM_MODULATION, // Added for Wefax.
SPEC_MODULATION
SPEC_MODULATION,
AMFM_MODULATION // Added for Wefax.
};
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
+7 -5
View File
@@ -49,6 +49,7 @@ options_t freqman_modulations = {
{"NFM", 1},
{"WFM", 2},
{"SPEC", 3},
{"AMFM", 4},
};
options_t freqman_bandwidths[5] = {
@@ -72,10 +73,6 @@ options_t freqman_bandwidths[5] = {
{"180k", 1},
{"200k", 0},
},
{
// AMFM for Wefax-
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (Wfax) tones.
},
{
// SPEC -- TODO: these should be indexes.
{"12k5", 12500},
@@ -103,7 +100,12 @@ options_t freqman_bandwidths[5] = {
{"4500k", 4500000},
{"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.
options_t freqman_steps = {
+3 -3
View File
@@ -84,7 +84,7 @@ bool Debounce::feed(const uint8_t bit) {
// (by toggling reported state every 1/2 of the delay time)
if (--repeat_ctr_ == 0) {
state_to_report_ = !state_to_report_;
repeat_ctr_ = REPEAT_SUBSEQUENT_DELAY / 2;
repeat_ctr_ = portapack::persistent_memory::ui_button_repeat_speed() ? REPEAT_SUBSEQUENT_DELAY_FAST / 2 : REPEAT_SUBSEQUENT_DELAY_NORMAL / 2;
return true;
}
}
@@ -96,7 +96,7 @@ bool Debounce::feed(const uint8_t bit) {
// if LONG_PRESS_DELAY is reached then finally report that switch is pressed and set flag
// indicating it was a LONG press
// (note that repeat_support and long_press support are mutually exclusive)
if (held_time_ >= LONG_PRESS_DELAY) {
if (held_time_ >= (portapack::persistent_memory::ui_button_long_press_delay() ? LONG_PRESS_DELAY_FAST : LONG_PRESS_DELAY_NORMAL)) {
pulse_upon_release_ = false;
long_press_occurred_ = true;
state_to_report_ = 1;
@@ -104,7 +104,7 @@ bool Debounce::feed(const uint8_t bit) {
}
} else if (repeat_enabled_ && !long_press_enabled_) {
// Repeat support -- 4 directional buttons only (unless long_press is enabled)
if (held_time_ >= REPEAT_INITIAL_DELAY) {
if (held_time_ >= (portapack::persistent_memory::ui_button_repeat_delay() ? REPEAT_INITIAL_DELAY_FAST : REPEAT_INITIAL_DELAY_NORMAL)) {
// Delay reached; trigger repeat code on NEXT tick
repeat_ctr_ = 1;
held_time_ = 0;
+7 -3
View File
@@ -30,9 +30,13 @@
#define DEBOUNCE_MASK ((1 << DEBOUNCE_COUNT) - 1)
// # of timer0 ticks before a held button starts being counted as repeated presses
#define REPEAT_INITIAL_DELAY 250
#define REPEAT_SUBSEQUENT_DELAY 92
#define LONG_PRESS_DELAY 800
#define REPEAT_INITIAL_DELAY_NORMAL 250
#define REPEAT_SUBSEQUENT_DELAY_NORMAL 92
#define LONG_PRESS_DELAY_NORMAL 800
#define REPEAT_INITIAL_DELAY_FAST 188
#define REPEAT_SUBSEQUENT_DELAY_FAST 66
#define LONG_PRESS_DELAY_FAST 555
class Debounce {
public:
+6 -1
View File
@@ -279,7 +279,12 @@ int32_t ReceiverModel::tuning_offset() {
void ReceiverModel::update_tuning_frequency() {
// TODO: use positive offset if freq < offset.
radio::set_tuning_frequency(target_frequency() + tuning_offset());
radio::set_tuning_frequency(target_frequency() + hidden_offset + tuning_offset());
}
void ReceiverModel::set_hidden_offset(rf::Frequency offset) {
hidden_offset = offset;
update_tuning_frequency();
}
void ReceiverModel::update_baseband_bandwidth() {
+6 -3
View File
@@ -40,9 +40,9 @@ class ReceiverModel {
AMAudio = 0,
NarrowbandFMAudio = 1,
WidebandFMAudio = 2,
AMAudioFMApt = 3, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
SpectrumAnalysis = 4,
Capture = 5
SpectrumAnalysis = 3,
AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
Capture = 5,
};
struct settings_t {
@@ -110,6 +110,8 @@ class ReceiverModel {
uint8_t normalized_headphone_volume() const;
void set_normalized_headphone_volume(uint8_t v);
void set_hidden_offset(rf::Frequency offset);
void enable();
void disable();
@@ -133,6 +135,7 @@ class ReceiverModel {
private:
settings_t settings_{};
bool enabled_ = false;
rf::Frequency hidden_offset = 0; // when we need to hide the offset from user, we set this. like when WeFax needs -300Hz.
int32_t tuning_offset();
@@ -53,13 +53,21 @@ class BoundFrequencyField : public FrequencyField {
};
on_edit = [this, &nav]() {
if (on_edit_shown)
on_edit_shown();
auto freq_view = nav.push<FrequencyKeypadView>(model->target_frequency());
freq_view->on_changed = [this](rf::Frequency f) {
set_value(f);
};
nav.set_on_pop([this]() {
if (on_edit_hidden)
on_edit_hidden();
});
};
}
std::function<void(void)> on_edit_shown{}; // fired, when the FrequencyKeypadView pops up
std::function<void(void)> on_edit_hidden{}; // fired, when the FrequencyKeypadView ended
// TODO: override set_step and update the rx model then call base.
};
+18 -1
View File
@@ -126,8 +126,8 @@ const NavigationView::AppList NavigationView::appList = {
{"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::cyan(), &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 ********************************************************************/
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
@@ -165,6 +165,7 @@ const NavigationView::AppList NavigationView::appList = {
{"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>()},
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
// Dangerous apps.
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
@@ -868,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) {
+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);
+8
View File
@@ -668,6 +668,14 @@ DeclareTargets(POSK ookstream)
### WeFax Rx
set(MODE_CPPSRC
proc_wefaxrx.cpp
)
DeclareTargets(PWFX wefaxrx)
### HackRF "factory" firmware
add_custom_command(
+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); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except WFAX with "lpf-coef".
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);
+1 -1
View File
@@ -79,7 +79,7 @@ static inline float angle_precise(const complex32_t t) {
return atan2f(t.imag(), t.real());
}
buffer_f32_t SSB_FM::execute( // Added to handle WFAX (HF weather map )-
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.
+2 -2
View File
@@ -48,7 +48,7 @@ class SSB {
static constexpr float k = 1.0f / 32768.0f;
};
class SSB_FM { // Added to handle WFAX-
class SSB_FM { // Added to handle AMFM for WFAX
public:
buffer_f32_t execute(
const buffer_c16_t& src,
@@ -56,7 +56,7 @@ class SSB_FM { // Added to handle WFAX-
private:
static constexpr float k = 1.0f / 32768.0f;
dsp::Real_to_Complex real_to_complex; // It is a member variable of SSB_FM.
dsp::Real_to_Complex real_to_complex{}; // It is a member variable of SSB_FM.
};
class FM {
+9 -2
View File
@@ -65,7 +65,9 @@ buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) {
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;
}
}
@@ -110,9 +112,14 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
channel_filter_low_f = message.channel_filter.low_frequency_normalized * channel_filter_input_fs;
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); // 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}
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}
if (modulation_ssb == (int)(AMConfigureMessage::Modulation::SSB_FM)) {
channel_spectrum.set_decimation_factor(6.0f); // zooming for better tuning {SSB_FM = 2}
} else {
channel_spectrum.set_decimation_factor(1.0f); // no zooming .{DSB = 0, SSB = 1,}
}
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;
+186
View File
@@ -0,0 +1,186 @@
/*
* Copyright (C) 2025 Brumi, HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "proc_wefaxrx.hpp"
#include "sine_table_int8.hpp"
#include "portapack_shared_memory.hpp"
#include "audio_dma.hpp"
#include "math.h"
#include "event_m4.hpp"
#include "fxpt_atan2.hpp"
#include <cstdint>
#include <cstddef>
#define STARTSIGNAL_TH 0.33
#define STARTSIGNAL_NEEDCNT 110
#define STARTSIGNAL_MAXBAD 20
#define WEFAX_PX_SIZE 840.0
// updates the per pixel timers
void WeFaxRx::update_params() {
switch (ioc_mode) {
case 1:
freq_start_tone = 675;
break;
default:
case 0:
freq_start_tone = 300;
break;
}
// 840 px / line with line start
pxRem = (double)channel_filter_input_fs / ((lpm / 60.0) * WEFAX_PX_SIZE);
samples_per_pixel = pxRem;
pxRem -= samples_per_pixel;
pxRoll = 0;
status_message.state = 0;
shared_memory.application_queue.push(status_message);
}
void WeFaxRx::execute(const buffer_c8_t& buffer) {
// This is called at 3072000 / 2048 = 1500Hz
if (!configured) return;
const auto decim_0_out = decim_0.execute(buffer, dst_buffer);
const auto decim_1_out = decim_1.execute(decim_0_out, dst_buffer);
channel_spectrum.feed(decim_1_out, channel_filter_low_f, channel_filter_high_f, channel_filter_transition);
const auto decim_2_out = decim_2.execute(decim_1_out, dst_buffer);
const auto channel_out = channel_filter.execute(decim_2_out, dst_buffer);
feed_channel_stats(channel_out);
auto audio = demod_ssb_fm.execute(channel_out, audio_buffer);
audio_compressor.execute_in_place(audio);
audio_output.write(audio);
for (size_t c = 0; c < audio.count; c++) {
if (status_message.state == 0 && false) { // disabled this due to so sensitive to noise
// first look for the sync!
if (audio.p[c] <= STARTSIGNAL_TH && audio.p[c] >= 0.0001) {
sync_cnt++;
if (sync_cnt >= STARTSIGNAL_NEEDCNT) {
status_message.state = 1;
shared_memory.application_queue.push(status_message);
sync_cnt = 0;
syncnot_cnt = 0;
}
} else {
syncnot_cnt++;
if (syncnot_cnt >= STARTSIGNAL_MAXBAD) {
sync_cnt = 0;
syncnot_cnt = 0;
}
}
} else {
cnt++;
if (cnt >= (samples_per_pixel + (uint32_t)pxRoll)) { // got a pixel
cnt = 0;
if (pxRoll >= 1) pxRoll -= 1.0;
pxRoll += pxRem;
if (image_message.cnt < 400) {
if (audio.p[c] >= 0.68) {
image_message.image[image_message.cnt++] = 255;
} else if (audio.p[c] >= 0.45) {
image_message.image[image_message.cnt++] = (uint8_t)(((audio.p[c] - 0.45f) * 1108));
} else {
image_message.image[image_message.cnt++] = 0;
}
}
if (image_message.cnt >= 399) {
shared_memory.application_queue.push(image_message);
image_message.cnt = 0;
if (status_message.state != 2) {
status_message.state = 2;
shared_memory.application_queue.push(status_message);
}
}
}
}
}
}
void WeFaxRx::on_message(const Message* const message) {
switch (message->id) {
case Message::ID::UpdateSpectrum:
case Message::ID::SpectrumStreamingConfig:
channel_spectrum.on_message(message);
break;
case Message::ID::WeFaxRxConfigure:
configure(*reinterpret_cast<const WeFaxRxConfigureMessage*>(message));
break;
case Message::ID::CaptureConfig:
capture_config(*reinterpret_cast<const CaptureConfigMessage*>(message));
break;
default:
break;
}
}
void WeFaxRx::configure(const WeFaxRxConfigureMessage& message) {
constexpr size_t decim_0_input_fs = baseband_fs;
constexpr size_t decim_0_output_fs = decim_0_input_fs / decim_0.decimation_factor;
constexpr size_t decim_1_input_fs = decim_0_output_fs;
constexpr size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor;
constexpr size_t decim_2_input_fs = decim_1_output_fs;
constexpr size_t decim_2_output_fs = decim_2_input_fs / decim_2_decimation_factor;
channel_filter_input_fs = decim_2_output_fs;
// const size_t channel_filter_output_fs = channel_filter_input_fs / channel_filter_decimation_factor;
decim_0.configure(taps_6k0_decim_0.taps);
decim_1.configure(taps_6k0_decim_1.taps);
decim_2.configure(taps_6k0_decim_2.taps, decim_2_decimation_factor);
channel_filter.configure(taps_2k6_usb_wefax_channel.taps, channel_filter_decimation_factor);
channel_filter_low_f = taps_2k6_usb_wefax_channel.low_frequency_normalized * channel_filter_input_fs;
channel_filter_high_f = taps_2k6_usb_wefax_channel.high_frequency_normalized * channel_filter_input_fs;
channel_filter_transition = taps_2k6_usb_wefax_channel.transition_normalized * channel_filter_input_fs;
channel_spectrum.set_decimation_factor(1.0f);
audio_output.configure(audio_12k_lpf_1500hz_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
lpm = message.lpm;
ioc_mode = message.ioc;
update_params();
configured = true;
}
void WeFaxRx::capture_config(const CaptureConfigMessage& message) {
if (message.config) {
audio_output.set_stream(std::make_unique<StreamInput>(message.config));
} else {
audio_output.set_stream(nullptr);
}
}
int main() {
audio::dma::init_audio_out();
EventDispatcher event_dispatcher{std::make_unique<WeFaxRx>()};
event_dispatcher.run();
return 0;
}
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2025 Brumi, HTotoo
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __PROC_WEFAXRX_H__
#define __PROC_WEFAXRX_H__
#include "baseband_processor.hpp"
#include "baseband_thread.hpp"
#include "rssi_thread.hpp"
#include "dsp_decimate.hpp"
#include "dsp_demodulate.hpp"
#include "dsp_iir.hpp"
#include "audio_compressor.hpp"
#include "audio_output.hpp"
#include "spectrum_collector.hpp"
#include <cstdint>
class WeFaxRx : public BasebandProcessor {
public:
void execute(const buffer_c8_t& buffer) override;
void on_message(const Message* const message) override;
private:
void update_params();
// todo rethink
uint8_t lpm = 120; // 60, 90, 100, 120, 180, 240 lpm
uint8_t ioc_mode = 0; // 0 - ioc576, 1 - ioc 288, 2 - colour fax
uint32_t samples_per_pixel = 0;
// not yet used:
uint32_t time_start_tone = 3000; // 3s - 5s
uint32_t freq_start_tone = 300; // 300hz for ioc576 675hz for ioc288, 200hz for colour fax
uint32_t freq_stop_tone = 450; // 450hz for the 3-5s stop tone
// to exactly match the pixel / samples.
double pxRem = 0; // if has remainder, it'll store it
double pxRoll = 0; // summs remainders, so won't misalign
uint32_t cnt = 0; // signal counter
uint16_t sync_cnt = 0;
uint16_t syncnot_cnt = 0;
static constexpr size_t baseband_fs = 3072000;
static constexpr size_t decim_2_decimation_factor = 4;
static constexpr size_t channel_filter_decimation_factor = 1;
std::array<complex16_t, 512> dst{};
const buffer_c16_t dst_buffer{
dst.data(),
dst.size()};
std::array<float, 32> audio{};
const buffer_f32_t audio_buffer{
audio.data(),
audio.size()};
size_t channel_filter_input_fs = 0;
dsp::decimate::FIRC8xR16x24FS4Decim8 decim_0{};
dsp::decimate::FIRC16xR16x32Decim8 decim_1{};
dsp::decimate::FIRAndDecimateComplex decim_2{};
dsp::decimate::FIRAndDecimateComplex channel_filter{};
int32_t channel_filter_low_f = 0;
int32_t channel_filter_high_f = 0;
int32_t channel_filter_transition = 0;
bool configured{false};
dsp::demodulate::SSB_FM demod_ssb_fm{}; // added for Wfax mode.
FeedForwardCompressor audio_compressor{};
AudioOutput audio_output{};
SpectrumCollector channel_spectrum{};
void configure(const WeFaxRxConfigureMessage& message);
void capture_config(const CaptureConfigMessage& message);
WeFaxRxStatusDataMessage status_message{0};
WeFaxRxImageDataMessage image_message{};
/* NB: Threads should be the last members in the class definition. */
BasebandThread baseband_thread{baseband_fs, this, baseband::Direction::Receive};
RSSIThread rssi_thread{};
};
#endif /*__PROC_WEFAXRX_H__*/
+1 -1
View File
@@ -282,4 +282,4 @@ bool BMPFile::expand_y(uint32_t new_y) {
seek(0, curry + 1); // seek to the begin of the new chunk
}
return true;
}
}
+2 -2
View File
@@ -34,7 +34,7 @@ constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
{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 WFAX demod.
// 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},
@@ -43,7 +43,7 @@ constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = {
{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 WFAX demod.
// 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},
+32
View File
@@ -127,6 +127,10 @@ class Message {
FreqChangeCommand = 70,
I2CDevListChanged = 71,
LightData = 72,
WeFaxRxConfigure = 73,
WeFaxRxStatusData = 74,
WeFaxRxImageData = 75,
MAX
};
@@ -1438,4 +1442,32 @@ class I2CDevListChangedMessage : public Message {
: Message{ID::I2CDevListChanged} {}
};
class WeFaxRxConfigureMessage : public Message {
public:
constexpr WeFaxRxConfigureMessage(uint8_t lpm, uint8_t ioc)
: Message{ID::WeFaxRxConfigure},
lpm{lpm},
ioc{ioc} {
}
uint8_t lpm = 120;
uint8_t ioc = 0;
};
class WeFaxRxStatusDataMessage : public Message {
public:
constexpr WeFaxRxStatusDataMessage(uint8_t state)
: Message{ID::WeFaxRxStatusData},
state{state} {
}
uint8_t state = 0;
};
class WeFaxRxImageDataMessage : public Message {
public:
constexpr WeFaxRxImageDataMessage()
: Message{ID::WeFaxRxImageData} {}
uint8_t image[400]{0};
uint32_t cnt = 0;
};
#endif /*__MESSAGE_H__*/
+28 -13
View File
@@ -103,7 +103,7 @@ struct ui_config_t {
uint8_t show_gui_return_icon : 1;
uint8_t load_app_settings : 1;
uint8_t save_app_settings : 1;
uint8_t show_large_qr_code : 1;
uint8_t UNUSED_7 : 1; // Deprecated, but bit can be set by older firmware
bool disable_touchscreen : 1;
bool hide_clock : 1;
@@ -134,9 +134,9 @@ struct ui_config2_t {
bool hide_numeric_battery : 1;
bool hide_battery_icon : 1;
bool override_batt_calc : 1;
bool UNUSED_4 : 1;
bool UNUSED_5 : 1;
bool UNUSED_6 : 1;
bool button_repeat_delay : 1;
bool button_repeat_speed : 1;
bool button_long_press_delay : 1;
uint8_t theme_id;
uint8_t PLACEHOLDER_3;
@@ -603,10 +603,6 @@ bool show_gui_return_icon() { // add return icon in touchscreen menu
return data->ui_config.show_gui_return_icon != 0;
}
bool show_bigger_qr_code() { // show bigger QR code
return data->ui_config.show_large_qr_code != 0;
}
bool disable_touchscreen() { // Option to disable touch screen
return data->ui_config.disable_touchscreen;
}
@@ -684,10 +680,6 @@ void set_save_app_settings(bool v) {
data->ui_config.save_app_settings = v ? 1 : 0;
}
void set_show_bigger_qr_code(bool v) {
data->ui_config.show_large_qr_code = v ? 1 : 0;
}
void set_disable_touchscreen(bool v) {
data->ui_config.disable_touchscreen = v;
}
@@ -963,6 +955,15 @@ uint8_t ui_theme_id() {
bool ui_override_batt_calc() {
return data->ui_config2.override_batt_calc;
}
bool ui_button_repeat_delay() {
return data->ui_config2.button_repeat_delay;
}
bool ui_button_repeat_speed() {
return data->ui_config2.button_repeat_speed;
}
bool ui_button_long_press_delay() {
return data->ui_config2.button_long_press_delay;
}
void set_ui_hide_speaker(bool v) {
data->ui_config2.hide_speaker = v;
@@ -1008,6 +1009,15 @@ void set_ui_theme_id(uint8_t theme_id) {
void set_ui_override_batt_calc(bool v) {
data->ui_config2.override_batt_calc = v;
}
void set_ui_button_repeat_delay(bool v) {
data->ui_config2.button_repeat_delay = v;
}
void set_ui_button_repeat_speed(bool v) {
data->ui_config2.button_repeat_speed = v;
}
void set_ui_button_long_press_delay(bool v) {
data->ui_config2.button_long_press_delay = v;
}
/* Converter */
bool config_converter() {
@@ -1270,7 +1280,6 @@ bool debug_dump() {
pmem_dump_file.write_line("ui_config show_gui_return_icon: " + to_string_dec_uint(data->ui_config.show_gui_return_icon));
pmem_dump_file.write_line("ui_config load_app_settings: " + to_string_dec_uint(data->ui_config.load_app_settings));
pmem_dump_file.write_line("ui_config save_app_settings: " + to_string_dec_uint(data->ui_config.save_app_settings));
pmem_dump_file.write_line("ui_config show_bigger_qr_code: " + to_string_dec_uint(data->ui_config.show_large_qr_code));
pmem_dump_file.write_line("ui_config disable_touchscreen: " + to_string_dec_uint(data->ui_config.disable_touchscreen));
pmem_dump_file.write_line("ui_config hide_clock: " + to_string_dec_uint(data->ui_config.hide_clock));
pmem_dump_file.write_line("ui_config clock_with_date: " + to_string_dec_uint(data->ui_config.clock_show_date));
@@ -1295,6 +1304,9 @@ bool debug_dump() {
pmem_dump_file.write_line("ui_config2 hide_numeric_battery: " + to_string_dec_uint(data->ui_config2.hide_numeric_battery));
pmem_dump_file.write_line("ui_config2 theme_id: " + to_string_dec_uint(data->ui_config2.theme_id));
pmem_dump_file.write_line("ui_config2 override_batt_calc: " + to_string_dec_uint(data->ui_config2.override_batt_calc));
pmem_dump_file.write_line("ui_config2 button_repeat_delay: " + to_string_dec_uint(data->ui_config2.button_repeat_delay));
pmem_dump_file.write_line("ui_config2 button_repeat_speed: " + to_string_dec_uint(data->ui_config2.button_repeat_speed));
pmem_dump_file.write_line("ui_config2 button_long_press_delay: " + to_string_dec_uint(data->ui_config2.button_long_press_delay));
// misc_config bits
pmem_dump_file.write_line("misc_config config_audio_mute: " + to_string_dec_int(config_audio_mute()));
@@ -1329,6 +1341,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;
@@ -205,7 +205,6 @@ bool config_converter();
bool config_updown_converter();
int64_t config_converter_freq();
bool show_gui_return_icon();
bool show_bigger_qr_code();
bool hide_clock();
bool clock_with_date();
bool config_login();
@@ -217,7 +216,6 @@ bool disable_touchscreen();
void set_gui_return_icon(bool v);
void set_load_app_settings(bool v);
void set_save_app_settings(bool v);
void set_show_bigger_qr_code(bool v);
void set_config_disable_external_tcxo(bool v);
void set_config_sdcard_high_speed_io(bool v, bool save);
void set_config_disable_config_mode(bool v);
@@ -342,6 +340,9 @@ bool ui_hide_battery_icon();
bool ui_hide_sd_card();
uint8_t ui_theme_id();
bool ui_override_batt_calc();
bool ui_button_repeat_delay();
bool ui_button_repeat_speed();
bool ui_button_long_press_delay();
void set_ui_hide_speaker(bool v);
void set_ui_hide_mute(bool v);
void set_ui_hide_converter(bool v);
@@ -356,6 +357,9 @@ void set_ui_hide_battery_icon(bool v);
void set_ui_hide_sd_card(bool v);
void set_ui_theme_id(uint8_t v);
void set_ui_override_batt_calc(bool v);
void set_ui_button_repeat_delay(bool v);
void set_ui_button_repeat_speed(bool v);
void set_ui_button_long_press_delay(bool v);
// sd persisting settings
bool should_use_sdcard_for_pmem();
+1
View File
@@ -118,6 +118,7 @@ constexpr image_tag_t image_tag_usb_sd{'P', 'U', 'S', 'B'};
constexpr image_tag_t image_tag_weather{'P', 'W', 'T', 'H'};
constexpr image_tag_t image_tag_subghzd{'P', 'S', 'G', 'D'};
constexpr image_tag_t image_tag_protoview{'P', 'P', 'V', 'W'};
constexpr image_tag_t image_tag_wefaxrx{'P', 'W', 'F', 'X'};
constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'};
+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 {
+53 -48
View File
@@ -528,58 +528,63 @@ void BigFrequency::paint(Painter& painter) {
Point digit_pos;
ui::Color segment_color;
const auto rect = screen_rect();
if (_frequency != _previous_frequency) {
_previous_frequency = _frequency;
// Erase
painter.fill_rectangle(
{{0, rect.location().y()}, {screen_width, 52}},
Theme::getInstance()->bg_darkest->background);
rf::Frequency frequency{_frequency};
const auto rect = screen_rect();
// Prepare digits
if (!_frequency) {
digits.fill(10); // ----.---
digit_pos = {0, rect.location().y()};
} else {
_frequency /= 1000; // GMMM.KKK(uuu)
// Erase
painter.fill_rectangle(
{{0, rect.location().y()}, {screen_width, 52}},
Theme::getInstance()->bg_darkest->background);
for (i = 0; i < 7; i++) {
digits[6 - i] = _frequency % 10;
_frequency /= 10;
}
// Remove leading zeros
for (i = 0; i < 3; i++) {
if (!digits[i])
digits[i] = 16; // "Don't draw" code
else
break;
}
digit_pos = {(Coord)(240 - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
}
segment_color = style().foreground;
// Draw
for (i = 0; i < 7; i++) {
digit = digits[i];
if (digit < 16) {
digit_def = segment_font[(uint8_t)digit];
for (size_t s = 0; s < 7; s++) {
if (digit_def & 1)
painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color);
digit_def >>= 1;
}
}
if (i == 3) {
// Dot
painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color);
digit_pos += {(digit_width + 8), 0};
// Prepare digits
if (!frequency) {
digits.fill(10); // ----.---
digit_pos = {0, rect.location().y()};
} else {
digit_pos += {digit_width, 0};
frequency /= 1000; // GMMM.KKK(uuu)
for (i = 0; i < 7; i++) {
digits[6 - i] = frequency % 10;
frequency /= 10;
}
// Remove leading zeros
for (i = 0; i < 3; i++) {
if (!digits[i])
digits[i] = 16; // "Don't draw" code
else
break;
}
digit_pos = {(Coord)(240 - ((7 * digit_width) + 8) - (i * digit_width)) / 2, rect.location().y()};
}
segment_color = style().foreground;
// Draw
for (i = 0; i < 7; i++) {
digit = digits[i];
if (digit < 16) {
digit_def = segment_font[(uint8_t)digit];
for (size_t s = 0; s < 7; s++) {
if (digit_def & 1)
painter.fill_rectangle({digit_pos + segments[s].location(), segments[s].size()}, segment_color);
digit_def >>= 1;
}
}
if (i == 3) {
// Dot
painter.fill_rectangle({digit_pos + Point(34, 48), {4, 4}}, segment_color);
digit_pos += {(digit_width + 8), 0};
} else {
digit_pos += {digit_width, 0};
}
}
}
}
+1
View File
@@ -291,6 +291,7 @@ class BigFrequency : public Widget {
private:
rf::Frequency _frequency;
rf::Frequency _previous_frequency{~0LL};
static constexpr Dim digit_width = 32;
Binary file not shown.

After

Width:  |  Height:  |  Size: 137 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 B

After

Width:  |  Height:  |  Size: 215 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 B

+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,
@@ -0,0 +1,129 @@
#!/usr/bin/env python3
#
# Copyleft (ɔ) 2024 Whiterose from the Dark Army
#
# 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 sys
from pathlib import Path
def parse_memory_regions(ld_file_path):
regions = []
with open(ld_file_path, 'r') as f:
content = f.read()
memory_section = re.search(r'MEMORY\s*\{(.*?)\}', content, re.DOTALL)
if not memory_section:
print("ERROR: Could not find MEMORY section in the linker script.")
return []
memory_content = memory_section.group(1)
pattern = r'ram_external_app_(\w+)\s*\(rwx\)\s*:\s*org\s*=\s*(0x[A-Fa-f0-9]+),\s*len\s*=\s*(\d+)k'
matches = re.finditer(pattern, memory_content)
for match in matches:
app_name = match.group(1)
address = int(match.group(2), 16) # string with hex -> int
length = int(match.group(3)) * 1024 # kb -> bytes
regions.append({
'app_name': app_name,
'address': address,
'length': length
})
return sorted(regions, key=lambda x: x['address'])
def validate_memory_regions(regions):
if not regions:
return False
expected_step = 0x10000 # 64k as step (not sure why)
expected_base = 0xADB10000 # the start (not sure why this one)
expected_size = 32 * 1024 # 32k
issues_found = False
print("\n")
print(f"checking {len(regions)} external apps address memory regions")
if regions[0]['address'] != expected_base:
print(f"WARNING: external app first region should start at {hex(expected_base)}, but starts at {hex(regions[0]['address'])}")
issues_found = True
for i, region in enumerate(regions):
expected_address = expected_base + (i * expected_step)
# address count
if region['address'] != expected_address:
print(f"WARNING: external app region '{region['app_name']}' has incorrect address")
print(f"want: {hex(expected_address)}, Found: {hex(region['address'])}")
issues_found = True
# size
if region['length'] != expected_size:
print(f"WARNING: external app region '{region['app_name']}' has incorrect size")
print(f"want: {expected_size//1024}KB, Found: {region['length']//1024}KB")
issues_found = True
# overlap
if i < len(regions) - 1:
next_region = regions[i + 1]
if region['address'] + region['length'] > next_region['address']:
print(f"WARNING: external app region '{region['app_name']}' overlapped with '{next_region['app_name']}'")
issues_found = True
return not issues_found
def main():
if len(sys.argv) > 1:
# arg
input_path = Path(sys.argv[1])
if input_path.is_dir():
ld_file_path = input_path / "external.ld"
if not ld_file_path.exists():
print(f"some issue causing that we can't see external app's linker script {input_path}, pass.")
return
else:
ld_file_path = input_path
else:
# if no arg, this assume this script is in mayhemrepo/firmware/tools
ld_file_path = Path("..") / "application" / "external" / "external.ld"
try:
regions = parse_memory_regions(ld_file_path)
if not regions:
print("some issue causing that we can't see external app's linker script's address list, pass")
return
if validate_memory_regions(regions):
print("external app addr seems correct, pass")
else:
print("\nWARENING: It seems you are having incorrect external app addresses.")
except Exception as e:
print(f"err: {e}")
if __name__ == "__main__":
main()
+104
View File
@@ -30,6 +30,9 @@ from external_app_info import external_apps_address_start
from external_app_info import external_apps_address_end
import subprocess
import re
from pathlib import Path
usage_message = """
PortaPack SPI flash image generator
@@ -50,6 +53,80 @@ def write_image(data, path):
f.write(data)
f.close()
########external app linker script address check########
def parse_memory_regions(ld_file_path):
regions = []
with open(ld_file_path, 'r') as f:
content = f.read()
memory_section = re.search(r'MEMORY\s*\{(.*?)\}', content, re.DOTALL)
if not memory_section:
print("ERROR: Could not find MEMORY section in the linker script.")
return []
memory_content = memory_section.group(1)
pattern = r'ram_external_app_(\w+)\s*\(rwx\)\s*:\s*org\s*=\s*(0x[A-Fa-f0-9]+),\s*len\s*=\s*(\d+)k'
matches = re.finditer(pattern, memory_content)
for match in matches:
app_name = match.group(1)
address = int(match.group(2), 16) # string with hex -> int
length = int(match.group(3)) * 1024 # kb -> bytes
regions.append({
'app_name': app_name,
'address': address,
'length': length
})
return sorted(regions, key=lambda x: x['address'])
def validate_memory_regions(regions):
if not regions:
return False
expected_step = 0x10000 # 64k as step (not sure why)
expected_base = 0xADB10000 # the start (not sure why this one)
expected_size = 32 * 1024 # 32k
issues_found = False
print("\n")
print(f"checking {len(regions)} external apps address memory regions")
if regions[0]['address'] != expected_base:
print(f"WARNING: external app first region should start at {hex(expected_base)}, but starts at {hex(regions[0]['address'])}")
issues_found = True
for i, region in enumerate(regions):
expected_address = expected_base + (i * expected_step)
# address count
if region['address'] != expected_address:
print(f"WARNING: external app region '{region['app_name']}' has incorrect address")
print(f"want: {hex(expected_address)}, Found: {hex(region['address'])}")
issues_found = True
# size
if region['length'] != expected_size:
print(f"WARNING: external app region '{region['app_name']}' has incorrect size")
print(f"want: {expected_size//1024}KB, Found: {region['length']//1024}KB")
issues_found = True
# overlap
if i < len(regions) - 1:
next_region = regions[i + 1]
if region['address'] + region['length'] > next_region['address']:
print(f"WARNING: external app region '{region['app_name']}' overlapped with '{next_region['app_name']}'")
issues_found = True
return not issues_found
#^^^^^^^^external app linker script address check^^^^^^^^
########gcc version check from elf file########
def get_gcc_version_from_elf(elf_file):
succeed = False
@@ -87,6 +164,7 @@ def get_gcc_version_from_elf_files_in_giving_path_or_filename_s_path(path):
gcc_versions.append("gcc version of " + extract_elf_file_name + " is " + version_info)
return gcc_versions
#^^^^^^^^gcc version check from elf file^^^^^^^^
if len(sys.argv) != 4:
print(usage_message)
@@ -107,6 +185,28 @@ for itbb in baseband_gcc_versions:
print(itbb)
print("\n")
########external app linker script address check worker########
ld_file_path = Path("..") / ".." / "firmware" / "application" / "external" / "external.ld"
try:
regions = parse_memory_regions(ld_file_path)
if not regions:
print("some issue causing that we can't see external app's linker script's address list, pass")
if validate_memory_regions(regions):
print("external app addr seems correct, pass")
else:
print("\nWARENING: It seems you are having incorrect external app addresses.")
except Exception as e:
print(f"err: {e}")
#^^^^^^^^external app linker script address check worker^^^^^^^^
spi_size = 1048576
images = (
@@ -125,6 +225,8 @@ images = (
spi_image = bytearray()
spi_image_default_byte = bytearray((255,))
#########check if the fw size ok and check external addr leak#########
for image in images:
if len(image['data']) > image['size']:
raise RuntimeError(
@@ -163,6 +265,8 @@ percent_remaining = round(1000 * pad_size / spi_size) / 10;
print("Space remaining in flash ROM:", pad_size, "bytes (", percent_remaining, "%)")
#^^^^^^^^check if the fw size ok and check external addr leak^^^^^^^^
# copy the fast flash script
build_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'build')
flash_py_path = os.path.join(build_dir, 'flash.py')
+25
View File
@@ -0,0 +1,25 @@
# Description: WEather FAX list
# Author: gullradriel
# Deutscher Wetterdienst (DWD) - Offenbach, Germany
# Surface analysis: 04:30,10:50,16:36,22:00 UTC
f=3855000,m=AMFM,bw=USB+FM,d=DWD DDH3 Analysis
# Surface analysis, forecasts, warnings (continuous)
f=7880000,m=AMFM,bw=USB+FM,d=DWD DDK3 Forecasts
# Surface analysis & wave height (continuous)
f=13882500,m=AMFM,bw=USB+FM,d=DWD DDK6 Waves
# Royal Navy (Northwood) - United Kingdom
# Surface/Wave charts every 6 hrs
f=2618500,m=AMFM,bw=USB+FM,d=Northwood 2.6MHz WX
f=4610000,m=AMFM,bw=USB+FM,d=Northwood 4.6MHz WX
f=8040000,m=AMFM,bw=USB+FM,d=Northwood 8.0MHz WX
f=11086500,m=AMFM,bw=USB+FM,d=Northwood 11MHz WX
# United States Coast Guard (USCG) - Boston/NMF
# Atlantic Analysis: 03:00,09:00,15:00,21:00 UTC
f=6340500,m=AMFM,bw=USB+FM,d=USCG 6.3MHz Atlantic
# Forecasts & Hurricane Warnings (continuous updates)
f=9110000,m=AMFM,bw=USB+FM,d=USCG 9.1MHz WX Warn
# Extended Marine Forecasts (continuous updates)
f=12750000,m=AMFM,bw=USB+FM,d=USCG 12.7MHz Marine