/** 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(); }; void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) { printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); }; /********************* 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 = pDescriptor->getValue(); 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); }