this role reversal for Intel/jp works, but further work needed to make Intel/jp ambidexterous
This commit is contained in:
parent
0ca0ec605a
commit
cbc3553436
9 changed files with 523 additions and 21 deletions
|
|
@ -22,6 +22,7 @@ Exercise 306 uses the same payload files as the Pi Zero BLE Reticulum tests:
|
||||||
texts/If.txt 195 bytes
|
texts/If.txt 195 bytes
|
||||||
texts/If_full.txt 1583 bytes
|
texts/If_full.txt 1583 bytes
|
||||||
texts/US_Constitution.txt 44225 bytes
|
texts/US_Constitution.txt 44225 bytes
|
||||||
|
texts/little_boy_blue.txt 942 bytes
|
||||||
```
|
```
|
||||||
|
|
||||||
The selected file is compiled into the firmware. The transfer code does not care which file is selected; `platformio.ini` chooses the source text through `custom_text_source`, and `scripts/embed_text.py` generates `SelectedText.h` in the build directory before compilation.
|
The selected file is compiled into the firmware. The transfer code does not care which file is selected; `platformio.ini` chooses the source text through `custom_text_source`, and `scripts/embed_text.py` generates `SelectedText.h` in the build directory before compilation.
|
||||||
|
|
@ -77,9 +78,22 @@ tbeam_if_full_pi_zero_profile
|
||||||
tbeam_constitution_pi_zero_profile
|
tbeam_constitution_pi_zero_profile
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build Once, Upload Twice
|
Host-native environment:
|
||||||
|
|
||||||
Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards.
|
```text
|
||||||
|
jp_native
|
||||||
|
jp_native_peripheral
|
||||||
|
```
|
||||||
|
|
||||||
|
`jp_native` builds a Linux console program instead of ESP32 firmware. It uses the host Bluetooth adapter through BlueZ D-Bus, skips the OLED path, and prints received text to stdout. The current jp payload is `texts/little_boy_blue.txt`.
|
||||||
|
|
||||||
|
`jp_native_peripheral` is the first Linux peripheral/server scaffold. It builds a separate binary that checks for BlueZ `GattManager1` and `LEAdvertisingManager1` support on the host adapter. It does not yet register the full Exercise 306 GATT service or accept a T-Beam connection.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### ESP32
|
||||||
|
|
||||||
|
Build the ESP32/T-Beam firmware with one of the `tbeam_*` environments. Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards.
|
||||||
|
|
||||||
Build the short If sample:
|
Build the short If sample:
|
||||||
|
|
||||||
|
|
@ -132,6 +146,88 @@ pio device monitor -p /dev/ttytDAN -b 115200
|
||||||
pio device monitor -p /dev/ttytBOB -b 115200
|
pio device monitor -p /dev/ttytBOB -b 115200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Intel x86_64
|
||||||
|
|
||||||
|
Build the jp Linux host binary with the `jp_native` environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
cd /usr/local/src/microreticulum/microReticulumTbeam
|
||||||
|
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting executable is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program
|
||||||
|
```
|
||||||
|
|
||||||
|
The host binary is a BlueZ BLE central. It expects the T-Beam to advertise the Exercise 306 service, then receives file-transfer text on the console. Because it uses system D-Bus and the Bluetooth adapter, sandboxed runs may require approval.
|
||||||
|
|
||||||
|
Build the jp peripheral capability-check binary with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
cd /usr/local/src/microreticulum/microReticulumTbeam
|
||||||
|
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native_peripheral
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting executable is:
|
||||||
|
|
||||||
|
```text
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_peripheral/program
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it from the repository root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native_peripheral/program
|
||||||
|
```
|
||||||
|
|
||||||
|
This build is expected to report whether the current BlueZ adapter exposes the GATT server and LE advertising managers needed for true Linux peripheral mode.
|
||||||
|
|
||||||
|
### AMD64
|
||||||
|
|
||||||
|
AMD64 is the same 64-bit x86 Linux target class as Intel x86_64 for this PlatformIO `native` build. On eos, build the same `jp_native` environment on that machine:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
cd /usr/local/src/microreticulum/microReticulumTbeam
|
||||||
|
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native
|
||||||
|
```
|
||||||
|
|
||||||
|
The output path is the same relative path:
|
||||||
|
|
||||||
|
```text
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not copy the jp-built executable to ARM machines. Rebuild on the target architecture unless a cross-compile environment is added.
|
||||||
|
|
||||||
|
### ARM64
|
||||||
|
|
||||||
|
For a 64-bit Raspberry Pi OS on a Pi Zero 2W or Pi 4B, use the same `jp_native` environment and build directly on the Pi:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/jlpoole/rnsenv/bin/activate
|
||||||
|
cd /usr/local/src/microreticulum/microReticulumTbeam
|
||||||
|
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e jp_native
|
||||||
|
```
|
||||||
|
|
||||||
|
PlatformIO `native` produces a binary for the machine doing the build, so an ARM64 Pi build produces an ARM64 executable at:
|
||||||
|
|
||||||
|
```text
|
||||||
|
exercises/306_microReticulum_ble_file_transfer_oled/.pio/build/jp_native/program
|
||||||
|
```
|
||||||
|
|
||||||
|
The Pi must have BlueZ, GLib/GIO development headers, and a BLE adapter that supports LE central mode. If the Pi is running a 32-bit OS, the result is a 32-bit ARM binary, not ARM64.
|
||||||
|
|
||||||
## Expected Output
|
## Expected Output
|
||||||
|
|
||||||
Once the Link is active, both nodes start sending:
|
Once the Link is active, both nodes start sending:
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ build_src_filter =
|
||||||
+<*>
|
+<*>
|
||||||
-<host_jp_main.cpp>
|
-<host_jp_main.cpp>
|
||||||
-<HostBluezBleInterface.cpp>
|
-<HostBluezBleInterface.cpp>
|
||||||
|
-<HostBluezPeripheralInterface.cpp>
|
||||||
|
|
||||||
build_flags =
|
build_flags =
|
||||||
-Wall
|
-Wall
|
||||||
|
|
@ -98,7 +99,7 @@ extends = tbeam_base
|
||||||
platform = native
|
platform = native
|
||||||
build_type = debug
|
build_type = debug
|
||||||
extra_scripts = pre:scripts/embed_text.py
|
extra_scripts = pre:scripts/embed_text.py
|
||||||
custom_text_source = texts/If_full.txt
|
custom_text_source = texts/little_boy_blue.txt
|
||||||
build_unflags =
|
build_unflags =
|
||||||
-std=gnu++11
|
-std=gnu++11
|
||||||
build_flags =
|
build_flags =
|
||||||
|
|
@ -133,6 +134,46 @@ lib_deps =
|
||||||
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
|
microReticulum=symlink:///usr/local/src/microreticulum/microReticulum
|
||||||
lib_compat_mode = off
|
lib_compat_mode = off
|
||||||
|
|
||||||
|
[env:jp_native_peripheral]
|
||||||
|
platform = native
|
||||||
|
build_type = debug
|
||||||
|
extra_scripts = pre:scripts/embed_text.py
|
||||||
|
custom_text_source = texts/little_boy_blue.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 HOST_BLE_PERIPHERAL
|
||||||
|
-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-PERIPHERAL\"
|
||||||
|
!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>
|
||||||
|
+<HostBluezPeripheralInterface.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 = tbeam_base
|
extends = tbeam_base
|
||||||
upload_port = /dev/ttytAMY
|
upload_port = /dev/ttytAMY
|
||||||
|
|
|
||||||
|
|
@ -286,6 +286,10 @@ bool HostBluezBleInterface::connect_device(const std::string& device_path) {
|
||||||
}
|
}
|
||||||
g_variant_unref(result);
|
g_variant_unref(result);
|
||||||
device_path_ = device_path;
|
device_path_ = device_path;
|
||||||
|
device_subscription_ = g_dbus_connection_signal_subscribe(
|
||||||
|
bus_, BLUEZ_BUS, "org.freedesktop.DBus.Properties", "PropertiesChanged",
|
||||||
|
device_path_.c_str(), "org.bluez.Device1",
|
||||||
|
G_DBUS_SIGNAL_FLAGS_NONE, device_properties_changed, this, nullptr);
|
||||||
std::printf("BLE linux-central: connected to %s\n", device_path_.c_str());
|
std::printf("BLE linux-central: connected to %s\n", device_path_.c_str());
|
||||||
|
|
||||||
uint64_t deadline = now_ms() + 10000;
|
uint64_t deadline = now_ms() + 10000;
|
||||||
|
|
@ -315,9 +319,11 @@ bool HostBluezBleInterface::connect_device(const std::string& device_path) {
|
||||||
size_t h2 = hasher(node_label_ + ":exercise306");
|
size_t h2 = hasher(node_label_ + ":exercise306");
|
||||||
std::memcpy(identity, &h1, std::min(sizeof(h1), sizeof(identity)));
|
std::memcpy(identity, &h1, std::min(sizeof(h1), sizeof(identity)));
|
||||||
std::memcpy(identity + 8, &h2, std::min(sizeof(h2), sizeof(identity) - 8));
|
std::memcpy(identity + 8, &h2, std::min(sizeof(h2), sizeof(identity) - 8));
|
||||||
write_characteristic(rx_char_path_, identity, sizeof(identity), true);
|
|
||||||
|
|
||||||
connected_ = true;
|
connected_ = true;
|
||||||
|
if (!write_characteristic(rx_char_path_, identity, sizeof(identity), true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::printf("BLE linux-central: notifications active; identity handshake sent\n");
|
std::printf("BLE linux-central: notifications active; identity handshake sent\n");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -408,7 +414,9 @@ bool HostBluezBleInterface::write_characteristic(const std::string& path,
|
||||||
g_variant_new("(aya{sv})", &bytes, &options), nullptr,
|
g_variant_new("(aya{sv})", &bytes, &options), nullptr,
|
||||||
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
std::string message = error && error->message ? error->message : "unknown write failure";
|
||||||
log_error("BLE linux-central: WriteValue failed", error);
|
log_error("BLE linux-central: WriteValue failed", error);
|
||||||
|
mark_disconnected(message.c_str());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
g_variant_unref(result);
|
g_variant_unref(result);
|
||||||
|
|
@ -474,15 +482,17 @@ void HostBluezBleInterface::send_outgoing(const RNS::Bytes& data) {
|
||||||
put_u32_be(fragment + 6, msg_id);
|
put_u32_be(fragment + 6, msg_id);
|
||||||
put_u32_be(fragment + 10, (uint32_t)data.size());
|
put_u32_be(fragment + 10, (uint32_t)data.size());
|
||||||
std::memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
|
std::memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
|
||||||
send_fragment(fragment, FRAG_HEADER_SIZE + chunk);
|
if (!send_fragment(fragment, FRAG_HEADER_SIZE + chunk)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
RNS::Utilities::OS::sleep(0.008);
|
RNS::Utilities::OS::sleep(0.008);
|
||||||
}
|
}
|
||||||
|
|
||||||
InterfaceImpl::handle_outgoing(data);
|
InterfaceImpl::handle_outgoing(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostBluezBleInterface::send_fragment(const uint8_t* data, size_t len) {
|
bool HostBluezBleInterface::send_fragment(const uint8_t* data, size_t len) {
|
||||||
write_characteristic(rx_char_path_, data, len, true);
|
return write_characteristic(rx_char_path_, data, len, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostBluezBleInterface::properties_changed(GDBusConnection* connection,
|
void HostBluezBleInterface::properties_changed(GDBusConnection* connection,
|
||||||
|
|
@ -520,6 +530,36 @@ void HostBluezBleInterface::properties_changed(GDBusConnection* connection,
|
||||||
g_variant_unref(invalidated);
|
g_variant_unref(invalidated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::device_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->device_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.Device1") == 0) {
|
||||||
|
gboolean connected = TRUE;
|
||||||
|
if (g_variant_lookup(changed, "Connected", "b", &connected) && !connected) {
|
||||||
|
self->mark_disconnected("device disconnected");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_variant_unref(changed);
|
||||||
|
g_variant_unref(invalidated);
|
||||||
|
}
|
||||||
|
|
||||||
void HostBluezBleInterface::handle_fragment(const uint8_t* data, size_t len) {
|
void HostBluezBleInterface::handle_fragment(const uint8_t* data, size_t len) {
|
||||||
if (len < FRAG_HEADER_SIZE) {
|
if (len < FRAG_HEADER_SIZE) {
|
||||||
std::fprintf(stderr, "BLE linux-central: fragment too short len=%zu\n", len);
|
std::fprintf(stderr, "BLE linux-central: fragment too short len=%zu\n", len);
|
||||||
|
|
@ -598,15 +638,40 @@ void HostBluezBleInterface::reset_reassembly() {
|
||||||
current_rx_message_id_ = 0;
|
current_rx_message_id_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void HostBluezBleInterface::disconnect_peer() {
|
void HostBluezBleInterface::mark_disconnected(const char* reason) {
|
||||||
|
bool was_connected = connected_ || !device_path_.empty() || !tx_char_path_.empty() || !rx_char_path_.empty();
|
||||||
connected_ = false;
|
connected_ = false;
|
||||||
if (notify_subscription_) {
|
if (bus_ && notify_subscription_) {
|
||||||
g_dbus_connection_signal_unsubscribe(bus_, notify_subscription_);
|
g_dbus_connection_signal_unsubscribe(bus_, notify_subscription_);
|
||||||
notify_subscription_ = 0;
|
notify_subscription_ = 0;
|
||||||
}
|
}
|
||||||
if (!device_path_.empty()) {
|
if (bus_ && device_subscription_) {
|
||||||
|
g_dbus_connection_signal_unsubscribe(bus_, device_subscription_);
|
||||||
|
device_subscription_ = 0;
|
||||||
|
}
|
||||||
|
device_path_.clear();
|
||||||
|
tx_char_path_.clear();
|
||||||
|
rx_char_path_.clear();
|
||||||
|
identity_char_path_.clear();
|
||||||
|
reset_reassembly();
|
||||||
|
next_scan_ms_ = now_ms() + 1000;
|
||||||
|
if (online_) {
|
||||||
|
if (was_connected) {
|
||||||
|
std::printf("BLE linux-central: disconnected; %s; returning to scan\n", reason ? reason : "link lost");
|
||||||
|
}
|
||||||
|
start_discovery();
|
||||||
|
} else if (was_connected) {
|
||||||
|
std::printf("BLE linux-central: disconnected; %s\n", reason ? reason : "link lost");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezBleInterface::disconnect_peer() {
|
||||||
|
std::string path = device_path_;
|
||||||
|
bool should_disconnect = connected_ && !path.empty();
|
||||||
|
mark_disconnected("local stop");
|
||||||
|
if (should_disconnect && bus_) {
|
||||||
GError* error = nullptr;
|
GError* error = nullptr;
|
||||||
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, device_path_.c_str(),
|
GVariant* result = g_dbus_connection_call_sync(bus_, BLUEZ_BUS, path.c_str(),
|
||||||
"org.bluez.Device1", "Disconnect",
|
"org.bluez.Device1", "Disconnect",
|
||||||
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
|
nullptr, nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
|
||||||
nullptr, &error);
|
nullptr, &error);
|
||||||
|
|
@ -616,9 +681,4 @@ void HostBluezBleInterface::disconnect_peer() {
|
||||||
g_variant_unref(result);
|
g_variant_unref(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device_path_.clear();
|
|
||||||
tx_char_path_.clear();
|
|
||||||
rx_char_path_.clear();
|
|
||||||
identity_char_path_.clear();
|
|
||||||
reset_reassembly();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,12 @@ private:
|
||||||
bool discover_characteristics();
|
bool discover_characteristics();
|
||||||
bool start_notify();
|
bool start_notify();
|
||||||
bool write_characteristic(const std::string& path, const uint8_t* data, size_t len, bool with_response);
|
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);
|
bool send_fragment(const uint8_t* data, size_t len);
|
||||||
void handle_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);
|
void enqueue_packet(const RNS::Bytes& packet);
|
||||||
bool dequeue_packet(RNS::Bytes& packet);
|
bool dequeue_packet(RNS::Bytes& packet);
|
||||||
void reset_reassembly();
|
void reset_reassembly();
|
||||||
|
void mark_disconnected(const char* reason);
|
||||||
void disconnect_peer();
|
void disconnect_peer();
|
||||||
|
|
||||||
static void properties_changed(GDBusConnection* connection,
|
static void properties_changed(GDBusConnection* connection,
|
||||||
|
|
@ -49,6 +50,14 @@ private:
|
||||||
GVariant* parameters,
|
GVariant* parameters,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
|
||||||
|
static void device_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* BLUEZ_BUS = "org.bluez";
|
||||||
static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3";
|
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* TX_UUID = "37145b00-442d-4a94-917f-8f42c5da28e4";
|
||||||
|
|
@ -74,6 +83,7 @@ private:
|
||||||
std::string rx_char_path_;
|
std::string rx_char_path_;
|
||||||
std::string identity_char_path_;
|
std::string identity_char_path_;
|
||||||
guint notify_subscription_ = 0;
|
guint notify_subscription_ = 0;
|
||||||
|
guint device_subscription_ = 0;
|
||||||
bool online_ = false;
|
bool online_ = false;
|
||||||
bool connected_ = false;
|
bool connected_ = false;
|
||||||
bool discovering_ = false;
|
bool discovering_ = false;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
#include "HostBluezPeripheralInterface.h"
|
||||||
|
|
||||||
|
#include <Utilities/OS.h>
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
using namespace RNS;
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HostBluezPeripheralInterface::HostBluezPeripheralInterface(const std::string& node_label,
|
||||||
|
const char* name)
|
||||||
|
: InterfaceImpl(name), node_label_(node_label) {
|
||||||
|
_IN = true;
|
||||||
|
_OUT = true;
|
||||||
|
_bitrate = 1000000;
|
||||||
|
_HW_MTU = 168;
|
||||||
|
}
|
||||||
|
|
||||||
|
HostBluezPeripheralInterface::~HostBluezPeripheralInterface() {
|
||||||
|
stop();
|
||||||
|
if (bus_) {
|
||||||
|
g_object_unref(bus_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezPeripheralInterface::start() {
|
||||||
|
if (online_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!connect_bus() || !find_adapter()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
online_ = true;
|
||||||
|
_online = true;
|
||||||
|
std::printf("BLE linux-peripheral: adapter=%s label=%s service=%s\n",
|
||||||
|
adapter_path_.c_str(), node_label_.c_str(), SERVICE_UUID);
|
||||||
|
|
||||||
|
if (!adapter_supports_peripheral()) {
|
||||||
|
std::fprintf(stderr,
|
||||||
|
"BLE linux-peripheral: adapter/BlueZ is missing GattManager1 or LEAdvertisingManager1; peripheral mode cannot run on this adapter as configured\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf("BLE linux-peripheral: BlueZ reports GATT server and LE advertising managers are present\n");
|
||||||
|
std::printf("BLE linux-peripheral: GATT object registration scaffold builds; runtime service registration is the next step\n");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezPeripheralInterface::stop() {
|
||||||
|
online_ = false;
|
||||||
|
connected_ = false;
|
||||||
|
_online = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezPeripheralInterface::connect_bus() {
|
||||||
|
GError* error = nullptr;
|
||||||
|
bus_ = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, &error);
|
||||||
|
if (!bus_) {
|
||||||
|
log_error("BLE linux-peripheral: cannot connect to system D-Bus", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezPeripheralInterface::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-peripheral: 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)) {
|
||||||
|
GVariant* adapter = g_variant_lookup_value(ifaces, "org.bluez.Adapter1", G_VARIANT_TYPE("a{sv}"));
|
||||||
|
if (adapter) {
|
||||||
|
adapter_path_ = object_path;
|
||||||
|
g_variant_unref(adapter);
|
||||||
|
GVariant* gatt_manager = g_variant_lookup_value(ifaces, "org.bluez.GattManager1", G_VARIANT_TYPE("a{sv}"));
|
||||||
|
GVariant* advertising_manager = g_variant_lookup_value(ifaces, "org.bluez.LEAdvertisingManager1", G_VARIANT_TYPE("a{sv}"));
|
||||||
|
has_gatt_manager_ = gatt_manager != nullptr;
|
||||||
|
has_advertising_manager_ = advertising_manager != nullptr;
|
||||||
|
if (gatt_manager) {
|
||||||
|
g_variant_unref(gatt_manager);
|
||||||
|
}
|
||||||
|
if (advertising_manager) {
|
||||||
|
g_variant_unref(advertising_manager);
|
||||||
|
}
|
||||||
|
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-peripheral: no BlueZ adapter found\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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-peripheral: could not power adapter", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
g_variant_unref(powered);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezPeripheralInterface::adapter_supports_peripheral() const {
|
||||||
|
return has_gatt_manager_ && has_advertising_manager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezPeripheralInterface::loop() {
|
||||||
|
if (!online_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (g_main_context_iteration(nullptr, FALSE)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
RNS::Bytes packet({RNS::Type::NONE});
|
||||||
|
while (dequeue_packet(packet)) {
|
||||||
|
InterfaceImpl::handle_incoming(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezPeripheralInterface::send_outgoing(const RNS::Bytes& data) {
|
||||||
|
if (!online_ || !connected_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Full TX notify support depends on BlueZ GATT characteristic object
|
||||||
|
// registration. This scaffold intentionally compiles separately from the
|
||||||
|
// proven central path so adapter capability can be tested first.
|
||||||
|
InterfaceImpl::handle_outgoing(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HostBluezPeripheralInterface::enqueue_packet(const RNS::Bytes& packet) {
|
||||||
|
incoming_packets_.push_back(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HostBluezPeripheralInterface::dequeue_packet(RNS::Bytes& packet) {
|
||||||
|
if (incoming_packets_.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
packet = incoming_packets_.front();
|
||||||
|
incoming_packets_.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Bytes.h>
|
||||||
|
#include <Interface.h>
|
||||||
|
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class HostBluezPeripheralInterface : public RNS::InterfaceImpl {
|
||||||
|
public:
|
||||||
|
explicit HostBluezPeripheralInterface(const std::string& node_label,
|
||||||
|
const char* name = "HostBluezPeripheral");
|
||||||
|
~HostBluezPeripheralInterface() override;
|
||||||
|
|
||||||
|
bool start() override;
|
||||||
|
void stop() override;
|
||||||
|
void loop() override;
|
||||||
|
|
||||||
|
bool connected() const { return connected_; }
|
||||||
|
const char* role_name() const { return "linux-peripheral"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void send_outgoing(const RNS::Bytes& data) override;
|
||||||
|
|
||||||
|
bool connect_bus();
|
||||||
|
bool find_adapter();
|
||||||
|
bool adapter_supports_peripheral() const;
|
||||||
|
void enqueue_packet(const RNS::Bytes& packet);
|
||||||
|
bool dequeue_packet(RNS::Bytes& packet);
|
||||||
|
|
||||||
|
static constexpr const char* BLUEZ_BUS = "org.bluez";
|
||||||
|
static constexpr const char* SERVICE_UUID = "37145b00-442d-4a94-917f-8f42c5da28e3";
|
||||||
|
|
||||||
|
std::string node_label_;
|
||||||
|
GDBusConnection* bus_ = nullptr;
|
||||||
|
std::string adapter_path_;
|
||||||
|
bool online_ = false;
|
||||||
|
bool connected_ = false;
|
||||||
|
bool has_gatt_manager_ = false;
|
||||||
|
bool has_advertising_manager_ = false;
|
||||||
|
std::deque<RNS::Bytes> incoming_packets_;
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,10 @@
|
||||||
|
#if defined(HOST_BLE_PERIPHERAL)
|
||||||
|
#include "HostBluezPeripheralInterface.h"
|
||||||
|
using HostBleInterface = HostBluezPeripheralInterface;
|
||||||
|
#else
|
||||||
#include "HostBluezBleInterface.h"
|
#include "HostBluezBleInterface.h"
|
||||||
|
using HostBleInterface = HostBluezBleInterface;
|
||||||
|
#endif
|
||||||
#include "SelectedText.h"
|
#include "SelectedText.h"
|
||||||
|
|
||||||
#include <Destination.h>
|
#include <Destination.h>
|
||||||
|
|
@ -58,7 +64,7 @@ static std::string peer_label;
|
||||||
static bool have_peer = false;
|
static bool have_peer = false;
|
||||||
static bool link_active = false;
|
static bool link_active = false;
|
||||||
static bool link_attempted = false;
|
static bool link_attempted = false;
|
||||||
static HostBluezBleInterface* ble_impl = nullptr;
|
static HostBleInterface* ble_impl = nullptr;
|
||||||
static std::string node_label = HOST_NODE_LABEL;
|
static std::string node_label = HOST_NODE_LABEL;
|
||||||
static bool running = true;
|
static bool running = true;
|
||||||
|
|
||||||
|
|
@ -415,8 +421,8 @@ static void setup_reticulum() {
|
||||||
filesystem.init();
|
filesystem.init();
|
||||||
RNS::Utilities::OS::register_filesystem(filesystem);
|
RNS::Utilities::OS::register_filesystem(filesystem);
|
||||||
|
|
||||||
auto impl = std::shared_ptr<RNS::InterfaceImpl>(new HostBluezBleInterface(node_label));
|
auto impl = std::shared_ptr<RNS::InterfaceImpl>(new HostBleInterface(node_label));
|
||||||
ble_impl = static_cast<HostBluezBleInterface*>(impl.get());
|
ble_impl = static_cast<HostBleInterface*>(impl.get());
|
||||||
ble_interface = RNS::Interface(impl);
|
ble_interface = RNS::Interface(impl);
|
||||||
ble_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
|
ble_interface.mode(RNS::Type::Interface::MODE_GATEWAY);
|
||||||
RNS::Transport::register_interface(ble_interface);
|
RNS::Transport::register_interface(ble_interface);
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue