#include "EggDuino.h" #ifdef ESP32 #include #include namespace { constexpr char kBleDeviceNamePrefix[] = "EggBot_"; constexpr char kBleServiceUuid[] = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; constexpr char kBleRxCharUuid[] = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; constexpr char kBleTxCharUuid[] = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; constexpr size_t kBleRxQueueSize = 512; constexpr size_t kBleNotifyChunkSize = 20; NimBLEServer *g_pBleServer = NULL; NimBLECharacteristic *g_pBleTxCharacteristic = NULL; uint8_t g_bleRxQueue[kBleRxQueueSize]; size_t g_bleRxHead = 0; size_t g_bleRxTail = 0; bool g_bleRxQueueOverflow = false; bool g_bleClientConnected = false; portMUX_TYPE g_bleQueueMux = portMUX_INITIALIZER_UNLOCKED; void logBleDiag(const String &message) { Log(message); } bool queueBleByte(uint8_t value) { bool queued = false; portENTER_CRITICAL(&g_bleQueueMux); const size_t nextHead = (g_bleRxHead + 1) % kBleRxQueueSize; if (nextHead == g_bleRxTail) { g_bleRxQueueOverflow = true; } else { g_bleRxQueue[g_bleRxHead] = value; g_bleRxHead = nextHead; queued = true; } portEXIT_CRITICAL(&g_bleQueueMux); return queued; } bool dequeueBleByte(uint8_t *value) { bool hasData = false; portENTER_CRITICAL(&g_bleQueueMux); if (g_bleRxHead != g_bleRxTail) { *value = g_bleRxQueue[g_bleRxTail]; g_bleRxTail = (g_bleRxTail + 1) % kBleRxQueueSize; hasData = true; } portEXIT_CRITICAL(&g_bleQueueMux); return hasData; } class EggDuinoBleServerCallbacks : public NimBLEServerCallbacks { void onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) { (void)pServer; (void)connInfo; g_bleClientConnected = true; Log("BLE client connected"); } void onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) { (void)connInfo; (void)reason; g_bleClientConnected = false; const bool restartedAdvertising = pServer->startAdvertising(); logBleDiag(String("BLE client disconnected; advertising restart: ") + (restartedAdvertising ? "ok" : "failed")); } }; class EggDuinoBleRxCallbacks : public NimBLECharacteristicCallbacks { void onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) { (void)connInfo; const std::string value = pCharacteristic->getValue(); for (size_t i = 0; i < value.size(); ++i) { queueBleByte(static_cast(value[i])); } } }; } // namespace void startBleInterface() { char bleDeviceName[32] = {0}; buildDeviceName(bleDeviceName, sizeof(bleDeviceName)); if (bleDeviceName[0] == '\0') { snprintf(bleDeviceName, sizeof(bleDeviceName), "%sUNKNOWN", kBleDeviceNamePrefix); } logBleDiag("BLE init begin"); logBleDiag(String("BLE device name: ") + bleDeviceName); logBleDiag(String("BLE service UUID: ") + kBleServiceUuid); logBleDiag(String("BLE RX UUID: ") + kBleRxCharUuid); logBleDiag(String("BLE TX UUID: ") + kBleTxCharUuid); NimBLEDevice::init(bleDeviceName); const bool blePowerSet = NimBLEDevice::setPower(ESP_PWR_LVL_P6); const std::string bleAddress = NimBLEDevice::getAddress().toString(); logBleDiag(String("BLE radio address: ") + bleAddress.c_str()); logBleDiag(String("BLE TX power set: ") + (blePowerSet ? "ok" : "failed")); g_pBleServer = NimBLEDevice::createServer(); if (g_pBleServer == NULL) { logBleDiag("BLE init failed: createServer returned null"); return; } g_pBleServer->setCallbacks(new EggDuinoBleServerCallbacks()); logBleDiag("BLE server created"); NimBLEService *pService = g_pBleServer->createService(kBleServiceUuid); if (pService == NULL) { logBleDiag("BLE init failed: createService returned null"); return; } g_pBleTxCharacteristic = pService->createCharacteristic( kBleTxCharUuid, NIMBLE_PROPERTY::NOTIFY | NIMBLE_PROPERTY::READ); if (g_pBleTxCharacteristic == NULL) { logBleDiag("BLE init failed: TX characteristic creation failed"); return; } NimBLECharacteristic *pRxCharacteristic = pService->createCharacteristic( kBleRxCharUuid, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); if (pRxCharacteristic == NULL) { logBleDiag("BLE init failed: RX characteristic creation failed"); return; } pRxCharacteristic->setCallbacks(new EggDuinoBleRxCallbacks()); logBleDiag("BLE characteristics created"); const bool serviceStarted = pService->start(); logBleDiag(String("BLE service start: ") + (serviceStarted ? "ok" : "failed")); if (!serviceStarted) { return; } NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising(); if (pAdvertising == NULL) { logBleDiag("BLE init failed: getAdvertising returned null"); return; } pAdvertising->enableScanResponse(true); const bool localNameSet = pAdvertising->setName(bleDeviceName); const bool serviceUuidAdded = pAdvertising->addServiceUUID(kBleServiceUuid); const bool advertisingStarted = pAdvertising->start(); logBleDiag(String("BLE advertising set local name: ") + (localNameSet ? "ok" : "failed")); logBleDiag(String("BLE advertising add service UUID: ") + (serviceUuidAdded ? "ok" : "failed")); logBleDiag(String("BLE advertising start: ") + (advertisingStarted ? "ok" : "failed")); if (serviceUuidAdded && advertisingStarted) { logBleDiag("BLE service started"); } } void handleBleInterface() { if (g_bleRxQueueOverflow) { g_bleRxQueueOverflow = false; Log("BLE RX queue overflow"); } uint8_t value = 0; while (dequeueBleByte(&value)) { setActiveProtocolContext(&g_BLECmd, PROTOCOL_TRANSPORT_BLE); g_BLECmd.readChar(static_cast(value)); } } bool bleProtocolWrite(const char *message) { if ((message == NULL) || !g_bleClientConnected || (g_pBleTxCharacteristic == NULL)) { return false; } const uint8_t *payload = reinterpret_cast(message); size_t remaining = strlen(message); if (remaining == 0) { return true; } while (remaining > 0) { size_t chunkLen = remaining; if (chunkLen > kBleNotifyChunkSize) { chunkLen = kBleNotifyChunkSize; } g_pBleTxCharacteristic->setValue(payload, chunkLen); g_pBleTxCharacteristic->notify(); payload += chunkLen; remaining -= chunkLen; delay(3); } return true; } #endif