SerialBLEInterface.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. #include "SerialBLEInterface.h"
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include "ble_gap.h"
  5. #include "ble_hci.h"
  6. // Magic numbers came from actual testing
  7. #define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds
  8. #define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected
  9. // Connection parameters (units: interval=1.25ms, timeout=10ms)
  10. #define BLE_MIN_CONN_INTERVAL 12 // 15ms
  11. #define BLE_MAX_CONN_INTERVAL 24 // 30ms
  12. #define BLE_SLAVE_LATENCY 4
  13. #define BLE_CONN_SUP_TIMEOUT 200 // 2000ms
  14. // Advertising parameters
  15. #define BLE_ADV_INTERVAL_MIN 32 // 20ms (units: 0.625ms)
  16. #define BLE_ADV_INTERVAL_MAX 244 // 152.5ms (units: 0.625ms)
  17. #define BLE_ADV_FAST_TIMEOUT 30 // seconds
  18. // RX drain buffer size for overflow protection
  19. #define BLE_RX_DRAIN_BUF_SIZE 32
  20. static SerialBLEInterface* instance = nullptr;
  21. void SerialBLEInterface::onConnect(uint16_t connection_handle) {
  22. BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle);
  23. if (instance) {
  24. instance->_conn_handle = connection_handle;
  25. instance->_isDeviceConnected = false;
  26. instance->clearBuffers();
  27. }
  28. }
  29. void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) {
  30. BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected handle=0x%04X reason=%u", connection_handle, reason);
  31. if (instance) {
  32. if (instance->_conn_handle == connection_handle) {
  33. instance->_conn_handle = BLE_CONN_HANDLE_INVALID;
  34. instance->_isDeviceConnected = false;
  35. instance->clearBuffers();
  36. }
  37. }
  38. }
  39. void SerialBLEInterface::onSecured(uint16_t connection_handle) {
  40. BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle);
  41. if (instance) {
  42. if (instance->isValidConnection(connection_handle, true)) {
  43. instance->_isDeviceConnected = true;
  44. // Connection interval units: 1.25ms, supervision timeout units: 10ms
  45. // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic."
  46. // So we explicitly set it here to make Android & Apple match
  47. ble_gap_conn_params_t conn_params;
  48. conn_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
  49. conn_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
  50. conn_params.slave_latency = BLE_SLAVE_LATENCY;
  51. conn_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
  52. uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params);
  53. if (err_code == NRF_SUCCESS) {
  54. BLE_DEBUG_PRINTLN("Connection parameter update requested: %u-%ums interval, latency=%u, %ums timeout",
  55. conn_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units)
  56. conn_params.max_conn_interval * 5 / 4,
  57. conn_params.slave_latency,
  58. conn_params.conn_sup_timeout * 10); // convert to ms (10ms units)
  59. } else {
  60. BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code);
  61. }
  62. } else {
  63. BLE_DEBUG_PRINTLN("onSecured: ignoring stale/duplicate callback");
  64. }
  65. }
  66. }
  67. bool SerialBLEInterface::onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request) {
  68. (void)connection_handle;
  69. (void)passkey;
  70. BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing passkey request match=%d", match_request);
  71. return true;
  72. }
  73. void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t auth_status) {
  74. BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing complete handle=0x%04X status=%u", connection_handle, auth_status);
  75. if (instance) {
  76. if (instance->isValidConnection(connection_handle)) {
  77. if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
  78. BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful");
  79. } else {
  80. BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting");
  81. instance->disconnect();
  82. }
  83. } else {
  84. BLE_DEBUG_PRINTLN("onPairingComplete: ignoring stale callback");
  85. }
  86. }
  87. }
  88. void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) {
  89. if (!instance) return;
  90. if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) {
  91. uint16_t conn_handle = evt->evt.gap_evt.conn_handle;
  92. if (instance->isValidConnection(conn_handle)) {
  93. BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u",
  94. conn_handle,
  95. evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval,
  96. evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval,
  97. evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency,
  98. evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout);
  99. uint32_t err_code = sd_ble_gap_conn_param_update(conn_handle, NULL);
  100. if (err_code == NRF_SUCCESS) {
  101. BLE_DEBUG_PRINTLN("Accepted CONN_PARAM_UPDATE_REQUEST (using PPCP)");
  102. } else {
  103. BLE_DEBUG_PRINTLN("ERROR: Failed to accept CONN_PARAM_UPDATE_REQUEST: 0x%08X", err_code);
  104. }
  105. } else {
  106. BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: ignoring stale callback for handle=0x%04X", conn_handle);
  107. }
  108. }
  109. }
  110. void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
  111. instance = this;
  112. char charpin[20];
  113. snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code);
  114. // If we want to control BLE LED ourselves, uncomment this:
  115. // Bluefruit.autoConnLed(false);
  116. Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
  117. Bluefruit.begin();
  118. char dev_name[32+16];
  119. if (strcmp(name, "@@MAC") == 0) {
  120. ble_gap_addr_t addr;
  121. if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) {
  122. sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param)
  123. addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]);
  124. }
  125. }
  126. sprintf(dev_name, "%s%s", prefix, name);
  127. // Connection interval units: 1.25ms, supervision timeout units: 10ms
  128. ble_gap_conn_params_t ppcp_params;
  129. ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
  130. ppcp_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
  131. ppcp_params.slave_latency = BLE_SLAVE_LATENCY;
  132. ppcp_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
  133. uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params);
  134. if (err_code == NRF_SUCCESS) {
  135. BLE_DEBUG_PRINTLN("PPCP set: %u-%ums interval, latency=%u, %ums timeout",
  136. ppcp_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units)
  137. ppcp_params.max_conn_interval * 5 / 4,
  138. ppcp_params.slave_latency,
  139. ppcp_params.conn_sup_timeout * 10); // convert to ms (10ms units)
  140. } else {
  141. BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code);
  142. }
  143. Bluefruit.setTxPower(BLE_TX_POWER);
  144. Bluefruit.setName(dev_name);
  145. Bluefruit.Security.setMITM(true);
  146. Bluefruit.Security.setPIN(charpin);
  147. Bluefruit.Security.setIOCaps(true, false, false);
  148. Bluefruit.Security.setPairPasskeyCallback(onPairingPasskey);
  149. Bluefruit.Security.setPairCompleteCallback(onPairingComplete);
  150. Bluefruit.Periph.setConnectCallback(onConnect);
  151. Bluefruit.Periph.setDisconnectCallback(onDisconnect);
  152. Bluefruit.Security.setSecuredCallback(onSecured);
  153. Bluefruit.setEventCallback(onBLEEvent);
  154. bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
  155. bleuart.begin();
  156. bleuart.setRxCallback(onBleUartRX);
  157. Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  158. Bluefruit.Advertising.addTxPower();
  159. Bluefruit.Advertising.addService(bleuart);
  160. Bluefruit.ScanResponse.addName();
  161. Bluefruit.Advertising.setInterval(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX);
  162. Bluefruit.Advertising.setFastTimeout(BLE_ADV_FAST_TIMEOUT);
  163. Bluefruit.Advertising.restartOnDisconnect(true);
  164. }
  165. void SerialBLEInterface::clearBuffers() {
  166. send_queue_len = 0;
  167. recv_queue_len = 0;
  168. _last_retry_attempt = 0;
  169. bleuart.flush();
  170. }
  171. void SerialBLEInterface::shiftSendQueueLeft() {
  172. if (send_queue_len > 0) {
  173. send_queue_len--;
  174. for (uint8_t i = 0; i < send_queue_len; i++) {
  175. send_queue[i] = send_queue[i + 1];
  176. }
  177. }
  178. }
  179. void SerialBLEInterface::shiftRecvQueueLeft() {
  180. if (recv_queue_len > 0) {
  181. recv_queue_len--;
  182. for (uint8_t i = 0; i < recv_queue_len; i++) {
  183. recv_queue[i] = recv_queue[i + 1];
  184. }
  185. }
  186. }
  187. bool SerialBLEInterface::isValidConnection(uint16_t handle, bool requireWaitingForSecurity) const {
  188. if (_conn_handle != handle) {
  189. return false;
  190. }
  191. BLEConnection* conn = Bluefruit.Connection(handle);
  192. if (conn == nullptr || !conn->connected()) {
  193. return false;
  194. }
  195. if (requireWaitingForSecurity && _isDeviceConnected) {
  196. return false;
  197. }
  198. return true;
  199. }
  200. bool SerialBLEInterface::isAdvertising() const {
  201. ble_gap_addr_t adv_addr;
  202. uint32_t err_code = sd_ble_gap_adv_addr_get(0, &adv_addr);
  203. return (err_code == NRF_SUCCESS);
  204. }
  205. void SerialBLEInterface::enable() {
  206. if (_isEnabled) return;
  207. _isEnabled = true;
  208. clearBuffers();
  209. _last_health_check = millis();
  210. Bluefruit.Advertising.restartOnDisconnect(true);
  211. Bluefruit.Advertising.start(0);
  212. }
  213. void SerialBLEInterface::disconnect() {
  214. if (_conn_handle != BLE_CONN_HANDLE_INVALID) {
  215. sd_ble_gap_disconnect(_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
  216. }
  217. }
  218. void SerialBLEInterface::disable() {
  219. _isEnabled = false;
  220. BLE_DEBUG_PRINTLN("SerialBLEInterface: disable");
  221. Bluefruit.Advertising.restartOnDisconnect(false);
  222. Bluefruit.Advertising.stop();
  223. disconnect();
  224. _last_health_check = 0;
  225. }
  226. size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
  227. if (len > MAX_FRAME_SIZE) {
  228. BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len);
  229. return 0;
  230. }
  231. bool connected = isConnected();
  232. if (connected && len > 0) {
  233. if (send_queue_len >= FRAME_QUEUE_SIZE) {
  234. BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
  235. return 0;
  236. }
  237. send_queue[send_queue_len].len = len;
  238. memcpy(send_queue[send_queue_len].buf, src, len);
  239. send_queue_len++;
  240. return len;
  241. }
  242. return 0;
  243. }
  244. size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
  245. if (send_queue_len > 0) {
  246. if (!isConnected()) {
  247. BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue");
  248. send_queue_len = 0;
  249. } else {
  250. unsigned long now = millis();
  251. bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS);
  252. if (!throttle_active) {
  253. Frame frame_to_send = send_queue[0];
  254. size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len);
  255. if (written == frame_to_send.len) {
  256. BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]);
  257. _last_retry_attempt = 0;
  258. shiftSendQueueLeft();
  259. } else if (written > 0) {
  260. BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len);
  261. _last_retry_attempt = 0;
  262. shiftSendQueueLeft();
  263. } else {
  264. if (!isConnected()) {
  265. BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame");
  266. _last_retry_attempt = 0;
  267. shiftSendQueueLeft();
  268. } else {
  269. BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry");
  270. _last_retry_attempt = now;
  271. }
  272. }
  273. }
  274. }
  275. }
  276. if (recv_queue_len > 0) {
  277. size_t len = recv_queue[0].len;
  278. memcpy(dest, recv_queue[0].buf, len);
  279. BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, (unsigned)dest[0]);
  280. shiftRecvQueueLeft();
  281. return len;
  282. }
  283. // Advertising watchdog: periodically check if advertising is running, restart if not
  284. // Only run when truly disconnected (no connection handle), not during connection establishment
  285. unsigned long now = millis();
  286. if (_isEnabled && !isConnected() && _conn_handle == BLE_CONN_HANDLE_INVALID) {
  287. if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) {
  288. _last_health_check = now;
  289. if (!isAdvertising()) {
  290. BLE_DEBUG_PRINTLN("SerialBLEInterface: advertising watchdog - advertising stopped, restarting");
  291. Bluefruit.Advertising.start(0);
  292. }
  293. }
  294. }
  295. return 0;
  296. }
  297. void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) {
  298. if (!instance) {
  299. return;
  300. }
  301. if (instance->_conn_handle != conn_handle || !instance->isConnected()) {
  302. while (instance->bleuart.available() > 0) {
  303. instance->bleuart.read();
  304. }
  305. return;
  306. }
  307. while (instance->bleuart.available() > 0) {
  308. if (instance->recv_queue_len >= FRAME_QUEUE_SIZE) {
  309. while (instance->bleuart.available() > 0) {
  310. instance->bleuart.read();
  311. }
  312. BLE_DEBUG_PRINTLN("onBleUartRX: recv queue full, dropping data");
  313. break;
  314. }
  315. int avail = instance->bleuart.available();
  316. if (avail > MAX_FRAME_SIZE) {
  317. BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail);
  318. uint8_t drain_buf[BLE_RX_DRAIN_BUF_SIZE];
  319. while (instance->bleuart.available() > 0) {
  320. int chunk = instance->bleuart.available() > BLE_RX_DRAIN_BUF_SIZE ? BLE_RX_DRAIN_BUF_SIZE : instance->bleuart.available();
  321. instance->bleuart.readBytes(drain_buf, chunk);
  322. }
  323. continue;
  324. }
  325. int read_len = avail;
  326. instance->recv_queue[instance->recv_queue_len].len = read_len;
  327. instance->bleuart.readBytes(instance->recv_queue[instance->recv_queue_len].buf, read_len);
  328. instance->recv_queue_len++;
  329. }
  330. }
  331. bool SerialBLEInterface::isConnected() const {
  332. return _isDeviceConnected && Bluefruit.connected() > 0;
  333. }
  334. bool SerialBLEInterface::isWriteBusy() const {
  335. return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
  336. }