Files
MeshCore/src/helpers/nrf52/SerialBLEInterface.h
T

231 lines
8.7 KiB
C++

#pragma once
#include "../BaseSerialInterface.h"
#include "../BluetoothMac.h"
#include "../BleTxStallWatchdog.h"
#include "../UsbLogging.h"
#include "SecuritySessionTimer.h"
#include <bluefruit.h>
#include <atomic>
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
#include "../BleMotaProtocol.h"
#include "../BleMotaStream.h"
#endif
#ifndef BLE_TX_POWER
#define BLE_TX_POWER 4
#endif
// Bluefruit's BANDWIDTH_MAX preset reserves a 100-unit connection event and
// three notification buffers. That is useful on boards with roomy RAM, but
// it exceeds the usable SoftDevice reservation of some bootloader layouts.
// Keep the existing preset as the default while allowing constrained boards
// to retain the 247-byte MTU with a smaller event/buffer reservation.
#ifndef COMPANION_BLE_PRPH_MTU
#define COMPANION_BLE_PRPH_MTU 247
#endif
#ifndef COMPANION_BLE_PRPH_EVENT_LENGTH
#define COMPANION_BLE_PRPH_EVENT_LENGTH 100
#endif
#ifndef COMPANION_BLE_PRPH_HVN_QUEUE
#define COMPANION_BLE_PRPH_HVN_QUEUE 3
#endif
#ifndef COMPANION_BLE_PRPH_WRCMD_QUEUE
#define COMPANION_BLE_PRPH_WRCMD_QUEUE 1
#endif
// Nordic UART is the required Companion service. The legacy Nordic DFU
// service is an optional extension that some constrained field combinations
// cannot register alongside the Companion GATT table.
#ifndef COMPANION_FEATURE_BLE_DFU
#define COMPANION_FEATURE_BLE_DFU 1
#endif
class SerialBLEInterface : public BaseSerialInterface {
#if COMPANION_FEATURE_BLE_DFU
BLEDfu bledfu;
#endif
BLEUart bleuart;
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
BLEService _mota_service = BLEService(mesh::ota::BLE_MOTA_SERVICE_UUID);
BLECharacteristic _mota_request =
BLECharacteristic(mesh::ota::BLE_MOTA_REQUEST_UUID);
BLECharacteristic _mota_response =
BLECharacteristic(mesh::ota::BLE_MOTA_RESPONSE_UUID);
mesh::ota::BleMotaStream _mota_stream;
// The normal Nordic UART service is the Companion transport. mOTA is an
// optional Full-Companion extension and must never take that transport down
// if a fielded SoftDevice rejects one of its extra GATT attributes.
bool _mota_available = false;
#endif
bool _isEnabled;
bool _begin_attempted = false;
bool _begin_ready = false;
char _begin_failure[96] = {};
char _active_name[48] = {};
#if defined(MESH_NRF52_BLE_TRACE) && MESH_NRF52_BLE_TRACE
uint32_t _debug_last_loop_ms = 0;
std::atomic<uint32_t> _debug_mtu{0};
static void onBleUartNotify(uint16_t handle, bool enabled);
static void onBleUartOverflow(uint16_t handle, uint16_t leftover);
#endif
bool _isDeviceConnected;
uint16_t _conn_handle;
unsigned long _last_health_check;
unsigned long _last_retry_attempt;
ble_gap_addr_t _peer_address = {};
bool _peer_address_valid;
bool _bond_removed_for_connection;
ble_gap_addr_t _successful_peer_address = {};
bool _stealth_pair_once;
bool _bonded_only;
bool _bonded_only_configure_pending;
mesh::companion::BluetoothPeerIdentity _pending_bonded_peer;
std::atomic<bool> _advertisingSuppressed{false};
std::atomic<bool> _bondedOnlyRecoveryPending{false};
std::atomic<bool> _pairingRequestPending{false};
std::atomic<bool> _companionDataSeen{false};
std::atomic<bool> _successfulConnectionPending{false};
std::atomic<uint32_t> _successfulConnectionStarted{0};
SecuritySessionTimer _security_timer;
SecuritySessionTimer _companion_start_timer;
mesh::BleTxStallWatchdog _tx_stall_watchdog;
mesh::BleDisconnectRecovery _tx_disconnect_recovery;
struct Frame {
uint8_t len;
uint8_t buf[MAX_FRAME_SIZE];
};
#define FRAME_QUEUE_SIZE 12
uint8_t send_queue_len;
Frame send_queue[FRAME_QUEUE_SIZE];
uint8_t recv_queue_len;
Frame recv_queue[FRAME_QUEUE_SIZE];
void clearBuffers();
void shiftSendQueueLeft();
void shiftRecvQueueLeft();
size_t writeBleUartFrame(const Frame& frame);
void recoverStalledTx(const char* cause);
void serviceTxRecovery(uint32_t now);
bool removeStoredBondForPeer(const char* cause);
void noteSuccessfulConnection(const ble_gap_addr_t& peer_address);
bool resolveSuccessfulPeer(
mesh::companion::BluetoothPeerIdentity& peer) const;
bool configureBondedOnlyAdvertising(
const mesh::companion::BluetoothPeerIdentity& peer,
bool require_stored_bond);
void serviceBondedOnlyTransition();
void requestBondedOnlyRecovery(const char* cause);
bool advertisingAllowed() const;
bool startAdvertising(const char* failure_cause);
bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const;
bool isAdvertising() const;
static void onConnect(uint16_t connection_handle);
static void onDisconnect(uint16_t connection_handle, uint8_t reason);
static void onSecured(uint16_t connection_handle);
static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request);
static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status);
static void onBLEEvent(ble_evt_t* evt);
static void onBleUartRX(uint16_t conn_handle);
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
static void onMotaResponse(uint16_t conn_handle,
BLECharacteristic* characteristic,
uint8_t* data, uint16_t length);
static size_t sendMotaRequest(void* context, const uint8_t* data,
size_t length);
#endif
public:
SerialBLEInterface() {
_isEnabled = false;
_isDeviceConnected = false;
_conn_handle = BLE_CONN_HANDLE_INVALID;
_last_health_check = 0;
_last_retry_attempt = 0;
_peer_address_valid = false;
_bond_removed_for_connection = false;
_stealth_pair_once = false;
_bonded_only = false;
_bonded_only_configure_pending = false;
send_queue_len = 0;
recv_queue_len = 0;
}
/**
* init the BLE interface.
* @param prefix a prefix for the device name
* @param name a name for the device (combined with prefix); "@@MAC" uses the hardware address
* @param pin_code the BLE security pin
* @param custom_address optional human-order BLE random-static address
* @param clear_bonds clear saved peer bonds before accepting connections
* @param stealth_pair_once suppress general advertising after first pairing
* @param bonded_only_peer optional peer allowed to reconnect in stealth mode
*/
bool begin(const char* prefix, const char* name, uint32_t pin_code,
const uint8_t* custom_address = nullptr,
bool clear_bonds = false, bool stealth_pair_once = false,
const mesh::companion::BluetoothPeerIdentity*
bonded_only_peer = nullptr);
// Refresh the GAP and scan-response name after a saved node/Bluetooth name
// changes. A connected phone is left alone; the next idle pass applies it.
bool refreshName(const char* prefix, const char* name);
// Kept after a failed begin so a normal Companion terminal can report the
// cause even when early USB logging was not available.
const char* beginFailure() const { return _begin_failure; }
#if defined(MESH_NRF52_BLE_TRACE) && MESH_NRF52_BLE_TRACE
void formatDebugState(char* reply, size_t capacity);
#endif
void disconnect();
void loop() override;
void enable() override;
void disable() override;
bool isEnabled() const override { return _isEnabled; }
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
bool hasPendingIO() const override;
bool takePairingRequest() override {
return _pairingRequestPending.exchange(false, std::memory_order_acq_rel);
}
bool takeSuccessfulConnection(
mesh::companion::BluetoothPeerIdentity* peer = nullptr);
bool enableBondedOnlyAdvertising(
const mesh::companion::BluetoothPeerIdentity& peer);
void cancelStealthPairingTransition();
bool takeBondedOnlyRecovery() {
return _bondedOnlyRecoveryPending.exchange(
false, std::memory_order_acq_rel);
}
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
#if COMPANION_FEATURE_BLE_MOTA_SOURCE
Stream& motaStream() { return _mota_stream; }
bool isMotaChannelReady();
bool isMotaStreamActive() const { return _mota_stream.isActive(); }
void setMotaStreamActive(bool active) {
_mota_stream.setActive(active);
if (active) {
_companionDataSeen.store(true, std::memory_order_release);
_companion_start_timer.cancel();
}
}
#endif
};
#if BLE_DEBUG_LOGGING && ARDUINO
#include <Arduino.h>
#define BLE_DEBUG_PRINT(F, ...) do { if (mesh::isUsbLoggingEnabled()) { mesh::usbLoggingPort().printf("BLE: " F, ##__VA_ARGS__); } } while(0)
#define BLE_DEBUG_PRINTLN(F, ...) do { if (mesh::isUsbLoggingEnabled()) { mesh::usbLoggingPort().printf("BLE: " F "\n", ##__VA_ARGS__); } } while(0)
#else
#define BLE_DEBUG_PRINT(...) {}
#define BLE_DEBUG_PRINTLN(...) {}
#endif