diff --git a/examples/Advanced/NimBLE_Client/CMakeLists.txt b/examples/Advanced/NimBLE_Client/CMakeLists.txt new file mode 100644 index 0000000..7b68bed --- /dev/null +++ b/examples/Advanced/NimBLE_Client/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(NimBLE_Client) diff --git a/examples/Advanced/NimBLE_Client/Makefile b/examples/Advanced/NimBLE_Client/Makefile new file mode 100644 index 0000000..d1a0fa2 --- /dev/null +++ b/examples/Advanced/NimBLE_Client/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_Client + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Advanced/NimBLE_Client/main/CMakeLists.txt b/examples/Advanced/NimBLE_Client/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/Advanced/NimBLE_Client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/Advanced/NimBLE_Client/main/component.mk b/examples/Advanced/NimBLE_Client/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Advanced/NimBLE_Client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Advanced/NimBLE_Client/main/main.cpp b/examples/Advanced/NimBLE_Client/main/main.cpp new file mode 100644 index 0000000..1bf6ab1 --- /dev/null +++ b/examples/Advanced/NimBLE_Client/main/main.cpp @@ -0,0 +1,372 @@ + +/** NimBLE_Server Demo: + * + * Demonstrates many of the available features of the NimBLE client library. + * + * Created: on March 24 2020 + * Author: H2zero + * +*/ +#include + +extern "C" {void app_main(void);} + +void scanEndedCB(NimBLEScanResults results); + +static NimBLEAdvertisedDevice* advDevice; + +static bool doConnect = false; +static uint32_t scanTime = 0; /** 0 = scan forever */ + + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class ClientCallbacks : public NimBLEClientCallbacks { + void onConnect(NimBLEClient* pClient) { + printf("Connected\n"); + /** After connection we should change the parameters if we don't need fast response times. + * These settings are 150ms interval, 0 latency, 450ms timout. + * Timeout should be a multiple of the interval, minimum is 100ms. + * I find a multiple of 3-5 * the interval works best for quick response/reconnect. + * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 45 * 10ms = 450ms timeout + */ + pClient->updateConnParams(120,120,0,45); + }; + + void onDisconnect(NimBLEClient* pClient) { + printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str()); + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); + }; + + /********************* Security handled here ********************** + ****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + printf("Client Passkey Request\n"); + /** return the passkey to send to the server */ + return 123456; + }; + + bool onConfirmPIN(uint32_t pass_key){ + printf("The passkey YES/NO number: %d\n", pass_key); + /** Return false if passkeys don't match. */ + return true; + }; + + /** Pairing process complete, we can check the results in ble_gap_conn_desc */ + void onAuthenticationComplete(ble_gap_conn_desc* desc){ + if(!desc->sec_state.encrypted) { + printf("Encrypt connection failed - disconnecting\n"); + /** Find the client with the connection handle provided in desc */ + NimBLEDevice::getClientByID(desc->conn_handle)->disconnect(); + return; + } + }; +}; + + +/** Define a class to handle the callbacks when advertisments are received */ +class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { + + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { + printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); + if(advertisedDevice->isAdvertisingService(NimBLEUUID("DEAD"))) + { + printf("Found Our Service\n"); + /** stop scan before connecting */ + NimBLEDevice::getScan()->stop(); + /** Save the device reference in a global for the client to use*/ + advDevice = advertisedDevice; + /** Ready to connect now */ + doConnect = true; + } + }; +}; + + +/** Notification / Indication receiving handler callback */ +void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ + std::string str = (isNotify == true) ? "Notification" : "Indication"; + str += " from "; + str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString(); + str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); + str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); + str += ", Value = " + std::string((char*)pData, length); + printf("%s\n", str.c_str()); +} + +/** Callback to process the results of the last scan or restart it */ +void scanEndedCB(NimBLEScanResults results){ + printf("Scan Ended\n"); +} + + +/** Create a single global instance of the callback class to be used by all clients */ +static ClientCallbacks clientCB; + + +/** Handles the provisioning of clients and connects / interfaces with the server */ +bool connectToServer() { + NimBLEClient* pClient = nullptr; + + /** Check if we have a client we should reuse first **/ + if(NimBLEDevice::getClientListSize()) { + /** Special case when we already know this device, we send false as the + * second argument in connect() to prevent refreshing the service database. + * This saves considerable time and power. + */ + pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress()); + if(pClient){ + if(!pClient->connect(advDevice, false)) { + printf("Reconnect failed\n"); + return false; + } + printf("Reconnected client\n"); + } + /** We don't already have a client that knows this device, + * we will check for a client that is disconnected that we can use. + */ + else { + pClient = NimBLEDevice::getDisconnectedClient(); + } + } + + /** No client to reuse? Create a new one. */ + if(!pClient) { + if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { + printf("Max clients reached - no more connections available\n"); + return false; + } + + pClient = NimBLEDevice::createClient(); + + printf("New client created\n"); + + pClient->setClientCallbacks(&clientCB, false); + /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. + * These settings are safe for 3 clients to connect reliably, can go faster if you have less + * connections. Timeout should be a multiple of the interval, minimum is 100ms. + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 12 * 10ms = 120ms timeout + */ + pClient->setConnectionParams(6,6,0,15); + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ + pClient->setConnectTimeout(5); + + + if (!pClient->connect(advDevice)) { + /** Created a client but failed to connect, don't need to keep it as it has no data */ + NimBLEDevice::deleteClient(pClient); + printf("Failed to connect, deleted client\n"); + return false; + } + } + + if(!pClient->isConnected()) { + if (!pClient->connect(advDevice)) { + printf("Failed to connect\n"); + return false; + } + } + + printf("Connected to: %s RSSI: %d\n", + pClient->getPeerAddress().toString().c_str(), + pClient->getRssi()); + + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ + NimBLERemoteService* pSvc = nullptr; + NimBLERemoteCharacteristic* pChr = nullptr; + NimBLERemoteDescriptor* pDsc = nullptr; + + pSvc = pClient->getService("DEAD"); + if(pSvc) { /** make sure it's not null */ + pChr = pSvc->getCharacteristic("BEEF"); + } + + if(pChr) { /** make sure it's not null */ + if(pChr->canRead()) { + printf("%s Value: %s\n", + pChr->getUUID().toString().c_str(), + pChr->readValue().c_str()); + } + + if(pChr->canWrite()) { + if(pChr->writeValue("Tasty")) { + printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str()); + } + else { + /** Disconnect if write failed */ + pClient->disconnect(); + return false; + } + + if(pChr->canRead()) { + printf("The value of: %s is now: %s\n", + pChr->getUUID().toString().c_str(), + pChr->readValue().c_str()); + } + } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if(pChr->canNotify()) { + //if(!pChr->registerForNotify(notifyCB)) { + if(!pChr->subscribe(true, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + else if(pChr->canIndicate()) { + /** Send false as first argument to subscribe to indications instead of notifications */ + //if(!pChr->registerForNotify(notifyCB, false)) { + if(!pChr->subscribe(false, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + } + + else{ + printf("DEAD service not found.\n"); + } + + pSvc = pClient->getService("BAAD"); + if(pSvc) { /** make sure it's not null */ + pChr = pSvc->getCharacteristic("F00D"); + } + + if(pChr) { /** make sure it's not null */ + if(pChr->canRead()) { + printf("%s Value: %s\n", + pChr->getUUID().toString().c_str(), + pChr->readValue().c_str()); + } + + pDsc = pChr->getDescriptor(NimBLEUUID("C01D")); + if(pDsc) { /** make sure it's not null */ + printf("Descriptor: %s Value: %s\n", + pDsc->getUUID().toString().c_str(), + pDsc->readValue().c_str()); + } + + if(pChr->canWrite()) { + if(pChr->writeValue("No tip!")) { + printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str()); + } + else { + /** Disconnect if write failed */ + pClient->disconnect(); + return false; + } + + if(pChr->canRead()) { + printf("The value of: %s is now: %s\n", + pChr->getUUID().toString().c_str(), + pChr->readValue().c_str()); + } + } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if(pChr->canNotify()) { + //if(!pChr->registerForNotify(notifyCB)) { + if(!pChr->subscribe(true, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + else if(pChr->canIndicate()) { + /** Send false as first argument to subscribe to indications instead of notifications */ + //if(!pChr->registerForNotify(notifyCB, false)) { + if(!pChr->subscribe(false, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + } + + else{ + printf("BAAD service not found.\n"); + } + + printf("Done with this device!\n"); + return true; +} + +void connectTask (void * parameter){ + /** Loop here until we find a device we want to connect to */ + for(;;) { + if(doConnect) { + doConnect = false; + /** Found a device we want to connect to, do it now */ + if(connectToServer()) { + printf("Success! we should now be getting notifications, scanning for more!\n"); + } else { + printf("Failed to connect, starting scan\n"); + } + + NimBLEDevice::getScan()->start(scanTime,scanEndedCB); + } + vTaskDelay(10/portTICK_PERIOD_MS); + } + + vTaskDelete(NULL); +} + +void app_main (void){ + printf("Starting NimBLE Client\n"); + /** Initialize NimBLE, no device name spcified as we are not advertising */ + NimBLEDevice::init(""); + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison + + /** 2 different ways to set security - both calls achieve the same result. + * no bonding, no man in the middle protection, secure connections. + * + * These are the default values, only shown here for demonstration. + */ + //NimBLEDevice::setSecurityAuth(false, false, true); + NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC); + + /** Optional: set the transmit power, default is -3db */ + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** 12db */ + + /** Optional: set any devices you don't want to get advertisments from */ + // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); + + /** create new scan */ + NimBLEScan* pScan = NimBLEDevice::getScan(); + + /** create a callback that gets called when advertisers are found */ + pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); + + /** Set scan interval (how often) and window (how long) in milliseconds */ + pScan->setInterval(400); + pScan->setWindow(100); + + /** Active scan will gather scan response data from advertisers + * but will use more energy from both devices + */ + pScan->setActiveScan(true); + /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever + * Optional callback for when scanning stops. + */ + pScan->start(scanTime, scanEndedCB); + + printf("Scanning for peripherals\n"); + + xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL); +} + diff --git a/examples/Advanced/NimBLE_Server/CMakeLists.txt b/examples/Advanced/NimBLE_Server/CMakeLists.txt new file mode 100644 index 0000000..e24a91b --- /dev/null +++ b/examples/Advanced/NimBLE_Server/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(NimBLE_Server) diff --git a/examples/Advanced/NimBLE_Server/Makefile b/examples/Advanced/NimBLE_Server/Makefile new file mode 100644 index 0000000..dd998b1 --- /dev/null +++ b/examples/Advanced/NimBLE_Server/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := NimBLE_Server + +include $(IDF_PATH)/make/project.mk diff --git a/examples/Advanced/NimBLE_Server/main/CMakeLists.txt b/examples/Advanced/NimBLE_Server/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/Advanced/NimBLE_Server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/Advanced/NimBLE_Server/main/component.mk b/examples/Advanced/NimBLE_Server/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/Advanced/NimBLE_Server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/Advanced/NimBLE_Server/main/main.cpp b/examples/Advanced/NimBLE_Server/main/main.cpp new file mode 100644 index 0000000..62bd2cd --- /dev/null +++ b/examples/Advanced/NimBLE_Server/main/main.cpp @@ -0,0 +1,226 @@ + +/** NimBLE_Server Demo: + * + * Demonstrates many of the available features of the NimBLE server library. + * + * Created: on March 22 2020 + * Author: H2zero + * +*/ +#include "NimBLEDevice.h" +#include "NimBLELog.h" + +#include + +extern "C" {void app_main(void);} + +static NimBLEServer* pServer; + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class ServerCallbacks: public NimBLEServerCallbacks { + void onConnect(NimBLEServer* pServer) { + printf("Client connected\n"); + NimBLEDevice::startAdvertising(); + }; + /** Alternative onConnect() method to extract details of the connection. + * See: src/ble_gap.h for the details of the ble_gap_conn_desc struct. + */ + void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) { + printf("Client address: %s\n", NimBLEAddress(desc->peer_ota_addr).toString().c_str()); + /** We can use the connection handle here to ask for different connection parameters. + * Args: connection handle, min connection interval, max connection interval + * latency, supervision timeout. + * Units; Min/Max Intervals: 1.25 millisecond increments. + * Latency: number of intervals allowed to skip. + * Timeout: 10 millisecond increments, try for 3x interval time for best results. + */ + pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 18); + }; + void onDisconnect(NimBLEServer* pServer) { + printf("Client disconnected - start advertising\n"); + NimBLEDevice::startAdvertising(); + }; + +/********************* Security handled here ********************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + printf("Server Passkey Request\n"); + /** This should return a random 6 digit number for security + * or make your own static passkey as done here. + */ + return 123456; + }; + + bool onConfirmPIN(uint32_t pass_key){ + printf("The passkey YES/NO number: %d\n", pass_key); + /** Return false if passkeys don't match. */ + return true; + }; + + void onAuthenticationComplete(ble_gap_conn_desc* desc){ + /** Check that encryption was successful, if not we disconnect the client */ + if(!desc->sec_state.encrypted) { + /** NOTE: createServer returns the current server reference unless one is not already created */ + NimBLEDevice::createServer()->disconnect(desc->conn_handle); + printf("Encrypt connection failed - disconnecting client\n"); + return; + } + printf("Starting BLE work!"); + }; +}; + +/** Handler class for characteristic actions */ +class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { + void onRead(NimBLECharacteristic* pCharacteristic){ + printf("%s : onRead(), value: %s\n", + pCharacteristic->getUUID().toString().c_str(), + pCharacteristic->getValue().c_str()); + }; + + void onWrite(NimBLECharacteristic* pCharacteristic) { + printf("%s : onWrite(), value: %s\n", + pCharacteristic->getUUID().toString().c_str(), + pCharacteristic->getValue().c_str()); + }; + /** Called before notification or indication is sent, + * the value can be changed here before sending if desired. + */ + void onNotify(NimBLECharacteristic* pCharacteristic) { + printf("Sending notification to clients\n"); + }; + + + /** The status returned in status is defined in NimBLECharacteristic.h. + * The value returned in code is the NimBLE host return code. + */ + void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) { + printf("Notification/Indication status code: %d , return code: %d, %s\n", + status, + code, + NimBLEUtils::returnCodeToString(code)); + }; +}; + +/** Handler class for descriptor actions */ +class DescriptorCallbacks : public NimBLEDescriptorCallbacks { + void onWrite(NimBLEDescriptor* pDescriptor) { + std::string dscVal((char*)pDescriptor->getValue(), pDescriptor->getLength()); + printf("Descriptor witten value: %s\n", dscVal.c_str()); + }; + + void onRead(NimBLEDescriptor* pDescriptor) { + printf("%s Descriptor read\n", pDescriptor->getUUID().toString().c_str()); + };; +}; + + +/** Define callback instances globally to use for multiple Charateristics \ Descriptors */ +static DescriptorCallbacks dscCallbacks; +static CharacteristicCallbacks chrCallbacks; + +void notifyTask(void * parameter){ + for(;;) { + if(pServer->getConnectedCount()) { + NimBLEService* pSvc = pServer->getServiceByUUID("BAAD"); + if(pSvc) { + NimBLECharacteristic* pChr = pSvc->getCharacteristic("F00D"); + if(pChr) { + pChr->notify(true); + } + } + } + vTaskDelay(2000/portTICK_PERIOD_MS); + } + + vTaskDelete(NULL); +} + +void app_main(void) { + printf("Starting NimBLE Server\n"); + + /** sets device name */ + NimBLEDevice::init("NimBLE"); + + /** Set the IO capabilities of the device, each option will trigger a different pairing method. + * BLE_HS_IO_DISPLAY_ONLY - Passkey pairing + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing + */ + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); // use passkey + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison + + /** 2 different ways to set security - both calls achieve the same result. + * no bonding, no man in the middle protection, secure connections. + * + * These are the default values, only shown here for demonstration. + */ + //NimBLEDevice::setSecurityAuth(false, false, true); + NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC); + + pServer = NimBLEDevice::createServer(); + pServer->setCallbacks(new ServerCallbacks()); + + NimBLEService* pDeadService = pServer->createService("DEAD"); + NimBLECharacteristic* pBeefCharacteristic = pDeadService->createCharacteristic( + "BEEF", + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + /** Require a secure connection for read and write access */ + NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted + NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted + ); + + pBeefCharacteristic->setValue("Burger"); + pBeefCharacteristic->setCallbacks(&chrCallbacks); + + /** 2902 and 2904 descriptors are a special case, when createDescriptor is called with + * either of those uuid's it will create the associated class with the correct properties + * and sizes. However we must cast the returned reference to the correct type as the method + * only returns a pointer to the base NimBLEDescriptor class. + */ + NimBLE2904* pBeef2904 = (NimBLE2904*)pBeefCharacteristic->createDescriptor("2904"); + pBeef2904->setFormat(NimBLE2904::FORMAT_UTF8); + pBeef2904->setCallbacks(&dscCallbacks); + + + NimBLEService* pBaadService = pServer->createService("BAAD"); + NimBLECharacteristic* pFoodCharacteristic = pBaadService->createCharacteristic( + "F00D", + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY + ); + + pFoodCharacteristic->setValue("Fries"); + pFoodCharacteristic->setCallbacks(&chrCallbacks); + + /** Custom descriptor: Arguments are UUID, Properties, max length in bytes of the value */ + NimBLEDescriptor* pC01Ddsc = pFoodCharacteristic->createDescriptor( + "C01D", + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE| + NIMBLE_PROPERTY::WRITE_ENC, // only allow writing if paired / encrypted + 20 + ); + pC01Ddsc->setValue("Send it back!"); + pC01Ddsc->setCallbacks(&dscCallbacks); + + /** Start the services when finished creating all Characteristics and Descriptors */ + pDeadService->start(); + pBaadService->start(); + + NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising(); + /** Add the services to the advertisment data **/ + pAdvertising->addServiceUUID(pDeadService->getUUID()); + pAdvertising->addServiceUUID(pBaadService->getUUID()); + /** If your device is battery powered you may consider setting scan response + * to false as it will extend battery life at the expense of less data sent. + */ + pAdvertising->setScanResponse(true); + pAdvertising->start(); + + printf("Advertising Started\n"); + + xTaskCreate(notifyTask, "notifyTask", 5000, NULL, 1, NULL); +} diff --git a/examples/basic/BLE_client/CMakeLists.txt b/examples/basic/BLE_client/CMakeLists.txt new file mode 100644 index 0000000..8f619c4 --- /dev/null +++ b/examples/basic/BLE_client/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(BLE_client) diff --git a/examples/basic/BLE_client/Makefile b/examples/basic/BLE_client/Makefile new file mode 100644 index 0000000..d2e7b5a --- /dev/null +++ b/examples/basic/BLE_client/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := BLE_client + +include $(IDF_PATH)/make/project.mk diff --git a/examples/basic/BLE_client/main/CMakeLists.txt b/examples/basic/BLE_client/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/basic/BLE_client/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/basic/BLE_client/main/component.mk b/examples/basic/BLE_client/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/basic/BLE_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/basic/BLE_client/main/main.cpp b/examples/basic/BLE_client/main/main.cpp new file mode 100644 index 0000000..49e8ad4 --- /dev/null +++ b/examples/basic/BLE_client/main/main.cpp @@ -0,0 +1,200 @@ +/** + * A BLE client example that is rich in capabilities. + * There is a lot new capabilities implemented. + * author unknown + * updated by chegewara + * updated for NimBLE by H2zero + */ + +/** NimBLE differences highlighted in comment blocks **/ + +/*******original******** +#include "BLEDevice.h" +***********************/ +#include "NimBLEDevice.h" + +extern "C"{void app_main(void);} + +// The remote service we wish to connect to. +static BLEUUID serviceUUID("4fafc201-1fb5-459e-8fcc-c5c9c331914b"); +// The characteristic of the remote service we are interested in. +static BLEUUID charUUID("beb5483e-36e1-4688-b7f5-ea07361b26a8"); + +static bool doConnect = false; +static bool connected = false; +static bool doScan = false; +static BLERemoteCharacteristic* pRemoteCharacteristic; +static BLEAdvertisedDevice* myDevice; + +static void notifyCallback( + BLERemoteCharacteristic* pBLERemoteCharacteristic, + uint8_t* pData, + size_t length, + bool isNotify) { + printf("Notify callback for characteristic %s of data length %d data: %s\n", + pBLERemoteCharacteristic->getUUID().toString().c_str(), + length, + (char*)pData); +} + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class MyClientCallback : public BLEClientCallbacks { + void onConnect(BLEClient* pclient) { + } + + void onDisconnect(BLEClient* pclient) { + connected = false; + printf("onDisconnect"); + } +/***************** New - Security handled here ******************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + printf("Client PassKeyRequest\n"); + return 123456; + } + bool onConfirmPIN(uint32_t pass_key){ + printf("The passkey YES/NO number: %d\n", pass_key); + return true; + } + + void onAuthenticationComplete(ble_gap_conn_desc desc){ + printf("Starting BLE work!\n"); + } +/*******************************************************************/ +}; + +bool connectToServer() { + printf("Forming a connection to %s\n", myDevice->getAddress().toString().c_str()); + + BLEClient* pClient = BLEDevice::createClient(); + printf(" - Created client\n"); + + pClient->setClientCallbacks(new MyClientCallback()); + + // Connect to the remove BLE Server. + pClient->connect(myDevice); // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private) + printf(" - Connected to server\n"); + + // Obtain a reference to the service we are after in the remote BLE server. + BLERemoteService* pRemoteService = pClient->getService(serviceUUID); + if (pRemoteService == nullptr) { + printf("Failed to find our service UUID: %s\n", serviceUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + printf(" - Found our service\n"); + + + // Obtain a reference to the characteristic in the service of the remote BLE server. + pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID); + if (pRemoteCharacteristic == nullptr) { + printf("Failed to find our characteristic UUID: %s\n", charUUID.toString().c_str()); + pClient->disconnect(); + return false; + } + printf(" - Found our characteristic\n"); + + // Read the value of the characteristic. + if(pRemoteCharacteristic->canRead()) { + std::string value = pRemoteCharacteristic->readValue(); + printf("The characteristic value was: %s\n", value.c_str()); + } + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if(pRemoteCharacteristic->canNotify()) { + //pRemoteCharacteristic->registerForNotify(notifyCallback); + pRemoteCharacteristic->subscribe(true, notifyCallback); + } + + connected = true; + return true; +} + +/** + * Scan for BLE servers and find the first one that advertises the service we are looking for. + */ +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + /** + * Called for each advertising BLE server. + */ + +/*** Only a reference to the advertised device is passed now + void onResult(BLEAdvertisedDevice advertisedDevice) { **/ + void onResult(BLEAdvertisedDevice* advertisedDevice) { + printf("BLE Advertised Device found: %s\n", advertisedDevice->toString().c_str()); + + // We have found a device, let us now see if it contains the service we are looking for. +/******************************************************************************** + if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) { +********************************************************************************/ + if (advertisedDevice->haveServiceUUID() && advertisedDevice->isAdvertisingService(serviceUUID)) { + + BLEDevice::getScan()->stop(); +/******************************************************************* + myDevice = new BLEAdvertisedDevice(advertisedDevice); +*******************************************************************/ + myDevice = advertisedDevice; /** Just save the reference now, no need to copy the object */ + doConnect = true; + doScan = true; + + } // Found our server + } // onResult +}; // MyAdvertisedDeviceCallbacks + + +// This is the Arduino main loop function. +void connectTask (void * parameter){ + for(;;) { + // If the flag "doConnect" is true then we have scanned for and found the desired + // BLE Server with which we wish to connect. Now we connect to it. Once we are + // connected we set the connected flag to be true. + if (doConnect == true) { + if (connectToServer()) { + printf("We are now connected to the BLE Server.\n"); + } else { + printf("We have failed to connect to the server; there is nothin more we will do.\n"); + } + doConnect = false; + } + + // If we are connected to a peer BLE Server, update the characteristic each time we are reached + // with the current time since boot. + if (connected) { + char buf[256]; + snprintf(buf, 256, "Time since boot: %lu", (unsigned long)(esp_timer_get_time() / 1000000ULL)); + printf("Setting new characteristic value to %s\n", buf); + + // Set the characteristic's value to be the array of bytes that is actually a string. + /*** Note: write value now returns true if successful, false otherwise - try again or disconnect ***/ + pRemoteCharacteristic->writeValue((uint8_t*)buf, strlen(buf), false); + }else if(doScan){ + BLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino + } + + vTaskDelay(1000/portTICK_PERIOD_MS); // Delay a second between loops. + } + + vTaskDelete(NULL); +} // End of loop + + +void app_main(void) { + printf("Starting BLE Client application...\n"); + BLEDevice::init(""); + + // Retrieve a Scanner and set the callback we want to use to be informed when we + // have detected a new device. Specify that we want active scanning and start the + // scan to run for 5 seconds. + BLEScan* pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setInterval(1349); + pBLEScan->setWindow(449); + pBLEScan->setActiveScan(true); + + xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL); + pBLEScan->start(5, false); +} // End of setup. + diff --git a/examples/basic/BLE_notify/CMakeLists.txt b/examples/basic/BLE_notify/CMakeLists.txt new file mode 100644 index 0000000..dbfacf5 --- /dev/null +++ b/examples/basic/BLE_notify/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(BLE_notify) diff --git a/examples/basic/BLE_notify/Makefile b/examples/basic/BLE_notify/Makefile new file mode 100644 index 0000000..b895d99 --- /dev/null +++ b/examples/basic/BLE_notify/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := BLE_notify + +include $(IDF_PATH)/make/project.mk diff --git a/examples/basic/BLE_notify/main/CMakeLists.txt b/examples/basic/BLE_notify/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/basic/BLE_notify/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/basic/BLE_notify/main/component.mk b/examples/basic/BLE_notify/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/basic/BLE_notify/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/basic/BLE_notify/main/main.cpp b/examples/basic/BLE_notify/main/main.cpp new file mode 100644 index 0000000..8382547 --- /dev/null +++ b/examples/basic/BLE_notify/main/main.cpp @@ -0,0 +1,155 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + updated by chegewara + Refactored back to IDF by H2zero + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 4fafc201-1fb5-459e-8fcc-c5c9c331914b + And has a characteristic of: beb5483e-36e1-4688-b7f5-ea07361b26a8 + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + A connect hander associated with the server starts a background task that performs notification + every couple of seconds. +*/ + +/** NimBLE differences highlighted in comment blocks **/ + +/*******original******** +#include +#include +#include +#include +***********************/ +#include + +extern "C" {void app_main(void);} + +BLEServer* pServer = NULL; +BLECharacteristic* pCharacteristic = NULL; +bool deviceConnected = false; +bool oldDeviceConnected = false; +uint32_t value = 0; + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } +/***************** New - Security handled here ******************** +****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + printf("Server PassKeyRequest\n"); + return 123456; + } + + bool onConfirmPIN(uint32_t pass_key){ + printf("The passkey YES/NO number: %d\n", pass_key); + return true; + } + + void onAuthenticationComplete(ble_gap_conn_desc desc){ + printf("Starting BLE work!\n"); + } +/*******************************************************************/ +}; + +void connectedTask (void * parameter){ + for(;;) { + // notify changed value + if (deviceConnected) { + pCharacteristic->setValue((uint8_t*)&value, 4); + pCharacteristic->notify(); + value++; + vTaskDelay(100/portTICK_PERIOD_MS); // bluetooth stack will go into congestion, if too many packets are sent + } + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + vTaskDelay(500/portTICK_PERIOD_MS); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + printf("start advertising\n"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } + + vTaskDelay(10/portTICK_PERIOD_MS); // Delay between loops to reset watchdog timer + } + + vTaskDelete(NULL); +} + +void app_main(void) { + // Create the BLE Device + BLEDevice::init("ESP32"); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + /******* Enum Type NIMBLE_PROPERTY now ******* + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE + ); + **********************************************/ + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE | + NIMBLE_PROPERTY::NOTIFY | + NIMBLE_PROPERTY::INDICATE + ); + + // https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.client_characteristic_configuration.xml + // Create a BLE Descriptor + /*************************************************** + NOTE: DO NOT create a 2902 descriptor. + it will be created automatically if notifications + or indications are enabled on a characteristic. + + pCharacteristic->addDescriptor(new BLE2902()); + ****************************************************/ + // Start the service + pService->start(); + + // Start advertising + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(false); + /** This method had been removed ** + pAdvertising->setMinPreferred(0x0); // set value to 0x00 to not advertise this parameter + **/ + + xTaskCreate(connectedTask, "connectedTask", 5000, NULL, 1, NULL); + + BLEDevice::startAdvertising(); + printf("Waiting a client connection to notify...\n"); +} diff --git a/examples/basic/BLE_scan/CMakeLists.txt b/examples/basic/BLE_scan/CMakeLists.txt new file mode 100644 index 0000000..0f64bee --- /dev/null +++ b/examples/basic/BLE_scan/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(BLE_scan) diff --git a/examples/basic/BLE_scan/Makefile b/examples/basic/BLE_scan/Makefile new file mode 100644 index 0000000..ebca276 --- /dev/null +++ b/examples/basic/BLE_scan/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := BLE_scan + +include $(IDF_PATH)/make/project.mk diff --git a/examples/basic/BLE_scan/main/CMakeLists.txt b/examples/basic/BLE_scan/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/basic/BLE_scan/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/basic/BLE_scan/main/component.mk b/examples/basic/BLE_scan/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/basic/BLE_scan/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/basic/BLE_scan/main/main.cpp b/examples/basic/BLE_scan/main/main.cpp new file mode 100644 index 0000000..5686dd9 --- /dev/null +++ b/examples/basic/BLE_scan/main/main.cpp @@ -0,0 +1,52 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by Evandro Copercini + Refactored back to IDF by H2zero +*/ + +/** NimBLE differences highlighted in comment blocks **/ + +/*******original******** +#include +#include +#include +#include +***********************/ + +#include + +extern "C"{void app_main(void);} + +int scanTime = 5; //In seconds +BLEScan* pBLEScan; + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + void onResult(BLEAdvertisedDevice* advertisedDevice) { + printf("Advertised Device: %s \n", advertisedDevice->toString().c_str()); + } +}; + +void scanTask (void * parameter){ + for(;;) { + // put your main code here, to run repeatedly: + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); + printf("Devices found: %d\n", foundDevices.getCount()); + printf("Scan done!\n"); + pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory + vTaskDelay(2000/portTICK_PERIOD_MS); // Delay a second between loops. + } + + vTaskDelete(NULL); +} + +void app_main(void) { + printf("Scanning...\n"); + + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); //create new scan + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); // less or equal setInterval value + xTaskCreate(scanTask, "scanTask", 5000, NULL, 1, NULL); +} diff --git a/examples/basic/BLE_server/CMakeLists.txt b/examples/basic/BLE_server/CMakeLists.txt new file mode 100644 index 0000000..03b5365 --- /dev/null +++ b/examples/basic/BLE_server/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(BLE_server) diff --git a/examples/basic/BLE_server/Makefile b/examples/basic/BLE_server/Makefile new file mode 100644 index 0000000..92dd6cd --- /dev/null +++ b/examples/basic/BLE_server/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := BLE_server + +include $(IDF_PATH)/make/project.mk diff --git a/examples/basic/BLE_server/main/CMakeLists.txt b/examples/basic/BLE_server/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/basic/BLE_server/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/basic/BLE_server/main/component.mk b/examples/basic/BLE_server/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/basic/BLE_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/basic/BLE_server/main/main.cpp b/examples/basic/BLE_server/main/main.cpp new file mode 100644 index 0000000..85c0a3e --- /dev/null +++ b/examples/basic/BLE_server/main/main.cpp @@ -0,0 +1,57 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleServer.cpp + Ported to Arduino ESP32 by Evandro Copercini + updates by chegewara + Refactored back to IDF by H2zero +*/ + +/** NimBLE differences highlighted in comment blocks **/ + +/*******original******** +#include +#include +#include +***********************/ + +#include + +extern "C"{void app_main(void);} + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ + +#define SERVICE_UUID "4fafc201-1fb5-459e-8fcc-c5c9c331914b" +#define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" + +void app_main(void) { + printf("Starting BLE work!\n"); + + BLEDevice::init("Long name works now"); + BLEServer *pServer = BLEDevice::createServer(); + BLEService *pService = pServer->createService(SERVICE_UUID); + BLECharacteristic *pCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID, + /***** Enum Type NIMBLE_PROPERTY now ***** + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_WRITE + ); + *****************************************/ + NIMBLE_PROPERTY::READ | + NIMBLE_PROPERTY::WRITE + ); + + pCharacteristic->setValue("Hello World says Neil"); + pService->start(); + // BLEAdvertising *pAdvertising = pServer->getAdvertising(); // this still is working for backward compatibility + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + pAdvertising->addServiceUUID(SERVICE_UUID); + pAdvertising->setScanResponse(true); + + /** These methods have been removed ** + pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue + pAdvertising->setMinPreferred(0x12); + */ + + BLEDevice::startAdvertising(); + printf("Characteristic defined! Now you can read it in your phone!\n"); +} diff --git a/examples/basic/BLE_uart/CMakeLists.txt b/examples/basic/BLE_uart/CMakeLists.txt new file mode 100644 index 0000000..9060e47 --- /dev/null +++ b/examples/basic/BLE_uart/CMakeLists.txt @@ -0,0 +1,7 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(SUPPORTED_TARGETS esp32) +project(BLE_uart) diff --git a/examples/basic/BLE_uart/Makefile b/examples/basic/BLE_uart/Makefile new file mode 100644 index 0000000..9323b57 --- /dev/null +++ b/examples/basic/BLE_uart/Makefile @@ -0,0 +1,3 @@ +PROJECT_NAME := BLE_uart + +include $(IDF_PATH)/make/project.mk diff --git a/examples/basic/BLE_uart/main/CMakeLists.txt b/examples/basic/BLE_uart/main/CMakeLists.txt new file mode 100644 index 0000000..9be9075 --- /dev/null +++ b/examples/basic/BLE_uart/main/CMakeLists.txt @@ -0,0 +1,4 @@ +set(COMPONENT_SRCS "main.cpp") +set(COMPONENT_ADD_INCLUDEDIRS ".") + +register_component() \ No newline at end of file diff --git a/examples/basic/BLE_uart/main/component.mk b/examples/basic/BLE_uart/main/component.mk new file mode 100644 index 0000000..a98f634 --- /dev/null +++ b/examples/basic/BLE_uart/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/basic/BLE_uart/main/main.cpp b/examples/basic/BLE_uart/main/main.cpp new file mode 100644 index 0000000..e0d9422 --- /dev/null +++ b/examples/basic/BLE_uart/main/main.cpp @@ -0,0 +1,167 @@ +/* + Video: https://www.youtube.com/watch?v=oCMOYS71NIU + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp + Ported to Arduino ESP32 by Evandro Copercini + Refactored back to IDF by H2zero + + Create a BLE server that, once we receive a connection, will send periodic notifications. + The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E + Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE" + Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with "NOTIFY" + + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create a BLE Service + 3. Create a BLE Characteristic on the Service + 4. Create a BLE Descriptor on the characteristic + 5. Start the service. + 6. Start advertising. + + In this example rxValue is the data received (only accessible inside that function). + And txValue is the data to be sent, in this example just a byte incremented every second. +*/ + +/** NimBLE differences highlighted in comment blocks **/ + +/*******original******** +#include +#include +#include +#include +***********************/ +#include + +extern "C"{void app_main(void);} + +BLEServer *pServer = NULL; +BLECharacteristic * pTxCharacteristic; +bool deviceConnected = false; +bool oldDeviceConnected = false; +uint8_t txValue = 0; + +// 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" + + +/** None of these are required as they will be handled by the library with defaults. ** + ** Remove as you see fit for your needs */ +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + deviceConnected = true; + }; + + void onDisconnect(BLEServer* pServer) { + deviceConnected = false; + } + /***************** New - Security handled here ******************** + ****** Note: these are the same return values as defaults ********/ + uint32_t onPassKeyRequest(){ + printf("Server PassKeyRequest\n"); + return 123456; + } + + bool onConfirmPIN(uint32_t pass_key){ + printf("The passkey YES/NO number: %d\n", pass_key); + return true; + } + + void onAuthenticationComplete(ble_gap_conn_desc desc){ + printf("Starting BLE work!\n"); + } + /*******************************************************************/ +}; + +class MyCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + std::string rxValue = pCharacteristic->getValue(); + + if (rxValue.length() > 0) { + printf("*********\n"); + printf("Received Value: "); + for (int i = 0; i < rxValue.length(); i++) + printf("%d", rxValue[i]); + + printf("\n*********\n"); + } + } +}; + +void connectedTask (void * parameter){ + for(;;) { + if (deviceConnected) { + pTxCharacteristic->setValue(&txValue, 1); + pTxCharacteristic->notify(); + txValue++; + } + + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + pServer->startAdvertising(); // restart advertising + printf("start advertising\n"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } + + vTaskDelay(10/portTICK_PERIOD_MS); // Delay between loops to reset watchdog timer + } + + vTaskDelete(NULL); +} + +void app_main(void) { + // Create the BLE Device + BLEDevice::init("UART Service"); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pService = pServer->createService(SERVICE_UUID); + + // Create a BLE Characteristic + pTxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_TX, + /******* Enum Type NIMBLE_PROPERTY now ******* + BLECharacteristic::PROPERTY_NOTIFY + ); + **********************************************/ + NIMBLE_PROPERTY::NOTIFY + ); + + /*************************************************** + NOTE: DO NOT create a 2902 descriptor + it will be created automatically if notifications + or indications are enabled on a characteristic. + + pCharacteristic->addDescriptor(new BLE2902()); + ****************************************************/ + + BLECharacteristic * pRxCharacteristic = pService->createCharacteristic( + CHARACTERISTIC_UUID_RX, + /******* Enum Type NIMBLE_PROPERTY now ******* + BLECharacteristic::PROPERTY_WRITE + ); + *********************************************/ + NIMBLE_PROPERTY::WRITE + ); + + pRxCharacteristic->setCallbacks(new MyCallbacks()); + + // Start the service + pService->start(); + + xTaskCreate(connectedTask, "connectedTask", 5000, NULL, 1, NULL); + + // Start advertising + pServer->getAdvertising()->start(); + printf("Waiting a client connection to notify...\n"); +}