build for Intel jp and low grade Bluetooth thereon works and pairs with T-Beam
This commit is contained in:
parent
de75f7d865
commit
0ca0ec605a
8 changed files with 1377 additions and 19 deletions
2
exercises/305_microReticulum_ble_file_transfer/.gitignore
vendored
Normal file
2
exercises/305_microReticulum_ble_file_transfer/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
transport_identity
|
||||||
|
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
Between the dark and the daylight,
|
||||||
|
When the night is beginning to lower,
|
||||||
|
Comes a pause in the day's occupations,
|
||||||
|
That is known as the Children's Hour.
|
||||||
|
|
||||||
|
I hear in the chamber above me
|
||||||
|
The patter of little feet,
|
||||||
|
The sound of a door that is opened,
|
||||||
|
And voices soft and sweet.
|
||||||
|
|
||||||
|
From my study I see in the lamplight,
|
||||||
|
Descending the broad hall stair,
|
||||||
|
Grave Alice, and laughing Allegra,
|
||||||
|
And Edith with golden hair.
|
||||||
|
|
||||||
|
A whisper, and then a silence:
|
||||||
|
Yet I know by their merry eyes
|
||||||
|
They are plotting and planning together
|
||||||
|
To take me by surprise.
|
||||||
|
|
||||||
|
A sudden rush from the stairway,
|
||||||
|
A sudden raid from the hall!
|
||||||
|
By three doors left unguarded
|
||||||
|
They enter my castle wall!
|
||||||
|
|
||||||
|
They climb up into my turret
|
||||||
|
O'er the arms and back of my chair;
|
||||||
|
If I try to escape, they surround me;
|
||||||
|
They seem to be everywhere.
|
||||||
|
|
||||||
|
They almost devour me with kisses,
|
||||||
|
Their arms about me entwine,
|
||||||
|
Till I think of the Bishop of Bingen
|
||||||
|
In his Mouse-Tower on the Rhine!
|
||||||
|
|
||||||
|
Do you think, O blue-eyed banditti,
|
||||||
|
Because you have scaled the wall,
|
||||||
|
Such an old mustache as I am
|
||||||
|
Is not a match for you all!
|
||||||
|
|
||||||
|
I have you fast in my fortress,
|
||||||
|
And will not let you depart,
|
||||||
|
But put you down into the dungeon
|
||||||
|
In the round-tower of my heart.
|
||||||
|
|
||||||
|
And there will I keep you forever,
|
||||||
|
Yes, forever and a day,
|
||||||
|
Till the walls shall crumble to ruin,
|
||||||
|
And moulder in dust away!
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
The little toy dog is covered with dust,
|
||||||
|
But sturdy and staunch he stands;
|
||||||
|
And the little toy soldier is red with rust,
|
||||||
|
And his musket molds in his hands.
|
||||||
|
Time was when the little toy dog was new,
|
||||||
|
And the soldier was passing fair;
|
||||||
|
And that was the time when our Little Boy Blue
|
||||||
|
Kissed them and put them there.
|
||||||
|
|
||||||
|
"Now, don't you go till I come," he said,
|
||||||
|
"And don't you make any noise!"
|
||||||
|
So, toddling off to his trundle-bed,
|
||||||
|
He dreamed of the pretty toys;
|
||||||
|
And, as he was dreaming, an angel song
|
||||||
|
Awakened our Little Boy Blue
|
||||||
|
Oh! the years are many, the years are long,
|
||||||
|
But the little toy friends are true!
|
||||||
|
|
||||||
|
Ay, faithful to Little Boy Blue they stand,
|
||||||
|
Each in the same old place
|
||||||
|
Awaiting the touch of a little hand,
|
||||||
|
The smile of a little face;
|
||||||
|
And they wonder, as waiting the long years through
|
||||||
|
In the dust of that little chair,
|
||||||
|
What has become of our Little Boy Blue,
|
||||||
|
Since he kissed them and put them there.
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
[platformio]
|
[platformio]
|
||||||
default_envs = tbeam_if
|
default_envs = tbeam_if
|
||||||
|
|
||||||
[env]
|
[tbeam_base]
|
||||||
platform = espressif32
|
platform = espressif32
|
||||||
framework = arduino
|
framework = arduino
|
||||||
board = esp32-s3-devkitc-1
|
board = esp32-s3-devkitc-1
|
||||||
|
|
@ -14,6 +14,10 @@ extra_scripts = pre:scripts/embed_text.py
|
||||||
custom_text_source = texts/If.txt
|
custom_text_source = texts/If.txt
|
||||||
lib_extra_dirs =
|
lib_extra_dirs =
|
||||||
../../lib
|
../../lib
|
||||||
|
build_src_filter =
|
||||||
|
+<*>
|
||||||
|
-<host_jp_main.cpp>
|
||||||
|
-<HostBluezBleInterface.cpp>
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-Wall
|
-Wall
|
||||||
|
|
@ -40,77 +44,116 @@ lib_deps =
|
||||||
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
|
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
|
||||||
|
|
||||||
[env:tbeam_if]
|
[env:tbeam_if]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/If.txt
|
custom_text_source = texts/If.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=32
|
-D FILE_TRANSFER_CHUNK_SIZE=32
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
||||||
|
|
||||||
[env:tbeam_if_full]
|
[env:tbeam_if_full]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/If_full.txt
|
custom_text_source = texts/If_full.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=32
|
-D FILE_TRANSFER_CHUNK_SIZE=32
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
||||||
|
|
||||||
[env:tbeam_constitution]
|
[env:tbeam_constitution]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/US_Constitution.txt
|
custom_text_source = texts/US_Constitution.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=32
|
-D FILE_TRANSFER_CHUNK_SIZE=32
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
||||||
|
|
||||||
[env:tbeam_if_pi_zero_profile]
|
[env:tbeam_if_pi_zero_profile]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/If.txt
|
custom_text_source = texts/If.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=300
|
-D FILE_TRANSFER_CHUNK_SIZE=300
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
||||||
|
|
||||||
[env:tbeam_if_full_pi_zero_profile]
|
[env:tbeam_if_full_pi_zero_profile]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/If_full.txt
|
custom_text_source = texts/If_full.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=300
|
-D FILE_TRANSFER_CHUNK_SIZE=300
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
||||||
|
|
||||||
[env:tbeam_constitution_pi_zero_profile]
|
[env:tbeam_constitution_pi_zero_profile]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
custom_text_source = texts/US_Constitution.txt
|
custom_text_source = texts/US_Constitution.txt
|
||||||
build_flags =
|
build_flags =
|
||||||
${env.build_flags}
|
${tbeam_base.build_flags}
|
||||||
-D FILE_TRANSFER_CHUNK_SIZE=300
|
-D FILE_TRANSFER_CHUNK_SIZE=300
|
||||||
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
|
||||||
|
|
||||||
[env:tbeam]
|
[env:tbeam]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
|
|
||||||
|
[env:jp_native]
|
||||||
|
platform = native
|
||||||
|
build_type = debug
|
||||||
|
extra_scripts = pre:scripts/embed_text.py
|
||||||
|
custom_text_source = texts/If_full.txt
|
||||||
|
build_unflags =
|
||||||
|
-std=gnu++11
|
||||||
|
build_flags =
|
||||||
|
-std=c++17
|
||||||
|
-g3
|
||||||
|
-ggdb
|
||||||
|
-Wall
|
||||||
|
-Wextra
|
||||||
|
-Wno-missing-field-initializers
|
||||||
|
-Wno-format
|
||||||
|
-Wno-unused-parameter
|
||||||
|
-include stdint.h
|
||||||
|
-D HOST_NATIVE
|
||||||
|
-D NATIVE
|
||||||
|
-D RNS_USE_FS
|
||||||
|
-D RNS_PERSIST_PATHS
|
||||||
|
-D USTORE_USE_UNIVERSALFS
|
||||||
|
-D MSGPACK_USE_BOOST=OFF
|
||||||
|
-D FILE_TRANSFER_CHUNK_SIZE=32
|
||||||
|
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
|
||||||
|
-D HOST_NODE_LABEL=\"Node-JP-CLIENT\"
|
||||||
|
!pkg-config --cflags gio-2.0 glib-2.0 bluez
|
||||||
|
!pkg-config --libs gio-2.0 glib-2.0 bluez
|
||||||
|
build_src_filter =
|
||||||
|
+<host_jp_main.cpp>
|
||||||
|
+<HostBluezBleInterface.cpp>
|
||||||
|
lib_deps =
|
||||||
|
ArduinoJson@^7.4.2
|
||||||
|
MsgPack@^0.4.2
|
||||||
|
https://github.com/attermann/Crypto.git
|
||||||
|
https://github.com/attermann/microStore.git
|
||||||
|
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
|
||||||
|
lib_compat_mode = off
|
||||||
|
|
||||||
[env:amy]
|
[env:amy]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytAMY
|
upload_port = /dev/ttytAMY
|
||||||
monitor_port = /dev/ttytAMY
|
monitor_port = /dev/ttytAMY
|
||||||
|
|
||||||
[env:bob]
|
[env:bob]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytBOB
|
upload_port = /dev/ttytBOB
|
||||||
monitor_port = /dev/ttytBOB
|
monitor_port = /dev/ttytBOB
|
||||||
|
|
||||||
[env:cy]
|
[env:cy]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytCY
|
upload_port = /dev/ttytCY
|
||||||
monitor_port = /dev/ttytCY
|
monitor_port = /dev/ttytCY
|
||||||
|
|
||||||
[env:dan]
|
[env:dan]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytDAN
|
upload_port = /dev/ttytDAN
|
||||||
monitor_port = /dev/ttytDAN
|
monitor_port = /dev/ttytDAN
|
||||||
|
|
||||||
[env:ed]
|
[env:ed]
|
||||||
extends = env
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytED
|
upload_port = /dev/ttytED
|
||||||
monitor_port = /dev/ttytED
|
monitor_port = /dev/ttytED
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,20 @@ symbol_name = source_path.name.replace("\\", "/")
|
||||||
lines = [
|
lines = [
|
||||||
"#pragma once",
|
"#pragma once",
|
||||||
"",
|
"",
|
||||||
|
"#include <stddef.h>",
|
||||||
|
"#include <stdint.h>",
|
||||||
|
"",
|
||||||
|
"#if defined(ARDUINO)",
|
||||||
"#include <Arduino.h>",
|
"#include <Arduino.h>",
|
||||||
"#include <pgmspace.h>",
|
"#include <pgmspace.h>",
|
||||||
|
"#else",
|
||||||
|
"#ifndef PROGMEM",
|
||||||
|
"#define PROGMEM",
|
||||||
|
"#endif",
|
||||||
|
"#ifndef pgm_read_byte",
|
||||||
|
"#define pgm_read_byte(addr) (*(const uint8_t*)(addr))",
|
||||||
|
"#endif",
|
||||||
|
"#endif",
|
||||||
"",
|
"",
|
||||||
f'static constexpr const char* SELECTED_TEXT_NAME = "{symbol_name}";',
|
f'static constexpr const char* SELECTED_TEXT_NAME = "{symbol_name}";',
|
||||||
f"static constexpr size_t SELECTED_TEXT_SIZE = {len(data)};",
|
f"static constexpr size_t SELECTED_TEXT_SIZE = {len(data)};",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,624 @@
|
||||||
|
#include "HostBluezBleInterface.h"
|
||||||
|
|
||||||
|
#include <Utilities/OS.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace RNS;
|
||||||
|
|
||||||
|
static uint64_t now_ms() {
|
||||||
|
return RNS::Utilities::OS::ltime();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u16_be(uint8_t* data, uint16_t value) {
|
||||||
|
data[0] = (uint8_t)((value >> 8) & 0xFF);
|
||||||
|
data[1] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u32_be(uint8_t* data, uint32_t value) {
|
||||||
|
data[0] = (uint8_t)((value >> 24) & 0xFF);
|
||||||
|
data[1] = (uint8_t)((value >> 16) & 0xFF);
|
||||||
|
data[2] = (uint8_t)((value >> 8) & 0xFF);
|
||||||
|
data[3] = (uint8_t)(value & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint16_t get_u16_be(const uint8_t* data) {
|
||||||
|
return ((uint16_t)data[0] << 8) | data[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t get_u32_be(const uint8_t* data) {
|
||||||
|
return ((uint32_t)data[0] << 24) |
|
||||||
|
((uint32_t)data[1] << 16) |
|
||||||
|
((uint32_t)data[2] << 8) |
|
||||||
|
(uint32_t)data[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool uuid_matches(const char* found, const char* expected) {
|
||||||
|
return found && g_ascii_strcasecmp(found, expected) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void log_error(const char* what, GError* error) {
|
||||||
|
if (error) {
|
||||||
|
std::fprintf(stderr, "%s: %s\n", what, error->message);
|
||||||
|
g_error_free(error);
|
||||||
|
} else {
|
||||||
|
std::fprintf(stderr, "%s\n", what);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HostBluezBleInterface::HostBluezBleInterface(const std::string& node_label, const char* name)
|
||||||
|
: InterfaceImpl(name), node_label_(node_label) {
|
||||||
|
_IN = true;
|
||||||
|
_OUT = true;
|
||||||
|
_bitrate = 1000000;
|
||||||
|
_HW_MTU = BLE_PAYLOAD_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
HostBluezBleInterface::~HostBluezBleInterface() {
|
||||||
|
stop();
|
||||||
|
if (bus_) {
|
||||||
|
g_object_unref(bus_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::start() {
|
||||||
|
if (online_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!connect_bus() || !find_adapter()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
online_ = true;
|
||||||
|
_online = true;
|
||||||
|
std::printf("BLE linux-central: adapter=%s label=%s\n", adapter_path_.c_str(), node_label_.c_str());
|
||||||
|
start_discovery();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::stop() {
|
||||||
|
online_ = false;
|
||||||
|
_online = false;
|
||||||
|
disconnect_peer();
|
||||||
|
stop_discovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::connect_bus() {
|
||||||
|
GError* error = nullptr;
|
||||||
|
bus_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error);
|
||||||
|
if (!bus_) {
|
||||||
|
log_error("BLE linux-central: cannot connect to system D-Bus", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::find_adapter() {
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* managed = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, "/",
|
||||||
|
"org.freedesktop.DBus.ObjectManager",
|
||||||
|
"GetManagedObjects", nullptr,
|
||||||
|
G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!managed) {
|
||||||
|
log_error("BLE linux-central: GetManagedObjects failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GVariantIter* objects = nullptr;
|
||||||
|
g_variant_get(managed, "(a{oa{sa{sv}}})", &objects);
|
||||||
|
const gchar* object_path = nullptr;
|
||||||
|
GVariant* ifaces = nullptr;
|
||||||
|
while (g_variant_iter_next(objects, "{&o@a{sa{sv}}}", &object_path, &ifaces)) {
|
||||||
|
if (g_variant_lookup_value(ifaces, "org.bluez.Adapter1", G_VARIANT_TYPE("a{sv}"))) {
|
||||||
|
adapter_path_ = object_path;
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
}
|
||||||
|
g_variant_iter_free(objects);
|
||||||
|
g_variant_unref(managed);
|
||||||
|
|
||||||
|
if (adapter_path_.empty()) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: no BlueZ adapter found\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GVariantBuilder props;
|
||||||
|
g_variant_builder_init(&props, G_VARIANT_TYPE("a{sv}"));
|
||||||
|
g_variant_builder_add(&props, "{sv}", "Powered", g_variant_new_boolean(TRUE));
|
||||||
|
GVariantBuilder invalidated;
|
||||||
|
g_variant_builder_init(&invalidated, G_VARIANT_TYPE("as"));
|
||||||
|
g_dbus_connection_emit_signal(bus_, nullptr, adapter_path_.c_str(),
|
||||||
|
"org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||||
|
g_variant_new("(sa{sv}as)", "org.bluez.Adapter1", &props, &invalidated),
|
||||||
|
nullptr);
|
||||||
|
|
||||||
|
error = nullptr;
|
||||||
|
GVariant* powered = g_dbus_connection_call_sync(
|
||||||
|
bus_, BLUEZ_BUS, adapter_path_.c_str(), "org.freedesktop.DBus.Properties", "Set",
|
||||||
|
g_variant_new("(ssv)", "org.bluez.Adapter1", "Powered", g_variant_new_boolean(TRUE)),
|
||||||
|
nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!powered) {
|
||||||
|
log_error("BLE linux-central: could not power adapter", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(powered);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::start_discovery() {
|
||||||
|
if (discovering_ || adapter_path_.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
GVariantBuilder uuids;
|
||||||
|
g_variant_builder_init(&uuids, G_VARIANT_TYPE("as"));
|
||||||
|
g_variant_builder_add(&uuids, "s", SERVICE_UUID);
|
||||||
|
GVariantBuilder filter;
|
||||||
|
g_variant_builder_init(&filter, G_VARIANT_TYPE("a{sv}"));
|
||||||
|
g_variant_builder_add(&filter, "{sv}", "UUIDs", g_variant_builder_end(&uuids));
|
||||||
|
g_variant_builder_add(&filter, "{sv}", "Transport", g_variant_new_string("le"));
|
||||||
|
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(
|
||||||
|
bus_, BLUEZ_BUS, adapter_path_.c_str(), "org.bluez.Adapter1", "SetDiscoveryFilter",
|
||||||
|
g_variant_new("(a{sv})", &filter), nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: SetDiscoveryFilter failed", error);
|
||||||
|
} else {
|
||||||
|
g_variant_unref(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
error = nullptr;
|
||||||
|
result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, adapter_path_.c_str(),
|
||||||
|
"org.bluez.Adapter1", "StartDiscovery",
|
||||||
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: StartDiscovery failed", error);
|
||||||
|
next_scan_ms_ = now_ms() + SCAN_RETRY_MS;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
discovering_ = true;
|
||||||
|
next_scan_ms_ = now_ms() + SCAN_RETRY_MS;
|
||||||
|
std::printf("BLE linux-central: scanning for Reticulum service\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::stop_discovery() {
|
||||||
|
if (!discovering_ || adapter_path_.empty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, adapter_path_.c_str(),
|
||||||
|
"org.bluez.Adapter1", "StopDiscovery",
|
||||||
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||||
|
nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: StopDiscovery failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
discovering_ = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::scan_for_peer() {
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* managed = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, "/",
|
||||||
|
"org.freedesktop.DBus.ObjectManager",
|
||||||
|
"GetManagedObjects", nullptr,
|
||||||
|
G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!managed) {
|
||||||
|
log_error("BLE linux-central: scan GetManagedObjects failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string found_path;
|
||||||
|
std::string found_name;
|
||||||
|
GVariantIter* objects = nullptr;
|
||||||
|
g_variant_get(managed, "(a{oa{sa{sv}}})", &objects);
|
||||||
|
const gchar* object_path = nullptr;
|
||||||
|
GVariant* ifaces = nullptr;
|
||||||
|
while (g_variant_iter_next(objects, "{&o@a{sa{sv}}}", &object_path, &ifaces)) {
|
||||||
|
GVariant* device = g_variant_lookup_value(ifaces, "org.bluez.Device1", G_VARIANT_TYPE("a{sv}"));
|
||||||
|
if (!device) {
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gchar* name = nullptr;
|
||||||
|
g_variant_lookup(device, "Name", "&s", &name);
|
||||||
|
bool has_rns_name = name && std::strncmp(name, "RNS-", 4) == 0;
|
||||||
|
bool has_service = false;
|
||||||
|
GVariant* uuids = g_variant_lookup_value(device, "UUIDs", G_VARIANT_TYPE("as"));
|
||||||
|
if (uuids) {
|
||||||
|
GVariantIter uuid_iter;
|
||||||
|
const gchar* uuid = nullptr;
|
||||||
|
g_variant_iter_init(&uuid_iter, uuids);
|
||||||
|
while (g_variant_iter_next(&uuid_iter, "&s", &uuid)) {
|
||||||
|
if (uuid_matches(uuid, SERVICE_UUID)) {
|
||||||
|
has_service = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_variant_unref(uuids);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_service || has_rns_name) {
|
||||||
|
found_path = object_path;
|
||||||
|
found_name = name ? name : "";
|
||||||
|
g_variant_unref(device);
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_variant_unref(device);
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
}
|
||||||
|
g_variant_iter_free(objects);
|
||||||
|
g_variant_unref(managed);
|
||||||
|
|
||||||
|
if (found_path.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("BLE linux-central: peer candidate path=%s name=%s\n", found_path.c_str(), found_name.c_str());
|
||||||
|
stop_discovery();
|
||||||
|
return connect_device(found_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::connect_device(const std::string& device_path) {
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, device_path.c_str(),
|
||||||
|
"org.bluez.Device1", "Connect",
|
||||||
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, 30000,
|
||||||
|
nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: Device1.Connect failed", error);
|
||||||
|
next_scan_ms_ = now_ms() + SCAN_RETRY_MS;
|
||||||
|
start_discovery();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
device_path_ = device_path;
|
||||||
|
std::printf("BLE linux-central: connected to %s\n", device_path_.c_str());
|
||||||
|
|
||||||
|
uint64_t deadline = now_ms() + 10000;
|
||||||
|
while (now_ms() < deadline) {
|
||||||
|
while (g_main_context_iteration(nullptr, FALSE)) {
|
||||||
|
}
|
||||||
|
if (discover_characteristics()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
RNS::Utilities::OS::sleep(0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rx_char_path_.empty() || tx_char_path_.empty()) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: Reticulum characteristics not found\n");
|
||||||
|
disconnect_peer();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!start_notify()) {
|
||||||
|
disconnect_peer();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t identity[16] = {};
|
||||||
|
std::hash<std::string> hasher;
|
||||||
|
size_t h1 = hasher(node_label_);
|
||||||
|
size_t h2 = hasher(node_label_ + ":exercise306");
|
||||||
|
std::memcpy(identity, &h1, std::min(sizeof(h1), sizeof(identity)));
|
||||||
|
std::memcpy(identity + 8, &h2, std::min(sizeof(h2), sizeof(identity) - 8));
|
||||||
|
write_characteristic(rx_char_path_, identity, sizeof(identity), true);
|
||||||
|
|
||||||
|
connected_ = true;
|
||||||
|
std::printf("BLE linux-central: notifications active; identity handshake sent\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::discover_characteristics() {
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* managed = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, "/",
|
||||||
|
"org.freedesktop.DBus.ObjectManager",
|
||||||
|
"GetManagedObjects", nullptr,
|
||||||
|
G_VARIANT_TYPE("(a{oa{sa{sv}}})"),
|
||||||
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!managed) {
|
||||||
|
log_error("BLE linux-central: characteristic discovery failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx_char_path_.clear();
|
||||||
|
rx_char_path_.clear();
|
||||||
|
identity_char_path_.clear();
|
||||||
|
|
||||||
|
GVariantIter* objects = nullptr;
|
||||||
|
g_variant_get(managed, "(a{oa{sa{sv}}})", &objects);
|
||||||
|
const gchar* object_path = nullptr;
|
||||||
|
GVariant* ifaces = nullptr;
|
||||||
|
while (g_variant_iter_next(objects, "{&o@a{sa{sv}}}", &object_path, &ifaces)) {
|
||||||
|
if (device_path_.empty() || std::strncmp(object_path, device_path_.c_str(), device_path_.size()) != 0) {
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
GVariant* characteristic = g_variant_lookup_value(ifaces, "org.bluez.GattCharacteristic1", G_VARIANT_TYPE("a{sv}"));
|
||||||
|
if (!characteristic) {
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const gchar* uuid = nullptr;
|
||||||
|
if (g_variant_lookup(characteristic, "UUID", "&s", &uuid)) {
|
||||||
|
if (uuid_matches(uuid, TX_UUID)) {
|
||||||
|
tx_char_path_ = object_path;
|
||||||
|
} else if (uuid_matches(uuid, RX_UUID)) {
|
||||||
|
rx_char_path_ = object_path;
|
||||||
|
} else if (uuid_matches(uuid, IDENTITY_UUID)) {
|
||||||
|
identity_char_path_ = object_path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_variant_unref(characteristic);
|
||||||
|
g_variant_unref(ifaces);
|
||||||
|
}
|
||||||
|
g_variant_iter_free(objects);
|
||||||
|
g_variant_unref(managed);
|
||||||
|
return !tx_char_path_.empty() && !rx_char_path_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::start_notify() {
|
||||||
|
notify_subscription_ = g_dbus_connection_signal_subscribe(
|
||||||
|
bus_, BLUEZ_BUS, "org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||||
|
tx_char_path_.c_str(), "org.bluez.GattCharacteristic1",
|
||||||
|
G_DBUS_SIGNAL_FLAGS_NONE, properties_changed, this, nullptr);
|
||||||
|
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, tx_char_path_.c_str(),
|
||||||
|
"org.bluez.GattCharacteristic1", "StartNotify",
|
||||||
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||||
|
nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: StartNotify failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::write_characteristic(const std::string& path,
|
||||||
|
const uint8_t* data,
|
||||||
|
size_t len,
|
||||||
|
bool with_response) {
|
||||||
|
GVariantBuilder bytes;
|
||||||
|
g_variant_builder_init(&bytes, G_VARIANT_TYPE("ay"));
|
||||||
|
for (size_t i = 0; i < len; ++i) {
|
||||||
|
g_variant_builder_add(&bytes, "y", data[i]);
|
||||||
|
}
|
||||||
|
GVariantBuilder options;
|
||||||
|
g_variant_builder_init(&options, G_VARIANT_TYPE("a{sv}"));
|
||||||
|
g_variant_builder_add(&options, "{sv}", "type", g_variant_new_string(with_response ? "request" : "command"));
|
||||||
|
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(
|
||||||
|
bus_, BLUEZ_BUS, path.c_str(), "org.bluez.GattCharacteristic1", "WriteValue",
|
||||||
|
g_variant_new("(aya{sv})", &bytes, &options), nullptr,
|
||||||
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: WriteValue failed", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::loop() {
|
||||||
|
if (!online_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (g_main_context_iteration(nullptr, FALSE)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connected_) {
|
||||||
|
uint64_t now = now_ms();
|
||||||
|
if (!discovering_ && now >= next_scan_ms_) {
|
||||||
|
start_discovery();
|
||||||
|
}
|
||||||
|
if (discovering_) {
|
||||||
|
scan_for_peer();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reassembly_started_ms_ != 0 && now_ms() - reassembly_started_ms_ > REASSEMBLY_TIMEOUT_MS) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: reassembly timeout; dropping partial packet\n");
|
||||||
|
reset_reassembly();
|
||||||
|
}
|
||||||
|
|
||||||
|
RNS::Bytes packet({RNS::Type::NONE});
|
||||||
|
while (dequeue_packet(packet)) {
|
||||||
|
InterfaceImpl::handle_incoming(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::send_outgoing(const RNS::Bytes& data) {
|
||||||
|
if (!online_ || !connected_ || rx_char_path_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total = (data.size() + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
|
||||||
|
if (total == 0 || total > 65535) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: cannot fragment packet len=%zu fragments=%zu\n", data.size(), total);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t msg_id = ++tx_message_id_;
|
||||||
|
for (size_t i = 0; i < total; ++i) {
|
||||||
|
uint8_t fragment[BLE_VALUE_SIZE];
|
||||||
|
uint8_t fragment_type = FRAG_CONTINUE;
|
||||||
|
if (i == 0) {
|
||||||
|
fragment_type = FRAG_START;
|
||||||
|
} else if (i == total - 1) {
|
||||||
|
fragment_type = FRAG_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t offset = i * BLE_PAYLOAD_SIZE;
|
||||||
|
size_t chunk = std::min(BLE_PAYLOAD_SIZE, data.size() - offset);
|
||||||
|
fragment[0] = fragment_type;
|
||||||
|
fragment[1] = FRAG_HEADER_VERSION;
|
||||||
|
put_u16_be(fragment + 2, (uint16_t)i);
|
||||||
|
put_u16_be(fragment + 4, (uint16_t)total);
|
||||||
|
put_u32_be(fragment + 6, msg_id);
|
||||||
|
put_u32_be(fragment + 10, (uint32_t)data.size());
|
||||||
|
std::memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
|
||||||
|
send_fragment(fragment, FRAG_HEADER_SIZE + chunk);
|
||||||
|
RNS::Utilities::OS::sleep(0.008);
|
||||||
|
}
|
||||||
|
|
||||||
|
InterfaceImpl::handle_outgoing(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::send_fragment(const uint8_t* data, size_t len) {
|
||||||
|
write_characteristic(rx_char_path_, data, len, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::properties_changed(GDBusConnection* connection,
|
||||||
|
const gchar* sender_name,
|
||||||
|
const gchar* object_path,
|
||||||
|
const gchar* interface_name,
|
||||||
|
const gchar* signal_name,
|
||||||
|
GVariant* parameters,
|
||||||
|
gpointer user_data) {
|
||||||
|
(void)connection;
|
||||||
|
(void)sender_name;
|
||||||
|
(void)interface_name;
|
||||||
|
(void)signal_name;
|
||||||
|
auto* self = static_cast<HostBluezBleInterface*>(user_data);
|
||||||
|
if (!self || self->tx_char_path_ != object_path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gchar* changed_iface = nullptr;
|
||||||
|
GVariant* changed = nullptr;
|
||||||
|
GVariant* invalidated = nullptr;
|
||||||
|
g_variant_get(parameters, "(&s@a{sv}@as)", &changed_iface, &changed, &invalidated);
|
||||||
|
if (std::strcmp(changed_iface, "org.bluez.GattCharacteristic1") == 0) {
|
||||||
|
GVariant* value = g_variant_lookup_value(changed, "Value", G_VARIANT_TYPE("ay"));
|
||||||
|
if (value) {
|
||||||
|
gsize len = 0;
|
||||||
|
const uint8_t* data = static_cast<const uint8_t*>(g_variant_get_fixed_array(value, &len, sizeof(uint8_t)));
|
||||||
|
if (data && len > 0) {
|
||||||
|
self->handle_fragment(data, len);
|
||||||
|
}
|
||||||
|
g_variant_unref(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_variant_unref(changed);
|
||||||
|
g_variant_unref(invalidated);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::handle_fragment(const uint8_t* data, size_t len) {
|
||||||
|
if (len < FRAG_HEADER_SIZE) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: fragment too short len=%zu\n", len);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t fragment_type = data[0];
|
||||||
|
uint8_t version = data[1];
|
||||||
|
uint16_t sequence = get_u16_be(data + 2);
|
||||||
|
uint16_t total = get_u16_be(data + 4);
|
||||||
|
uint32_t msg_id = get_u32_be(data + 6);
|
||||||
|
uint32_t msg_len = get_u32_be(data + 10);
|
||||||
|
const uint8_t* payload = data + FRAG_HEADER_SIZE;
|
||||||
|
size_t payload_len = len - FRAG_HEADER_SIZE;
|
||||||
|
uint32_t expected_fragments = (msg_len + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
|
||||||
|
size_t expected_offset = (size_t)sequence * BLE_PAYLOAD_SIZE;
|
||||||
|
|
||||||
|
if ((fragment_type != FRAG_START && fragment_type != FRAG_CONTINUE && fragment_type != FRAG_END) ||
|
||||||
|
version != FRAG_HEADER_VERSION ||
|
||||||
|
total == 0 || sequence >= total ||
|
||||||
|
msg_id == 0 || msg_len == 0 ||
|
||||||
|
expected_fragments == 0 || expected_fragments != total ||
|
||||||
|
expected_offset >= msg_len || payload_len > (msg_len - expected_offset)) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: invalid fragment header\n");
|
||||||
|
reset_reassembly();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sequence == 0) {
|
||||||
|
reset_reassembly();
|
||||||
|
expected_total_ = total;
|
||||||
|
received_fragments_ = 0;
|
||||||
|
expected_message_len_ = msg_len;
|
||||||
|
reassembly_started_ms_ = now_ms();
|
||||||
|
current_rx_message_id_ = msg_id;
|
||||||
|
} else if (reassembly_started_ms_ == 0 || msg_id != current_rx_message_id_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_total_ != total || expected_message_len_ != msg_len || sequence != received_fragments_) {
|
||||||
|
std::fprintf(stderr, "BLE linux-central: out-of-order fragment; dropping partial packet\n");
|
||||||
|
reset_reassembly();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
reassembly_buffer_.append(payload, payload_len);
|
||||||
|
received_fragments_++;
|
||||||
|
|
||||||
|
if (received_fragments_ == expected_total_) {
|
||||||
|
if (reassembly_buffer_.size() == expected_message_len_) {
|
||||||
|
enqueue_packet(reassembly_buffer_);
|
||||||
|
}
|
||||||
|
reset_reassembly();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::enqueue_packet(const RNS::Bytes& packet) {
|
||||||
|
incoming_packets_.push_back(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezBleInterface::dequeue_packet(RNS::Bytes& packet) {
|
||||||
|
if (incoming_packets_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
packet = incoming_packets_.front();
|
||||||
|
incoming_packets_.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::reset_reassembly() {
|
||||||
|
reassembly_buffer_.clear();
|
||||||
|
expected_total_ = 0;
|
||||||
|
received_fragments_ = 0;
|
||||||
|
expected_message_len_ = 0;
|
||||||
|
reassembly_started_ms_ = 0;
|
||||||
|
current_rx_message_id_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::disconnect_peer() {
|
||||||
|
connected_ = false;
|
||||||
|
if (notify_subscription_) {
|
||||||
|
g_dbus_connection_signal_unsubscribe(bus_, notify_subscription_);
|
||||||
|
notify_subscription_ = 0;
|
||||||
|
}
|
||||||
|
if (!device_path_.empty()) {
|
||||||
|
GError* error = nullptr;
|
||||||
|
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, device_path_.c_str(),
|
||||||
|
"org.bluez.Device1", "Disconnect",
|
||||||
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||||
|
nullptr, &error);
|
||||||
|
if (!result) {
|
||||||
|
log_error("BLE linux-central: Device1.Disconnect failed", error);
|
||||||
|
} else {
|
||||||
|
g_variant_unref(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
device_path_.clear();
|
||||||
|
tx_char_path_.clear();
|
||||||
|
rx_char_path_.clear();
|
||||||
|
identity_char_path_.clear();
|
||||||
|
reset_reassembly();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Bytes.h>
|
||||||
|
#include <Interface.h>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class HostBluezBleInterface : public RNS::InterfaceImpl {
|
||||||
|
public:
|
||||||
|
explicit HostBluezBleInterface(const std::string& node_label,
|
||||||
|
const char* name = "HostBluezBLE");
|
||||||
|
~HostBluezBleInterface() override;
|
||||||
|
|
||||||
|
bool start() override;
|
||||||
|
void stop() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
bool connected() const { return connected_; }
|
||||||
|
const char* role_name() const { return "linux-central"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void send_outgoing(const RNS::Bytes& data) override;
|
||||||
|
|
||||||
|
bool connect_bus();
|
||||||
|
bool find_adapter();
|
||||||
|
bool start_discovery();
|
||||||
|
bool stop_discovery();
|
||||||
|
bool scan_for_peer();
|
||||||
|
bool connect_device(const std::string& device_path);
|
||||||
|
bool discover_characteristics();
|
||||||
|
bool start_notify();
|
||||||
|
bool write_characteristic(const std::string& path, const uint8_t* data, size_t len, bool with_response);
|
||||||
|
void send_fragment(const uint8_t* data, size_t len);
|
||||||
|
void handle_fragment(const uint8_t* data, size_t len);
|
||||||
|
void enqueue_packet(const RNS::Bytes& packet);
|
||||||
|
bool dequeue_packet(RNS::Bytes& packet);
|
||||||
|
void reset_reassembly();
|
||||||
|
void disconnect_peer();
|
||||||
|
|
||||||
|
static void properties_changed(GDBusConnection* connection,
|
||||||
|
const gchar* sender_name,
|
||||||
|
const gchar* object_path,
|
||||||
|
const gchar* interface_name,
|
||||||
|
const gchar* signal_name,
|
||||||
|
GVariant* parameters,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
static constexpr const char* BLUEZ_BUS = "org.bluez";
|
||||||
|
static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3";
|
||||||
|
static constexpr const char* TX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e4";
|
||||||
|
static constexpr const char* RX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e5";
|
||||||
|
static constexpr const char* IDENTITY_UUID = "37145b00-442d-4a94-917f-8f42c5da28e6";
|
||||||
|
|
||||||
|
static constexpr uint8_t FRAG_START = 0x01;
|
||||||
|
static constexpr uint8_t FRAG_CONTINUE = 0x02;
|
||||||
|
static constexpr uint8_t FRAG_END = 0x03;
|
||||||
|
static constexpr uint8_t FRAG_HEADER_VERSION = 0x02;
|
||||||
|
static constexpr size_t FRAG_HEADER_SIZE = 14;
|
||||||
|
static constexpr size_t BLE_ATT_MTU = 185;
|
||||||
|
static constexpr size_t BLE_VALUE_SIZE = BLE_ATT_MTU - 3;
|
||||||
|
static constexpr size_t BLE_PAYLOAD_SIZE = BLE_VALUE_SIZE - FRAG_HEADER_SIZE;
|
||||||
|
static constexpr uint64_t REASSEMBLY_TIMEOUT_MS = 30000;
|
||||||
|
static constexpr uint64_t SCAN_RETRY_MS = 5000;
|
||||||
|
|
||||||
|
std::string node_label_;
|
||||||
|
GDBusConnection* bus_ = nullptr;
|
||||||
|
std::string adapter_path_;
|
||||||
|
std::string device_path_;
|
||||||
|
std::string tx_char_path_;
|
||||||
|
std::string rx_char_path_;
|
||||||
|
std::string identity_char_path_;
|
||||||
|
guint notify_subscription_ = 0;
|
||||||
|
bool online_ = false;
|
||||||
|
bool connected_ = false;
|
||||||
|
bool discovering_ = false;
|
||||||
|
uint64_t next_scan_ms_ = 0;
|
||||||
|
uint32_t tx_message_id_ = 0;
|
||||||
|
|
||||||
|
RNS::Bytes reassembly_buffer_;
|
||||||
|
uint16_t expected_total_ = 0;
|
||||||
|
uint16_t received_fragments_ = 0;
|
||||||
|
uint32_t expected_message_len_ = 0;
|
||||||
|
uint64_t reassembly_started_ms_ = 0;
|
||||||
|
uint32_t current_rx_message_id_ = 0;
|
||||||
|
std::deque<RNS::Bytes> incoming_packets_;
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,512 @@
|
||||||
|
#include "HostBluezBleInterface.h"
|
||||||
|
#include "SelectedText.h"
|
||||||
|
|
||||||
|
#include <Destination.h>
|
||||||
|
#include <Identity.h>
|
||||||
|
#include <Interface.h>
|
||||||
|
#include <Link.h>
|
||||||
|
#include <Log.h>
|
||||||
|
#include <Packet.h>
|
||||||
|
#include <Reticulum.h>
|
||||||
|
#include <Transport.h>
|
||||||
|
#include <Type.h>
|
||||||
|
#include <Utilities/OS.h>
|
||||||
|
#include <microStore/Adapters/UniversalFileSystem.h>
|
||||||
|
#include <microStore/FileSystem.h>
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
static constexpr const char* APP_NAME = "microreticulum";
|
||||||
|
static constexpr const char* APP_ASPECT = "filetransfer";
|
||||||
|
static constexpr const char* ANNOUNCE_FILTER = "microreticulum.filetransfer";
|
||||||
|
|
||||||
|
#ifndef FILE_TRANSFER_CHUNK_SIZE
|
||||||
|
#define FILE_TRANSFER_CHUNK_SIZE 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FILE_TRANSFER_CHUNK_INTERVAL_MS
|
||||||
|
#define FILE_TRANSFER_CHUNK_INTERVAL_MS 500
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef FILE_TRANSFER_REPEAT_INTERVAL_MS
|
||||||
|
#define FILE_TRANSFER_REPEAT_INTERVAL_MS 10000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HOST_NODE_LABEL
|
||||||
|
#define HOST_NODE_LABEL "Node-JP-CLIENT"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static constexpr size_t TRANSFER_CHUNK_SIZE = FILE_TRANSFER_CHUNK_SIZE;
|
||||||
|
static constexpr uint32_t TRANSFER_CHUNK_INTERVAL_MS = FILE_TRANSFER_CHUNK_INTERVAL_MS;
|
||||||
|
static constexpr uint32_t TRANSFER_REPEAT_INTERVAL_MS = FILE_TRANSFER_REPEAT_INTERVAL_MS;
|
||||||
|
static constexpr uint32_t FNV1A_OFFSET = 2166136261UL;
|
||||||
|
static constexpr uint32_t FNV1A_PRIME = 16777619UL;
|
||||||
|
|
||||||
|
static RNS::Reticulum reticulum({RNS::Type::NONE});
|
||||||
|
static RNS::Interface ble_interface({RNS::Type::NONE});
|
||||||
|
static RNS::Identity local_identity({RNS::Type::NONE});
|
||||||
|
static RNS::Destination inbound_destination({RNS::Type::NONE});
|
||||||
|
static RNS::Destination peer_destination({RNS::Type::NONE});
|
||||||
|
static RNS::Link active_link({RNS::Type::NONE});
|
||||||
|
static RNS::Link pending_link({RNS::Type::NONE});
|
||||||
|
static RNS::Bytes peer_hash;
|
||||||
|
static std::string peer_label;
|
||||||
|
static bool have_peer = false;
|
||||||
|
static bool link_active = false;
|
||||||
|
static bool link_attempted = false;
|
||||||
|
static HostBluezBleInterface* ble_impl = nullptr;
|
||||||
|
static std::string node_label = HOST_NODE_LABEL;
|
||||||
|
static bool running = true;
|
||||||
|
|
||||||
|
struct RxTransferState {
|
||||||
|
bool active = false;
|
||||||
|
std::string sender;
|
||||||
|
std::string file_name;
|
||||||
|
size_t expected_size = 0;
|
||||||
|
uint32_t expected_chunks = 0;
|
||||||
|
uint32_t expected_crc = 0;
|
||||||
|
uint32_t received_chunks = 0;
|
||||||
|
size_t received_size = 0;
|
||||||
|
uint32_t crc = FNV1A_OFFSET;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TxTransferState {
|
||||||
|
bool active = false;
|
||||||
|
bool complete = false;
|
||||||
|
size_t offset = 0;
|
||||||
|
uint32_t sequence = 0;
|
||||||
|
uint32_t total_chunks = 0;
|
||||||
|
uint32_t crc = 0;
|
||||||
|
uint64_t next_start_ms = 0;
|
||||||
|
uint32_t round = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
static RxTransferState rx_transfer;
|
||||||
|
static TxTransferState tx_transfer;
|
||||||
|
|
||||||
|
static uint64_t millis64() {
|
||||||
|
return RNS::Utilities::OS::ltime();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t fnv1a_update(uint32_t crc, uint8_t byte) {
|
||||||
|
crc ^= byte;
|
||||||
|
crc *= FNV1A_PRIME;
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t selected_text_crc() {
|
||||||
|
uint32_t crc = FNV1A_OFFSET;
|
||||||
|
for (size_t i = 0; i < SELECTED_TEXT_SIZE; ++i) {
|
||||||
|
crc = fnv1a_update(crc, read_selected_text_byte(i));
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string hex32(uint32_t value) {
|
||||||
|
char buffer[9];
|
||||||
|
std::snprintf(buffer, sizeof(buffer), "%08lX", (unsigned long)value);
|
||||||
|
return std::string(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string link_id_hex(const RNS::Link& link) {
|
||||||
|
if (!link) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
return link.link_id().toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool should_initiate_link_to(const std::string& label) {
|
||||||
|
return std::strcmp(node_label.c_str(), label.c_str()) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_text_payload(const std::string& payload) {
|
||||||
|
std::fwrite(payload.data(), 1, payload.size(), stdout);
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t find_pipe(const std::string& text, size_t start = 0) {
|
||||||
|
return text.find('|', start);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t parse_u32(const std::string& text) {
|
||||||
|
return (uint32_t)std::strtoul(text.c_str(), nullptr, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t parse_hex32(const std::string& text) {
|
||||||
|
return (uint32_t)std::strtoul(text.c_str(), nullptr, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_file_begin(const std::string& sender,
|
||||||
|
const std::string& file_name,
|
||||||
|
size_t size,
|
||||||
|
uint32_t chunks,
|
||||||
|
uint32_t crc) {
|
||||||
|
rx_transfer.active = true;
|
||||||
|
rx_transfer.sender = sender;
|
||||||
|
rx_transfer.file_name = file_name;
|
||||||
|
rx_transfer.expected_size = size;
|
||||||
|
rx_transfer.expected_chunks = chunks;
|
||||||
|
rx_transfer.expected_crc = crc;
|
||||||
|
rx_transfer.received_chunks = 0;
|
||||||
|
rx_transfer.received_size = 0;
|
||||||
|
rx_transfer.crc = FNV1A_OFFSET;
|
||||||
|
|
||||||
|
std::printf("\nRX FILE BEGIN: from=%s file=%s bytes=%u chunks=%lu crc=%s\n",
|
||||||
|
sender.c_str(), file_name.c_str(), (unsigned)size,
|
||||||
|
(unsigned long)chunks, hex32(crc).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_file_data(const std::string& sender,
|
||||||
|
uint32_t sequence,
|
||||||
|
uint32_t total,
|
||||||
|
const std::string& payload) {
|
||||||
|
if (!rx_transfer.active || rx_transfer.sender != sender) {
|
||||||
|
std::printf("\nRX FILE DATA ignored: sender=%s seq=%lu no active transfer\n",
|
||||||
|
sender.c_str(), (unsigned long)sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (char c : payload) {
|
||||||
|
rx_transfer.crc = fnv1a_update(rx_transfer.crc, (uint8_t)c);
|
||||||
|
}
|
||||||
|
rx_transfer.received_chunks++;
|
||||||
|
rx_transfer.received_size += payload.size();
|
||||||
|
|
||||||
|
(void)sequence;
|
||||||
|
(void)total;
|
||||||
|
print_text_payload(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_file_end(const std::string& sender,
|
||||||
|
const std::string& file_name,
|
||||||
|
size_t size,
|
||||||
|
uint32_t chunks,
|
||||||
|
uint32_t crc) {
|
||||||
|
bool ok = rx_transfer.active &&
|
||||||
|
rx_transfer.sender == sender &&
|
||||||
|
rx_transfer.file_name == file_name &&
|
||||||
|
rx_transfer.expected_size == size &&
|
||||||
|
rx_transfer.expected_chunks == chunks &&
|
||||||
|
rx_transfer.expected_crc == crc &&
|
||||||
|
rx_transfer.received_size == size &&
|
||||||
|
rx_transfer.received_chunks == chunks &&
|
||||||
|
rx_transfer.crc == crc;
|
||||||
|
|
||||||
|
std::printf("\nRX FILE END: from=%s file=%s received=%u/%u chunks=%lu/%lu crc=%s status=%s\n",
|
||||||
|
sender.c_str(), file_name.c_str(),
|
||||||
|
(unsigned)rx_transfer.received_size, (unsigned)size,
|
||||||
|
(unsigned long)rx_transfer.received_chunks, (unsigned long)chunks,
|
||||||
|
hex32(rx_transfer.crc).c_str(), ok ? "OK" : "VERIFY_FAIL");
|
||||||
|
rx_transfer.active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_file_transfer_packet(const std::string& message) {
|
||||||
|
size_t p1 = find_pipe(message);
|
||||||
|
size_t p2 = p1 == std::string::npos ? std::string::npos : find_pipe(message, p1 + 1);
|
||||||
|
if (p1 == std::string::npos || p2 == std::string::npos) {
|
||||||
|
print_text_payload(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string type = message.substr(0, p1);
|
||||||
|
std::string sender = message.substr(p1 + 1, p2 - p1 - 1);
|
||||||
|
|
||||||
|
if (type == "FTB") {
|
||||||
|
size_t p3 = find_pipe(message, p2 + 1);
|
||||||
|
size_t p4 = p3 == std::string::npos ? std::string::npos : find_pipe(message, p3 + 1);
|
||||||
|
size_t p5 = p4 == std::string::npos ? std::string::npos : find_pipe(message, p4 + 1);
|
||||||
|
if (p3 == std::string::npos || p4 == std::string::npos || p5 == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handle_file_begin(sender,
|
||||||
|
message.substr(p2 + 1, p3 - p2 - 1),
|
||||||
|
parse_u32(message.substr(p3 + 1, p4 - p3 - 1)),
|
||||||
|
parse_u32(message.substr(p4 + 1, p5 - p4 - 1)),
|
||||||
|
parse_hex32(message.substr(p5 + 1)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "FTD") {
|
||||||
|
size_t p3 = find_pipe(message, p2 + 1);
|
||||||
|
size_t p4 = p3 == std::string::npos ? std::string::npos : find_pipe(message, p3 + 1);
|
||||||
|
if (p3 == std::string::npos || p4 == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handle_file_data(sender,
|
||||||
|
parse_u32(message.substr(p2 + 1, p3 - p2 - 1)),
|
||||||
|
parse_u32(message.substr(p3 + 1, p4 - p3 - 1)),
|
||||||
|
message.substr(p4 + 1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "FTE") {
|
||||||
|
size_t p3 = find_pipe(message, p2 + 1);
|
||||||
|
size_t p4 = p3 == std::string::npos ? std::string::npos : find_pipe(message, p3 + 1);
|
||||||
|
size_t p5 = p4 == std::string::npos ? std::string::npos : find_pipe(message, p4 + 1);
|
||||||
|
if (p3 == std::string::npos || p4 == std::string::npos || p5 == std::string::npos) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handle_file_end(sender,
|
||||||
|
message.substr(p2 + 1, p3 - p2 - 1),
|
||||||
|
parse_u32(message.substr(p3 + 1, p4 - p3 - 1)),
|
||||||
|
parse_u32(message.substr(p4 + 1, p5 - p4 - 1)),
|
||||||
|
parse_hex32(message.substr(p5 + 1)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
print_text_payload(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_link_packet(const RNS::Bytes& data, const RNS::Packet& packet) {
|
||||||
|
(void)packet;
|
||||||
|
parse_file_transfer_packet(data.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_link_closed(RNS::Link& link) {
|
||||||
|
std::printf("LINK CLOSED: peer=%s link_id=%s\n",
|
||||||
|
peer_label.empty() ? "unknown" : peer_label.c_str(),
|
||||||
|
link_id_hex(link).c_str());
|
||||||
|
active_link = {RNS::Type::NONE};
|
||||||
|
pending_link = {RNS::Type::NONE};
|
||||||
|
link_active = false;
|
||||||
|
link_attempted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_outbound_link_established(RNS::Link& link) {
|
||||||
|
active_link = link;
|
||||||
|
active_link.set_packet_callback(on_link_packet);
|
||||||
|
active_link.set_link_closed_callback(on_link_closed);
|
||||||
|
link_active = true;
|
||||||
|
std::printf("LINK ACTIVE: outbound peer=%s link_id=%s\n",
|
||||||
|
peer_label.empty() ? "unknown" : peer_label.c_str(),
|
||||||
|
link_id_hex(active_link).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_inbound_link_established(RNS::Link& link) {
|
||||||
|
active_link = link;
|
||||||
|
active_link.set_packet_callback(on_link_packet);
|
||||||
|
active_link.set_link_closed_callback(on_link_closed);
|
||||||
|
link_active = true;
|
||||||
|
link_attempted = true;
|
||||||
|
std::printf("LINK ACTIVE: inbound peer=%s link_id=%s\n",
|
||||||
|
peer_label.empty() ? "unknown" : peer_label.c_str(),
|
||||||
|
link_id_hex(active_link).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileAnnounceHandler : public RNS::AnnounceHandler {
|
||||||
|
public:
|
||||||
|
FileAnnounceHandler() : RNS::AnnounceHandler(ANNOUNCE_FILTER) {}
|
||||||
|
|
||||||
|
void received_announce(const RNS::Bytes& destination_hash,
|
||||||
|
const RNS::Identity& announced_identity,
|
||||||
|
const RNS::Bytes& app_data) override {
|
||||||
|
std::string label = app_data ? app_data.toString() : "(no label)";
|
||||||
|
if (label == node_label || !announced_identity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
peer_hash = destination_hash;
|
||||||
|
peer_label = label;
|
||||||
|
peer_destination = RNS::Destination(announced_identity,
|
||||||
|
RNS::Type::Destination::OUT,
|
||||||
|
RNS::Type::Destination::SINGLE,
|
||||||
|
destination_hash);
|
||||||
|
have_peer = true;
|
||||||
|
std::printf("RX ANNOUNCE: label=%s hash=%s\n",
|
||||||
|
peer_label.c_str(), peer_hash.toHex().c_str());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static RNS::HAnnounceHandler announce_handler(new FileAnnounceHandler());
|
||||||
|
|
||||||
|
static void send_announce() {
|
||||||
|
if (!inbound_destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::printf("TX ANNOUNCE: %s\n", node_label.c_str());
|
||||||
|
inbound_destination.announce(RNS::bytesFromString(node_label.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void maybe_open_link() {
|
||||||
|
if (!have_peer || link_active || link_attempted || !peer_destination) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!should_initiate_link_to(peer_label)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("TX LINKREQUEST: opening link to %s\n", peer_label.c_str());
|
||||||
|
pending_link = RNS::Link(peer_destination);
|
||||||
|
pending_link.set_packet_callback(on_link_packet);
|
||||||
|
pending_link.set_link_established_callback(on_outbound_link_established);
|
||||||
|
pending_link.set_link_closed_callback(on_link_closed);
|
||||||
|
link_attempted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_link_message(const std::string& message) {
|
||||||
|
RNS::Packet(active_link, RNS::bytesFromString(message.c_str())).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool send_next_file_packet(uint64_t now) {
|
||||||
|
if (tx_transfer.complete && now < tx_transfer.next_start_ms) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tx_transfer.active) {
|
||||||
|
tx_transfer.active = true;
|
||||||
|
tx_transfer.complete = false;
|
||||||
|
tx_transfer.offset = 0;
|
||||||
|
tx_transfer.sequence = 0;
|
||||||
|
tx_transfer.crc = selected_text_crc();
|
||||||
|
tx_transfer.total_chunks = (SELECTED_TEXT_SIZE + TRANSFER_CHUNK_SIZE - 1) / TRANSFER_CHUNK_SIZE;
|
||||||
|
tx_transfer.round++;
|
||||||
|
|
||||||
|
std::string begin = std::string("FTB|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
|
||||||
|
std::to_string((unsigned)SELECTED_TEXT_SIZE) + "|" +
|
||||||
|
std::to_string((unsigned long)tx_transfer.total_chunks) + "|" +
|
||||||
|
hex32(tx_transfer.crc);
|
||||||
|
std::printf("TX FILE BEGIN: round=%lu file=%s bytes=%u chunks=%lu crc=%s\n",
|
||||||
|
(unsigned long)tx_transfer.round, SELECTED_TEXT_NAME,
|
||||||
|
(unsigned)SELECTED_TEXT_SIZE, (unsigned long)tx_transfer.total_chunks,
|
||||||
|
hex32(tx_transfer.crc).c_str());
|
||||||
|
send_link_message(begin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx_transfer.offset < SELECTED_TEXT_SIZE) {
|
||||||
|
size_t remaining = SELECTED_TEXT_SIZE - tx_transfer.offset;
|
||||||
|
size_t count = remaining < TRANSFER_CHUNK_SIZE ? remaining : TRANSFER_CHUNK_SIZE;
|
||||||
|
std::string payload;
|
||||||
|
payload.reserve(count);
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
payload += (char)read_selected_text_byte(tx_transfer.offset + i);
|
||||||
|
}
|
||||||
|
tx_transfer.sequence++;
|
||||||
|
tx_transfer.offset += count;
|
||||||
|
|
||||||
|
std::string data = std::string("FTD|") + node_label + "|" +
|
||||||
|
std::to_string((unsigned long)tx_transfer.sequence) + "|" +
|
||||||
|
std::to_string((unsigned long)tx_transfer.total_chunks) + "|" + payload;
|
||||||
|
send_link_message(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string end = std::string("FTE|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
|
||||||
|
std::to_string((unsigned)SELECTED_TEXT_SIZE) + "|" +
|
||||||
|
std::to_string((unsigned long)tx_transfer.total_chunks) + "|" +
|
||||||
|
hex32(tx_transfer.crc);
|
||||||
|
std::printf("TX FILE END: round=%lu file=%s bytes=%u chunks=%lu crc=%s next_round_in_ms=%lu\n",
|
||||||
|
(unsigned long)tx_transfer.round, SELECTED_TEXT_NAME,
|
||||||
|
(unsigned)SELECTED_TEXT_SIZE, (unsigned long)tx_transfer.total_chunks,
|
||||||
|
hex32(tx_transfer.crc).c_str(), (unsigned long)TRANSFER_REPEAT_INTERVAL_MS);
|
||||||
|
send_link_message(end);
|
||||||
|
tx_transfer.active = false;
|
||||||
|
tx_transfer.complete = true;
|
||||||
|
tx_transfer.next_start_ms = now + TRANSFER_REPEAT_INTERVAL_MS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setup_reticulum() {
|
||||||
|
microStore::FileSystem filesystem{microStore::Adapters::UniversalFileSystem()};
|
||||||
|
filesystem.init();
|
||||||
|
RNS::Utilities::OS::register_filesystem(filesystem);
|
||||||
|
|
||||||
|
auto impl = std::shared_ptr<RNS::InterfaceImpl>(new HostBluezBleInterface(node_label));
|
||||||
|
ble_impl = static_cast<HostBluezBleInterface*>(impl.get());
|
||||||
|
ble_interface = RNS::Interface(impl);
|
||||||
|
ble_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
|
||||||
|
RNS::Transport::register_interface(ble_interface);
|
||||||
|
ble_interface.start();
|
||||||
|
|
||||||
|
reticulum = RNS::Reticulum();
|
||||||
|
reticulum.transport_enabled(false);
|
||||||
|
reticulum.probe_destination_enabled(false);
|
||||||
|
reticulum.start();
|
||||||
|
|
||||||
|
local_identity = RNS::Identity();
|
||||||
|
inbound_destination = RNS::Destination(local_identity,
|
||||||
|
RNS::Type::Destination::IN,
|
||||||
|
RNS::Type::Destination::SINGLE,
|
||||||
|
APP_NAME,
|
||||||
|
APP_ASPECT);
|
||||||
|
inbound_destination.set_link_established_callback(on_inbound_link_established);
|
||||||
|
inbound_destination.set_proof_strategy(RNS::Type::Destination::PROVE_NONE);
|
||||||
|
|
||||||
|
RNS::Transport::register_announce_handler(announce_handler);
|
||||||
|
|
||||||
|
std::printf("Local SINGLE destination: %s\n", inbound_destination.hash().toHex().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_signal(int signal) {
|
||||||
|
(void)signal;
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::signal(SIGINT, handle_signal);
|
||||||
|
std::signal(SIGTERM, handle_signal);
|
||||||
|
|
||||||
|
RNS::loglevel(RNS::LOG_NOTICE);
|
||||||
|
std::printf("Exercise 306 jp native BLE file transfer console\n");
|
||||||
|
std::printf("Node=%s\n", node_label.c_str());
|
||||||
|
std::printf("Selected file=%s bytes=%u chunk=%u interval_ms=%lu repeat_rest_ms=%lu\n",
|
||||||
|
SELECTED_TEXT_NAME, (unsigned)SELECTED_TEXT_SIZE,
|
||||||
|
(unsigned)TRANSFER_CHUNK_SIZE,
|
||||||
|
(unsigned long)TRANSFER_CHUNK_INTERVAL_MS,
|
||||||
|
(unsigned long)TRANSFER_REPEAT_INTERVAL_MS);
|
||||||
|
|
||||||
|
setup_reticulum();
|
||||||
|
std::printf("microReticulum ready; OLED skipped on host native build\n");
|
||||||
|
|
||||||
|
uint64_t next_announce_ms = 0;
|
||||||
|
uint64_t next_transfer_ms = 0;
|
||||||
|
uint64_t next_wait_log_ms = 0;
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
reticulum.loop();
|
||||||
|
if (ble_impl) {
|
||||||
|
ble_impl->loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t now = millis64();
|
||||||
|
if (ble_impl && !ble_impl->connected()) {
|
||||||
|
if (next_wait_log_ms == 0 || now >= next_wait_log_ms) {
|
||||||
|
next_wait_log_ms = now + 10000;
|
||||||
|
std::printf("BLE %s waiting for peer\n", ble_impl->role_name());
|
||||||
|
}
|
||||||
|
RNS::Utilities::OS::sleep(0.005);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next_announce_ms == 0) {
|
||||||
|
next_announce_ms = now + 1100;
|
||||||
|
}
|
||||||
|
if (!link_active && now >= next_announce_ms) {
|
||||||
|
next_announce_ms = now + 15000;
|
||||||
|
send_announce();
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_open_link();
|
||||||
|
|
||||||
|
if (link_active && next_transfer_ms == 0) {
|
||||||
|
next_transfer_ms = now + (should_initiate_link_to(peer_label) ? 900 : 1200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link_active && now >= next_transfer_ms) {
|
||||||
|
next_transfer_ms = now + TRANSFER_CHUNK_INTERVAL_MS;
|
||||||
|
send_next_file_packet(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
RNS::Utilities::OS::sleep(0.005);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ble_impl) {
|
||||||
|
ble_impl->stop();
|
||||||
|
}
|
||||||
|
std::printf("\nStopped\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue