/** 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, NimBLEConnInfo& connInfo) { printf("Client address: %s\n", connInfo.getAddress().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(connInfo.getConnHandle(), 24, 48, 0, 18); }; void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) { printf("Client disconnected - start advertising\n"); NimBLEDevice::startAdvertising(); }; void onMTUChange(uint16_t MTU, NimBLEConnInfo& connInfo) { printf("MTU updated: %u for connection ID: %u\n", MTU, connInfo.getConnHandle()); pServer->updateConnParams(connInfo.getConnHandle(), 24, 48, 0, 60); }; /********************* Security handled here ********************** ****** Note: these are the same return values as defaults ********/ uint32_t onPassKeyDisplay(){ printf("Server Passkey Display\n"); /** This should return a random 6 digit number for security * or make your own static passkey as done here. */ return 123456; }; void onConfirmPIN(NimBLEConnInfo& connInfo, uint32_t pass_key){ printf("The passkey YES/NO number: %" PRIu32 "\n", pass_key); /** Inject false if passkeys don't match. */ NimBLEDevice::injectConfirmPasskey(connInfo, true); }; void onAuthenticationComplete(NimBLEConnInfo& connInfo){ /** Check that encryption was successful, if not we disconnect the client */ if(!connInfo.isEncrypted()) { NimBLEDevice::getServer()->disconnect(connInfo.getConnHandle()); 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, NimBLEConnInfo& connInfo) { printf("%s : onRead(), value: %s\n", pCharacteristic->getUUID().toString().c_str(), pCharacteristic->getValue().c_str()); } void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) { 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 value returned in code is the NimBLE host return code. */ void onStatus(NimBLECharacteristic* pCharacteristic, int code) { printf("Notification/Indication return code: %d, %s\n", code, NimBLEUtils::returnCodeToString(code)); } void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) { std::string str = "Client ID: "; str += connInfo.getConnHandle(); str += " Address: "; str += connInfo.getAddress().toString(); if(subValue == 0) { str += " Unsubscribed to "; }else if(subValue == 1) { str += " Subscribed to notfications for "; } else if(subValue == 2) { str += " Subscribed to indications for "; } else if(subValue == 3) { str += " Subscribed to notifications and indications for "; } str += std::string(pCharacteristic->getUUID()); printf("%s\n", str.c_str()); } }; /** Handler class for descriptor actions */ class DescriptorCallbacks : public NimBLEDescriptorCallbacks { void onWrite(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { std::string dscVal = pDescriptor->getValue(); printf("Descriptor witten value: %s\n", dscVal.c_str()); }; void onRead(NimBLEDescriptor* pDescriptor, NimBLEConnInfo& connInfo) { 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(); } } } 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); }