2024-11-17 15:23:56 -05:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
#include "ui_ook_remote.hpp"
|
2024-11-17 15:23:56 -05:00
|
|
|
#include "portapack.hpp"
|
|
|
|
#include "io_file.hpp"
|
|
|
|
#include "ui_fileman.hpp"
|
|
|
|
#include "file_path.hpp"
|
|
|
|
#include <cstring>
|
|
|
|
#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;
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
namespace ui::external_app::ook_remote {
|
2024-11-17 15:23:56 -05:00
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
void OOKRemoteAppView::focus() {
|
2024-11-17 15:23:56 -05:00
|
|
|
button_set.focus();
|
|
|
|
}
|
|
|
|
|
|
|
|
// `start_tx` method: Configures and begins OOK data transmission with a specific message.
|
2024-11-18 14:51:49 +01:00
|
|
|
void OOKRemoteAppView::start_tx(const std::string& message) {
|
2024-11-17 15:23:56 -05:00
|
|
|
size_t bitstream_length = encoders::make_bitstream(const_cast<std::string&>(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();
|
2024-11-18 14:51:49 +01:00
|
|
|
int32_t SYMBOL_DURATION_VALUE = field_bit_duration.value();
|
|
|
|
int32_t REPEAT = field_repeat.value();
|
|
|
|
int32_t PAUSE_SYMBOL = field_pause_symbol.value();
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
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(
|
2024-11-18 14:51:49 +01:00
|
|
|
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
|
2024-11-17 15:23:56 -05:00
|
|
|
);
|
2024-11-18 14:51:49 +01:00
|
|
|
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
|
2024-11-17 15:23:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// `stop_tx` method: Stops the transmission and resets the progress bar.
|
2024-11-18 14:51:49 +01:00
|
|
|
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
|
2024-11-17 15:23:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// `on_file_changed` method: Called when a new file is loaded; parses file data into variables
|
2024-11-18 14:51:49 +01:00
|
|
|
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
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
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
|
2024-11-18 14:51:49 +01:00
|
|
|
rf::Frequency frequency = std::stoull(frequency_str);
|
2024-11-17 15:23:56 -05:00
|
|
|
field_frequency.set_value(frequency);
|
|
|
|
transmitter_model.set_target_frequency(frequency);
|
|
|
|
|
|
|
|
// Convert and assign symbols rate
|
2024-11-18 14:51:49 +01:00
|
|
|
unsigned int bit_duration_value = static_cast<unsigned int>(atoi(symbols_rate_str.c_str()));
|
2024-11-17 15:23:56 -05:00
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
field_bit_duration.set_value(bit_duration_value);
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
// Convert and assign repeat count
|
|
|
|
unsigned int repeat = static_cast<unsigned int>(atoi(repeat_str.c_str()));
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
field_repeat.set_value(repeat);
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
// Convert and assign pause per symbol
|
|
|
|
unsigned int pause_symbol = static_cast<unsigned int>(atoi(pause_symbol_str.c_str()));
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
field_pause_symbol.set_value(pause_symbol);
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
// 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);
|
2024-11-18 14:51:49 +01:00
|
|
|
button_send_stop.focus();
|
2024-11-17 15:23:56 -05:00
|
|
|
} else {
|
|
|
|
text_payload.set("parent not available");
|
|
|
|
}
|
2024-11-18 14:51:49 +01:00
|
|
|
text_loaded_file.set("Loaded: " + new_file_path.filename().string());
|
2024-11-17 15:23:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// `on_tx_progress` method: Updates the progress bar based on transmission progress.
|
2024-11-18 14:51:49 +01:00
|
|
|
void OOKRemoteAppView::on_tx_progress(const uint32_t progress, const bool done) {
|
|
|
|
progressbar.set_value(progress); // Update progress bar value
|
2024-11-17 15:23:56 -05:00
|
|
|
if (done) {
|
|
|
|
stop_tx(); // Stop transmission when progress reaches maximum
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// `draw_waveform` method: Draws the waveform on the UI based on the payload data
|
2024-11-18 14:51:49 +01:00
|
|
|
void OOKRemoteAppView::draw_waveform() {
|
2024-11-17 15:23:56 -05:00
|
|
|
// 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();
|
|
|
|
}
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
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<File>();
|
|
|
|
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() {
|
2024-11-17 15:23:56 -05:00
|
|
|
transmitter_model.disable();
|
|
|
|
baseband::shutdown();
|
|
|
|
}
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
// Constructor for `OOKRemoteAppView`: Sets up the app view and initializes UI elements
|
|
|
|
OOKRemoteAppView::OOKRemoteAppView(NavigationView& nav)
|
2024-11-17 15:23:56 -05:00
|
|
|
: nav_{nav} {
|
|
|
|
baseband::run_image(portapack::spi_flash::image_tag_ook);
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
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});
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
// Initialize default values for controls
|
2024-11-18 14:51:49 +01:00
|
|
|
field_pause_symbol.set_value(100);
|
|
|
|
field_repeat.set_value(4);
|
2024-11-17 15:23:56 -05:00
|
|
|
|
|
|
|
button_open.on_select = [this](Button&) {
|
2024-11-18 14:51:49 +01:00
|
|
|
auto open_view = nav_.push<FileLoadView>(".OOK");
|
|
|
|
ensure_directory(ook_remote_dir);
|
|
|
|
open_view->push_dir(ook_remote_dir);
|
2024-11-17 15:23:56 -05:00
|
|
|
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);
|
2024-11-18 14:51:49 +01:00
|
|
|
button_send_stop.focus();
|
2024-11-17 15:23:56 -05:00
|
|
|
draw_waveform();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
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
|
2024-11-17 15:23:56 -05:00
|
|
|
};
|
2024-11-18 14:51:49 +01:00
|
|
|
// 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);
|
2024-11-17 15:23:56 -05:00
|
|
|
|
2024-11-18 14:51:49 +01:00
|
|
|
// 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<FrequencyKeypadView>(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);
|
2024-11-17 15:23:56 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
// 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();
|
2024-11-18 14:51:49 +01:00
|
|
|
text_loaded_file.set(""); // Clear loaded file text field
|
2024-11-17 15:23:56 -05:00
|
|
|
});
|
|
|
|
};
|
|
|
|
draw_waveform();
|
2024-11-18 14:51:49 +01:00
|
|
|
button_send_stop.on_select = [this](Button&) {
|
|
|
|
if (!is_transmitting) {
|
|
|
|
start_tx(payload); // Begin transmission
|
|
|
|
} else {
|
|
|
|
stop_tx();
|
|
|
|
}
|
2024-11-17 15:23:56 -05:00
|
|
|
};
|
|
|
|
}
|
2024-11-18 14:51:49 +01:00
|
|
|
} // namespace ui::external_app::ook_remote
|