From 4aa5fc1fbef00facd279e818f2ad61d690cc828a Mon Sep 17 00:00:00 2001 From: Totoo Date: Wed, 19 Mar 2025 00:31:40 +0100 Subject: [PATCH] WeFax rx ext app (#2566) * wf3 * Ookbrute (#2354) * Revert "Ookbrute (#2354)" This reverts commit abb8143eec7b8106a6523f808109f9d394d721f1. * fix * test edition * re enable ble * re enable ert * steal amfm stuff * something happens * save bmp on start btn * kinda works * exit crash fixed * redline, remove some hardcoded * removed cpu killer red line, and some fixes * simplify #1 * seems ok. time to improve * added hidden freq offset to receiver model, so wefax can be set to the "correct" freq without users needs to substract 300 hz * badly implemented sync detection, and disabled it. * fix for fix * fixes * fix offset to real life off * no line on freq enter * fixes --- firmware/application/baseband_api.cpp | 5 + firmware/application/baseband_api.hpp | 1 + firmware/application/external/external.cmake | 6 + firmware/application/external/external.ld | 8 + .../application/external/wefax_rx/main.cpp | 83 ++++++++ .../external/wefax_rx/ui_wefax_rx.cpp | 152 ++++++++++++++ .../external/wefax_rx/ui_wefax_rx.hpp | 153 ++++++++++++++ firmware/application/receiver_model.cpp | 2 +- firmware/application/receiver_model.hpp | 3 + firmware/application/ui/ui_freq_field.hpp | 8 + firmware/baseband/CMakeLists.txt | 8 + firmware/baseband/proc_wefaxrx.cpp | 186 ++++++++++++++++++ firmware/baseband/proc_wefaxrx.hpp | 104 ++++++++++ firmware/common/bmpfile.cpp | 2 +- firmware/common/message.hpp | 32 +++ firmware/common/spi_image.hpp | 1 + 16 files changed, 752 insertions(+), 2 deletions(-) create mode 100644 firmware/application/external/wefax_rx/main.cpp create mode 100644 firmware/application/external/wefax_rx/ui_wefax_rx.cpp create mode 100644 firmware/application/external/wefax_rx/ui_wefax_rx.hpp create mode 100644 firmware/baseband/proc_wefaxrx.cpp create mode 100644 firmware/baseband/proc_wefaxrx.hpp diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index 0c8eaf68..8ff5c467 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -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)}; diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index e8e1bd58..efc30623 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -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(); diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 7fe07491..6e3f84c6 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -115,6 +115,11 @@ 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 @@ -215,6 +220,7 @@ set(EXTAPPLIST #acars_rx ookbrute ook_editor + wefax_rx shoppingcart_lock flippertx remote diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index baf2266c..71ba55f6 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -67,6 +67,7 @@ MEMORY ram_external_app_breakout(rwx) : org = 0xADDA0000, len = 32k ram_external_app_snake(rwx) : org = 0xADDB0000, len = 32k ram_external_app_stopwatch(rwx) : org = 0xADDC0000, len = 32k + ram_external_app_wefax_rx(rwx) : org = 0xADDD0000, len = 32k } SECTIONS @@ -334,4 +335,11 @@ SECTIONS 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 + + } diff --git a/firmware/application/external/wefax_rx/main.cpp b/firmware/application/external/wefax_rx/main.cpp new file mode 100644 index 00000000..0ab7f5e7 --- /dev/null +++ b/firmware/application/external/wefax_rx/main.cpp @@ -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(); +} +} // 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 +}; +} diff --git a/firmware/application/external/wefax_rx/ui_wefax_rx.cpp b/firmware/application/external/wefax_rx/ui_wefax_rx.cpp new file mode 100644 index 00000000..88052bc0 --- /dev/null +++ b/firmware/application/external/wefax_rx/ui_wefax_rx.cpp @@ -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 diff --git a/firmware/application/external/wefax_rx/ui_wefax_rx.hpp b/firmware/application/external/wefax_rx/ui_wefax_rx.hpp new file mode 100644 index 00000000..a3de3a00 --- /dev/null +++ b/firmware/application/external/wefax_rx/ui_wefax_rx.hpp @@ -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(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(p); + on_image(message); + }}; +}; + +} // namespace ui::external_app::wefax_rx + +#endif /*__UI_WEFAX_RX_H__*/ diff --git a/firmware/application/receiver_model.cpp b/firmware/application/receiver_model.cpp index 2dcf6b03..0be59fde 100644 --- a/firmware/application/receiver_model.cpp +++ b/firmware/application/receiver_model.cpp @@ -279,7 +279,7 @@ 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::update_baseband_bandwidth() { diff --git a/firmware/application/receiver_model.hpp b/firmware/application/receiver_model.hpp index 25acd67f..b13647b8 100644 --- a/firmware/application/receiver_model.hpp +++ b/firmware/application/receiver_model.hpp @@ -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) { hidden_offset = 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(); diff --git a/firmware/application/ui/ui_freq_field.hpp b/firmware/application/ui/ui_freq_field.hpp index c63e3791..d604cf1d 100644 --- a/firmware/application/ui/ui_freq_field.hpp +++ b/firmware/application/ui/ui_freq_field.hpp @@ -53,13 +53,21 @@ class BoundFrequencyField : public FrequencyField { }; on_edit = [this, &nav]() { + if (on_edit_shown) + on_edit_shown(); auto freq_view = nav.push(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 on_edit_shown{}; // fired, when the FrequencyKeypadView pops up + std::function on_edit_hidden{}; // fired, when the FrequencyKeypadView ended // TODO: override set_step and update the rx model then call base. }; diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 3f8f1af7..ace69726 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -668,6 +668,14 @@ DeclareTargets(POSK ookstream) +### WeFax Rx + +set(MODE_CPPSRC + proc_wefaxrx.cpp +) +DeclareTargets(PWFX wefaxrx) + + ### HackRF "factory" firmware add_custom_command( diff --git a/firmware/baseband/proc_wefaxrx.cpp b/firmware/baseband/proc_wefaxrx.cpp new file mode 100644 index 00000000..26e965c8 --- /dev/null +++ b/firmware/baseband/proc_wefaxrx.cpp @@ -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 +#include + +#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(message)); + break; + + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(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(message.config)); + } else { + audio_output.set_stream(nullptr); + } +} + +int main() { + audio::dma::init_audio_out(); + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_wefaxrx.hpp b/firmware/baseband/proc_wefaxrx.hpp new file mode 100644 index 00000000..a82aa720 --- /dev/null +++ b/firmware/baseband/proc_wefaxrx.hpp @@ -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 + +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 dst{}; + const buffer_c16_t dst_buffer{ + dst.data(), + dst.size()}; + std::array 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__*/ diff --git a/firmware/common/bmpfile.cpp b/firmware/common/bmpfile.cpp index 46e74340..d39c2fb6 100644 --- a/firmware/common/bmpfile.cpp +++ b/firmware/common/bmpfile.cpp @@ -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; -} \ No newline at end of file +} diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 08051925..e27368cf 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -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__*/ diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index 122ef23c..d7c9bec0 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -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'};