diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 02658c865..c9a6107ff 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -115,6 +115,10 @@ set(EXTCPPSRC external/ookbrute/main.cpp external/ookbrute/ui_ookbrute.cpp + #rook + external/rook/main.cpp + external/rook/ui_rook.cpp + #cvs_spam external/cvs_spam/main.cpp external/cvs_spam/cvs_spam.cpp @@ -153,6 +157,7 @@ set(EXTAPPLIST random_password #acars_rx ookbrute + rook shoppingcart_lock flippertx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 4dc986d8e..38996717e 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -52,6 +52,7 @@ MEMORY 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_rook(rwx) : org = 0xADCE0000, len = 32k } SECTIONS @@ -225,6 +226,12 @@ SECTIONS *(*ui*external_app*ookbrute*); } > ram_external_app_ookbrute + .external_app_rook : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_rook.application_information)); + *(*ui*external_app*rook*); + } > ram_external_app_rook + .external_app_flippertx : ALIGN(4) SUBALIGN(4) { KEEP(*(.external_app.app_flippertx.application_information)); diff --git a/firmware/application/external/rook/main.cpp b/firmware/application/external/rook/main.cpp new file mode 100644 index 000000000..0f36f4b7c --- /dev/null +++ b/firmware/application/external/rook/main.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92 + * + * 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_rook.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::rook { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::rook + +extern "C" { + +__attribute__((section(".external_app.app_rook.application_information"), used)) application_information_t _application_information_rook = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::rook::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "ROOK", + /*.bitmap_data = */ { + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0x20, + 0x00, + 0xE0, + 0x07, + 0xF0, + 0x0F, + 0x30, + 0x0C, + 0x30, + 0x0C, + 0xF0, + 0x0F, + 0xF0, + 0x0F, + 0x70, + 0x0D, + 0xB0, + 0x0E, + 0x70, + 0x0D, + 0xB0, + 0x0E, + 0xF0, + 0x0F, + 0xE0, + 0x07, + }, + /*.icon_color = */ ui::Color::orange().v, + /*.menu_location = */ app_location_t::TX, + + /*.m4_app_tag = portapack::spi_flash::image_tag_ook */ {'P', 'O', 'O', 'K'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} \ No newline at end of file diff --git a/firmware/application/external/rook/ui_rook.cpp b/firmware/application/external/rook/ui_rook.cpp new file mode 100644 index 000000000..e08bb99ce --- /dev/null +++ b/firmware/application/external/rook/ui_rook.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92 + * + * 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_rook.hpp" +#include "portapack.hpp" +#include "io_file.hpp" +#include "ui_fileman.hpp" +#include "file_path.hpp" +#include +#include "baseband_api.hpp" +#include "ui_textentry.hpp" +#include "encoders.hpp" // Includes data encoding functions for transmission +#include "baseband_api.hpp" // Includes baseband API for handling transmission settings +#include "file_reader.hpp" + +#define PADDING_LEFT 1 +#define PADDING_RIGHT 1 + +using namespace portapack; +using namespace ui; +namespace fs = std::filesystem; + +namespace ui::external_app::rook { + +void ROOKAppView::focus() { + button_set.focus(); +} + +// `start_tx` method: Configures and begins OOK data transmission with a specific message. +void ROOKAppView::start_tx(const std::string& message) { + size_t bitstream_length = encoders::make_bitstream(const_cast(message)); // Convert the message into a bitstream + // Retrieve selected sample rate using selected_index_value() + int32_t SAMPLE_RATE_VALUE = field_sample_rate.selected_index_value(); + int32_t SYMBOL_RATE_VALUE = cant_symbol_rate.value(); + int32_t REPEAT = cant_repeat.value(); + int32_t PAUSE_SYMBOL = cant_pause_symbol.value(); + + transmitter_model.set_sampling_rate(SAMPLE_RATE_VALUE); // Set the OOK sampling rate + transmitter_model.set_baseband_bandwidth(1750000); // Set the baseband bandwidth + transmitter_model.enable(); // Enable the transmitter + + // Configure OOK data and transmission characteristics + baseband::set_ook_data( + bitstream_length, // Length of the bitstream to transmit + SAMPLE_RATE_VALUE / SYMBOL_RATE_VALUE, // Calculate symbol period based on repetition rate + REPEAT, // Set the number of repetitions per symbol + PAUSE_SYMBOL // Set the pause between symbols + ); +} + +// `stop_tx` method: Stops the transmission and resets the progress bar. +void ROOKAppView::stop_tx() { + transmitter_model.disable(); // Disable the transmitter + progressBar_progress.set_value(0); // Reset progress bar to 0 +} + +// `on_file_changed` method: Called when a new file is loaded; parses file data into variables + +void ROOKAppView::on_file_changed(const fs::path& new_file_path) { + payload.clear(); // Clear previous payload content + + File data_file; + auto open_result = data_file.open(new_file_path); + if (open_result) { + nav_.display_modal("Error", "Unable to open file."); + return; + } + + FileLineReader reader(data_file); + for (const auto& line : reader) { + // Split the line into segments to extract each value + size_t first_space = line.find(' '); + size_t second_space = line.find(' ', first_space + 1); + size_t third_space = line.find(' ', second_space + 1); + size_t fourth_space = line.find(' ', third_space + 1); + size_t fifth_space = line.find(' ', fourth_space + 1); + + // Extract each component of the line + std::string frequency_str = line.substr(0, first_space); + std::string sample_rate_str = line.substr(first_space + 1, second_space - first_space - 1); + std::string symbols_rate_str = line.substr(second_space + 1, third_space - second_space - 1); + std::string repeat_str = line.substr(third_space + 1, fourth_space - third_space - 1); + std::string pause_symbol_str = line.substr(fourth_space + 1, fifth_space - fourth_space - 1); + std::string payload_data = line.substr(fifth_space + 1); // Extract binary payload as final value + + // Convert and assign frequency + unsigned long long frequency = std::stoull(frequency_str); + field_frequency.set_value(frequency); + transmitter_model.set_target_frequency(frequency); + + // Convert and assign symbols rate + unsigned int symbols_rate = static_cast(atoi(symbols_rate_str.c_str())); + + cant_symbol_rate.set_value(symbols_rate); + + // Convert and assign repeat count + unsigned int repeat = static_cast(atoi(repeat_str.c_str())); + + cant_repeat.set_value(repeat); + + // Convert and assign pause per symbol + unsigned int pause_symbol = static_cast(atoi(pause_symbol_str.c_str())); + + cant_pause_symbol.set_value(pause_symbol); + + // Select sample rate based on value read from file + if (sample_rate_str == "250k") { + field_sample_rate.set_by_value(250000U); + } else if (sample_rate_str == "1M") { + field_sample_rate.set_by_value(1000000U); + } else if (sample_rate_str == "2M") { + field_sample_rate.set_by_value(2000000U); + } else if (sample_rate_str == "5M") { + field_sample_rate.set_by_value(5000000U); + } else if (sample_rate_str == "10M") { + field_sample_rate.set_by_value(10000000U); + } else if (sample_rate_str == "20M") { + field_sample_rate.set_by_value(20000000U); + } else { + nav_.display_modal("Error", "Invalid sample rate."); + return; + } + + // Update payload with binary data + payload = payload_data; + + // Process only the first line + break; + } + + // Ensure UI elements are initialized before use + if (parent()) { + text_payload.set(payload); + button_start.focus(); + } else { + text_payload.set("parent not available"); + } +} + +// `on_tx_progress` method: Updates the progress bar based on transmission progress. +void ROOKAppView::on_tx_progress(const uint32_t progress, const bool done) { + progressBar_progress.set_value(progress); // Update progress bar value + if (done) { + stop_tx(); // Stop transmission when progress reaches maximum + } +} + +// `draw_waveform` method: Draws the waveform on the UI based on the payload data +void ROOKAppView::draw_waveform() { + // Padding reason: + // In real-world scenarios, the signal would always start low and return low after turning off the radio. + // `waveform_buffer` only controls drawing; the actual send logic is handled by frame_fragments. + size_t length = payload.length(); + + // Ensure waveform length does not exceed buffer size + if (length + (PADDING_LEFT + PADDING_RIGHT) >= WAVEFORM_BUFFER_SIZE) { + length = WAVEFORM_BUFFER_SIZE - (PADDING_LEFT + PADDING_RIGHT); + } + + // Left padding + for (size_t i = 0; i < PADDING_LEFT; i++) { + waveform_buffer[i] = 0; + } + + // Draw the actual waveform + for (size_t n = 0; n < length; n++) { + waveform_buffer[n + PADDING_LEFT] = (payload[n] == '0') ? 0 : 1; + } + + // Right padding + for (size_t i = length + PADDING_LEFT; i < WAVEFORM_BUFFER_SIZE; i++) { + waveform_buffer[i] = 0; + } + + waveform.set_length(length + PADDING_LEFT + PADDING_RIGHT); + waveform.set_dirty(); +} + +// Destructor for `ROOKAppView`: Disables the transmitter and shuts down the baseband +ROOKAppView::~ROOKAppView() { + transmitter_model.disable(); + baseband::shutdown(); +} + +// Constructor for `ROOKAppView`: Sets up the app view and initializes UI elements +ROOKAppView::ROOKAppView(NavigationView& nav) + : nav_{nav} { + baseband::run_image(portapack::spi_flash::image_tag_ook); + + add_children({ + &field_frequency, + &tx_view, + &button_start, + &sample_rate, + &step_symbol_rate, + &cant_step_symbol_rate, + &field_sample_rate, + &field_symbol_rate, + &field_symbol_us_rate, + &cant_symbol_rate, + &text_payload, + &button_set, + &progressBar_progress, + &repeat, + &cant_repeat, + &field_pause_symbol, + &cant_pause_symbol, + &label_waveform, + &waveform, + &button_open, + }); + + // Initialize default values for controls + cant_pause_symbol.set_value(100); + cant_repeat.set_value(4); + + button_open.on_select = [this](Button&) { + auto open_view = nav_.push(".ROOK"); + ensure_directory(rook_dir); + open_view->push_dir(rook_dir); + open_view->on_changed = [this](std::filesystem::path new_file_path) { + // Postpone `on_file_changed` call until `FileLoadView` is closed + nav_.set_on_pop([this, new_file_path]() { + on_file_changed(new_file_path); + button_start.focus(); + draw_waveform(); + }); + }; + }; + + // Set up changes for symbol rate and step + cant_symbol_rate.on_change = [this](int32_t value) { + if (value != cant_symbol_rate.value()) + cant_symbol_rate.set_value(value); + }; + + cant_step_symbol_rate.on_change = [this](size_t, int32_t value) { + cant_symbol_rate.set_step(value); + }; + + // Configure button to manually set payload through text input + button_set.on_select = [this, &nav](Button&) { + text_prompt( + nav, + payload, + 100, + [this](std::string& s) { + text_payload.set(s); + draw_waveform(); + }); + }; + draw_waveform(); + button_start.on_select = [this](Button&) { + start_tx(payload); // Begin transmission + }; +} +} // namespace ui::external_app::rook \ No newline at end of file diff --git a/firmware/application/external/rook/ui_rook.hpp b/firmware/application/external/rook/ui_rook.hpp new file mode 100644 index 000000000..5429dc8a7 --- /dev/null +++ b/firmware/application/external/rook/ui_rook.hpp @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2024 Samir Sánchez Garnica @sasaga92 + * + * This file is part of PortaPack. + * + */ + +#ifndef __UI_ROOK_H__ +#define __UI_ROOK_H__ + +#include "ui.hpp" +#include "ui_widget.hpp" +#include "ui_navigation.hpp" +#include "string_format.hpp" +#include "radio_state.hpp" +#include "ui_freq_field.hpp" +#include "ui_transmitter.hpp" +#include "app_settings.hpp" +#include "transmitter_model.hpp" +#include + +using namespace ui; + +namespace ui::external_app::rook { + +#define PROGRESS_MAX 100 +#define OOK_SAMPLERATE_DEFAULT 2280000U // Set the default Sample Rate +#define TRANSMISSION_FREQUENCY_DEFAULT 433920000U // Sets the default transmission frequency (27 MHz). +#define WAVEFORM_BUFFER_SIZE 550 + +class ROOKAppView : public View { + public: + void focus() override; + ROOKAppView(NavigationView& nav); + + ~ROOKAppView(); + + std::string title() const override { + return "RemoteOOK"; + }; + + private: + NavigationView& nav_; // Reference to the navigation system. + std::string payload{""}; // Holds the data payload as a string. + uint32_t progress = 0; // Stores the current transmission progress. + int16_t waveform_buffer[WAVEFORM_BUFFER_SIZE]; // Buffer for waveform data. + + void update(); + void draw_waveform(); + + // Initiates data transmission with a specified message. + void start_tx(const std::string& message); + + // Stops data transmission. + void stop_tx(); + + // Updates the transmission progress on the progress bar. + void on_tx_progress(const uint32_t progress, const bool done); + + // Updates data when a new file is loaded. + void on_file_changed(const std::filesystem::path& new_file_path); + + // Registers a message handler for transmission progress updates. + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, // Transmission progress message ID. + [this](const Message* const p) { // Callback to handle the message. + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.progress, message.done); + }}; + + // Sets transmission frequency, bandwidth, and default sample rate for OOK. + TxRadioState radio_state_{TRANSMISSION_FREQUENCY_DEFAULT, 1750000, OOK_SAMPLERATE_DEFAULT}; + + // Settings manager for app configuration. + app_settings::SettingsManager settings_{"tx_rook", app_settings::Mode::TX}; + + // UI components for frequency and transmitter view. + TxFrequencyField field_frequency{{0 * 8, 0 * 16}, nav_}; + TransmitterView2 tx_view{{20 * 7, 0 * 16}, true}; + + // Labels for various fields such as sample rate and repeat count. + Labels sample_rate{{{0, 40}, "S/Rate: ", Theme::getInstance()->fg_light->foreground}}; + // Labels step_symbol_rate {{{160, 0}, "Step/Sym: ",Theme::getInstance()->fg_light->foreground}}; + Labels step_symbol_rate{{{0, 18}, "Step/Symbols: ", Theme::getInstance()->fg_light->foreground}}; + Labels repeat{{{0, 60}, "Repeat: ", Theme::getInstance()->fg_light->foreground}}; + Labels field_symbol_rate{{{111, 40}, "Symbols: ", Theme::getInstance()->fg_light->foreground}}; + Labels field_pause_symbol{{{111, 60}, "Pause/Sym: ", Theme::getInstance()->fg_light->foreground}}; + Labels field_symbol_us_rate{{{210, 40}, "us ", Theme::getInstance()->fg_light->foreground}}; + Labels label_waveform{{{0, 140}, "Waveform:", Theme::getInstance()->fg_light->foreground}}; + + // OptionsField for selectable sample rates. + OptionsField field_sample_rate{{55, 40}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}}; + + // OptionsField for step symbol rates. + OptionsField cant_step_symbol_rate{{105, 18}, 7, {{"1", 1}, {"10", 10}, {"100", 100}}}; + + // Number fields for symbols, pause between symbols, and repeat count. + NumberField cant_symbol_rate{{176, 40}, 4, {0, 9999}, 1, '0', false}; + NumberField cant_pause_symbol{{200, 60}, 4, {0, 200}, 1, '0', false}; + NumberField cant_repeat{{55, 60}, 3, {0, 100}, 1, '0', false}; + + // Text field to display the payload data. + Text text_payload{{0 * 8, 90, 30 * 8, 16}, "Payload:"}; + + // Buttons for setting configurations, opening files, and starting transmission. + Button button_set{{0, 110, 60, 28}, "Set"}; + Button button_open{{100, 110, 80, 28}, "Open file"}; + Button button_start{{80, 273, 80, 32}, "Start"}; + + // Progress bar to display transmission progress. + ProgressBar progressBar_progress{{2 * 8, 250, 208, 16}}; + + // Waveform display using waveform buffer and yellow theme color. + Waveform waveform{{0, 165, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground}; +}; + +}; // namespace ui::external_app::rook + +#endif /*__UI_ROOK_H__*/ \ No newline at end of file diff --git a/firmware/application/file_path.cpp b/firmware/application/file_path.cpp index 614048952..4eb5f6710 100644 --- a/firmware/application/file_path.cpp +++ b/firmware/application/file_path.cpp @@ -48,3 +48,4 @@ const std::filesystem::path splash_dir = u"SPLASH"; const std::filesystem::path sstv_dir = u"SSTV"; const std::filesystem::path wav_dir = u"WAV"; const std::filesystem::path whipcalc_dir = u"WHIPCALC"; +const std::filesystem::path rook_dir = u"ROOK"; diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp index a519e8d7a..308eabae7 100644 --- a/firmware/application/file_path.hpp +++ b/firmware/application/file_path.hpp @@ -50,5 +50,6 @@ extern const std::filesystem::path splash_dir; extern const std::filesystem::path sstv_dir; extern const std::filesystem::path wav_dir; extern const std::filesystem::path whipcalc_dir; +extern const std::filesystem::path rook_dir; #endif /* __FILE_PATH_H__ */