diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index 74076af7a..c2f5bd5d7 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -244,6 +244,7 @@ set(CPPSRC hw/si5351.cpp hw/spi_pp.cpp hw/touch_adc.cpp + ook_file.cpp ui_baseband_stats_view.cpp ui_navigation.cpp ui_record_view.cpp diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index f5e2ccdfb..c151b55ce 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -115,9 +115,9 @@ set(EXTCPPSRC external/ookbrute/main.cpp external/ookbrute/ui_ookbrute.cpp - #ook_remote - external/ook_remote/main.cpp - external/ook_remote/ui_ook_remote.cpp + #ook_editor + external/ook_editor/main.cpp + external/ook_editor/ui_ook_editor.cpp #cvs_spam external/cvs_spam/main.cpp @@ -157,7 +157,7 @@ set(EXTAPPLIST random_password #acars_rx ookbrute - ook_remote + ook_editor shoppingcart_lock flippertx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index 7eb8f85fb..b4b3ac345 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -52,7 +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_ook_remote(rwx) : org = 0xADCE0000, len = 32k + ram_external_app_ook_editor(rwx) : org = 0xADCE0000, len = 32k } SECTIONS @@ -226,11 +226,11 @@ SECTIONS *(*ui*external_app*ookbrute*); } > ram_external_app_ookbrute - .external_app_ook_remote : ALIGN(4) SUBALIGN(4) + .external_app_ook_editor : ALIGN(4) SUBALIGN(4) { - KEEP(*(.external_app.app_ook_remote.application_information)); - *(*ui*external_app*ook_remote*); - } > ram_external_app_ook_remote + KEEP(*(.external_app.app_ook_editor.application_information)); + *(*ui*external_app*ook_editor*); + } > ram_external_app_ook_editor .external_app_flippertx : ALIGN(4) SUBALIGN(4) { diff --git a/firmware/application/external/ook_remote/main.cpp b/firmware/application/external/ook_editor/main.cpp similarity index 85% rename from firmware/application/external/ook_remote/main.cpp rename to firmware/application/external/ook_editor/main.cpp index 8534a6772..98dd80970 100644 --- a/firmware/application/external/ook_remote/main.cpp +++ b/firmware/application/external/ook_editor/main.cpp @@ -20,25 +20,25 @@ */ #include "ui.hpp" -#include "ui_ook_remote.hpp" +#include "ui_ook_editor.hpp" #include "ui_navigation.hpp" #include "external_app.hpp" -namespace ui::external_app::ook_remote { +namespace ui::external_app::ook_editor { void initialize_app(ui::NavigationView& nav) { - nav.push(); + nav.push(); } -} // namespace ui::external_app::ook_remote +} // namespace ui::external_app::ook_editor extern "C" { -__attribute__((section(".external_app.app_ook_remote.application_information"), used)) application_information_t _application_information_ook_remote = { +__attribute__((section(".external_app.app_ook_editor.application_information"), used)) application_information_t _application_information_ook_editor = { /*.memory_location = */ (uint8_t*)0x00000000, - /*.externalAppEntry = */ ui::external_app::ook_remote::initialize_app, + /*.externalAppEntry = */ ui::external_app::ook_editor::initialize_app, /*.header_version = */ CURRENT_HEADER_VERSION, /*.app_version = */ VERSION_MD5, - /*.app_name = */ "OOKRemote", + /*.app_name = */ "OOKEditor", /*.bitmap_data = */ { 0x20, 0x00, diff --git a/firmware/application/external/ook_editor/ui_ook_editor.cpp b/firmware/application/external/ook_editor/ui_ook_editor.cpp new file mode 100644 index 000000000..7f9ade0ee --- /dev/null +++ b/firmware/application/external/ook_editor/ui_ook_editor.cpp @@ -0,0 +1,282 @@ +/* + * 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_ook_editor.hpp" + +using namespace portapack; +using namespace ui; + +namespace fs = std::filesystem; + +namespace ui::external_app::ook_editor { + +// give focus to set button +void OOKEditorAppView::focus() { + button_set.focus(); +} + +// update internal ook_data with GUI values +void OOKEditorAppView::update_ook_data_from_app() { + ook_data.frequency = field_frequency.value(); + ook_data.sample_rate = field_sample_rate.selected_index_value(); + ook_data.symbol_rate = field_symbol_rate.value(); + ook_data.repeat = field_repeat.value(); + ook_data.pause_symbol_duration = field_pause_symbol_duration.value(); +} + +// `start_tx` method: Configures and begins OOK data transmission with a specific message. +void OOKEditorAppView::start_tx() { + // check if there is a payload + if (ook_data.payload.length() < 1) { + text_app_status.set("Error: no payload to tx !!"); + return; + } + progressbar.set_max(field_repeat.value()); // Size the progress bar accordingly to the number of repeat + is_transmitting = true; // set transmitting flag + button_send_stop.set_text(LanguageHelper::currentMessages[LANG_STOP]); // set button back to initial "start" state + start_ook_file_tx(ook_data); // start the transmission +} + +// `stop_tx` method: Stops the transmission and resets the progress bar. +void OOKEditorAppView::stop_tx() { + is_transmitting = false; // set transmitting flag + stop_ook_file_tx(); // stop transmission + progressbar.set_value(0); // Reset progress bar to 0 + button_send_stop.set_text(LanguageHelper::currentMessages[LANG_START]); // set button back to initial "start" state +} + +// `on_file_changed` method: Called when a new file is loaded; parses file data into variables +void OOKEditorAppView::on_file_changed(const fs::path& new_file_path) { + ook_data.payload.clear(); // Clear previous payload content + if (!read_ook_file(new_file_path, ook_data)) { + text_app_status.set("Error loading " + new_file_path.filename().string()); + return; + } + field_frequency.set_value(ook_data.frequency); + field_symbol_rate.set_value(ook_data.symbol_rate); + field_repeat.set_value(ook_data.repeat); + field_pause_symbol_duration.set_value(ook_data.pause_symbol_duration); + field_sample_rate.set_by_value(ook_data.sample_rate); + text_payload.set(ook_data.payload); + button_send_stop.focus(); + text_app_status.set("Loaded: " + new_file_path.filename().string()); +} + +// `on_tx_progress` method: Updates the progress bar based on transmission progress. +void OOKEditorAppView::on_tx_progress(const uint32_t progress, const bool done) { + progressbar.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 OOKEditorAppView::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 = ook_data.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] = (ook_data.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(); +} + +// build a new path+file, make some tests, call save_ook_to_file +void OOKEditorAppView::on_save_file(const std::string value) { + // check if there is a payload, else Error + if (ook_data.payload.length() < 1) { + text_app_status.set("Err: can't save, no payload !"); + return; + } + ensure_directory(ook_editor_dir); + auto new_path = ook_editor_dir / value + ".OOK"; + if (save_ook_to_file(new_path)) { + text_app_status.set("Saved to " + new_path.string()); + } else { + text_app_status.set("Error saving " + new_path.string()); + } +} + +// update ook_data from GUI and save +bool OOKEditorAppView::save_ook_to_file(const std::filesystem::path& path) { + update_ook_data_from_app(); + return save_ook_file(ook_data, path); +} + +// Destructor for `OOKEditorAppView`: Disables the transmitter and shuts down the baseband +OOKEditorAppView::~OOKEditorAppView() { + stop_ook_file_tx(); + baseband::shutdown(); +} + +// Constructor for `OOKEditorAppView`: Sets up the app view and initializes UI elements +OOKEditorAppView::OOKEditorAppView(NavigationView& nav) + : nav_{nav} { + // load OOK baseband + baseband::run_image(portapack::spi_flash::image_tag_ook); + + // add all the widgets + add_children({&field_frequency, + &tx_view, + &button_send_stop, + &label_step, + &field_step, + &label_sample_rate, + &field_sample_rate, + &label_symbol_rate, + &field_symbol_rate, + &label_symbol_rate_unit, + &text_payload, + &button_set, + &progressbar, + &label_repeat, + &field_repeat, + &label_pause_symbol_duration, + &field_pause_symbol_duration, + &label_pause_symbol_duration_unit, + &label_payload, + &text_app_status, + &label_waveform, + &waveform, + &button_open, + &button_save}); + + // Initialize default values for controls + field_symbol_rate.set_value(100); + field_pause_symbol_duration.set_value(100); + field_repeat.set_value(4); + + // Configure open ook file button + button_open.on_select = [this](Button&) { + auto open_view = nav_.push(".OOK"); + ensure_directory(ook_editor_dir); + open_view->push_dir(ook_editor_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_send_stop.focus(); + draw_waveform(); + }); + }; + }; + + // Configure save to ook file button + button_save.on_select = [this, &nav](const ui::Button&) { + outputFileBuffer = ""; + text_prompt( + nav, + outputFileBuffer, + 64, + [this](std::string& buffer) { + on_save_file(buffer); + }); + }; + + // clean out loaded file name if field is changed + field_symbol_rate.on_change = [this](int32_t) { + text_app_status.set(""); // Clear loaded file text field + }; + // clean out loaded file name if field is changed + field_repeat.on_change = [this](int32_t) { + text_app_status.set(""); // Clear loaded file text field + }; + // clean out loaded file name if field is changed + field_pause_symbol_duration.on_change = [this](int32_t) { + text_app_status.set(""); // Clear loaded file text field + }; + // clean out loaded file name if field is changed + field_sample_rate.on_change = [this](size_t, int32_t) { + text_app_status.set(""); // Clear loaded file text field + }; + + // setting up FrequencyField + field_frequency.set_value(ook_editor_tx_freq); + + // clean out loaded file name if field is changed, save ook_editor_tx_freq + field_frequency.on_change = [this](rf::Frequency f) { + ook_editor_tx_freq = f; + text_app_status.set(""); // Clear loaded file text field + }; + + // allow typing frequency number + field_frequency.on_edit = [this]() { + auto freq_view = nav_.push(field_frequency.value()); + freq_view->on_changed = [this](rf::Frequency f) { + field_frequency.set_value(f); + text_app_status.set(""); // Clear loaded file text field + }; + }; + + // allow different steps on symbol_rate and pause_symbol_duration + field_step.on_change = [this](size_t, int32_t value) { + text_app_status.set(""); // Clear loaded file text field + field_symbol_rate.set_step(value); + field_pause_symbol_duration.set_step(value); + }; + + // Configure button to manually set payload through text input + button_set.on_select = [this, &nav](Button&) { + text_prompt( + nav, + ook_data.payload, + 100, + [this](std::string& s) { + text_payload.set(s); + draw_waveform(); + text_app_status.set(""); // Clear loaded file text field + }); + }; + + // Configure button to start or stop the transmission + button_send_stop.on_select = [this](Button&) { + if (!is_transmitting) { + update_ook_data_from_app(); + start_tx(); // Begin transmission + } else { + stop_tx(); + } + }; + + // initial waveform drawing (should be a single line) + draw_waveform(); +} +} // namespace ui::external_app::ook_editor diff --git a/firmware/application/external/ook_remote/ui_ook_remote.hpp b/firmware/application/external/ook_editor/ui_ook_editor.hpp similarity index 59% rename from firmware/application/external/ook_remote/ui_ook_remote.hpp rename to firmware/application/external/ook_editor/ui_ook_editor.hpp index 3cd4ecf6f..343763342 100644 --- a/firmware/application/external/ook_remote/ui_ook_remote.hpp +++ b/firmware/application/external/ook_editor/ui_ook_editor.hpp @@ -5,57 +5,59 @@ * */ -#ifndef __UI_OOK_REMOTE_H__ -#define __UI_OOK_REMOTE_H__ +#ifndef __UI_OOK_EDITOR_H__ +#define __UI_OOK_EDITOR_H__ #include "ui.hpp" #include "ui_language.hpp" -#include "ui_widget.hpp" -#include "ui_navigation.hpp" -#include "string_format.hpp" -#include "radio_state.hpp" #include "ui_freq_field.hpp" +#include "ui_textentry.hpp" +#include "ui_fileman.hpp" #include "ui_transmitter.hpp" +#include "radio_state.hpp" #include "app_settings.hpp" -#include "transmitter_model.hpp" -#include +#include "file_path.hpp" +#include "ook_file.hpp" using namespace ui; -namespace ui::external_app::ook_remote { +namespace ui::external_app::ook_editor { -#define PROGRESS_MAX 100 +#define PADDING_LEFT 1 // waveform padding +#define PADDING_RIGHT 1 // waveform padding #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 OOKRemoteAppView : public View { +class OOKEditorAppView : public View { public: void focus() override; - OOKRemoteAppView(NavigationView& nav); + OOKEditorAppView(NavigationView& nav); - ~OOKRemoteAppView(); + ~OOKEditorAppView(); std::string title() const override { - return "OOKRemote"; + return "OOKEditor"; }; 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. bool is_transmitting = false; // State of transmission. - rf::Frequency ook_remote_tx_freq{24000000}; // last used transmit frequency + rf::Frequency ook_editor_tx_freq{24000000}; // last used transmit frequency std::string outputFileBuffer{}; // buffer for output file + ook_file_data ook_data = {0, 0, 0, 0, 0, ""}; // ook files handle - void update(); + // draw current payload waveform void draw_waveform(); - // Initiates data transmission with a specified message. - void start_tx(const std::string& message); + // update ook_data from GUI selection + void update_ook_data_from_app(); - // Stops data transmission. + // start transmission + void start_tx(); + // stop transmission void stop_tx(); // Updates the transmission progress on the progress bar. @@ -83,45 +85,44 @@ class OOKRemoteAppView : public View { // Settings manager for app configuration. app_settings::SettingsManager settings_{ - "ook_remote", + "ook_editor", app_settings::Mode::TX, - {{"ook_remote_tx_freq"sv, &ook_remote_tx_freq}}}; + {{"ook_editor_tx_freq"sv, &ook_editor_tx_freq}}}; // UI components for frequency and transmitter view. FrequencyField field_frequency{{0 * 8, 0 * 16}}; TransmitterView2 tx_view{{20 * 7, 0 * 16}, true}; // Labels for various fields such as sample rate and repeat count. - Labels label_bit_duration_step{{{111, 18}, "BitTimeStep:", Theme::getInstance()->fg_light->foreground}}; - Labels label_sample_rate{{{0, 18}, "S/Rate:", Theme::getInstance()->fg_light->foreground}}; - Labels label_bit_duration{{{111, 40}, "BitTime:", Theme::getInstance()->fg_light->foreground}}; - Labels label_bit_duration_unit{{{210, 40}, "us", Theme::getInstance()->fg_light->foreground}}; - Labels label_repeat{{{0, 40}, "Repeat:", Theme::getInstance()->fg_light->foreground}}; - Labels label_pause_symbol{{{0, 60}, "Pause/Sym:", Theme::getInstance()->fg_light->foreground}}; - Labels label_payload{{{0, 76}, "Payload:", Theme::getInstance()->fg_light->foreground}}; + Labels label_step{{{170, 20}, "Step:", Theme::getInstance()->fg_light->foreground}}; + Labels label_sample_rate{{{0, 20}, "SampleRate:", Theme::getInstance()->fg_light->foreground}}; + Labels label_symbol_rate{{{0, 40}, "SymbolRate:", Theme::getInstance()->fg_light->foreground}}; + Labels label_symbol_rate_unit{{{132, 40}, "/s", Theme::getInstance()->fg_light->foreground}}; + Labels label_repeat{{{154, 40}, "Repeat:", Theme::getInstance()->fg_light->foreground}}; + Labels label_pause_symbol_duration{{{0, 60}, "PauseSymbol:", Theme::getInstance()->fg_light->foreground}}; + Labels label_pause_symbol_duration_unit{{{132, 60}, "us", Theme::getInstance()->fg_light->foreground}}; + Labels label_payload{{{0, 80}, "Payload:", Theme::getInstance()->fg_light->foreground}}; Labels label_waveform{{{0, 188}, "Waveform:", Theme::getInstance()->fg_light->foreground}}; - // Text field to display the loaded file if any - Text text_loaded_file{{0, 140, 30 * 8, 16}, ""}; + // Text field to display the various status message of the app + Text text_app_status{{0, 160, 30 * 8, 16}, ""}; // OptionsField for selectable sample rates. - OptionsField field_sample_rate{{55, 18}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}}; - + OptionsField field_sample_rate{{96, 20}, 7, {{"250k", 250000U}, {"1M", 1000000U}, {"2M", 2000000U}, {"5M", 5000000U}, {"10M", 10000000U}, {"20M", 20000000U}}}; // OptionsField for step symbol rates. - OptionsField field_bit_duration_step{{210, 18}, 7, {{"1", 1}, {"10", 10}, {"100", 100}}}; - - // Number fields for symbols, pause between symbols, and repeat count. - NumberField field_bit_duration{{176, 40}, 4, {0, 9999}, 1, '0', false}; - NumberField field_pause_symbol{{78, 60}, 4, {0, 200}, 1, '0', false}; - NumberField field_repeat{{55, 40}, 3, {0, 100}, 1, '0', false}; + OptionsField field_step{{210, 20}, 7, {{"1", 1}, {"10", 10}, {"100", 100}}}; + // Number fields for symbols, pause_symbol between repeat, and repeat count. + NumberField field_symbol_rate{{96, 40}, 4, {0, 9999}, 1, '0', false}; + NumberField field_pause_symbol_duration{{96, 60}, 4, {0, 9999}, 1, '0', false}; + NumberField field_repeat{{210, 40}, 3, {1, 999}, 1, '0', false}; // Text field to display the payload data. - Text text_payload{{0 * 8, 90, 30 * 8, 16}, ""}; + Text text_payload{{0 * 8, 100, 30 * 8, 16}, ""}; // Buttons for setting configurations, opening files, and starting transmission. - Button button_set{{0, 110, 60, 28}, LanguageHelper::currentMessages[LANG_SET]}; - Button button_open{{68, 110, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]}; - Button button_save{{154, 110, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]}; + Button button_set{{0, 125, 60, 28}, LanguageHelper::currentMessages[LANG_SET]}; + Button button_open{{68, 125, 80, 28}, LanguageHelper::currentMessages[LANG_OPEN_FILE]}; + Button button_save{{154, 125, 80, 28}, LanguageHelper::currentMessages[LANG_SAVE_FILE]}; Button button_send_stop{{80, 273, 80, 32}, LanguageHelper::currentMessages[LANG_SEND]}; // Progress bar to display transmission progress. @@ -131,6 +132,6 @@ class OOKRemoteAppView : public View { Waveform waveform{{0, 208, 240, 32}, waveform_buffer, 0, 0, true, Theme::getInstance()->fg_yellow->foreground}; }; -}; // namespace ui::external_app::ook_remote +}; // namespace ui::external_app::ook_editor -#endif /*__UI_OOK_REMOTE_H__*/ +#endif /*__UI_OOK_EDITOR_H__*/ diff --git a/firmware/application/external/ook_remote/ui_ook_remote.cpp b/firmware/application/external/ook_remote/ui_ook_remote.cpp deleted file mode 100644 index f4df45502..000000000 --- a/firmware/application/external/ook_remote/ui_ook_remote.cpp +++ /dev/null @@ -1,369 +0,0 @@ -/* - * 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_ook_remote.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::ook_remote { - -void OOKRemoteAppView::focus() { - button_set.focus(); -} - -// `start_tx` method: Configures and begins OOK data transmission with a specific message. -void OOKRemoteAppView::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_DURATION_VALUE = field_bit_duration.value(); - int32_t REPEAT = field_repeat.value(); - int32_t PAUSE_SYMBOL = field_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_DURATION_VALUE, // Calculate symbol period based on repetition rate - REPEAT, // Set the number of times the whole bitstream is repeated - PAUSE_SYMBOL // Set the pause between symbols - ); - progressbar.set_max(REPEAT); // Size the progress bar accordingly to the number of repeat - is_transmitting = true; // set transmitting flag - button_send_stop.set_text(LanguageHelper::currentMessages[LANG_STOP]); // set button back to initial "start" state -} - -// `stop_tx` method: Stops the transmission and resets the progress bar. -void OOKRemoteAppView::stop_tx() { - is_transmitting = false; - transmitter_model.disable(); // Disable the transmitter - progressbar.set_value(0); // Reset progress bar to 0 - button_send_stop.set_text(LanguageHelper::currentMessages[LANG_START]); // set button back to initial "start" state -} - -// `on_file_changed` method: Called when a new file is loaded; parses file data into variables -void OOKRemoteAppView::on_file_changed(const fs::path& new_file_path) { - payload.clear(); // Clear previous payload content - text_loaded_file.set(""); // Clear loaded file text field - - 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 - rf::Frequency frequency = std::stoull(frequency_str); - field_frequency.set_value(frequency); - transmitter_model.set_target_frequency(frequency); - - // Convert and assign symbols rate - unsigned int bit_duration_value = static_cast(atoi(symbols_rate_str.c_str())); - - field_bit_duration.set_value(bit_duration_value); - - // Convert and assign repeat count - unsigned int repeat = static_cast(atoi(repeat_str.c_str())); - - field_repeat.set_value(repeat); - - // Convert and assign pause per symbol - unsigned int pause_symbol = static_cast(atoi(pause_symbol_str.c_str())); - - field_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_send_stop.focus(); - } else { - text_payload.set("parent not available"); - } - text_loaded_file.set("Loaded: " + new_file_path.filename().string()); -} - -// `on_tx_progress` method: Updates the progress bar based on transmission progress. -void OOKRemoteAppView::on_tx_progress(const uint32_t progress, const bool done) { - progressbar.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 OOKRemoteAppView::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(); -} - -void OOKRemoteAppView::on_save_file(const std::string value) { - // check if there is a payload, else Error - if (payload.length() < 1) { - text_loaded_file.set("Error: no payload !!"); - return; - } - - ensure_directory(ook_remote_dir); - auto new_path = ook_remote_dir / value + ".OOK"; - if (save_ook_to_file(new_path)) { - text_loaded_file.set("Saved to " + new_path.string()); - } else { - text_loaded_file.set("Error saving " + new_path.string()); - } -} - -bool OOKRemoteAppView::save_ook_to_file(const std::filesystem::path& path) { - // delete file if it exists - delete_file(path); - - // Attempt to open, if it can't be opened. Create new. - auto src = std::make_unique(); - auto error = src->open(path, false, true); - if (error) { - return false; - } - - // write informations - src->write_line(to_string_dec_uint(field_frequency.value()) + " " + - field_sample_rate.selected_index_name() + " " + - to_string_dec_uint(field_bit_duration.value()) + " " + - to_string_dec_uint(field_repeat.value()) + " " + - to_string_dec_uint(field_pause_symbol.value()) + " " + - payload); - - // Close files - src.reset(); - - return true; -} - -// Destructor for `OOKRemoteAppView`: Disables the transmitter and shuts down the baseband -OOKRemoteAppView::~OOKRemoteAppView() { - transmitter_model.disable(); - baseband::shutdown(); -} - -// Constructor for `OOKRemoteAppView`: Sets up the app view and initializes UI elements -OOKRemoteAppView::OOKRemoteAppView(NavigationView& nav) - : nav_{nav} { - baseband::run_image(portapack::spi_flash::image_tag_ook); - - add_children({&field_frequency, - &tx_view, - &button_send_stop, - &label_bit_duration_step, - &field_bit_duration_step, - &label_sample_rate, - &field_sample_rate, - &label_bit_duration, - &field_bit_duration, - &label_bit_duration_unit, - &text_payload, - &button_set, - &progressbar, - &label_repeat, - &field_repeat, - &label_pause_symbol, - &field_pause_symbol, - &label_payload, - &text_loaded_file, - &label_waveform, - &waveform, - &button_open, - &button_save}); - - // Initialize default values for controls - field_pause_symbol.set_value(100); - field_repeat.set_value(4); - - button_open.on_select = [this](Button&) { - auto open_view = nav_.push(".OOK"); - ensure_directory(ook_remote_dir); - open_view->push_dir(ook_remote_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_send_stop.focus(); - draw_waveform(); - }); - }; - }; - - button_save.on_select = [this, &nav](const ui::Button&) { - outputFileBuffer = ""; - text_prompt( - nav, - outputFileBuffer, - 64, - [this](std::string& buffer) { - on_save_file(buffer); - }); - }; - - // clean out loaded file name if field is changed - field_bit_duration.on_change = [this](int32_t) { - text_loaded_file.set(""); // Clear loaded file text field - }; - // clean out loaded file name if field is changed - field_repeat.on_change = [this](int32_t) { - text_loaded_file.set(""); // Clear loaded file text field - }; - // clean out loaded file name if field is changed - field_pause_symbol.on_change = [this](int32_t) { - text_loaded_file.set(""); // Clear loaded file text field - }; - // clean out loaded file name if field is changed - field_sample_rate.on_change = [this](size_t, int32_t) { - text_loaded_file.set(""); // Clear loaded file text field - }; - - // setting up FrequencyField - field_frequency.set_value(ook_remote_tx_freq); - - // clean out loaded file name if field is changed, save ook_remote_tx_freq - field_frequency.on_change = [this](rf::Frequency f) { - ook_remote_tx_freq = f; - text_loaded_file.set(""); // Clear loaded file text field - }; - - // allow typing frequency number - field_frequency.on_edit = [this]() { - auto freq_view = nav_.push(field_frequency.value()); - freq_view->on_changed = [this](rf::Frequency f) { - field_frequency.set_value(f); - text_loaded_file.set(""); // Clear loaded file text field - }; - }; - - field_bit_duration_step.on_change = [this](size_t, int32_t value) { - text_loaded_file.set(""); // Clear loaded file text field - field_bit_duration.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(); - text_loaded_file.set(""); // Clear loaded file text field - }); - }; - draw_waveform(); - button_send_stop.on_select = [this](Button&) { - if (!is_transmitting) { - start_tx(payload); // Begin transmission - } else { - stop_tx(); - } - }; -} -} // namespace ui::external_app::ook_remote diff --git a/firmware/application/file_path.cpp b/firmware/application/file_path.cpp index 8de4d9644..ce4acf48c 100644 --- a/firmware/application/file_path.cpp +++ b/firmware/application/file_path.cpp @@ -48,4 +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 ook_remote_dir = u"OOKFILES"; +const std::filesystem::path ook_editor_dir = u"OOKFILES"; diff --git a/firmware/application/file_path.hpp b/firmware/application/file_path.hpp index 47ae2d228..bc115736c 100644 --- a/firmware/application/file_path.hpp +++ b/firmware/application/file_path.hpp @@ -50,6 +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 ook_remote_dir; +extern const std::filesystem::path ook_editor_dir; #endif /* __FILE_PATH_H__ */ diff --git a/firmware/application/ook_file.cpp b/firmware/application/ook_file.cpp new file mode 100644 index 000000000..a3515fe9c --- /dev/null +++ b/firmware/application/ook_file.cpp @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 gullradriel + * + * 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 "ook_file.hpp" + +using namespace portapack; +namespace fs = std::filesystem; + +/* + struct of an OOK file: + + Frequency SampleRate SymbolRate Repeat PauseSymbolDuration Payload + + -Frequency is in hertz + -SampleRate is one of 250k, 1M, 2M, 5M , 10M ,20M + -SymbolRate is the number of symbols /s + -Repeat is the number of times we will repeat the payload + -PauseSymbolDuration is the duration of the pause between repeat, in usec + -Payload is the payload in form of a string of 0 and 1 +*/ + +bool read_ook_file(const std::filesystem::path& path, ook_file_data& ook_data) { + File data_file; + auto open_result = data_file.open(path); + if (open_result) { + return false; + } + + 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 symbol_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_duration_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 + ook_data.frequency = std::stoull(frequency_str); + // Convert and assign symbol_rate + ook_data.symbol_rate = static_cast(atoi(symbol_rate_str.c_str())); + // Convert and assign repeat count + ook_data.repeat = static_cast(atoi(repeat_str.c_str())); + // Convert and assign pause_symbol_duration + ook_data.pause_symbol_duration = static_cast(atoi(pause_symbol_duration_str.c_str())); + // Select sample rate based on value read from file + if (sample_rate_str == "250k") { + ook_data.sample_rate = 250000U; + } else if (sample_rate_str == "1M") { + ook_data.sample_rate = 1000000U; + } else if (sample_rate_str == "2M") { + ook_data.sample_rate = 2000000U; + } else if (sample_rate_str == "5M") { + ook_data.sample_rate = 5000000U; + } else if (sample_rate_str == "10M") { + ook_data.sample_rate = 10000000U; + } else if (sample_rate_str == "20M") { + ook_data.sample_rate = 20000000U; + } else { + return false; + } + // Update payload with binary data + ook_data.payload = std::move(payload_data); + // Process only the first line + break; + } + return true; +} + +bool save_ook_file(ook_file_data& ook_data, const std::filesystem::path& path) { + // delete file if it exists + delete_file(path); + + // Attempt to open, if it can't be opened. Create new. + auto src = std::make_unique(); + auto error = src->open(path, false, true); + if (error) { + return false; + } + + std::string sample_rate_str; + if (ook_data.sample_rate == 250000U) { + sample_rate_str = "250k"; + } else if (ook_data.sample_rate == 1000000U) { + sample_rate_str = "1M"; + } else if (ook_data.sample_rate == 2000000U) { + sample_rate_str = "2M"; + } else if (ook_data.sample_rate == 5000000U) { + sample_rate_str = "5M"; + } else if (ook_data.sample_rate == 10000000U) { + sample_rate_str = "10M"; + } else if (ook_data.sample_rate == 20000000U) { + sample_rate_str = "20M"; + } else { + return false; + } + + // write informations + src->write_line(to_string_dec_uint(ook_data.frequency) + " " + + sample_rate_str + " " + + to_string_dec_uint(ook_data.symbol_rate) + " " + + to_string_dec_uint(ook_data.repeat) + " " + + to_string_dec_uint(ook_data.pause_symbol_duration) + " " + + ook_data.payload); + + // Close files + src.reset(); + + return true; +} + +void start_ook_file_tx(ook_file_data& ook_data) { + size_t bitstream_length = encoders::make_bitstream(const_cast(ook_data.payload)); // Convert the message into a bitstream + + transmitter_model.set_target_frequency(ook_data.frequency); // Set target frequency + transmitter_model.set_sampling_rate(ook_data.sample_rate); // 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 + ook_data.sample_rate / ook_data.symbol_rate, // Calculate symbol period based on repetition rate + ook_data.repeat, // Set the number of times the whole bitstream is repeated + ook_data.pause_symbol_duration // Set the pause_symbol between reps + ); +} + +void stop_ook_file_tx() { + transmitter_model.disable(); // Disable the transmitter +} diff --git a/firmware/application/ook_file.hpp b/firmware/application/ook_file.hpp new file mode 100644 index 000000000..03ddaaceb --- /dev/null +++ b/firmware/application/ook_file.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 gullradriel + * + * 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 __OOK_FILE_HPP__ +#define __OOK_FILE_HPP__ + +#include "portapack.hpp" +#include "metadata_file.hpp" +#include "encoders.hpp" // Includes data encoding functions for transmission +#include "convert.hpp" +#include "file_reader.hpp" +#include "string_format.hpp" +#include +#include "transmitter_model.hpp" +#include "baseband_api.hpp" // Includes baseband API for handling transmission settings + +struct ook_file_data { + rf::Frequency frequency = rf::Frequency(0); // Default frequency + uint32_t sample_rate = 0; // Default sample rate + uint16_t symbol_rate = 0; // Default bit duration + uint16_t pause_symbol_duration = 0; // Default pause between repeat + uint16_t repeat = 0; // Default repeat + std::string payload = ""; // Default payload +}; + +bool read_ook_file(const std::filesystem::path& path, ook_file_data& ook_data); +bool save_ook_file(ook_file_data& data, const std::filesystem::path& path); +void start_ook_file_tx(ook_file_data& ook_data); +void stop_ook_file_tx(); + +#endif // __FLIPPER_SUBFILE_HPP__ diff --git a/sdcard/OOKFILES/Tesla_EU_CN.OOK b/sdcard/OOKFILES/Tesla_EU_CN.OOK deleted file mode 100644 index 2ddf781af..000000000 --- a/sdcard/OOKFILES/Tesla_EU_CN.OOK +++ /dev/null @@ -1 +0,0 @@ -433920000 250k 2300 5 100 101010101010101010101010100010101100101100110010110011001100110011001011010011010010110101001010110100110100110010101011010010110001010110010110011001011001100110011001100101101001101001011010100101011010011010011001010101101001011000101011001011001100101100110011001100110010110100110100101101010010101101001101001100101010110100101 diff --git a/sdcard/OOKFILES/Tesla_US.OOK b/sdcard/OOKFILES/Tesla_US.OOK deleted file mode 100644 index b4b81498c..000000000 --- a/sdcard/OOKFILES/Tesla_US.OOK +++ /dev/null @@ -1 +0,0 @@ -315000000 250k 2300 5 100 101010101010101010101010100010101100101100110010110011001100110011001011010011010010110101001010110100110100110010101011010010110001010110010110011001011001100110011001100101101001101001011010100101011010011010011001010101101001011000101011001011001100101100110011001100110010110100110100101101010010101101001101001100101010110100101