Compare commits

...

22 Commits

Author SHA1 Message Date
sommermorgentraum a60169c7f1 issue template fine tune (#2579) 2025-03-21 17:43:43 +01:00
sommermorgentraum 0ce6ea8318 stopwatch opt (#2578)
* stopwatch opt

* comments

* format

* fxi ms display when user tune display level
2025-03-21 10:34:10 +08:00
RocketGod 4ecc9d04fe Put ticker class and pp_colors in hpp file in namespace and remove helper files (#2577) 2025-03-20 08:08:21 +01:00
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
56 changed files with 8606 additions and 5410 deletions
+6
View File
@@ -8,6 +8,12 @@ body:
value: |
Thank you for taking the time to fill out an issue, this template is meant for any issues related to the Mayhem firmware.
Please try the latest nightly release before submitting this. You can find the latest nightly version here: https://github.com/portapack-mayhem/mayhem-firmware/releases
> [!NOTE]
For hardware related issue, please open as a hardware issue instead.
For feature requests, please open as a feature request instead.
If you have questions, please join our Discord server to ask.
For your information, they are options you can choose when you open an issue.
Unrelated issues will be closed without notice.
- type: textarea
id: description
attributes:
@@ -0,0 +1,40 @@
name: Hardware / QC / QA / selling
description: Issues that related with hardware, quality control, quality assurance, selling, etc.
labels:
- question
body:
- type: markdown
attributes:
value: |
Thank you for taking the time to fill out an issue.
We would like to inform you that we can't provide much help regarding hardware/QC/QA/selling issues because we are not controlling various vendors/manufacturers, and also we are not profiting from you buying your device.
You are still welcome to ask, but please be aware that for this kind of issue, ask the seller would be the best option.
> [!IMPORTANT]
If you are a seller/vendor and your customer has directed you to this page as a reference/evidence of a "broken device" or similar issue, please be aware that we are not providing any "official guarantee of a faulty device." We are only offering suggestions based on what the submitter has stated. We do not guarantee that the device is broken or anything else.
- type: textarea
id: description
attributes:
label: Describe the problem
description: "A clear and concise description of what the problem is."
validations:
required: true
- type: textarea
id: hardware-info
attributes:
label: Hardware information
description: "Please provide the hardware information here."
placeholder: |
1. HackrfRF revision
2. Portapack revision
3. (if it's audio/mic/display/touchscreen/buttons related) audio chip/ CPLD/ screen info...
4. (if hardware is visually damaged) please provide a photo
validations:
required: true
- type: textarea
id: anything-else
attributes:
label: Anything else?
description: Let us know if you have anything else to share.
+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:
@@ -265,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();
@@ -431,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.
+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);
+5
View File
@@ -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
+32 -32
View File
@@ -27,38 +27,38 @@ __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::GAMES,
+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,
};
}
File diff suppressed because it is too large Load Diff
+84
View File
@@ -0,0 +1,84 @@
/*
* ------------------------------------------------------------
* | 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 {
using Callback = void (*)();
class Ticker {
public:
Ticker() {}
void attach(Callback func, double delay_sec) {
game_update_callback = func;
game_update_timeout = delay_sec * 60;
}
void detach() {
game_update_callback = nullptr;
}
private:
Callback game_update_callback = nullptr;
uint32_t game_update_timeout = 0;
};
extern 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(),
};
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__*/
+43 -22
View File
@@ -1,17 +1,5 @@
set(EXTCPPSRC
#tetris
external/tetris/main.cpp
external/tetris/ui_tetris.cpp
#breakout
external/breakout/main.cpp
external/breakout/ui_breakout.cpp
#snake
external/snake/main.cpp
external/snake/ui_snake.cpp
#afsk_rx
external/afsk_rx/main.cpp
external/afsk_rx/ui_afsk_rx.cpp
@@ -67,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
@@ -115,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
@@ -129,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
@@ -142,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
@@ -162,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
@@ -178,6 +175,26 @@ 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
#screening
# external/screening/main.cpp
# external/screening/ui_screening.cpp
)
set(EXTAPPLIST
@@ -195,8 +212,6 @@ set(EXTAPPLIST
spainter
keyfob
tetris
breakout
snake
extsensors
foxhunt_rx
audio_test
@@ -211,6 +226,7 @@ set(EXTAPPLIST
#acars_rx
ookbrute
ook_editor
wefax_rx
shoppingcart_lock
flippertx
remote
@@ -224,4 +240,9 @@ set(EXTAPPLIST
view_wav
sd_wipe
playlist_editor
snake
stopwatch
breakout
doom
# screening
)
+86 -54
View File
@@ -26,46 +26,51 @@ 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_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_breakout(rwx) : org = 0xADDA0000, len = 32k
ram_external_app_snake(rwx) : org = 0xADDB0000, 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
/*ram_external_app_screening (rwx) : org = 0xADDF0000, len = 32k*/
}
SECTIONS
@@ -154,18 +159,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_snake : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_snake.application_information));
*(*ui*external_app*snake*);
} > ram_external_app_snake
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_extsensors.application_information));
@@ -208,7 +201,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));
@@ -274,13 +266,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));
@@ -328,4 +320,44 @@ 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
/*
.external_app_screening : ALIGN(4) SUBALIGN(4)
{
KEEP(*(.external_app.app_screening.application_information));
*(*ui*external_app*screening*);
} > ram_external_app_screening
*/
}
+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() {
+22 -21
View File
@@ -28,35 +28,36 @@ __attribute__((section(".external_app.app_snake.application_information"), used)
{
0x00,
0x00,
0x7E,
0x42,
0x42,
0x42,
0x7E,
0x00,
0x00,
0x7E,
0x42,
0x42,
0x42,
0x7E,
0xE0,
0x09,
0x70,
0xC7,
0xFC,
0xC9,
0x06,
0x00,
0x06,
0x00,
0x0C,
0x00,
0x7E,
0x42,
0x42,
0x42,
0x7E,
0xF0,
0x01,
0x00,
0x3E,
0x00,
0x7E,
0x42,
0x42,
0x42,
0x7E,
0x00,
0x40,
0xFC,
0x40,
0x02,
0x3F,
0x02,
0x00,
0x7C,
0x80,
0x80,
0x7F,
},
ui::Color::green().v,
app_location_t::GAMES,
+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
};
}
+309
View File
@@ -0,0 +1,309 @@
/*
* Copyright 2025 Mark Thompson
* copyleft Mr. Robot of F.Society
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#include "ui_stopwatch.hpp"
#include "portapack.hpp"
#include "ch.h"
using namespace portapack;
namespace ui::external_app::stopwatch {
// clang-format off
// clang-format doesn't allow use as this way but it's not worth to have const var for this thing. too expensive
// minute
#define TOTAL_M1_POS {0 * 8 * 4, 3 * 16}
#define TOTAL_M2_POS {1 * 8 * 4, 3 * 16}
// sec
#define TOTAL_S1_POS {2 * 8 * 4 + 24, 3 * 16}
#define TOTAL_S2_POS {3 * 8 * 4 + 24, 3 * 16}
// ms
#define TOTAL_MS1_POS {4 * 8 * 4 + 7 * 8, 3 * 16 + 24}
#define TOTAL_MS2_POS {5 * 8 * 4 + 5 * 8, 3 * 16 + 24}
#define TOTAL_MS3_POS {6 * 8 * 4 + 3 * 8, 3 * 16 + 24}
#define LAP_Y 9 * 16
//lap min
#define LAP_M1_POS {0 * 8 * 4, LAP_Y}
#define LAP_M2_POS {1 * 8 * 4, LAP_Y}
//lap sec
#define LAP_S1_POS {2 * 8 * 4 + 24, LAP_Y}
#define LAP_S2_POS {3 * 8 * 4 + 24, LAP_Y}
//lap ms
#define LAP_MS1_POS {4 * 8 * 4 + 7 * 8, LAP_Y + 24}
#define LAP_MS2_POS {5 * 8 * 4 + 5 * 8, LAP_Y + 24}
#define LAP_MS3_POS {6 * 8 * 4 + 3 * 8, LAP_Y + 24}
// clang-format on
StopwatchView::StopwatchView(NavigationView& nav)
: painter{} {
add_children({
&labels,
&button_run_stop,
&button_reset_lap,
&button_done,
&options_ms_display_level,
});
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();
};
options_ms_display_level.on_change = [&](size_t, ui::OptionsField::value_t) {
clean_ms_display(options_ms_display_level.selected_index());
};
options_ms_display_level.set_selected_index(0);
refresh_painting();
}
void StopwatchView::focus() {
button_run_stop.focus();
}
void StopwatchView::run() {
options_ms_display_level.hidden(true);
// ^ this won't take efferts if don't set_dirty, but needed to let user can't change during ticking
if (!paused) refresh_painting();
paused = false;
running = true;
start_time = chTimeNow() - previously_elapsed;
button_run_stop.set_text("STOP");
button_reset_lap.set_text("LAP");
}
void StopwatchView::stop() {
options_ms_display_level.hidden(false);
running = false;
paused = true;
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;
for (uint8_t i = 0; i < 7; i++) {
refresh_painting();
}
refresh_painting();
set_dirty();
}
void StopwatchView::lap() {
lap_time = chTimeNow();
}
void StopwatchView::paint(Painter&) {
}
void StopwatchView::refresh_painting() {
// minute
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
// sec
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
// ms
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
// lap min
painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, ' ', 4);
painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, ' ', 4);
// lap sec
painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, ' ', 4);
painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, ' ', 4);
// lap ms
painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
}
/*when user seted it to display more, then display less later, this prevent the remain value exist on screen*/
void StopwatchView::clean_ms_display(uint8_t level) {
level++;
switch (level) {
case 0:
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
break;
case 1:
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, ' ', 2);
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
break;
case 2:
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, ' ', 2);
break;
}
}
void StopwatchView::resume_last() {
// minute
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_light, last_displayed[0], 4);
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_light, last_displayed[1], 4);
// sec
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_light, last_displayed[2], 4);
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_light, last_displayed[3], 4);
// ms
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_light, last_displayed[4], 2);
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_light, last_displayed[5], 2);
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_light, last_displayed[6], 2);
}
/* NB:
* Due to the flaw of dirty management, it's using work around, to reduce screen flickering:
*
* for example when xx:15:xxx turn to xx:16:xxx, it actually only paint 6, the 1 is old,
* but not dirty, so we can see without flikering.
*
* So with these work around, it won't show false info, but bare in mind that it could be, if you add more things.
*/
void StopwatchView::frame_sync() {
uint32_t elapsed_ticks = 0;
if (running) {
end_time = chTimeNow();
elapsed_ticks = end_time - start_time;
} else if (previously_elapsed > 0) {
elapsed_ticks = previously_elapsed;
}
constexpr uint32_t TICKS_PER_SECOND = CH_FREQUENCY;
constexpr uint32_t TICKS_PER_MINUTE = TICKS_PER_SECOND * 60;
uint32_t minutes = elapsed_ticks / TICKS_PER_MINUTE;
uint32_t seconds = (elapsed_ticks % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
uint32_t milliseconds = ((elapsed_ticks % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
// minute
if (last_displayed[0] != (minutes / 10) % 10)
painter.draw_char(TOTAL_M1_POS, *Theme::getInstance()->fg_red, '0' + (minutes / 10) % 10, 4);
if (last_displayed[1] != minutes % 10)
painter.draw_char(TOTAL_M2_POS, *Theme::getInstance()->fg_red, '0' + minutes % 10, 4);
// sec
if (last_displayed[2] != (seconds / 10) % 10)
painter.draw_char(TOTAL_S1_POS, *Theme::getInstance()->fg_green, '0' + (seconds / 10) % 10, 4);
if (last_displayed[3] != seconds % 10)
painter.draw_char(TOTAL_S2_POS, *Theme::getInstance()->fg_green, '0' + seconds % 10, 4);
// ms
/* v place holder to aligh logic*/
if ((true) && last_displayed[4] != (milliseconds / 100) % 10)
painter.draw_char(TOTAL_MS1_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 100) % 10, 2);
if ((options_ms_display_level.selected_index() >= 1) && last_displayed[5] != (milliseconds / 10) % 10)
painter.draw_char(TOTAL_MS2_POS, *Theme::getInstance()->fg_yellow, '0' + (milliseconds / 10) % 10, 2);
if ((options_ms_display_level.selected_index() >= 2) && last_displayed[6] != milliseconds % 10)
painter.draw_char(TOTAL_MS3_POS, *Theme::getInstance()->fg_yellow, '0' + milliseconds % 10, 2);
// min
last_displayed[0] = (minutes / 10) % 10;
last_displayed[1] = minutes % 10;
// sec
last_displayed[2] = (seconds / 10) % 10;
last_displayed[3] = seconds % 10;
// ms
last_displayed[4] = (milliseconds / 100) % 10;
last_displayed[5] = (milliseconds / 10) % 10;
last_displayed[6] = milliseconds % 10;
if (lap_time > 0) {
uint32_t lap_elapsed = lap_time - start_time;
uint32_t lap_minutes = lap_elapsed / TICKS_PER_MINUTE;
uint32_t lap_seconds = (lap_elapsed % TICKS_PER_MINUTE) / TICKS_PER_SECOND;
uint32_t lap_milliseconds = ((lap_elapsed % TICKS_PER_SECOND) * 1000) / TICKS_PER_SECOND;
// lap min
if (lap_last_displayed[0] == (lap_minutes / 10) % 10) {
painter.draw_char(LAP_M1_POS, *Theme::getInstance()->fg_light, '0' + (lap_minutes / 10) % 10, 4);
}
if (lap_last_displayed[1] == lap_minutes % 10) {
painter.draw_char(LAP_M2_POS, *Theme::getInstance()->fg_light, '0' + lap_minutes % 10, 4);
}
// lap sec
if (lap_last_displayed[2] == (lap_seconds / 10) % 10) {
painter.draw_char(LAP_S1_POS, *Theme::getInstance()->fg_light, '0' + (lap_seconds / 10) % 10, 4);
}
if (lap_last_displayed[3] == lap_seconds % 10) {
painter.draw_char(LAP_S2_POS, *Theme::getInstance()->fg_light, '0' + lap_seconds % 10, 4);
}
// lap ms
if (lap_last_displayed[4] == (lap_milliseconds / 100) % 10) {
painter.draw_char(LAP_MS1_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 100) % 10, 2);
}
if (lap_last_displayed[5] == (lap_milliseconds / 10) % 10) {
painter.draw_char(LAP_MS2_POS, *Theme::getInstance()->fg_light, '0' + (lap_milliseconds / 10) % 10, 2);
}
if (lap_last_displayed[6] == lap_milliseconds % 10) {
painter.draw_char(LAP_MS3_POS, *Theme::getInstance()->fg_light, '0' + lap_milliseconds % 10, 2);
}
// lp m
lap_last_displayed[0] = (lap_minutes / 10) % 10;
lap_last_displayed[1] = lap_minutes % 10;
// lp s
lap_last_displayed[2] = (lap_seconds / 10) % 10;
lap_last_displayed[3] = lap_seconds % 10;
// lp mss
lap_last_displayed[4] = (lap_milliseconds / 100) % 10;
lap_last_displayed[5] = (lap_milliseconds / 10) % 10;
lap_last_displayed[6] = lap_milliseconds % 10;
}
}
} // namespace ui::external_app::stopwatch
@@ -0,0 +1,94 @@
/*
* Copyright 2025 Mark Thompson
* copyleft Mr. Robot of F.Society
*
* This file is part of PortaPack.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street,
* Boston, MA 02110-1301, USA.
*/
#ifndef __UI_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();
void cover_display_area_with_0();
void refresh_painting();
void resume_last();
void clean_ms_display(uint8_t level = 0);
bool running{false};
bool paused{false};
long long start_time{0};
long long end_time{0};
long long lap_time{0};
long long previously_elapsed{0};
uint8_t last_displayed[7] = {0, 0, 0, 0, 0, 0, 0};
/* m m s s ms ms ms*/
uint8_t lap_last_displayed[7] = {0, 0, 0, 0, 0, 0, 0};
/* m m s s ms ms ms*/
Labels labels{
{{0 * 8, 1 * 16}, "TOTAL:", Theme::getInstance()->fg_light->foreground},
{{0 * 8, 7 * 16}, "LAP:", Theme::getInstance()->fg_light->foreground},
};
OptionsField options_ms_display_level{
{4 * 8 * 4 + 7 * 8 + 4, 2 * 16},
5,
{{"& - -", 0},
{"& & -", 1},
{"& & &", 2}}};
Painter painter;
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__*/
+1 -1
View File
@@ -73,7 +73,7 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
0xFF,
0xF1,
},
/*.icon_color = */ ui::Color::orange().v,
/*.icon_color = */ ui::Color::green().v,
/*.menu_location = */ app_location_t::GAMES,
/*.desired_menu_position = */ -1,
+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__*/
+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() {
+3
View File
@@ -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.
};
+1 -1
View File
@@ -126,7 +126,7 @@ 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::purple(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
/* RX ********************************************************************/
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
+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(
+7 -2
View File
@@ -112,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;
}
}
+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__*/
+25 -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()));
@@ -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'};
+28 -3
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft Mr. Robot
*
* This file is part of PortaPack.
*
@@ -35,10 +36,34 @@ Style Style::invert() const {
.foreground = background};
}
int Painter::draw_char(Point p, const Style& style, char c) {
int Painter::draw_char(Point p, const Style& style, char c, uint8_t zoom_factor) {
const auto glyph = style.font.glyph(c);
display.draw_glyph(p, glyph, style.foreground, style.background);
return glyph.advance().x();
if (zoom_factor <= 1) {
display.draw_glyph(p, glyph, style.foreground, style.background);
} else {
// dot to square
const uint8_t* pixels = glyph.pixels();
for (int y = 0; y < glyph.h(); y++) { // each line
for (int x = 0; x < glyph.w(); x++) { // each clum
// pos
int byte_index = (y * glyph.w() + x) / 8;
int bit_pos = (y * glyph.w() + x) % 8;
// fill
Color pixel_color = ((pixels[byte_index] & (1 << bit_pos)) != 0) ? style.foreground : style.background;
/* ^ true: fg, false: bg
^ the byte_index-th bit AKA current bit
^ current px in current byte */
display.fill_rectangle(
{p.x() + x * zoom_factor, p.y() + y * zoom_factor,
zoom_factor, zoom_factor},
pixel_color);
}
}
}
return glyph.advance().x() * zoom_factor;
}
int Painter::draw_string(Point p, const Style& style, std::string_view text) {
+2 -1
View File
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc.
* copyleft Mr. Robot
*
* This file is part of PortaPack.
*
@@ -69,7 +70,7 @@ class Painter {
Painter(const Painter&) = delete;
Painter(Painter&&) = delete;
int draw_char(Point p, const Style& style, char c);
int draw_char(Point p, const Style& style, char c, uint8_t zoom_factor = 1);
int draw_string(Point p, const Style& style, std::string_view text);
int draw_string(Point p, const Font& font, Color foreground, Color background, std::string_view text);
+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

@@ -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