Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a60169c7f1 | |||
| 0ce6ea8318 | |||
| 4ecc9d04fe | |||
| d73db884f0 | |||
| ff2f27e297 | |||
| 443ffd8ad3 | |||
| 403c362702 | |||
| 6aa29e265b | |||
| 40cf2b3f8c | |||
| 4aa5fc1fbe | |||
| 717d615f4f | |||
| dce4e91e41 | |||
| dc2f3260f3 | |||
| 2d85e73f0d | |||
| 6ee7270db7 | |||
| b4112f0c04 | |||
| e920932886 | |||
| 081b11ad41 | |||
| adc23315a2 | |||
| 084b88564b | |||
| 24e9504688 | |||
| c670ca59a8 |
@@ -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.
|
||||
@@ -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:  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:  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.
|
||||
|
||||
@@ -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>(); }},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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__*/
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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__*/
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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__*/
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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.
|
||||
};
|
||||
|
||||
|
||||
@@ -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>()},
|
||||
|
||||
@@ -668,6 +668,14 @@ DeclareTargets(POSK ookstream)
|
||||
|
||||
|
||||
|
||||
### WeFax Rx
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_wefaxrx.cpp
|
||||
)
|
||||
DeclareTargets(PWFX wefaxrx)
|
||||
|
||||
|
||||
### HackRF "factory" firmware
|
||||
|
||||
add_custom_command(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__*/
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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__*/
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,6 +291,7 @@ class BigFrequency : public Widget {
|
||||
|
||||
private:
|
||||
rf::Frequency _frequency;
|
||||
rf::Frequency _previous_frequency{~0LL};
|
||||
|
||||
static constexpr Dim digit_width = 32;
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 137 B |
|
After Width: | Height: | Size: 182 B |
|
After Width: | Height: | Size: 315 B |
|
After Width: | Height: | Size: 194 B |
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 215 B |
|
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()
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||