306 works, stress tested range between units and learned reconnect logic is needed. Create Exercise 307 for connection resiliency: to be scoped out

This commit is contained in:
John Poole 2026-05-20 20:23:42 -07:00
commit de75f7d865
22 changed files with 5136 additions and 0 deletions

View file

@ -0,0 +1,189 @@
# Exercise 306: microReticulum BLE file transfer with OLED display
This exercise builds on Exercise 305's equal-peer BLE file transfer and adds the shared `lib/tbeam_display` OLED service. Both boards run the same dual-role BLE interface, form a Reticulum Link, and then send the selected text file across that Link at the same time.
The OLED display is intentionally plain: incoming `FTD` file data is rendered as scrolling text only. File begin/end metadata, checksums, link state, and debug records remain on the serial monitor but are not shown on the OLED.
The file transfer protocol is intentionally small and visible on the serial console:
```text
FTB -> file begin, with file name, byte count, chunk count, and checksum
FTD -> numbered file data chunk
FTE -> file end, with verification metadata repeated
```
Each receiver checks byte count, chunk count, and FNV-1a checksum. After a sender completes a transfer, it rests for 10 seconds and then starts the same selected file again.
## Sample Set
Exercise 306 uses the same payload files as the Pi Zero BLE Reticulum tests:
```text
texts/If.txt 195 bytes
texts/If_full.txt 1583 bytes
texts/US_Constitution.txt 44225 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.
## Transfer Profiles
The transfer pressure is selected in `platformio.ini` with build flags:
```ini
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
```
`FILE_TRANSFER_CHUNK_SIZE` is the number of text bytes placed in each application-level `FTD` message before microReticulum wraps and encrypts it as a Link packet. Larger chunks reduce the number of packets needed for a file, but each encrypted packet becomes larger. If it grows beyond what the ESP32 BLE transport can reliably carry under simultaneous two-way traffic, Reticulum may log Link decrypt/HMAC failures because ciphertext arrived damaged or incomplete.
`FILE_TRANSFER_CHUNK_INTERVAL_MS` is the delay between application-level file chunks. Smaller intervals increase throughput, but also increase BLE write/notify pressure. With both nodes transmitting at the same time, too short a cadence can overflow buffers or expose ordering/loss issues in the current ESP32 BLE transport.
The conservative bring-up profile uses:
```text
32 byte chunks, 500 ms between chunks
```
The Pi-Zero-comparison profile uses:
```text
300 byte chunks, 100 ms between chunks
```
That is the apples-to-apples starting point for the previous Zero-to-Zero tests. Those commands requested `--message-chunk-size 900`, but the Python sender intentionally applied an internal board/Link-budget cap before sending. The run17 report for the Constitution transfer shows effective chunk data around 300 to 316 bytes, not 900 bytes, with roughly 100 ms sender pacing.
`VERIFY_FAIL` means the file protocol received an incomplete or corrupted transfer. A Reticulum Link HMAC/decryption error means corruption happened earlier, before the file protocol could parse the packet.
## Priority
See Exercise 304_microReticulum_ble_dual_role_ping_pong README.md for explanation of "deterministic tie-breaker" of the role of client and server based on the ESP32 MAC.
## Environments
Conservative ESP32 bring-up environments:
```text
tbeam_if
tbeam_if_full
tbeam_constitution
```
Pi-Zero-comparison environments:
```text
tbeam_if_pi_zero_profile
tbeam_if_full_pi_zero_profile
tbeam_constitution_pi_zero_profile
```
## Build Once, Upload Twice
Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards.
Build the short If sample:
```bash
source /home/jlpoole/rnsenv/bin/activate
cd /usr/local/src/microreticulum/microReticulumTbeam
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e tbeam_if
```
Build the Pi-Zero-profile Constitution sample:
```bash
source /home/jlpoole/rnsenv/bin/activate
cd /usr/local/src/microreticulum/microReticulumTbeam
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e tbeam_constitution_pi_zero_profile
```
After the build succeeds, upload the same environment to both boards. These commands may be run one after the other:
```bash
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e tbeam_if -t upload --upload-port /dev/ttytDAN
pio run -d exercises/306_microReticulum_ble_file_transfer_oled -e tbeam_if -t upload --upload-port /dev/ttytBOB
```
Use the same `-e` value in upload commands that you used for the build.
For strict parallel uploads, use `esptool.py` directly against the already-built artifacts. This avoids two concurrent `pio run` processes touching the same `.pio` build directory:
```bash
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/306_microReticulum_ble_file_transfer_oled
esptool.py --chip esp32s3 --port /dev/ttytDAN --baud 460800 write_flash -z \
0x0000 .pio/build/tbeam_if/bootloader.bin \
0x8000 .pio/build/tbeam_if/partitions.bin \
0xe000 /home/jlpoole/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/tbeam_if/firmware.bin &
esptool.py --chip esp32s3 --port /dev/ttytBOB --baud 460800 write_flash -z \
0x0000 .pio/build/tbeam_if/bootloader.bin \
0x8000 .pio/build/tbeam_if/partitions.bin \
0xe000 /home/jlpoole/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/tbeam_if/firmware.bin &
wait
```
For another environment, replace each `.pio/build/tbeam_if/` path with that environment's build directory.
Monitor:
```bash
pio device monitor -p /dev/ttytDAN -b 115200
pio device monitor -p /dev/ttytBOB -b 115200
```
## Expected Output
Once the Link is active, both nodes start sending:
```text
Selected file=If.txt bytes=195 chunk=32 interval_ms=500 repeat_rest_ms=10000
TX FILE BEGIN: round=1 file=If.txt bytes=195 chunks=7 crc=...
TX FILE DATA: round=1 seq=1/7 bytes=32 preview="If you can keep your head..."
TX FILE END: round=1 file=If.txt bytes=195 chunks=7 crc=... next_round_in_ms=10000
```
The receiver verifies the transfer:
```text
RX FILE BEGIN: from=Node-... file=If.txt bytes=195 chunks=7 crc=...
RX FILE DATA: from=Node-... seq=1/7 bytes=32 preview="If you can keep your head..."
RX FILE END: from=Node-... file=If.txt received=195/195 chunks=7/7 crc=... status=OK
```
Ten seconds after `TX FILE END`, the same selected file starts again. This rest interval is measured after transfer completion, so large files get the same 10-second pause before the next round.
## Debug Lines
Exercise 305 includes machine-parseable debug records for the role-dependent BLE/Link failure investigation. Each record is one line of `key=value` fields.
```text
RNSLINK Link/announce events, peer hashes, Link ids, and Link object ids.
RNSTX Application plaintext sends and encrypted Reticulum packets handed to BLE.
RNSRX Reassembled BLE packets immediately before Reticulum receives them.
RNSDEC Link encrypt/decrypt attempts and failures from microReticulum Link.cpp.
RNSBLE BLE connect, identity, fragment TX/RX, and packet assembly events.
RNSQUEUE BLE RX queue depth, pushes, pops, drops, and high-water marks.
RNSMEM Heap, largest block, PSRAM, and current task stack high-water mark.
RNSERR Classified adapter errors: reassembly gaps, short fragments, queue overflow, allocation failure.
```
Normal packet logging is rate-limited: first 25 packets, then every 25th packet, then all packets for two seconds after the first failure. Define `RNS_DEBUG_VERBOSE=1` in `platformio.ini` to print every packet fingerprint.
Debug hooks are inserted at these points:
```text
src/TBeamSupremeBleInterface.cpp BLE callback boundary, fragment TX/RX, reassembly, queue, packet handoff.
src/main.cpp board-name mapping, announce/link events, application Link send.
/usr/local/src/microreticulum/microReticulum/src/Link.cpp
Link encrypt/decrypt token fingerprints and classified decrypt failures.
```
Use the CRC fields to split the failure:
```text
Same RNSTX token crc and RNSRX/RNSDEC token crc, but HMAC_INVALID -> likely wrong key/session/Link context.
Different RNSTX token crc and RNSRX/RNSDEC token crc -> BLE fragmentation, reassembly, queue, or buffer corruption.
RNSERR rx_reassembly_gap/timeout or queue_overflow -> mechanical adapter failure before Reticulum decrypt.
RNSMEM largest_block or min_heap collapse before failures -> heap pressure or fragmentation.
```

View file

@ -0,0 +1,116 @@
; Exercise 306: microReticulum BLE file transfer with OLED receive display
[platformio]
default_envs = tbeam_if
[env]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
monitor_speed = 115200
upload_speed = 460800
board_build.partitions = huge_app.csv
extra_scripts = pre:scripts/embed_text.py
custom_text_source = texts/If.txt
lib_extra_dirs =
../../lib
build_flags =
-Wall
-Wno-missing-field-initializers
-Wno-format
-D RNS_USE_FS
-D RNS_PERSIST_PATHS
-D USTORE_USE_UNIVERSALFS
-D MSGPACK_USE_BOOST=OFF
-D MCU_ESP32
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D OLED_SDA=17
-D OLED_SCL=18
-D OLED_ADDR=0x3C
lib_deps =
Wire
olikraus/U8g2@^2.36.4
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
[env:tbeam_if]
extends = env
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_full]
extends = env
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_constitution]
extends = env
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_pi_zero_profile]
extends = env
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_if_full_pi_zero_profile]
extends = env
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_constitution_pi_zero_profile]
extends = env
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam]
extends = env
[env:amy]
extends = env
upload_port = /dev/ttytAMY
monitor_port = /dev/ttytAMY
[env:bob]
extends = env
upload_port = /dev/ttytBOB
monitor_port = /dev/ttytBOB
[env:cy]
extends = env
upload_port = /dev/ttytCY
monitor_port = /dev/ttytCY
[env:dan]
extends = env
upload_port = /dev/ttytDAN
monitor_port = /dev/ttytDAN
[env:ed]
extends = env
upload_port = /dev/ttytED
monitor_port = /dev/ttytED

View file

@ -0,0 +1,49 @@
from pathlib import Path
Import("env")
project_dir = Path(env.subst("$PROJECT_DIR"))
source_name = env.GetProjectOption("custom_text_source", "texts/If.txt")
source_path = Path(source_name)
if not source_path.is_absolute():
source_path = project_dir / source_path
if not source_path.exists():
raise RuntimeError(f"custom_text_source file not found: {source_path}")
data = source_path.read_bytes()
generated_dir = Path(env.subst("$BUILD_DIR")) / "generated"
generated_dir.mkdir(parents=True, exist_ok=True)
header_path = generated_dir / "SelectedText.h"
symbol_name = source_path.name.replace("\\", "/")
lines = [
"#pragma once",
"",
"#include <Arduino.h>",
"#include <pgmspace.h>",
"",
f'static constexpr const char* SELECTED_TEXT_NAME = "{symbol_name}";',
f"static constexpr size_t SELECTED_TEXT_SIZE = {len(data)};",
"static const uint8_t SELECTED_TEXT[] PROGMEM = {",
]
for offset in range(0, len(data), 16):
chunk = data[offset : offset + 16]
values = ", ".join(f"0x{byte:02x}" for byte in chunk)
lines.append(f" {values},")
lines.extend(
[
"};",
"",
"static inline uint8_t read_selected_text_byte(size_t offset) {",
" return pgm_read_byte(SELECTED_TEXT + offset);",
"}",
"",
]
)
header_path.write_text("\n".join(lines))
env.Append(CPPPATH=[str(generated_dir)])
print(f"Embedded {source_path} ({len(data)} bytes) -> {header_path}")

View file

@ -0,0 +1,172 @@
#include "DebugStats.h"
#if RNS_DEBUG_INSTRUMENTATION
#include <esp_heap_caps.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#endif
static String debug_board = "unknown";
static const char* debug_role = "unknown";
static uint32_t debug_failure_window_until = 0;
static uint32_t debug_next_mem_ms = 0;
#if RNS_DEBUG_INSTRUMENTATION
static SemaphoreHandle_t debug_serial_mutex = nullptr;
static char debug_log_buffer[512];
static SemaphoreHandle_t serial_mutex() {
if (!debug_serial_mutex) {
debug_serial_mutex = xSemaphoreCreateMutex();
}
return debug_serial_mutex;
}
#endif
void DebugStats::set_board(const String& board) {
debug_board = board.length() ? board : String("unknown");
}
void DebugStats::set_role(const char* role) {
debug_role = role ? role : "unknown";
}
const char* DebugStats::board() {
return debug_board.c_str();
}
const char* DebugStats::role() {
return debug_role;
}
uint32_t DebugStats::crc32(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFFUL;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t bit = 0; bit < 8; ++bit) {
crc = (crc >> 1) ^ (0xEDB88320UL & (0UL - (crc & 1UL)));
}
}
return ~crc;
}
String DebugStats::edge_hex(const uint8_t* data, size_t len, bool first) {
char out[9];
out[0] = '\0';
if (!data || len == 0) {
return String("none");
}
size_t edge_len = len < 4 ? len : 4;
size_t start = first ? 0 : len - edge_len;
char* cursor = out;
for (size_t i = 0; i < edge_len; ++i) {
snprintf(cursor, 3, "%02X", data[start + i]);
cursor += 2;
}
*cursor = '\0';
return String(out);
}
uint32_t DebugStats::bytes_crc32(const RNS::Bytes& bytes) {
return crc32(bytes.data(), bytes.size());
}
String DebugStats::bytes_first4(const RNS::Bytes& bytes) {
return edge_hex(bytes.data(), bytes.size(), true);
}
String DebugStats::bytes_last4(const RNS::Bytes& bytes) {
return edge_hex(bytes.data(), bytes.size(), false);
}
bool DebugStats::should_log_packet(uint32_t seq) {
#if RNS_DEBUG_VERBOSE
(void)seq;
return true;
#else
uint32_t now = millis();
return seq <= 25 || (seq % 25) == 0 || (int32_t)(debug_failure_window_until - now) > 0;
#endif
}
void DebugStats::note_failure() {
debug_failure_window_until = millis() + 2000;
}
void DebugStats::vlogf(const char* format, va_list args) {
#if RNS_DEBUG_INSTRUMENTATION
SemaphoreHandle_t mutex = serial_mutex();
if (mutex) {
xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
}
vsnprintf(debug_log_buffer, sizeof(debug_log_buffer), format, args);
Serial.print(debug_log_buffer);
if (mutex) {
xSemaphoreGive(mutex);
}
#else
(void)format;
(void)args;
#endif
}
void DebugStats::logf(const char* format, ...) {
va_list args;
va_start(args, format);
vlogf(format, args);
va_end(args);
}
void DebugStats::println(const char* line) {
#if RNS_DEBUG_INSTRUMENTATION
SemaphoreHandle_t mutex = serial_mutex();
if (mutex) {
xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
}
Serial.println(line ? line : "");
if (mutex) {
xSemaphoreGive(mutex);
}
#else
(void)line;
#endif
}
void DebugStats::print_mem(const char* event) {
#if RNS_DEBUG_INSTRUMENTATION
UBaseType_t stack_words = uxTaskGetStackHighWaterMark(NULL);
logf("RNSMEM ms=%lu board=%s role=%s event=%s task=%s free_heap=%u min_heap=%u largest_block=%u free_psram=%u min_psram=%u stack_hw_words=%u stack_hw_bytes=%u\r\n",
(unsigned long)millis(),
board(),
role(),
event ? event : "periodic",
pcTaskGetName(NULL),
(unsigned)heap_caps_get_free_size(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
(unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
(unsigned)stack_words,
(unsigned)(stack_words * sizeof(StackType_t)));
#else
(void)event;
#endif
}
void DebugStats::maybe_print_periodic() {
#if RNS_DEBUG_INSTRUMENTATION
uint32_t now = millis();
if (debug_next_mem_ms == 0 || (int32_t)(now - debug_next_mem_ms) >= 0) {
debug_next_mem_ms = now + 1000;
print_mem("periodic");
}
#endif
}
extern "C" const char* rns_debug_board_name() {
return DebugStats::board();
}
extern "C" const char* rns_debug_role_name() {
return DebugStats::role();
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <Arduino.h>
#include <Bytes.h>
#include <stdarg.h>
#ifndef RNS_DEBUG_INSTRUMENTATION
#define RNS_DEBUG_INSTRUMENTATION 1
#endif
#ifndef RNS_DEBUG_VERBOSE
#define RNS_DEBUG_VERBOSE 0
#endif
class DebugStats {
public:
static void set_board(const String& board);
static void set_role(const char* role);
static const char* board();
static const char* role();
static uint32_t crc32(const uint8_t* data, size_t len);
static String edge_hex(const uint8_t* data, size_t len, bool first);
static uint32_t bytes_crc32(const RNS::Bytes& bytes);
static String bytes_first4(const RNS::Bytes& bytes);
static String bytes_last4(const RNS::Bytes& bytes);
static bool should_log_packet(uint32_t seq);
static void note_failure();
static void logf(const char* format, ...);
static void vlogf(const char* format, va_list args);
static void println(const char* line);
static void print_mem(const char* event = "periodic");
static void maybe_print_periodic();
};

View file

@ -0,0 +1,780 @@
#include "TBeamSupremeBleInterface.h"
#include "DebugStats.h"
#include <BLE2902.h>
#include <BLEService.h>
#include <Cryptography/Random.h>
#include <Identity.h>
#include <Log.h>
#include <algorithm>
#include <string.h>
using namespace RNS;
static TBeamSupremeBleInterface* active_ble_interface = nullptr;
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];
}
class TBeamSupremeBleInterface::ServerCallbacks : public BLEServerCallbacks {
public:
explicit ServerCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onConnect(BLEServer* server) override {
(void)server;
if (_owner->_client && _owner->_client->isConnected()) {
Serial.println("BLE dual-role: inbound connection arrived while outbound is active; keeping outbound link");
server->disconnect(server->getConnId());
return;
}
_owner->_connected = true;
_owner->_server_handshake_received = false;
DebugStats::set_role("server");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=connect conn=%u direction=inbound\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
Serial.println("BLE dual-role: central connected to local server");
INFO("BLE central connected to local server");
}
void onDisconnect(BLEServer* server) override {
(void)server;
if (_owner->_client && _owner->_client->isConnected()) {
_owner->_connected = true;
DebugStats::set_role("client");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=disconnect conn=%u direction=inbound action=ignored_outbound_active\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
return;
}
_owner->_connected = false;
_owner->_server_handshake_received = false;
_owner->reset_reassembly();
DebugStats::set_role("idle");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=disconnect conn=%u direction=inbound\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
Serial.println("BLE dual-role: central disconnected; restarting advertising");
INFO("BLE central disconnected; restarting advertising");
BLEDevice::startAdvertising();
}
private:
TBeamSupremeBleInterface* _owner;
};
class TBeamSupremeBleInterface::RxCallbacks : public BLECharacteristicCallbacks {
public:
explicit RxCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onWrite(BLECharacteristic* characteristic) override {
std::string value = characteristic->getValue();
if (value.empty()) {
return;
}
if (!_owner->_server_handshake_received && value.size() == 16) {
_owner->_server_handshake_received = true;
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=identity_handshake_rx len=%u crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)value.size(),
(unsigned long)DebugStats::crc32(reinterpret_cast<const uint8_t*>(value.data()), value.size()));
Serial.println("BLE dual-role: identity handshake received by local server");
INFOF("BLE identity handshake received: %d bytes", (int)value.size());
return;
}
// BLECharacteristic owns/reuses its callback storage; handle_fragment copies
// payload bytes into RNS::Bytes before data can outlive this callback.
_owner->handle_fragment(reinterpret_cast<const uint8_t*>(value.data()), value.size());
}
private:
TBeamSupremeBleInterface* _owner;
};
class TBeamSupremeBleInterface::AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
public:
explicit AdvertisedDeviceCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onResult(BLEAdvertisedDevice advertised_device) override {
bool has_service = advertised_device.haveServiceUUID() &&
advertised_device.isAdvertisingService(BLEUUID(TBeamSupremeBleInterface::SERVICE_UUID));
bool has_rns_name = advertised_device.haveName() &&
advertised_device.getName().rfind("RNS-", 0) == 0;
if ((has_service || has_rns_name) || _owner->_scan_report_count < 5) {
Serial.printf("BLE scan: addr=%s name=%s service=%s rssi=%d\r\n",
advertised_device.getAddress().toString().c_str(),
advertised_device.haveName() ? advertised_device.getName().c_str() : "",
has_service ? "yes" : "no",
advertised_device.getRSSI());
_owner->_scan_report_count++;
}
if (!has_service && !has_rns_name) {
return;
}
String peer_label = _owner->advertised_node_label(advertised_device);
String peer_address = String(advertised_device.getAddress().toString().c_str());
if (!_owner->should_connect_to_peer(peer_address)) {
Serial.printf("BLE dual-role: peer addr=%s label=%s has lower tie-breaker; staying available\r\n",
peer_address.c_str(),
peer_label.length() ? peer_label.c_str() : "(unknown)");
return;
}
BLEDevice::getScan()->stop();
delete _owner->_advertised_device;
_owner->_advertised_device = new BLEAdvertisedDevice(advertised_device);
_owner->_do_connect = true;
_owner->_scanning = false;
Serial.printf("BLE dual-role: peer candidate found: %s label=%s\r\n",
advertised_device.getAddress().toString().c_str(),
peer_label.c_str());
INFOF("BLE peer found: %s", advertised_device.getAddress().toString().c_str());
}
private:
TBeamSupremeBleInterface* _owner;
};
TBeamSupremeBleInterface::TBeamSupremeBleInterface(const String& node_label, const char* name) : InterfaceImpl(name) {
_node_label = node_label;
_IN = true;
_OUT = true;
_bitrate = 1000000;
_HW_MTU = BLE_PAYLOAD_SIZE;
}
TBeamSupremeBleInterface::~TBeamSupremeBleInterface() {
stop();
delete _advertised_device;
}
bool TBeamSupremeBleInterface::start() {
if (_started) {
return true;
}
active_ble_interface = this;
BLEDevice::init(std::string("RNS-") + _node_label.c_str());
BLEDevice::setMTU(BLE_ATT_MTU);
_local_address = String(BLEDevice::getAddress().toString().c_str());
Serial.printf("BLE dual-role: local addr=%s label=%s\r\n", _local_address.c_str(), _node_label.c_str());
Serial.println("BLE dual-role: starting advertiser and scanner");
INFO("BLE starting as dual-role client/server");
start_peripheral();
start_central_scan();
_started = true;
_online = true;
return true;
}
void TBeamSupremeBleInterface::stop() {
_online = false;
_connected = false;
if (_client && _client->isConnected()) {
_client->disconnect();
}
DebugStats::set_role("idle");
}
void TBeamSupremeBleInterface::loop() {
if (!_online) {
return;
}
if (_do_connect && _advertised_device) {
_do_connect = false;
if (!connect_to_advertised_device(_advertised_device)) {
_connected = false;
_next_scan_ms = millis() + SCAN_RETRY_MS;
}
}
if (!_connected && !_scanning && (int32_t)(millis() - _next_scan_ms) >= 0) {
start_central_scan();
}
if (_reassembly_started_ms != 0 && millis() - _reassembly_started_ms > REASSEMBLY_TIMEOUT_MS) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_timeout detail=msg_id_%lu received=%u expected=%u bytes=%u expected_bytes=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_received_fragments,
(unsigned)_expected_total,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_expected_message_len);
DebugStats::print_mem("rx_reassembly_timeout");
WARNING("BLE reassembly timeout; dropping partial packet");
reset_reassembly();
}
RNS::Bytes packet({RNS::Type::NONE});
while (dequeue_packet(packet)) {
_packet_rx_seq++;
if (DebugStats::should_log_packet(_packet_rx_seq)) {
DebugStats::logf("RNSRX ms=%lu board=%s role=%s seq=%lu link_id=unknown len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_packet_rx_seq,
(unsigned)packet.size(),
(unsigned long)DebugStats::bytes_crc32(packet),
DebugStats::bytes_first4(packet).c_str(),
DebugStats::bytes_last4(packet).c_str());
DebugStats::logf("RNSDEC ms=%lu board=%s role=%s event=attempt link_id=unknown token_len=%u token_crc32=%08lX link_obj=unknown\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)packet.size(),
(unsigned long)DebugStats::bytes_crc32(packet));
}
InterfaceImpl::handle_incoming(packet);
}
print_queue_stats(false);
DebugStats::maybe_print_periodic();
}
void TBeamSupremeBleInterface::start_peripheral() {
_server = BLEDevice::createServer();
_server->setCallbacks(new ServerCallbacks(this));
BLEService* service = _server->createService(SERVICE_UUID);
_tx_characteristic = service->createCharacteristic(
TX_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
_tx_characteristic->addDescriptor(new BLE2902());
BLECharacteristic* rx_characteristic = service->createCharacteristic(
RX_UUID,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
rx_characteristic->setCallbacks(new RxCallbacks(this));
_identity_characteristic = service->createCharacteristic(
IDENTITY_UUID,
BLECharacteristic::PROPERTY_READ);
RNS::Bytes identity = local_identity_hash();
_identity_characteristic->setValue((uint8_t*)identity.data(), identity.size());
service->start();
BLEAdvertising* advertising = BLEDevice::getAdvertising();
advertising->addServiceUUID(SERVICE_UUID);
advertising->setScanResponse(true);
advertising->setMinPreferred(0x00);
BLEDevice::startAdvertising();
Serial.println("BLE dual-role: advertising Reticulum service");
INFO("BLE advertising Reticulum service");
}
void TBeamSupremeBleInterface::start_central_scan() {
_scanning = true;
_next_scan_ms = millis() + SCAN_RETRY_MS;
_scan_report_count = 0;
BLEScan* scan = BLEDevice::getScan();
scan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks(this), true);
scan->setInterval(1349);
scan->setWindow(449);
scan->setActiveScan(true);
Serial.println("BLE dual-role: scanning for Reticulum service");
scan->start(5, false);
if (!_do_connect && !_connected) {
_scanning = false;
_next_scan_ms = millis() + SCAN_RETRY_MS;
Serial.println("BLE dual-role: scan complete; no eligible peer found");
}
scan->clearResults();
INFO("BLE scanning for Reticulum service");
}
bool TBeamSupremeBleInterface::connect_to_advertised_device(BLEAdvertisedDevice* device) {
Serial.printf("BLE dual-role: connecting to %s\r\n", device->getAddress().toString().c_str());
INFOF("BLE connecting to %s", device->getAddress().toString().c_str());
_client = BLEDevice::createClient();
if (!_client->connect(device)) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_connect_failed detail=connect_failed\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: connect failed");
WARNING("BLE connect failed");
return false;
}
_client->setMTU(BLE_ATT_MTU);
BLERemoteService* service = _client->getService(BLEUUID(SERVICE_UUID));
if (!service) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_service_missing detail=reticulum_service_not_found\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: Reticulum service not found after connect");
WARNING("BLE Reticulum service not found after connect");
_client->disconnect();
return false;
}
_remote_rx_characteristic = service->getCharacteristic(BLEUUID(RX_UUID));
_remote_tx_characteristic = service->getCharacteristic(BLEUUID(TX_UUID));
BLERemoteCharacteristic* identity_characteristic = service->getCharacteristic(BLEUUID(IDENTITY_UUID));
if (!_remote_rx_characteristic || !_remote_tx_characteristic) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_characteristic_missing detail=rx_or_tx_missing\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: RX/TX characteristics not found");
WARNING("BLE Reticulum RX/TX characteristics not found");
_client->disconnect();
return false;
}
if (identity_characteristic && identity_characteristic->canRead()) {
std::string peer_identity = identity_characteristic->readValue();
DebugStats::logf("RNSBLE ms=%lu board=%s role=client event=identity_read len=%u crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
(unsigned)peer_identity.size(),
(unsigned long)DebugStats::crc32(reinterpret_cast<const uint8_t*>(peer_identity.data()), peer_identity.size()));
Serial.printf("BLE dual-role: peer identity read: %d bytes\r\n", (int)peer_identity.size());
INFOF("BLE peer identity read: %d bytes", (int)peer_identity.size());
}
if (_remote_tx_characteristic->canNotify()) {
_remote_tx_characteristic->registerForNotify(client_notify_callback);
} else {
Serial.println("BLE dual-role: peer TX characteristic does not notify");
}
RNS::Bytes identity = local_identity_hash();
_remote_rx_characteristic->writeValue((uint8_t*)identity.data(), identity.size(), true);
_connected = true;
_scanning = false;
DebugStats::set_role("client");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=connect conn=client direction=outbound peer=%s identity_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
device->getAddress().toString().c_str(),
(unsigned long)DebugStats::bytes_crc32(identity));
Serial.println("BLE dual-role: connected and identity handshake sent");
INFO("BLE connected and identity handshake sent");
return true;
}
bool TBeamSupremeBleInterface::should_connect_to_peer(const String& peer_address) const {
if (peer_address.length() == 0 || peer_address == _local_address) {
return false;
}
return strcmp(_local_address.c_str(), peer_address.c_str()) < 0;
}
String TBeamSupremeBleInterface::advertised_node_label(BLEAdvertisedDevice& device) const {
if (!device.haveName()) {
return "";
}
std::string name = device.getName();
if (name.rfind("RNS-", 0) != 0) {
return "";
}
return String(name.substr(4).c_str());
}
void TBeamSupremeBleInterface::send_outgoing(const Bytes& data) {
if (!_online || !_connected) {
return;
}
size_t total = (data.size() + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
if (total == 0 || total > 65535) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=tx_fragment_count_invalid detail=packet_len_%u fragments_%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)data.size(),
(unsigned)total);
DebugStats::print_mem("tx_fragment_count_invalid");
WARNINGF("BLE cannot fragment packet of size %d", (int)data.size());
return;
}
_packet_tx_seq++;
uint32_t msg_id = ++_tx_message_id;
if (DebugStats::should_log_packet(_packet_tx_seq)) {
DebugStats::logf("RNSTX ms=%lu board=%s role=%s seq=%lu link_id=unknown msg_id=%lu len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_packet_tx_seq,
(unsigned long)msg_id,
(unsigned)data.size(),
(unsigned long)DebugStats::bytes_crc32(data),
DebugStats::bytes_first4(data).c_str(),
DebugStats::bytes_last4(data).c_str());
}
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());
memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
_ble_frag_tx_seq++;
if (DebugStats::should_log_packet(_packet_tx_seq)) {
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=fragment_tx conn=%s frag_seq=%lu frag_index=%u frag_total=%u msg_id=%lu msg_off=%u msg_total=%u frag_len=%u payload_len=%u frag_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
_client && _client->isConnected() ? "client" : "server",
(unsigned long)_ble_frag_tx_seq,
(unsigned)i,
(unsigned)total,
(unsigned long)msg_id,
(unsigned)offset,
(unsigned)data.size(),
(unsigned)(FRAG_HEADER_SIZE + chunk),
(unsigned)chunk,
(unsigned long)DebugStats::crc32(fragment, FRAG_HEADER_SIZE + chunk));
}
send_fragment(fragment, FRAG_HEADER_SIZE + chunk);
delay(8);
}
InterfaceImpl::handle_outgoing(data);
}
void TBeamSupremeBleInterface::send_fragment(const uint8_t* data, size_t len) {
if (_client && _client->isConnected() && _remote_rx_characteristic) {
_remote_rx_characteristic->writeValue((uint8_t*)data, len, true);
return;
}
if (_tx_characteristic) {
_tx_characteristic->setValue((uint8_t*)data, len);
_tx_characteristic->notify();
delay(20);
}
}
void TBeamSupremeBleInterface::handle_fragment(const uint8_t* data, size_t len) {
if (len < FRAG_HEADER_SIZE) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_fragment_too_short detail=len_%u\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role(), (unsigned)len);
DebugStats::print_mem("rx_fragment_too_short");
WARNINGF("BLE fragment too short: %d", (int)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;
_ble_frag_rx_seq++;
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)) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_fragment_header_bad detail=type_%u_ver_%u_seq_%u_total_%u_msg_id_%lu_msg_len_%lu_len_%u_payload_%u_expected_frags_%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)fragment_type,
(unsigned)version,
(unsigned)sequence,
(unsigned)total,
(unsigned long)msg_id,
(unsigned long)msg_len,
(unsigned)len,
(unsigned)payload_len,
(unsigned long)expected_fragments);
DebugStats::print_mem("rx_fragment_header_bad");
WARNING("BLE invalid fragment header");
reset_reassembly();
return;
}
if (sequence == 0) {
if (_reassembly_started_ms != 0 && _received_fragments != _expected_total) {
_reassembly_gaps++;
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_restart detail=old_msg_id_%lu_new_msg_id_%lu_received_%u_expected_%u bytes_%u gaps=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned long)msg_id,
(unsigned)_received_fragments,
(unsigned)_expected_total,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_reassembly_gaps);
DebugStats::print_mem("rx_reassembly_restart");
}
reset_reassembly();
_expected_total = total;
_received_fragments = 0;
_expected_message_len = msg_len;
_reassembly_started_ms = millis();
_current_rx_message_id = msg_id;
if (msg_id > _rx_message_id) {
_rx_message_id = msg_id;
}
} else if (_reassembly_started_ms == 0 || msg_id != _current_rx_message_id) {
_reassembly_out_of_order++;
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_stale_fragment detail=msg_id_%lu_current_%lu_seq_%u_received_%u total_%u expected_total_%u stale=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)msg_id,
(unsigned long)_current_rx_message_id,
(unsigned)sequence,
(unsigned)_received_fragments,
(unsigned)total,
(unsigned)_expected_total,
(unsigned long)_reassembly_out_of_order);
DebugStats::print_mem("rx_reassembly_stale_fragment");
return;
}
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=fragment_rx conn=%s frag_seq=%lu frag_index=%u frag_total=%u msg_id=%lu msg_off=%u msg_total=%lu frag_len=%u payload_len=%u frag_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
_client && _client->isConnected() ? "client" : "server",
(unsigned long)_ble_frag_rx_seq,
(unsigned)sequence,
(unsigned)total,
(unsigned long)_current_rx_message_id,
(unsigned)expected_offset,
(unsigned long)msg_len,
(unsigned)len,
(unsigned)payload_len,
(unsigned long)DebugStats::crc32(data, len));
if (_expected_total != total || _expected_message_len != msg_len || sequence != _received_fragments) {
if (sequence < _received_fragments) {
_reassembly_duplicates++;
} else if (sequence > _received_fragments) {
_reassembly_gaps++;
} else {
_reassembly_out_of_order++;
}
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_gap detail=msg_id_%lu_seq_%u_expected_seq_%u_total_%u_expected_total_%u_msg_len_%lu_expected_len_%lu gaps=%lu duplicates=%lu out_of_order=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)sequence,
(unsigned)_received_fragments,
(unsigned)total,
(unsigned)_expected_total,
(unsigned long)msg_len,
(unsigned long)_expected_message_len,
(unsigned long)_reassembly_gaps,
(unsigned long)_reassembly_duplicates,
(unsigned long)_reassembly_out_of_order);
DebugStats::print_mem("rx_reassembly_gap");
WARNING("BLE out-of-order fragment; dropping partial packet");
reset_reassembly();
return;
}
// Own the bytes from this point forward. The next Reticulum layer receives
// a deep-copied RNS::Bytes packet, never a pointer into a BLE callback buffer.
_reassembly_buffer.append(payload, payload_len);
_received_fragments++;
if (_received_fragments == _expected_total) {
if (_reassembly_buffer.size() != _expected_message_len) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_len_mismatch detail=msg_id_%lu assembled_%u expected_%lu fragments_%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_expected_message_len,
(unsigned)_received_fragments);
DebugStats::print_mem("rx_reassembly_len_mismatch");
reset_reassembly();
return;
}
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=packet_assembled msg_id=%lu assembled_len=%u assembled_crc32=%08lX fragments=%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_reassembly_buffer.size(),
(unsigned long)DebugStats::bytes_crc32(_reassembly_buffer),
(unsigned)_received_fragments);
enqueue_packet(_reassembly_buffer);
reset_reassembly();
}
}
void TBeamSupremeBleInterface::enqueue_packet(const RNS::Bytes& packet) {
portENTER_CRITICAL(&_queue_mux);
if (_incoming_packets.size() >= MAX_INCOMING_QUEUE_DEPTH) {
_queue_drops++;
size_t depth = _incoming_packets.size();
portEXIT_CRITICAL(&_queue_mux);
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=queue_overflow detail=ble_rx_depth_%u_drops_%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)depth,
(unsigned long)_queue_drops);
DebugStats::print_mem("queue_overflow");
print_queue_stats(true);
return;
}
try {
_incoming_packets.push_back(packet);
_queue_pushes++;
if (_incoming_packets.size() > _queue_highwater) {
_queue_highwater = _incoming_packets.size();
}
} catch (...) {
_queue_alloc_failures++;
portEXIT_CRITICAL(&_queue_mux);
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=queue_alloc_failure detail=ble_rx_push_failed failures=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_queue_alloc_failures);
DebugStats::print_mem("queue_alloc_failure");
print_queue_stats(true);
return;
}
portEXIT_CRITICAL(&_queue_mux);
}
bool TBeamSupremeBleInterface::dequeue_packet(RNS::Bytes& packet) {
portENTER_CRITICAL(&_queue_mux);
if (_incoming_packets.empty()) {
portEXIT_CRITICAL(&_queue_mux);
return false;
}
packet = _incoming_packets.front();
_incoming_packets.pop_front();
_queue_pops++;
portEXIT_CRITICAL(&_queue_mux);
return true;
}
void TBeamSupremeBleInterface::print_queue_stats(bool force) {
uint32_t now = millis();
if (!force && _next_queue_log_ms != 0 && (int32_t)(now - _next_queue_log_ms) < 0) {
return;
}
_next_queue_log_ms = now + 1000;
portENTER_CRITICAL(&_queue_mux);
size_t depth = _incoming_packets.size();
size_t highwater = _queue_highwater;
uint32_t pushes = _queue_pushes;
uint32_t pops = _queue_pops;
uint32_t drops = _queue_drops;
uint32_t overwrites = _queue_overwrites;
uint32_t alloc_failures = _queue_alloc_failures;
portEXIT_CRITICAL(&_queue_mux);
DebugStats::logf("RNSQUEUE ms=%lu board=%s role=%s name=ble_rx depth=%u max_depth=%u pushes=%lu pops=%lu drops=%lu overwrites=%lu alloc_failures=%lu highwater=%u\r\n",
(unsigned long)now,
DebugStats::board(),
DebugStats::role(),
(unsigned)depth,
(unsigned)MAX_INCOMING_QUEUE_DEPTH,
(unsigned long)pushes,
(unsigned long)pops,
(unsigned long)drops,
(unsigned long)overwrites,
(unsigned long)alloc_failures,
(unsigned)highwater);
}
void TBeamSupremeBleInterface::reset_reassembly() {
_reassembly_buffer.clear();
_expected_total = 0;
_received_fragments = 0;
_expected_message_len = 0;
_reassembly_started_ms = 0;
}
RNS::Bytes TBeamSupremeBleInterface::local_identity_hash() const {
String material = String("microReticulum BLE ") + _node_label;
return Identity::truncated_hash(RNS::bytesFromString(material.c_str()));
}
void TBeamSupremeBleInterface::client_notify_callback(BLERemoteCharacteristic* characteristic,
uint8_t* data,
size_t length,
bool is_notify) {
(void)characteristic;
(void)is_notify;
if (active_ble_interface) {
// NimBLE/Arduino BLE owns this callback buffer. handle_fragment copies it
// synchronously into owned reassembly storage before returning.
active_ble_interface->handle_fragment(data, length);
}
}

View file

@ -0,0 +1,115 @@
#pragma once
#include <Bytes.h>
#include <Interface.h>
#include <Arduino.h>
#include <BLEAdvertisedDevice.h>
#include <BLEClient.h>
#include <BLEDevice.h>
#include <BLERemoteCharacteristic.h>
#include <BLEScan.h>
#include <BLEServer.h>
#include <deque>
class TBeamSupremeBleInterface : public RNS::InterfaceImpl {
public:
explicit TBeamSupremeBleInterface(const String& node_label,
const char* name = "TBeamSupremeBLE");
~TBeamSupremeBleInterface() override;
bool start() override;
void stop() override;
void loop() override;
bool connected() const { return _connected; }
const char* role_name() const { return "dual-role"; }
private:
void send_outgoing(const RNS::Bytes& data) override;
void start_peripheral();
void start_central_scan();
bool connect_to_advertised_device(BLEAdvertisedDevice* device);
bool should_connect_to_peer(const String& peer_address) const;
String advertised_node_label(BLEAdvertisedDevice& device) const;
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 print_queue_stats(bool force);
RNS::Bytes local_identity_hash() const;
static void client_notify_callback(BLERemoteCharacteristic* characteristic,
uint8_t* data,
size_t length,
bool is_notify);
class ServerCallbacks;
class RxCallbacks;
class AdvertisedDeviceCallbacks;
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 uint32_t REASSEMBLY_TIMEOUT_MS = 30000;
static constexpr uint32_t SCAN_RETRY_MS = 5000;
static constexpr size_t MAX_INCOMING_QUEUE_DEPTH = 32;
String _node_label;
String _local_address;
bool _started = false;
bool _connected = false;
bool _do_connect = false;
bool _scanning = false;
bool _server_handshake_received = false;
uint8_t _scan_report_count = 0;
uint32_t _next_scan_ms = 0;
BLEServer* _server = nullptr;
BLECharacteristic* _tx_characteristic = nullptr;
BLECharacteristic* _identity_characteristic = nullptr;
BLEAdvertisedDevice* _advertised_device = nullptr;
BLEClient* _client = nullptr;
BLERemoteCharacteristic* _remote_rx_characteristic = nullptr;
BLERemoteCharacteristic* _remote_tx_characteristic = nullptr;
RNS::Bytes _reassembly_buffer;
uint16_t _expected_total = 0;
uint16_t _received_fragments = 0;
uint32_t _expected_message_len = 0;
uint32_t _reassembly_started_ms = 0;
uint32_t _rx_message_id = 0;
uint32_t _current_rx_message_id = 0;
uint32_t _tx_message_id = 0;
uint32_t _ble_frag_tx_seq = 0;
uint32_t _ble_frag_rx_seq = 0;
uint32_t _packet_tx_seq = 0;
uint32_t _packet_rx_seq = 0;
uint32_t _reassembly_gaps = 0;
uint32_t _reassembly_duplicates = 0;
uint32_t _reassembly_out_of_order = 0;
uint32_t _queue_pushes = 0;
uint32_t _queue_pops = 0;
uint32_t _queue_drops = 0;
uint32_t _queue_overwrites = 0;
uint32_t _queue_alloc_failures = 0;
size_t _queue_highwater = 0;
uint32_t _next_queue_log_ms = 0;
std::deque<RNS::Bytes> _incoming_packets;
portMUX_TYPE _queue_mux = portMUX_INITIALIZER_UNLOCKED;
};

View file

@ -0,0 +1,694 @@
#include "TBeamSupremeBleInterface.h"
#include "DebugStats.h"
#include "SelectedText.h"
#include "TBeamDisplay.h"
#include <Arduino.h>
#include <Destination.h>
#include <Identity.h>
#include <Link.h>
#include <Log.h>
#include <Packet.h>
#include <Reticulum.h>
#include <Transport.h>
#include <Type.h>
#include <Utilities/OS.h>
#include <U8g2lib.h>
#include <microStore/Adapters/UniversalFileSystem.h>
#include <microStore/FileSystem.h>
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
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 constexpr uint8_t OLED_TEXT_LINES = 8;
static constexpr uint8_t OLED_TEXT_COLS = 25;
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 String peer_label;
static bool have_peer = false;
static bool link_active = false;
static bool link_attempted = false;
static TBeamSupremeBleInterface* ble_impl = nullptr;
static String node_label;
static tbeam::TBeamDisplay oled_display;
static char oled_lines[OLED_TEXT_LINES][OLED_TEXT_COLS + 1] = {};
static uint8_t oled_line = 0;
static uint8_t oled_col = 0;
struct RxTransferState {
bool active = false;
String sender;
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;
};
static RxTransferState rx_transfer;
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;
uint32_t next_start_ms = 0;
uint32_t round = 0;
};
static TxTransferState tx_transfer;
static String default_node_label() {
uint64_t mac = ESP.getEfuseMac();
char label[24];
snprintf(label, sizeof(label), "Node-%012llX", (unsigned long long)(mac & 0xFFFFFFFFFFFFULL));
return String(label);
}
static String board_name_from_node_label(const String& label) {
if (label == "Node-68BF5B43CA48") return "AMY";
if (label == "Node-DC935A43CA48") return "BOB";
if (label == "Node-44915A43CA48") return "CY";
if (label == "Node-A0935A43CA48") return "DAN";
if (label == "Node-D0905A43CA48") return "ED";
if (label == "Node-748C5B43CA48") return "FLO";
if (label == "Node-E0405A43CA48") return "GUY";
return label;
}
static bool should_initiate_link_to(const String& label) {
return strcmp(node_label.c_str(), label.c_str()) < 0;
}
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 String hex32(uint32_t value) {
char buffer[9];
snprintf(buffer, sizeof(buffer), "%08lX", (unsigned long)value);
return String(buffer);
}
static String link_id_hex(const RNS::Link& link) {
if (!link) {
return String("none");
}
return String(link.link_id().toHex().c_str());
}
static uint32_t bytes_crc32(const RNS::Bytes& bytes) {
return DebugStats::bytes_crc32(bytes);
}
static String preview_payload(const String& payload) {
String preview;
for (size_t i = 0; i < payload.length() && preview.length() < 56; ++i) {
char c = payload.charAt(i);
if (c == '\n') {
preview += "\\n";
} else if (c == '\r') {
preview += "\\r";
} else if ((uint8_t)c < 32) {
preview += '?';
} else {
preview += c;
}
}
return preview;
}
static void render_oled_text() {
if (!oled_display.ready()) {
return;
}
U8G2& oled = oled_display.raw();
oled.clearBuffer();
oled.setFont(u8g2_font_5x8_tf);
for (uint8_t i = 0; i < OLED_TEXT_LINES; ++i) {
if (oled_lines[i][0] != '\0') {
oled.drawUTF8(0, 8 + (i * 8), oled_lines[i]);
}
}
oled.sendBuffer();
}
static void clear_oled_text() {
for (uint8_t i = 0; i < OLED_TEXT_LINES; ++i) {
oled_lines[i][0] = '\0';
}
oled_line = 0;
oled_col = 0;
render_oled_text();
}
static void show_startup_splash() {
if (!oled_display.ready()) {
return;
}
U8G2& oled = oled_display.raw();
oled.clearBuffer();
oled.setFont(u8g2_font_5x8_tf);
oled.drawUTF8(0, 8, "Exercise 306");
oled.drawUTF8(0, 16, "Commencing...");
oled.drawUTF8(0, 32, "pairing has not");
oled.drawUTF8(0, 40, "been completed");
oled.drawUTF8(0, 48, "and when completed,");
oled.drawUTF8(0, 56, "text will appear.");
oled.sendBuffer();
}
static void advance_oled_line() {
if (oled_line < OLED_TEXT_LINES - 1) {
oled_line++;
} else {
for (uint8_t i = 0; i < OLED_TEXT_LINES - 1; ++i) {
strlcpy(oled_lines[i], oled_lines[i + 1], sizeof(oled_lines[i]));
}
}
oled_lines[oled_line][0] = '\0';
oled_col = 0;
}
static void append_oled_text(const String& text) {
for (size_t i = 0; i < text.length(); ++i) {
char c = text.charAt(i);
if (c == '\r') {
continue;
}
if (c == '\n') {
advance_oled_line();
continue;
}
if ((uint8_t)c < 32) {
c = ' ';
}
if (oled_col >= OLED_TEXT_COLS) {
advance_oled_line();
}
oled_lines[oled_line][oled_col++] = c;
oled_lines[oled_line][oled_col] = '\0';
}
render_oled_text();
}
static void handle_file_begin(const String& sender,
const 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;
Serial.printf("RX FILE BEGIN: from=%s file=%s bytes=%u chunks=%lu crc=%s\r\n",
sender.c_str(),
file_name.c_str(),
(unsigned)size,
(unsigned long)chunks,
hex32(crc).c_str());
clear_oled_text();
}
static void handle_file_data(const String& sender,
uint32_t sequence,
uint32_t total,
const String& payload) {
if (!rx_transfer.active || rx_transfer.sender != sender) {
Serial.printf("RX FILE DATA ignored: sender=%s seq=%lu no active transfer\r\n",
sender.c_str(), (unsigned long)sequence);
return;
}
for (size_t i = 0; i < payload.length(); ++i) {
rx_transfer.crc = fnv1a_update(rx_transfer.crc, (uint8_t)payload.charAt(i));
}
rx_transfer.received_chunks++;
rx_transfer.received_size += payload.length();
Serial.printf("RX FILE DATA: from=%s seq=%lu/%lu bytes=%u preview=\"%s\"\r\n",
sender.c_str(),
(unsigned long)sequence,
(unsigned long)total,
(unsigned)payload.length(),
preview_payload(payload).c_str());
append_oled_text(payload);
}
static void handle_file_end(const String& sender,
const 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;
Serial.printf("RX FILE END: from=%s file=%s received=%u/%u chunks=%lu/%lu crc=%s status=%s\r\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 String& message) {
int p1 = message.indexOf('|');
int p2 = message.indexOf('|', p1 + 1);
if (p1 < 0 || p2 < 0) {
Serial.printf("RX LINK BLE: %s\r\n", message.c_str());
append_oled_text(message);
return;
}
String type = message.substring(0, p1);
String sender = message.substring(p1 + 1, p2);
if (type == "FTB") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
int p5 = message.indexOf('|', p4 + 1);
if (p3 < 0 || p4 < 0 || p5 < 0) {
Serial.printf("RX FILE malformed BEGIN: %s\r\n", message.c_str());
return;
}
handle_file_begin(sender,
message.substring(p2 + 1, p3),
(size_t)message.substring(p3 + 1, p4).toInt(),
(uint32_t)message.substring(p4 + 1, p5).toInt(),
(uint32_t)strtoul(message.substring(p5 + 1).c_str(), nullptr, 16));
return;
}
if (type == "FTD") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
if (p3 < 0 || p4 < 0) {
Serial.printf("RX FILE malformed DATA: %s\r\n", message.c_str());
return;
}
handle_file_data(sender,
(uint32_t)message.substring(p2 + 1, p3).toInt(),
(uint32_t)message.substring(p3 + 1, p4).toInt(),
message.substring(p4 + 1));
return;
}
if (type == "FTE") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
int p5 = message.indexOf('|', p4 + 1);
if (p3 < 0 || p4 < 0 || p5 < 0) {
Serial.printf("RX FILE malformed END: %s\r\n", message.c_str());
return;
}
handle_file_end(sender,
message.substring(p2 + 1, p3),
(size_t)message.substring(p3 + 1, p4).toInt(),
(uint32_t)message.substring(p4 + 1, p5).toInt(),
(uint32_t)strtoul(message.substring(p5 + 1).c_str(), nullptr, 16));
return;
}
Serial.printf("RX LINK BLE: %s\r\n", message.c_str());
}
static void on_link_packet(const RNS::Bytes& data, const RNS::Packet& packet) {
(void)packet;
parse_file_transfer_packet(String(data.toString().c_str()));
}
static void on_link_closed(RNS::Link& link) {
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_closed peer=%s link_id=%s link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(link).c_str(),
link.toString().c_str());
Serial.println("LINK CLOSED");
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_established direction=outbound peer=%s link_id=%s link_hash_crc32=%08lX peer_dest_crc32=%08lX sign_key_crc32=unavailable enc_key_crc32=unavailable link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(active_link).c_str(),
(unsigned long)bytes_crc32(active_link.hash()),
(unsigned long)bytes_crc32(peer_hash),
active_link.toString().c_str());
Serial.println("LINK ACTIVE: initiator link established");
}
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_established direction=inbound peer=%s link_id=%s link_hash_crc32=%08lX peer_dest_crc32=%08lX sign_key_crc32=unavailable enc_key_crc32=unavailable link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(active_link).c_str(),
(unsigned long)bytes_crc32(active_link.hash()),
(unsigned long)bytes_crc32(peer_hash),
active_link.toString().c_str());
Serial.println("RX LINK: inbound link established");
}
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 {
String label = app_data ? String(app_data.toString().c_str()) : String("(no label)");
if (label == node_label) {
return;
}
if (!announced_identity) {
Serial.printf("RX ANNOUNCE ignored: missing identity for hash=%s\r\n",
destination_hash.toHex().c_str());
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=announce_rx peer=%s peer_dest=%s peer_dest_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.c_str(),
peer_hash.toHex().c_str(),
(unsigned long)bytes_crc32(peer_hash));
Serial.printf("RX ANNOUNCE: label=%s hash=%s\r\n",
peer_label.c_str(), peer_hash.toHex().c_str());
}
};
static RNS::HAnnounceHandler announce_handler(new FileAnnounceHandler());
static void print_config() {
Serial.printf("Node=%s\r\n", node_label.c_str());
Serial.printf("BLE role=%s service=37145b00-442d-4a94-917f-8f42c5da28e3\r\n",
ble_impl ? ble_impl->role_name() : "?");
Serial.printf("Selected file=%s bytes=%u chunk=%u interval_ms=%lu repeat_rest_ms=%lu\r\n",
SELECTED_TEXT_NAME,
(unsigned)SELECTED_TEXT_SIZE,
(unsigned)TRANSFER_CHUNK_SIZE,
(unsigned long)TRANSFER_CHUNK_INTERVAL_MS,
(unsigned long)TRANSFER_REPEAT_INTERVAL_MS);
}
static void send_announce() {
if (!inbound_destination) {
return;
}
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=announce_tx peer=all local_dest=%s local_dest_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
inbound_destination.hash().toHex().c_str(),
(unsigned long)bytes_crc32(inbound_destination.hash()));
Serial.printf("TX ANNOUNCE: %s\r\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;
}
Serial.printf("TX LINKREQUEST: opening link to %s\r\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 String& message) {
RNS::Bytes plaintext = RNS::bytesFromString(message.c_str());
static uint32_t app_tx_seq = 0;
app_tx_seq++;
if (DebugStats::should_log_packet(app_tx_seq)) {
Serial.printf("RNSTX ms=%lu board=%s role=%s seq=%lu stage=link_plain link_id=%s len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)app_tx_seq,
link_id_hex(active_link).c_str(),
(unsigned)plaintext.size(),
(unsigned long)DebugStats::bytes_crc32(plaintext),
DebugStats::bytes_first4(plaintext).c_str(),
DebugStats::bytes_last4(plaintext).c_str());
}
RNS::Packet(active_link, RNS::bytesFromString(message.c_str())).send();
}
static bool send_next_file_packet(uint32_t now) {
if (tx_transfer.complete && (int32_t)(now - tx_transfer.next_start_ms) < 0) {
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++;
String begin = String("FTB|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
String((unsigned)SELECTED_TEXT_SIZE) + "|" + String((unsigned long)tx_transfer.total_chunks) +
"|" + hex32(tx_transfer.crc);
Serial.printf("TX FILE BEGIN: round=%lu file=%s bytes=%u chunks=%lu crc=%s\r\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;
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;
String data = String("FTD|") + node_label + "|" + String((unsigned long)tx_transfer.sequence) +
"|" + String((unsigned long)tx_transfer.total_chunks) + "|" + payload;
Serial.printf("TX FILE DATA: round=%lu seq=%lu/%lu bytes=%u preview=\"%s\"\r\n",
(unsigned long)tx_transfer.round,
(unsigned long)tx_transfer.sequence,
(unsigned long)tx_transfer.total_chunks,
(unsigned)count,
preview_payload(payload).c_str());
send_link_message(data);
return true;
}
String end = String("FTE|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
String((unsigned)SELECTED_TEXT_SIZE) + "|" + String((unsigned long)tx_transfer.total_chunks) +
"|" + hex32(tx_transfer.crc);
Serial.printf("TX FILE END: round=%lu file=%s bytes=%u chunks=%lu crc=%s next_round_in_ms=%lu\r\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);
ble_impl = new TBeamSupremeBleInterface(node_label);
ble_interface = ble_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);
Serial.printf("Local SINGLE destination: %s\r\n",
inbound_destination.hash().toHex().c_str());
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 5000) {
delay(100);
}
delay(250);
Serial.println();
RNS::loglevel(RNS::LOG_NOTICE);
node_label = default_node_label();
DebugStats::set_board(board_name_from_node_label(node_label));
DebugStats::set_role("boot");
tbeam::DisplayConfig display_config;
display_config.powerSave = false;
oled_display.begin(display_config);
show_startup_splash();
setup_reticulum();
Serial.println("Exercise 306: microReticulum BLE file transfer OLED display");
print_config();
Serial.println("microReticulum ready");
}
void loop() {
reticulum.loop();
static uint32_t next_announce_ms = 0;
static uint32_t next_transfer_ms = 0;
static uint32_t next_wait_log_ms = 0;
uint32_t now = millis();
if (ble_impl && !ble_impl->connected()) {
if (next_wait_log_ms == 0 || (int32_t)(now - next_wait_log_ms) >= 0) {
next_wait_log_ms = now + 10000;
Serial.printf("BLE %s waiting for peer\r\n", ble_impl->role_name());
}
delay(5);
return;
}
if (next_announce_ms == 0) {
uint32_t offset = 700 + ((uint32_t)node_label.charAt(node_label.length() - 1) % 5) * 400;
next_announce_ms = now + offset;
}
if (!link_active && (int32_t)(now - next_announce_ms) >= 0) {
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 && (int32_t)(now - next_transfer_ms) >= 0) {
next_transfer_ms = now + TRANSFER_CHUNK_INTERVAL_MS;
send_next_file_packet(now);
}
delay(5);
}
int _write(int file, char* ptr, int len) {
(void)file;
int wrote = Serial.write(ptr, len);
Serial.flush();
return wrote;
}

View file

@ -0,0 +1,4 @@
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;

View file

@ -0,0 +1,35 @@
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;
If you can wait and not be tired by waiting,
Or being lied about, dont deal in lies,
Or being hated, dont give way to hating,
And yet dont look too good, nor talk too wise:
If you can dream—and not make dreams your master;
If you can think—and not make thoughts your aim;
If you can meet with Triumph and Disaster
And treat those two impostors just the same;
If you can bear to hear the truth youve spoken
Twisted by knaves to make a trap for fools,
Or watch the things you gave your life to, broken,
And stoop and build em up with worn-out tools:
If you can make one heap of all your winnings
And risk it on one turn of pitch-and-toss,
And lose, and start again at your beginnings
And never breathe a word about your loss;
If you can force your heart and nerve and sinew
To serve your turn long after they are gone,
And so hold on when there is nothing in you
Except the Will which says to them: Hold on!
If you can talk with crowds and keep your virtue,
Or walk with Kings—nor lose the common touch,
If neither foes nor loving friends can hurt you,
If all men count with you, but none too much;
If you can fill the unforgiving minute
With sixty seconds worth of distance run,
Yours is the Earth and everything thats in it,
And—which is more—youll be a Man, my son!

View file

@ -0,0 +1,370 @@
The United States Constitution
We the People of the United States, in Order to form a more perfect Union, establish Justice, insure domestic Tranquility, provide for the common defence, promote the general Welfare, and secure the Blessings of Liberty to ourselves and our Posterity, do ordain and establish this Constitution for the United States of America.
The Constitutional Convention
Article I
Section 1: Congress
All legislative Powers herein granted shall be vested in a Congress of the United States, which shall consist of a Senate and House of Representatives.
Section 2: The House of Representatives
The House of Representatives shall be composed of Members chosen every second Year by the People of the several States, and the Electors in each State shall have the Qualifications requisite for Electors of the most numerous Branch of the State Legislature.
No Person shall be a Representative who shall not have attained to the Age of twenty five Years, and been seven Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State in which he shall be chosen.
Representatives and direct Taxes shall be apportioned among the several States which may be included within this Union, according to their respective Numbers, which shall be determined by adding to the whole Number of free Persons, including those bound to Service for a Term of Years, and excluding Indians not taxed, three fifths of all other Persons. The actual Enumeration shall be made within three Years after the first Meeting of the Congress of the United States, and within every subsequent Term of ten Years, in such Manner as they shall by Law direct.The Number of Representatives shall not exceed one for every thirty Thousand, but each State shall have at Least one Representative; and until such enumeration shall be made, the State of New Hampshire shall be entitled to chuse three, Massachusetts eight, Rhode-Island and Providence Plantations one, Connecticut five, New-York six, New Jersey four, Pennsylvania eight, Delaware one, Maryland six, Virginia ten, North Carolina five, South Carolina five, and Georgia three.
When vacancies happen in the Representation from any State, the Executive Authority thereof shall issue Writs of Election to fill such Vacancies.
The House of Representatives shall chuse their Speaker and other Officers;and shall have the sole Power of Impeachment.
Section 3: The Senate
The Senate of the United States shall be composed of two Senators from each State, chosen by the Legislature thereof, for six Years; and each Senator shall have one Vote.
Immediately after they shall be assembled in Consequence of the first Election, they shall be divided as equally as may be into three Classes. The Seats of the Senators of the first Class shall be vacated at the Expiration of the second Year, of the second Class at the Expiration of the fourth Year, and of the third Class at the Expiration of the sixth Year, so that one third may be chosen every second Year; and if Vacancies happen by Resignation, or otherwise, during the Recess of the Legislature of any State, the Executive thereof may make temporary Appointments until the next Meeting of the Legislature, which shall then fill such Vacancies.
No Person shall be a Senator who shall not have attained to the Age of thirty Years, and been nine Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State for which he shall be chosen.
The Vice President of the United States shall be President of the Senate, but shall have no Vote, unless they be equally divided.
The Senate shall chuse their other Officers, and also a President pro tempore, in the Absence of the Vice President, or when he shall exercise the Office of President of the United States.
The Senate shall have the sole Power to try all Impeachments. When sitting for that Purpose, they shall be on Oath or Affirmation. When the President of the United States is tried, the Chief Justice shall preside: And no Person shall be convicted without the Concurrence of two thirds of the Members present.
Judgment in Cases of Impeachment shall not extend further than to removal from Office, and disqualification to hold and enjoy any Office of honor, Trust or Profit under the United States: but the Party convicted shall nevertheless be liable and subject to Indictment, Trial, Judgment and Punishment, according to Law.
Section 4: Elections
The Times, Places and Manner of holding Elections for Senators and Representatives, shall be prescribed in each State by the Legislature thereof; but the Congress may at any time by Law make or alter such Regulations, except as to the Places of chusing Senators.
The Congress shall assemble at least once in every Year, and such Meeting shall be on the first Monday in December, unless they shall by Law appoint a different Day.
Section 5: Powers and Duties of Congress
Each House shall be the Judge of the Elections, Returns and Qualifications of its own Members,and a Majority of each shall constitute a Quorum to do Business; but a smaller Number may adjourn from day to day, and may be authorized to compel the Attendance of absent Members, in such Manner, and under such Penalties as each House may provide.
Each House may determine the Rules of its Proceedings, punish its Members for disorderly Behaviour, and, with the Concurrence of two thirds, expel a Member.
Each House shall keep a Journal of its Proceedings, and from time to time publish the same, excepting such Parts as may in their Judgment require Secrecy; and the Yeas and Nays of the Members of either House on any question shall, at the Desire of one fifth of those Present, be entered on the Journal.
Neither House, during the Session of Congress, shall, without the Consent of the other, adjourn for more than three days, nor to any other Place than that in which the two Houses shall be sitting.
Section 6: Rights and Disabilities of Members
The Senators and Representatives shall receive a Compensation for their Services, to be ascertained by Law, and paid out of the Treasury of the United States.They shall in all Cases, except Treason, Felony and Breach of the Peace, be privileged from Arrest during their Attendance at the Session of their respective Houses, and in going to and returning from the same; and for any Speech or Debate in either House, they shall not be questioned in any other Place.
No Senator or Representative shall, during the Time for which he was elected, be appointed to any civil Office under the Authority of the United States, which shall have been created, or the Emoluments whereof shall have been encreased during such time; and no Person holding any Office under the United States, shall be a Member of either House during his Continuance in Office.
Section 7: Legislative Process
All Bills for raising Revenue shall originate in the House of Representatives; but the Senate may propose or concur with Amendments as on other Bills.
Every Bill which shall have passed the House of Representatives and the Senate, shall, before it become a Law, be presented to the President of the United States; If he approve he shall sign it, but if not he shall return it, with his Objections to that House in which it shall have originated, who shall enter the Objections at large on their Journal, and proceed to reconsider it. If after such Reconsideration two thirds of that House shall agree to pass the Bill, it shall be sent, together with the Objections, to the other House, by which it shall likewise be reconsidered, and if approved by two thirds of that House, it shall become a Law. But in all such Cases the Votes of both Houses shall be determined by yeas and Nays, and the Names of the Persons voting for and against the Bill shall be entered on the Journal of each House respectively. If any Bill shall not be returned by the President within ten Days (Sundays excepted) after it shall have been presented to him, the Same shall be a Law, in like Manner as if he had signed it, unless the Congress by their Adjournment prevent its Return, in which Case it shall not be a Law.
Every Order, Resolution, or Vote to which the Concurrence of the Senate and House of Representatives may be necessary (except on a question of Adjournment) shall be presented to the President of the United States; and before the Same shall take Effect, shall be approved by him, or being disapproved by him, shall be repassed by two thirds of the Senate and House of Representatives, according to the Rules and Limitations prescribed in the Case of a Bill.
Section 8: Powers of Congress
The Congress shall have Power To lay and collect Taxes, Duties, Imposts and Excises, to pay the Debts and provide for the common Defence and general Welfare of the United States; but all Duties, Imposts and Excises shall be uniform throughout the United States;
To borrow Money on the credit of the United States;
To regulate Commerce with foreign Nations, and among the several States, and with the Indian Tribes;
To establish a uniform Rule of Naturalization, and uniform Laws on the subject of Bankruptcies throughout the United States;
To coin Money, regulate the Value thereof, and of foreign Coin, and fix the Standard of Weights and Measures;
To provide for the Punishment of counterfeiting the Securities and current Coin of the United States;
To establish Post Offices and post Roads;
To promote the Progress of Science and useful Arts, by securing for limited Times to Authors and Inventors the exclusive Right to their respective Writings and Discoveries;
To constitute Tribunals inferior to the supreme Court;
To define and punish Piracies and Felonies committed on the high Seas, and Offences against the Law of Nations;
To declare War, grant Letters of Marque and Reprisal, and make Rules concerning Captures on Land and Water;
To raise and support Armies, but no Appropriation of Money to that Use shall be for a longer Term than two Years;
To provide and maintain a Navy;
To make Rules for the Government and Regulation of the land and naval Forces;
To provide for calling forth the Militia to execute the Laws of the Union, suppress Insurrections and repel Invasions;
To provide for organizing, arming, and disciplining, the Militia, and for governing such Part of them as may be employed in the Service of the United States, reserving to the States respectively, the Appointment of the Officers, and the Authority of training the Militia according to the discipline prescribed by Congress;
To exercise exclusive Legislation in all Cases whatsoever, over such District (not exceeding ten Miles square) as may, by Cession of particular States, and the Acceptance of Congress, become the Seat of the Government of the United States, and to exercise like Authority over all Places purchased by the Consent of the Legislature of the State in which the Same shall be, for the Erection of Forts, Magazines, Arsenals, dock-Yards and other needful Buildings;-And
To make all Laws which shall be necessary and proper for carrying into Execution the foregoing Powers, and all other Powers vested by this Constitution in the Government of the United States, or in any Department or Officer thereof.
Section 9: Powers Denied Congress
The Migration or Importation of such Persons as any of the States now existing shall think proper to admit, shall not be prohibited by the Congress prior to the Year one thousand eight hundred and eight, but a Tax or duty may be imposed on such Importation, not exceeding ten dollars for each Person.
The Privilege of the Writ of Habeas Corpus shall not be suspended, unless when in Cases of Rebellion or Invasion the public Safety may require it.
No Bill of Attainder or ex post facto Law shall be passed.
No Capitation, or other direct, Tax shall be laid, unless in Proportion to the Census or enumeration herein before directed to be taken.
No Tax or Duty shall be laid on Articles exported from any State.
No Preference shall be given by any Regulation of Commerce or Revenue to the Ports of one State over those of another: nor shall Vessels bound to, or from, one State, be obliged to enter, clear, or pay Duties in another.
No Money shall be drawn from the Treasury, but in Consequence of Appropriations made by Law; and a regular Statement and Account of the Receipts and Expenditures of all public Money shall be published from time to time.
No Title of Nobility shall be granted by the United States: And no Person holding any Office of Profit or Trust under them, shall, without the Consent of the Congress, accept of any present, Emolument, Office, or Title, of any kind whatever, from any King, Prince, or foreign State.
Section 10: Powers Denied to the States
No State shall enter into any Treaty, Alliance, or Confederation; grant Letters of Marque and Reprisal; coin Money; emit Bills of Credit; make any Thing but gold and silver Coin a Tender in Payment of Debts; pass any Bill of Attainder, ex post facto Law, or Law impairing the Obligation of Contracts, or grant any Title of Nobility.
No State shall, without the Consent of the Congress, lay any Imposts or Duties on Imports or Exports, except what may be absolutely necessary for executing it's inspection Laws: and the net Produce of all Duties and Imposts, laid by any State on Imports or Exports, shall be for the Use of the Treasury of the United States; and all such Laws shall be subject to the Revision and Controul of the Congress.
No State shall, without the Consent of Congress, lay any Duty of Tonnage, keep Troops, or Ships of War in time of Peace, enter into any Agreement or Compact with another State, or with a foreign Power, or engage in War, unless actually invaded, or in such imminent Danger as will not admit of delay.
Article II
Section 1
The executive Power shall be vested in a President of the United States of America.
He shall hold his Office during the Term of four Years, and, together with the Vice President, chosen for the same Term, be elected, as follows:
Each State shall appoint, in such Manner as the Legislature thereof may direct, a Number of Electors, equal to the whole Number of Senators and Representatives to which the State may be entitled in the Congress: but no Senator or Representative, or Person holding an Office of Trust or Profit under the United States, shall be appointed an Elector.
The Electors shall meet in their respective States, and vote by Ballot for two Persons, of whom one at least shall not be an Inhabitant of the same State with themselves. And they shall make a List of all the Persons voted for, and of the Number of Votes for each; which List they shall sign and certify, and transmit sealed to the Seat of the Government of the United States, directed to the President of the Senate. The President of the Senate shall, in the Presence of the Senate and House of Representatives, open all the Certificates, and the Votes shall then be counted. The Person having the greatest Number of Votes shall be the President, if such Number be a Majority of the whole Number of Electors appointed; and if there be more than one who have such Majority, and have an equal Number of Votes, then the House of Representatives shall immediately chuse by Ballot one of them for President; and if no Person have a Majority, then from the five highest on the List the said House shall in like Manner chuse the President. But in chusing the President, the Votes shall be taken by States, the Representation from each State having one Vote; A quorum for this Purpose shall consist of a Member or Members from two thirds of the States, and a Majority of all the States shall be necessary to a Choice. In every Case, after the Choice of the President, the Person having the greatest Number of Votes of the Electors shall be the Vice President. But if there should remain two or more who have equal Votes, the Senate shall chuse from them by Ballot the Vice President.
The Congress may determine the Time of chusing the Electors, and the Day on which they shall give their Votes; which Day shall be the same throughout the United States.
No Person except a natural born Citizen, or a Citizen of the United States, at the time of the Adoption of this Constitution, shall be eligible to the Office of President; neither shall any Person be eligible to that Office who shall not have attained to the Age of thirty five Years, and been fourteen Years a Resident within the United States.
In Case of the Removal of the President from Office, or of his Death, Resignation, or Inability to discharge the Powers and Duties of the said Office, the Same shall devolve on the Vice President, and the Congress may by Law provide for the Case of Removal, Death, Resignation or Inability, both of the President and Vice President, declaring what Officer shall then act as President, and such Officer shall act accordingly, until the Disability be removed, or a President shall be elected.
The President shall, at stated Times, receive for his Services, a Compensation, which shall neither be encreased nor diminished during the Period for which he shall have been elected, and he shall not receive within that Period any other Emolument from the United States, or any of them.
Before he enter on the Execution of his Office, he shall take the following Oath or Affirmation:--"I do solemnly swear (or affirm) that I will faithfully execute the Office of President of the United States, and will to the best of my Ability, preserve, protect and defend the Constitution of the United States."
Section 2
The President shall be Commander in Chief of the Army and Navy of the United States, and of the Militia of the several States, when called into the actual Service of the United States; he may require the Opinion, in writing, of the principal Officer in each of the executive Departments, upon any Subject relating to the Duties of their respective Offices, and he shall have Power to grant Reprieves and Pardons for Offences against the United States, except in Cases of Impeachment.
He shall have Power, by and with the Advice and Consent of the Senate, to make Treaties, provided two thirds of the Senators present concur; and he shall nominate, and by and with the Advice and Consent of the Senate, shall appoint Ambassadors, other public Ministers and Consuls, Judges of the supreme Court, and all other Officers of the United States, whose Appointments are not herein otherwise provided for, and which shall be established by Law: but the Congress may by Law vest the Appointment of such inferior Officers, as they think proper, in the President alone, in the Courts of Law, or in the Heads of Departments.
The President shall have Power to fill up all Vacancies that may happen during the Recess of the Senate, by granting Commissions which shall expire at the End of their next Session.
Section 3
He shall from time to time give to the Congress Information of the State of the Union, and recommend to their Consideration such Measures as he shall judge necessary and expedient; he may, on extraordinary Occasions, convene both Houses, or either of them, and in Case of Disagreement between them, with Respect to the Time of Adjournment, he may adjourn them to such Time as he shall think proper; he shall receive Ambassadors and other public Ministers; he shall take Care that the Laws be faithfully executed, and shall Commission all the Officers of the United States.
Section 4
The President, Vice President and all civil Officers of the United States, shall be removed from Office on Impeachment for, and Conviction of, Treason, Bribery, or other high Crimes and Misdemeanors.
Article III
Section 1
The judicial Power of the United States, shall be vested in one supreme Court, and in such inferior Courts as the Congress may from time to time ordain and establish. The Judges, both of the supreme and inferior Courts, shall hold their Offices during good Behaviour, and shall, at stated Times, receive for their Services, a Compensation, which shall not be diminished during their Continuance in Office.
Section 2
The judicial Power shall extend to all Cases, in Law and Equity, arising under this Constitution, the Laws of the United States, and Treaties made, or which shall be made, under their Authority;--to all Cases affecting Ambassadors, other public Ministers and Consuls;--to all Cases of admiralty and maritime Jurisdiction;--to Controversies to which the United States shall be a Party;--to Controversies between two or more States;--between a State and Citizens of another State;--between Citizens of different States;--between Citizens of the same State claiming Lands under Grants of different States, and between a State, or the Citizens thereof, and foreign States, Citizens or Subjects.
In all Cases affecting Ambassadors, other public Ministers and Consuls, and those in which a State shall be Party, the supreme Court shall have original Jurisdiction. In all the other Cases before mentioned, the supreme Court shall have appellate Jurisdiction, both as to Law and Fact, with such Exceptions, and under such Regulations as the Congress shall make.
The Trial of all Crimes, except in Cases of Impeachment; shall be by Jury; and such Trial shall be held in the State where the said Crimes shall have been committed; but when not committed within any State, the Trial shall be at such Place or Places as the Congress may by Law have directed.
Section 3
Treason against the United States, shall consist only in levying War against them, or in adhering to their Enemies, giving them Aid and Comfort. No Person shall be convicted of Treason unless on the Testimony of two Witnesses to the same overt Act, or on Confession in open Court.
The Congress shall have Power to declare the Punishment of Treason, but no Attainder of Treason shall work Corruption of Blood, or Forfeiture except during the Life of the Person attainted.
Article IV
Section 1
Full Faith and Credit shall be given in each State to the public Acts, Records, and judicial Proceedings of every other State. And the Congress may by general Laws prescribe the Manner in which such Acts, Records and Proceedings shall be proved, and the Effect thereof.
Section 2
The Citizens of each State shall be entitled to all Privileges and Immunities of Citizens in the several States.
A Person charged in any State with Treason, Felony, or other Crime, who shall flee from Justice, and be found in another State, shall on Demand of the executive Authority of the State from which he fled, be delivered up, to be removed to the State having Jurisdiction of the Crime.
No Person held to Service or Labour in one State, under the Laws thereof, escaping into another, shall, in Consequence of any Law or Regulation therein, be discharged from such Service or Labour, but shall be delivered up on Claim of the Party to whom such Service or Labour may be due.
Section 3
New States may be admitted by the Congress into this Union; but no new State shall be formed or erected within the Jurisdiction of any other State; nor any State be formed by the Junction of two or more States, or Parts of States, without the Consent of the Legislatures of the States concerned as well as of the Congress.
The Congress shall have Power to dispose of and make all needful Rules and Regulations respecting the Territory or other Property belonging to the United States; and nothing in this Constitution shall be so construed as to Prejudice any Claims of the United States, or of any particular State.
Section 4
The United States shall guarantee to every State in this Union a Republican Form of Government, and shall protect each of them against Invasion; and on Application of the Legislature, or of the Executive (when the Legislature cannot be convened) against domestic Violence.
Article V
The Congress, whenever two thirds of both Houses shall deem it necessary, shall propose Amendments to this Constitution, or, on the Application of the Legislatures of two thirds of the several States, shall call a Convention for proposing Amendments, which, in either Case, shall be valid to all Intents and Purposes, as Part of this Constitution, when ratified by the Legislatures of three fourths of the several States, or by Conventions in three fourths thereof, as the one or the other Mode of Ratification may be proposed by the Congress; Provided that no Amendment which may be made prior to the Year One thousand eight hundred and eight shall in any Manner affect the first and fourth Clauses in the Ninth Section of the first Article; and that no State, without its Consent, shall be deprived of its equal Suffrage in the Senate.
Article VI
All Debts contracted and Engagements entered into, before the Adoption of this Constitution, shall be as valid against the United States under this Constitution, as under the Confederation.
This Constitution, and the Laws of the United States which shall be made in Pursuance thereof; and all Treaties made, or which shall be made, under the Authority of the United States, shall be the supreme Law of the Land; and the Judges in every State shall be bound thereby, any Thing in the Constitution or Laws of any State to the Contrary notwithstanding.
The Senators and Representatives before mentioned, and the Members of the several State Legislatures, and all executive and judicial Officers, both of the United States and of the several States, shall be bound by Oath or Affirmation, to support this Constitution; but no religious Test shall ever be required as a Qualification to any Office or public Trust under the United States.
Article VII
The Ratification of the Conventions of nine States, shall be sufficient for the Establishment of this Constitution between the States so ratifying the Same.
First Amendment
Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances.
Second Amendment
A well regulated Militia, being necessary to the security of a free State, the right of the people to keep and bear Arms, shall not be infringed.
Third Amendment
No Soldier shall, in time of peace be quartered in any house, without the consent of the Owner, nor in time of war, but in a manner to be prescribed by law.
Fourth Amendment
The right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures, shall not be violated, and no Warrants shall issue, but upon probable cause, supported by Oath or affirmation, and particularly describing the place to be searched, and the persons or things to be seized.
Fifth Amendment
No person shall be held to answer for a capital, or otherwise infamous crime, unless on a presentment or indictment of a Grand Jury, except in cases arising in the land or naval forces, or in the Militia, when in actual service in time of War or public danger; nor shall any person be subject for the same offence to be twice put in jeopardy of life or limb; nor shall be compelled in any criminal case to be a witness against himself, nor be deprived of life, liberty, or property, without due process of law; nor shall private property be taken for public use, without just compensation.
Sixth Amendment
In all criminal prosecutions, the accused shall enjoy the right to a speedy and public trial, by an impartial jury of the State and district wherein the crime shall have been committed, which district shall have been previously ascertained by law, and to be informed of the nature and cause of the accusation; to be confronted with the witnesses against him; to have compulsory process for obtaining witnesses in his favor, and to have the Assistance of Counsel for his defence.
Seventh Amendment
In Suits at common law, where the value in controversy shall exceed twenty dollars, the right of trial by jury shall be preserved, and no fact tried by a jury, shall be otherwise re-examined in any Court of the United States, than according to the rules of the common law.
Eighth Amendment
Excessive bail shall not be required, nor excessive fines imposed, nor cruel and unusual punishments inflicted.
Ninth Amendment
The enumeration in the Constitution, of certain rights, shall not be construed to deny or disparage others retained by the people.
10th Amendment
The powers not delegated to the United States by the Constitution, nor prohibited by it to the States, are reserved to the States respectively, or to the people.
11th Amendment
The Judicial power of the United States shall not be construed to extend to any suit in law or equity, commenced or prosecuted against one of the United States by Citizens of another State, or by Citizens or Subjects of any Foreign State.
12th Amendment
The Electors shall meet in their respective states and vote by ballot for President and Vice-President, one of whom, at least, shall not be an inhabitant of the same state with themselves; they shall name in their ballots the person voted for as President, and in distinct ballots the person voted for as Vice-President, and they shall make distinct lists of all persons voted for as President, and of all persons voted for as Vice-President, and of the number of votes for each, which lists they shall sign and certify, and transmit sealed to the seat of the government of the United States, directed to the President of the Senate; -- the President of the Senate shall, in the presence of the Senate and House of Representatives, open all the certificates and the votes shall then be counted; -- The person having the greatest number of votes for President, shall be the President, if such number be a majority of the whole number of Electors appointed; and if no person have such majority, then from the persons having the highest numbers not exceeding three on the list of those voted for as President, the House of Representatives shall choose immediately, by ballot, the President. But in choosing the President, the votes shall be taken by states, the representation from each state having one vote; a quorum for this purpose shall consist of a member or members from two-thirds of the states, and a majority of all the states shall be necessary to a choice. And if the House of Representatives shall not choose a President whenever the right of choice shall devolve upon them, before the fourth day of March next following, then the Vice-President shall act as President, as in the case of the death or other constitutional disability of the President.-- The person having the greatest number of votes as Vice-President, shall be the Vice-President, if such number be a majority of the whole number of Electors appointed, and if no person have a majority, then from the two highest numbers on the list, the Senate shall choose the Vice-President; a quorum for the purpose shall consist of two-thirds of the whole number of Senators, and a majority of the whole number shall be necessary to a choice. But no person constitutionally ineligible to the office of President shall be eligible to that of Vice-President of the United States.
13th Amendment
Section 1
Neither slavery nor involuntary servitude, except as a punishment for crime whereof the party shall have been duly convicted, shall exist within the United States, or any place subject to their jurisdiction.
Section 2
Congress shall have power to enforce this article by appropriate legislation.
14th Amendment
Section 1
All persons born or naturalized in the United States, and subject to the jurisdiction thereof, are citizens of the United States and of the State wherein they reside. No State shall make or enforce any law which shall abridge the privileges or immunities of citizens of the United States; nor shall any State deprive any person of life, liberty, or property, without due process of law; nor deny to any person within its jurisdiction the equal protection of the laws.
Section 2
Representatives shall be apportioned among the several States according to their respective numbers, counting the whole number of persons in each State, excluding Indians not taxed. But when the right to vote at any election for the choice of electors for President and Vice-President of the United States, Representatives in Congress, the Executive and Judicial officers of a State, or the members of the Legislature thereof, is denied to any of the male inhabitants of such State, being twenty-one years of age, and citizens of the United States, or in any way abridged, except for participation in rebellion, or other crime, the basis of representation therein shall be reduced in the proportion which the number of such male citizens shall bear to the whole number of male citizens twenty-one years of age in such State.
Section 3
No person shall be a Senator or Representative in Congress, or elector of President and Vice-President, or hold any office, civil or military, under the United States, or under any State, who, having previously taken an oath, as a member of Congress, or as an officer of the United States, or as a member of any State legislature, or as an executive or judicial officer of any State, to support the Constitution of the United States, shall have engaged in insurrection or rebellion against the same, or given aid or comfort to the enemies thereof. But Congress may by a vote of two-thirds of each House, remove such disability.
Section 4
The validity of the public debt of the United States, authorized by law, including debts incurred for payment of pensions and bounties for services in suppressing insurrection or rebellion, shall not be questioned. But neither the United States nor any State shall assume or pay any debt or obligation incurred in aid of insurrection or rebellion against the United States, or any claim for the loss or emancipation of any slave; but all such debts, obligations and claims shall be held illegal and void.
Section 5
The Congress shall have power to enforce, by appropriate legislation, the provisions of this article.
15th Amendment
Section 1
The right of citizens of the United States to vote shall not be denied or abridged by the United States or by any State on account of race, color, or previous condition of servitude.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
16th Amendment
The Congress shall have power to lay and collect taxes on incomes, from whatever source derived, without apportionment among the several States, and without regard to any census or enumeration.
17th Amendment
The Senate of the United States shall be composed of two Senators from each State, elected by the people thereof, for six years; and each Senator shall have one vote. The electors in each State shall have the qualifications requisite for electors of the most numerous branch of the State legislatures.
When vacancies happen in the representation of any State in the Senate, the executive authority of such State shall issue writs of election to fill such vacancies: Provided, That the legislature of any State may empower the executive thereof to make temporary appointments until the people fill the vacancies by election as the legislature may direct.
This amendment shall not be so construed as to affect the election or term of any Senator chosen before it becomes valid as part of the Constitution.
18th Amendment
Section 1
After one year from the ratification of this article the manufacture, sale, or transportation of intoxicating liquors within, the importation thereof into, or the exportation thereof from the United States and all territory subject to the jurisdiction thereof for beverage purposes is hereby prohibited.
Section 2
The Congress and the several States shall have concurrent power to enforce this article by appropriate legislation.
Section 3
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of the several States, as provided in the Constitution, within seven years from the date of the submission hereof to the States by the Congress.
19th Amendment
The right of citizens of the United States to vote shall not be denied or abridged by the United States or by any State on account of sex.
Congress shall have power to enforce this article by appropriate legislation.
20th Amendment
Section 1
The terms of the President and the Vice President shall end at noon on the 20th day of January, and the terms of Senators and Representatives at noon on the 3d day of January, of the years in which such terms would have ended if this article had not been ratified; and the terms of their successors shall then begin.
Section 2
The Congress shall assemble at least once in every year, and such meeting shall begin at noon on the 3d day of January, unless they shall by law appoint a different day.
Section 3
If, at the time fixed for the beginning of the term of the President, the President elect shall have died, the Vice President elect shall become President. If a President shall not have been chosen before the time fixed for the beginning of his term, or if the President elect shall have failed to qualify, then the Vice President elect shall act as President until a President shall have qualified; and the Congress may by law provide for the case wherein neither a President elect nor a Vice President elect shall have qualified, declaring who shall then act as President, or the manner in which one who is to act shall be selected, and such person shall act accordingly until a President or Vice President shall have qualified.
Section 4
The Congress may by law provide for the case of the death of any of the persons from whom the House of Representatives may choose a President whenever the right of choice shall have devolved upon them, and for the case of the death of any of the persons from whom the Senate may choose a Vice President whenever the right of choice shall have devolved upon them.
Section 5
Sections 1 and 2 shall take effect on the 15th day of October following the ratification of this article.
Section 6
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of three-fourths of the several States within seven years from the date of its submission.
21st Amendment
Section 1
The eighteenth article of amendment to the Constitution of the United States is hereby repealed.
Section 2
The transportation or importation into any State, Territory, or possession of the United States for delivery or use therein of intoxicating liquors, in violation of the laws thereof, is hereby prohibited.
Section 3
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by conventions in the several States, as provided in the Constitution, within seven years from the date of the submission hereof to the States by the Congress.
22nd Amendment
Section 1
No person shall be elected to the office of the President more than twice, and no person who has held the office of President, or acted as President, for more than two years of a term to which some other person was elected President shall be elected to the office of the President more than once. But this Article shall not apply to any person holding the office of President when this Article was proposed by the Congress, and shall not prevent any person who may be holding the office of President, or acting as President, during the term within which this Article becomes operative from holding the office of President or acting as President during the remainder of such term.
Section 2
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of three-fourths of the several States within seven years from the date of its submission to the States by the Congress.
23rd Amendment
Section 1
The District constituting the seat of Government of the United States shall appoint in such manner as Congress may direct:
A number of electors of President and Vice President equal to the whole number of Senators and Representatives in Congress to which the District would be entitled if it were a State, but in no event more than the least populous State; they shall be in addition to those appointed by the States, but they shall be considered, for the purposes of the election of President and Vice President, to be electors appointed by a State; and they shall meet in the District and perform such duties as provided by the twelfth article of amendment.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
24th Amendment
Section 1
The right of citizens of the United States to vote in any primary or other election for President or Vice President, for electors for President or Vice President, or for Senator or Representative in Congress, shall not be denied or abridged by the United States or any State by reason of failure to pay poll tax or other tax.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
25th Amendment
Section 1
In case of the removal of the President from office or of his death or resignation, the Vice President shall become President.
Section 2
Whenever there is a vacancy in the office of the Vice President, the President shall nominate a Vice President who shall take office upon confirmation by a majority vote of both Houses of Congress.
Section 3
Whenever the President transmits to the President pro tempore of the Senate and the Speaker of the House of Representatives his written declaration that he is unable to discharge the powers and duties of his office, and until he transmits to them a written declaration to the contrary, such powers and duties shall be discharged by the Vice President as Acting President.
Section 4
Whenever the Vice President and a majority of either the principal officers of the executive departments or of such other body as Congress may by law provide, transmit to the President pro tempore of the Senate and the Speaker of the House of Representatives their written declaration that the President is unable to discharge the powers and duties of his office, the Vice President shall immediately assume the powers and duties of the office as Acting President.
Thereafter, when the President transmits to the President pro tempore of the Senate and the Speaker of the House of Representatives his written declaration that no inability exists, he shall resume the powers and duties of his office unless the Vice President and a majority of either the principal officers of the executive department or of such other body as Congress may by law provide, transmit within four days to the President pro tempore of the Senate and the Speaker of the House of Representatives their written declaration that the President is unable to discharge the powers and duties of his office. Thereupon Congress shall decide the issue, assembling within forty-eight hours for that purpose if not in session. If the Congress, within twenty-one days after receipt of the latter written declaration, or, if Congress is not in session, within twenty-one days after Congress is required to assemble, determines by two-thirds vote of both Houses that the President is unable to discharge the powers and duties of his office, the Vice President shall continue to discharge the same as Acting President; otherwise, the President shall resume the powers and duties of his office.
26th Amendment
Section 1
The right of citizens of the United States, who are eighteen years of age or older, to vote shall not be denied or abridged by the United States or by any State on account of age.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
27th Amendment
No law, varying the compensation for the services of the Senators and Representatives, shall take effect, until an election of Representatives shall have intervened.

View file

@ -0,0 +1,205 @@
# Exercise 307: microReticulum BLE file transfer OLED resiliency staging
This exercise is staged as a copy of Exercise 306 for future out-of-range resiliency work. Exercise 306 is frozen as the working OLED file-transfer baseline.
The current code still builds on Exercise 305's equal-peer BLE file transfer and adds the shared `lib/tbeam_display` OLED service. Both boards run the same dual-role BLE interface, form a Reticulum Link, and then send the selected text file across that Link at the same time.
The OLED display is intentionally plain: incoming `FTD` file data is rendered as scrolling text only. File begin/end metadata, checksums, link state, and debug records remain on the serial monitor but are not shown on the OLED.
## Resiliency Work Queue
Exercise 307 is reserved for out-of-range and reconnection behavior. The likely next changes are:
```text
Heartbeat packets independent of user text.
Peer liveness timeout based on missed heartbeats or missing valid packets.
RSSI reporting as a weak-signal indicator, not the sole liveness source.
Forced BLE and Reticulum Link teardown when a stale half-open connection is detected.
Automatic return to advertising/scanning so peers reconnect when they come back into range.
OLED status for pairing, weak signal, peer lost, searching, and recovered.
Optional outbound message queue and ACK/resume behavior for non-demo traffic.
```
The file transfer protocol is intentionally small and visible on the serial console:
```text
FTB -> file begin, with file name, byte count, chunk count, and checksum
FTD -> numbered file data chunk
FTE -> file end, with verification metadata repeated
```
Each receiver checks byte count, chunk count, and FNV-1a checksum. After a sender completes a transfer, it rests for 10 seconds and then starts the same selected file again.
## Sample Set
Exercise 307 uses the same payload files as the Pi Zero BLE Reticulum tests:
```text
texts/If.txt 195 bytes
texts/If_full.txt 1583 bytes
texts/US_Constitution.txt 44225 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.
## Transfer Profiles
The transfer pressure is selected in `platformio.ini` with build flags:
```ini
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
```
`FILE_TRANSFER_CHUNK_SIZE` is the number of text bytes placed in each application-level `FTD` message before microReticulum wraps and encrypts it as a Link packet. Larger chunks reduce the number of packets needed for a file, but each encrypted packet becomes larger. If it grows beyond what the ESP32 BLE transport can reliably carry under simultaneous two-way traffic, Reticulum may log Link decrypt/HMAC failures because ciphertext arrived damaged or incomplete.
`FILE_TRANSFER_CHUNK_INTERVAL_MS` is the delay between application-level file chunks. Smaller intervals increase throughput, but also increase BLE write/notify pressure. With both nodes transmitting at the same time, too short a cadence can overflow buffers or expose ordering/loss issues in the current ESP32 BLE transport.
The conservative bring-up profile uses:
```text
32 byte chunks, 500 ms between chunks
```
The Pi-Zero-comparison profile uses:
```text
300 byte chunks, 100 ms between chunks
```
That is the apples-to-apples starting point for the previous Zero-to-Zero tests. Those commands requested `--message-chunk-size 900`, but the Python sender intentionally applied an internal board/Link-budget cap before sending. The run17 report for the Constitution transfer shows effective chunk data around 300 to 316 bytes, not 900 bytes, with roughly 100 ms sender pacing.
`VERIFY_FAIL` means the file protocol received an incomplete or corrupted transfer. A Reticulum Link HMAC/decryption error means corruption happened earlier, before the file protocol could parse the packet.
## Priority
See Exercise 304_microReticulum_ble_dual_role_ping_pong README.md for explanation of "deterministic tie-breaker" of the role of client and server based on the ESP32 MAC.
## Environments
Conservative ESP32 bring-up environments:
```text
tbeam_if
tbeam_if_full
tbeam_constitution
```
Pi-Zero-comparison environments:
```text
tbeam_if_pi_zero_profile
tbeam_if_full_pi_zero_profile
tbeam_constitution_pi_zero_profile
```
## Build Once, Upload Twice
Each selected text environment produces one firmware image. Build it once, then upload that same image to both boards.
Build the short If sample:
```bash
source /home/jlpoole/rnsenv/bin/activate
cd /usr/local/src/microreticulum/microReticulumTbeam
pio run -d exercises/307_microReticulum_ble_file_transfer_oeld_resilency -e tbeam_if
```
Build the Pi-Zero-profile Constitution sample:
```bash
source /home/jlpoole/rnsenv/bin/activate
cd /usr/local/src/microreticulum/microReticulumTbeam
pio run -d exercises/307_microReticulum_ble_file_transfer_oeld_resilency -e tbeam_constitution_pi_zero_profile
```
After the build succeeds, upload the same environment to both boards. These commands may be run one after the other:
```bash
pio run -d exercises/307_microReticulum_ble_file_transfer_oeld_resilency -e tbeam_if -t upload --upload-port /dev/ttytDAN
pio run -d exercises/307_microReticulum_ble_file_transfer_oeld_resilency -e tbeam_if -t upload --upload-port /dev/ttytBOB
```
Use the same `-e` value in upload commands that you used for the build.
For strict parallel uploads, use `esptool.py` directly against the already-built artifacts. This avoids two concurrent `pio run` processes touching the same `.pio` build directory:
```bash
cd /usr/local/src/microreticulum/microReticulumTbeam/exercises/307_microReticulum_ble_file_transfer_oeld_resilency
esptool.py --chip esp32s3 --port /dev/ttytDAN --baud 460800 write_flash -z \
0x0000 .pio/build/tbeam_if/bootloader.bin \
0x8000 .pio/build/tbeam_if/partitions.bin \
0xe000 /home/jlpoole/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/tbeam_if/firmware.bin &
esptool.py --chip esp32s3 --port /dev/ttytBOB --baud 460800 write_flash -z \
0x0000 .pio/build/tbeam_if/bootloader.bin \
0x8000 .pio/build/tbeam_if/partitions.bin \
0xe000 /home/jlpoole/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin \
0x10000 .pio/build/tbeam_if/firmware.bin &
wait
```
For another environment, replace each `.pio/build/tbeam_if/` path with that environment's build directory.
Monitor:
```bash
pio device monitor -p /dev/ttytDAN -b 115200
pio device monitor -p /dev/ttytBOB -b 115200
```
## Expected Output
Once the Link is active, both nodes start sending:
```text
Selected file=If.txt bytes=195 chunk=32 interval_ms=500 repeat_rest_ms=10000
TX FILE BEGIN: round=1 file=If.txt bytes=195 chunks=7 crc=...
TX FILE DATA: round=1 seq=1/7 bytes=32 preview="If you can keep your head..."
TX FILE END: round=1 file=If.txt bytes=195 chunks=7 crc=... next_round_in_ms=10000
```
The receiver verifies the transfer:
```text
RX FILE BEGIN: from=Node-... file=If.txt bytes=195 chunks=7 crc=...
RX FILE DATA: from=Node-... seq=1/7 bytes=32 preview="If you can keep your head..."
RX FILE END: from=Node-... file=If.txt received=195/195 chunks=7/7 crc=... status=OK
```
Ten seconds after `TX FILE END`, the same selected file starts again. This rest interval is measured after transfer completion, so large files get the same 10-second pause before the next round.
## Debug Lines
Exercise 307 includes machine-parseable debug records for the role-dependent BLE/Link failure investigation. Each record is one line of `key=value` fields.
```text
RNSLINK Link/announce events, peer hashes, Link ids, and Link object ids.
RNSTX Application plaintext sends and encrypted Reticulum packets handed to BLE.
RNSRX Reassembled BLE packets immediately before Reticulum receives them.
RNSDEC Link encrypt/decrypt attempts and failures from microReticulum Link.cpp.
RNSBLE BLE connect, identity, fragment TX/RX, and packet assembly events.
RNSQUEUE BLE RX queue depth, pushes, pops, drops, and high-water marks.
RNSMEM Heap, largest block, PSRAM, and current task stack high-water mark.
RNSERR Classified adapter errors: reassembly gaps, short fragments, queue overflow, allocation failure.
```
Normal packet logging is rate-limited: first 25 packets, then every 25th packet, then all packets for two seconds after the first failure. Define `RNS_DEBUG_VERBOSE=1` in `platformio.ini` to print every packet fingerprint.
Debug hooks are inserted at these points:
```text
src/TBeamSupremeBleInterface.cpp BLE callback boundary, fragment TX/RX, reassembly, queue, packet handoff.
src/main.cpp board-name mapping, announce/link events, application Link send.
/usr/local/src/microreticulum/microReticulum/src/Link.cpp
Link encrypt/decrypt token fingerprints and classified decrypt failures.
```
Use the CRC fields to split the failure:
```text
Same RNSTX token crc and RNSRX/RNSDEC token crc, but HMAC_INVALID -> likely wrong key/session/Link context.
Different RNSTX token crc and RNSRX/RNSDEC token crc -> BLE fragmentation, reassembly, queue, or buffer corruption.
RNSERR rx_reassembly_gap/timeout or queue_overflow -> mechanical adapter failure before Reticulum decrypt.
RNSMEM largest_block or min_heap collapse before failures -> heap pressure or fragmentation.
```

View file

@ -0,0 +1,116 @@
; Exercise 307: microReticulum BLE file transfer OLED resiliency staging
[platformio]
default_envs = tbeam_if
[env]
platform = espressif32
framework = arduino
board = esp32-s3-devkitc-1
monitor_speed = 115200
upload_speed = 460800
board_build.partitions = huge_app.csv
extra_scripts = pre:scripts/embed_text.py
custom_text_source = texts/If.txt
lib_extra_dirs =
../../lib
build_flags =
-Wall
-Wno-missing-field-initializers
-Wno-format
-D RNS_USE_FS
-D RNS_PERSIST_PATHS
-D USTORE_USE_UNIVERSALFS
-D MSGPACK_USE_BOOST=OFF
-D MCU_ESP32
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D OLED_SDA=17
-D OLED_SCL=18
-D OLED_ADDR=0x3C
lib_deps =
Wire
olikraus/U8g2@^2.36.4
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
[env:tbeam_if]
extends = env
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_full]
extends = env
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_constitution]
extends = env
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=32
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=500
[env:tbeam_if_pi_zero_profile]
extends = env
custom_text_source = texts/If.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_if_full_pi_zero_profile]
extends = env
custom_text_source = texts/If_full.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam_constitution_pi_zero_profile]
extends = env
custom_text_source = texts/US_Constitution.txt
build_flags =
${env.build_flags}
-D FILE_TRANSFER_CHUNK_SIZE=300
-D FILE_TRANSFER_CHUNK_INTERVAL_MS=100
[env:tbeam]
extends = env
[env:amy]
extends = env
upload_port = /dev/ttytAMY
monitor_port = /dev/ttytAMY
[env:bob]
extends = env
upload_port = /dev/ttytBOB
monitor_port = /dev/ttytBOB
[env:cy]
extends = env
upload_port = /dev/ttytCY
monitor_port = /dev/ttytCY
[env:dan]
extends = env
upload_port = /dev/ttytDAN
monitor_port = /dev/ttytDAN
[env:ed]
extends = env
upload_port = /dev/ttytED
monitor_port = /dev/ttytED

View file

@ -0,0 +1,49 @@
from pathlib import Path
Import("env")
project_dir = Path(env.subst("$PROJECT_DIR"))
source_name = env.GetProjectOption("custom_text_source", "texts/If.txt")
source_path = Path(source_name)
if not source_path.is_absolute():
source_path = project_dir / source_path
if not source_path.exists():
raise RuntimeError(f"custom_text_source file not found: {source_path}")
data = source_path.read_bytes()
generated_dir = Path(env.subst("$BUILD_DIR")) / "generated"
generated_dir.mkdir(parents=True, exist_ok=True)
header_path = generated_dir / "SelectedText.h"
symbol_name = source_path.name.replace("\\", "/")
lines = [
"#pragma once",
"",
"#include <Arduino.h>",
"#include <pgmspace.h>",
"",
f'static constexpr const char* SELECTED_TEXT_NAME = "{symbol_name}";',
f"static constexpr size_t SELECTED_TEXT_SIZE = {len(data)};",
"static const uint8_t SELECTED_TEXT[] PROGMEM = {",
]
for offset in range(0, len(data), 16):
chunk = data[offset : offset + 16]
values = ", ".join(f"0x{byte:02x}" for byte in chunk)
lines.append(f" {values},")
lines.extend(
[
"};",
"",
"static inline uint8_t read_selected_text_byte(size_t offset) {",
" return pgm_read_byte(SELECTED_TEXT + offset);",
"}",
"",
]
)
header_path.write_text("\n".join(lines))
env.Append(CPPPATH=[str(generated_dir)])
print(f"Embedded {source_path} ({len(data)} bytes) -> {header_path}")

View file

@ -0,0 +1,172 @@
#include "DebugStats.h"
#if RNS_DEBUG_INSTRUMENTATION
#include <esp_heap_caps.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include <freertos/task.h>
#endif
static String debug_board = "unknown";
static const char* debug_role = "unknown";
static uint32_t debug_failure_window_until = 0;
static uint32_t debug_next_mem_ms = 0;
#if RNS_DEBUG_INSTRUMENTATION
static SemaphoreHandle_t debug_serial_mutex = nullptr;
static char debug_log_buffer[512];
static SemaphoreHandle_t serial_mutex() {
if (!debug_serial_mutex) {
debug_serial_mutex = xSemaphoreCreateMutex();
}
return debug_serial_mutex;
}
#endif
void DebugStats::set_board(const String& board) {
debug_board = board.length() ? board : String("unknown");
}
void DebugStats::set_role(const char* role) {
debug_role = role ? role : "unknown";
}
const char* DebugStats::board() {
return debug_board.c_str();
}
const char* DebugStats::role() {
return debug_role;
}
uint32_t DebugStats::crc32(const uint8_t* data, size_t len) {
uint32_t crc = 0xFFFFFFFFUL;
for (size_t i = 0; i < len; ++i) {
crc ^= data[i];
for (uint8_t bit = 0; bit < 8; ++bit) {
crc = (crc >> 1) ^ (0xEDB88320UL & (0UL - (crc & 1UL)));
}
}
return ~crc;
}
String DebugStats::edge_hex(const uint8_t* data, size_t len, bool first) {
char out[9];
out[0] = '\0';
if (!data || len == 0) {
return String("none");
}
size_t edge_len = len < 4 ? len : 4;
size_t start = first ? 0 : len - edge_len;
char* cursor = out;
for (size_t i = 0; i < edge_len; ++i) {
snprintf(cursor, 3, "%02X", data[start + i]);
cursor += 2;
}
*cursor = '\0';
return String(out);
}
uint32_t DebugStats::bytes_crc32(const RNS::Bytes& bytes) {
return crc32(bytes.data(), bytes.size());
}
String DebugStats::bytes_first4(const RNS::Bytes& bytes) {
return edge_hex(bytes.data(), bytes.size(), true);
}
String DebugStats::bytes_last4(const RNS::Bytes& bytes) {
return edge_hex(bytes.data(), bytes.size(), false);
}
bool DebugStats::should_log_packet(uint32_t seq) {
#if RNS_DEBUG_VERBOSE
(void)seq;
return true;
#else
uint32_t now = millis();
return seq <= 25 || (seq % 25) == 0 || (int32_t)(debug_failure_window_until - now) > 0;
#endif
}
void DebugStats::note_failure() {
debug_failure_window_until = millis() + 2000;
}
void DebugStats::vlogf(const char* format, va_list args) {
#if RNS_DEBUG_INSTRUMENTATION
SemaphoreHandle_t mutex = serial_mutex();
if (mutex) {
xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
}
vsnprintf(debug_log_buffer, sizeof(debug_log_buffer), format, args);
Serial.print(debug_log_buffer);
if (mutex) {
xSemaphoreGive(mutex);
}
#else
(void)format;
(void)args;
#endif
}
void DebugStats::logf(const char* format, ...) {
va_list args;
va_start(args, format);
vlogf(format, args);
va_end(args);
}
void DebugStats::println(const char* line) {
#if RNS_DEBUG_INSTRUMENTATION
SemaphoreHandle_t mutex = serial_mutex();
if (mutex) {
xSemaphoreTake(mutex, pdMS_TO_TICKS(50));
}
Serial.println(line ? line : "");
if (mutex) {
xSemaphoreGive(mutex);
}
#else
(void)line;
#endif
}
void DebugStats::print_mem(const char* event) {
#if RNS_DEBUG_INSTRUMENTATION
UBaseType_t stack_words = uxTaskGetStackHighWaterMark(NULL);
logf("RNSMEM ms=%lu board=%s role=%s event=%s task=%s free_heap=%u min_heap=%u largest_block=%u free_psram=%u min_psram=%u stack_hw_words=%u stack_hw_bytes=%u\r\n",
(unsigned long)millis(),
board(),
role(),
event ? event : "periodic",
pcTaskGetName(NULL),
(unsigned)heap_caps_get_free_size(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT),
(unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
(unsigned)heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM),
(unsigned)stack_words,
(unsigned)(stack_words * sizeof(StackType_t)));
#else
(void)event;
#endif
}
void DebugStats::maybe_print_periodic() {
#if RNS_DEBUG_INSTRUMENTATION
uint32_t now = millis();
if (debug_next_mem_ms == 0 || (int32_t)(now - debug_next_mem_ms) >= 0) {
debug_next_mem_ms = now + 1000;
print_mem("periodic");
}
#endif
}
extern "C" const char* rns_debug_board_name() {
return DebugStats::board();
}
extern "C" const char* rns_debug_role_name() {
return DebugStats::role();
}

View file

@ -0,0 +1,36 @@
#pragma once
#include <Arduino.h>
#include <Bytes.h>
#include <stdarg.h>
#ifndef RNS_DEBUG_INSTRUMENTATION
#define RNS_DEBUG_INSTRUMENTATION 1
#endif
#ifndef RNS_DEBUG_VERBOSE
#define RNS_DEBUG_VERBOSE 0
#endif
class DebugStats {
public:
static void set_board(const String& board);
static void set_role(const char* role);
static const char* board();
static const char* role();
static uint32_t crc32(const uint8_t* data, size_t len);
static String edge_hex(const uint8_t* data, size_t len, bool first);
static uint32_t bytes_crc32(const RNS::Bytes& bytes);
static String bytes_first4(const RNS::Bytes& bytes);
static String bytes_last4(const RNS::Bytes& bytes);
static bool should_log_packet(uint32_t seq);
static void note_failure();
static void logf(const char* format, ...);
static void vlogf(const char* format, va_list args);
static void println(const char* line);
static void print_mem(const char* event = "periodic");
static void maybe_print_periodic();
};

View file

@ -0,0 +1,780 @@
#include "TBeamSupremeBleInterface.h"
#include "DebugStats.h"
#include <BLE2902.h>
#include <BLEService.h>
#include <Cryptography/Random.h>
#include <Identity.h>
#include <Log.h>
#include <algorithm>
#include <string.h>
using namespace RNS;
static TBeamSupremeBleInterface* active_ble_interface = nullptr;
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];
}
class TBeamSupremeBleInterface::ServerCallbacks : public BLEServerCallbacks {
public:
explicit ServerCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onConnect(BLEServer* server) override {
(void)server;
if (_owner->_client && _owner->_client->isConnected()) {
Serial.println("BLE dual-role: inbound connection arrived while outbound is active; keeping outbound link");
server->disconnect(server->getConnId());
return;
}
_owner->_connected = true;
_owner->_server_handshake_received = false;
DebugStats::set_role("server");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=connect conn=%u direction=inbound\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
Serial.println("BLE dual-role: central connected to local server");
INFO("BLE central connected to local server");
}
void onDisconnect(BLEServer* server) override {
(void)server;
if (_owner->_client && _owner->_client->isConnected()) {
_owner->_connected = true;
DebugStats::set_role("client");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=disconnect conn=%u direction=inbound action=ignored_outbound_active\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
return;
}
_owner->_connected = false;
_owner->_server_handshake_received = false;
_owner->reset_reassembly();
DebugStats::set_role("idle");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=disconnect conn=%u direction=inbound\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)server->getConnId());
Serial.println("BLE dual-role: central disconnected; restarting advertising");
INFO("BLE central disconnected; restarting advertising");
BLEDevice::startAdvertising();
}
private:
TBeamSupremeBleInterface* _owner;
};
class TBeamSupremeBleInterface::RxCallbacks : public BLECharacteristicCallbacks {
public:
explicit RxCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onWrite(BLECharacteristic* characteristic) override {
std::string value = characteristic->getValue();
if (value.empty()) {
return;
}
if (!_owner->_server_handshake_received && value.size() == 16) {
_owner->_server_handshake_received = true;
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=identity_handshake_rx len=%u crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)value.size(),
(unsigned long)DebugStats::crc32(reinterpret_cast<const uint8_t*>(value.data()), value.size()));
Serial.println("BLE dual-role: identity handshake received by local server");
INFOF("BLE identity handshake received: %d bytes", (int)value.size());
return;
}
// BLECharacteristic owns/reuses its callback storage; handle_fragment copies
// payload bytes into RNS::Bytes before data can outlive this callback.
_owner->handle_fragment(reinterpret_cast<const uint8_t*>(value.data()), value.size());
}
private:
TBeamSupremeBleInterface* _owner;
};
class TBeamSupremeBleInterface::AdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
public:
explicit AdvertisedDeviceCallbacks(TBeamSupremeBleInterface* owner) : _owner(owner) {}
void onResult(BLEAdvertisedDevice advertised_device) override {
bool has_service = advertised_device.haveServiceUUID() &&
advertised_device.isAdvertisingService(BLEUUID(TBeamSupremeBleInterface::SERVICE_UUID));
bool has_rns_name = advertised_device.haveName() &&
advertised_device.getName().rfind("RNS-", 0) == 0;
if ((has_service || has_rns_name) || _owner->_scan_report_count < 5) {
Serial.printf("BLE scan: addr=%s name=%s service=%s rssi=%d\r\n",
advertised_device.getAddress().toString().c_str(),
advertised_device.haveName() ? advertised_device.getName().c_str() : "",
has_service ? "yes" : "no",
advertised_device.getRSSI());
_owner->_scan_report_count++;
}
if (!has_service && !has_rns_name) {
return;
}
String peer_label = _owner->advertised_node_label(advertised_device);
String peer_address = String(advertised_device.getAddress().toString().c_str());
if (!_owner->should_connect_to_peer(peer_address)) {
Serial.printf("BLE dual-role: peer addr=%s label=%s has lower tie-breaker; staying available\r\n",
peer_address.c_str(),
peer_label.length() ? peer_label.c_str() : "(unknown)");
return;
}
BLEDevice::getScan()->stop();
delete _owner->_advertised_device;
_owner->_advertised_device = new BLEAdvertisedDevice(advertised_device);
_owner->_do_connect = true;
_owner->_scanning = false;
Serial.printf("BLE dual-role: peer candidate found: %s label=%s\r\n",
advertised_device.getAddress().toString().c_str(),
peer_label.c_str());
INFOF("BLE peer found: %s", advertised_device.getAddress().toString().c_str());
}
private:
TBeamSupremeBleInterface* _owner;
};
TBeamSupremeBleInterface::TBeamSupremeBleInterface(const String& node_label, const char* name) : InterfaceImpl(name) {
_node_label = node_label;
_IN = true;
_OUT = true;
_bitrate = 1000000;
_HW_MTU = BLE_PAYLOAD_SIZE;
}
TBeamSupremeBleInterface::~TBeamSupremeBleInterface() {
stop();
delete _advertised_device;
}
bool TBeamSupremeBleInterface::start() {
if (_started) {
return true;
}
active_ble_interface = this;
BLEDevice::init(std::string("RNS-") + _node_label.c_str());
BLEDevice::setMTU(BLE_ATT_MTU);
_local_address = String(BLEDevice::getAddress().toString().c_str());
Serial.printf("BLE dual-role: local addr=%s label=%s\r\n", _local_address.c_str(), _node_label.c_str());
Serial.println("BLE dual-role: starting advertiser and scanner");
INFO("BLE starting as dual-role client/server");
start_peripheral();
start_central_scan();
_started = true;
_online = true;
return true;
}
void TBeamSupremeBleInterface::stop() {
_online = false;
_connected = false;
if (_client && _client->isConnected()) {
_client->disconnect();
}
DebugStats::set_role("idle");
}
void TBeamSupremeBleInterface::loop() {
if (!_online) {
return;
}
if (_do_connect && _advertised_device) {
_do_connect = false;
if (!connect_to_advertised_device(_advertised_device)) {
_connected = false;
_next_scan_ms = millis() + SCAN_RETRY_MS;
}
}
if (!_connected && !_scanning && (int32_t)(millis() - _next_scan_ms) >= 0) {
start_central_scan();
}
if (_reassembly_started_ms != 0 && millis() - _reassembly_started_ms > REASSEMBLY_TIMEOUT_MS) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_timeout detail=msg_id_%lu received=%u expected=%u bytes=%u expected_bytes=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_received_fragments,
(unsigned)_expected_total,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_expected_message_len);
DebugStats::print_mem("rx_reassembly_timeout");
WARNING("BLE reassembly timeout; dropping partial packet");
reset_reassembly();
}
RNS::Bytes packet({RNS::Type::NONE});
while (dequeue_packet(packet)) {
_packet_rx_seq++;
if (DebugStats::should_log_packet(_packet_rx_seq)) {
DebugStats::logf("RNSRX ms=%lu board=%s role=%s seq=%lu link_id=unknown len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_packet_rx_seq,
(unsigned)packet.size(),
(unsigned long)DebugStats::bytes_crc32(packet),
DebugStats::bytes_first4(packet).c_str(),
DebugStats::bytes_last4(packet).c_str());
DebugStats::logf("RNSDEC ms=%lu board=%s role=%s event=attempt link_id=unknown token_len=%u token_crc32=%08lX link_obj=unknown\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)packet.size(),
(unsigned long)DebugStats::bytes_crc32(packet));
}
InterfaceImpl::handle_incoming(packet);
}
print_queue_stats(false);
DebugStats::maybe_print_periodic();
}
void TBeamSupremeBleInterface::start_peripheral() {
_server = BLEDevice::createServer();
_server->setCallbacks(new ServerCallbacks(this));
BLEService* service = _server->createService(SERVICE_UUID);
_tx_characteristic = service->createCharacteristic(
TX_UUID,
BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
_tx_characteristic->addDescriptor(new BLE2902());
BLECharacteristic* rx_characteristic = service->createCharacteristic(
RX_UUID,
BLECharacteristic::PROPERTY_WRITE | BLECharacteristic::PROPERTY_WRITE_NR);
rx_characteristic->setCallbacks(new RxCallbacks(this));
_identity_characteristic = service->createCharacteristic(
IDENTITY_UUID,
BLECharacteristic::PROPERTY_READ);
RNS::Bytes identity = local_identity_hash();
_identity_characteristic->setValue((uint8_t*)identity.data(), identity.size());
service->start();
BLEAdvertising* advertising = BLEDevice::getAdvertising();
advertising->addServiceUUID(SERVICE_UUID);
advertising->setScanResponse(true);
advertising->setMinPreferred(0x00);
BLEDevice::startAdvertising();
Serial.println("BLE dual-role: advertising Reticulum service");
INFO("BLE advertising Reticulum service");
}
void TBeamSupremeBleInterface::start_central_scan() {
_scanning = true;
_next_scan_ms = millis() + SCAN_RETRY_MS;
_scan_report_count = 0;
BLEScan* scan = BLEDevice::getScan();
scan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks(this), true);
scan->setInterval(1349);
scan->setWindow(449);
scan->setActiveScan(true);
Serial.println("BLE dual-role: scanning for Reticulum service");
scan->start(5, false);
if (!_do_connect && !_connected) {
_scanning = false;
_next_scan_ms = millis() + SCAN_RETRY_MS;
Serial.println("BLE dual-role: scan complete; no eligible peer found");
}
scan->clearResults();
INFO("BLE scanning for Reticulum service");
}
bool TBeamSupremeBleInterface::connect_to_advertised_device(BLEAdvertisedDevice* device) {
Serial.printf("BLE dual-role: connecting to %s\r\n", device->getAddress().toString().c_str());
INFOF("BLE connecting to %s", device->getAddress().toString().c_str());
_client = BLEDevice::createClient();
if (!_client->connect(device)) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_connect_failed detail=connect_failed\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: connect failed");
WARNING("BLE connect failed");
return false;
}
_client->setMTU(BLE_ATT_MTU);
BLERemoteService* service = _client->getService(BLEUUID(SERVICE_UUID));
if (!service) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_service_missing detail=reticulum_service_not_found\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: Reticulum service not found after connect");
WARNING("BLE Reticulum service not found after connect");
_client->disconnect();
return false;
}
_remote_rx_characteristic = service->getCharacteristic(BLEUUID(RX_UUID));
_remote_tx_characteristic = service->getCharacteristic(BLEUUID(TX_UUID));
BLERemoteCharacteristic* identity_characteristic = service->getCharacteristic(BLEUUID(IDENTITY_UUID));
if (!_remote_rx_characteristic || !_remote_tx_characteristic) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=ble_characteristic_missing detail=rx_or_tx_missing\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role());
Serial.println("BLE dual-role: RX/TX characteristics not found");
WARNING("BLE Reticulum RX/TX characteristics not found");
_client->disconnect();
return false;
}
if (identity_characteristic && identity_characteristic->canRead()) {
std::string peer_identity = identity_characteristic->readValue();
DebugStats::logf("RNSBLE ms=%lu board=%s role=client event=identity_read len=%u crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
(unsigned)peer_identity.size(),
(unsigned long)DebugStats::crc32(reinterpret_cast<const uint8_t*>(peer_identity.data()), peer_identity.size()));
Serial.printf("BLE dual-role: peer identity read: %d bytes\r\n", (int)peer_identity.size());
INFOF("BLE peer identity read: %d bytes", (int)peer_identity.size());
}
if (_remote_tx_characteristic->canNotify()) {
_remote_tx_characteristic->registerForNotify(client_notify_callback);
} else {
Serial.println("BLE dual-role: peer TX characteristic does not notify");
}
RNS::Bytes identity = local_identity_hash();
_remote_rx_characteristic->writeValue((uint8_t*)identity.data(), identity.size(), true);
_connected = true;
_scanning = false;
DebugStats::set_role("client");
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=connect conn=client direction=outbound peer=%s identity_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
device->getAddress().toString().c_str(),
(unsigned long)DebugStats::bytes_crc32(identity));
Serial.println("BLE dual-role: connected and identity handshake sent");
INFO("BLE connected and identity handshake sent");
return true;
}
bool TBeamSupremeBleInterface::should_connect_to_peer(const String& peer_address) const {
if (peer_address.length() == 0 || peer_address == _local_address) {
return false;
}
return strcmp(_local_address.c_str(), peer_address.c_str()) < 0;
}
String TBeamSupremeBleInterface::advertised_node_label(BLEAdvertisedDevice& device) const {
if (!device.haveName()) {
return "";
}
std::string name = device.getName();
if (name.rfind("RNS-", 0) != 0) {
return "";
}
return String(name.substr(4).c_str());
}
void TBeamSupremeBleInterface::send_outgoing(const Bytes& data) {
if (!_online || !_connected) {
return;
}
size_t total = (data.size() + BLE_PAYLOAD_SIZE - 1) / BLE_PAYLOAD_SIZE;
if (total == 0 || total > 65535) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=tx_fragment_count_invalid detail=packet_len_%u fragments_%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)data.size(),
(unsigned)total);
DebugStats::print_mem("tx_fragment_count_invalid");
WARNINGF("BLE cannot fragment packet of size %d", (int)data.size());
return;
}
_packet_tx_seq++;
uint32_t msg_id = ++_tx_message_id;
if (DebugStats::should_log_packet(_packet_tx_seq)) {
DebugStats::logf("RNSTX ms=%lu board=%s role=%s seq=%lu link_id=unknown msg_id=%lu len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_packet_tx_seq,
(unsigned long)msg_id,
(unsigned)data.size(),
(unsigned long)DebugStats::bytes_crc32(data),
DebugStats::bytes_first4(data).c_str(),
DebugStats::bytes_last4(data).c_str());
}
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());
memcpy(fragment + FRAG_HEADER_SIZE, data.data() + offset, chunk);
_ble_frag_tx_seq++;
if (DebugStats::should_log_packet(_packet_tx_seq)) {
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=fragment_tx conn=%s frag_seq=%lu frag_index=%u frag_total=%u msg_id=%lu msg_off=%u msg_total=%u frag_len=%u payload_len=%u frag_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
_client && _client->isConnected() ? "client" : "server",
(unsigned long)_ble_frag_tx_seq,
(unsigned)i,
(unsigned)total,
(unsigned long)msg_id,
(unsigned)offset,
(unsigned)data.size(),
(unsigned)(FRAG_HEADER_SIZE + chunk),
(unsigned)chunk,
(unsigned long)DebugStats::crc32(fragment, FRAG_HEADER_SIZE + chunk));
}
send_fragment(fragment, FRAG_HEADER_SIZE + chunk);
delay(8);
}
InterfaceImpl::handle_outgoing(data);
}
void TBeamSupremeBleInterface::send_fragment(const uint8_t* data, size_t len) {
if (_client && _client->isConnected() && _remote_rx_characteristic) {
_remote_rx_characteristic->writeValue((uint8_t*)data, len, true);
return;
}
if (_tx_characteristic) {
_tx_characteristic->setValue((uint8_t*)data, len);
_tx_characteristic->notify();
delay(20);
}
}
void TBeamSupremeBleInterface::handle_fragment(const uint8_t* data, size_t len) {
if (len < FRAG_HEADER_SIZE) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_fragment_too_short detail=len_%u\r\n",
(unsigned long)millis(), DebugStats::board(), DebugStats::role(), (unsigned)len);
DebugStats::print_mem("rx_fragment_too_short");
WARNINGF("BLE fragment too short: %d", (int)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;
_ble_frag_rx_seq++;
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)) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_fragment_header_bad detail=type_%u_ver_%u_seq_%u_total_%u_msg_id_%lu_msg_len_%lu_len_%u_payload_%u_expected_frags_%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)fragment_type,
(unsigned)version,
(unsigned)sequence,
(unsigned)total,
(unsigned long)msg_id,
(unsigned long)msg_len,
(unsigned)len,
(unsigned)payload_len,
(unsigned long)expected_fragments);
DebugStats::print_mem("rx_fragment_header_bad");
WARNING("BLE invalid fragment header");
reset_reassembly();
return;
}
if (sequence == 0) {
if (_reassembly_started_ms != 0 && _received_fragments != _expected_total) {
_reassembly_gaps++;
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_restart detail=old_msg_id_%lu_new_msg_id_%lu_received_%u_expected_%u bytes_%u gaps=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned long)msg_id,
(unsigned)_received_fragments,
(unsigned)_expected_total,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_reassembly_gaps);
DebugStats::print_mem("rx_reassembly_restart");
}
reset_reassembly();
_expected_total = total;
_received_fragments = 0;
_expected_message_len = msg_len;
_reassembly_started_ms = millis();
_current_rx_message_id = msg_id;
if (msg_id > _rx_message_id) {
_rx_message_id = msg_id;
}
} else if (_reassembly_started_ms == 0 || msg_id != _current_rx_message_id) {
_reassembly_out_of_order++;
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_stale_fragment detail=msg_id_%lu_current_%lu_seq_%u_received_%u total_%u expected_total_%u stale=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)msg_id,
(unsigned long)_current_rx_message_id,
(unsigned)sequence,
(unsigned)_received_fragments,
(unsigned)total,
(unsigned)_expected_total,
(unsigned long)_reassembly_out_of_order);
DebugStats::print_mem("rx_reassembly_stale_fragment");
return;
}
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=fragment_rx conn=%s frag_seq=%lu frag_index=%u frag_total=%u msg_id=%lu msg_off=%u msg_total=%lu frag_len=%u payload_len=%u frag_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
_client && _client->isConnected() ? "client" : "server",
(unsigned long)_ble_frag_rx_seq,
(unsigned)sequence,
(unsigned)total,
(unsigned long)_current_rx_message_id,
(unsigned)expected_offset,
(unsigned long)msg_len,
(unsigned)len,
(unsigned)payload_len,
(unsigned long)DebugStats::crc32(data, len));
if (_expected_total != total || _expected_message_len != msg_len || sequence != _received_fragments) {
if (sequence < _received_fragments) {
_reassembly_duplicates++;
} else if (sequence > _received_fragments) {
_reassembly_gaps++;
} else {
_reassembly_out_of_order++;
}
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_gap detail=msg_id_%lu_seq_%u_expected_seq_%u_total_%u_expected_total_%u_msg_len_%lu_expected_len_%lu gaps=%lu duplicates=%lu out_of_order=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)sequence,
(unsigned)_received_fragments,
(unsigned)total,
(unsigned)_expected_total,
(unsigned long)msg_len,
(unsigned long)_expected_message_len,
(unsigned long)_reassembly_gaps,
(unsigned long)_reassembly_duplicates,
(unsigned long)_reassembly_out_of_order);
DebugStats::print_mem("rx_reassembly_gap");
WARNING("BLE out-of-order fragment; dropping partial packet");
reset_reassembly();
return;
}
// Own the bytes from this point forward. The next Reticulum layer receives
// a deep-copied RNS::Bytes packet, never a pointer into a BLE callback buffer.
_reassembly_buffer.append(payload, payload_len);
_received_fragments++;
if (_received_fragments == _expected_total) {
if (_reassembly_buffer.size() != _expected_message_len) {
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=rx_reassembly_len_mismatch detail=msg_id_%lu assembled_%u expected_%lu fragments_%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_reassembly_buffer.size(),
(unsigned long)_expected_message_len,
(unsigned)_received_fragments);
DebugStats::print_mem("rx_reassembly_len_mismatch");
reset_reassembly();
return;
}
DebugStats::logf("RNSBLE ms=%lu board=%s role=%s event=packet_assembled msg_id=%lu assembled_len=%u assembled_crc32=%08lX fragments=%u\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_current_rx_message_id,
(unsigned)_reassembly_buffer.size(),
(unsigned long)DebugStats::bytes_crc32(_reassembly_buffer),
(unsigned)_received_fragments);
enqueue_packet(_reassembly_buffer);
reset_reassembly();
}
}
void TBeamSupremeBleInterface::enqueue_packet(const RNS::Bytes& packet) {
portENTER_CRITICAL(&_queue_mux);
if (_incoming_packets.size() >= MAX_INCOMING_QUEUE_DEPTH) {
_queue_drops++;
size_t depth = _incoming_packets.size();
portEXIT_CRITICAL(&_queue_mux);
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=queue_overflow detail=ble_rx_depth_%u_drops_%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned)depth,
(unsigned long)_queue_drops);
DebugStats::print_mem("queue_overflow");
print_queue_stats(true);
return;
}
try {
_incoming_packets.push_back(packet);
_queue_pushes++;
if (_incoming_packets.size() > _queue_highwater) {
_queue_highwater = _incoming_packets.size();
}
} catch (...) {
_queue_alloc_failures++;
portEXIT_CRITICAL(&_queue_mux);
DebugStats::note_failure();
DebugStats::logf("RNSERR ms=%lu board=%s role=%s class=queue_alloc_failure detail=ble_rx_push_failed failures=%lu\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)_queue_alloc_failures);
DebugStats::print_mem("queue_alloc_failure");
print_queue_stats(true);
return;
}
portEXIT_CRITICAL(&_queue_mux);
}
bool TBeamSupremeBleInterface::dequeue_packet(RNS::Bytes& packet) {
portENTER_CRITICAL(&_queue_mux);
if (_incoming_packets.empty()) {
portEXIT_CRITICAL(&_queue_mux);
return false;
}
packet = _incoming_packets.front();
_incoming_packets.pop_front();
_queue_pops++;
portEXIT_CRITICAL(&_queue_mux);
return true;
}
void TBeamSupremeBleInterface::print_queue_stats(bool force) {
uint32_t now = millis();
if (!force && _next_queue_log_ms != 0 && (int32_t)(now - _next_queue_log_ms) < 0) {
return;
}
_next_queue_log_ms = now + 1000;
portENTER_CRITICAL(&_queue_mux);
size_t depth = _incoming_packets.size();
size_t highwater = _queue_highwater;
uint32_t pushes = _queue_pushes;
uint32_t pops = _queue_pops;
uint32_t drops = _queue_drops;
uint32_t overwrites = _queue_overwrites;
uint32_t alloc_failures = _queue_alloc_failures;
portEXIT_CRITICAL(&_queue_mux);
DebugStats::logf("RNSQUEUE ms=%lu board=%s role=%s name=ble_rx depth=%u max_depth=%u pushes=%lu pops=%lu drops=%lu overwrites=%lu alloc_failures=%lu highwater=%u\r\n",
(unsigned long)now,
DebugStats::board(),
DebugStats::role(),
(unsigned)depth,
(unsigned)MAX_INCOMING_QUEUE_DEPTH,
(unsigned long)pushes,
(unsigned long)pops,
(unsigned long)drops,
(unsigned long)overwrites,
(unsigned long)alloc_failures,
(unsigned)highwater);
}
void TBeamSupremeBleInterface::reset_reassembly() {
_reassembly_buffer.clear();
_expected_total = 0;
_received_fragments = 0;
_expected_message_len = 0;
_reassembly_started_ms = 0;
}
RNS::Bytes TBeamSupremeBleInterface::local_identity_hash() const {
String material = String("microReticulum BLE ") + _node_label;
return Identity::truncated_hash(RNS::bytesFromString(material.c_str()));
}
void TBeamSupremeBleInterface::client_notify_callback(BLERemoteCharacteristic* characteristic,
uint8_t* data,
size_t length,
bool is_notify) {
(void)characteristic;
(void)is_notify;
if (active_ble_interface) {
// NimBLE/Arduino BLE owns this callback buffer. handle_fragment copies it
// synchronously into owned reassembly storage before returning.
active_ble_interface->handle_fragment(data, length);
}
}

View file

@ -0,0 +1,115 @@
#pragma once
#include <Bytes.h>
#include <Interface.h>
#include <Arduino.h>
#include <BLEAdvertisedDevice.h>
#include <BLEClient.h>
#include <BLEDevice.h>
#include <BLERemoteCharacteristic.h>
#include <BLEScan.h>
#include <BLEServer.h>
#include <deque>
class TBeamSupremeBleInterface : public RNS::InterfaceImpl {
public:
explicit TBeamSupremeBleInterface(const String& node_label,
const char* name = "TBeamSupremeBLE");
~TBeamSupremeBleInterface() override;
bool start() override;
void stop() override;
void loop() override;
bool connected() const { return _connected; }
const char* role_name() const { return "dual-role"; }
private:
void send_outgoing(const RNS::Bytes& data) override;
void start_peripheral();
void start_central_scan();
bool connect_to_advertised_device(BLEAdvertisedDevice* device);
bool should_connect_to_peer(const String& peer_address) const;
String advertised_node_label(BLEAdvertisedDevice& device) const;
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 print_queue_stats(bool force);
RNS::Bytes local_identity_hash() const;
static void client_notify_callback(BLERemoteCharacteristic* characteristic,
uint8_t* data,
size_t length,
bool is_notify);
class ServerCallbacks;
class RxCallbacks;
class AdvertisedDeviceCallbacks;
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 uint32_t REASSEMBLY_TIMEOUT_MS = 30000;
static constexpr uint32_t SCAN_RETRY_MS = 5000;
static constexpr size_t MAX_INCOMING_QUEUE_DEPTH = 32;
String _node_label;
String _local_address;
bool _started = false;
bool _connected = false;
bool _do_connect = false;
bool _scanning = false;
bool _server_handshake_received = false;
uint8_t _scan_report_count = 0;
uint32_t _next_scan_ms = 0;
BLEServer* _server = nullptr;
BLECharacteristic* _tx_characteristic = nullptr;
BLECharacteristic* _identity_characteristic = nullptr;
BLEAdvertisedDevice* _advertised_device = nullptr;
BLEClient* _client = nullptr;
BLERemoteCharacteristic* _remote_rx_characteristic = nullptr;
BLERemoteCharacteristic* _remote_tx_characteristic = nullptr;
RNS::Bytes _reassembly_buffer;
uint16_t _expected_total = 0;
uint16_t _received_fragments = 0;
uint32_t _expected_message_len = 0;
uint32_t _reassembly_started_ms = 0;
uint32_t _rx_message_id = 0;
uint32_t _current_rx_message_id = 0;
uint32_t _tx_message_id = 0;
uint32_t _ble_frag_tx_seq = 0;
uint32_t _ble_frag_rx_seq = 0;
uint32_t _packet_tx_seq = 0;
uint32_t _packet_rx_seq = 0;
uint32_t _reassembly_gaps = 0;
uint32_t _reassembly_duplicates = 0;
uint32_t _reassembly_out_of_order = 0;
uint32_t _queue_pushes = 0;
uint32_t _queue_pops = 0;
uint32_t _queue_drops = 0;
uint32_t _queue_overwrites = 0;
uint32_t _queue_alloc_failures = 0;
size_t _queue_highwater = 0;
uint32_t _next_queue_log_ms = 0;
std::deque<RNS::Bytes> _incoming_packets;
portMUX_TYPE _queue_mux = portMUX_INITIALIZER_UNLOCKED;
};

View file

@ -0,0 +1,694 @@
#include "TBeamSupremeBleInterface.h"
#include "DebugStats.h"
#include "SelectedText.h"
#include "TBeamDisplay.h"
#include <Arduino.h>
#include <Destination.h>
#include <Identity.h>
#include <Link.h>
#include <Log.h>
#include <Packet.h>
#include <Reticulum.h>
#include <Transport.h>
#include <Type.h>
#include <Utilities/OS.h>
#include <U8g2lib.h>
#include <microStore/Adapters/UniversalFileSystem.h>
#include <microStore/FileSystem.h>
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
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 constexpr uint8_t OLED_TEXT_LINES = 8;
static constexpr uint8_t OLED_TEXT_COLS = 25;
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 String peer_label;
static bool have_peer = false;
static bool link_active = false;
static bool link_attempted = false;
static TBeamSupremeBleInterface* ble_impl = nullptr;
static String node_label;
static tbeam::TBeamDisplay oled_display;
static char oled_lines[OLED_TEXT_LINES][OLED_TEXT_COLS + 1] = {};
static uint8_t oled_line = 0;
static uint8_t oled_col = 0;
struct RxTransferState {
bool active = false;
String sender;
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;
};
static RxTransferState rx_transfer;
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;
uint32_t next_start_ms = 0;
uint32_t round = 0;
};
static TxTransferState tx_transfer;
static String default_node_label() {
uint64_t mac = ESP.getEfuseMac();
char label[24];
snprintf(label, sizeof(label), "Node-%012llX", (unsigned long long)(mac & 0xFFFFFFFFFFFFULL));
return String(label);
}
static String board_name_from_node_label(const String& label) {
if (label == "Node-68BF5B43CA48") return "AMY";
if (label == "Node-DC935A43CA48") return "BOB";
if (label == "Node-44915A43CA48") return "CY";
if (label == "Node-A0935A43CA48") return "DAN";
if (label == "Node-D0905A43CA48") return "ED";
if (label == "Node-748C5B43CA48") return "FLO";
if (label == "Node-E0405A43CA48") return "GUY";
return label;
}
static bool should_initiate_link_to(const String& label) {
return strcmp(node_label.c_str(), label.c_str()) < 0;
}
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 String hex32(uint32_t value) {
char buffer[9];
snprintf(buffer, sizeof(buffer), "%08lX", (unsigned long)value);
return String(buffer);
}
static String link_id_hex(const RNS::Link& link) {
if (!link) {
return String("none");
}
return String(link.link_id().toHex().c_str());
}
static uint32_t bytes_crc32(const RNS::Bytes& bytes) {
return DebugStats::bytes_crc32(bytes);
}
static String preview_payload(const String& payload) {
String preview;
for (size_t i = 0; i < payload.length() && preview.length() < 56; ++i) {
char c = payload.charAt(i);
if (c == '\n') {
preview += "\\n";
} else if (c == '\r') {
preview += "\\r";
} else if ((uint8_t)c < 32) {
preview += '?';
} else {
preview += c;
}
}
return preview;
}
static void render_oled_text() {
if (!oled_display.ready()) {
return;
}
U8G2& oled = oled_display.raw();
oled.clearBuffer();
oled.setFont(u8g2_font_5x8_tf);
for (uint8_t i = 0; i < OLED_TEXT_LINES; ++i) {
if (oled_lines[i][0] != '\0') {
oled.drawUTF8(0, 8 + (i * 8), oled_lines[i]);
}
}
oled.sendBuffer();
}
static void clear_oled_text() {
for (uint8_t i = 0; i < OLED_TEXT_LINES; ++i) {
oled_lines[i][0] = '\0';
}
oled_line = 0;
oled_col = 0;
render_oled_text();
}
static void show_startup_splash() {
if (!oled_display.ready()) {
return;
}
U8G2& oled = oled_display.raw();
oled.clearBuffer();
oled.setFont(u8g2_font_5x8_tf);
oled.drawUTF8(0, 8, "Exercise 307");
oled.drawUTF8(0, 16, "Commencing...");
oled.drawUTF8(0, 32, "pairing has not");
oled.drawUTF8(0, 40, "been completed");
oled.drawUTF8(0, 48, "and when completed,");
oled.drawUTF8(0, 56, "text will appear.");
oled.sendBuffer();
}
static void advance_oled_line() {
if (oled_line < OLED_TEXT_LINES - 1) {
oled_line++;
} else {
for (uint8_t i = 0; i < OLED_TEXT_LINES - 1; ++i) {
strlcpy(oled_lines[i], oled_lines[i + 1], sizeof(oled_lines[i]));
}
}
oled_lines[oled_line][0] = '\0';
oled_col = 0;
}
static void append_oled_text(const String& text) {
for (size_t i = 0; i < text.length(); ++i) {
char c = text.charAt(i);
if (c == '\r') {
continue;
}
if (c == '\n') {
advance_oled_line();
continue;
}
if ((uint8_t)c < 32) {
c = ' ';
}
if (oled_col >= OLED_TEXT_COLS) {
advance_oled_line();
}
oled_lines[oled_line][oled_col++] = c;
oled_lines[oled_line][oled_col] = '\0';
}
render_oled_text();
}
static void handle_file_begin(const String& sender,
const 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;
Serial.printf("RX FILE BEGIN: from=%s file=%s bytes=%u chunks=%lu crc=%s\r\n",
sender.c_str(),
file_name.c_str(),
(unsigned)size,
(unsigned long)chunks,
hex32(crc).c_str());
clear_oled_text();
}
static void handle_file_data(const String& sender,
uint32_t sequence,
uint32_t total,
const String& payload) {
if (!rx_transfer.active || rx_transfer.sender != sender) {
Serial.printf("RX FILE DATA ignored: sender=%s seq=%lu no active transfer\r\n",
sender.c_str(), (unsigned long)sequence);
return;
}
for (size_t i = 0; i < payload.length(); ++i) {
rx_transfer.crc = fnv1a_update(rx_transfer.crc, (uint8_t)payload.charAt(i));
}
rx_transfer.received_chunks++;
rx_transfer.received_size += payload.length();
Serial.printf("RX FILE DATA: from=%s seq=%lu/%lu bytes=%u preview=\"%s\"\r\n",
sender.c_str(),
(unsigned long)sequence,
(unsigned long)total,
(unsigned)payload.length(),
preview_payload(payload).c_str());
append_oled_text(payload);
}
static void handle_file_end(const String& sender,
const 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;
Serial.printf("RX FILE END: from=%s file=%s received=%u/%u chunks=%lu/%lu crc=%s status=%s\r\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 String& message) {
int p1 = message.indexOf('|');
int p2 = message.indexOf('|', p1 + 1);
if (p1 < 0 || p2 < 0) {
Serial.printf("RX LINK BLE: %s\r\n", message.c_str());
append_oled_text(message);
return;
}
String type = message.substring(0, p1);
String sender = message.substring(p1 + 1, p2);
if (type == "FTB") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
int p5 = message.indexOf('|', p4 + 1);
if (p3 < 0 || p4 < 0 || p5 < 0) {
Serial.printf("RX FILE malformed BEGIN: %s\r\n", message.c_str());
return;
}
handle_file_begin(sender,
message.substring(p2 + 1, p3),
(size_t)message.substring(p3 + 1, p4).toInt(),
(uint32_t)message.substring(p4 + 1, p5).toInt(),
(uint32_t)strtoul(message.substring(p5 + 1).c_str(), nullptr, 16));
return;
}
if (type == "FTD") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
if (p3 < 0 || p4 < 0) {
Serial.printf("RX FILE malformed DATA: %s\r\n", message.c_str());
return;
}
handle_file_data(sender,
(uint32_t)message.substring(p2 + 1, p3).toInt(),
(uint32_t)message.substring(p3 + 1, p4).toInt(),
message.substring(p4 + 1));
return;
}
if (type == "FTE") {
int p3 = message.indexOf('|', p2 + 1);
int p4 = message.indexOf('|', p3 + 1);
int p5 = message.indexOf('|', p4 + 1);
if (p3 < 0 || p4 < 0 || p5 < 0) {
Serial.printf("RX FILE malformed END: %s\r\n", message.c_str());
return;
}
handle_file_end(sender,
message.substring(p2 + 1, p3),
(size_t)message.substring(p3 + 1, p4).toInt(),
(uint32_t)message.substring(p4 + 1, p5).toInt(),
(uint32_t)strtoul(message.substring(p5 + 1).c_str(), nullptr, 16));
return;
}
Serial.printf("RX LINK BLE: %s\r\n", message.c_str());
}
static void on_link_packet(const RNS::Bytes& data, const RNS::Packet& packet) {
(void)packet;
parse_file_transfer_packet(String(data.toString().c_str()));
}
static void on_link_closed(RNS::Link& link) {
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_closed peer=%s link_id=%s link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(link).c_str(),
link.toString().c_str());
Serial.println("LINK CLOSED");
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_established direction=outbound peer=%s link_id=%s link_hash_crc32=%08lX peer_dest_crc32=%08lX sign_key_crc32=unavailable enc_key_crc32=unavailable link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(active_link).c_str(),
(unsigned long)bytes_crc32(active_link.hash()),
(unsigned long)bytes_crc32(peer_hash),
active_link.toString().c_str());
Serial.println("LINK ACTIVE: initiator link established");
}
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=link_established direction=inbound peer=%s link_id=%s link_hash_crc32=%08lX peer_dest_crc32=%08lX sign_key_crc32=unavailable enc_key_crc32=unavailable link_obj=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.length() ? peer_label.c_str() : "unknown",
link_id_hex(active_link).c_str(),
(unsigned long)bytes_crc32(active_link.hash()),
(unsigned long)bytes_crc32(peer_hash),
active_link.toString().c_str());
Serial.println("RX LINK: inbound link established");
}
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 {
String label = app_data ? String(app_data.toString().c_str()) : String("(no label)");
if (label == node_label) {
return;
}
if (!announced_identity) {
Serial.printf("RX ANNOUNCE ignored: missing identity for hash=%s\r\n",
destination_hash.toHex().c_str());
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;
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=announce_rx peer=%s peer_dest=%s peer_dest_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
peer_label.c_str(),
peer_hash.toHex().c_str(),
(unsigned long)bytes_crc32(peer_hash));
Serial.printf("RX ANNOUNCE: label=%s hash=%s\r\n",
peer_label.c_str(), peer_hash.toHex().c_str());
}
};
static RNS::HAnnounceHandler announce_handler(new FileAnnounceHandler());
static void print_config() {
Serial.printf("Node=%s\r\n", node_label.c_str());
Serial.printf("BLE role=%s service=37145b00-442d-4a94-917f-8f42c5da28e3\r\n",
ble_impl ? ble_impl->role_name() : "?");
Serial.printf("Selected file=%s bytes=%u chunk=%u interval_ms=%lu repeat_rest_ms=%lu\r\n",
SELECTED_TEXT_NAME,
(unsigned)SELECTED_TEXT_SIZE,
(unsigned)TRANSFER_CHUNK_SIZE,
(unsigned long)TRANSFER_CHUNK_INTERVAL_MS,
(unsigned long)TRANSFER_REPEAT_INTERVAL_MS);
}
static void send_announce() {
if (!inbound_destination) {
return;
}
Serial.printf("RNSLINK ms=%lu board=%s role=%s event=announce_tx peer=all local_dest=%s local_dest_crc32=%08lX\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
inbound_destination.hash().toHex().c_str(),
(unsigned long)bytes_crc32(inbound_destination.hash()));
Serial.printf("TX ANNOUNCE: %s\r\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;
}
Serial.printf("TX LINKREQUEST: opening link to %s\r\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 String& message) {
RNS::Bytes plaintext = RNS::bytesFromString(message.c_str());
static uint32_t app_tx_seq = 0;
app_tx_seq++;
if (DebugStats::should_log_packet(app_tx_seq)) {
Serial.printf("RNSTX ms=%lu board=%s role=%s seq=%lu stage=link_plain link_id=%s len=%u crc32=%08lX first4=%s last4=%s\r\n",
(unsigned long)millis(),
DebugStats::board(),
DebugStats::role(),
(unsigned long)app_tx_seq,
link_id_hex(active_link).c_str(),
(unsigned)plaintext.size(),
(unsigned long)DebugStats::bytes_crc32(plaintext),
DebugStats::bytes_first4(plaintext).c_str(),
DebugStats::bytes_last4(plaintext).c_str());
}
RNS::Packet(active_link, RNS::bytesFromString(message.c_str())).send();
}
static bool send_next_file_packet(uint32_t now) {
if (tx_transfer.complete && (int32_t)(now - tx_transfer.next_start_ms) < 0) {
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++;
String begin = String("FTB|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
String((unsigned)SELECTED_TEXT_SIZE) + "|" + String((unsigned long)tx_transfer.total_chunks) +
"|" + hex32(tx_transfer.crc);
Serial.printf("TX FILE BEGIN: round=%lu file=%s bytes=%u chunks=%lu crc=%s\r\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;
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;
String data = String("FTD|") + node_label + "|" + String((unsigned long)tx_transfer.sequence) +
"|" + String((unsigned long)tx_transfer.total_chunks) + "|" + payload;
Serial.printf("TX FILE DATA: round=%lu seq=%lu/%lu bytes=%u preview=\"%s\"\r\n",
(unsigned long)tx_transfer.round,
(unsigned long)tx_transfer.sequence,
(unsigned long)tx_transfer.total_chunks,
(unsigned)count,
preview_payload(payload).c_str());
send_link_message(data);
return true;
}
String end = String("FTE|") + node_label + "|" + SELECTED_TEXT_NAME + "|" +
String((unsigned)SELECTED_TEXT_SIZE) + "|" + String((unsigned long)tx_transfer.total_chunks) +
"|" + hex32(tx_transfer.crc);
Serial.printf("TX FILE END: round=%lu file=%s bytes=%u chunks=%lu crc=%s next_round_in_ms=%lu\r\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);
ble_impl = new TBeamSupremeBleInterface(node_label);
ble_interface = ble_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);
Serial.printf("Local SINGLE destination: %s\r\n",
inbound_destination.hash().toHex().c_str());
}
void setup() {
Serial.begin(115200);
while (!Serial && millis() < 5000) {
delay(100);
}
delay(250);
Serial.println();
RNS::loglevel(RNS::LOG_NOTICE);
node_label = default_node_label();
DebugStats::set_board(board_name_from_node_label(node_label));
DebugStats::set_role("boot");
tbeam::DisplayConfig display_config;
display_config.powerSave = false;
oled_display.begin(display_config);
show_startup_splash();
setup_reticulum();
Serial.println("Exercise 307: microReticulum BLE file transfer OLED resiliency staging");
print_config();
Serial.println("microReticulum ready");
}
void loop() {
reticulum.loop();
static uint32_t next_announce_ms = 0;
static uint32_t next_transfer_ms = 0;
static uint32_t next_wait_log_ms = 0;
uint32_t now = millis();
if (ble_impl && !ble_impl->connected()) {
if (next_wait_log_ms == 0 || (int32_t)(now - next_wait_log_ms) >= 0) {
next_wait_log_ms = now + 10000;
Serial.printf("BLE %s waiting for peer\r\n", ble_impl->role_name());
}
delay(5);
return;
}
if (next_announce_ms == 0) {
uint32_t offset = 700 + ((uint32_t)node_label.charAt(node_label.length() - 1) % 5) * 400;
next_announce_ms = now + offset;
}
if (!link_active && (int32_t)(now - next_announce_ms) >= 0) {
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 && (int32_t)(now - next_transfer_ms) >= 0) {
next_transfer_ms = now + TRANSFER_CHUNK_INTERVAL_MS;
send_next_file_packet(now);
}
delay(5);
}
int _write(int file, char* ptr, int len) {
(void)file;
int wrote = Serial.write(ptr, len);
Serial.flush();
return wrote;
}

View file

@ -0,0 +1,4 @@
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;

View file

@ -0,0 +1,35 @@
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;
If you can wait and not be tired by waiting,
Or being lied about, dont deal in lies,
Or being hated, dont give way to hating,
And yet dont look too good, nor talk too wise:
If you can dream—and not make dreams your master;
If you can think—and not make thoughts your aim;
If you can meet with Triumph and Disaster
And treat those two impostors just the same;
If you can bear to hear the truth youve spoken
Twisted by knaves to make a trap for fools,
Or watch the things you gave your life to, broken,
And stoop and build em up with worn-out tools:
If you can make one heap of all your winnings
And risk it on one turn of pitch-and-toss,
And lose, and start again at your beginnings
And never breathe a word about your loss;
If you can force your heart and nerve and sinew
To serve your turn long after they are gone,
And so hold on when there is nothing in you
Except the Will which says to them: Hold on!
If you can talk with crowds and keep your virtue,
Or walk with Kings—nor lose the common touch,
If neither foes nor loving friends can hurt you,
If all men count with you, but none too much;
If you can fill the unforgiving minute
With sixty seconds worth of distance run,
Yours is the Earth and everything thats in it,
And—which is more—youll be a Man, my son!

View file

@ -0,0 +1,370 @@
The United States Constitution
We the People of the United States, in Order to form a more perfect Union, establish Justice, insure domestic Tranquility, provide for the common defence, promote the general Welfare, and secure the Blessings of Liberty to ourselves and our Posterity, do ordain and establish this Constitution for the United States of America.
The Constitutional Convention
Article I
Section 1: Congress
All legislative Powers herein granted shall be vested in a Congress of the United States, which shall consist of a Senate and House of Representatives.
Section 2: The House of Representatives
The House of Representatives shall be composed of Members chosen every second Year by the People of the several States, and the Electors in each State shall have the Qualifications requisite for Electors of the most numerous Branch of the State Legislature.
No Person shall be a Representative who shall not have attained to the Age of twenty five Years, and been seven Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State in which he shall be chosen.
Representatives and direct Taxes shall be apportioned among the several States which may be included within this Union, according to their respective Numbers, which shall be determined by adding to the whole Number of free Persons, including those bound to Service for a Term of Years, and excluding Indians not taxed, three fifths of all other Persons. The actual Enumeration shall be made within three Years after the first Meeting of the Congress of the United States, and within every subsequent Term of ten Years, in such Manner as they shall by Law direct.The Number of Representatives shall not exceed one for every thirty Thousand, but each State shall have at Least one Representative; and until such enumeration shall be made, the State of New Hampshire shall be entitled to chuse three, Massachusetts eight, Rhode-Island and Providence Plantations one, Connecticut five, New-York six, New Jersey four, Pennsylvania eight, Delaware one, Maryland six, Virginia ten, North Carolina five, South Carolina five, and Georgia three.
When vacancies happen in the Representation from any State, the Executive Authority thereof shall issue Writs of Election to fill such Vacancies.
The House of Representatives shall chuse their Speaker and other Officers;and shall have the sole Power of Impeachment.
Section 3: The Senate
The Senate of the United States shall be composed of two Senators from each State, chosen by the Legislature thereof, for six Years; and each Senator shall have one Vote.
Immediately after they shall be assembled in Consequence of the first Election, they shall be divided as equally as may be into three Classes. The Seats of the Senators of the first Class shall be vacated at the Expiration of the second Year, of the second Class at the Expiration of the fourth Year, and of the third Class at the Expiration of the sixth Year, so that one third may be chosen every second Year; and if Vacancies happen by Resignation, or otherwise, during the Recess of the Legislature of any State, the Executive thereof may make temporary Appointments until the next Meeting of the Legislature, which shall then fill such Vacancies.
No Person shall be a Senator who shall not have attained to the Age of thirty Years, and been nine Years a Citizen of the United States, and who shall not, when elected, be an Inhabitant of that State for which he shall be chosen.
The Vice President of the United States shall be President of the Senate, but shall have no Vote, unless they be equally divided.
The Senate shall chuse their other Officers, and also a President pro tempore, in the Absence of the Vice President, or when he shall exercise the Office of President of the United States.
The Senate shall have the sole Power to try all Impeachments. When sitting for that Purpose, they shall be on Oath or Affirmation. When the President of the United States is tried, the Chief Justice shall preside: And no Person shall be convicted without the Concurrence of two thirds of the Members present.
Judgment in Cases of Impeachment shall not extend further than to removal from Office, and disqualification to hold and enjoy any Office of honor, Trust or Profit under the United States: but the Party convicted shall nevertheless be liable and subject to Indictment, Trial, Judgment and Punishment, according to Law.
Section 4: Elections
The Times, Places and Manner of holding Elections for Senators and Representatives, shall be prescribed in each State by the Legislature thereof; but the Congress may at any time by Law make or alter such Regulations, except as to the Places of chusing Senators.
The Congress shall assemble at least once in every Year, and such Meeting shall be on the first Monday in December, unless they shall by Law appoint a different Day.
Section 5: Powers and Duties of Congress
Each House shall be the Judge of the Elections, Returns and Qualifications of its own Members,and a Majority of each shall constitute a Quorum to do Business; but a smaller Number may adjourn from day to day, and may be authorized to compel the Attendance of absent Members, in such Manner, and under such Penalties as each House may provide.
Each House may determine the Rules of its Proceedings, punish its Members for disorderly Behaviour, and, with the Concurrence of two thirds, expel a Member.
Each House shall keep a Journal of its Proceedings, and from time to time publish the same, excepting such Parts as may in their Judgment require Secrecy; and the Yeas and Nays of the Members of either House on any question shall, at the Desire of one fifth of those Present, be entered on the Journal.
Neither House, during the Session of Congress, shall, without the Consent of the other, adjourn for more than three days, nor to any other Place than that in which the two Houses shall be sitting.
Section 6: Rights and Disabilities of Members
The Senators and Representatives shall receive a Compensation for their Services, to be ascertained by Law, and paid out of the Treasury of the United States.They shall in all Cases, except Treason, Felony and Breach of the Peace, be privileged from Arrest during their Attendance at the Session of their respective Houses, and in going to and returning from the same; and for any Speech or Debate in either House, they shall not be questioned in any other Place.
No Senator or Representative shall, during the Time for which he was elected, be appointed to any civil Office under the Authority of the United States, which shall have been created, or the Emoluments whereof shall have been encreased during such time; and no Person holding any Office under the United States, shall be a Member of either House during his Continuance in Office.
Section 7: Legislative Process
All Bills for raising Revenue shall originate in the House of Representatives; but the Senate may propose or concur with Amendments as on other Bills.
Every Bill which shall have passed the House of Representatives and the Senate, shall, before it become a Law, be presented to the President of the United States; If he approve he shall sign it, but if not he shall return it, with his Objections to that House in which it shall have originated, who shall enter the Objections at large on their Journal, and proceed to reconsider it. If after such Reconsideration two thirds of that House shall agree to pass the Bill, it shall be sent, together with the Objections, to the other House, by which it shall likewise be reconsidered, and if approved by two thirds of that House, it shall become a Law. But in all such Cases the Votes of both Houses shall be determined by yeas and Nays, and the Names of the Persons voting for and against the Bill shall be entered on the Journal of each House respectively. If any Bill shall not be returned by the President within ten Days (Sundays excepted) after it shall have been presented to him, the Same shall be a Law, in like Manner as if he had signed it, unless the Congress by their Adjournment prevent its Return, in which Case it shall not be a Law.
Every Order, Resolution, or Vote to which the Concurrence of the Senate and House of Representatives may be necessary (except on a question of Adjournment) shall be presented to the President of the United States; and before the Same shall take Effect, shall be approved by him, or being disapproved by him, shall be repassed by two thirds of the Senate and House of Representatives, according to the Rules and Limitations prescribed in the Case of a Bill.
Section 8: Powers of Congress
The Congress shall have Power To lay and collect Taxes, Duties, Imposts and Excises, to pay the Debts and provide for the common Defence and general Welfare of the United States; but all Duties, Imposts and Excises shall be uniform throughout the United States;
To borrow Money on the credit of the United States;
To regulate Commerce with foreign Nations, and among the several States, and with the Indian Tribes;
To establish a uniform Rule of Naturalization, and uniform Laws on the subject of Bankruptcies throughout the United States;
To coin Money, regulate the Value thereof, and of foreign Coin, and fix the Standard of Weights and Measures;
To provide for the Punishment of counterfeiting the Securities and current Coin of the United States;
To establish Post Offices and post Roads;
To promote the Progress of Science and useful Arts, by securing for limited Times to Authors and Inventors the exclusive Right to their respective Writings and Discoveries;
To constitute Tribunals inferior to the supreme Court;
To define and punish Piracies and Felonies committed on the high Seas, and Offences against the Law of Nations;
To declare War, grant Letters of Marque and Reprisal, and make Rules concerning Captures on Land and Water;
To raise and support Armies, but no Appropriation of Money to that Use shall be for a longer Term than two Years;
To provide and maintain a Navy;
To make Rules for the Government and Regulation of the land and naval Forces;
To provide for calling forth the Militia to execute the Laws of the Union, suppress Insurrections and repel Invasions;
To provide for organizing, arming, and disciplining, the Militia, and for governing such Part of them as may be employed in the Service of the United States, reserving to the States respectively, the Appointment of the Officers, and the Authority of training the Militia according to the discipline prescribed by Congress;
To exercise exclusive Legislation in all Cases whatsoever, over such District (not exceeding ten Miles square) as may, by Cession of particular States, and the Acceptance of Congress, become the Seat of the Government of the United States, and to exercise like Authority over all Places purchased by the Consent of the Legislature of the State in which the Same shall be, for the Erection of Forts, Magazines, Arsenals, dock-Yards and other needful Buildings;-And
To make all Laws which shall be necessary and proper for carrying into Execution the foregoing Powers, and all other Powers vested by this Constitution in the Government of the United States, or in any Department or Officer thereof.
Section 9: Powers Denied Congress
The Migration or Importation of such Persons as any of the States now existing shall think proper to admit, shall not be prohibited by the Congress prior to the Year one thousand eight hundred and eight, but a Tax or duty may be imposed on such Importation, not exceeding ten dollars for each Person.
The Privilege of the Writ of Habeas Corpus shall not be suspended, unless when in Cases of Rebellion or Invasion the public Safety may require it.
No Bill of Attainder or ex post facto Law shall be passed.
No Capitation, or other direct, Tax shall be laid, unless in Proportion to the Census or enumeration herein before directed to be taken.
No Tax or Duty shall be laid on Articles exported from any State.
No Preference shall be given by any Regulation of Commerce or Revenue to the Ports of one State over those of another: nor shall Vessels bound to, or from, one State, be obliged to enter, clear, or pay Duties in another.
No Money shall be drawn from the Treasury, but in Consequence of Appropriations made by Law; and a regular Statement and Account of the Receipts and Expenditures of all public Money shall be published from time to time.
No Title of Nobility shall be granted by the United States: And no Person holding any Office of Profit or Trust under them, shall, without the Consent of the Congress, accept of any present, Emolument, Office, or Title, of any kind whatever, from any King, Prince, or foreign State.
Section 10: Powers Denied to the States
No State shall enter into any Treaty, Alliance, or Confederation; grant Letters of Marque and Reprisal; coin Money; emit Bills of Credit; make any Thing but gold and silver Coin a Tender in Payment of Debts; pass any Bill of Attainder, ex post facto Law, or Law impairing the Obligation of Contracts, or grant any Title of Nobility.
No State shall, without the Consent of the Congress, lay any Imposts or Duties on Imports or Exports, except what may be absolutely necessary for executing it's inspection Laws: and the net Produce of all Duties and Imposts, laid by any State on Imports or Exports, shall be for the Use of the Treasury of the United States; and all such Laws shall be subject to the Revision and Controul of the Congress.
No State shall, without the Consent of Congress, lay any Duty of Tonnage, keep Troops, or Ships of War in time of Peace, enter into any Agreement or Compact with another State, or with a foreign Power, or engage in War, unless actually invaded, or in such imminent Danger as will not admit of delay.
Article II
Section 1
The executive Power shall be vested in a President of the United States of America.
He shall hold his Office during the Term of four Years, and, together with the Vice President, chosen for the same Term, be elected, as follows:
Each State shall appoint, in such Manner as the Legislature thereof may direct, a Number of Electors, equal to the whole Number of Senators and Representatives to which the State may be entitled in the Congress: but no Senator or Representative, or Person holding an Office of Trust or Profit under the United States, shall be appointed an Elector.
The Electors shall meet in their respective States, and vote by Ballot for two Persons, of whom one at least shall not be an Inhabitant of the same State with themselves. And they shall make a List of all the Persons voted for, and of the Number of Votes for each; which List they shall sign and certify, and transmit sealed to the Seat of the Government of the United States, directed to the President of the Senate. The President of the Senate shall, in the Presence of the Senate and House of Representatives, open all the Certificates, and the Votes shall then be counted. The Person having the greatest Number of Votes shall be the President, if such Number be a Majority of the whole Number of Electors appointed; and if there be more than one who have such Majority, and have an equal Number of Votes, then the House of Representatives shall immediately chuse by Ballot one of them for President; and if no Person have a Majority, then from the five highest on the List the said House shall in like Manner chuse the President. But in chusing the President, the Votes shall be taken by States, the Representation from each State having one Vote; A quorum for this Purpose shall consist of a Member or Members from two thirds of the States, and a Majority of all the States shall be necessary to a Choice. In every Case, after the Choice of the President, the Person having the greatest Number of Votes of the Electors shall be the Vice President. But if there should remain two or more who have equal Votes, the Senate shall chuse from them by Ballot the Vice President.
The Congress may determine the Time of chusing the Electors, and the Day on which they shall give their Votes; which Day shall be the same throughout the United States.
No Person except a natural born Citizen, or a Citizen of the United States, at the time of the Adoption of this Constitution, shall be eligible to the Office of President; neither shall any Person be eligible to that Office who shall not have attained to the Age of thirty five Years, and been fourteen Years a Resident within the United States.
In Case of the Removal of the President from Office, or of his Death, Resignation, or Inability to discharge the Powers and Duties of the said Office, the Same shall devolve on the Vice President, and the Congress may by Law provide for the Case of Removal, Death, Resignation or Inability, both of the President and Vice President, declaring what Officer shall then act as President, and such Officer shall act accordingly, until the Disability be removed, or a President shall be elected.
The President shall, at stated Times, receive for his Services, a Compensation, which shall neither be encreased nor diminished during the Period for which he shall have been elected, and he shall not receive within that Period any other Emolument from the United States, or any of them.
Before he enter on the Execution of his Office, he shall take the following Oath or Affirmation:--"I do solemnly swear (or affirm) that I will faithfully execute the Office of President of the United States, and will to the best of my Ability, preserve, protect and defend the Constitution of the United States."
Section 2
The President shall be Commander in Chief of the Army and Navy of the United States, and of the Militia of the several States, when called into the actual Service of the United States; he may require the Opinion, in writing, of the principal Officer in each of the executive Departments, upon any Subject relating to the Duties of their respective Offices, and he shall have Power to grant Reprieves and Pardons for Offences against the United States, except in Cases of Impeachment.
He shall have Power, by and with the Advice and Consent of the Senate, to make Treaties, provided two thirds of the Senators present concur; and he shall nominate, and by and with the Advice and Consent of the Senate, shall appoint Ambassadors, other public Ministers and Consuls, Judges of the supreme Court, and all other Officers of the United States, whose Appointments are not herein otherwise provided for, and which shall be established by Law: but the Congress may by Law vest the Appointment of such inferior Officers, as they think proper, in the President alone, in the Courts of Law, or in the Heads of Departments.
The President shall have Power to fill up all Vacancies that may happen during the Recess of the Senate, by granting Commissions which shall expire at the End of their next Session.
Section 3
He shall from time to time give to the Congress Information of the State of the Union, and recommend to their Consideration such Measures as he shall judge necessary and expedient; he may, on extraordinary Occasions, convene both Houses, or either of them, and in Case of Disagreement between them, with Respect to the Time of Adjournment, he may adjourn them to such Time as he shall think proper; he shall receive Ambassadors and other public Ministers; he shall take Care that the Laws be faithfully executed, and shall Commission all the Officers of the United States.
Section 4
The President, Vice President and all civil Officers of the United States, shall be removed from Office on Impeachment for, and Conviction of, Treason, Bribery, or other high Crimes and Misdemeanors.
Article III
Section 1
The judicial Power of the United States, shall be vested in one supreme Court, and in such inferior Courts as the Congress may from time to time ordain and establish. The Judges, both of the supreme and inferior Courts, shall hold their Offices during good Behaviour, and shall, at stated Times, receive for their Services, a Compensation, which shall not be diminished during their Continuance in Office.
Section 2
The judicial Power shall extend to all Cases, in Law and Equity, arising under this Constitution, the Laws of the United States, and Treaties made, or which shall be made, under their Authority;--to all Cases affecting Ambassadors, other public Ministers and Consuls;--to all Cases of admiralty and maritime Jurisdiction;--to Controversies to which the United States shall be a Party;--to Controversies between two or more States;--between a State and Citizens of another State;--between Citizens of different States;--between Citizens of the same State claiming Lands under Grants of different States, and between a State, or the Citizens thereof, and foreign States, Citizens or Subjects.
In all Cases affecting Ambassadors, other public Ministers and Consuls, and those in which a State shall be Party, the supreme Court shall have original Jurisdiction. In all the other Cases before mentioned, the supreme Court shall have appellate Jurisdiction, both as to Law and Fact, with such Exceptions, and under such Regulations as the Congress shall make.
The Trial of all Crimes, except in Cases of Impeachment; shall be by Jury; and such Trial shall be held in the State where the said Crimes shall have been committed; but when not committed within any State, the Trial shall be at such Place or Places as the Congress may by Law have directed.
Section 3
Treason against the United States, shall consist only in levying War against them, or in adhering to their Enemies, giving them Aid and Comfort. No Person shall be convicted of Treason unless on the Testimony of two Witnesses to the same overt Act, or on Confession in open Court.
The Congress shall have Power to declare the Punishment of Treason, but no Attainder of Treason shall work Corruption of Blood, or Forfeiture except during the Life of the Person attainted.
Article IV
Section 1
Full Faith and Credit shall be given in each State to the public Acts, Records, and judicial Proceedings of every other State. And the Congress may by general Laws prescribe the Manner in which such Acts, Records and Proceedings shall be proved, and the Effect thereof.
Section 2
The Citizens of each State shall be entitled to all Privileges and Immunities of Citizens in the several States.
A Person charged in any State with Treason, Felony, or other Crime, who shall flee from Justice, and be found in another State, shall on Demand of the executive Authority of the State from which he fled, be delivered up, to be removed to the State having Jurisdiction of the Crime.
No Person held to Service or Labour in one State, under the Laws thereof, escaping into another, shall, in Consequence of any Law or Regulation therein, be discharged from such Service or Labour, but shall be delivered up on Claim of the Party to whom such Service or Labour may be due.
Section 3
New States may be admitted by the Congress into this Union; but no new State shall be formed or erected within the Jurisdiction of any other State; nor any State be formed by the Junction of two or more States, or Parts of States, without the Consent of the Legislatures of the States concerned as well as of the Congress.
The Congress shall have Power to dispose of and make all needful Rules and Regulations respecting the Territory or other Property belonging to the United States; and nothing in this Constitution shall be so construed as to Prejudice any Claims of the United States, or of any particular State.
Section 4
The United States shall guarantee to every State in this Union a Republican Form of Government, and shall protect each of them against Invasion; and on Application of the Legislature, or of the Executive (when the Legislature cannot be convened) against domestic Violence.
Article V
The Congress, whenever two thirds of both Houses shall deem it necessary, shall propose Amendments to this Constitution, or, on the Application of the Legislatures of two thirds of the several States, shall call a Convention for proposing Amendments, which, in either Case, shall be valid to all Intents and Purposes, as Part of this Constitution, when ratified by the Legislatures of three fourths of the several States, or by Conventions in three fourths thereof, as the one or the other Mode of Ratification may be proposed by the Congress; Provided that no Amendment which may be made prior to the Year One thousand eight hundred and eight shall in any Manner affect the first and fourth Clauses in the Ninth Section of the first Article; and that no State, without its Consent, shall be deprived of its equal Suffrage in the Senate.
Article VI
All Debts contracted and Engagements entered into, before the Adoption of this Constitution, shall be as valid against the United States under this Constitution, as under the Confederation.
This Constitution, and the Laws of the United States which shall be made in Pursuance thereof; and all Treaties made, or which shall be made, under the Authority of the United States, shall be the supreme Law of the Land; and the Judges in every State shall be bound thereby, any Thing in the Constitution or Laws of any State to the Contrary notwithstanding.
The Senators and Representatives before mentioned, and the Members of the several State Legislatures, and all executive and judicial Officers, both of the United States and of the several States, shall be bound by Oath or Affirmation, to support this Constitution; but no religious Test shall ever be required as a Qualification to any Office or public Trust under the United States.
Article VII
The Ratification of the Conventions of nine States, shall be sufficient for the Establishment of this Constitution between the States so ratifying the Same.
First Amendment
Congress shall make no law respecting an establishment of religion, or prohibiting the free exercise thereof; or abridging the freedom of speech, or of the press; or the right of the people peaceably to assemble, and to petition the Government for a redress of grievances.
Second Amendment
A well regulated Militia, being necessary to the security of a free State, the right of the people to keep and bear Arms, shall not be infringed.
Third Amendment
No Soldier shall, in time of peace be quartered in any house, without the consent of the Owner, nor in time of war, but in a manner to be prescribed by law.
Fourth Amendment
The right of the people to be secure in their persons, houses, papers, and effects, against unreasonable searches and seizures, shall not be violated, and no Warrants shall issue, but upon probable cause, supported by Oath or affirmation, and particularly describing the place to be searched, and the persons or things to be seized.
Fifth Amendment
No person shall be held to answer for a capital, or otherwise infamous crime, unless on a presentment or indictment of a Grand Jury, except in cases arising in the land or naval forces, or in the Militia, when in actual service in time of War or public danger; nor shall any person be subject for the same offence to be twice put in jeopardy of life or limb; nor shall be compelled in any criminal case to be a witness against himself, nor be deprived of life, liberty, or property, without due process of law; nor shall private property be taken for public use, without just compensation.
Sixth Amendment
In all criminal prosecutions, the accused shall enjoy the right to a speedy and public trial, by an impartial jury of the State and district wherein the crime shall have been committed, which district shall have been previously ascertained by law, and to be informed of the nature and cause of the accusation; to be confronted with the witnesses against him; to have compulsory process for obtaining witnesses in his favor, and to have the Assistance of Counsel for his defence.
Seventh Amendment
In Suits at common law, where the value in controversy shall exceed twenty dollars, the right of trial by jury shall be preserved, and no fact tried by a jury, shall be otherwise re-examined in any Court of the United States, than according to the rules of the common law.
Eighth Amendment
Excessive bail shall not be required, nor excessive fines imposed, nor cruel and unusual punishments inflicted.
Ninth Amendment
The enumeration in the Constitution, of certain rights, shall not be construed to deny or disparage others retained by the people.
10th Amendment
The powers not delegated to the United States by the Constitution, nor prohibited by it to the States, are reserved to the States respectively, or to the people.
11th Amendment
The Judicial power of the United States shall not be construed to extend to any suit in law or equity, commenced or prosecuted against one of the United States by Citizens of another State, or by Citizens or Subjects of any Foreign State.
12th Amendment
The Electors shall meet in their respective states and vote by ballot for President and Vice-President, one of whom, at least, shall not be an inhabitant of the same state with themselves; they shall name in their ballots the person voted for as President, and in distinct ballots the person voted for as Vice-President, and they shall make distinct lists of all persons voted for as President, and of all persons voted for as Vice-President, and of the number of votes for each, which lists they shall sign and certify, and transmit sealed to the seat of the government of the United States, directed to the President of the Senate; -- the President of the Senate shall, in the presence of the Senate and House of Representatives, open all the certificates and the votes shall then be counted; -- The person having the greatest number of votes for President, shall be the President, if such number be a majority of the whole number of Electors appointed; and if no person have such majority, then from the persons having the highest numbers not exceeding three on the list of those voted for as President, the House of Representatives shall choose immediately, by ballot, the President. But in choosing the President, the votes shall be taken by states, the representation from each state having one vote; a quorum for this purpose shall consist of a member or members from two-thirds of the states, and a majority of all the states shall be necessary to a choice. And if the House of Representatives shall not choose a President whenever the right of choice shall devolve upon them, before the fourth day of March next following, then the Vice-President shall act as President, as in the case of the death or other constitutional disability of the President.-- The person having the greatest number of votes as Vice-President, shall be the Vice-President, if such number be a majority of the whole number of Electors appointed, and if no person have a majority, then from the two highest numbers on the list, the Senate shall choose the Vice-President; a quorum for the purpose shall consist of two-thirds of the whole number of Senators, and a majority of the whole number shall be necessary to a choice. But no person constitutionally ineligible to the office of President shall be eligible to that of Vice-President of the United States.
13th Amendment
Section 1
Neither slavery nor involuntary servitude, except as a punishment for crime whereof the party shall have been duly convicted, shall exist within the United States, or any place subject to their jurisdiction.
Section 2
Congress shall have power to enforce this article by appropriate legislation.
14th Amendment
Section 1
All persons born or naturalized in the United States, and subject to the jurisdiction thereof, are citizens of the United States and of the State wherein they reside. No State shall make or enforce any law which shall abridge the privileges or immunities of citizens of the United States; nor shall any State deprive any person of life, liberty, or property, without due process of law; nor deny to any person within its jurisdiction the equal protection of the laws.
Section 2
Representatives shall be apportioned among the several States according to their respective numbers, counting the whole number of persons in each State, excluding Indians not taxed. But when the right to vote at any election for the choice of electors for President and Vice-President of the United States, Representatives in Congress, the Executive and Judicial officers of a State, or the members of the Legislature thereof, is denied to any of the male inhabitants of such State, being twenty-one years of age, and citizens of the United States, or in any way abridged, except for participation in rebellion, or other crime, the basis of representation therein shall be reduced in the proportion which the number of such male citizens shall bear to the whole number of male citizens twenty-one years of age in such State.
Section 3
No person shall be a Senator or Representative in Congress, or elector of President and Vice-President, or hold any office, civil or military, under the United States, or under any State, who, having previously taken an oath, as a member of Congress, or as an officer of the United States, or as a member of any State legislature, or as an executive or judicial officer of any State, to support the Constitution of the United States, shall have engaged in insurrection or rebellion against the same, or given aid or comfort to the enemies thereof. But Congress may by a vote of two-thirds of each House, remove such disability.
Section 4
The validity of the public debt of the United States, authorized by law, including debts incurred for payment of pensions and bounties for services in suppressing insurrection or rebellion, shall not be questioned. But neither the United States nor any State shall assume or pay any debt or obligation incurred in aid of insurrection or rebellion against the United States, or any claim for the loss or emancipation of any slave; but all such debts, obligations and claims shall be held illegal and void.
Section 5
The Congress shall have power to enforce, by appropriate legislation, the provisions of this article.
15th Amendment
Section 1
The right of citizens of the United States to vote shall not be denied or abridged by the United States or by any State on account of race, color, or previous condition of servitude.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
16th Amendment
The Congress shall have power to lay and collect taxes on incomes, from whatever source derived, without apportionment among the several States, and without regard to any census or enumeration.
17th Amendment
The Senate of the United States shall be composed of two Senators from each State, elected by the people thereof, for six years; and each Senator shall have one vote. The electors in each State shall have the qualifications requisite for electors of the most numerous branch of the State legislatures.
When vacancies happen in the representation of any State in the Senate, the executive authority of such State shall issue writs of election to fill such vacancies: Provided, That the legislature of any State may empower the executive thereof to make temporary appointments until the people fill the vacancies by election as the legislature may direct.
This amendment shall not be so construed as to affect the election or term of any Senator chosen before it becomes valid as part of the Constitution.
18th Amendment
Section 1
After one year from the ratification of this article the manufacture, sale, or transportation of intoxicating liquors within, the importation thereof into, or the exportation thereof from the United States and all territory subject to the jurisdiction thereof for beverage purposes is hereby prohibited.
Section 2
The Congress and the several States shall have concurrent power to enforce this article by appropriate legislation.
Section 3
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of the several States, as provided in the Constitution, within seven years from the date of the submission hereof to the States by the Congress.
19th Amendment
The right of citizens of the United States to vote shall not be denied or abridged by the United States or by any State on account of sex.
Congress shall have power to enforce this article by appropriate legislation.
20th Amendment
Section 1
The terms of the President and the Vice President shall end at noon on the 20th day of January, and the terms of Senators and Representatives at noon on the 3d day of January, of the years in which such terms would have ended if this article had not been ratified; and the terms of their successors shall then begin.
Section 2
The Congress shall assemble at least once in every year, and such meeting shall begin at noon on the 3d day of January, unless they shall by law appoint a different day.
Section 3
If, at the time fixed for the beginning of the term of the President, the President elect shall have died, the Vice President elect shall become President. If a President shall not have been chosen before the time fixed for the beginning of his term, or if the President elect shall have failed to qualify, then the Vice President elect shall act as President until a President shall have qualified; and the Congress may by law provide for the case wherein neither a President elect nor a Vice President elect shall have qualified, declaring who shall then act as President, or the manner in which one who is to act shall be selected, and such person shall act accordingly until a President or Vice President shall have qualified.
Section 4
The Congress may by law provide for the case of the death of any of the persons from whom the House of Representatives may choose a President whenever the right of choice shall have devolved upon them, and for the case of the death of any of the persons from whom the Senate may choose a Vice President whenever the right of choice shall have devolved upon them.
Section 5
Sections 1 and 2 shall take effect on the 15th day of October following the ratification of this article.
Section 6
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of three-fourths of the several States within seven years from the date of its submission.
21st Amendment
Section 1
The eighteenth article of amendment to the Constitution of the United States is hereby repealed.
Section 2
The transportation or importation into any State, Territory, or possession of the United States for delivery or use therein of intoxicating liquors, in violation of the laws thereof, is hereby prohibited.
Section 3
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by conventions in the several States, as provided in the Constitution, within seven years from the date of the submission hereof to the States by the Congress.
22nd Amendment
Section 1
No person shall be elected to the office of the President more than twice, and no person who has held the office of President, or acted as President, for more than two years of a term to which some other person was elected President shall be elected to the office of the President more than once. But this Article shall not apply to any person holding the office of President when this Article was proposed by the Congress, and shall not prevent any person who may be holding the office of President, or acting as President, during the term within which this Article becomes operative from holding the office of President or acting as President during the remainder of such term.
Section 2
This article shall be inoperative unless it shall have been ratified as an amendment to the Constitution by the legislatures of three-fourths of the several States within seven years from the date of its submission to the States by the Congress.
23rd Amendment
Section 1
The District constituting the seat of Government of the United States shall appoint in such manner as Congress may direct:
A number of electors of President and Vice President equal to the whole number of Senators and Representatives in Congress to which the District would be entitled if it were a State, but in no event more than the least populous State; they shall be in addition to those appointed by the States, but they shall be considered, for the purposes of the election of President and Vice President, to be electors appointed by a State; and they shall meet in the District and perform such duties as provided by the twelfth article of amendment.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
24th Amendment
Section 1
The right of citizens of the United States to vote in any primary or other election for President or Vice President, for electors for President or Vice President, or for Senator or Representative in Congress, shall not be denied or abridged by the United States or any State by reason of failure to pay poll tax or other tax.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
25th Amendment
Section 1
In case of the removal of the President from office or of his death or resignation, the Vice President shall become President.
Section 2
Whenever there is a vacancy in the office of the Vice President, the President shall nominate a Vice President who shall take office upon confirmation by a majority vote of both Houses of Congress.
Section 3
Whenever the President transmits to the President pro tempore of the Senate and the Speaker of the House of Representatives his written declaration that he is unable to discharge the powers and duties of his office, and until he transmits to them a written declaration to the contrary, such powers and duties shall be discharged by the Vice President as Acting President.
Section 4
Whenever the Vice President and a majority of either the principal officers of the executive departments or of such other body as Congress may by law provide, transmit to the President pro tempore of the Senate and the Speaker of the House of Representatives their written declaration that the President is unable to discharge the powers and duties of his office, the Vice President shall immediately assume the powers and duties of the office as Acting President.
Thereafter, when the President transmits to the President pro tempore of the Senate and the Speaker of the House of Representatives his written declaration that no inability exists, he shall resume the powers and duties of his office unless the Vice President and a majority of either the principal officers of the executive department or of such other body as Congress may by law provide, transmit within four days to the President pro tempore of the Senate and the Speaker of the House of Representatives their written declaration that the President is unable to discharge the powers and duties of his office. Thereupon Congress shall decide the issue, assembling within forty-eight hours for that purpose if not in session. If the Congress, within twenty-one days after receipt of the latter written declaration, or, if Congress is not in session, within twenty-one days after Congress is required to assemble, determines by two-thirds vote of both Houses that the President is unable to discharge the powers and duties of his office, the Vice President shall continue to discharge the same as Acting President; otherwise, the President shall resume the powers and duties of his office.
26th Amendment
Section 1
The right of citizens of the United States, who are eighteen years of age or older, to vote shall not be denied or abridged by the United States or by any State on account of age.
Section 2
The Congress shall have power to enforce this article by appropriate legislation.
27th Amendment
No law, varying the compensation for the services of the Senators and Representatives, shall take effect, until an election of Representatives shall have intervened.