diff --git a/firmware/application/external/external.cmake b/firmware/application/external/external.cmake index 8560cf68..02658c86 100644 --- a/firmware/application/external/external.cmake +++ b/firmware/application/external/external.cmake @@ -117,7 +117,12 @@ set(EXTCPPSRC #cvs_spam external/cvs_spam/main.cpp - external/cvs_spam/cvs_spam.cpp + external/cvs_spam/cvs_spam.cpp + + #flippertx + external/flippertx/main.cpp + external/flippertx/ui_flippertx.cpp + ) set(EXTAPPLIST @@ -149,4 +154,5 @@ set(EXTAPPLIST #acars_rx ookbrute shoppingcart_lock + flippertx ) diff --git a/firmware/application/external/external.ld b/firmware/application/external/external.ld index cfcc20fb..4dc986d8 100644 --- a/firmware/application/external/external.ld +++ b/firmware/application/external/external.ld @@ -51,6 +51,7 @@ MEMORY ram_external_app_shoppingcart_lock(rwx) : org = 0xADCA0000, len = 32k 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 } SECTIONS @@ -224,4 +225,10 @@ SECTIONS *(*ui*external_app*ookbrute*); } > ram_external_app_ookbrute + .external_app_flippertx : ALIGN(4) SUBALIGN(4) + { + KEEP(*(.external_app.app_flippertx.application_information)); + *(*ui*external_app*flippertx*); + } > ram_external_app_flippertx + } diff --git a/firmware/application/external/flippertx/main.cpp b/firmware/application/external/flippertx/main.cpp new file mode 100644 index 00000000..48613d7a --- /dev/null +++ b/firmware/application/external/flippertx/main.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2023 Bernd Herzog + * + * 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_flippertx.hpp" +#include "ui_navigation.hpp" +#include "external_app.hpp" + +namespace ui::external_app::flippertx { +void initialize_app(ui::NavigationView& nav) { + nav.push(); +} +} // namespace ui::external_app::flippertx + +extern "C" { + +__attribute__((section(".external_app.app_flippertx.application_information"), used)) application_information_t _application_information_flippertx = { + /*.memory_location = */ (uint8_t*)0x00000000, + /*.externalAppEntry = */ ui::external_app::flippertx::initialize_app, + /*.header_version = */ CURRENT_HEADER_VERSION, + /*.app_version = */ VERSION_MD5, + + /*.app_name = */ "FlipperTx", + /*.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_ookstreaming */ {'P', 'O', 'S', 'K'}, + /*.m4_app_offset = */ 0x00000000, // will be filled at compile time +}; +} diff --git a/firmware/application/external/flippertx/ui_flippertx.cpp b/firmware/application/external/flippertx/ui_flippertx.cpp new file mode 100644 index 00000000..03572ed0 --- /dev/null +++ b/firmware/application/external/flippertx/ui_flippertx.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2024 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_flippertx.hpp" +#include "audio.hpp" +#include "rtc_time.hpp" +#include "baseband_api.hpp" +#include "string_format.hpp" +#include "portapack_persistent_memory.hpp" +#include "buffer_exchange.hpp" + +using namespace portapack; +using namespace ui; + +namespace ui::external_app::flippertx { + +void FlipperTxView::focus() { + button_browse.focus(); +} + +void FlipperTxView::set_ready() { + ready_signal = true; +} + +FlipperTxView::FlipperTxView(NavigationView& nav) + : nav_{nav} { + add_children({&button_startstop, + &field_frequency, + &tx_view, + &field_filename, + &button_browse}); + + button_startstop.on_select = [this](Button&) { + if (is_running) { + stop(); + } else { + start(); + } + }; + + button_browse.on_select = [this](Button&) { + auto open_view = nav_.push(".sub"); + open_view->push_dir(subghz_dir); + open_view->on_changed = [this](std::filesystem::path new_file_path) { + if (on_file_changed(new_file_path)) { + ; // tif (button_startstop.parent()) button_startstop.focus(); parent_ is null error..... + } + }; + }; +} + +bool FlipperTxView::on_file_changed(std::filesystem::path new_file_path) { + submeta = read_flippersub_file(new_file_path); + filename = ""; + if (!submeta.is_valid()) { + field_filename.set_text("File: err, bad file"); + return false; + } + proto = submeta.value().protocol; + if (proto != FLIPPER_PROTO_RAW && proto != FLIPPER_PROTO_BINRAW) { // temp disabled + field_filename.set_text("File: err, not supp. proto"); + return false; + } + preset = submeta.value().preset; + if (preset != FLIPPER_PRESET_OOK) { + field_filename.set_text("File: err, not supp. preset"); + return false; + } + te = submeta.value().te; + binraw_bit_count = submeta.value().binraw_bit_count; + /*if (binraw_bit_count >= 512 * 800) { + field_filename.set_text("File: err, too long"); // should stream, but not in this scope YET + return false; + }*/ + field_filename.set_text("File: " + new_file_path.string()); + filename = new_file_path; + return true; +} + +void FlipperTxView::stop() { + if (is_running && replay_thread) replay_thread.reset(); + is_running = false; + ready_signal = false; + + transmitter_model.disable(); + baseband::shutdown(); + button_startstop.set_text(LanguageHelper::currentMessages[LANG_START]); +} + +bool FlipperTxView::start() { + if (filename.empty()) return false; + baseband::run_prepared_image(portapack::memory::map::m4_code.base()); + transmitter_model.enable(); + button_startstop.set_text(LanguageHelper::currentMessages[LANG_STOP]); + // start thread + replay_thread = std::make_unique( + filename, + 1024, 4, + &ready_signal, + submeta.value().protocol, + submeta.value().te, + [](uint32_t return_code) { + ReplayThreadDoneMessage message{return_code}; + EventDispatcher::send_message(message); + }); + is_running = true; + return true; +} + +void FlipperTxView::on_tx_progress(const bool done) { + if (done) { + if (is_running) { + stop(); + } + } +} + +FlipperTxView::~FlipperTxView() { + stop(); +} + +FlipperPlayThread::FlipperPlayThread( + std::filesystem::path filename, + size_t read_size, + size_t buffer_count, + bool* ready_signal, + FlipperProto proto, + uint16_t te, + std::function terminate_callback) + : config{read_size, buffer_count}, + filename{filename}, + ready_sig{ready_signal}, + proto{proto}, + te{te}, + terminate_callback{std::move(terminate_callback)} { + thread = chThdCreateFromHeap(NULL, 6 * 1024, NORMALPRIO + 10, FlipperPlayThread::static_fn, this); +} + +FlipperPlayThread::~FlipperPlayThread() { + if (thread) { + chThdTerminate(thread); + if (thread->p_refs) chThdWait(thread); + thread = nullptr; + } +} + +msg_t FlipperPlayThread::static_fn(void* arg) { + auto obj = static_cast(arg); + const auto return_code = obj->run(); + if (obj->terminate_callback) { + obj->terminate_callback(return_code); + } + chThdExit(0); + return 0; +} + +uint32_t FlipperPlayThread::run() { + BasebandReplay replay{&config}; + BufferExchange buffers{&config}; + StreamBuffer* prefill_buffer{nullptr}; + int32_t read_result = 0; + // assume it is ok, since prev we checked it. + // open the sub file + File f; // don't worry, destructor will close it. + auto error = f.open(filename); + if (error) return READ_ERROR; + // seek to the first data + if (proto == FLIPPER_PROTO_RAW) + seek_flipper_raw_first_data(f); + else { + seek_flipper_binraw_first_data(f); + } + // Wait for FIFOs to be allocated in baseband + // Wait for _view to tell us that the buffers are ready + while (!(*ready_sig) && !chThdShouldTerminate()) { + chThdSleep(100); + }; + uint8_t readall = 0; + int32_t endsignals[3] = {0, 42069, 613379}; // unlikely a valid data + // While empty buffers fifo is not empty... + while (!buffers.empty() && !chThdShouldTerminate()) { + prefill_buffer = buffers.get_prefill(); + + if (prefill_buffer == nullptr) { + buffers.put_app(prefill_buffer); + } else { + size_t c = 0; + for (c = 0; c < config.read_size / 4;) { + read_result = 0; + if (0 == readall) { + if (proto == FLIPPER_PROTO_RAW) { + auto data = read_flipper_raw_next_data(f); + if (!data.is_valid()) { + ((int32_t*)prefill_buffer->data())[c] = endsignals[++readall]; + } else { + read_result = data.value(); + ((int32_t*)prefill_buffer->data())[c] = read_result; + } + c++; + + } else { // BINRAW + auto data = read_flipper_binraw_next_data(f); + if (!data.is_valid()) + readall = 1; + else { + read_result = data.value(); + // add 8 data + for (uint8_t bit = 0; bit < 8; bit++) { + ((int32_t*)prefill_buffer->data())[c + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te); + c++; + } + } + } + } else { + ((int32_t*)prefill_buffer->data())[c] = endsignals[readall]; + if (readall == 1) readall++; + c++; + } + } + prefill_buffer->set_size(config.read_size); + buffers.put(prefill_buffer); + } + }; + baseband::set_fifo_data(nullptr); + if (readall) return END_OF_FILE; + + while (!chThdShouldTerminate()) { + auto buffer = buffers.get(); + int32_t read_result = 0; + for (size_t d = 0; d < buffer->capacity() / 4; d++) { + read_result = -133469; + if (!readall) { + if (proto == FLIPPER_PROTO_RAW) { + auto data = read_flipper_raw_next_data(f); + if (!data.is_valid()) { + readall = 1; + } else { + read_result = data.value(); + } + } else { + auto data = read_flipper_binraw_next_data(f); + if (!data.is_valid()) + readall = 1; + else { + read_result = data.value(); + // add 8 data + for (uint8_t bit = 0; bit < 8; bit++) { + ((int32_t*)prefill_buffer->data())[d + bit] = ((get_flipper_binraw_bitvalue(read_result, (7 - bit)) ? 1 : -1) * te); + } + d += 7; + continue; + } + } + } + if (readall >= 1 && readall <= 2) { + read_result = endsignals[readall++]; + } + ((int32_t*)buffer->data())[d] = read_result; + } + buffer->set_size(buffer->capacity()); + buffers.put(buffer); + if (readall == 3) return END_OF_FILE; + } + return TERMINATED; +} + +} // namespace ui::external_app::flippertx diff --git a/firmware/application/external/flippertx/ui_flippertx.hpp b/firmware/application/external/flippertx/ui_flippertx.hpp new file mode 100644 index 00000000..a1e05877 --- /dev/null +++ b/firmware/application/external/flippertx/ui_flippertx.hpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2024 HTotoo + * + * This file is part of PortaPack. + * + */ + +#ifndef __UI_flippertx_H__ +#define __UI_flippertx_H__ + +#include "ui.hpp" +#include "ui_language.hpp" +#include "ui_navigation.hpp" +#include "ui_transmitter.hpp" +#include "ui_freq_field.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "utility.hpp" +#include "string_format.hpp" +#include "file_path.hpp" +#include "metadata_file.hpp" +#include "flipper_subfile.hpp" +#include "ui_fileman.hpp" +#include "baseband_api.hpp" + +using namespace ui; + +namespace ui::external_app::flippertx { + +#define OOK_SAMPLERATE 2280000U +class FlipperPlayThread; +class FlipperTxView : public View { + public: + FlipperTxView(NavigationView& nav); + ~FlipperTxView(); + + void focus() override; + + std::string title() const override { + return "FlipperTx"; + }; + + private: + NavigationView& nav_; + TxRadioState radio_state_{ + 433920000 /* frequency */, + 1750000 /* bandwidth */, + OOK_SAMPLERATE /* sampling rate */ + }; + + TxFrequencyField field_frequency{ + {0 * 8, 0 * 16}, + nav_}; + TransmitterView2 tx_view{ + {11 * 8, 0 * 16}, + /*short_ui*/ true}; + + app_settings::SettingsManager settings_{ + "tx_flippertx", app_settings::Mode::TX}; + + TextField field_filename{ + {0, 2 * 16, 300, 1 * 8}, + "File: -"}; + + Button button_startstop{ + {1, 6 * 16, 96, 24}, + LanguageHelper::currentMessages[LANG_START]}; + + Button button_browse{ + {1, 3 * 16 + 3, 96, 24}, + LanguageHelper::currentMessages[LANG_BROWSE]}; + + bool is_running{false}; + + bool start(); + void stop(); + + void set_ready(); + void on_tx_progress(const bool done); + bool on_file_changed(std::filesystem::path new_file_path); + + std::filesystem::path filename = {}; + FlipperProto proto = FLIPPER_PROTO_UNSUPPORTED; + FlipperPreset preset = FLIPPER_PRESET_UNK; + uint16_t te = 0; // for binraw + uint32_t binraw_bit_count = 0; + bool ready_signal = false; + + std::unique_ptr replay_thread{}; + Optional submeta{}; + + const std::filesystem::path subghz_dir = u"subghz"; + + MessageHandlerRegistration message_handler_tx_progress{ + Message::ID::TXProgress, + [this](const Message* const p) { + const auto message = *reinterpret_cast(p); + this->on_tx_progress(message.done); + }}; + + MessageHandlerRegistration message_handler_fifo_signal{ + Message::ID::RequestSignal, + [this](const Message* const p) { + const auto message = static_cast(p); + if (message->signal == RequestSignalMessage::Signal::FillRequest) { + this->set_ready(); + } + }}; + + MessageHandlerRegistration message_handler_replay_thread_done{ + Message::ID::ReplayThreadDone, + [this](const Message* const p) { + // const auto message = *reinterpret_cast(p); + (void)p; + // stop(); //don't stop for now, stop when i get the tx finished msg + }}; +}; + +struct BasebandReplay { + BasebandReplay(ReplayConfig* const config) { + baseband::replay_start(config); + } + + ~BasebandReplay() { + // baseband::replay_stop(); // wont, since we need to send out that is in the buffer + } +}; + +class FlipperPlayThread { + public: + FlipperPlayThread( + std::filesystem::path, + size_t read_size, + size_t buffer_count, + bool* ready_signal, + FlipperProto proto, + uint16_t te, + std::function terminate_callback); + ~FlipperPlayThread(); + + FlipperPlayThread(const FlipperPlayThread&) = delete; + FlipperPlayThread(FlipperPlayThread&&) = delete; + FlipperPlayThread& operator=(const FlipperPlayThread&) = delete; + FlipperPlayThread& operator=(FlipperPlayThread&&) = delete; + + const ReplayConfig& state() const { + return config; + }; + + enum FlipperPlayThread_return { + READ_ERROR = 0, + END_OF_FILE, + TERMINATED + }; + + private: + ReplayConfig config; + std::filesystem::path filename; + bool* ready_sig; + FlipperProto proto; + uint16_t te; + std::function terminate_callback; + Thread* thread{nullptr}; + + static msg_t static_fn(void* arg); + + uint32_t run(); +}; +}; // namespace ui::external_app::flippertx + +#endif /*__UI_flippertx_H__*/ diff --git a/firmware/application/flipper_subfile.cpp b/firmware/application/flipper_subfile.cpp index b172ee29..fe57d976 100644 --- a/firmware/application/flipper_subfile.cpp +++ b/firmware/application/flipper_subfile.cpp @@ -29,15 +29,16 @@ namespace fs = std::filesystem; using namespace std::literals; -const std::string_view filetype_name = "Filetype"sv; -const std::string_view frequency_name = "Frequency"sv; -const std::string_view latitude_name_old = "Latitute"sv; -const std::string_view longitude_name_old = "Longitude"sv; -const std::string_view latitude_name = "Lat"sv; -const std::string_view longitude_name = "Lon"sv; -const std::string_view protocol_name = "Protocol"sv; -const std::string_view preset_name = "Preset"sv; -const std::string_view te_name = "TE"sv; // only in BinRAW +const std::string filetype_name = "Filetype"; +const std::string frequency_name = "Frequency"; +const std::string latitude_name_old = "Latitute"; +const std::string longitude_name_old = "Longitude"; +const std::string latitude_name = "Lat"; +const std::string longitude_name = "Lon"; +const std::string protocol_name = "Protocol"; +const std::string preset_name = "Preset"; +const std::string te_name = "TE"; // only in BinRAW +const std::string bit_count_name = "Bit"; // for us, only in BinRAW /* Filetype: Flipper SubGhz Key File @@ -72,39 +73,54 @@ raw_data- positive: carrier for n time, negative: no carrier for n time. (us) Optional read_flippersub_file(const fs::path& path) { File f; auto error = f.open(path); - if (error) return {}; - flippersub_metadata metadata{}; - auto reader = FileLineReader(f); - for (const auto& line : reader) { - auto cols = split_string(line, ':'); - - if (cols.size() != 2) + char ch = 0; + std::string line = ""; + auto fr = f.read(&ch, 1); + while (!fr.is_error() && fr.value() > 0) { + if (line.length() < 130 && ch != '\n') line += ch; + if (ch != '\n') { + fr = f.read(&ch, 1); + continue; + } + auto it = line.find(':', 0); + if (it == std::string::npos) { + fr = f.read(&ch, 1); continue; // Bad line. - if (cols[1].length() <= 1) continue; - std::string fixed = cols[1].data() + 1; + } + std::string fixed = line.data() + it + 1; fixed = trim(fixed); - if (cols[0] == filetype_name) { + std::string head = line.substr(0, it); + line = ""; + + if (fixed.length() <= 1) { + fr = f.read(&ch, 1); + continue; + } + + if (head == filetype_name) { if (fixed != "Flipper SubGhz Key File" && fixed != "Flipper SubGhz RAW File") return {}; // not supported - } else if (cols[0] == frequency_name) + } else if (head == frequency_name) parse_int(fixed, metadata.center_frequency); - else if (cols[0] == latitude_name) + else if (head == latitude_name) parse_float_meta(fixed, metadata.latitude); - else if (cols[0] == longitude_name) + else if (head == longitude_name) parse_float_meta(fixed, metadata.longitude); - else if (cols[0] == latitude_name_old) + else if (head == latitude_name_old) parse_float_meta(fixed, metadata.latitude); - else if (cols[0] == longitude_name_old) + else if (head == longitude_name_old) parse_float_meta(fixed, metadata.longitude); - else if (cols[0] == protocol_name) { + else if (head == protocol_name) { if (fixed == "RAW") metadata.protocol = FLIPPER_PROTO_RAW; if (fixed == "BinRAW") metadata.protocol = FLIPPER_PROTO_BINRAW; - } else if (cols[0] == te_name) { + } else if (head == te_name) { metadata.te = atoi(fixed.c_str()); - } else if (cols[0] == preset_name) { + } else if (head == bit_count_name) { + metadata.binraw_bit_count = atol(fixed.c_str()); + } else if (head == preset_name) { if (fixed.find("FSK") != std::string::npos) { metadata.preset = FLIPPER_PRESET_2FSK; } else if (fixed.find("Ook") != std::string::npos) { @@ -112,12 +128,92 @@ Optional read_flippersub_file(const fs::path& path) { } else if (fixed.find("Custom") != std::string::npos) { metadata.preset = FLIPPER_PRESET_CUSTOM; } - - } else - continue; + } + fr = f.read(&ch, 1); } - + f.close(); if (metadata.center_frequency == 0) return {}; // Parse failed. return metadata; } + +bool seek_flipper_raw_first_data(File& f) { + f.seek(0); + std::string chs = ""; + char ch; + while (f.read(&ch, 1)) { + if (ch == '\r') continue; + if (ch == '\n') { + chs = ""; + continue; + }; + chs += ch; + if (ch == 0) break; + if (chs == "RAW_Data: ") { + return true; + } + } + return false; +} +bool seek_flipper_binraw_first_data(File& f, bool seekzero) { + if (seekzero) f.seek(0); + std::string chs = ""; + char ch; + while (f.read(&ch, 1)) { + if (ch == '\r') continue; + if (ch == '\n') { + chs = ""; + continue; + }; + if (ch == 0) break; + chs += ch; + if (chs == "Data_RAW: ") { + return true; + } + } + return false; +} + +Optional read_flipper_raw_next_data(File& f) { + // RAW_Data: 5832 -12188 130 -162 + std::string chs = ""; + char ch = 0; + while (f.read(&ch, 1).is_ok()) { + if (ch == '\r') continue; // should not present + if ((ch == ' ') || ch == '\n') { + if (chs == "RAW_Data:") { + chs = ""; + continue; + } + break; + }; + if (ch == 0) break; + chs += ch; + } + if (chs == "") return {}; + return atol(chs.c_str()); +} + +Optional read_flipper_binraw_next_data(File& f) { + // Data_RAW: 02 10 84 BUT THERE ARE Bit_RAW lines to skip! + std::string chs = ""; + char ch = 0; + while (f.read(&ch, 1)) { + if (ch == '\r') continue; // should not present + if ((ch == ' ') || ch == '\n') { + if (chs == "RAW_Data:") { + chs = ""; + continue; + } + break; + }; + if (ch == 0) break; + chs += ch; + } + if (chs == "") return {}; + return static_cast(std::stoul(chs, nullptr, 16)); +} + +bool get_flipper_binraw_bitvalue(uint8_t byte, uint8_t nthBit) { + return (byte & (1 << nthBit)) != 0; +} \ No newline at end of file diff --git a/firmware/application/flipper_subfile.hpp b/firmware/application/flipper_subfile.hpp index 0a123edf..3681c849 100644 --- a/firmware/application/flipper_subfile.hpp +++ b/firmware/application/flipper_subfile.hpp @@ -44,9 +44,15 @@ struct flippersub_metadata { FlipperProto protocol = FLIPPER_PROTO_UNSUPPORTED; FlipperPreset preset = FLIPPER_PRESET_UNK; uint16_t te = 0; + uint32_t binraw_bit_count = 0; }; Optional read_flippersub_file(const std::filesystem::path& path); +bool seek_flipper_raw_first_data(File& f); +bool seek_flipper_binraw_first_data(File& f, bool seekzero = true); +Optional read_flipper_raw_next_data(File& f); +Optional read_flipper_binraw_next_data(File& f); +bool get_flipper_binraw_bitvalue(uint8_t byte, uint8_t nthBit); // Maybe sometime there will be a data part reader / converter diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index 51be53be..232ec34b 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -656,6 +656,14 @@ set(MODE_CPPSRC ) DeclareTargets(PADT adsbtx) +### OOKStream + +set(MODE_CPPSRC + proc_ook_stream_tx.cpp +) +DeclareTargets(POSK ookstream) + + ### HackRF "factory" firmware diff --git a/firmware/baseband/proc_ook_stream_tx.cpp b/firmware/baseband/proc_ook_stream_tx.cpp new file mode 100644 index 00000000..a82f413c --- /dev/null +++ b/firmware/baseband/proc_ook_stream_tx.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2024 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_ook_stream_tx.hpp" +#include "sine_table_int8.hpp" +#include "portapack_shared_memory.hpp" + +#include "event_m4.hpp" + +#include "utility.hpp" + +OOKProcessorStreamed::OOKProcessorStreamed() { + configured = false; + baseband_thread.start(); +} + +inline void OOKProcessorStreamed::write_sample(const buffer_c8_t& buffer, bool bit_value, size_t i) { + int8_t re, im; + + if (bit_value) { + phase = (phase + 200); // What ? + sphase = phase + (64 << 18); + re = (sine_table_i8[(sphase & 0x03FC0000) >> 18]); + im = (sine_table_i8[(phase & 0x03FC0000) >> 18]); + } else { + re = 0; + im = 0; + } + buffer.p[i] = {re, im}; +} + +void OOKProcessorStreamed::execute(const buffer_c8_t& buffer) { + if (!configured || !stream) return; + + for (size_t i = 0; i < buffer.count; i++) { + if (rem_samples <= curr_samples) { + // get a new sample from stream + int32_t sample = -13346; + rem_samples = 0; // reset my pointer + curr_samples = 0; + size_t readed = 0; + if (configured) readed = stream->read(&sample, 4); // read from stream // todo htotoo error handling?! + if (readed == 0) { + txprogress_message.progress = -10; + shared_memory.application_queue.push(txprogress_message); // debug + } else { + if (sample == endsignals[readerrs + 1]) { // if no more samples, stop + readerrs++; + if (readerrs == 2) { + configured = false; + txprogress_message.done = true; + txprogress_message.progress = 100; + curr_hilow = false; + shared_memory.application_queue.push(txprogress_message); + } + } else { + if (sample < 0) { + rem_samples = OOK_SAMPLERATE * ((-1 * sample) / 1000000.0); + curr_hilow = false; + } else { + rem_samples = OOK_SAMPLERATE * (sample / 1000000.0); + curr_hilow = true; + } + } + } + } + ++curr_samples; + write_sample(buffer, curr_hilow, i); + } +} + +void OOKProcessorStreamed::on_message(const Message* const message) { + switch (message->id) { + case Message::ID::ReplayConfig: + configured = false; + replay_config(*reinterpret_cast(message)); + break; + + case Message::ID::FIFOData: + configured = true; + txprogress_message.progress = -4; + shared_memory.application_queue.push(txprogress_message); + break; + + default: + break; + } +} + +void OOKProcessorStreamed::replay_config(const ReplayConfigMessage& message) { + if (message.config) { + txprogress_message.progress = -2; + shared_memory.application_queue.push(txprogress_message); + stream = std::make_unique(message.config); + // Tell application that the buffers and FIFO pointers are ready, prefill + RequestSignalMessage sig_message{RequestSignalMessage::Signal::FillRequest}; + shared_memory.application_queue.push(sig_message); + } else { + txprogress_message.progress = -3; + shared_memory.application_queue.push(txprogress_message); + stream.reset(); + } +} + +int main() { + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_ook_stream_tx.hpp b/firmware/baseband/proc_ook_stream_tx.hpp new file mode 100644 index 00000000..44c1629e --- /dev/null +++ b/firmware/baseband/proc_ook_stream_tx.hpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2024 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. + */ + +/* + For usage check the FlipperTX app. Don't forget to "close" the tx by sending 42069, 613379 values at the end of the stream. + Only close baseband when you get the TX done msg, not on replay thread done, because, there can still data in the buffer. +*/ + +#ifndef __PROC_OOKSTREAMTX_HPP__ +#define __PROC_OOKSTREAMTX_HPP__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" +#include "stream_output.hpp" +#include +#include + +#define OOK_SAMPLERATE 2280000U + +class OOKProcessorStreamed : public BasebandProcessor { + public: + OOKProcessorStreamed(); + + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const message) override; + + private: + uint32_t rem_samples = 0; + uint32_t curr_samples = 0; + bool curr_hilow = false; + + uint32_t phase{0}, sphase{0}; + void write_sample(const buffer_c8_t& buffer, bool bit_value, size_t i); + + std::unique_ptr stream{}; + bool configured{false}; + void replay_config(const ReplayConfigMessage& message); + + int32_t endsignals[3] = {0, 42069, 613379}; // 0 is skipped, count from 1, don't ask... + uint8_t readerrs = 0; // to count in the array + + TXProgressMessage txprogress_message{}; + /* NB: Threads should be the last members in the class definition. */ + BasebandThread baseband_thread{OOK_SAMPLERATE, this, baseband::Direction::Transmit}; +}; + +#endif /*__PROC_OOKSTREAMTX_HPP__*/ diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index ccc3d9cb..122ef23c 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -104,6 +104,7 @@ constexpr image_tag_t image_tag_fskrx{'P', 'F', 'S', 'R'}; constexpr image_tag_t image_tag_jammer{'P', 'J', 'A', 'M'}; constexpr image_tag_t image_tag_mic_tx{'P', 'M', 'T', 'X'}; constexpr image_tag_t image_tag_ook{'P', 'O', 'O', 'K'}; +constexpr image_tag_t image_tag_ookstream{'P', 'O', 'S', 'K'}; constexpr image_tag_t image_tag_rds{'P', 'R', 'D', 'S'}; constexpr image_tag_t image_tag_replay{'P', 'R', 'E', 'P'}; constexpr image_tag_t image_tag_gps{'P', 'G', 'P', 'S'}; diff --git a/firmware/common/ui_language.cpp b/firmware/common/ui_language.cpp index 206fbe0b..19225dc5 100644 --- a/firmware/common/ui_language.cpp +++ b/firmware/common/ui_language.cpp @@ -1,7 +1,7 @@ #include "ui_language.hpp" // use the exact position in this array! the enum's value is the identifier. Best to add to the end -const char* LanguageHelper::englishMessages[] = {"OK", "Cancel", "Error", "Modem setup", "Debug", "Log", "Done", "Start", "Stop", "Scan", "Clear", "Ready", "Data:", "Loop", "Reset", "Pause", "Resume", "Flood", "Show QR", "Save", "Lock", "Unlock"}; +const char* LanguageHelper::englishMessages[] = {"OK", "Cancel", "Error", "Modem setup", "Debug", "Log", "Done", "Start", "Stop", "Scan", "Clear", "Ready", "Data:", "Loop", "Reset", "Pause", "Resume", "Flood", "Show QR", "Save", "Lock", "Unlock", "Browse"}; // multi language support will changes (not in use for now) const char** LanguageHelper::currentMessages = englishMessages; diff --git a/firmware/common/ui_language.hpp b/firmware/common/ui_language.hpp index 78dd213e..6c550208 100644 --- a/firmware/common/ui_language.hpp +++ b/firmware/common/ui_language.hpp @@ -60,7 +60,8 @@ enum LangConsts { LANG_SHOWQR, LANG_SAVE, LANG_LOCK, - LANG_UNLOCK + LANG_UNLOCK, + LANG_BROWSE }; class LanguageHelper {