Flipper sub (raw+binraw AND ONLY OOK) TX (#2361)

This commit is contained in:
Totoo 2024-11-16 18:03:53 +01:00 committed by GitHub
parent 31c53dc455
commit 59f72cbff1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 889 additions and 34 deletions

View file

@ -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
)

View file

@ -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
}

View file

@ -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<FlipperTxView>();
}
} // 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
};
}

View file

@ -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<FileLoadView>(".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<FlipperPlayThread>(
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<void(uint32_t return_code)> 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<FlipperPlayThread*>(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

View file

@ -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<FlipperPlayThread> replay_thread{};
Optional<flippersub_metadata> 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<const TXProgressMessage*>(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<const RequestSignalMessage*>(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<const ReplayThreadDoneMessage*>(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<void(uint32_t return_code)> 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<void(uint32_t return_code)> terminate_callback;
Thread* thread{nullptr};
static msg_t static_fn(void* arg);
uint32_t run();
};
}; // namespace ui::external_app::flippertx
#endif /*__UI_flippertx_H__*/

View file

@ -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<flippersub_metadata> 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<flippersub_metadata> 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<int32_t> 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<uint8_t> 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<uint8_t>(std::stoul(chs, nullptr, 16));
}
bool get_flipper_binraw_bitvalue(uint8_t byte, uint8_t nthBit) {
return (byte & (1 << nthBit)) != 0;
}

View file

@ -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<flippersub_metadata> 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<int32_t> read_flipper_raw_next_data(File& f);
Optional<uint8_t> 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