BLE Tx App / BLE Rx Filtering. (#1543)

BLE TX app creation
BLE RX and TX app improvements
This commit is contained in:
Netro 2023-11-01 06:46:41 -04:00 committed by GitHub
parent 62307b93d7
commit dceb7255b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1402 additions and 34 deletions

View File

@ -245,6 +245,7 @@ set(CPPSRC
apps/analog_audio_app.cpp apps/analog_audio_app.cpp
apps/analog_tv_app.cpp apps/analog_tv_app.cpp
apps/ble_app.cpp apps/ble_app.cpp
apps/ble_tx_app.cpp
apps/capture_app.cpp apps/capture_app.cpp
apps/ert_app.cpp apps/ert_app.cpp
apps/gps_sim_app.cpp apps/gps_sim_app.cpp

View File

@ -30,6 +30,7 @@
#include "baseband_api.hpp" #include "baseband_api.hpp"
#include "string_format.hpp" #include "string_format.hpp"
#include "portapack_persistent_memory.hpp" #include "portapack_persistent_memory.hpp"
#include "ui_textentry.hpp"
using namespace portapack; using namespace portapack;
using namespace modems; using namespace modems;
@ -214,15 +215,32 @@ BLERxView::BLERxView(NavigationView& nav)
&check_log, &check_log,
&label_sort, &label_sort,
&options_sort, &options_sort,
&button_message,
&recent_entries_view, &recent_entries_view,
&recent_entries_filter_view,
&recent_entry_detail_view}); &recent_entry_detail_view});
recent_entry_detail_view.hidden(true); recent_entry_detail_view.hidden(true);
recent_entries_filter_view.hidden(true);
recent_entries_view.on_select = [this](const BleRecentEntry& entry) { recent_entries_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(entry); nav_.push<BleRecentEntryDetailView>(entry);
}; };
recent_entries_filter_view.on_select = [this](const BleRecentEntry& entry) {
nav_.push<BleRecentEntryDetailView>(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); field_frequency.set_step(0);
check_log.set_value(logging); check_log.set_value(logging);
@ -244,20 +262,27 @@ BLERxView::BLERxView(NavigationView& nav)
case 0: case 0:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
break; break;
case 1: case 1:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
break; break;
case 2: case 2:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
break; break;
default: default:
break; break;
} }
recent_entries_view.set_dirty(); recent_entries_view.set_dirty();
recent_entries_filter_view.set_dirty();
}; };
options_channel.set_selected_index(0, true); options_channel.set_selected_index(0, true);
@ -319,8 +344,6 @@ void BLERxView::on_data(BlePacketData* packet) {
break; break;
} }
// str_console += to_string_dec_uint(value);
str_console += " Len:"; str_console += " Len:";
str_console += to_string_dec_uint(packet->size); str_console += to_string_dec_uint(packet->size);
@ -335,7 +358,7 @@ void BLERxView::on_data(BlePacketData* packet) {
int i; int i;
for (i = 0; i < packet->dataLen; 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"; str_console += "\n";
@ -345,9 +368,60 @@ void BLERxView::on_data(BlePacketData* packet) {
// Start of Packet stuffing. // Start of Packet stuffing.
// Masking off the top 2 bytes to avoid invalid keys. // Masking off the top 2 bytes to avoid invalid keys.
auto& entry = ::on_packet(recent, macAddressEncoded & 0xFFFFFFFFFFFF); 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.dbValue = packet->max_dB;
entry.timestamp = to_string_timestamp(rtc_time::now()); entry.timestamp = to_string_timestamp(rtc_time::now());
entry.dataString = data_string;
entry.packetData.type = packet->type; entry.packetData.type = packet->type;
entry.packetData.size = packet->size; entry.packetData.size = packet->size;
@ -370,48 +444,31 @@ void BLERxView::on_data(BlePacketData* packet) {
case 0: case 0:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true); recent, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.macAddress; }, true);
break; break;
case 1: case 1:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true); recent, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.dbValue; }, true);
break; break;
case 2: case 2:
sortEntriesBy( sortEntriesBy(
recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false); recent, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
sortEntriesBy(
filterEntries, [](const BleRecentEntry& entry) { return entry.timestamp; }, false);
break; break;
default: default:
break; break;
} }
recent_entries_view.set_dirty(); on_switch_table(filter);
// TODO: Crude hack, should be a more formal listener arrangement... // TODO: Crude hack, should be a more formal listener arrangement...
if (entry.key() == recent_entry_detail_view.entry().key()) { if (entry.key() == recent_entry_detail_view.entry().key()) {
recent_entry_detail_view.set_entry(entry); 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 */ } /* namespace ui */

View File

@ -77,6 +77,7 @@ struct BleRecentEntry {
int dbValue; int dbValue;
BlePacketData packetData; BlePacketData packetData;
std::string timestamp; std::string timestamp;
std::string dataString;
BleRecentEntry() BleRecentEntry()
: BleRecentEntry{0} { : BleRecentEntry{0} {
@ -87,14 +88,13 @@ struct BleRecentEntry {
: macAddress{macAddress}, : macAddress{macAddress},
dbValue{}, dbValue{},
packetData{}, packetData{},
timestamp{} { timestamp{},
dataString{} {
} }
Key key() const { Key key() const {
return macAddress; return macAddress;
} }
// void update(const BlePacketData * packet);
}; };
using BleRecentEntries = RecentEntries<BleRecentEntry>; using BleRecentEntries = RecentEntries<BleRecentEntry>;
@ -151,6 +151,8 @@ class BLERxView : public View {
private: private:
void on_data(BlePacketData* packetData); void on_data(BlePacketData* packetData);
void on_switch_table(const std::string value);
void updateEntry(const BlePacketData* packet, BleRecentEntry& entry);
NavigationView& nav_; NavigationView& nav_;
RxRadioState radio_state_{ RxRadioState radio_state_{
@ -165,6 +167,9 @@ class BLERxView : public View {
uint32_t prev_value{0}; uint32_t prev_value{0};
uint8_t channel_number = 37; uint8_t channel_number = 37;
std::string filterBuffer{};
std::string filter{};
static constexpr auto header_height = 12 + 2 * 16; static constexpr auto header_height = 12 + 2 * 16;
OptionsField options_channel{ OptionsField options_channel{
@ -209,6 +214,10 @@ class BLERxView : public View {
{"dB", 1}, {"dB", 1},
{"Recent", 2}}}; {"Recent", 2}}};
Button button_message{
{22 * 8, 3 * 8, 4 * 8, 16},
"Filter"};
Console console{ Console console{
{0, 4 * 16, 240, 240}}; {0, 4 * 16, 240, 240}};
@ -223,6 +232,7 @@ class BLERxView : public View {
// {"Time", 8}}}; // {"Time", 8}}};
BleRecentEntries recent{}; BleRecentEntries recent{};
BleRecentEntries filterEntries{};
const RecentEntriesColumns columns{{ const RecentEntriesColumns columns{{
{"Mac Address", 20}, {"Mac Address", 20},
@ -231,6 +241,7 @@ class BLERxView : public View {
BleRecentEntry entry_{}; BleRecentEntry entry_{};
BleRecentEntriesView recent_entries_view{columns, recent}; BleRecentEntriesView recent_entries_view{columns, recent};
BleRecentEntriesView recent_entries_filter_view{columns, filterEntries};
BleRecentEntryDetailView recent_entry_detail_view{nav_, entry_}; BleRecentEntryDetailView recent_entry_detail_view{nav_, entry_};
MessageHandlerRegistration message_handler_packet{ MessageHandlerRegistration message_handler_packet{

View File

@ -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<std::string> splitIntoStrings(const char* input) {
std::vector<std::string> 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<File::Size> 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<FileLoadView>(".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<std::string> 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 */

View File

@ -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 <string>
#include <memory>
class BLELoggerTx {
public:
Optional<File::Error> 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<BLELoggerTx> logger{};
MessageHandlerRegistration message_handler_packet{
Message::ID::AFSKData,
[this](Message* const p) {
const auto message = static_cast<const AFSKDataMessage*>(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<const TXProgressMessage*>(p);
this->on_tx_progress(message.done);
}};
};
} /* namespace ui */
#endif /*__UI_AFSK_RX_H__*/

View File

@ -155,6 +155,14 @@ void set_btle(uint8_t channel_number) {
send_message(&message); 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) { void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word) {
const NRFRxConfigureMessage message{ const NRFRxConfigureMessage message{
baudrate, baudrate,

View File

@ -73,6 +73,7 @@ void set_fsk(const size_t deviation);
void set_aprs(const uint32_t baudrate); void set_aprs(const uint32_t baudrate);
void set_btle(uint8_t channel_number); 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); void set_nrf(const uint32_t baudrate, const uint32_t word_length, const uint32_t trigger_value, const bool trigger_word);

View File

@ -95,6 +95,20 @@ void sortEntriesBy(ContainerType& entries, KeySelector keySelector, SortOrder as
}); });
} }
template <typename ContainerType, typename KeySelector>
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 { namespace ui {
using RecentEntriesColumn = std::pair<std::string, size_t>; using RecentEntriesColumn = std::pair<std::string, size_t>;
@ -279,6 +293,10 @@ class RecentEntriesView : public View {
_table.focus(); _table.focus();
} }
void set_table(Entries& new_table) {
_table = new_table;
}
private: private:
RecentEntriesHeader _header; RecentEntriesHeader _header;
RecentEntriesTable<Entries> _table; RecentEntriesTable<Entries> _table;

View File

@ -323,6 +323,20 @@ std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length) {
return string; 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) { 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}; const uint32_t powers_of_ten[5] = {1, 10, 100, 1000, 10000};
std::string string{""}; std::string string{""};

View File

@ -78,6 +78,7 @@ std::string to_string_file_size(uint32_t file_size);
// Converts Mac Address to string. // Converts Mac Address to string.
std::string to_string_mac_address(const uint8_t* macAddress, uint8_t length); 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 /* 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. */ * 'unit_prefix' that 'n' is in initially. 3 is the index of the '1s' unit. */

View File

@ -118,7 +118,12 @@ void AlphanumView::refresh_keys() {
size_t n = 0; size_t n = 0;
for (auto& button : buttons) { for (auto& button : buttons) {
if (n > strlen(key_list)) {
button.set_text(std::string{'\0'});
} else {
button.set_text(std::string{key_list[n]}); button.set_text(std::string{key_list[n]});
}
n++; n++;
} }
@ -137,7 +142,9 @@ void AlphanumView::refresh_keys() {
void AlphanumView::on_button(Button& button) { void AlphanumView::on_button(Button& button) {
const auto c = button.text()[0]; const auto c = button.text()[0];
if (c != '\0') {
char_add(c); char_add(c);
}
// TODO: Consolidate shift handling. // TODO: Consolidate shift handling.
if (shift_mode == ShiftMode::Shift) { if (shift_mode == ShiftMode::Shift) {

View File

@ -57,6 +57,7 @@ class AlphanumView : public TextEntryView {
const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, ."; const char* const keys_upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ, .";
const char* const keys_digit = "1234567890()'`\"+-*/=<>_\\!?, ."; const char* const keys_digit = "1234567890()'`\"+-*/=<>_\\!?, .";
const char* const keys_symbl = "!@#$%^&*()[]'`\"{}|:;<>-_~?, ."; const char* const keys_symbl = "!@#$%^&*()[]'`\"{}|:;<>-_~?, .";
const char* const keys_hex = "1234567890ABCDEF ";
struct key_set_t { struct key_set_t {
const char* name; const char* name;
@ -64,9 +65,10 @@ class AlphanumView : public TextEntryView {
const char* shifted; const char* shifted;
}; };
const key_set_t key_sets[2] = { const key_set_t key_sets[3] = {
{"abc", keys_lower, keys_upper}, {"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; int16_t focused_button = 0;
uint32_t mode = 0; // Letters. uint32_t mode = 0; // Letters.

View File

@ -87,6 +87,7 @@
#include "analog_audio_app.hpp" #include "analog_audio_app.hpp"
#include "analog_tv_app.hpp" #include "analog_tv_app.hpp"
#include "ble_app.hpp" #include "ble_app.hpp"
#include "ble_tx_app.hpp"
#include "capture_app.hpp" #include "capture_app.hpp"
#include "ert_app.hpp" #include "ert_app.hpp"
#include "gps_sim_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<ADSBTxView>(); }}, {"ADS-B TX", ui::Color::green(), &bitmap_icon_adsb, [&nav]() { nav.push<ADSBTxView>(); }},
{"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }}, {"APRS TX", ui::Color::green(), &bitmap_icon_aprs, [&nav]() { nav.push<APRSTXView>(); }},
{"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }}, {"BHT Xy/EP", ui::Color::green(), &bitmap_icon_bht, [&nav]() { nav.push<BHTView>(); }},
{"BLE Tx", ui::Color::green(), &bitmap_icon_btle, [&nav]() { nav.push<BLETxView>(); }},
{"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push<CoasterPagerView>(); }}, {"BurgerPgr", ui::Color::yellow(), &bitmap_icon_burger, [&nav]() { nav.push<CoasterPagerView>(); }},
{"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }}, {"GPS Sim", ui::Color::green(), &bitmap_icon_gps_sim, [&nav]() { nav.push<GpsSimAppView>(); }},
{"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }}, {"Jammer", ui::Color::green(), &bitmap_icon_jammer, [&nav]() { nav.push<JammerView>(); }},

View File

@ -360,6 +360,14 @@ set(MODE_CPPSRC
proc_btlerx.cpp proc_btlerx.cpp
) )
DeclareTargets(PBTR btlerx) DeclareTargets(PBTR btlerx)
### BTLE TX
set(MODE_CPPSRC
proc_ble_tx.cpp
)
DeclareTargets(PBTT btletx)
### AIS ### AIS
set(MODE_CPPSRC set(MODE_CPPSRC

View File

@ -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 <cstdint>
#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<const BTLETxConfigureMessage*>(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<BTLETxProcessor>()};
event_dispatcher.run();
return 0;
}

View File

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

View File

@ -114,6 +114,7 @@ class Message {
POCSAGStats = 57, POCSAGStats = 57,
FSKRxConfigure = 58, FSKRxConfigure = 58,
BlePacket = 58, BlePacket = 58,
BTLETxConfigure = 59,
MAX MAX
}; };
@ -755,6 +756,22 @@ class BTLERxConfigureMessage : public Message {
const uint8_t channel_number; 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 { class NRFRxConfigureMessage : public Message {
public: public:
constexpr NRFRxConfigureMessage( constexpr NRFRxConfigureMessage(

View File

@ -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_afsk_rx{'P', 'A', 'F', 'R'};
constexpr image_tag_t image_tag_aprs_rx{'P', 'A', 'P', '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_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_nrf_rx{'P', 'N', 'R', 'R'};
constexpr image_tag_t image_tag_ais{'P', 'A', 'I', 'S'}; constexpr image_tag_t image_tag_ais{'P', 'A', 'I', 'S'};
constexpr image_tag_t image_tag_am_audio{'P', 'A', 'M', 'A'}; constexpr image_tag_t image_tag_am_audio{'P', 'A', 'M', 'A'};

View File

@ -0,0 +1 @@
010203040506 00112233445566778899AABBCCDDEEFF 1000