Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d73db884f0 | |||
| ff2f27e297 | |||
| 443ffd8ad3 | |||
| 403c362702 | |||
| 6aa29e265b | |||
| 40cf2b3f8c | |||
| 4aa5fc1fbe | |||
| 717d615f4f | |||
| dce4e91e41 | |||
| dc2f3260f3 | |||
| 2d85e73f0d | |||
| 6ee7270db7 | |||
| b4112f0c04 | |||
| e920932886 | |||
| 081b11ad41 | |||
| adc23315a2 | |||
| 084b88564b | |||
| 24e9504688 | |||
| c670ca59a8 | |||
| 2ca8ea6342 | |||
| eb50b790c2 | |||
| 1df318355b | |||
| a17995fd2b |
@@ -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:
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ AMFMAptOptionsView::AMFMAptOptionsView(
|
||||
});
|
||||
|
||||
freqman_set_bandwidth_option(AMFM_MODULATION, options_config); // adding the common message from freqman.cpp to the options_config
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
options_config.set_by_value(receiver_model.amfm_configuration());
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
}
|
||||
|
||||
/* SPECOptionsView *******************************************************/
|
||||
@@ -193,9 +193,10 @@ AnalogAudioView::AnalogAudioView(
|
||||
};
|
||||
|
||||
auto modulation = receiver_model.modulation();
|
||||
|
||||
// This app doesn't handle "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis) // This two should be together in the last index position : SpectrumAnalysis = 4, and Capture = 5
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis; // For sw simplicity , Wefax_mode, should NOT be added between.
|
||||
if (modulation == ReceiverModel::Mode::Capture)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
options_modulation.set_by_value(toUType(modulation));
|
||||
options_modulation.on_change = [this](size_t, OptionsField::value_t v) {
|
||||
@@ -264,6 +265,7 @@ void AnalogAudioView::set_spec_trigger(uint16_t trigger) {
|
||||
}
|
||||
|
||||
AnalogAudioView::~AnalogAudioView() {
|
||||
receiver_model.set_hidden_offset(0);
|
||||
audio::output::stop();
|
||||
receiver_model.disable();
|
||||
baseband::shutdown();
|
||||
@@ -285,10 +287,6 @@ void AnalogAudioView::on_baseband_bandwidth_changed(uint32_t bandwidth_hz) {
|
||||
}
|
||||
|
||||
void AnalogAudioView::on_modulation_changed(ReceiverModel::Mode modulation) {
|
||||
// This app doesn't know what to do with "Capture" mode.
|
||||
if (modulation > ReceiverModel::Mode::SpectrumAnalysis)
|
||||
modulation = ReceiverModel::Mode::SpectrumAnalysis;
|
||||
|
||||
baseband::spectrum_streaming_stop();
|
||||
update_modulation(modulation);
|
||||
on_show_options_modulation();
|
||||
@@ -434,6 +432,8 @@ void AnalogAudioView::update_modulation(ReceiverModel::Mode modulation) {
|
||||
receiver_model.set_sampling_rate(is_wideband_spectrum_mode ? spec_bw : 3072000);
|
||||
receiver_model.set_baseband_bandwidth(is_wideband_spectrum_mode ? spec_bw / 2 : 1750000);
|
||||
|
||||
receiver_model.set_hidden_offset(modulation == ReceiverModel::Mode::AMAudioFMApt ? -2200 : 0); // wefax needs to be shifted, see wefax rx app.
|
||||
|
||||
receiver_model.enable();
|
||||
|
||||
// TODO: This doesn't belong here! There's a better way.
|
||||
|
||||
@@ -224,11 +224,13 @@ class AnalogAudioView : public View {
|
||||
OptionsField options_modulation{
|
||||
{0 * 8, 0 * 16},
|
||||
4,
|
||||
{{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
|
||||
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"WFAX", toUType(ReceiverModel::Mode::AMAudioFMApt)}, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)}}};
|
||||
{
|
||||
{" AM ", toUType(ReceiverModel::Mode::AMAudio)},
|
||||
{"NFM ", toUType(ReceiverModel::Mode::NarrowbandFMAudio)},
|
||||
{"WFM ", toUType(ReceiverModel::Mode::WidebandFMAudio)},
|
||||
{"SPEC", toUType(ReceiverModel::Mode::SpectrumAnalysis)},
|
||||
{"AMFM", toUType(ReceiverModel::Mode::AMAudioFMApt)} // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
}};
|
||||
|
||||
AudioVolumeField field_volume{
|
||||
{28 * 8, 0 * 16}};
|
||||
|
||||
@@ -101,6 +101,9 @@ void ExternalModuleView::on_tick_second() {
|
||||
case app_location_t::HOME:
|
||||
btnText += " (Home)";
|
||||
break;
|
||||
case app_location_t::GAMES:
|
||||
btnText += " (Games)";
|
||||
break;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
@@ -122,4 +125,5 @@ void ExternalModuleView::on_tick_second() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -45,7 +45,7 @@ using option_db_t = std::pair<std::string_view, int32_t>;
|
||||
using options_db_t = std::vector<option_db_t>;
|
||||
|
||||
extern options_db_t freqman_modulations;
|
||||
extern options_db_t freqman_bandwidths[4];
|
||||
extern options_db_t freqman_bandwidths[5];
|
||||
extern options_db_t freqman_steps;
|
||||
extern options_db_t freqman_steps_short;
|
||||
|
||||
|
||||
@@ -277,6 +277,15 @@ size_t LevelView::change_mode(freqman_index_t new_mod) {
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t index, OptionsField::value_t n) { radio_bw = index ; receiver_model.set_wfm_configuration(n); };
|
||||
break;
|
||||
case AMFM_MODULATION:
|
||||
audio_sampling_rate = audio::Rate::Hz_12000;
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
audio_sampling_rate = audio::Rate::Hz_24000;
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
|
||||
@@ -1227,6 +1227,16 @@ size_t ReconView::change_mode(freqman_index_t new_mod) {
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 48000;
|
||||
break;
|
||||
case AMFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
|
||||
receiver_model.set_amfm_configuration(5); // Fix index 5 manually, not from freqman: set to RX AM (USB+FM) mode to demod audio tone, and get Wefax_APT signal.
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
|
||||
field_bw.set_by_value(0);
|
||||
text_ctcss.set(" ");
|
||||
recording_sampling_rate = 12000;
|
||||
break;
|
||||
case SPEC_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_capture);
|
||||
|
||||
@@ -725,6 +725,14 @@ void ScannerView::change_mode(freqman_index_t new_mod) {
|
||||
field_bw.set_by_value(receiver_model.wfm_configuration());
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { receiver_model.set_wfm_configuration(n); };
|
||||
break;
|
||||
case AMFM_MODULATION:
|
||||
freqman_set_bandwidth_option(new_mod, field_bw);
|
||||
baseband::run_image(portapack::spi_flash::image_tag_am_audio);
|
||||
receiver_model.set_modulation(ReceiverModel::Mode::AMAudioFMApt);
|
||||
receiver_model.set_amfm_configuration(5);
|
||||
field_bw.set_by_value(0);
|
||||
field_bw.on_change = [this](size_t, OptionsField::value_t n) { (void)n; };
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,10 +66,10 @@ static void send_message(const Message* const message) {
|
||||
|
||||
void AMConfig::apply() const {
|
||||
const AMConfigureMessage message{
|
||||
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, WFAX)
|
||||
taps_6k0_decim_0, // common FIR filter taps pre-decim_0 to all 6 x AM mod types.(AM-9K, AM-6K, USB, LSB, CW, AMFM-WFAX)
|
||||
taps_6k0_decim_1, // common FIR filter taps pre-decim_1 to all 6 x AM mod. types. (")
|
||||
decim_2, // var decim_2 FIR taps filter , variable values, depending selected AM mod(AM 9k / 6k and all rest AM modes)
|
||||
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,WFAX)
|
||||
channel, // var channel FIR taps filter , variable values, depending selected AM mode, each one different (DSB-9K, DSB-6K, USB-3K, LSB-3K,CW,AMFM-WFAX)
|
||||
modulation, // var parameter . enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
|
||||
audio_12k_iir_filter_config}; // var parameter , 300 Hz hpf all except Wefax (1.500Hz lpf)
|
||||
send_message(&message);
|
||||
@@ -304,6 +304,11 @@ void set_spectrum(const size_t sampling_rate, const size_t trigger) {
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) {
|
||||
const WeFaxRxConfigureMessage message{lpm, ioc};
|
||||
send_message(&message);
|
||||
}
|
||||
|
||||
void set_siggen_tone(const uint32_t tone) {
|
||||
const SigGenToneMessage message{
|
||||
TONES_F2D(tone, TONES_SAMPLERATE)};
|
||||
|
||||
@@ -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,41 +27,41 @@ __attribute__((section(".external_app.app_breakout.application_information"), us
|
||||
|
||||
/*.app_name = */ "Breakout",
|
||||
/*.bitmap_data = */ {
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0x01,
|
||||
0xFF,
|
||||
0xFF,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x80,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0x00,
|
||||
0x00,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x7F,
|
||||
0x00,
|
||||
0x00,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
0xF7,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// dummy include file to avoid changing original source
|
||||
|
||||
#ifndef __UI_Arial12x12_H__
|
||||
#define __UI_Arial12x12_H__
|
||||
|
||||
#define Arial12x12 (0)
|
||||
|
||||
#endif /*__UI_Arial12x12_H__*/
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef __UI_SPI_TFT_ILI9341_H__
|
||||
#define __UI_SPI_TFT_ILI9341_H__
|
||||
|
||||
ui::Painter painter;
|
||||
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
static const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
#endif /*__UI_SPI_TFT_ILI9341_H__*/
|
||||
@@ -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,81 @@
|
||||
#ifndef __UI_mbed_H__
|
||||
#define __UI_mbed_H__
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
#define wait_us(x) (void)0
|
||||
#define wait(x) chThdSleepMilliseconds(x * 1000)
|
||||
#define PullUp 1
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
enum {
|
||||
dp0,
|
||||
dp1,
|
||||
dp2,
|
||||
dp3,
|
||||
dp4,
|
||||
dp5,
|
||||
dp6,
|
||||
dp7,
|
||||
dp8,
|
||||
dp9,
|
||||
dp10,
|
||||
dp11,
|
||||
dp12,
|
||||
dp13,
|
||||
dp14,
|
||||
dp15,
|
||||
dp16,
|
||||
dp17,
|
||||
dp18,
|
||||
dp19,
|
||||
dp20,
|
||||
dp21,
|
||||
dp22,
|
||||
dp23,
|
||||
dp24,
|
||||
dp25,
|
||||
};
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { (void)0; };
|
||||
void reset() { (void)0; };
|
||||
void start() { (void)0; }
|
||||
uint32_t read_ms() { return 1000; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
static Callback game_update_callback;
|
||||
static uint32_t game_update_timeout;
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() { (void)0; };
|
||||
void attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
void detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
static Callback button_callback;
|
||||
|
||||
class InterruptIn {
|
||||
public:
|
||||
InterruptIn(int reg) {
|
||||
(void)reg;
|
||||
};
|
||||
void fall(Callback func) { button_callback = func; };
|
||||
void mode(int v) { (void)v; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif /*__UI_mbed_H__*/
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_DOOM_H__
|
||||
#define __UI_DOOM_H__
|
||||
|
||||
#include "ui_widget.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "message.hpp"
|
||||
|
||||
namespace ui::external_app::doom {
|
||||
|
||||
class DoomView : public View {
|
||||
public:
|
||||
DoomView(NavigationView& nav);
|
||||
void on_show() override;
|
||||
std::string title() const override { return "Doom"; }
|
||||
void focus() override { dummy.focus(); }
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_key(const KeyEvent key) override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
Button dummy{{240, 0, 0, 0}, ""};
|
||||
bool initialized{false};
|
||||
bool prev_velocity_moving{false};
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::doom
|
||||
|
||||
#endif /*__UI_DOOM_H__*/
|
||||
@@ -1,13 +1,5 @@
|
||||
set(EXTCPPSRC
|
||||
|
||||
#tetris
|
||||
external/tetris/main.cpp
|
||||
external/tetris/ui_tetris.cpp
|
||||
|
||||
#tetris
|
||||
external/breakout/main.cpp
|
||||
external/breakout/ui_breakout.cpp
|
||||
|
||||
#afsk_rx
|
||||
external/afsk_rx/main.cpp
|
||||
external/afsk_rx/ui_afsk_rx.cpp
|
||||
@@ -63,6 +55,10 @@ set(EXTCPPSRC
|
||||
external/keyfob/ui_keyfob.cpp
|
||||
external/keyfob/ui_keyfob.hpp
|
||||
|
||||
#tetris
|
||||
external/tetris/main.cpp
|
||||
external/tetris/ui_tetris.cpp
|
||||
|
||||
#extsensors
|
||||
external/extsensors/main.cpp
|
||||
external/extsensors/ui_extsensors.cpp
|
||||
@@ -111,9 +107,14 @@ set(EXTCPPSRC
|
||||
external/acars_rx/main.cpp
|
||||
external/acars_rx/acars_app.cpp
|
||||
|
||||
#wefax_rx
|
||||
external/wefax_rx/main.cpp
|
||||
external/wefax_rx/ui_wefax_rx.cpp
|
||||
|
||||
|
||||
#shoppingcart_lock
|
||||
external/shoppingcart_lock/main.cpp
|
||||
external/shoppingcart_lock/shoppingcart_lock.cpp
|
||||
external/shoppingcart_lock/shoppingcart_lock.cpp
|
||||
|
||||
#ookbrute
|
||||
external/ookbrute/main.cpp
|
||||
@@ -125,8 +126,8 @@ set(EXTCPPSRC
|
||||
|
||||
#cvs_spam
|
||||
external/cvs_spam/main.cpp
|
||||
external/cvs_spam/cvs_spam.cpp
|
||||
|
||||
external/cvs_spam/cvs_spam.cpp
|
||||
|
||||
#flippertx
|
||||
external/flippertx/main.cpp
|
||||
external/flippertx/ui_flippertx.cpp
|
||||
@@ -138,15 +139,15 @@ set(EXTCPPSRC
|
||||
#mcu_temperature
|
||||
external/mcu_temperature/main.cpp
|
||||
external/mcu_temperature/mcu_temperature.cpp
|
||||
|
||||
|
||||
#fmradio
|
||||
external/fmradio/main.cpp
|
||||
external/fmradio/ui_fmradio.cpp
|
||||
|
||||
|
||||
#tuner
|
||||
external/tuner/main.cpp
|
||||
external/tuner/ui_tuner.cpp
|
||||
|
||||
|
||||
#metronome
|
||||
external/metronome/main.cpp
|
||||
external/metronome/ui_metronome.cpp
|
||||
@@ -158,8 +159,8 @@ set(EXTCPPSRC
|
||||
#hopper
|
||||
external/hopper/main.cpp
|
||||
external/hopper/ui_hopper.cpp
|
||||
|
||||
# whip calculator
|
||||
|
||||
# whip calculator
|
||||
external/antenna_length/main.cpp
|
||||
external/antenna_length/ui_whipcalc.cpp
|
||||
|
||||
@@ -174,6 +175,22 @@ set(EXTCPPSRC
|
||||
# playlist editor
|
||||
external/playlist_editor/main.cpp
|
||||
external/playlist_editor/ui_playlist_editor.cpp
|
||||
|
||||
#snake
|
||||
external/snake/main.cpp
|
||||
external/snake/ui_snake.cpp
|
||||
|
||||
#stopwatch
|
||||
external/stopwatch/main.cpp
|
||||
external/stopwatch/ui_stopwatch.cpp
|
||||
|
||||
#breakout
|
||||
external/breakout/main.cpp
|
||||
external/breakout/ui_breakout.cpp
|
||||
|
||||
#doom
|
||||
external/doom/main.cpp
|
||||
external/doom/ui_doom.cpp
|
||||
)
|
||||
|
||||
set(EXTAPPLIST
|
||||
@@ -191,7 +208,6 @@ set(EXTAPPLIST
|
||||
spainter
|
||||
keyfob
|
||||
tetris
|
||||
breakout
|
||||
extsensors
|
||||
foxhunt_rx
|
||||
audio_test
|
||||
@@ -206,6 +222,7 @@ set(EXTAPPLIST
|
||||
#acars_rx
|
||||
ookbrute
|
||||
ook_editor
|
||||
wefax_rx
|
||||
shoppingcart_lock
|
||||
flippertx
|
||||
remote
|
||||
@@ -219,4 +236,8 @@ set(EXTAPPLIST
|
||||
view_wav
|
||||
sd_wipe
|
||||
playlist_editor
|
||||
snake
|
||||
stopwatch
|
||||
breakout
|
||||
doom
|
||||
)
|
||||
|
||||
@@ -26,45 +26,49 @@ MEMORY
|
||||
ram_external_app_afsk_rx (rwx) : org = 0xADB10000, len = 32k
|
||||
ram_external_app_calculator (rwx) : org = 0xADB20000, len = 32k
|
||||
ram_external_app_font_viewer(rwx) : org = 0xADB30000, len = 32k
|
||||
ram_external_app_blespam(rwx) : org = 0xADB40000, len = 32k
|
||||
ram_external_app_analogtv(rwx) : org = 0xADB50000, len = 32k
|
||||
ram_external_app_nrf_rx(rwx) : org = 0xADB60000, len = 32k
|
||||
ram_external_app_coasterp(rwx) : org = 0xADB70000, len = 32k
|
||||
ram_external_app_lge(rwx) : org = 0xADB80000, len = 32k
|
||||
ram_external_app_lcr(rwx) : org = 0xADB90000, len = 32k
|
||||
ram_external_app_jammer(rwx) : org = 0xADBA0000, len = 32k
|
||||
ram_external_app_gpssim(rwx) : org = 0xADBB0000, len = 32k
|
||||
ram_external_app_spainter(rwx) : org = 0xADBC0000, len = 32k
|
||||
ram_external_app_keyfob(rwx) : org = 0xADBD0000, len = 32k
|
||||
ram_external_app_tetris(rwx) : org = 0xADBE0000, len = 32k
|
||||
ram_external_app_extsensors(rwx) : org = 0xADBF0000, len = 32k
|
||||
ram_external_app_breakout(rwx) : org = 0xADE00000, len = 32k
|
||||
ram_external_app_foxhunt_rx(rwx) : org = 0xADC00000, len = 32k
|
||||
ram_external_app_audio_test(rwx) : org = 0xADC10000, len = 32k
|
||||
ram_external_app_blespam (rwx) : org = 0xADB40000, len = 32k
|
||||
ram_external_app_analogtv (rwx) : org = 0xADB50000, len = 32k
|
||||
ram_external_app_nrf_rx (rwx) : org = 0xADB60000, len = 32k
|
||||
ram_external_app_coasterp (rwx) : org = 0xADB70000, len = 32k
|
||||
ram_external_app_lge (rwx) : org = 0xADB80000, len = 32k
|
||||
ram_external_app_lcr (rwx) : org = 0xADB90000, len = 32k
|
||||
ram_external_app_jammer (rwx) : org = 0xADBA0000, len = 32k
|
||||
ram_external_app_gpssim (rwx) : org = 0xADBB0000, len = 32k
|
||||
ram_external_app_spainter (rwx) : org = 0xADBC0000, len = 32k
|
||||
ram_external_app_keyfob (rwx) : org = 0xADBD0000, len = 32k
|
||||
ram_external_app_tetris (rwx) : org = 0xADBE0000, len = 32k
|
||||
ram_external_app_extsensors (rwx) : org = 0xADBF0000, len = 32k
|
||||
ram_external_app_foxhunt_rx (rwx) : org = 0xADC00000, len = 32k
|
||||
ram_external_app_audio_test (rwx) : org = 0xADC10000, len = 32k
|
||||
ram_external_app_wardrivemap(rwx) : org = 0xADC20000, len = 32k
|
||||
ram_external_app_tpmsrx(rwx) : org = 0xADC30000, len = 32k
|
||||
ram_external_app_protoview(rwx) : org = 0xADC40000, len = 32k
|
||||
ram_external_app_adsbtx(rwx) : org = 0xADC50000, len = 32k
|
||||
ram_external_app_morse_tx(rwx) : org = 0xADC60000, len = 32k
|
||||
ram_external_app_sstvtx(rwx) : org = 0xADC70000, len = 32k
|
||||
ram_external_app_random_password(rwx) : org = 0xADC80000, len = 32k
|
||||
ram_external_app_acars_rx(rwx) : org = 0xADC90000, len = 32k
|
||||
ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k
|
||||
ram_external_app_cvs_spam(rwx) : org = 0xADCB0000, len = 32k
|
||||
ram_external_app_ookbrute(rwx) : org = 0xADCC0000, len = 32k
|
||||
ram_external_app_flippertx(rwx) : org = 0xADCD0000, len = 32k
|
||||
ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k
|
||||
ram_external_app_remote(rwx) : org = 0xADCF0000, len = 32k
|
||||
ram_external_app_mcu_temperature(rwx) : org = 0xADD00000, len = 32k
|
||||
ram_external_app_fmradio(rwx) : org = 0xADD10000, len = 32k
|
||||
ram_external_app_tuner(rwx) : org = 0xADD20000, len = 32k
|
||||
ram_external_app_metronome(rwx) : org = 0xADD30000, len = 32k
|
||||
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
|
||||
ram_external_app_hopper(rwx) : org = 0xADD50000, len = 32k
|
||||
ram_external_app_antenna_length(rwx) : org = 0xADD60000, len = 32k
|
||||
ram_external_app_view_wav(rwx) : org = 0xADD70000, len = 32k
|
||||
ram_external_app_sd_wipe(rwx) : org = 0xADD80000, len = 32k
|
||||
ram_external_app_playlist_editor(rwx) : org = 0xADD90000, len = 32k
|
||||
ram_external_app_tpmsrx (rwx) : org = 0xADC30000, len = 32k
|
||||
ram_external_app_protoview (rwx) : org = 0xADC40000, len = 32k
|
||||
ram_external_app_adsbtx (rwx) : org = 0xADC50000, len = 32k
|
||||
ram_external_app_morse_tx (rwx) : org = 0xADC60000, len = 32k
|
||||
ram_external_app_sstvtx (rwx) : org = 0xADC70000, len = 32k
|
||||
ram_external_app_random_password(rwx): org = 0xADC80000, len = 32k
|
||||
ram_external_app_acars_rx (rwx) : org = 0xADC90000, len = 32k
|
||||
ram_external_app_shoppingcart_lock(rwx): org = 0xADCA0000, len = 32k
|
||||
ram_external_app_cvs_spam (rwx) : org = 0xADCB0000, len = 32k
|
||||
ram_external_app_ookbrute (rwx) : org = 0xADCC0000, len = 32k
|
||||
ram_external_app_flippertx (rwx) : org = 0xADCD0000, len = 32k
|
||||
ram_external_app_ook_editor (rwx) : org = 0xADCE0000, len = 32k
|
||||
ram_external_app_remote (rwx) : org = 0xADCF0000, len = 32k
|
||||
ram_external_app_mcu_temperature(rwx): org = 0xADD00000, len = 32k
|
||||
ram_external_app_fmradio (rwx) : org = 0xADD10000, len = 32k
|
||||
ram_external_app_tuner (rwx) : org = 0xADD20000, len = 32k
|
||||
ram_external_app_metronome (rwx) : org = 0xADD30000, len = 32k
|
||||
ram_external_app_app_manager(rwx) : org = 0xADD40000, len = 32k
|
||||
ram_external_app_hopper (rwx) : org = 0xADD50000, len = 32k
|
||||
ram_external_app_antenna_length(rwx): org = 0xADD60000, len = 32k
|
||||
ram_external_app_view_wav (rwx) : org = 0xADD70000, len = 32k
|
||||
ram_external_app_sd_wipe (rwx) : org = 0xADD80000, len = 32k
|
||||
ram_external_app_playlist_editor(rwx): org = 0xADD90000, len = 32k
|
||||
ram_external_app_snake (rwx) : org = 0xADDA0000, len = 32k
|
||||
ram_external_app_stopwatch (rwx) : org = 0xADDB0000, len = 32k
|
||||
ram_external_app_wefax_rx (rwx) : org = 0xADDC0000, len = 32k
|
||||
ram_external_app_breakout (rwx) : org = 0xADDD0000, len = 32k
|
||||
ram_external_app_doom (rwx) : org = 0xADDE0000, len = 32k
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
@@ -153,12 +157,6 @@ SECTIONS
|
||||
*(*ui*external_app*tetris*);
|
||||
} > ram_external_app_tetris
|
||||
|
||||
.external_app_breakout : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_breakout.application_information));
|
||||
*(*ui*external_app*breakout*);
|
||||
} > ram_external_app_breakout
|
||||
|
||||
.external_app_extsensors : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_extsensors.application_information));
|
||||
@@ -201,7 +199,6 @@ SECTIONS
|
||||
*(*ui*external_app*adsbtx*);
|
||||
} > ram_external_app_adsbtx
|
||||
|
||||
|
||||
.external_app_morse_tx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_morse_tx.application_information));
|
||||
@@ -267,13 +264,13 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_mcu_temperature.application_information));
|
||||
*(*ui*external_app*mcu_temperature*);
|
||||
} > ram_external_app_mcu_temperature
|
||||
|
||||
|
||||
.external_app_fmradio : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_fmradio.application_information));
|
||||
*(*ui*external_app*fmradio*);
|
||||
} > ram_external_app_fmradio
|
||||
|
||||
|
||||
.external_app_tuner : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_tuner.application_information));
|
||||
@@ -321,4 +318,35 @@ SECTIONS
|
||||
KEEP(*(.external_app.app_playlist_editor.application_information));
|
||||
*(*ui*external_app*playlist_editor*);
|
||||
} > ram_external_app_playlist_editor
|
||||
|
||||
.external_app_snake : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_snake.application_information));
|
||||
*(*ui*external_app*snake*);
|
||||
} > ram_external_app_snake
|
||||
|
||||
.external_app_stopwatch : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_stopwatch.application_information));
|
||||
*(*ui*external_app*stopwatch*);
|
||||
} > ram_external_app_stopwatch
|
||||
.external_app_wefax_rx : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_wefax_rx.application_information));
|
||||
*(*ui*external_app*wefax_rx*);
|
||||
} > ram_external_app_wefax_rx
|
||||
|
||||
.external_app_breakout : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_breakout.application_information));
|
||||
*(*ui*external_app*breakout*);
|
||||
} > ram_external_app_breakout
|
||||
|
||||
.external_app_doom : ALIGN(4) SUBALIGN(4)
|
||||
{
|
||||
KEEP(*(.external_app.app_doom.application_information));
|
||||
*(*ui*external_app*doom*);
|
||||
} > ram_external_app_doom
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (C) 2024 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
// dummy include file to avoid changing original source
|
||||
|
||||
#ifndef __UI_Arial12x12_H__
|
||||
#define __UI_Arial12x12_H__
|
||||
|
||||
#define Arial12x12 (0)
|
||||
|
||||
#endif /*__UI_Arial12x12_H__*/
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_SPI_TFT_ILI9341_H__
|
||||
#define __UI_SPI_TFT_ILI9341_H__
|
||||
|
||||
ui::Painter painter;
|
||||
|
||||
static int bg_color;
|
||||
|
||||
enum {
|
||||
White,
|
||||
Blue,
|
||||
Yellow,
|
||||
Purple,
|
||||
Green,
|
||||
Red,
|
||||
Maroon,
|
||||
Orange,
|
||||
Black,
|
||||
};
|
||||
|
||||
static const Color pp_colors[] = {
|
||||
Color::white(),
|
||||
Color::blue(),
|
||||
Color::yellow(),
|
||||
Color::purple(),
|
||||
Color::green(),
|
||||
Color::red(),
|
||||
Color::magenta(),
|
||||
Color::orange(),
|
||||
Color::black(),
|
||||
};
|
||||
|
||||
static void claim(__FILE* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void cls() {
|
||||
painter.fill_rectangle({0, 0, portapack::display.width(), portapack::display.height()}, Color::black());
|
||||
};
|
||||
|
||||
static void background(int color) {
|
||||
bg_color = color;
|
||||
};
|
||||
|
||||
static void set_orientation(int x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void set_font(unsigned char* x) {
|
||||
(void)x;
|
||||
};
|
||||
|
||||
static void fillrect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.fill_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
static void rect(int x1, int y1, int x2, int y2, int color) {
|
||||
painter.draw_rectangle({x1, y1, x2 - x1, y2 - y1}, pp_colors[color]);
|
||||
};
|
||||
|
||||
#endif /*__UI_SPI_TFT_ILI9341_H__*/
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_snake.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "external_app.hpp"
|
||||
|
||||
namespace ui::external_app::snake {
|
||||
void initialize_app(ui::NavigationView& nav) {
|
||||
nav.push<SnakeView>();
|
||||
}
|
||||
} // namespace ui::external_app::snake
|
||||
|
||||
extern "C" {
|
||||
|
||||
__attribute__((section(".external_app.app_snake.application_information"), used)) application_information_t _application_information_snake = {
|
||||
(uint8_t*)0x00000000,
|
||||
ui::external_app::snake::initialize_app,
|
||||
CURRENT_HEADER_VERSION,
|
||||
VERSION_MD5,
|
||||
"Snake",
|
||||
{
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0xE0,
|
||||
0x09,
|
||||
0x70,
|
||||
0xC7,
|
||||
0xFC,
|
||||
0xC9,
|
||||
0x06,
|
||||
0x00,
|
||||
0x06,
|
||||
0x00,
|
||||
0x0C,
|
||||
0x00,
|
||||
0xF0,
|
||||
0x01,
|
||||
0x00,
|
||||
0x3E,
|
||||
0x00,
|
||||
0x40,
|
||||
0xFC,
|
||||
0x40,
|
||||
0x02,
|
||||
0x3F,
|
||||
0x02,
|
||||
0x00,
|
||||
0x7C,
|
||||
0x80,
|
||||
0x80,
|
||||
0x7F,
|
||||
},
|
||||
ui::Color::green().v,
|
||||
app_location_t::GAMES,
|
||||
-1,
|
||||
{0, 0, 0, 0},
|
||||
0x00000000,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
#ifndef __UI_mbed_H__
|
||||
#define __UI_mbed_H__
|
||||
|
||||
using Callback = void (*)(void);
|
||||
|
||||
#define wait_us(x) (void)0
|
||||
#define wait(x) chThdSleepMilliseconds(x * 1000)
|
||||
#define PullUp 1
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
enum {
|
||||
dp0,
|
||||
dp1,
|
||||
dp2,
|
||||
dp3,
|
||||
dp4,
|
||||
dp5,
|
||||
dp6,
|
||||
dp7,
|
||||
dp8,
|
||||
dp9,
|
||||
dp10,
|
||||
dp11,
|
||||
dp12,
|
||||
dp13,
|
||||
dp14,
|
||||
dp15,
|
||||
dp16,
|
||||
dp17,
|
||||
dp18,
|
||||
dp19,
|
||||
dp20,
|
||||
dp21,
|
||||
dp22,
|
||||
dp23,
|
||||
dp24,
|
||||
dp25,
|
||||
};
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-variable"
|
||||
static bool but_RIGHT;
|
||||
static bool but_LEFT;
|
||||
#pragma GCC diagnostic pop
|
||||
static bool but_SELECT;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer() { (void)0; };
|
||||
void reset() { (void)0; };
|
||||
void start() { (void)0; }
|
||||
uint32_t read_ms() { return 1000; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
static Callback game_update_callback;
|
||||
static uint32_t game_update_timeout;
|
||||
static uint32_t game_update_counter;
|
||||
|
||||
static void check_game_timer() {
|
||||
if (game_update_callback) {
|
||||
if (++game_update_counter >= game_update_timeout) {
|
||||
game_update_counter = 0;
|
||||
game_update_callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Ticker {
|
||||
public:
|
||||
Ticker() { (void)0; };
|
||||
void attach(Callback func, double delay_sec) {
|
||||
game_update_callback = func;
|
||||
game_update_timeout = delay_sec * 60;
|
||||
}
|
||||
void detach() {
|
||||
game_update_callback = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
static Callback button_callback;
|
||||
|
||||
class InterruptIn {
|
||||
public:
|
||||
InterruptIn(int reg) {
|
||||
(void)reg;
|
||||
};
|
||||
void fall(Callback func) { button_callback = func; };
|
||||
void mode(int v) { (void)v; };
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif /*__UI_mbed_H__*/
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "mbed.h"
|
||||
#include "SPI_TFT_ILI9341.h"
|
||||
#include "Arial12x12.h"
|
||||
#include "ui.hpp"
|
||||
#include "random.hpp"
|
||||
|
||||
extern int game_state;
|
||||
|
||||
#define SCREEN_WIDTH 240
|
||||
#define SCREEN_HEIGHT 320
|
||||
#define SNAKE_SIZE 10
|
||||
#define INFO_BAR_HEIGHT 25
|
||||
#define GAME_AREA_TOP (INFO_BAR_HEIGHT + 1)
|
||||
#define GAME_AREA_HEIGHT (SCREEN_HEIGHT - INFO_BAR_HEIGHT - 2)
|
||||
#define GRID_WIDTH ((SCREEN_WIDTH - 2) / SNAKE_SIZE)
|
||||
#define GRID_HEIGHT (GAME_AREA_HEIGHT / SNAKE_SIZE)
|
||||
#define STATE_MENU 0
|
||||
#define STATE_PLAYING 1
|
||||
#define STATE_GAME_OVER 2
|
||||
|
||||
#define COLOR_BACKGROUND Black
|
||||
#define COLOR_SNAKE Green
|
||||
#define COLOR_FOOD Red
|
||||
#define COLOR_BORDER White
|
||||
|
||||
Ticker game_timer;
|
||||
|
||||
int snake_x[GRID_WIDTH * GRID_HEIGHT];
|
||||
int snake_y[GRID_WIDTH * GRID_HEIGHT];
|
||||
int snake_length = 1;
|
||||
int snake_dx = 1, snake_dy = 0;
|
||||
int food_x, food_y;
|
||||
int score = 0;
|
||||
int game_state = STATE_MENU;
|
||||
bool initialized = false;
|
||||
|
||||
extern ui::Painter painter;
|
||||
|
||||
void init_game();
|
||||
void update_game();
|
||||
void draw_screen();
|
||||
void draw_snake();
|
||||
void draw_full_snake();
|
||||
void erase_tail(int x, int y);
|
||||
void draw_food();
|
||||
void erase_food();
|
||||
void draw_score();
|
||||
void draw_borders();
|
||||
void spawn_food();
|
||||
bool check_collision();
|
||||
void show_menu();
|
||||
void show_game_over();
|
||||
|
||||
void game_timer_check() {
|
||||
if (game_state == STATE_PLAYING) {
|
||||
update_game();
|
||||
}
|
||||
}
|
||||
|
||||
void init_game() {
|
||||
claim(stdout);
|
||||
set_orientation(2);
|
||||
set_font((unsigned char*)Arial12x12);
|
||||
snake_x[0] = GRID_WIDTH / 2;
|
||||
snake_y[0] = GRID_HEIGHT / 2;
|
||||
snake_length = 1;
|
||||
snake_dx = 1;
|
||||
snake_dy = 0;
|
||||
score = 0;
|
||||
spawn_food();
|
||||
if (game_state == STATE_MENU) {
|
||||
show_menu();
|
||||
} else if (game_state == STATE_PLAYING) {
|
||||
draw_screen();
|
||||
}
|
||||
}
|
||||
|
||||
void spawn_food() {
|
||||
bool valid;
|
||||
do {
|
||||
food_x = rand() % GRID_WIDTH;
|
||||
food_y = rand() % GRID_HEIGHT;
|
||||
valid = true;
|
||||
for (int i = 0; i < snake_length; i++) {
|
||||
if (snake_x[i] == food_x && snake_y[i] == food_y) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (!valid);
|
||||
}
|
||||
|
||||
void update_game() {
|
||||
int new_x = snake_x[0] + snake_dx;
|
||||
int new_y = snake_y[0] + snake_dy;
|
||||
bool ate_food = (new_x == food_x && new_y == food_y);
|
||||
|
||||
int tail_x = snake_x[snake_length - 1];
|
||||
int tail_y = snake_y[snake_length - 1];
|
||||
|
||||
for (int i = snake_length - 1; i > 0; i--) {
|
||||
snake_x[i] = snake_x[i - 1];
|
||||
snake_y[i] = snake_y[i - 1];
|
||||
}
|
||||
|
||||
snake_x[0] = new_x;
|
||||
snake_y[0] = new_y;
|
||||
|
||||
if (ate_food) {
|
||||
snake_x[snake_length] = tail_x;
|
||||
snake_y[snake_length] = tail_y;
|
||||
snake_length++;
|
||||
score += 10;
|
||||
spawn_food();
|
||||
draw_food();
|
||||
} else {
|
||||
erase_tail(tail_x, tail_y);
|
||||
}
|
||||
|
||||
draw_snake();
|
||||
draw_score();
|
||||
|
||||
if (check_collision()) {
|
||||
draw_borders();
|
||||
game_state = STATE_GAME_OVER;
|
||||
show_game_over();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool check_collision() {
|
||||
if (snake_x[0] < 0 || snake_x[0] >= GRID_WIDTH || snake_y[0] < 0 || snake_y[0] >= GRID_HEIGHT) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 1; i < snake_length; i++) {
|
||||
if (snake_x[0] == snake_x[i] && snake_y[0] == snake_y[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void draw_screen() {
|
||||
cls();
|
||||
background(COLOR_BACKGROUND);
|
||||
draw_borders();
|
||||
draw_full_snake();
|
||||
draw_food();
|
||||
draw_score();
|
||||
}
|
||||
|
||||
void draw_snake() {
|
||||
fillrect(1 + snake_x[0] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE,
|
||||
1 + snake_x[0] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[0] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
|
||||
}
|
||||
|
||||
void draw_full_snake() {
|
||||
for (int i = 0; i < snake_length; i++) {
|
||||
fillrect(1 + snake_x[i] * SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE,
|
||||
1 + snake_x[i] * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + snake_y[i] * SNAKE_SIZE + SNAKE_SIZE, COLOR_SNAKE);
|
||||
}
|
||||
}
|
||||
|
||||
void erase_tail(int x, int y) {
|
||||
fillrect(1 + x * SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE,
|
||||
1 + x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
|
||||
}
|
||||
|
||||
void draw_food() {
|
||||
fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
|
||||
1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_FOOD);
|
||||
}
|
||||
|
||||
void erase_food() {
|
||||
fillrect(1 + food_x * SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE,
|
||||
1 + food_x * SNAKE_SIZE + SNAKE_SIZE, GAME_AREA_TOP + food_y * SNAKE_SIZE + SNAKE_SIZE, COLOR_BACKGROUND);
|
||||
}
|
||||
|
||||
void draw_score() {
|
||||
auto style = *ui::Theme::getInstance()->fg_blue;
|
||||
painter.draw_string({5, 5}, style, "Score: " + std::to_string(score));
|
||||
}
|
||||
|
||||
void draw_borders() {
|
||||
rect(0, GAME_AREA_TOP - 1, SCREEN_WIDTH, GAME_AREA_TOP, COLOR_BORDER);
|
||||
rect(0, GAME_AREA_TOP, SCREEN_WIDTH, SCREEN_HEIGHT, COLOR_BORDER);
|
||||
}
|
||||
|
||||
void show_menu() {
|
||||
cls();
|
||||
background(COLOR_BACKGROUND);
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||
auto style_blue = *ui::Theme::getInstance()->fg_blue;
|
||||
painter.draw_string({50, 40}, style_yellow, "* * * SNAKE * * *");
|
||||
painter.draw_string({0, 120}, style_blue, "USE THE D-PAD TO MOVE");
|
||||
painter.draw_string({0, 150}, style_blue, "EAT THE RED SQUARES TO GROW");
|
||||
painter.draw_string({0, 180}, style_blue, "DON'T HIT THE WALLS OR SELF");
|
||||
painter.draw_string({15, 240}, style_green, "** PRESS SELECT TO START **");
|
||||
}
|
||||
|
||||
void show_game_over() {
|
||||
cls();
|
||||
background(COLOR_BACKGROUND);
|
||||
auto style_red = *ui::Theme::getInstance()->fg_red;
|
||||
auto style_yellow = *ui::Theme::getInstance()->fg_yellow;
|
||||
auto style_green = *ui::Theme::getInstance()->fg_green;
|
||||
painter.draw_string({75, 90}, style_red, "GAME OVER");
|
||||
painter.draw_string({74, 150}, style_yellow, "SCORE: " + std::to_string(score));
|
||||
painter.draw_string({20, 220}, style_green, "PRESS SELECT TO RESTART");
|
||||
wait(1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
game_timer.attach(&game_timer_check, 1.0 / 5.0);
|
||||
init_game();
|
||||
}
|
||||
while (1) {
|
||||
if (but_SELECT && (game_state == STATE_MENU || game_state == STATE_GAME_OVER)) {
|
||||
game_state = STATE_PLAYING;
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#include "ui_snake.hpp"
|
||||
|
||||
namespace ui::external_app::snake {
|
||||
|
||||
#include "snake.cpp"
|
||||
|
||||
SnakeView::SnakeView(NavigationView& nav)
|
||||
: nav_{nav} {
|
||||
add_children({&dummy});
|
||||
game_timer.attach(&game_timer_check, 1.0 / 5.0);
|
||||
}
|
||||
|
||||
void SnakeView::on_show() {
|
||||
}
|
||||
|
||||
void SnakeView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
std::srand(LPC_RTC->CTIME0);
|
||||
init_game();
|
||||
}
|
||||
}
|
||||
|
||||
void SnakeView::frame_sync() {
|
||||
check_game_timer();
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool SnakeView::on_key(const KeyEvent key) {
|
||||
if (key == KeyEvent::Select) {
|
||||
if (game_state == STATE_MENU || game_state == STATE_GAME_OVER) {
|
||||
game_state = STATE_PLAYING;
|
||||
init_game();
|
||||
}
|
||||
} else if (game_state == STATE_PLAYING) {
|
||||
if (key == KeyEvent::Left) {
|
||||
if (snake_dx == 0) {
|
||||
snake_dx = -1;
|
||||
snake_dy = 0;
|
||||
}
|
||||
} else if (key == KeyEvent::Right) {
|
||||
if (snake_dx == 0) {
|
||||
snake_dx = 1;
|
||||
snake_dy = 0;
|
||||
}
|
||||
} else if (key == KeyEvent::Up) {
|
||||
if (snake_dy == 0) {
|
||||
snake_dx = 0;
|
||||
snake_dy = -1;
|
||||
}
|
||||
} else if (key == KeyEvent::Down) {
|
||||
if (snake_dy == 0) {
|
||||
snake_dx = 0;
|
||||
snake_dy = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
set_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::snake
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* ------------------------------------------------------------
|
||||
* | Made by RocketGod |
|
||||
* | Find me at https://betaskynet.com |
|
||||
* | Argh matey! |
|
||||
* ------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef __UI_SNAKE_H__
|
||||
#define __UI_SNAKE_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "message.hpp"
|
||||
#include "irq_controls.hpp"
|
||||
#include "random.hpp"
|
||||
#include "lpc43xx_cpp.hpp"
|
||||
#include "limits.h"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace ui::external_app::snake {
|
||||
|
||||
class SnakeView : public View {
|
||||
public:
|
||||
SnakeView(NavigationView& nav);
|
||||
void on_show() override;
|
||||
std::string title() const override { return "Snake"; };
|
||||
void focus() override { dummy.focus(); };
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
bool on_key(KeyEvent key) override;
|
||||
|
||||
private:
|
||||
bool initialized = false;
|
||||
NavigationView& nav_;
|
||||
Button dummy{
|
||||
{240, 0, 0, 0},
|
||||
""};
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::snake
|
||||
|
||||
#endif /*__UI_SNAKE_H__*/
|
||||
@@ -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,101 @@
|
||||
/*
|
||||
* Copyright 2025 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_stopwatch.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "ch.h"
|
||||
|
||||
using namespace portapack;
|
||||
|
||||
namespace ui::external_app::stopwatch {
|
||||
|
||||
StopwatchView::StopwatchView(NavigationView& nav) {
|
||||
add_children({
|
||||
&labels,
|
||||
&button_run_stop,
|
||||
&button_reset_lap,
|
||||
&button_done,
|
||||
&big_display,
|
||||
&lap_display,
|
||||
});
|
||||
|
||||
button_run_stop.on_select = [this](Button&) {
|
||||
if (running)
|
||||
stop();
|
||||
else
|
||||
run();
|
||||
};
|
||||
|
||||
button_reset_lap.on_select = [this](Button&) {
|
||||
if (running)
|
||||
lap();
|
||||
else
|
||||
reset();
|
||||
};
|
||||
|
||||
button_done.on_select = [&nav](Button&) {
|
||||
nav.pop();
|
||||
};
|
||||
}
|
||||
|
||||
void StopwatchView::focus() {
|
||||
button_run_stop.focus();
|
||||
}
|
||||
|
||||
void StopwatchView::run() {
|
||||
running = true;
|
||||
start_time = chTimeNow() - previously_elapsed;
|
||||
button_run_stop.set_text("STOP");
|
||||
button_reset_lap.set_text("LAP");
|
||||
}
|
||||
|
||||
void StopwatchView::stop() {
|
||||
running = false;
|
||||
end_time = chTimeNow();
|
||||
previously_elapsed = end_time - start_time;
|
||||
button_run_stop.set_text("START");
|
||||
button_reset_lap.set_text("RESET");
|
||||
}
|
||||
|
||||
void StopwatchView::reset() {
|
||||
lap_time = end_time = start_time = previously_elapsed = 0;
|
||||
big_display.set(0);
|
||||
lap_display.set(0);
|
||||
}
|
||||
|
||||
void StopwatchView::lap() {
|
||||
lap_time = chTimeNow();
|
||||
lap_display.set((lap_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget
|
||||
}
|
||||
|
||||
void StopwatchView::paint(Painter& painter) {
|
||||
(void)painter;
|
||||
if (running) {
|
||||
end_time = chTimeNow();
|
||||
big_display.set((end_time - start_time) * 1000); // convert elapsed time in ms to MHz for BigFrequency widget
|
||||
}
|
||||
}
|
||||
|
||||
void StopwatchView::frame_sync() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
} // namespace ui::external_app::stopwatch
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2025 Mark Thompson
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_STOPWATCH_H__
|
||||
#define __UI_STOPWATCH_H__
|
||||
|
||||
#include "ui_navigation.hpp"
|
||||
|
||||
namespace ui::external_app::stopwatch {
|
||||
|
||||
class StopwatchView : public View {
|
||||
public:
|
||||
StopwatchView(NavigationView& nav);
|
||||
void focus() override;
|
||||
void paint(Painter& painter) override;
|
||||
void frame_sync();
|
||||
std::string title() const override { return "Stopwatch"; };
|
||||
|
||||
private:
|
||||
void run();
|
||||
void stop();
|
||||
void reset();
|
||||
void lap();
|
||||
|
||||
bool running{false};
|
||||
long long start_time{0};
|
||||
long long end_time{0};
|
||||
long long lap_time{0};
|
||||
long long previously_elapsed{0};
|
||||
|
||||
Labels labels{
|
||||
{{0 * 8, 1 * 16}, "TOTAL:", Theme::getInstance()->fg_light->foreground},
|
||||
{{0 * 8, 7 * 16}, "LAP:", Theme::getInstance()->fg_light->foreground},
|
||||
};
|
||||
|
||||
BigFrequency big_display{
|
||||
{4, 2 * 16 + 4, 28 * 8, 52},
|
||||
0};
|
||||
|
||||
BigFrequency lap_display{
|
||||
{4, 8 * 16 + 4, 28 * 8, 52},
|
||||
0};
|
||||
|
||||
Button button_run_stop{
|
||||
{72, 210, 96, 24},
|
||||
"START"};
|
||||
|
||||
Button button_reset_lap{
|
||||
{72, 240, 96, 24},
|
||||
"RESET"};
|
||||
|
||||
Button button_done{
|
||||
{72, 270, 96, 24},
|
||||
"EXIT"};
|
||||
|
||||
MessageHandlerRegistration message_handler_frame_sync{
|
||||
Message::ID::DisplayFrameSync,
|
||||
[this](const Message* const) {
|
||||
this->frame_sync();
|
||||
}};
|
||||
};
|
||||
|
||||
} // namespace ui::external_app::stopwatch
|
||||
|
||||
#endif /*__UI_STOPWATCH_H__*/
|
||||
@@ -73,8 +73,8 @@ __attribute__((section(".external_app.app_tetris.application_information"), used
|
||||
0xFF,
|
||||
0xF1,
|
||||
},
|
||||
/*.icon_color = */ ui::Color::orange().v,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.icon_color = */ ui::Color::green().v,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
/*.desired_menu_position = */ -1,
|
||||
|
||||
/*.m4_app_tag = portapack::spi_flash::image_tag_none */ {0, 0, 0, 0},
|
||||
|
||||
@@ -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__*/
|
||||
@@ -41,8 +41,8 @@ enum freqman_entry_modulation : uint8_t {
|
||||
AM_MODULATION = 0,
|
||||
NFM_MODULATION,
|
||||
WFM_MODULATION,
|
||||
AMFM_MODULATION, // Added for Wefax.
|
||||
SPEC_MODULATION
|
||||
SPEC_MODULATION,
|
||||
AMFM_MODULATION // Added for Wefax.
|
||||
};
|
||||
|
||||
// TODO: Can these be removed after Recon is migrated to FreqmanDB?
|
||||
|
||||
@@ -49,6 +49,7 @@ options_t freqman_modulations = {
|
||||
{"NFM", 1},
|
||||
{"WFM", 2},
|
||||
{"SPEC", 3},
|
||||
{"AMFM", 4},
|
||||
};
|
||||
|
||||
options_t freqman_bandwidths[5] = {
|
||||
@@ -72,10 +73,6 @@ options_t freqman_bandwidths[5] = {
|
||||
{"180k", 1},
|
||||
{"200k", 0},
|
||||
},
|
||||
{
|
||||
// AMFM for Wefax-
|
||||
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (Wfax) tones.
|
||||
},
|
||||
{
|
||||
// SPEC -- TODO: these should be indexes.
|
||||
{"12k5", 12500},
|
||||
@@ -103,7 +100,12 @@ options_t freqman_bandwidths[5] = {
|
||||
{"4500k", 4500000},
|
||||
{"5000k", 5500000},
|
||||
{"5500k", 5500000}, // Max capture, needs /4 decimation, (22Mhz sampling ADC).
|
||||
}};
|
||||
},
|
||||
{
|
||||
// AMFM for Wefax-
|
||||
{"USB+FM", 5}, // Fixed RX demodul AM config Index 5 : USB+FM for Audio Weather fax (WFAX) tones.
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: these should be indexes.
|
||||
options_t freqman_steps = {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -40,9 +40,9 @@ class ReceiverModel {
|
||||
AMAudio = 0,
|
||||
NarrowbandFMAudio = 1,
|
||||
WidebandFMAudio = 2,
|
||||
AMAudioFMApt = 3, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
SpectrumAnalysis = 4,
|
||||
Capture = 5
|
||||
SpectrumAnalysis = 3,
|
||||
AMAudioFMApt = 4, // Added to handle HF WeatherFax , SSB (USB demod) + Tone_Subcarrier FM demod
|
||||
Capture = 5,
|
||||
};
|
||||
|
||||
struct settings_t {
|
||||
@@ -110,6 +110,8 @@ class ReceiverModel {
|
||||
uint8_t normalized_headphone_volume() const;
|
||||
void set_normalized_headphone_volume(uint8_t v);
|
||||
|
||||
void set_hidden_offset(rf::Frequency offset);
|
||||
|
||||
void enable();
|
||||
void disable();
|
||||
|
||||
@@ -133,6 +135,7 @@ class ReceiverModel {
|
||||
private:
|
||||
settings_t settings_{};
|
||||
bool enabled_ = false;
|
||||
rf::Frequency hidden_offset = 0; // when we need to hide the offset from user, we set this. like when WeFax needs -300Hz.
|
||||
|
||||
int32_t tuning_offset();
|
||||
|
||||
|
||||
@@ -53,13 +53,21 @@ class BoundFrequencyField : public FrequencyField {
|
||||
};
|
||||
|
||||
on_edit = [this, &nav]() {
|
||||
if (on_edit_shown)
|
||||
on_edit_shown();
|
||||
auto freq_view = nav.push<FrequencyKeypadView>(model->target_frequency());
|
||||
freq_view->on_changed = [this](rf::Frequency f) {
|
||||
set_value(f);
|
||||
};
|
||||
nav.set_on_pop([this]() {
|
||||
if (on_edit_hidden)
|
||||
on_edit_hidden();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
std::function<void(void)> on_edit_shown{}; // fired, when the FrequencyKeypadView pops up
|
||||
std::function<void(void)> on_edit_hidden{}; // fired, when the FrequencyKeypadView ended
|
||||
// TODO: override set_step and update the rx model then call base.
|
||||
};
|
||||
|
||||
|
||||
@@ -126,8 +126,8 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"microphone", "Microphone", HOME, Color::green(), &bitmap_icon_microphone, new ViewFactory<MicTXView>()},
|
||||
{"lookingglass", "Looking Glass", HOME, Color::green(), &bitmap_icon_looking, new ViewFactory<GlassView>()},
|
||||
{nullptr, "Utilities", HOME, Color::cyan(), &bitmap_icon_utilities, new ViewFactory<UtilitiesMenuView>()},
|
||||
{nullptr, "Games", HOME, Color::cyan(), &bitmap_icon_games, new ViewFactory<GamesMenuView>()},
|
||||
{nullptr, "Settings", HOME, Color::cyan(), &bitmap_icon_setup, new ViewFactory<SettingsMenuView>()},
|
||||
{nullptr, "Debug", HOME, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
/* RX ********************************************************************/
|
||||
{"adsbrx", "ADS-B", RX, Color::green(), &bitmap_icon_adsb, new ViewFactory<ADSBRxView>()},
|
||||
{"ais", "AIS Boats", RX, Color::green(), &bitmap_icon_ais, new ViewFactory<AISAppView>()},
|
||||
@@ -165,6 +165,7 @@ const NavigationView::AppList NavigationView::appList = {
|
||||
{"iqtrim", "IQ Trim", UTILITIES, Color::orange(), &bitmap_icon_trim, new ViewFactory<IQTrimView>()},
|
||||
{"notepad", "Notepad", UTILITIES, Color::dark_cyan(), &bitmap_icon_notepad, new ViewFactory<TextEditorView>()},
|
||||
{nullptr, "SD Over USB", UTILITIES, Color::yellow(), &bitmap_icon_hackrf, new ViewFactory<SdOverUsbView>()},
|
||||
{nullptr, "Debug", UTILITIES, Color::light_grey(), &bitmap_icon_debug, new ViewFactory<DebugMenuView>()},
|
||||
//{"testapp", "Test App", UTILITIES, Color::dark_grey(), nullptr, new ViewFactory<TestView>()},
|
||||
// Dangerous apps.
|
||||
{nullptr, "Flash Utility", UTILITIES, Color::red(), &bitmap_icon_peripherals_details, new ViewFactory<FlashUtilityView>()},
|
||||
@@ -868,6 +869,22 @@ void UtilitiesMenuView::on_populate() {
|
||||
add_external_items(nav_, app_location_t::UTILITIES, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* GamesMenuView ********************************************************/
|
||||
|
||||
GamesMenuView::GamesMenuView(NavigationView& nav)
|
||||
: nav_(nav) {
|
||||
set_max_rows(2);
|
||||
}
|
||||
|
||||
void GamesMenuView::on_populate() {
|
||||
bool return_icon = pmem::show_gui_return_icon();
|
||||
if (return_icon) {
|
||||
add_item({"..", Theme::getInstance()->fg_light->foreground, &bitmap_icon_previous, [this]() { nav_.pop(); }});
|
||||
}
|
||||
add_apps(nav_, *this, GAMES);
|
||||
add_external_items(nav_, app_location_t::GAMES, *this, return_icon ? 1 : 0);
|
||||
}
|
||||
|
||||
/* SystemMenuView ********************************************************/
|
||||
|
||||
void SystemMenuView::hackrf_mode(NavigationView& nav) {
|
||||
|
||||
@@ -389,6 +389,16 @@ class UtilitiesMenuView : public BtnGridView {
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class GamesMenuView : public BtnGridView {
|
||||
public:
|
||||
GamesMenuView(NavigationView& nav);
|
||||
std::string title() const override { return "Games"; };
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
void on_populate() override;
|
||||
};
|
||||
|
||||
class SystemMenuView : public BtnGridView {
|
||||
public:
|
||||
SystemMenuView(NavigationView& nav);
|
||||
|
||||
@@ -668,6 +668,14 @@ DeclareTargets(POSK ookstream)
|
||||
|
||||
|
||||
|
||||
### WeFax Rx
|
||||
|
||||
set(MODE_CPPSRC
|
||||
proc_wefaxrx.cpp
|
||||
)
|
||||
DeclareTargets(PWFX wefaxrx)
|
||||
|
||||
|
||||
### HackRF "factory" firmware
|
||||
|
||||
add_custom_command(
|
||||
|
||||
@@ -72,7 +72,7 @@ void AudioOutput::on_block(const buffer_f32_t& audio) {
|
||||
if (do_processing) {
|
||||
const auto audio_present_now = squelch.execute(audio);
|
||||
|
||||
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except WFAX with "lpf-coef".
|
||||
hpf.execute_in_place(audio); // IIRBiquadFilter name is "hpf", but we will call with "hpf-coef" for all except AMFM (WFAX) with "lpf-coef"
|
||||
deemph.execute_in_place(audio);
|
||||
|
||||
audio_present_history = (audio_present_history << 1) | (audio_present_now ? 1 : 0);
|
||||
|
||||
@@ -79,7 +79,7 @@ static inline float angle_precise(const complex32_t t) {
|
||||
return atan2f(t.imag(), t.real());
|
||||
}
|
||||
|
||||
buffer_f32_t SSB_FM::execute( // Added to handle WFAX (HF weather map )-
|
||||
buffer_f32_t SSB_FM::execute( // Added to handle AMFM (WFAX, HF weather map )
|
||||
const buffer_c16_t& src, // input arg , pointer Complex c16 i,q buffer.
|
||||
const buffer_f32_t& dst) { // input arg , pointer f32 buffer audio demodulated
|
||||
complex16_t* src_p = src.p; // removed const ; init src_p pointer with the mem address pointed by src.p.
|
||||
|
||||
@@ -48,7 +48,7 @@ class SSB {
|
||||
static constexpr float k = 1.0f / 32768.0f;
|
||||
};
|
||||
|
||||
class SSB_FM { // Added to handle WFAX-
|
||||
class SSB_FM { // Added to handle AMFM for WFAX
|
||||
public:
|
||||
buffer_f32_t execute(
|
||||
const buffer_c16_t& src,
|
||||
@@ -56,7 +56,7 @@ class SSB_FM { // Added to handle WFAX-
|
||||
|
||||
private:
|
||||
static constexpr float k = 1.0f / 32768.0f;
|
||||
dsp::Real_to_Complex real_to_complex; // It is a member variable of SSB_FM.
|
||||
dsp::Real_to_Complex real_to_complex{}; // It is a member variable of SSB_FM.
|
||||
};
|
||||
|
||||
class FM {
|
||||
|
||||
@@ -65,7 +65,9 @@ buffer_f32_t NarrowbandAMAudio::demodulate(const buffer_c16_t& channel) {
|
||||
return demod_ssb_fm.execute(channel, audio_buffer); // Calling a derivative of demod_ssb (USB) , but with different FIR taps + FM audio tones demod.
|
||||
break;
|
||||
|
||||
// return demod am as a default
|
||||
default:
|
||||
return demod_am.execute(channel, audio_buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -110,9 +112,14 @@ void NarrowbandAMAudio::configure(const AMConfigureMessage& message) {
|
||||
channel_filter_low_f = message.channel_filter.low_frequency_normalized * channel_filter_input_fs;
|
||||
channel_filter_high_f = message.channel_filter.high_frequency_normalized * channel_filter_input_fs;
|
||||
channel_filter_transition = message.channel_filter.transition_normalized * channel_filter_input_fs;
|
||||
channel_spectrum.set_decimation_factor(1.0f);
|
||||
|
||||
// modulation_ssb = (message.modulation == AMConfigureMessage::Modulation::SSB); // originally we had just 2 AM types of demod. (DSB , SSB)
|
||||
modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
|
||||
modulation_ssb = (int)message.modulation; // now sending by message , 3 types of AM demod : enum class Modulation : int32_t {DSB = 0, SSB = 1, SSB_FM = 2}
|
||||
if (modulation_ssb == (int)(AMConfigureMessage::Modulation::SSB_FM)) {
|
||||
channel_spectrum.set_decimation_factor(6.0f); // zooming for better tuning {SSB_FM = 2}
|
||||
} else {
|
||||
channel_spectrum.set_decimation_factor(1.0f); // no zooming .{DSB = 0, SSB = 1,}
|
||||
}
|
||||
audio_output.configure(message.audio_hpf_lpf_config); // hpf in all AM demod modes (AM-6K/9K, USB/LSB,DSB), except Wefax (lpf there).
|
||||
|
||||
configured = true;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ constexpr iir_biquad_df2_config_t half_band_lpf_config[5] = {
|
||||
{1.0f, 0.08720754, 1.0f, 1.0f, 0.00220944f, 0.98743139f}};
|
||||
|
||||
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5, rs = 60.0, Wn = 0.99, btype = 'lowpass', output="sos")
|
||||
// 6khz cutofff @fs:12Khz , used in WFAX demod.
|
||||
// 6khz cutofff @fs:12Khz , used in AMFM demod for WFAX
|
||||
constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = {
|
||||
{0.88095275f, 1.76184993f, 0.88095275f, 1.0f, 1.89055677f, 0.89616378f},
|
||||
{1.0f, 1.99958798f, 1.0f, 1.0f, 1.9781807f, 0.98002549f},
|
||||
@@ -43,7 +43,7 @@ constexpr iir_biquad_df2_config_t full_band_lpf_config[5] = {
|
||||
{1.0f, 1.99909558f, 1.0f, 1.0f, 1.9986187f, 0.99960319f}};
|
||||
|
||||
// scipy.signal.iirfilter(ftype="ellip", N = 10, rp = 0.5 , rs = 60.0, Wn = 0.25, btype = 'lowpass', output="sos")
|
||||
// 1.5khz cutofff @fs:12Khz, used in WFAX demod.
|
||||
// 1.5khz cutofff @fs:12Khz, used in AMFM demod for WFAX
|
||||
constexpr iir_biquad_df2_config_t quarter_band_lpf_config[5] = {
|
||||
{0.00349312f, 0.00319397f, 0.00349312f, 1.0f, -1.53025211f, 0.6203438f},
|
||||
{1.0f, -0.83483341f, 1.0f, 1.0f, -1.47619047f, 0.77120659f},
|
||||
|
||||
@@ -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()));
|
||||
@@ -1329,6 +1341,9 @@ bool debug_dump() {
|
||||
case ReceiverModel::Mode::Capture:
|
||||
pmem_dump_file.write_line("modulation: Mode::Capture");
|
||||
break;
|
||||
case ReceiverModel::Mode::AMAudioFMApt:
|
||||
pmem_dump_file.write_line("modulation: Mode::AMAudioFMApt");
|
||||
break;
|
||||
default:
|
||||
pmem_dump_file.write_line("modulation: !!unknown mode!!");
|
||||
break;
|
||||
|
||||
@@ -205,7 +205,6 @@ bool config_converter();
|
||||
bool config_updown_converter();
|
||||
int64_t config_converter_freq();
|
||||
bool show_gui_return_icon();
|
||||
bool show_bigger_qr_code();
|
||||
bool hide_clock();
|
||||
bool clock_with_date();
|
||||
bool config_login();
|
||||
@@ -217,7 +216,6 @@ bool disable_touchscreen();
|
||||
void set_gui_return_icon(bool v);
|
||||
void set_load_app_settings(bool v);
|
||||
void set_save_app_settings(bool v);
|
||||
void set_show_bigger_qr_code(bool v);
|
||||
void set_config_disable_external_tcxo(bool v);
|
||||
void set_config_sdcard_high_speed_io(bool v, bool save);
|
||||
void set_config_disable_config_mode(bool v);
|
||||
@@ -342,6 +340,9 @@ bool ui_hide_battery_icon();
|
||||
bool ui_hide_sd_card();
|
||||
uint8_t ui_theme_id();
|
||||
bool ui_override_batt_calc();
|
||||
bool ui_button_repeat_delay();
|
||||
bool ui_button_repeat_speed();
|
||||
bool ui_button_long_press_delay();
|
||||
void set_ui_hide_speaker(bool v);
|
||||
void set_ui_hide_mute(bool v);
|
||||
void set_ui_hide_converter(bool v);
|
||||
@@ -356,6 +357,9 @@ void set_ui_hide_battery_icon(bool v);
|
||||
void set_ui_hide_sd_card(bool v);
|
||||
void set_ui_theme_id(uint8_t v);
|
||||
void set_ui_override_batt_calc(bool v);
|
||||
void set_ui_button_repeat_delay(bool v);
|
||||
void set_ui_button_repeat_speed(bool v);
|
||||
void set_ui_button_long_press_delay(bool v);
|
||||
|
||||
// sd persisting settings
|
||||
bool should_use_sdcard_for_pmem();
|
||||
|
||||
@@ -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'};
|
||||
|
||||
|
||||
@@ -72,7 +72,8 @@ enum app_location_t : uint32_t {
|
||||
TX,
|
||||
DEBUG,
|
||||
HOME,
|
||||
SETTINGS
|
||||
SETTINGS,
|
||||
GAMES
|
||||
};
|
||||
|
||||
struct standalone_application_information_t {
|
||||
|
||||
@@ -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 |
@@ -65,7 +65,7 @@ __attribute__((section(".standalone_application_information"), used)) standalone
|
||||
0x00,
|
||||
},
|
||||
/*.icon_color = 16 bit: 5R 6G 5B*/ 0x0000FFE0,
|
||||
/*.menu_location = */ app_location_t::UTILITIES,
|
||||
/*.menu_location = */ app_location_t::GAMES,
|
||||
|
||||
/*.initialize_app = */ initialize,
|
||||
/*.on_event = */ on_event,
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
#
|
||||
# Copyleft (ɔ) 2024 Whiterose from the Dark Army
|
||||
#
|
||||
# This file is part of PortaPack.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2, or (at your option)
|
||||
# any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; see the file COPYING. If not, write to
|
||||
# the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def parse_memory_regions(ld_file_path):
|
||||
regions = []
|
||||
|
||||
with open(ld_file_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
memory_section = re.search(r'MEMORY\s*\{(.*?)\}', content, re.DOTALL)
|
||||
if not memory_section:
|
||||
print("ERROR: Could not find MEMORY section in the linker script.")
|
||||
return []
|
||||
|
||||
memory_content = memory_section.group(1)
|
||||
|
||||
pattern = r'ram_external_app_(\w+)\s*\(rwx\)\s*:\s*org\s*=\s*(0x[A-Fa-f0-9]+),\s*len\s*=\s*(\d+)k'
|
||||
matches = re.finditer(pattern, memory_content)
|
||||
|
||||
for match in matches:
|
||||
app_name = match.group(1)
|
||||
address = int(match.group(2), 16) # string with hex -> int
|
||||
length = int(match.group(3)) * 1024 # kb -> bytes
|
||||
|
||||
regions.append({
|
||||
'app_name': app_name,
|
||||
'address': address,
|
||||
'length': length
|
||||
})
|
||||
|
||||
return sorted(regions, key=lambda x: x['address'])
|
||||
|
||||
def validate_memory_regions(regions):
|
||||
if not regions:
|
||||
return False
|
||||
|
||||
expected_step = 0x10000 # 64k as step (not sure why)
|
||||
expected_base = 0xADB10000 # the start (not sure why this one)
|
||||
expected_size = 32 * 1024 # 32k
|
||||
issues_found = False
|
||||
|
||||
print("\n")
|
||||
print(f"checking {len(regions)} external apps address memory regions")
|
||||
|
||||
if regions[0]['address'] != expected_base:
|
||||
print(f"WARNING: external app first region should start at {hex(expected_base)}, but starts at {hex(regions[0]['address'])}")
|
||||
issues_found = True
|
||||
|
||||
for i, region in enumerate(regions):
|
||||
expected_address = expected_base + (i * expected_step)
|
||||
|
||||
# address count
|
||||
if region['address'] != expected_address:
|
||||
print(f"WARNING: external app region '{region['app_name']}' has incorrect address")
|
||||
print(f"want: {hex(expected_address)}, Found: {hex(region['address'])}")
|
||||
issues_found = True
|
||||
|
||||
# size
|
||||
if region['length'] != expected_size:
|
||||
print(f"WARNING: external app region '{region['app_name']}' has incorrect size")
|
||||
print(f"want: {expected_size//1024}KB, Found: {region['length']//1024}KB")
|
||||
issues_found = True
|
||||
|
||||
# overlap
|
||||
if i < len(regions) - 1:
|
||||
next_region = regions[i + 1]
|
||||
if region['address'] + region['length'] > next_region['address']:
|
||||
print(f"WARNING: external app region '{region['app_name']}' overlapped with '{next_region['app_name']}'")
|
||||
issues_found = True
|
||||
|
||||
return not issues_found
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
# arg
|
||||
input_path = Path(sys.argv[1])
|
||||
if input_path.is_dir():
|
||||
ld_file_path = input_path / "external.ld"
|
||||
if not ld_file_path.exists():
|
||||
print(f"some issue causing that we can't see external app's linker script {input_path}, pass.")
|
||||
return
|
||||
else:
|
||||
ld_file_path = input_path
|
||||
else:
|
||||
# if no arg, this assume this script is in mayhemrepo/firmware/tools
|
||||
ld_file_path = Path("..") / "application" / "external" / "external.ld"
|
||||
|
||||
try:
|
||||
regions = parse_memory_regions(ld_file_path)
|
||||
|
||||
if not regions:
|
||||
print("some issue causing that we can't see external app's linker script's address list, pass")
|
||||
|
||||
return
|
||||
|
||||
if validate_memory_regions(regions):
|
||||
print("external app addr seems correct, pass")
|
||||
else:
|
||||
print("\nWARENING: It seems you are having incorrect external app addresses.")
|
||||
|
||||
except Exception as e:
|
||||
print(f"err: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -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
|
||||