From db65ae192a867d7272e7a682deed4f7a155a9482 Mon Sep 17 00:00:00 2001 From: Totoo Date: Sun, 11 May 2025 20:24:52 +0200 Subject: [PATCH] Noaa apt decoder (#2648) --- firmware/application/baseband_api.cpp | 5 + firmware/application/baseband_api.hpp | 1 + firmware/application/bitmap.hpp | 38 ++++ firmware/application/external/external.cmake | 6 + firmware/application/external/external.ld | 8 + .../application/external/noaaapt_rx/main.cpp | 83 ++++++++ .../external/noaaapt_rx/ui_noaaapt_rx.cpp | 155 +++++++++++++++ .../external/noaaapt_rx/ui_noaaapt_rx.hpp | 144 ++++++++++++++ firmware/baseband/CMakeLists.txt | 8 + firmware/baseband/audio_output.cpp | 12 ++ firmware/baseband/audio_output.hpp | 1 + firmware/baseband/proc_noaaapt_rx.cpp | 180 ++++++++++++++++++ firmware/baseband/proc_noaaapt_rx.hpp | 124 ++++++++++++ firmware/common/message.hpp | 26 +++ firmware/common/spi_image.hpp | 1 + firmware/graphics/icon_noaa.png | Bin 0 -> 365 bytes 16 files changed, 792 insertions(+) create mode 100644 firmware/application/external/noaaapt_rx/main.cpp create mode 100644 firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp create mode 100644 firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp create mode 100644 firmware/baseband/proc_noaaapt_rx.cpp create mode 100644 firmware/baseband/proc_noaaapt_rx.hpp create mode 100644 firmware/graphics/icon_noaa.png diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index 2b5d8ae46..f13adcf99 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -322,6 +322,11 @@ void set_wefax_config(uint8_t lpm = 120, uint8_t ioc = 0) { send_message(&message); } +void set_noaaapt_config() { + const NoaaAptRxConfigureMessage message{}; + 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 43153b336..2b4671306 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -100,6 +100,7 @@ void set_siggen_config(const uint32_t bw, const uint32_t shape, const uint32_t d 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 set_noaaapt_config(); void request_roger_beep(); void request_rssi_beep(); diff --git a/firmware/application/bitmap.hpp b/firmware/application/bitmap.hpp index b895e9fd6..f3e1767d2 100644 --- a/firmware/application/bitmap.hpp +++ b/firmware/application/bitmap.hpp @@ -2435,6 +2435,44 @@ static constexpr Bitmap bitmap_icon_new_file{ {16, 16}, bitmap_icon_new_file_data}; +static constexpr uint8_t bitmap_icon_noaa_data[] = { + 0x1C, + 0x80, + 0x3C, + 0x40, + 0x78, + 0x18, + 0xF0, + 0x20, + 0xE0, + 0x26, + 0x00, + 0x0F, + 0x80, + 0x0F, + 0xC0, + 0x07, + 0xE1, + 0x1B, + 0xC5, + 0x39, + 0x95, + 0x78, + 0x35, + 0xF0, + 0x09, + 0xE0, + 0x72, + 0xC0, + 0x04, + 0x00, + 0x78, + 0x00, +}; +static constexpr Bitmap bitmap_icon_noaa{ + {16, 16}, + bitmap_icon_noaa_data}; + static constexpr uint8_t bitmap_icon_notepad_data[] = { 0x0C, 0x00, diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 66a9ceb3e..2ab738950 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -111,6 +111,11 @@ set(EXTCPPSRC external/wefax_rx/main.cpp external/wefax_rx/ui_wefax_rx.cpp + #noaaapt_rx + external/noaaapt_rx/main.cpp + external/noaaapt_rx/ui_noaaapt_rx.cpp + + #shoppingcart_lock external/shoppingcart_lock/main.cpp @@ -239,6 +244,7 @@ set(EXTAPPLIST ookbrute ook_editor wefax_rx + noaaapt_rx shoppingcart_lock flippertx remote diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 4f03965f0..ff7c3c189 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -73,6 +73,7 @@ MEMORY ram_external_app_scanner (rwx) : org = 0xADE00000, len = 32k ram_external_app_level (rwx) : org = 0xADE10000, len = 32k ram_external_app_gfxeq (rwx) : org = 0xADE20000, len = 32k + ram_external_app_noaaapt_rx (rwx) : org = 0xADE30000, len = 32k } SECTIONS @@ -334,12 +335,19 @@ 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 + .external_app_noaaapt_rx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_noaaapt_rx.application_information)); + *(*ui*external_app*noaaapt_rx*); + } > ram_external_app_noaaapt_rx + .external_app_breakout : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_breakout.application_information)); diff --git a/firmware/application/external/noaaapt_rx/main.cpp b/firmware/application/external/noaaapt_rx/main.cpp new file mode 100644 index 000000000..0e4c8e8fb --- /dev/null +++ b/firmware/application/external/noaaapt_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_noaaapt_rx.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::noaaapt_rx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::noaaapt_rx + +extern "C" { + +__attribute__((section(".external_app.app_noaaapt_rx.application_information"), used)) application_information_t _application_information_noaaapt_rx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::noaaapt_rx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "NOAA APT", + /*.bitmap_data = */ { + 0x1C, + 0x80, + 0x3C, + 0x40, + 0x78, + 0x18, + 0xF0, + 0x20, + 0xE0, + 0x26, + 0x00, + 0x0F, + 0x80, + 0x0F, + 0xC0, + 0x07, + 0xE1, + 0x1B, + 0xC5, + 0x39, + 0x95, + 0x78, + 0x35, + 0xF0, + 0x09, + 0xE0, + 0x72, + 0xC0, + 0x04, + 0x00, + 0x78, + 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_noaaapt_rx */ {'P', 'N', 'O', 'A'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp new file mode 100644 index 000000000..b93dd3362 --- /dev/null +++ b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.cpp @@ -0,0 +1,155 @@ +/* + * 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?!? + - Auto doppler correction + - fix and enable sync detection + - auto start / stop bmp save on each image +*/ + +#include "ui_noaaapt_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::noaaapt_rx { + +void NoaaAptRxView::focus() { + field_frequency.focus(); +} + +NoaaAptRxView::NoaaAptRxView(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, + //&check_wav, // enable this or the record view, but not both. yet it has some error, says "Invalid object" a lot. so disabled it + //&record_view, // + &button_ss}); + + record_view.set_filename_date_frequency(true); + record_view.on_error = [&nav](std::string message) { + nav.display_modal("Error", message); + }; + record_view.set_sampling_rate(12000); + + field_frequency.set_step(100); + field_frequency.on_edit_shown = [this]() { + paused = true; + }; + field_frequency.on_edit_hidden = [this]() { + paused = false; + }; + audio::set_rate(audio::Rate::Hz_12000); + audio::output::start(); + receiver_model.set_hidden_offset(NOAAAPT_FREQ_OFFSET); + receiver_model.set_baseband_bandwidth(1750000); + receiver_model.set_sampling_rate(3072000); + 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]); + if (check_wav.value()) { + record_view.stop(); + } + return; + } + if (check_wav.value()) { + record_view.start(); + } + ensure_directory("/BMP"); + bmp.create("/BMP/noaa_" + to_string_timestamp(rtc_time::now()) + ".bmp", NOAAAPT_PX_SIZE, 1); + + button_ss.set_text(LanguageHelper::currentMessages[LANG_STOP]); + }; + on_settings_changed(); +} + +NoaaAptRxView::~NoaaAptRxView() { + stopping = true; + receiver_model.set_hidden_offset(0); + bmp.close(); + receiver_model.disable(); + baseband::shutdown(); + audio::output::stop(); + record_view.stop(); +} + +void NoaaAptRxView::on_settings_changed() { + baseband::set_noaaapt_config(); +} + +void NoaaAptRxView::on_status(NoaaAptRxStatusDataMessage 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 NoaaAptRxView::on_image(NoaaAptRxImageDataMessage msg) { + if ((line_num) >= 320 - NOAA_IMG_START_ROW * 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 == NOAAAPT_PX_SIZE) { + line_in_part = 0; + line_num++; + bmp.expand_y_delta(1); + } + + uint16_t xpos = line_in_part / (NOAAAPT_PX_SIZE / 240); + if (xpos >= 240) xpos = 239; + line_buffer[xpos] = pxl; + if ((line_in_part == 0)) { + portapack::display.render_line({0, line_num + NOAA_IMG_START_ROW * 16}, 240, line_buffer); + } + } +} + +} // namespace ui::external_app::noaaapt_rx diff --git a/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp new file mode 100644 index 000000000..7e9bdd653 --- /dev/null +++ b/firmware/application/external/noaaapt_rx/ui_noaaapt_rx.hpp @@ -0,0 +1,144 @@ +/* + * 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_noaaapt_RX_H__ +#define __UI_noaaapt_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::noaaapt_rx { + +#define NOAAAPT_PX_SIZE 2080 + +#define NOAAAPT_FREQ_OFFSET 0 + +#define NOAA_IMG_START_ROW 4 + +class NoaaAptRxView : public View { + public: + NoaaAptRxView(NavigationView& nav); + ~NoaaAptRxView(); + + void focus() override; + + std::string title() const override { return "NOAA APT"; }; + + private: + void on_settings_changed(); + void on_status(NoaaAptRxStatusDataMessage msg); + void on_image(NoaaAptRxImageDataMessage msg); + + bool stopping = false; + + 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_noaaapt", + app_settings::Mode::RX, + {}}; + + 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_}; + + RecordView record_view{ + {0 * 8, 2 * 16, 30 * 8, 1 * 16}, + u"AUD", + u"AUDIO", + RecordView::FileType::WAV, + 4096, + 4}; + + Checkbox check_wav{ + {0 * 8, 2 * 16}, + 12, + "Save WAV too", + true}; + + /*Labels labels{ + {{1 * 8, 1 * 16}, "LPM:", Theme::getInstance()->fg_light->foreground}, + {{13 * 8, 1 * 16}, "IOC:", Theme::getInstance()->fg_light->foreground}, + };*/ + + Text txt_status{ + {0 * 8, 1 * 16, 20 * 8, 16}, + }; + + Button button_ss{ + {190, 1 * 16, 5 * 8, 16}, + LanguageHelper::currentMessages[LANG_START]}; + + MessageHandlerRegistration message_handler_stats{ + Message::ID::NoaaAptRxStatusData, + [this](const Message* const p) { + if (stopping || paused) return; + const auto message = *reinterpret_cast(p); + on_status(message); + }}; + + MessageHandlerRegistration message_handler_image{ + Message::ID::NoaaAptRxImageData, + [this](const Message* const p) { + if (stopping || paused) return; + const auto message = *reinterpret_cast(p); + on_image(message); + }}; +}; + +} // namespace ui::external_app::noaaapt_rx + +#endif /*__UI_noaaapt_RX_H__*/ diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index ace697266..28d094dc4 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -676,6 +676,14 @@ set(MODE_CPPSRC DeclareTargets(PWFX wefaxrx) +### NoaaApt Rx + +set(MODE_CPPSRC + proc_noaaapt_rx.cpp +) +DeclareTargets(PNOA noaaapt_rx) + + ### HackRF "factory" firmware add_custom_command( diff --git a/firmware/baseband/audio_output.cpp b/firmware/baseband/audio_output.cpp index f9b529786..52f091369 100644 --- a/firmware/baseband/audio_output.cpp +++ b/firmware/baseband/audio_output.cpp @@ -65,6 +65,18 @@ void AudioOutput::apt_write(const buffer_s16_t& audio) { write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate}); } +void AudioOutput::apt_write(const buffer_s16_t& audio, std::array& audio_f) { + for (size_t i = 0; i < audio.count; i++) { + cur = audio.p[i]; + cur2 = cur * cur; + mag_am = sqrtf(prev2 + cur2 - (2 * prev * cur * cos_theta)) / sin_theta; + audio_f[i] = mag_am * ki; // normalize. + prev = cur; + prev2 = cur2; + } + write(buffer_f32_t{audio_f.data(), audio.count, audio.sampling_rate}); +} + void AudioOutput::write(const buffer_s16_t& audio) { std::array audio_f; for (size_t i = 0; i < audio.count; i++) { diff --git a/firmware/baseband/audio_output.hpp b/firmware/baseband/audio_output.hpp index 1c3b41378..b9ccabf86 100644 --- a/firmware/baseband/audio_output.hpp +++ b/firmware/baseband/audio_output.hpp @@ -47,6 +47,7 @@ class AudioOutput { void write_unprocessed(const buffer_s16_t& audio); void apt_write(const buffer_s16_t& audio); + void apt_write(const buffer_s16_t& audio, std::array& audio_f); void write(const buffer_s16_t& audio); void write(const buffer_f32_t& audio); diff --git a/firmware/baseband/proc_noaaapt_rx.cpp b/firmware/baseband/proc_noaaapt_rx.cpp new file mode 100644 index 000000000..938f9b418 --- /dev/null +++ b/firmware/baseband/proc_noaaapt_rx.cpp @@ -0,0 +1,180 @@ +/* + * 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_noaaapt_rx.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 NOAAAPT_PX_SIZE 2080.0 + +// updates the per pixel timers +void NoaaAptRx::update_params() { + // TODO HTOTOO + // 2080 px / line with chan a,b + sync + telemetry + pxRem = (double)12000.0 / NOAAAPT_PX_SIZE / 2.0; + samples_per_pixel = pxRem; + pxRem -= samples_per_pixel; + pxRoll = 0; + status_message.state = 0; + shared_memory.application_queue.push(status_message); +} + +void NoaaAptRx::execute(const buffer_c8_t& buffer) { + if (!configured) { + return; + } + const auto decim_0_out = decim_0.execute(buffer, dst_buffer); + const auto channel = decim_1.execute(decim_0_out, dst_buffer); + // TODO: Feed channel_stats post-decimation data? + feed_channel_stats(channel); + + /* spectrum_samples += channel.count; + if (spectrum_samples >= spectrum_interval_samples) { + spectrum_samples -= spectrum_interval_samples; + channel_spectrum.feed(channel, channel_filter_low_f, channel_filter_high_f, channel_filter_transition); + } + */ + /* 96kHz complex[64] for wfmam NOAA + * -> FM demodulation + * -> 96kHz int16_t[64] */ + auto audio_oversampled = demod.execute(channel, work_audio_buffer); // fs 384khz wfm , 96khz wfmam for NOAA + /* 96kHz int16_t[64] for wfam + * -> 4th order CIC decimation by 2, gain of 1 + * -> 48kHz int16_t[32] */ + auto audio_4fs = audio_dec_1.execute(audio_oversampled, work_audio_buffer); + /* 48kHz int16_t[32] for wfman + * -> 4th order CIC decimation by 2, gain of 1 + * -> 24kHz int16_t[16] */ + auto audio_2fs = audio_dec_2.execute(audio_4fs, work_audio_buffer); + /* 24kHz int16_t[16] for wfmam + * -> FIR filter, <4.5kHz (0.1875fs) pass, >5.2kHz (0.2166fs) stop, gain of 1 + * -> 12kHz int16_t[8] */ + auto audio = audio_filter.execute(audio_2fs, work_audio_buffer); + /* -> 12kHz int16_t[8] for wfmam , */ + std::array audio_f; + audio_output.apt_write(audio, audio_f); // we are in added wfmam (noaa), decim_1.decimation_factor == 8 + + for (size_t c = 0; c < audio.count; c++) { + if (status_message.state == 0 && false) { // disabled this due to NIY + // first look for the sync! + + } 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_f[c] >= 1) { + image_message.image[image_message.cnt++] = 255; + } else if (audio_f[c] <= 0) { + image_message.image[image_message.cnt++] = 0; + } else { + image_message.image[image_message.cnt++] = (audio_f[c]) * 255; + } + } + 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 NoaaAptRx::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::NoaaAptRxConfigure: + configure(*reinterpret_cast(message)); + break; + + case Message::ID::CaptureConfig: + capture_config(*reinterpret_cast(message)); + break; + + default: + break; + } +} + +void NoaaAptRx::configure(const NoaaAptRxConfigureMessage& message) { + (void)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; + + decim_0.configure(taps_16k0_decim_0.taps); + + // decim_1.configure(message.decim_1_filter.taps); // Original . + // TODO dynamic decim1 , with decimation 2 / 8 and 16 x taps , / 32 taps . + // Temptatively , I splitted, in two WidebandFMAudio::configure_wfm / WidebandFMAudio::configure_wfmam and dynamically /2, /8 . (here /8) + // decim_1.set().configure(message.decim_1_filter.taps); // for wfm + // decim_1.set().configure(message.decim_1_filter.taps); // for wfmam + decim_1.configure(taps_84k_wfmam_decim_1.taps); // for wfmam + size_t decim_1_output_fs = decim_1_input_fs / decim_1.decimation_factor; // wfmam, decim_1.decimation_factor() = /8 ,if applied after the line, decim_1.set().configure(message.decim_1_filter.taps); + size_t demod_input_fs = decim_1_output_fs; + + // spectrum_interval_samples = decim_1_output_fs / spectrum_rate_hz; + // spectrum_samples = 0; + + channel_filter_low_f = taps_84k_wfmam_decim_1.low_frequency_normalized * decim_1_input_fs; + channel_filter_high_f = taps_84k_wfmam_decim_1.high_frequency_normalized * decim_1_input_fs; + channel_filter_transition = taps_84k_wfmam_decim_1.transition_normalized * decim_1_input_fs; + demod.configure(demod_input_fs, 17000); + audio_filter.configure(taps_64_lp_1875_2166.taps); + audio_output.configure(apt_audio_12k_notch_2k4_config, apt_audio_12k_lpf_2000hz_config); + // channel_spectrum.set_decimation_factor(1); + update_params(); + configured = true; +} + +void NoaaAptRx::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_noaaapt_rx.hpp b/firmware/baseband/proc_noaaapt_rx.hpp new file mode 100644 index 000000000..2798ab34a --- /dev/null +++ b/firmware/baseband/proc_noaaapt_rx.hpp @@ -0,0 +1,124 @@ +/* + * 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_NOAAAPTRX_H__ +#define __PROC_NOAAAPTRX_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 NoaaAptRx : 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 + + uint32_t samples_per_pixel = 0; + + // 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 auto spectrum_rate_hz = 50.0f; + + std::array dst{}; + const buffer_c16_t dst_buffer{ + dst.data(), + dst.size()}; + // work_audio_buffer and dst_buffer use the same data pointer + const buffer_s16_t work_audio_buffer{ + (int16_t*)dst.data(), + sizeof(dst) / sizeof(int16_t)}; + + std::array complex_audio{}; + const buffer_c16_t complex_audio_buffer{ + complex_audio.data(), + complex_audio.size()}; + + dsp::decimate::FIRC8xR16x24FS4Decim4 decim_0{}; + // dsp::decimate::FIRC16xR16x16Decim2 decim_1{}; //original condition , before adding wfmam + + // decim_1 will handle different types of FIR filters depending on selection. + dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; + + // dsp::decimate::FIRC16xR16x32Decim8 decim_1{}; // For FMAM + + int32_t channel_filter_low_f = 0; + int32_t channel_filter_high_f = 0; + int32_t channel_filter_transition = 0; + + dsp::demodulate::FM demod{}; + dsp::decimate::DecimateBy2CIC4Real audio_dec_1{}; + dsp::decimate::DecimateBy2CIC4Real audio_dec_2{}; + dsp::decimate::FIR64AndDecimateBy2Real audio_filter{}; + + AudioOutput audio_output{}; + + // For fs=96kHz FFT streaming + BlockDecimator audio_spectrum_decimator{1}; + std::array, 256> audio_spectrum{}; + uint32_t audio_spectrum_timer{0}; + enum AudioSpectrumState { + IDLE = 0, + FEED, + FFT + }; + AudioSpectrumState audio_spectrum_state{IDLE}; + AudioSpectrum spectrum{}; + uint32_t fft_step{0}; + + // SpectrumCollector channel_spectrum{}; + // size_t spectrum_interval_samples = 0; + // size_t spectrum_samples = 0; + + bool configured{false}; + + void configure(const NoaaAptRxConfigureMessage& message); + void capture_config(const CaptureConfigMessage& message); + + NoaaAptRxStatusDataMessage status_message{0}; + NoaaAptRxImageDataMessage 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_NOAAAPTRX_H__*/ diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index cdf86f641..798f0762a 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -131,6 +131,9 @@ class Message { WeFaxRxStatusData = 74, WeFaxRxImageData = 75, WFMAMConfigure = 76, + NoaaAptRxConfigure = 77, + NoaaAptRxStatusData = 78, + NoaaAptRxImageData = 79, MAX }; @@ -1504,4 +1507,27 @@ class WeFaxRxImageDataMessage : public Message { uint32_t cnt = 0; }; +class NoaaAptRxConfigureMessage : public Message { + public: + constexpr NoaaAptRxConfigureMessage() + : Message{ID::NoaaAptRxConfigure} {} +}; + +class NoaaAptRxStatusDataMessage : public Message { + public: + constexpr NoaaAptRxStatusDataMessage(uint8_t state) + : Message{ID::NoaaAptRxStatusData}, + state{state} { + } + uint8_t state = 0; +}; + +class NoaaAptRxImageDataMessage : public Message { + public: + constexpr NoaaAptRxImageDataMessage() + : Message{ID::NoaaAptRxImageData} {} + 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 d7c9bec06..cb6dc8c6f 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -119,6 +119,7 @@ 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_noaaapt_rx{'P', 'N', 'O', 'A'}; constexpr image_tag_t image_tag_noop{'P', 'N', 'O', 'P'}; diff --git a/firmware/graphics/icon_noaa.png b/firmware/graphics/icon_noaa.png new file mode 100644 index 0000000000000000000000000000000000000000..818f538f7e05f2affa8b6fd0e7d4b605e048c59b GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^0w65F1|14Ba#1H&(%P{RubhEf9thF1v;3|2E37{m+a>4nJa0`PlBg3pY5H=O_Lr<&0=$yb6@M`Ug${eVIEHxeuAOYici4c3`SG9s z=iffMT)^mPzCzqh$n})bCcU}m?Y!c%<)7a__T$cSCX+dzg!yIlwn!OX74#_WS)pDh z)8NKEk$tj3setU~*TOp(9hKP@yw+SN?a}*WcH(Y_y9{o}(sJs$>hB#`S-9Jh`R?+j z={U=BXY-1=~t$z1714mQdmWj!~gre5-HB6A6UAVgcUd@9ohEH9s`7Rot zi+4CUdGhwjmFt#UyyV?4)b?|J|ILP{w-oZt6Xxr$-_O4FZYU=Y&^rvCu6{1-oD!M< Dx