From dceb7255b039f15b2212f4f3808c4035756118ca Mon Sep 17 00:00:00 2001 From: Netro <146584182+iNetro@users.noreply.github.com> Date: Wed, 1 Nov 2023 06:46:41 -0400 Subject: [PATCH] BLE Tx App / BLE Rx Filtering. (#1543) BLE TX app creation BLE RX and TX app improvements --- firmware/application/CMakeLists.txt | 1 + firmware/application/apps/ble_app.cpp | 111 ++++-- firmware/application/apps/ble_app.hpp | 17 +- firmware/application/apps/ble_tx_app.cpp | 351 +++++++++++++++++++ firmware/application/apps/ble_tx_app.hpp | 207 +++++++++++ firmware/application/baseband_api.cpp | 8 + firmware/application/baseband_api.hpp | 1 + firmware/application/recent_entries.hpp | 18 + firmware/application/string_format.cpp | 14 + firmware/application/string_format.hpp | 1 + firmware/application/ui/ui_alphanum.cpp | 11 +- firmware/application/ui/ui_alphanum.hpp | 6 +- firmware/application/ui_navigation.cpp | 2 + firmware/baseband/CMakeLists.txt | 8 + firmware/baseband/proc_ble_tx.cpp | 422 +++++++++++++++++++++++ firmware/baseband/proc_ble_tx.hpp | 239 +++++++++++++ firmware/common/message.hpp | 17 + firmware/common/spi_image.hpp | 1 + sdcard/BLETX/BLE Tx Sample.txt | 1 + 19 files changed, 1402 insertions(+), 34 deletions(-) create mode 100644 firmware/application/apps/ble_tx_app.cpp create mode 100644 firmware/application/apps/ble_tx_app.hpp create mode 100644 firmware/baseband/proc_ble_tx.cpp create mode 100644 firmware/baseband/proc_ble_tx.hpp create mode 100644 sdcard/BLETX/BLE Tx Sample.txt diff --git a/firmware/application/CMakeLists.txt b/firmware/application/CMakeLists.txt index ba5f7814c..680048dbe 100644 --- a/firmware/application/CMakeLists.txt +++ b/firmware/application/CMakeLists.txt @@ -245,6 +245,7 @@ set(CPPSRC apps/analog_audio_app.cpp apps/analog_tv_app.cpp apps/ble_app.cpp + apps/ble_tx_app.cpp apps/capture_app.cpp apps/ert_app.cpp apps/gps_sim_app.cpp diff --git a/firmware/application/apps/ble_app.cpp b/firmware/application/apps/ble_app.cpp index 36185634e..e98994ed5 100644 --- a/firmware/application/apps/ble_app.cpp +++ b/firmware/application/apps/ble_app.cpp @@ -30,6 +30,7 @@ #include "baseband_api.hpp" #include "string_format.hpp" #include "portapack_persistent_memory.hpp" +#include "ui_textentry.hpp" using namespace portapack; using namespace modems; @@ -214,15 +215,32 @@ BLERxView::BLERxView(NavigationView& nav) &check_log, &label_sort, &options_sort, + &button_message, &recent_entries_view, + &recent_entries_filter_view, &recent_entry_detail_view}); recent_entry_detail_view.hidden(true); + recent_entries_filter_view.hidden(true); recent_entries_view.on_select = [this](const BleRecentEntry& entry) { nav_.push(entry); }; + recent_entries_filter_view.on_select = [this](const BleRecentEntry& entry) { + nav_.push(entry); + }; + + button_message.on_select = [this, &nav](Button&) { + text_prompt( + nav, + filterBuffer, + 64, + [this](std::string& buffer) { + on_switch_table(buffer); + }); + }; + field_frequency.set_step(0); check_log.set_value(logging); @@ -244,20 +262,27 @@ BLERxView::BLERxView(NavigationView& nav) case 0: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); break; case 1: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); break; case 2: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); break; default: break; } recent_entries_view.set_dirty(); + recent_entries_filter_view.set_dirty(); }; options_channel.set_selected_index(0, true); @@ -319,8 +344,6 @@ void BLERxView::on_data(BlePacketData* packet) { break; } - // str_console += to_string_dec_uint(value); - str_console += " Len:"; str_console += to_string_dec_uint(packet->size); @@ -335,7 +358,7 @@ void BLERxView::on_data(BlePacketData* packet) { int i; for (i = 0; i < packet->dataLen; i++) { - str_console += " " + to_string_hex(packet->data[i], 2); + str_console += to_string_hex(packet->data[i], 2); } str_console += "\n"; @@ -345,9 +368,60 @@ void BLERxView::on_data(BlePacketData* packet) { // Start of Packet stuffing. // Masking off the top 2 bytes to avoid invalid keys. auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF); + updateEntry(packet, entry); + + // Log at End of Packet. + if (logger && logging) { + logger->log_raw_data(str_console); + } +} + +void BLERxView::on_switch_table(const std::string value) { + filter = value; + + if (!value.empty()) { + removeEntriesWithoutKey(recent, filterEntries, [&value](const BleRecentEntry& entry) { + return entry.dataString.find(value) == std::string::npos; + }); + + recent_entries_view.set_dirty(); + + recent_entries_filter_view.hidden(false); + recent_entries_view.hidden(true); + } else { + recent_entries_view.hidden(false); + recent_entries_filter_view.hidden(true); + } + + recent_entries_view.set_dirty(); + recent_entries_filter_view.set_dirty(); +} + +void BLERxView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height}; + recent_entries_view.set_parent_rect(content_rect); + recent_entry_detail_view.set_parent_rect(content_rect); + recent_entries_filter_view.set_parent_rect(content_rect); +} + +BLERxView::~BLERxView() { + receiver_model.disable(); + baseband::shutdown(); +} + +void BLERxView::updateEntry(const BlePacketData* packet, BleRecentEntry& entry) { + std::string data_string; + + int i; + + for (i = 0; i < packet->dataLen; i++) { + data_string += to_string_hex(packet->data[i], 2); + } entry.dbValue = packet->max_dB; entry.timestamp = to_string_timestamp(rtc_time::now()); + entry.dataString = data_string; entry.packetData.type = packet->type; entry.packetData.size = packet->size; @@ -370,48 +444,31 @@ void BLERxView::on_data(BlePacketData* packet) { case 0: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); break; case 1: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); break; case 2: sortEntriesBy( recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); + sortEntriesBy( + filterEntries, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); break; default: break; } - recent_entries_view.set_dirty(); + on_switch_table(filter); // TODO: Crude hack, should be a more formal listener arrangement... if (entry.key() == recent_entry_detail_view.entry().key()) { recent_entry_detail_view.set_entry(entry); } - - // Log at End of Packet. - if (logger && logging) { - logger->log_raw_data(str_console); - } } -void BLERxView::set_parent_rect(const Rect new_parent_rect) { - View::set_parent_rect(new_parent_rect); - const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height}; - recent_entries_view.set_parent_rect(content_rect); - recent_entry_detail_view.set_parent_rect(content_rect); -} - -BLERxView::~BLERxView() { - receiver_model.disable(); - baseband::shutdown(); -} - -// BleRecentEntry -// void BleRecentEntry::update(const BlePacketData * packet) -// { - -// } - } /* namespace ui */ diff --git a/firmware/application/apps/ble_app.hpp b/firmware/application/apps/ble_app.hpp index 6d516afc8..033963a0c 100644 --- a/firmware/application/apps/ble_app.hpp +++ b/firmware/application/apps/ble_app.hpp @@ -77,6 +77,7 @@ struct BleRecentEntry { int dbValue; BlePacketData packetData; std::string timestamp; + std::string dataString; BleRecentEntry() : BleRecentEntry{0} { @@ -87,14 +88,13 @@ struct BleRecentEntry { : macAddress{macAddress}, dbValue{}, packetData{}, - timestamp{} { + timestamp{}, + dataString{} { } Key key() const { return macAddress; } - - // void update(const BlePacketData * packet); }; using BleRecentEntries = RecentEntries; @@ -151,6 +151,8 @@ class BLERxView : public View { private: void on_data(BlePacketData* packetData); + void on_switch_table(const std::string value); + void updateEntry(const BlePacketData* packet, BleRecentEntry& entry); NavigationView& nav_; RxRadioState radio_state_{ @@ -165,6 +167,9 @@ class BLERxView : public View { uint32_t prev_value{0}; uint8_t channel_number = 37; + std::string filterBuffer{}; + std::string filter{}; + static constexpr auto header_height = 12 + 2 * 16; OptionsField options_channel{ @@ -209,6 +214,10 @@ class BLERxView : public View { {"dB", 1}, {"Recent", 2}}}; + Button button_message{ + {22 * 8, 3 * 8, 4 * 8, 16}, + "Filter"}; + Console console{ {0, 4 * 16, 240, 240}}; @@ -223,6 +232,7 @@ class BLERxView : public View { // {"Time", 8}}}; BleRecentEntries recent{}; + BleRecentEntries filterEntries{}; const RecentEntriesColumns columns{{ {"Mac Address", 20}, @@ -231,6 +241,7 @@ class BLERxView : public View { BleRecentEntry entry_{}; BleRecentEntriesView recent_entries_view{columns, recent}; + BleRecentEntriesView recent_entries_filter_view{columns, filterEntries}; BleRecentEntryDetailView recent_entry_detail_view{nav_, entry_}; MessageHandlerRegistration message_handler_packet{ diff --git a/firmware/application/apps/ble_tx_app.cpp b/firmware/application/apps/ble_tx_app.cpp new file mode 100644 index 000000000..84e3722de --- /dev/null +++ b/firmware/application/apps/ble_tx_app.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * Copyright (C) 2023 TJ Baginski + * + * 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 "ble_tx_app.hpp" + +#include "ui_fileman.hpp" +#include "ui_modemsetup.hpp" + +#include "audio.hpp" +#include "baseband_api.hpp" +#include "io_file.hpp" +#include "modems.hpp" +#include "portapack_persistent_memory.hpp" +#include "rtc_time.hpp" +#include "string_format.hpp" + +using namespace portapack; +using namespace modems; +namespace fs = std::filesystem; + +void BLELoggerTx::log_raw_data(const std::string& data) { + log_file.write_entry(data); +} + +bool hasValidHexPairs(const std::string& str, int totalPairs) { + int validPairs = 0; + + for (int i = 0; i < totalPairs * 2; i += 2) { + char c1 = str[i]; + char c2 = str[i + 1]; + + if (!(std::isxdigit(c1) && std::isxdigit(c2))) { + return false; // Return false if any pair is invalid. + } + + validPairs++; + } + + return (validPairs == totalPairs); +} + +std::vector splitIntoStrings(const char* input) { + std::vector result; + int length = std::strlen(input); + int start = 0; + + while (start < length) { + int remaining = length - start; + int chunkSize = (remaining > 30) ? 30 : remaining; + result.push_back(std::string(input + start, chunkSize)); + start += chunkSize; + } + + return result; +} + +uint32_t stringToUint32(const std::string& str) { + size_t pos = 0; + uint32_t result = 0; + + while (pos < str.size() && std::isdigit(str[pos])) { + int digit = str[pos] - '0'; + + // Check for overflow before adding the next digit + if (result > (UINT32_MAX - digit) / 10) { + return 0; + } + + result = result * 10 + digit; + pos++; + } + + // Check if there are any non-digit characters left + if (pos < str.size()) { + return 0; + } + + return result; +} + +void readUntilSpace(File& file, char* result, std::size_t maxBufferSize) { + std::size_t bytesRead = 0; + + while (true) { + char ch; + File::Result readResult = file.read(&ch, 1); + + if (readResult.is_ok() && readResult.value() > 0) { + if (ch == ' ') { + // Found a space character, stop reading + break; + } else if (bytesRead < maxBufferSize) { + // Append the character to the result if there's space + result[bytesRead++] = ch; + } else { + // Buffer is full, break to prevent overflow + break; + } + } else { + break; // End of file or error + } + } + + // Null-terminate the result string + result[bytesRead] = '\0'; +} + +static std::uint64_t get_freq_by_channel_number(uint8_t channel_number) { + uint64_t freq_hz; + + switch (channel_number) { + case 37: + freq_hz = 2'402'000'000ull; + break; + case 38: + freq_hz = 2'426'000'000ull; + break; + case 39: + freq_hz = 2'480'000'000ull; + break; + case 0 ... 10: + freq_hz = 2'404'000'000ull + channel_number * 2'000'000ull; + break; + case 11 ... 36: + freq_hz = 2'428'000'000ull + (channel_number - 11) * 2'000'000ull; + break; + default: + freq_hz = UINT64_MAX; + } + + return freq_hz; +} + +namespace ui { + +void BLETxView::focus() { + field_frequency.focus(); +} + +bool BLETxView::is_active() const { + return (bool)is_running; +} + +void BLETxView::file_error() { + nav_.display_modal("Error", "File read error."); +} + +void BLETxView::toggle() { + if (is_active()) { + stop(); + } else { + start(); + } +} + +void BLETxView::start() { + if (!is_active()) { + // Check if file is present before continuing. + File data_file; + + auto error = data_file.open(file_path); + if (error) { + file_error(); + check_loop.set_value(false); + return; + } else { + // Send first or single packet. + progressbar.set_max(packet_count); + button_play.set_bitmap(&bitmap_stop); + baseband::set_btletx(channel_number, macAddress, advertisementData); + transmitter_model.enable(); + + is_running = true; + } + } else { + // Send next packet. + baseband::set_btletx(channel_number, macAddress, advertisementData); + } + + if ((packet_counter % 10) == 0) { + text_packets_sent.set(to_string_dec_uint(packet_counter)); + } + + packet_counter--; + + progressbar.set_value(packet_count - packet_counter); +} + +void BLETxView::stop() { + transmitter_model.disable(); + progressbar.set_value(0); + button_play.set_bitmap(&bitmap_play); + check_loop.set_value(false); + text_packets_sent.set(to_string_dec_uint(packet_count)); + packet_counter = packet_count; + is_running = false; +} + +void BLETxView::on_tx_progress(const bool done) { + if (done) { + if (check_loop.value() && (packet_counter != 0) && is_active()) { + if ((timer_count % timer_period) == 0) { + start(); + } + } else { + if (is_active()) { + stop(); + } + } + + timer_count++; + } +} + +BLETxView::BLETxView(NavigationView& nav) + : nav_{nav} { + baseband::run_image(portapack::spi_flash::image_tag_btle_tx); + + add_children({&button_open, + &text_filename, + &progressbar, + &field_frequency, + &tx_view, // now it handles previous rfgain, rfamp. + &check_loop, + &button_play, + &label_speed, + &options_speed, + &options_channel, + &label_packets_sent, + &text_packets_sent, + &label_mac_address, + &text_mac_address, + &label_data_packet, + &console}); + + field_frequency.set_step(0); + + button_play.on_select = [this](ImageButton&) { + this->toggle(); + }; + + options_channel.on_change = [this](size_t, int32_t i) { + field_frequency.set_value(get_freq_by_channel_number(i)); + channel_number = i; + }; + + options_speed.on_change = [this](size_t, int32_t i) { + timer_period = i; + }; + + options_speed.set_selected_index(0); + + button_open.on_select = [this, &nav](Button&) { + auto open_view = nav.push(".TXT"); + open_view->on_changed = [this](std::filesystem::path new_file_path) { + on_file_changed(new_file_path); + }; + }; +} + +void BLETxView::on_file_changed(const fs::path& new_file_path) { + file_path = fs::path(u"/") + new_file_path; + + { // Get the size of the data file. + File data_file; + auto error = data_file.open(file_path); + if (error) { + file_error(); + file_path = ""; + return; + } + + readUntilSpace(data_file, macAddress, mac_address_size_str); + readUntilSpace(data_file, advertisementData, max_packet_size_str); + readUntilSpace(data_file, packetCount, max_packet_count_str); + + uint64_t macAddressSize = strlen(macAddress); + uint64_t advertisementDataSize = strlen(advertisementData); + uint64_t packetCountSize = strlen(packetCount); + + packet_count = stringToUint32(packetCount); + packet_counter = packet_count; + + // Verify Data. + if ((macAddressSize == mac_address_size_str) && (advertisementDataSize < max_packet_size_str) && (packetCountSize < max_packet_count_str) && + hasValidHexPairs(macAddress, macAddressSize / 2) && hasValidHexPairs(advertisementData, advertisementDataSize / 2) && (packet_count > 0) && (packet_count < UINT32_MAX)) { + text_packets_sent.set(to_string_dec_uint(packet_count)); + + std::string formattedMacAddress = to_string_formatted_mac_address(macAddress); + + text_mac_address.set(formattedMacAddress); + + std::vector strings = splitIntoStrings(advertisementData); + + console.clear(true); + + for (const std::string& str : strings) { + console.writeln(str); + } + + text_filename.set(truncate(file_path.filename().string(), 12)); + } else { + // file_error(); + file_path = ""; + return; + } + } +} + +void BLETxView::on_data(uint32_t value, bool is_data) { + std::string str_console = ""; + + if (is_data) { + str_console += (char)(value); + } + + console.write(str_console); +} + +void BLETxView::set_parent_rect(const Rect new_parent_rect) { + View::set_parent_rect(new_parent_rect); + const Rect content_rect{0, header_height, new_parent_rect.width(), new_parent_rect.height() - header_height}; + console.set_parent_rect(content_rect); +} + +BLETxView::~BLETxView() { + transmitter_model.disable(); + baseband::shutdown(); +} + +} /* namespace ui */ diff --git a/firmware/application/apps/ble_tx_app.hpp b/firmware/application/apps/ble_tx_app.hpp new file mode 100644 index 000000000..50caae93c --- /dev/null +++ b/firmware/application/apps/ble_tx_app.hpp @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2017 Furrtek + * Copyright (C) 2023 TJ Baginski + * + * 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 __BLE_TX_APP_H__ +#define __BLE_TX_APP_H__ + +#include "ui.hpp" +#include "ui_navigation.hpp" +#include "ui_receiver.hpp" +#include "ui_transmitter.hpp" +#include "ui_freq_field.hpp" +#include "ui_record_view.hpp" +#include "app_settings.hpp" +#include "radio_state.hpp" +#include "replay_thread.hpp" +#include "log_file.hpp" +#include "utility.hpp" + +#include "recent_entries.hpp" + +#include +#include + +class BLELoggerTx { + public: + Optional append(const std::string& filename) { + return log_file.append(filename); + } + + void log_raw_data(const std::string& data); + + private: + LogFile log_file{}; +}; + +namespace ui { + +class BLETxView : public View { + public: + BLETxView(NavigationView& nav); + ~BLETxView(); + + void set_parent_rect(const Rect new_parent_rect) override; + void paint(Painter&) override{}; + + void focus() override; + + bool is_active() const; + void toggle(); + void start(); + void stop(); + void handle_replay_thread_done(const uint32_t return_code); + void file_error(); + + std::string title() const override { return "BLE TX"; }; + + private: + void on_data(uint32_t value, bool is_data); + void on_tx_progress(const bool done); + void on_file_changed(const std::filesystem::path& new_file_path); + + NavigationView& nav_; + TxRadioState radio_state_{ + 2'402'000'000 /* frequency */, + 4'000'000 /* bandwidth */, + 4'000'000 /* sampling rate */ + }; + app_settings::SettingsManager settings_{ + "ble_tx_app", app_settings::Mode::TX}; + + uint8_t console_color{0}; + uint32_t prev_value{0}; + + std::filesystem::path file_path{}; + uint8_t channel_number = 37; + char macAddress[13] = "010203040506"; + char advertisementData[63] = "00112233445566778899AABBCCDDEEFF"; + char packetCount[11] = "0"; + + bool is_running = false; + uint64_t timer_count{0}; + uint64_t timer_period{256}; + bool repeatLoop = false; + uint32_t packet_count{0}; + uint32_t packet_counter{0}; + + static constexpr uint8_t mac_address_size_str{12}; + static constexpr uint8_t max_packet_size_str{62}; + static constexpr uint8_t max_packet_count_str{10}; + static constexpr uint32_t max_packet_count{UINT32_MAX}; + + static constexpr auto header_height = 8 * 16; + + Button button_open{ + {0 * 8, 0 * 16, 10 * 8, 2 * 16}, + "Open file"}; + + Text text_filename{ + {11 * 8, 0 * 16, 12 * 8, 16}, + "-"}; + + ProgressBar progressbar{ + {11 * 8, 1 * 16, 12 * 8, 16}}; + + TxFrequencyField field_frequency{ + {0 * 8, 2 * 16}, + nav_}; + + TransmitterView2 tx_view{ + {11 * 8, 2 * 16}, + /*short_ui*/ true}; + + Checkbox check_loop{ + {21 * 8, 2 * 16}, + 4, + "Loop", + true}; + + ImageButton button_play{ + {28 * 8, 2 * 16, 2 * 8, 1 * 16}, + &bitmap_play, + Color::green(), + Color::black()}; + + Labels label_speed{ + {{0 * 8, 6 * 8}, "Speed:", Color::light_grey()}}; + + OptionsField options_speed{ + {7 * 8, 6 * 8}, + 3, + {{"1 ", 256}, + {"2 ", 128}, + {"3 ", 64}, + {"4 ", 32}, + {"5 ", 16}}}; + + OptionsField options_channel{ + {11 * 8, 6 * 8}, + 5, + {{"Ch.37 ", 37}, + {"Ch.38", 38}, + {"Ch.39", 39}}}; + + Labels label_packets_sent{ + {{0 * 8, 10 * 8}, "Packets Left:", Color::light_grey()}}; + + Text text_packets_sent{ + {13 * 8, 5 * 16, 10 * 8, 16}, + "-"}; + + Labels label_mac_address{ + {{0 * 8, 12 * 8}, "Mac Address:", Color::light_grey()}}; + + Text text_mac_address{ + {12 * 8, 6 * 16, 20 * 8, 16}, + "-"}; + + Labels label_data_packet{ + {{0 * 8, 14 * 8}, "Packet Data:", Color::light_grey()}}; + + Console console{ + {0, 7 * 16, 240, 240}}; + + std::string str_log{""}; + bool logging{true}; + bool logging_done{false}; + + std::unique_ptr logger{}; + + MessageHandlerRegistration message_handler_packet{ + Message::ID::AFSKData, + [this](Message* const p) { + const auto message = static_cast(p); + this->on_data(message->value, message->is_data); + }}; + + 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); + }}; +}; + +} /* namespace ui */ + +#endif /*__UI_AFSK_RX_H__*/ diff --git a/firmware/application/baseband_api.cpp b/firmware/application/baseband_api.cpp index dc934edb7..fc970d98b 100644 --- a/firmware/application/baseband_api.cpp +++ b/firmware/application/baseband_api.cpp @@ -155,6 +155,14 @@ void set_btle(uint8_t channel_number) { send_message(&message); } +void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData) { + const BTLETxConfigureMessage message{ + channel_number, + macAddress, + advertisementData}; + send_message(&message); +} + void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) { const NRFRxConfigureMessage message{ baudrate, diff --git a/firmware/application/baseband_api.hpp b/firmware/application/baseband_api.hpp index fef615a0b..846b5b802 100644 --- a/firmware/application/baseband_api.hpp +++ b/firmware/application/baseband_api.hpp @@ -73,6 +73,7 @@ void set_fsk(const size_t deviation); void set_aprs(const uint32_t baudrate); void set_btle(uint8_t channel_number); +void set_btletx(uint8_t channel_number, char* macAddress, char* advertisementData); void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word); diff --git a/firmware/application/recent_entries.hpp b/firmware/application/recent_entries.hpp index 8dc022abf..0338d216f 100644 --- a/firmware/application/recent_entries.hpp +++ b/firmware/application/recent_entries.hpp @@ -95,6 +95,20 @@ void sortEntriesBy(ContainerType& entries, KeySelector keySelector, SortOrder as }); } +template +void removeEntriesWithoutKey(ContainerType& entries, ContainerType& filteredEntries, KeySelector keySelector) { + // Clear the filteredEntries container + filteredEntries.clear(); + + auto it = entries.begin(); + while (it != entries.end()) { + if (!keySelector(*it)) { + filteredEntries.emplace_back(*it); // Add a new entry to filteredEntries + } + ++it; // Move to the next element, outside of the if block + } +} + namespace ui { using RecentEntriesColumn = std::pair; @@ -279,6 +293,10 @@ class RecentEntriesView : public View { _table.focus(); } + void set_table(Entries& new_table) { + _table = new_table; + } + private: RecentEntriesHeader _header; RecentEntriesTable _table; diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index c16541463..74e1097c0 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -323,6 +323,20 @@ std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length) { return string; } +std::string to_string_formatted_mac_address(const char* macAddress) { + std::string formattedAddress; + + for (int i = 0; i < 12; i += 2) { + if (i > 0) { + formattedAddress += ':'; + } + formattedAddress += macAddress[i]; + formattedAddress += macAddress[i + 1]; + } + + return formattedAddress; +} + std::string unit_auto_scale(double n, const uint32_t base_unit, uint32_t precision) { const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000}; std::string string{""}; diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index eba45a8e0..3ab566b9a 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -78,6 +78,7 @@ std::string to_string_file_size(uint32_t file_size); // Converts Mac Address to string. std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length); +std::string to_string_formatted_mac_address(const char* macAddress); /* Scales 'n' to be a value less than 1000. 'base_unit' is the index of the unit from * 'unit_prefix' that 'n' is in initially. 3 is the index of the '1s' unit. */ diff --git a/firmware/application/ui/ui_alphanum.cpp b/firmware/application/ui/ui_alphanum.cpp index fb116c75c..acd30655d 100644 --- a/firmware/application/ui/ui_alphanum.cpp +++ b/firmware/application/ui/ui_alphanum.cpp @@ -118,7 +118,12 @@ void AlphanumView::refresh_keys() { size_t n = 0; for (auto& button : buttons) { - button.set_text(std::string{key_list[n]}); + if (n > strlen(key_list)) { + button.set_text(std::string{'\0'}); + } else { + button.set_text(std::string{key_list[n]}); + } + n++; } @@ -137,7 +142,9 @@ void AlphanumView::refresh_keys() { void AlphanumView::on_button(Button& button) { const auto c = button.text()[0]; - char_add(c); + if (c != '\0') { + char_add(c); + } // TODO: Consolidate shift handling. if (shift_mode == ShiftMode::Shift) { diff --git a/firmware/application/ui/ui_alphanum.hpp b/firmware/application/ui/ui_alphanum.hpp index f93dae24c..58af6d2e7 100644 --- a/firmware/application/ui/ui_alphanum.hpp +++ b/firmware/application/ui/ui_alphanum.hpp @@ -57,6 +57,7 @@ class AlphanumView : public TextEntryView { const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, ."; const char* const keys_digit = "1234567890()'`\"+-*/=<>_\\!?, ."; const char* const keys_symbl = "!@#$%^&*()[]'`\"{}|:;<>-_~?, ."; + const char* const keys_hex = "1234567890ABCDEF "; struct key_set_t { const char* name; @@ -64,9 +65,10 @@ class AlphanumView : public TextEntryView { const char* shifted; }; - const key_set_t key_sets[2] = { + const key_set_t key_sets[3] = { {"abc", keys_lower, keys_upper}, - {"123", keys_digit, keys_symbl}}; + {"123", keys_digit, keys_symbl}, + {"hex", keys_hex, keys_hex}}; int16_t focused_button = 0; uint32_t mode = 0; // Letters. diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index daedb2bc8..96e895389 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -87,6 +87,7 @@ #include "analog_audio_app.hpp" #include "analog_tv_app.hpp" #include "ble_app.hpp" +#include "ble_tx_app.hpp" #include "capture_app.hpp" #include "ert_app.hpp" #include "gps_sim_app.hpp" @@ -582,6 +583,7 @@ TransmittersMenuView::TransmittersMenuView(NavigationView& nav) { {"ADS-B TX", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push(); }}, {"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push(); }}, {"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push(); }}, + {"BLE Tx", ui::Color::green(), &bitmap_icon_btle, [&nav]() { nav.push(); }}, {"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push(); }}, {"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push(); }}, {"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push(); }}, diff --git a/firmware/baseband/CMakeLists.txt b/firmware/baseband/CMakeLists.txt index a97645f24..a6e3ec5aa 100644 --- a/firmware/baseband/CMakeLists.txt +++ b/firmware/baseband/CMakeLists.txt @@ -360,6 +360,14 @@ set(MODE_CPPSRC proc_btlerx.cpp ) DeclareTargets(PBTR btlerx) + +### BTLE TX + +set(MODE_CPPSRC + proc_ble_tx.cpp +) +DeclareTargets(PBTT btletx) + ### AIS set(MODE_CPPSRC diff --git a/firmware/baseband/proc_ble_tx.cpp b/firmware/baseband/proc_ble_tx.cpp new file mode 100644 index 000000000..a73c08424 --- /dev/null +++ b/firmware/baseband/proc_ble_tx.cpp @@ -0,0 +1,422 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * Copyright (C) TJ Baginski + * + * 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_ble_tx.hpp" +#include "portapack_shared_memory.hpp" +#include "sine_table_int8.hpp" +#include "event_m4.hpp" + +#include + +#define new_way + +int BTLETxProcessor::gen_sample_from_phy_bit(char* bit, char* sample, int num_bit) { + int num_sample = (num_bit * SAMPLE_PER_SYMBOL) + (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL); + + int8_t* tmp_phy_bit_over_sampling_int8 = (int8_t*)tmp_phy_bit_over_sampling; + + int i, j; + + for (i = 0; i < (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 1); i++) { + tmp_phy_bit_over_sampling_int8[i] = 0; + } + for (i = (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 1 + num_bit * SAMPLE_PER_SYMBOL); i < (2 * LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 2 + num_bit * SAMPLE_PER_SYMBOL); i++) { + tmp_phy_bit_over_sampling_int8[i] = 0; + } + for (i = 0; i < (num_bit * SAMPLE_PER_SYMBOL); i++) { + if (i % SAMPLE_PER_SYMBOL == 0) { + tmp_phy_bit_over_sampling_int8[i + (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 1)] = (bit[i / SAMPLE_PER_SYMBOL]) * 2 - 1; + } else { + tmp_phy_bit_over_sampling_int8[i + (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 1)] = 0; + } + } + + int16_t tmp = 0; + sample[0] = cos_table_int8[tmp]; + sample[1] = sin_table_int8[tmp]; + + int len_conv_result = num_sample - 1; + for (i = 0; i < len_conv_result; i++) { + int16_t acc = 0; + for (j = 3; j < (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL - 4); j++) { + acc = acc + gauss_coef_int8[(LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL) - j - 1] * tmp_phy_bit_over_sampling_int8[i + j]; + } + + tmp = (tmp + acc) & 1023; + sample[(i + 1) * 2 + 0] = cos_table_int8[tmp]; + sample[(i + 1) * 2 + 1] = sin_table_int8[tmp]; + } + + return (num_sample); +} + +void BTLETxProcessor::octet_hex_to_bit(char* hex, char* bit) { + char tmp_hex[3]; + + tmp_hex[0] = hex[0]; + tmp_hex[1] = hex[1]; + tmp_hex[2] = 0; + + int n = strtol(tmp_hex, NULL, 16); + + bit[0] = 0x01 & (n >> 0); + bit[1] = 0x01 & (n >> 1); + bit[2] = 0x01 & (n >> 2); + bit[3] = 0x01 & (n >> 3); + bit[4] = 0x01 & (n >> 4); + bit[5] = 0x01 & (n >> 5); + bit[6] = 0x01 & (n >> 6); + bit[7] = 0x01 & (n >> 7); +} + +int BTLETxProcessor::convert_hex_to_bit(char* hex, char* bit, int stream_flip, int octet_limit) { + int num_hex_orig = strlen(hex); + + int i, num_hex; + num_hex = num_hex_orig; + for (i = 0; i < num_hex_orig; i++) { + if (!((hex[i] >= 48 && hex[i] <= 57) || (hex[i] >= 65 && hex[i] <= 70) || (hex[i] >= 97 && hex[i] <= 102))) // not a hex + num_hex--; + } + + if (num_hex % 2 != 0) { + return (-1); + } + + if (num_hex > (octet_limit * 2)) { + return (-1); + } + if (num_hex <= 1) { // NULL data + return (-1); + } + + char tmp_str[max_char]; + + if (stream_flip == 1) { + strcpy(tmp_str, hex); + for (i = 0; i < num_hex; i = i + 2) { + hex[num_hex - i - 2] = tmp_str[i]; + hex[num_hex - i - 1] = tmp_str[i + 1]; + } + } + + int num_bit = num_hex * 4; + + int j; + for (i = 0; i < num_hex; i = i + 2) { + j = i * 4; + octet_hex_to_bit(hex + i, bit + j); + } + + return (num_bit); +} + +void BTLETxProcessor::crc24(char* bit_in, int num_bit, char* init_hex, char* crc_result) { + char bit_store[24], bit_store_update[24]; + int i; + convert_hex_to_bit(init_hex, bit_store, 0, 3); + + for (i = 0; i < num_bit; i++) { + char new_bit = (bit_store[23] + bit_in[i]) % 2; + bit_store_update[0] = new_bit; + bit_store_update[1] = (bit_store[0] + new_bit) % 2; + bit_store_update[2] = bit_store[1]; + bit_store_update[3] = (bit_store[2] + new_bit) % 2; + bit_store_update[4] = (bit_store[3] + new_bit) % 2; + bit_store_update[5] = bit_store[4]; + bit_store_update[6] = (bit_store[5] + new_bit) % 2; + + bit_store_update[7] = bit_store[6]; + bit_store_update[8] = bit_store[7]; + + bit_store_update[9] = (bit_store[8] + new_bit) % 2; + bit_store_update[10] = (bit_store[9] + new_bit) % 2; + + memcpy(bit_store_update + 11, bit_store + 10, 13); + + memcpy(bit_store, bit_store_update, 24); + } + + for (i = 0; i < 24; i++) { + crc_result[i] = bit_store[23 - i]; + } +} + +void BTLETxProcessor::scramble(char* bit_in, int num_bit, int channel_number, char* bit_out) { + char bit_store[7], bit_store_update[7]; + int i; + + bit_store[0] = 1; + bit_store[1] = 0x01 & (channel_number >> 5); + bit_store[2] = 0x01 & (channel_number >> 4); + bit_store[3] = 0x01 & (channel_number >> 3); + bit_store[4] = 0x01 & (channel_number >> 2); + bit_store[5] = 0x01 & (channel_number >> 1); + bit_store[6] = 0x01 & (channel_number >> 0); + + for (i = 0; i < num_bit; i++) { + bit_out[i] = (bit_store[6] + bit_in[i]) % 2; + + bit_store_update[0] = bit_store[6]; + + bit_store_update[1] = bit_store[0]; + bit_store_update[2] = bit_store[1]; + bit_store_update[3] = bit_store[2]; + + bit_store_update[4] = (bit_store[3] + bit_store[6]) % 2; + + bit_store_update[5] = bit_store[4]; + bit_store_update[6] = bit_store[5]; + + memcpy(bit_store, bit_store_update, 7); + } +} + +void BTLETxProcessor::disp_bit_in_hex(char* bit, int num_bit) { + int i, a; + + for (i = 0; i < num_bit; i = i + 8) { + a = bit[i] + bit[i + 1] * 2 + bit[i + 2] * 4 + bit[i + 3] * 8 + bit[i + 4] * 16 + bit[i + 5] * 32 + bit[i + 6] * 64 + bit[i + 7] * 128; + + data_message.is_data = true; + data_message.value = (uint8_t)a; + shared_memory.application_queue.push(data_message); + } +} + +void BTLETxProcessor::crc24_and_scramble_to_gen_phy_bit(char* crc_init_hex, PKT_INFO* pkt) { + crc24(pkt->info_bit + 5 * 8, pkt->num_info_bit - 5 * 8, crc_init_hex, pkt->info_bit + pkt->num_info_bit); + + // disp_bit_in_hex(pkt->info_bit, pkt->num_info_bit + 3*8); + + scramble(pkt->info_bit + 5 * 8, pkt->num_info_bit - 5 * 8 + 24, pkt->channel_number, pkt->phy_bit + 5 * 8); + memcpy(pkt->phy_bit, pkt->info_bit, 5 * 8); + pkt->num_phy_bit = pkt->num_info_bit + 24; + + // disp_bit_in_hex(pkt->phy_bit, pkt->num_phy_bit); +} + +void BTLETxProcessor::fill_adv_pdu_header(PKT_INFO* pkt, int txadd, int rxadd, int payload_len, char* bit_out) { + if (pkt->pkt_type == ADV_IND || pkt->pkt_type == IBEACON) { + bit_out[3] = 0; + bit_out[2] = 0; + bit_out[1] = 0; + bit_out[0] = 0; + } else if (pkt->pkt_type == ADV_DIRECT_IND) { + bit_out[3] = 0; + bit_out[2] = 0; + bit_out[1] = 0; + bit_out[0] = 1; + } else if (pkt->pkt_type == ADV_NONCONN_IND || pkt->pkt_type == DISCOVERY) { + bit_out[3] = 0; + bit_out[2] = 0; + bit_out[1] = 1; + bit_out[0] = 0; + } else if (pkt->pkt_type == SCAN_REQ) { + bit_out[3] = 0; + bit_out[2] = 0; + bit_out[1] = 1; + bit_out[0] = 1; + } else if (pkt->pkt_type == SCAN_RSP) { + bit_out[3] = 0; + bit_out[2] = 1; + bit_out[1] = 0; + bit_out[0] = 0; + } else if (pkt->pkt_type == CONNECT_REQ) { + bit_out[3] = 0; + bit_out[2] = 1; + bit_out[1] = 0; + bit_out[0] = 1; + } else if (pkt->pkt_type == ADV_SCAN_IND) { + bit_out[3] = 0; + bit_out[2] = 1; + bit_out[1] = 1; + bit_out[0] = 0; + } else { + bit_out[3] = 1; + bit_out[2] = 1; + bit_out[1] = 1; + bit_out[0] = 1; + } + + bit_out[4] = 0; + bit_out[5] = 0; + + bit_out[6] = txadd; + bit_out[7] = rxadd; + + bit_out[8] = 0x01 & (payload_len >> 0); + bit_out[9] = 0x01 & (payload_len >> 1); + bit_out[10] = 0x01 & (payload_len >> 2); + bit_out[11] = 0x01 & (payload_len >> 3); + bit_out[12] = 0x01 & (payload_len >> 4); + bit_out[13] = 0x01 & (payload_len >> 5); + + bit_out[14] = 0; + bit_out[15] = 0; +} + +int BTLETxProcessor::calculate_sample_for_ADV_IND(PKT_INFO* pkt) { + pkt->num_info_bit = 0; + + // gen preamble and access address + const char* AA = "AA"; + const char* AAValue = "D6BE898E"; + pkt->num_info_bit = pkt->num_info_bit + convert_hex_to_bit((char*)AA, pkt->info_bit, 0, 1); + pkt->num_info_bit = pkt->num_info_bit + convert_hex_to_bit((char*)AAValue, pkt->info_bit + pkt->num_info_bit, 0, 4); + + // get txadd and rxadd + int txadd = 1, rxadd = 0; + + pkt->num_info_bit = pkt->num_info_bit + 16; // 16 is header length + + // get AdvA and AdvData + pkt->num_info_bit = pkt->num_info_bit + convert_hex_to_bit(macAddress, pkt->info_bit + pkt->num_info_bit, 1, 6); + + pkt->num_info_bit = pkt->num_info_bit + convert_hex_to_bit(advertisementData, pkt->info_bit + pkt->num_info_bit, 0, 31); + + int payload_len = (pkt->num_info_bit / 8) - 7; + + fill_adv_pdu_header(pkt, txadd, rxadd, payload_len, pkt->info_bit + 5 * 8); + const char* checksumInit = "555555"; + crc24_and_scramble_to_gen_phy_bit((char*)checksumInit, pkt); + +#ifdef new_way + pkt->num_phy_sample = gen_sample_from_phy_bit(pkt->phy_bit, pkt->phy_sample, pkt->num_phy_bit); +#endif + + // disp_bit_in_hex(pkt->phy_sample, pkt->num_phy_sample); + + return (0); +} + +int BTLETxProcessor::calculate_sample_from_pkt_type(PKT_INFO* pkt) { + // Todo: Handle other Enum values. + // if (packetType == ADV_IND); + + if (calculate_sample_for_ADV_IND(pkt) == -1) { + return (-1); + } + + return (0); +} + +int BTLETxProcessor::calculate_pkt_info(PKT_INFO* pkt) { + if (calculate_sample_from_pkt_type(pkt) == -1) { + return (-1); + } + + return (0); +} + +void BTLETxProcessor::execute(const buffer_c8_t& buffer) { + int8_t re, im; + + // This is called at 4M/2048 = 1953Hz + for (size_t i = 0; i < buffer.count; i++) { + if (configured) { + // This is going to loop through each sample bit and push it to the output buffer. + if (sample_count > length) { + configured = false; + sample_count = 0; + } else { + // Real and imaginary was already calculated in gen_sample_from_phy_bit. + // It was processed from each data bit, run through a Gaussian Filter, and then ran through sin and cos table to get each IQ bit. + re = (int8_t)packets.phy_sample[sample_count++]; + im = (int8_t)packets.phy_sample[sample_count++]; + + buffer.p[i] = {re, im}; + + if (progress_count >= progress_notice) { + progress_count = 0; + txprogress_message.progress++; + txprogress_message.done = false; + shared_memory.application_queue.push(txprogress_message); + } else { + progress_count++; + } + } + } else { + re = 0; + im = 0; + + buffer.p[i] = {re, im}; + } + } + + txprogress_message.done = true; + shared_memory.application_queue.push(txprogress_message); +} + +void BTLETxProcessor::on_message(const Message* const message) { + if (message->id == Message::ID::BTLETxConfigure) { + configure(*reinterpret_cast(message)); + } +} + +void BTLETxProcessor::configure(const BTLETxConfigureMessage& message) { + channel_number = message.channel_number; + + memcpy(macAddress, message.macAddress, sizeof(macAddress)); + memcpy(advertisementData, message.advertisementData, sizeof(advertisementData)); + + packets.channel_number = channel_number; + packets.pkt_type = packetType; + + // Calculates the samples based on the BLE packet data and generates IQ values into an array to be sent out. + calculate_pkt_info(&packets); + + // Todo: determine if we need to copy these values to shared_memory.bb_data.data. I suspect that we might. + // I think once we add the UI level, only the generated BLE payload will be sent down. This layer will take care of PHY sample generation + // using the bits sent in by the UI level. In short, we will seperate this implementation to the UI level. + // memcpy(shared_memory.bb_data.data, (uint8_t *)packets.phy_sample, packets.num_phy_sample); + + // 2 Sample buffers for each IQ data. + samples_per_bit = 8; + +#ifdef new_way + // This is because each sample contains I and Q, but packet.num_phy_samples just returns the total samples. + length = (uint32_t)(packets.num_phy_sample * 2); +#else + length = (uint32_t)packets.num_phy_bit; +#endif + + // 200kHz shift frequency of BLE + shift_one = 200000 * (0xFFFFFFFFULL / 4000000); + shift_zero = -shift_one; + + // Starting at sample_count 0 since packets.num_phy_sample contains every sample needed to be sent out. + sample_count = 0; + progress_count = 0; + progress_notice = 64; + + txprogress_message.progress = 0; + txprogress_message.done = false; + configured = true; +} + +int main() { + EventDispatcher event_dispatcher{std::make_unique()}; + event_dispatcher.run(); + return 0; +} diff --git a/firmware/baseband/proc_ble_tx.hpp b/firmware/baseband/proc_ble_tx.hpp new file mode 100644 index 000000000..e028c8762 --- /dev/null +++ b/firmware/baseband/proc_ble_tx.hpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 Jared Boone, ShareBrained Technology, Inc. + * Copyright (C) 2016 Furrtek + * + * 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 __PROC_BLE_TX_H__ +#define __PROC_BLE_TX_H__ + +#include "baseband_processor.hpp" +#include "baseband_thread.hpp" + +class BTLETxProcessor : public BasebandProcessor { + public: + void execute(const buffer_c8_t& buffer) override; + void on_message(const Message* const message) override; + void configure(const BTLETxConfigureMessage& message); + + private: + static constexpr int max_char{256}; + static constexpr int SAMPLE_PER_SYMBOL{4}; + static constexpr float AMPLITUDE{127.0}; + static constexpr float MOD_IDX{0.5}; + static constexpr int LEN_GAUSS_FILTER{4}; + static constexpr int MAX_NUM_INFO_BYTE{43}; + static constexpr int MAX_NUM_PHY_BYTE{47}; + static constexpr int MAX_NUM_PHY_SAMPLE{(MAX_NUM_PHY_BYTE * 8 * SAMPLE_PER_SYMBOL) + (LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL)}; + + enum PKT_TYPE { + INVALID_TYPE, + RAW, + DISCOVERY, + IBEACON, + ADV_IND, + ADV_DIRECT_IND, + ADV_NONCONN_IND, + ADV_SCAN_IND, + SCAN_REQ, + SCAN_RSP, + CONNECT_REQ, + LL_DATA, + LL_CONNECTION_UPDATE_REQ, + LL_CHANNEL_MAP_REQ, + LL_TERMINATE_IND, + LL_ENC_REQ, + LL_ENC_RSP, + LL_START_ENC_REQ, + LL_START_ENC_RSP, + LL_UNKNOWN_RSP, + LL_FEATURE_REQ, + LL_FEATURE_RSP, + LL_PAUSE_ENC_REQ, + LL_PAUSE_ENC_RSP, + LL_VERSION_IND, + LL_REJECT_IND, + NUM_PKT_TYPE + }; + + struct PKT_INFO { + int channel_number; + PKT_TYPE pkt_type; + + int num_info_bit; + char info_bit[MAX_NUM_PHY_BYTE * 8]; // without CRC and whitening + + int num_info_byte; + uint8_t info_byte[MAX_NUM_PHY_BYTE]; + + int num_phy_bit; + char phy_bit[MAX_NUM_PHY_BYTE * 8]; // all bits which will be fed to GFSK modulator + + int num_phy_byte; + uint8_t phy_byte[MAX_NUM_PHY_BYTE]; + + int num_phy_sample; + char phy_sample[2 * MAX_NUM_PHY_SAMPLE]; // GFSK output to D/A (hackrf board) + int8_t phy_sample1[2 * MAX_NUM_PHY_SAMPLE]; // GFSK output to D/A (hackrf board) + + int space; // how many millisecond null signal shouwl be padded after this packet + }; + + PKT_INFO packets{}; + + int calculate_pkt_info(PKT_INFO* pkt); + int calculate_sample_from_pkt_type(PKT_INFO* pkt); + int calculate_sample_for_ADV_IND(PKT_INFO* pkt); + void fill_adv_pdu_header(PKT_INFO* pkt, int txadd, int rxadd, int payload_len, char* bit_out); + void crc24_and_scramble_to_gen_phy_bit(char* crc_init_hex, PKT_INFO* pkt); + void disp_bit_in_hex(char* bit, int num_bit); + void scramble(char* bit_in, int num_bit, int channel_number, char* bit_out); + void crc24(char* bit_in, int num_bit, char* init_hex, char* crc_result); + int convert_hex_to_bit(char* hex, char* bit, int stream_flip, int octet_limit); + void octet_hex_to_bit(char* hex, char* bit); + int gen_sample_from_phy_bit(char* bit, char* sample, int num_bit); + bool configured = false; + + float tmp_phy_bit_over_sampling[MAX_NUM_PHY_SAMPLE + 2 * LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL]; + float gauss_coef[LEN_GAUSS_FILTER * SAMPLE_PER_SYMBOL] = {7.561773e-09, 1.197935e-06, 8.050684e-05, 2.326833e-03, 2.959908e-02, 1.727474e-01, 4.999195e-01, 8.249246e-01, 9.408018e-01, 8.249246e-01, 4.999195e-01, 1.727474e-01, 2.959908e-02, 2.326833e-03, 8.050684e-05, 1.197935e-06}; + + uint32_t samples_per_bit{4}; + uint32_t channel_number{37}; + char macAddress[13] = "FFFFFFFFFF"; + char advertisementData[63] = {0}; + + uint32_t length{0}; + uint32_t shift_zero{}, shift_one{}; + uint32_t progress_notice{}, progress_count{0}; + uint32_t sample_count{0}; + uint32_t phase{0}, sphase{0}; + + uint8_t cur_bit{0}; + uint16_t bit_pos{0}; + + uint16_t process{0}; + + // clang-format off + const int8_t gauss_coef_int8[16] = { + 0, 0, 0, 0, 2, 11, 32, 53, 60, 53, 32, 11, 2, 0, 0, 0, }; + + const int8_t cos_table_int8[1024] = { + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 125, 125, 125, 125, 125, 125, 125, 124, 124, 124, 124, 124, 124, 123, 123, 123, 123, 123, 122, 122, 122, 122, + 122, 121, 121, 121, 121, 120, 120, 120, 120, 119, 119, 119, 118, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, + 115, 114, 114, 114, 113, 113, 113, 112, 112, 112, 111, 111, 111, 110, 110, 109, 109, 109, 108, 108, 107, 107, 106, 106, + 106, 105, 105, 104, 104, 103, 103, 102, 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, 96, 96, 95, 95, + 94, 94, 93, 93, 92, 91, 91, 90, 90, 89, 89, 88, 88, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, + 81, 80, 79, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 71, 70, 69, 69, 68, 67, 67, 66, + 65, 65, 64, 63, 63, 62, 61, 61, 60, 59, 58, 58, 57, 56, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, + 49, 48, 47, 46, 46, 45, 44, 44, 43, 42, 41, 41, 40, 39, 38, 38, 37, 36, 35, 35, 34, 33, 32, 32, + 31, 30, 29, 29, 28, 27, 26, 26, 25, 24, 23, 22, 22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 14, 13, + 12, 12, 11, 10, 9, 9, 8, 7, 6, 5, 5, 4, 3, 2, 2, 1, 0, -1, -2, -2, -3, -4, -5, -5, + -6, -7, -8, -9, -9, -10, -11, -12, -12, -13, -14, -15, -16, -16, -17, -18, -19, -19, -20, -21, -22, -22, -23, -24, + -25, -26, -26, -27, -28, -29, -29, -30, -31, -32, -32, -33, -34, -35, -35, -36, -37, -38, -38, -39, -40, -41, -41, -42, + -43, -44, -44, -45, -46, -46, -47, -48, -49, -49, -50, -51, -51, -52, -53, -54, -54, -55, -56, -56, -57, -58, -58, -59, + -60, -61, -61, -62, -63, -63, -64, -65, -65, -66, -67, -67, -68, -69, -69, -70, -71, -71, -72, -72, -73, -74, -74, -75, + -76, -76, -77, -78, -78, -79, -79, -80, -81, -81, -82, -82, -83, -84, -84, -85, -85, -86, -86, -87, -88, -88, -89, -89, + -90, -90, -91, -91, -92, -93, -93, -94, -94, -95, -95, -96, -96, -97, -97, -98, -98, -99, -99, -100, -100, -101, -101, -102, + -102, -102, -103, -103, -104, -104, -105, -105, -106, -106, -106, -107, -107, -108, -108, -109, -109, -109, -110, -110, -111, -111, -111, -112, + -112, -112, -113, -113, -113, -114, -114, -114, -115, -115, -115, -116, -116, -116, -117, -117, -117, -118, -118, -118, -118, -119, -119, -119, + -120, -120, -120, -120, -121, -121, -121, -121, -122, -122, -122, -122, -122, -123, -123, -123, -123, -123, -124, -124, -124, -124, -124, -124, + -125, -125, -125, -125, -125, -125, -125, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -127, -127, -127, -127, -127, -127, + -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -126, + -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -125, -125, -125, -125, -125, -125, -125, -124, -124, -124, -124, -124, -124, -123, + -123, -123, -123, -123, -122, -122, -122, -122, -122, -121, -121, -121, -121, -120, -120, -120, -120, -119, -119, -119, -118, -118, -118, -118, + -117, -117, -117, -116, -116, -116, -115, -115, -115, -114, -114, -114, -113, -113, -113, -112, -112, -112, -111, -111, -111, -110, -110, -109, + -109, -109, -108, -108, -107, -107, -106, -106, -106, -105, -105, -104, -104, -103, -103, -102, -102, -102, -101, -101, -100, -100, -99, -99, + -98, -98, -97, -97, -96, -96, -95, -95, -94, -94, -93, -93, -92, -91, -91, -90, -90, -89, -89, -88, -88, -87, -86, -86, + -85, -85, -84, -84, -83, -82, -82, -81, -81, -80, -79, -79, -78, -78, -77, -76, -76, -75, -74, -74, -73, -72, -72, -71, + -71, -70, -69, -69, -68, -67, -67, -66, -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -58, -58, -57, -56, -56, -55, + -54, -54, -53, -52, -51, -51, -50, -49, -49, -48, -47, -46, -46, -45, -44, -44, -43, -42, -41, -41, -40, -39, -38, -38, + -37, -36, -35, -35, -34, -33, -32, -32, -31, -30, -29, -29, -28, -27, -26, -26, -25, -24, -23, -22, -22, -21, -20, -19, + -19, -18, -17, -16, -16, -15, -14, -13, -12, -12, -11, -10, -9, -9, -8, -7, -6, -5, -5, -4, -3, -2, -2, -1, + 0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 16, 17, 18, + 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, 47, 48, 49, 49, 50, 51, 51, 52, 53, 54, + 54, 55, 56, 56, 57, 58, 58, 59, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 70, + 71, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, + 85, 86, 86, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, + 98, 99, 99, 100, 100, 101, 101, 102, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 106, 107, 107, 108, 108, 109, + 109, 109, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, + 117, 118, 118, 118, 118, 119, 119, 119, 120, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, 123, 123, + 123, 123, 124, 124, 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, }; + + const int8_t sin_table_int8[1024] = { + 0, 1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 15, 16, 16, 17, 18, + 19, 19, 20, 21, 22, 22, 23, 24, 25, 26, 26, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, + 37, 38, 38, 39, 40, 41, 41, 42, 43, 44, 44, 45, 46, 46, 47, 48, 49, 49, 50, 51, 51, 52, 53, 54, + 54, 55, 56, 56, 57, 58, 58, 59, 60, 61, 61, 62, 63, 63, 64, 65, 65, 66, 67, 67, 68, 69, 69, 70, + 71, 71, 72, 72, 73, 74, 74, 75, 76, 76, 77, 78, 78, 79, 79, 80, 81, 81, 82, 82, 83, 84, 84, 85, + 85, 86, 86, 87, 88, 88, 89, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94, 95, 95, 96, 96, 97, 97, 98, + 98, 99, 99, 100, 100, 101, 101, 102, 102, 102, 103, 103, 104, 104, 105, 105, 106, 106, 106, 107, 107, 108, 108, 109, + 109, 109, 110, 110, 111, 111, 111, 112, 112, 112, 113, 113, 113, 114, 114, 114, 115, 115, 115, 116, 116, 116, 117, 117, + 117, 118, 118, 118, 118, 119, 119, 119, 120, 120, 120, 120, 121, 121, 121, 121, 122, 122, 122, 122, 122, 123, 123, 123, + 123, 123, 124, 124, 124, 124, 124, 124, 125, 125, 125, 125, 125, 125, 125, 126, 126, 126, 126, 126, 126, 126, 126, 126, + 126, 126, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 126, 125, 125, 125, 125, 125, 125, + 125, 124, 124, 124, 124, 124, 124, 123, 123, 123, 123, 123, 122, 122, 122, 122, 122, 121, 121, 121, 121, 120, 120, 120, + 120, 119, 119, 119, 118, 118, 118, 118, 117, 117, 117, 116, 116, 116, 115, 115, 115, 114, 114, 114, 113, 113, 113, 112, + 112, 112, 111, 111, 111, 110, 110, 109, 109, 109, 108, 108, 107, 107, 106, 106, 106, 105, 105, 104, 104, 103, 103, 102, + 102, 102, 101, 101, 100, 100, 99, 99, 98, 98, 97, 97, 96, 96, 95, 95, 94, 94, 93, 93, 92, 91, 91, 90, + 90, 89, 89, 88, 88, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81, 80, 79, 79, 78, 78, 77, 76, + 76, 75, 74, 74, 73, 72, 72, 71, 71, 70, 69, 69, 68, 67, 67, 66, 65, 65, 64, 63, 63, 62, 61, 61, + 60, 59, 58, 58, 57, 56, 56, 55, 54, 54, 53, 52, 51, 51, 50, 49, 49, 48, 47, 46, 46, 45, 44, 44, + 43, 42, 41, 41, 40, 39, 38, 38, 37, 36, 35, 35, 34, 33, 32, 32, 31, 30, 29, 29, 28, 27, 26, 26, + 25, 24, 23, 22, 22, 21, 20, 19, 19, 18, 17, 16, 16, 15, 14, 13, 12, 12, 11, 10, 9, 9, 8, 7, + 6, 5, 5, 4, 3, 2, 2, 1, 0, -1, -2, -2, -3, -4, -5, -5, -6, -7, -8, -9, -9, -10, -11, -12, + -12, -13, -14, -15, -16, -16, -17, -18, -19, -19, -20, -21, -22, -22, -23, -24, -25, -26, -26, -27, -28, -29, -29, -30, + -31, -32, -32, -33, -34, -35, -35, -36, -37, -38, -38, -39, -40, -41, -41, -42, -43, -44, -44, -45, -46, -46, -47, -48, + -49, -49, -50, -51, -51, -52, -53, -54, -54, -55, -56, -56, -57, -58, -58, -59, -60, -61, -61, -62, -63, -63, -64, -65, + -65, -66, -67, -67, -68, -69, -69, -70, -71, -71, -72, -72, -73, -74, -74, -75, -76, -76, -77, -78, -78, -79, -79, -80, + -81, -81, -82, -82, -83, -84, -84, -85, -85, -86, -86, -87, -88, -88, -89, -89, -90, -90, -91, -91, -92, -93, -93, -94, + -94, -95, -95, -96, -96, -97, -97, -98, -98, -99, -99, -100, -100, -101, -101, -102, -102, -102, -103, -103, -104, -104, -105, -105, + -106, -106, -106, -107, -107, -108, -108, -109, -109, -109, -110, -110, -111, -111, -111, -112, -112, -112, -113, -113, -113, -114, -114, -114, + -115, -115, -115, -116, -116, -116, -117, -117, -117, -118, -118, -118, -118, -119, -119, -119, -120, -120, -120, -120, -121, -121, -121, -121, + -122, -122, -122, -122, -122, -123, -123, -123, -123, -123, -124, -124, -124, -124, -124, -124, -125, -125, -125, -125, -125, -125, -125, -126, + -126, -126, -126, -126, -126, -126, -126, -126, -126, -126, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, + -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -126, -126, -126, -126, -126, -126, -126, -126, -126, + -126, -126, -125, -125, -125, -125, -125, -125, -125, -124, -124, -124, -124, -124, -124, -123, -123, -123, -123, -123, -122, -122, -122, -122, + -122, -121, -121, -121, -121, -120, -120, -120, -120, -119, -119, -119, -118, -118, -118, -118, -117, -117, -117, -116, -116, -116, -115, -115, + -115, -114, -114, -114, -113, -113, -113, -112, -112, -112, -111, -111, -111, -110, -110, -109, -109, -109, -108, -108, -107, -107, -106, -106, + -106, -105, -105, -104, -104, -103, -103, -102, -102, -102, -101, -101, -100, -100, -99, -99, -98, -98, -97, -97, -96, -96, -95, -95, + -94, -94, -93, -93, -92, -91, -91, -90, -90, -89, -89, -88, -88, -87, -86, -86, -85, -85, -84, -84, -83, -82, -82, -81, + -81, -80, -79, -79, -78, -78, -77, -76, -76, -75, -74, -74, -73, -72, -72, -71, -71, -70, -69, -69, -68, -67, -67, -66, + -65, -65, -64, -63, -63, -62, -61, -61, -60, -59, -58, -58, -57, -56, -56, -55, -54, -54, -53, -52, -51, -51, -50, -49, + -49, -48, -47, -46, -46, -45, -44, -44, -43, -42, -41, -41, -40, -39, -38, -38, -37, -36, -35, -35, -34, -33, -32, -32, + -31, -30, -29, -29, -28, -27, -26, -26, -25, -24, -23, -22, -22, -21, -20, -19, -19, -18, -17, -16, -16, -15, -14, -13, + -12, -12, -11, -10, -9, -9, -8, -7, -6, -5, -5, -4, -3, -2, -2, -1, }; + // clang-format on + + PKT_TYPE packetType = ADV_IND; + + TXProgressMessage txprogress_message{}; + + /* NB: Threads should be the last members in the class definition. */ + + // BasebandThread baseband_thread{4000000, this, baseband::Direction::Transmit}; + // Rx for now because trying to test formulation of packet. + AFSKDataMessage data_message{false, 0}; + BasebandThread baseband_thread{4000000, this, baseband::Direction::Transmit}; +}; + +#endif diff --git a/firmware/common/message.hpp b/firmware/common/message.hpp index 73f1a052f..4c7ad5d0c 100644 --- a/firmware/common/message.hpp +++ b/firmware/common/message.hpp @@ -114,6 +114,7 @@ class Message { POCSAGStats = 57, FSKRxConfigure = 58, BlePacket = 58, + BTLETxConfigure = 59, MAX }; @@ -755,6 +756,22 @@ class BTLERxConfigureMessage : public Message { const uint8_t channel_number; }; +class BTLETxConfigureMessage : public Message { + public: + constexpr BTLETxConfigureMessage( + const uint8_t channel_number, + char* macAddress, + char* advertisementData) + : Message{ID::BTLETxConfigure}, + channel_number(channel_number), + macAddress(macAddress), + advertisementData(advertisementData) { + } + const uint8_t channel_number; + char* macAddress; + char* advertisementData; +}; + class NRFRxConfigureMessage : public Message { public: constexpr NRFRxConfigureMessage( diff --git a/firmware/common/spi_image.hpp b/firmware/common/spi_image.hpp index c1aa14d69..f009b5c45 100644 --- a/firmware/common/spi_image.hpp +++ b/firmware/common/spi_image.hpp @@ -78,6 +78,7 @@ constexpr image_tag_t image_tag_adsb_rx{'P', 'A', 'D', 'R'}; constexpr image_tag_t image_tag_afsk_rx{'P', 'A', 'F', 'R'}; constexpr image_tag_t image_tag_aprs_rx{'P', 'A', 'P', 'R'}; constexpr image_tag_t image_tag_btle_rx{'P', 'B', 'T', 'R'}; +constexpr image_tag_t image_tag_btle_tx{'P', 'B', 'T', 'T'}; constexpr image_tag_t image_tag_nrf_rx{'P', 'N', 'R', 'R'}; constexpr image_tag_t image_tag_ais{'P', 'A', 'I', 'S'}; constexpr image_tag_t image_tag_am_audio{'P', 'A', 'M', 'A'}; diff --git a/sdcard/BLETX/BLE Tx Sample.txt b/sdcard/BLETX/BLE Tx Sample.txt new file mode 100644 index 000000000..405053521 --- /dev/null +++ b/sdcard/BLETX/BLE Tx Sample.txt @@ -0,0 +1 @@ +010203040506 00112233445566778899AABBCCDDEEFF 1000 \ No newline at end of file