#include "SerialBLEInterface.h" static SerialBLEInterface* instance; void SerialBLEInterface::onConnect(uint16_t connection_handle) { BLE_DEBUG_PRINTLN("SerialBLEInterface: connected"); // we now set _isDeviceConnected=true in onSecured callback instead } void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) { BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason); if(instance){ instance->_isDeviceConnected = false; instance->startAdv(); } } void SerialBLEInterface::onSecured(uint16_t connection_handle) { BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured"); if(instance){ instance->_isDeviceConnected = true; // no need to stop advertising on connect, as the ble stack does this automatically } } void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { instance = this; char charpin[20]; sprintf(charpin, "%d", pin_code); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.configPrphConn(250, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); // increase MTU Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.begin(); Bluefruit.setName(device_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); Bluefruit.Security.setSecuredCallback(onSecured); // To be consistent OTA DFU should be added first if it exists //bledfu.begin(); // Configure and start the BLE Uart service bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bleuart.begin(); } void SerialBLEInterface::startAdv() { BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising"); // clean restart if already advertising if(Bluefruit.Advertising.isRunning()){ BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart"); Bluefruit.Advertising.stop(); } Bluefruit.Advertising.clearData(); // clear advertising data Bluefruit.ScanResponse.clearData(); // clear scan response data // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); // Include the BLE UART (AKA 'NUS') 128-bit UUID Bluefruit.Advertising.addService(bleuart); // Secondary Scan Response packet (optional) // Since there is no room for 'Name' in Advertising packet Bluefruit.ScanResponse.addName(); /* Start Advertising * - Enable auto advertising if disconnected * - Interval: fast mode = 20 ms, slow mode = 152.5 ms * - Timeout for fast mode is 30 seconds * - Start(timeout) with timeout = 0 will advertise forever (until connected) * * For recommended advertising interval * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect Bluefruit.Advertising.setInterval(32, 244); Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds } void SerialBLEInterface::stopAdv() { BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising"); // we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack if(!Bluefruit.Advertising.isRunning()){ return; } // stop advertising Bluefruit.Advertising.stop(); } // ---------- public methods void SerialBLEInterface::enable() { if (_isEnabled) return; _isEnabled = true; clearBuffers(); // Start advertising startAdv(); } void SerialBLEInterface::disable() { _isEnabled = false; BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); #ifdef RAK_BOARD Bluefruit.disconnect(Bluefruit.connHandle()); #else uint16_t conn_id; if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { Bluefruit.disconnect(conn_id); } #endif Bluefruit.Advertising.restartOnDisconnect(false); Bluefruit.Advertising.stop(); Bluefruit.Advertising.clearData(); stopAdv(); } size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { if (len > MAX_FRAME_SIZE) { BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len); return 0; } if (_isDeviceConnected && len > 0) { if (send_queue_len >= FRAME_QUEUE_SIZE) { BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); return 0; } send_queue[send_queue_len].len = len; // add to send queue memcpy(send_queue[send_queue_len].buf, src, len); send_queue_len++; return len; } return 0; } #define BLE_WRITE_MIN_INTERVAL 60 bool SerialBLEInterface::isWriteBusy() const { return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? } size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { if (send_queue_len > 0 // first, check send queue && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL // space the writes apart ) { _last_write = millis(); bleuart.write(send_queue[0].buf, send_queue[0].len); BLE_DEBUG_PRINTLN("writeBytes: sz=%d, hdr=%d", (uint32_t)send_queue[0].len, (uint32_t) send_queue[0].buf[0]); send_queue_len--; for (int i = 0; i < send_queue_len; i++) { // delete top item from queue send_queue[i] = send_queue[i + 1]; } } else { int len = bleuart.available(); if (len > 0) { bleuart.readBytes(dest, len); BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); return len; } } return 0; } bool SerialBLEInterface::isConnected() const { return _isDeviceConnected; }