diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7e636acee..6ddb8a341 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -42,8 +42,13 @@ static uint32_t _atoi(const char* sp) { #define TCP_PORT 5000 #endif #elif defined(BLE_PIN_CODE) - #include - SerialBLEInterface serial_interface; + #ifdef BLE_USE_NIMBLE + #include + SerialNimBLEInterface serial_interface; + #else + #include + SerialBLEInterface serial_interface; + #endif #elif defined(SERIAL_RX) #include ArduinoSerialInterface serial_interface; diff --git a/src/helpers/esp32/SerialNimBLEInterface.cpp b/src/helpers/esp32/SerialNimBLEInterface.cpp new file mode 100644 index 000000000..c527087f9 --- /dev/null +++ b/src/helpers/esp32/SerialNimBLEInterface.cpp @@ -0,0 +1,246 @@ +#include "SerialNimBLEInterface.h" + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID +#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" +#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" + +#define ADVERT_RESTART_DELAY 1000 // millis + +void SerialNimBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { + _pin_code = pin_code; + + if (strcmp(name, "@@MAC") == 0) { + uint8_t addr[8]; + memset(addr, 0, sizeof(addr)); + esp_efuse_mac_get_default(addr); + sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param) + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + } + char dev_name[32+16]; + sprintf(dev_name, "%s%s", prefix, name); + + // Create the BLE Device + NimBLEDevice::init(dev_name); + NimBLEDevice::setMTU(MAX_FRAME_SIZE); + NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); + NimBLEDevice::setSecurityAuth(BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM | BLE_SM_PAIR_AUTHREQ_SC); + + //NimBLEDevice::setPower(ESP_PWR_LVL_N8); + + // Create the BLE Server + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(this); + + // Create the BLE Service + pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ_ENC); + + NimBLE2904* pTx2904 = pTxCharacteristic->create2904(); + pTx2904->setFormat(NimBLE2904::FORMAT_UTF8); + + NimBLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC); + pRxCharacteristic->setCallbacks(this); + + pService->start(); + + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->setName(dev_name); + pAdvertising->addServiceUUID(pService->getUUID()); + pAdvertising->enableScanResponse(true); + pAdvertising->start(); +} + +// -------- NimBLEServerCallbacks methods +uint32_t SerialNimBLEInterface::onPassKeyDisplay() { + BLE_DEBUG_PRINTLN("onPassKeyDisplay()"); + return _pin_code; +} + +void SerialNimBLEInterface::onConfirmPassKey(NimBLEConnInfo& connInfo, uint32_t pass_key) { + BLE_DEBUG_PRINTLN("onConfirmPassKey(%u)", pass_key); + NimBLEDevice::injectConfirmPasskey(connInfo, true); +} + +void SerialNimBLEInterface::onAuthenticationComplete(NimBLEConnInfo& connInfo) { + if (connInfo.isEncrypted()) { + BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success"); + deviceConnected = true; + } else { + BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*"); + + //pServer->removePeerDevice(connInfo.getConnHandle(), true); + pServer->disconnect(connInfo.getConnHandle()); + adv_restart_time = millis() + ADVERT_RESTART_DELAY; + } +} + +void SerialNimBLEInterface::onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) { + uint16_t conn_id = connInfo.getConnHandle(); + uint16_t mtu = pServer->getPeerMTU(conn_id); + + BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", conn_id, mtu); + + last_conn_id = conn_id; +} + +void SerialNimBLEInterface::onMTUChange(uint16_t mtu, NimBLEConnInfo& connInfo) { + BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", mtu); +} + +void SerialNimBLEInterface::onDisconnect(NimBLEServer* pServer, NimBLEConnInfo &connInfo, int reason) { + BLE_DEBUG_PRINTLN("onDisconnect()"); + if (_isEnabled) { + adv_restart_time = millis() + ADVERT_RESTART_DELAY; + + // loop() will detect this on next loop, and set deviceConnected to false + } +} + +// -------- NimBLECharacteristicCallbacks methods + +void SerialNimBLEInterface::onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo &connInfo) { + std::string rxValue = pCharacteristic->getValue(); + size_t len = rxValue.length(); + + if (len > MAX_FRAME_SIZE) { + BLE_DEBUG_PRINTLN("ERROR: onWrite(), frame too big, len=%d", len); + } else if (recv_queue_len >= FRAME_QUEUE_SIZE) { + BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!"); + } else { + recv_queue[recv_queue_len].len = len; + memcpy(recv_queue[recv_queue_len].buf, rxValue.c_str(), len); + recv_queue_len++; + } +} + +// ---------- public methods + +void SerialNimBLEInterface::enable() { + if (_isEnabled) return; + + _isEnabled = true; + clearBuffers(); + + // Start the service + pService->start(); + + // Start advertising + + //pServer->getAdvertising()->setMinInterval(500); + //pServer->getAdvertising()->setMaxInterval(1000); + + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + pAdvertising->start(); + + adv_restart_time = 0; +} + +void SerialNimBLEInterface::disable() { + _isEnabled = false; + + BLE_DEBUG_PRINTLN("SerialNimBLEInterface::disable"); + + pServer->getAdvertising()->stop(); + pServer->disconnect(last_conn_id); + NimBLEDevice::stopAdvertising(); + oldDeviceConnected = deviceConnected = false; + adv_restart_time = 0; +} + +size_t SerialNimBLEInterface::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 (deviceConnected && 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 SerialNimBLEInterface::isWriteBusy() const { + return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? +} + +size_t SerialNimBLEInterface::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(); + pTxCharacteristic->setValue(send_queue[0].buf, send_queue[0].len); + pTxCharacteristic->notify(); + + 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]; + } + } + + if (recv_queue_len > 0) { // check recv queue + size_t len = recv_queue[0].len; // take from top of queue + memcpy(dest, recv_queue[0].buf, len); + + BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); + + recv_queue_len--; + for (int i = 0; i < recv_queue_len; i++) { // delete top item from queue + recv_queue[i] = recv_queue[i + 1]; + } + return len; + } + + if (pServer->getConnectedCount() == 0) deviceConnected = false; + + if (deviceConnected != oldDeviceConnected) { + if (!deviceConnected) { // disconnecting + clearBuffers(); + + BLE_DEBUG_PRINTLN("SerialNimBLEInterface -> disconnecting..."); + + //pServer->getAdvertising()->setMinInterval(500); + //pServer->getAdvertising()->setMaxInterval(1000); + + adv_restart_time = millis() + ADVERT_RESTART_DELAY; + } else { + BLE_DEBUG_PRINTLN("SerialNimBLEInterface -> stopping advertising"); + BLE_DEBUG_PRINTLN("SerialNimBLEInterface -> connecting..."); + // connecting + // do stuff here on connecting + pServer->getAdvertising()->stop(); + adv_restart_time = 0; + } + oldDeviceConnected = deviceConnected; + } + + if (adv_restart_time && millis() >= adv_restart_time) { + if (pServer->getConnectedCount() == 0) { + BLE_DEBUG_PRINTLN("SerialNimBLEInterface -> re-starting advertising"); + pServer->getAdvertising()->start(); // re-Start advertising + } + adv_restart_time = 0; + } + return 0; +} + +bool SerialNimBLEInterface::isConnected() const { + return deviceConnected; //pServer != NULL && pServer->getConnectedCount() > 0; +} diff --git a/src/helpers/esp32/SerialNimBLEInterface.h b/src/helpers/esp32/SerialNimBLEInterface.h new file mode 100644 index 000000000..64117674c --- /dev/null +++ b/src/helpers/esp32/SerialNimBLEInterface.h @@ -0,0 +1,85 @@ +#pragma once + +#include "../BaseSerialInterface.h" +#include +#include +#include + +class SerialNimBLEInterface : public BaseSerialInterface, NimBLEServerCallbacks, NimBLECharacteristicCallbacks { + NimBLEServer *pServer; + NimBLEService *pService; + NimBLECharacteristic * pTxCharacteristic; + bool deviceConnected; + bool oldDeviceConnected; + bool _isEnabled; + uint16_t last_conn_id; + uint32_t _pin_code; + unsigned long _last_write; + unsigned long adv_restart_time; + + struct Frame { + uint8_t len; + uint8_t buf[MAX_FRAME_SIZE]; + }; + + #define FRAME_QUEUE_SIZE 4 + int recv_queue_len; + Frame recv_queue[FRAME_QUEUE_SIZE]; + int send_queue_len; + Frame send_queue[FRAME_QUEUE_SIZE]; + + void clearBuffers() { recv_queue_len = 0; send_queue_len = 0; } + +protected: + // NimBLEServerCallbacks methods + uint32_t onPassKeyDisplay() override; + void onConfirmPassKey (NimBLEConnInfo &connInfo, uint32_t pin) override; + void onAuthenticationComplete(NimBLEConnInfo &connInfo) override; + void onConnect(NimBLEServer* pServer, NimBLEConnInfo &connInfo) override; + void onMTUChange(uint16_t MTU, NimBLEConnInfo &connInfo) override; + void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo &connInfo, int reason) override; + + // NimBLECharacteristicCallbacks methods + void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo &connInfo) override; + +public: + SerialNimBLEInterface() { + pServer = NULL; + pService = NULL; + deviceConnected = false; + oldDeviceConnected = false; + adv_restart_time = 0; + _isEnabled = false; + _last_write = 0; + last_conn_id = 0; + send_queue_len = recv_queue_len = 0; + } + + /** + * init the BLE interface. + * @param prefix a prefix for the device name + * @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned + * @param pin_code the BLE security pin + */ + void begin(const char* prefix, char* name, uint32_t pin_code); + + // BaseSerialInterface methods + void enable() override; + void disable() override; + bool isEnabled() const override { return _isEnabled; } + + bool isConnected() const override; + + bool isWriteBusy() const override; + size_t writeFrame(const uint8_t src[], size_t len) override; + size_t checkRecvFrame(uint8_t dest[]) override; +}; + +#if BLE_DEBUG_LOGGING && ARDUINO + #include + #define BLE_DEBUG_PRINT(F, ...) Serial.printf("BLE: " F, ##__VA_ARGS__) + #define BLE_DEBUG_PRINTLN(F, ...) Serial.printf("BLE: " F "\n", ##__VA_ARGS__) +#else + #define BLE_DEBUG_PRINT(...) {} + #define BLE_DEBUG_PRINTLN(...) {} +#endif diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 1f2e330a3..649e6e045 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -119,6 +119,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D BLE_PIN_CODE=123456 + -D BLE_USE_NIMBLE=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} @@ -127,6 +128,7 @@ build_src_filter = ${Heltec_ct62.build_src_filter} lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} + h2zero/NimBLE-Arduino densaugeo/base64 @ ~1.4.0 [env:Heltec_ct62_sensor] diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 76b72174a..1055c9740 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -76,6 +76,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 + -D BLE_USE_NIMBLE=1 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 @@ -83,6 +84,7 @@ build_flags = lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} + h2zero/NimBLE-Arduino densaugeo/base64 @ ~1.4.0 [env:Xiao_C3_companion_radio_usb]