highly technical
The goal is simple to state and hard to implement: move the Reticulum node closer to the radio hardware, so the T-Beam is no longer merely a modem for a phone, but a Reticulum participant in its own right.

GitHub user torlando-tech designed a Bluetooth protocol for Reticulum. Bluetooth is not easy. A BLE interface has to manage discovery, connection direction, dropped connections, identity, fragmentation, reassembly, and recovery. It is not a simple ON/OFF radio link.
Torlando also built a Python scaffold around that protocol. My goal was to see whether the protocol behavior could be migrated into C++ so it could eventually support a Bluetooth interface for the LilyGO T-Beam SUPREME running Chad Attermann’s microReticulum.
Torlando has also built a Reticulum client application named Columba, described as:
“a simple messaging and voice app for the Reticulum network on Android. Send LXMF messages and make LXST voice calls without relying on the internet, cell towers, or any central servers.”

Chad Attermann’s microReticulum is written in C++. I have compiled microReticulum and run it on a T-Beam. It worked well for my simple test, but the T-Beam’s built-in interface is a tiny screen intended mostly for status messages. My test used predetermined messages; it did not provide a practical human interface for typing, reading, or managing conversations.
The usual approach is to run a Reticulum client on Android, where a person can read and compose messages, while the T-Beam acts as a LoRa modem. That works, but it keeps the Reticulum node conceptually higher up the stack, on the phone side.
What interests me is the opposite architecture: run the Reticulum instance on the T-Beam itself, and let the phone or other human-friendly device connect over Bluetooth as the user interface. In that model, the T-Beam is not merely a modem. It is the Reticulum node. The phone becomes the keyboard, screen, and operator console.
Moving the Reticulum instance down onto the T-Beam creates a more self-contained communications device. If cellular service or the internet is unavailable, the T-Beam still has its LoRa radio, its Reticulum identity, and its mesh-network role.
To make that architecture practical, the Bluetooth protocol cannot remain only in Python. A future T-Beam/microReticulum implementation needs the BLE protocol and session behavior available in C++.
So I set out to migrate the protocol-relevant parts of Torlando’s BLE Reticulum work into a C++ core. The important point is not merely that C++ code was compiled. The milestone is that the BLE protocol behavior was separated from the Python/Linux scaffolding and moved into a C++ library that Python can call for testing.
Python still serves as the Linux test harness and adapter. The protocol/session core is now C++. That is the bridge toward eventually using the same logic inside microReticulum on ESP32-S3 hardware.
Mind you, this is not the type of programming I did at Oracle — far from it. In retirement I have become fascinated with smaller electronic devices, radio, and embedded systems. I used OpenAI’s ChatGPT as a high-level architectural tutor and reviewer, and I used OpenAI Codex inside Visual Studio Code to inspect, modify, build, and test the code on my workstation.
My role was part programmer, part test operator, and part project manager. I maintained the Forgejo repository, moved code between my Intel workstation and two Raspberry Pi Zero 2Ws, ran field tests, preserved logs, and forced the work through a staged validation process.
Over a couple of days, with ChatGPT and Codex assisting, I reached a working C++ protocol/session core based on Torlando’s BLE Reticulum protocol. The major intellectual credit for the protocol belongs to Torlando. My contribution was to help distill that behavior into C++ and prove that it could still move Reticulum traffic in a live test.
For a field test, I used two Raspberry Pi Zero 2Ws. Each ran the Reticulum BLE test harness, explicitly configured to require the C++ session backend and the C++ fragmentation backend. I then had the two devices exchange the complete text of the United States Constitution over Reticulum using BLE.
The point of this test was not literary patriotism, although the Constitution is a convenient piece of public-domain text. The point was to force a non-trivial bilateral transfer through the BLE Reticulum stack and verify that the C++ protocol/session core was actually being used.
Here are the consoles for the two Pis:
Then I generate a report:
Reticulum BLE file transfer analysis
Generated: 2026-05-18 21:22:51 PDT
Input files:
tmp/run17/20260518_1855_zerodev1_Gate2F_BilateralConstitution_90seconds.txt
tmp/run17/20260518_1855_zerodev2_Gate2F_BilateralConstitution_90seconds.txt
Log provenance summary:
20260518_1855_zerodev1_Gate2F_BilateralConstitution_90seconds.txt receiver=zerodev1 date='Mon May 18 18:51:32 PDT 2026' provenance=script command_lines=0 data_lines=602
invoked_script : migration/zerodev1_command_clump_Gate2F_BilateralConstitution_90seconds.sh
invoked_working_dir : /usr/local/src/ble-reticulum
resolved_script : /usr/local/src/ble-reticulum/migration/zerodev1_command_clump_Gate2F_BilateralConstitution_90seconds.sh
20260518_1855_zerodev2_Gate2F_BilateralConstitution_90seconds.txt receiver=zerodev2 date='Mon May 18 06:51:34 PM PDT 2026' provenance=script command_lines=0 data_lines=597
invoked_script : migration/zerodev2_command_clump_Gate2F_BilateralConstitution_90seconds.sh
invoked_working_dir : /usr/local/src/ble-reticulum
resolved_script : /usr/local/src/ble-reticulum/migration/zerodev2_command_clump_Gate2F_BilateralConstitution_90seconds.sh
CPP backend preflight summary:
20260518_1855_zerodev1_Gate2F_BilateralConstitution_90seconds.txt
BLE_RETICULUM_SESSION_BACKEND : cpp
BLE_RETICULUM_FRAGMENTATION_BACKEND : cpp
ble_protocol_core_cpp : /usr/local/src/ble-reticulum/migration/protocol_core/ble_protocol_core_cpp.cpython-313-aarch64-linux-gnu.so
fragmentation backend : cpp
session backend : cpp
CPP backend preflight : OK
BLEInterface backend line : cpp (fragmenter=ble_protocol_core_cpp.BLEFragmenter, reassembler=ble_protocol_core_cpp.BLEReassembler)
20260518_1855_zerodev2_Gate2F_BilateralConstitution_90seconds.txt
BLE_RETICULUM_SESSION_BACKEND : cpp
BLE_RETICULUM_FRAGMENTATION_BACKEND : cpp
ble_protocol_core_cpp : /usr/local/src/ble-reticulum/migration/protocol_core/ble_protocol_core_cpp.cpython-313-aarch64-linux-gnu.so
fragmentation backend : cpp
session backend : cpp
CPP backend preflight : OK
BLEInterface backend line : cpp (fragmenter=ble_protocol_core_cpp.BLEFragmenter, reassembler=ble_protocol_core_cpp.BLEReassembler)
Command provenance:
--- tmp/run17/20260518_1855_zerodev1_Gate2F_BilateralConstitution_90seconds.txt ---
Invoked from terminal capture:
cwd : /usr/local/src/ble-reticulum
script : migration/zerodev1_command_clump_Gate2F_BilateralConstitution_90seconds.sh
Resolved local script file: /usr/local/src/ble-reticulum/migration/zerodev1_command_clump_Gate2F_BilateralConstitution_90seconds.sh
#!/usr/bin/bash
#
#
#
# Gate 2F Life Field Acceptance Bilateral Constitution 90 seconds
# zerodev1 CPP Command clump START
date
cd /usr/local/src/ble-reticulum/
chronyc tracking
chronyc sources -v
echo .
cd migration/protocol_core
python3 setup.py build_ext --inplace
cd ../..
PYTHONPATH=src:migration/protocol_core \
BLE_RETICULUM_SESSION_BACKEND=cpp \
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \
python3 - <<'PY'
import os, sys
print("PYTHON:", sys.executable)
print("PYTHONPATH:", os.environ.get("PYTHONPATH"))
print("BLE_RETICULUM_SESSION_BACKEND:", os.environ.get("BLE_RETICULUM_SESSION_BACKEND"))
print("BLE_RETICULUM_FRAGMENTATION_BACKEND:", os.environ.get("BLE_RETICULUM_FRAGMENTATION_BACKEND"))
import ble_protocol_core_cpp
print("ble_protocol_core_cpp:", ble_protocol_core_cpp.__file__)
from ble_reticulum.BLEFragmentationBackend import BACKEND as FRAG_BACKEND
from ble_reticulum.BLESessionBackend import BACKEND as SESSION_BACKEND
print("fragmentation backend:", FRAG_BACKEND)
print("session backend:", SESSION_BACKEND)
if FRAG_BACKEND != "cpp":
raise SystemExit(f"ERROR: expected fragmentation backend cpp, got {FRAG_BACKEND!r}")
if SESSION_BACKEND != "cpp":
raise SystemExit(f"ERROR: expected session backend cpp, got {SESSION_BACKEND!r}")
print("CPP backend preflight: OK")
PY
echo .
PYTHONPATH=src:migration/protocol_core \
BLE_RETICULUM_SESSION_BACKEND=cpp \
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \
BLE_RETICULUM_FRAGMENTATION_BACKEND_REPORT=1 \
timeout 90 python3 examples/ble_dual_node_echo.py \
--ble-role peripheral \
--message-file /usr/local/src/ble-reticulum/samples/US_Constitution.txt \
--message-chunk-size 900 \
--announce-only-when-disconnected \
--verbosity debug
echo .
chronyc tracking
chronyc sources -v
# zerodev1 Command clump END for Gate 2F Life Field Acceptance Bilateral Constitution 90 seconds
--- tmp/run17/20260518_1855_zerodev2_Gate2F_BilateralConstitution_90seconds.txt ---
Invoked from terminal capture:
cwd : /usr/local/src/ble-reticulum
script : migration/zerodev2_command_clump_Gate2F_BilateralConstitution_90seconds.sh
Resolved local script file: /usr/local/src/ble-reticulum/migration/zerodev2_command_clump_Gate2F_BilateralConstitution_90seconds.sh
#!/usr/bin/bash
#
#
#
# Gate 2F Life Field Acceptance Bilateral Constitution 90 seconds
# zerodev2 CPP Command clump START
date
cd /usr/local/src/ble-reticulum/
chronyc tracking
chronyc sources -v
echo .
cd migration/protocol_core
python3 setup.py build_ext --inplace
cd ../..
PYTHONPATH=src:migration/protocol_core \
BLE_RETICULUM_SESSION_BACKEND=cpp \
BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \
python3 - <<'PY' import os, sys print("PYTHON:", sys.executable) print("PYTHONPATH:", os.environ.get("PYTHONPATH")) print("BLE_RETICULUM_SESSION_BACKEND:", os.environ.get("BLE_RETICULUM_SESSION_BACKEND")) print("BLE_RETICULUM_FRAGMENTATION_BACKEND:", os.environ.get("BLE_RETICULUM_FRAGMENTATION_BACKEND")) import ble_protocol_core_cpp print("ble_protocol_core_cpp:", ble_protocol_core_cpp.__file__) from ble_reticulum.BLEFragmentationBackend import BACKEND as FRAG_BACKEND from ble_reticulum.BLESessionBackend import BACKEND as SESSION_BACKEND print("fragmentation backend:", FRAG_BACKEND) print("session backend:", SESSION_BACKEND) if FRAG_BACKEND != "cpp": raise SystemExit(f"ERROR: expected fragmentation backend cpp, got {FRAG_BACKEND!r}") if SESSION_BACKEND != "cpp": raise SystemExit(f"ERROR: expected session backend cpp, got {SESSION_BACKEND!r}") print("CPP backend preflight: OK") PY echo . PYTHONPATH=src:migration/protocol_core \ BLE_RETICULUM_SESSION_BACKEND=cpp \ BLE_RETICULUM_FRAGMENTATION_BACKEND=cpp \ BLE_RETICULUM_FRAGMENTATION_BACKEND_REPORT=1 \ timeout 90 python3 examples/ble_dual_node_echo.py \ --ble-role both \ --message-file /usr/local/src/ble-reticulum/samples/US_Constitution.txt \ --peer 926e6d3b35b7d5940be7edeb47c41b78 \ --announce-only-when-disconnected \ --verbosity debug echo . chronyc tracking chronyc sources -v # zerodev2 Command clump END Bilateral Constitution 90 seconds Chrony clock notes from logs: 20260518_1855_zerodev1_Gate2F_BilateralConstitution_90seconds.txt System time : 0.000026430 seconds fast of NTP time System time : 0.000025351 seconds fast of NTP time 20260518_1855_zerodev2_Gate2F_BilateralConstitution_90seconds.txt System time : 0.000424481 seconds slow of NTP time System time : 0.000403937 seconds slow of NTP time Declared outbound sends observed in logs: sender=zerodev1 file=US_Constitution.txt chunks= 140 bytes= 44225 chunk_data_bytes=316 sender=zerodev2 file=US_Constitution.txt chunks= 148 bytes= 44225 chunk_data_bytes=n/a Direction: zerodev1->zerodev2
file : US_Constitution.txt
chunks received : 140 of 140
completeness : 100.00%
missing chunks : none
duplicate chunks : none
payload bytes RX : 44225
first chunk RX : 18:51:56.440
last chunk RX : 18:52:32.224
receiver span : 35.784 s
sender span : 14.284 s
payload rate RX span : 1235.9 B/s 9887.1 bit/s
payload rate TX span : 3096.2 B/s 24769.5 bit/s
one-way latency min/median/mean/p95/max/stddev: 246.149 / 11198.795 / 11161.263 / 21055.598 / 21746.446 / 6355.931 ms
receiver inter-chunk gap min/median/mean/p95/max/stddev: 183.000 / 244.000 / 257.439 / 294.000 / 387.000 / 37.358 ms
sender inter-chunk gap min/median/mean/p95/max/stddev: 101.891 / 102.930 / 102.760 / 103.330 / 108.815 / 0.875 ms
Direction: zerodev2->zerodev1
file : US_Constitution.txt
chunks received : 148 of 148
completeness : 100.00%
missing chunks : none
duplicate chunks : none
payload bytes RX : 44225
first chunk RX : 18:51:56.442
last chunk RX : 18:52:30.667
receiver span : 34.225 s
sender span : 15.483 s
payload rate RX span : 1292.2 B/s 10337.5 bit/s
payload rate TX span : 2856.4 B/s 22851.6 bit/s
one-way latency min/median/mean/p95/max/stddev: 255.098 / 9635.558 / 9644.813 / 18185.079 / 18997.584 / 5480.514 ms
receiver inter-chunk gap min/median/mean/p95/max/stddev: 145.000 / 243.000 / 232.823 / 244.000 / 293.000 / 22.165 ms
sender inter-chunk gap min/median/mean/p95/max/stddev: 103.735 / 104.058 / 105.323 / 109.003 / 131.558 / 3.637 ms
Hello/handshake RX records:
zerodev2 -> zerodev1 recv=18:51:56.118 latency= 129.678 ms message='hello'
zerodev1 -> zerodev2 recv=18:51:56.124 latency= 117.157 ms message='hello back'
Caution: one-way latency assumes sender and receiver clocks are synchronized.
Your chronyc tracking output helps bound this error, but it is not a substitute for ACK/round-trip timing.
The preflight output in both consoles confirms that the C++ module was imported and that both the fragmentation backend and the session backend were set to cpp. That matters: this was not a silent fallback to the original Python path.
SQLite
SQLite became my project manager. Each function or method was a row. I tracked whether it was protocol core, Python glue, platform code, or test scaffolding; then I advanced selected rows through phases such as design, native testing, Python binding, equivalence testing, optional integration, and field acceptance. The schema worked, but I later realized I should have made status changes append-only so the full development progression could be reconstructed.
Rust
Rust is tempting here because the BLE session manager is fundamentally a state-ownership problem. But the immediate target is microReticulum on ESP32-S3, and that ecosystem is already C++. So the practical path is to stabilize the protocol/session model in C++ first, while preserving enough tests and documentation that a future Rust implementation would not be a translation of Python, but a clean implementation of a known protocol model.
Conclusion
This is not yet a finished BLE interface for microReticulum on the T-Beam. But it is an important bridge: Torlando’s Python BLE Reticulum protocol behavior has now been partially distilled into a field-tested C++ core. The next challenge is adapting that C++ session layer to microReticulum’s interface model and the ESP32-S3 Bluetooth stack.

Leave a Reply