Files
pyxis/lib/ble_interface/BLEInterface.h
T
torlando-tech a0ff631001 Track A.5/6/7: Identity persistence + Transport stats + Interface overrides
Three API migrations to keep the graft moving against vanilla
attermann/microReticulum @ 0.3.0:

(A.5) Identity persistence migrated to OS::set_loop_callback.
  Was:  Identity::set_persist_yield_callback(cb)        // fork-only
        Identity::should_persist_data()                 // fork-only
  Now:  RNS::Utilities::OS::set_loop_callback(cb)       // upstream global
        reticulum->should_persist_data()                // already used
  The fork's split between Identity-specific 5s fast-flush and
  Reticulum-level 60s full-persist is unified upstream into a single
  Reticulum::should_persist_data() entry point. The fast cadence is
  folded into microStore's dirty-tracking. If we observe excessive
  lost-known-destinations after crashes, revisit microStore's flush
  cadence rather than re-adding the fork-only Identity API.

(A.6) Transport stats diagnostics disabled — vanilla upstream doesn't
  expose the *_count() getter family the fork added. Two [TABLES]
  diagnostic blocks in main.cpp now print a placeholder. Restore by
  porting to upstream's get_path_table().size() and friends, or PR the
  getters back to upstream Transport. Tracked in
  pyxis_microReticulum_graft_spike_findings.md.

(A.7) BLE/SX1262 Interface stat methods are no longer virtual overrides.
  Vanilla upstream Interface base class doesn't declare get_stats /
  get_rssi / get_snr. Kept the methods as plain (non-virtual)
  BLEInterface / SX1262Interface members; callers needing stats access
  must hold the concrete type, not the base Interface*. Propose
  upstream PR adding to base API if polymorphic access matters.

Also: setLogCallback -> set_log_callback (renamed in upstream commit
4d6f0b9 "Added dual-class PSRAM/TLSF allocator system").

Pyxis still doesn't build — next failures (4 distinct):
  - OS::register_filesystem signature changed to microStore::FileSystem&.
    Real microStore migration needed for UniversalFileSystem.
  - LXMRouter::process_sync still missing despite vendored src-shim copy.
    Include-order or shadowing — needs investigation.
  - MEMORY_MONITOR_POLL macro not picked up despite -I src-shim/Instrumentation.
  - Identity::should_persist_data appears to still be referenced via
    LXMF or another vendored layer — would surface once the above land.
2026-05-04 20:25:02 -04:00

297 lines
11 KiB
C++

/**
* @file BLEInterface.h
* @brief BLE-Reticulum Protocol v2.2 interface for microReticulum
*
* Main BLEInterface class that integrates with the Reticulum transport layer.
* Supports dual-mode operation (central + peripheral) for mesh networking.
*
* Usage:
* BLEInterface ble("ble0");
* ble.setDeviceName("my-node");
* ble.setLocalIdentity(identity.hash());
*
* Interface interface(&ble);
* interface.start();
* Transport::register_interface(interface);
*/
#pragma once
#include "Interface.h"
#include "Bytes.h"
#include "Type.h"
#include "BLETypes.h"
#include "BLEPlatform.h"
#include "BLEFragmenter.h"
#include "BLEReassembler.h"
#include "BLEPeerManager.h"
#include "BLEIdentityManager.h"
#include <mutex>
#ifdef ARDUINO
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#endif
/**
* @brief Reticulum BLE Interface
*
* Implements the BLE-Reticulum protocol v2.2 as a microReticulum interface.
* Manages BLE connections, fragmentation, and peer discovery.
*/
class BLEInterface : public RNS::InterfaceImpl {
public:
// Protocol constants
static constexpr uint32_t BITRATE_GUESS = 100000; // ~100 kbps effective throughput
static constexpr uint16_t HW_MTU_DEFAULT = 512; // Default after MTU negotiation
// Timing constants
static constexpr double SCAN_INTERVAL = 5.0; // Seconds between scans
static constexpr double KEEPALIVE_INTERVAL = 15.0; // Seconds between keepalives
static constexpr double MAINTENANCE_INTERVAL = 1.0; // Seconds between maintenance
static constexpr double CONNECTION_COOLDOWN = 10.0; // Seconds between connection attempts (reduces 574 risk)
public:
/**
* @brief Construct a BLE interface
* @param name Interface name (e.g., "ble0")
*/
explicit BLEInterface(const char* name = "BLEInterface");
virtual ~BLEInterface();
//=========================================================================
// Configuration (call before start())
//=========================================================================
/**
* @brief Set the BLE role
* @param role CENTRAL, PERIPHERAL, or DUAL (default: DUAL)
*/
void setRole(RNS::BLE::Role role);
/**
* @brief Set the advertised device name
* @param name Device name (max ~8 characters recommended)
*/
void setDeviceName(const std::string& name);
/**
* @brief Set our local identity hash
*
* Required for the identity handshake protocol.
* Should be the first 16 bytes of the transport identity hash.
*
* @param identity 16-byte identity hash
*/
void setLocalIdentity(const RNS::Bytes& identity);
/**
* @brief Set maximum connections
* @param max Maximum simultaneous connections (default: 7)
*/
void setMaxConnections(uint8_t max);
//=========================================================================
// InterfaceImpl Overrides
//=========================================================================
virtual bool start() override;
virtual void stop() override;
virtual void loop() override;
virtual std::string toString() const override {
return "BLEInterface[" + _name + "/" + _device_name + "]";
}
/**
* @brief Get interface statistics
* @return Map with central_connections and peripheral_connections counts
*
* NOT a virtual override post-graft to attermann/microReticulum @ 0.3.0:
* vanilla upstream's `Interface` base class doesn't declare get_stats().
* The fork added it for memory diagnostics. Callers that wanted
* polymorphic stats access have to downcast to BLEInterface, or we
* propose a PR adding it to upstream's Interface API.
*/
std::map<std::string, float> get_stats() const;
//=========================================================================
// Status
//=========================================================================
/**
* @brief Summary info for a connected peer (fixed-size, no heap allocation)
*/
struct PeerSummary {
char identity[14]; // First 12 hex chars + null, or empty if unknown
char mac[18]; // "AA:BB:CC:DD:EE:FF" format
int8_t rssi;
};
static constexpr size_t MAX_PEER_SUMMARIES = 8;
/**
* @brief Get count of connected peers
*/
size_t peerCount() const;
/**
* @brief Get summaries of connected peers (for UI display)
* @param out Pre-allocated array to fill
* @param max_count Maximum entries to fill
* @return Actual number of entries filled
*/
size_t getConnectedPeerSummaries(PeerSummary* out, size_t max_count) const;
/**
* @brief Check if BLE is running
*/
bool isRunning() const { return _platform && _platform->isRunning(); }
/**
* @brief Start BLE on its own FreeRTOS task
*
* This allows BLE operations to run independently of the main loop,
* preventing UI freezes during scans and connections.
*
* @param priority Task priority (default 1)
* @param core Core to pin the task to (default 0, where BT controller runs)
* @return true if task started successfully
*/
bool start_task(int priority = 1, int core = 0);
/**
* @brief Check if BLE is running on its own task
*/
bool is_task_running() const { return _task_handle != nullptr; }
protected:
virtual void send_outgoing(const RNS::Bytes& data) override;
private:
//=========================================================================
// Platform Callbacks
//=========================================================================
void onScanResult(const RNS::BLE::ScanResult& result);
void onConnected(const RNS::BLE::ConnectionHandle& conn);
void onDisconnected(const RNS::BLE::ConnectionHandle& conn, uint8_t reason);
void onMTUChanged(const RNS::BLE::ConnectionHandle& conn, uint16_t mtu);
void onServicesDiscovered(const RNS::BLE::ConnectionHandle& conn, bool success);
void onDataReceived(const RNS::BLE::ConnectionHandle& conn, const RNS::Bytes& data);
void onCentralConnected(const RNS::BLE::ConnectionHandle& conn);
void onCentralDisconnected(const RNS::BLE::ConnectionHandle& conn);
void onWriteReceived(const RNS::BLE::ConnectionHandle& conn, const RNS::Bytes& data);
//=========================================================================
// Handshake Callbacks
//=========================================================================
void onHandshakeComplete(const RNS::Bytes& mac, const RNS::Bytes& identity, bool is_central);
void onHandshakeFailed(const RNS::Bytes& mac, const std::string& reason);
void onMacRotation(const RNS::Bytes& old_mac, const RNS::Bytes& new_mac, const RNS::Bytes& identity);
//=========================================================================
// Reassembly Callbacks
//=========================================================================
void onPacketReassembled(const RNS::Bytes& peer_identity, const RNS::Bytes& packet);
void onReassemblyTimeout(const RNS::Bytes& peer_identity, const std::string& reason);
//=========================================================================
// Internal Operations
//=========================================================================
void setupCallbacks();
void performScan();
void processDiscoveredPeers();
void sendKeepalives();
void performMaintenance();
/**
* @brief Send data to a specific peer (with fragmentation)
*/
bool sendToPeer(const RNS::Bytes& peer_identity, const RNS::Bytes& data);
/**
* @brief Process incoming data from a peer
*/
void handleIncomingData(const RNS::BLE::ConnectionHandle& conn, const RNS::Bytes& data);
/**
* @brief Initiate handshake for a new connection
*/
void initiateHandshake(const RNS::BLE::ConnectionHandle& conn);
//=========================================================================
// Configuration
//=========================================================================
RNS::BLE::Role _role = RNS::BLE::Role::DUAL;
std::string _device_name = "RNS-Node";
uint8_t _max_connections = RNS::BLE::Limits::MAX_PEERS;
RNS::Bytes _local_identity;
//=========================================================================
// Components
//=========================================================================
RNS::BLE::IBLEPlatform::Ptr _platform;
RNS::BLE::BLEPeerManager _peer_manager;
RNS::BLE::BLEIdentityManager _identity_manager;
RNS::BLE::BLEReassembler _reassembler;
// Per-peer fragmenters (fixed-size pool, keyed by identity)
struct FragmenterSlot {
bool in_use = false;
RNS::Bytes identity;
RNS::BLE::BLEFragmenter fragmenter;
void clear() { in_use = false; identity.clear(); fragmenter = RNS::BLE::BLEFragmenter(); }
};
static constexpr size_t MAX_FRAGMENTERS = 4;
FragmenterSlot _fragmenter_pool[MAX_FRAGMENTERS];
//=========================================================================
// State
//=========================================================================
double _last_scan = 0;
double _last_keepalive = 0;
double _last_maintenance = 0;
double _last_connection_attempt = 0; // Cooldown after connection failures
double _last_advertising_refresh = 0;
// Pending handshake completions (deferred from callback to loop for stack safety)
static constexpr size_t MAX_PENDING_HANDSHAKES = 4;
struct PendingHandshake {
RNS::Bytes mac;
RNS::Bytes identity;
bool is_central = false;
};
PendingHandshake _pending_handshake_pool[MAX_PENDING_HANDSHAKES];
size_t _pending_handshake_count = 0;
// Pending data fragments (deferred from callback to loop for stack safety)
static constexpr size_t MAX_PENDING_DATA = 8;
struct PendingData {
RNS::Bytes identity;
RNS::Bytes data;
double queued_at = 0; // Timestamp for expiry of unresolvable entries
};
PendingData _pending_data_pool[MAX_PENDING_DATA];
size_t _pending_data_count = 0;
// Thread safety for callbacks from BLE stack
// Using recursive_mutex because handleIncomingData holds the lock while
// calling processReceivedData, which can trigger onHandshakeComplete callback
// that also needs the lock
mutable std::recursive_mutex _mutex;
//=========================================================================
// FreeRTOS Task Support
//=========================================================================
TaskHandle_t _task_handle = nullptr;
static void ble_task(void* param);
};