build for Intel jp and low grade Bluetooth thereon works and pairs with T-Beam

This commit is contained in:
John Poole 2026-05-21 14:27:58 -07:00
commit 0ca0ec605a
8 changed files with 1377 additions and 19 deletions

View file

@ -0,0 +1,2 @@
transport_identity

View file

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

View file

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

View file

@ -3,7 +3,7 @@
[platformio]
default_envs = tbeam_if
[env]
[tbeam_base]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
@ -14,6 +14,10 @@ extra_scripts = pre:scripts/embed_text.py
custom_text_source = texts/If.txt
lib_extra_dirs =
../../lib
build_src_filter =
+<*>
-<host_jp_main.cpp>
-<HostBluezBleInterface.cpp>
build_flags =
-Wall
@ -40,77 +44,116 @@ lib_deps =
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
[env:tbeam_if]
extends = env
extends = tbeam_base
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_full]
extends = env
extends = tbeam_base
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_constitution]
extends = env
extends = tbeam_base
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_pi_zero_profile]
extends = env
extends = tbeam_base
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_if_full_pi_zero_profile]
extends = env
extends = tbeam_base
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_constitution_pi_zero_profile]
extends = env
extends = tbeam_base
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
${tbeam_base.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[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]
extends = env
extends = tbeam_base
upload_port = /dev/ttytAMY
monitor_port = /dev/ttytAMY
[env:bob]
extends = env
extends = tbeam_base
upload_port = /dev/ttytBOB
monitor_port = /dev/ttytBOB
[env:cy]
extends = env
extends = tbeam_base
upload_port = /dev/ttytCY
monitor_port = /dev/ttytCY
[env:dan]
extends = env
extends = tbeam_base
upload_port = /dev/ttytDAN
monitor_port = /dev/ttytDAN
[env:ed]
extends = env
extends = tbeam_base
upload_port = /dev/ttytED
monitor_port = /dev/ttytED

View file

@ -20,8 +20,20 @@ symbol_name = source_path.name.replace("\\", "/")
lines = [
"#pragma once",
"",
"#include <stddef.h>",
"#include <stdint.h>",
"",
"#if defined(ARDUINO)",
"#include <Arduino.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 size_t SELECTED_TEXT_SIZE = {len(data)};",

View file

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

View file

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

View file

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