Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions examples/companion_radio/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,13 @@ static uint32_t _atoi(const char* sp) {
#define TCP_PORT 5000
#endif
#elif defined(BLE_PIN_CODE)
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#ifdef BLE_USE_NIMBLE
#include <helpers/esp32/SerialNimBLEInterface.h>
SerialNimBLEInterface serial_interface;
#else
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#endif
#elif defined(SERIAL_RX)
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
Expand Down
246 changes: 246 additions & 0 deletions src/helpers/esp32/SerialNimBLEInterface.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
85 changes: 85 additions & 0 deletions src/helpers/esp32/SerialNimBLEInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#pragma once

#include "../BaseSerialInterface.h"
#include <NimBLEDevice.h>
#include <NimBLEServer.h>
#include <NimBLEUtils.h>

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 <Arduino.h>
#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
2 changes: 2 additions & 0 deletions variants/heltec_ct62/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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]
Expand Down
2 changes: 2 additions & 0 deletions variants/xiao_c3/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,15 @@ 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
; -D MESH_DEBUG=1
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]
Expand Down