|
| 1 | + |
| 2 | +/** NimBLE_Server Demo: |
| 3 | + * |
| 4 | + * Demonstrates many of the available features of the NimBLE client library. |
| 5 | + * |
| 6 | + * Created: on March 24 2020 |
| 7 | + * Author: H2zero |
| 8 | + * |
| 9 | +*/ |
| 10 | +#include <NimBLEDevice.h> |
| 11 | + |
| 12 | +extern "C" {void app_main(void);} |
| 13 | + |
| 14 | +void scanEndedCB(NimBLEScanResults results); |
| 15 | + |
| 16 | +static NimBLEAdvertisedDevice* advDevice; |
| 17 | + |
| 18 | +static bool doConnect = false; |
| 19 | +static uint32_t scanTime = 0; /** 0 = scan forever */ |
| 20 | + |
| 21 | + |
| 22 | +/** None of these are required as they will be handled by the library with defaults. ** |
| 23 | + ** Remove as you see fit for your needs */ |
| 24 | +class ClientCallbacks : public NimBLEClientCallbacks { |
| 25 | + void onConnect(NimBLEClient* pClient) { |
| 26 | + printf("Connected\n"); |
| 27 | + /** After connection we should change the parameters if we don't need fast response times. |
| 28 | + * These settings are 150ms interval, 0 latency, 450ms timout. |
| 29 | + * Timeout should be a multiple of the interval, minimum is 100ms. |
| 30 | + * I find a multiple of 3-5 * the interval works best for quick response/reconnect. |
| 31 | + * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 45 * 10ms = 450ms timeout |
| 32 | + */ |
| 33 | + pClient->updateConnParams(120,120,0,45); |
| 34 | + }; |
| 35 | + |
| 36 | + void onDisconnect(NimBLEClient* pClient) { |
| 37 | + printf("%s Disconnected - Starting scan\n", pClient->getPeerAddress().toString().c_str()); |
| 38 | + NimBLEDevice::getScan()->start(scanTime, scanEndedCB); |
| 39 | + }; |
| 40 | + |
| 41 | + /********************* Security handled here ********************** |
| 42 | + ****** Note: these are the same return values as defaults ********/ |
| 43 | + uint32_t onPassKeyRequest(){ |
| 44 | + printf("Client Passkey Request\n"); |
| 45 | + /** return the passkey to send to the server */ |
| 46 | + return 123456; |
| 47 | + }; |
| 48 | + |
| 49 | + bool onConfirmPIN(uint32_t pass_key){ |
| 50 | + printf("The passkey YES/NO number: %d\n", pass_key); |
| 51 | + /** Return false if passkeys don't match. */ |
| 52 | + return true; |
| 53 | + }; |
| 54 | + |
| 55 | + /** Pairing process complete, we can check the results in ble_gap_conn_desc */ |
| 56 | + void onAuthenticationComplete(ble_gap_conn_desc* desc){ |
| 57 | + if(!desc->sec_state.encrypted) { |
| 58 | + printf("Encrypt connection failed - disconnecting\n"); |
| 59 | + /** Find the client with the connection handle provided in desc */ |
| 60 | + NimBLEDevice::getClientByID(desc->conn_handle)->disconnect(); |
| 61 | + return; |
| 62 | + } |
| 63 | + }; |
| 64 | +}; |
| 65 | + |
| 66 | + |
| 67 | +/** Define a class to handle the callbacks when advertisments are received */ |
| 68 | +class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { |
| 69 | + |
| 70 | + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { |
| 71 | + printf("Advertised Device found: %s\n", advertisedDevice->toString().c_str()); |
| 72 | + if(advertisedDevice->isAdvertisingService(NimBLEUUID("DEAD"))) |
| 73 | + { |
| 74 | + printf("Found Our Service\n"); |
| 75 | + /** stop scan before connecting */ |
| 76 | + NimBLEDevice::getScan()->stop(); |
| 77 | + /** Save the device reference in a global for the client to use*/ |
| 78 | + advDevice = advertisedDevice; |
| 79 | + /** Ready to connect now */ |
| 80 | + doConnect = true; |
| 81 | + } |
| 82 | + }; |
| 83 | +}; |
| 84 | + |
| 85 | + |
| 86 | +/** Notification / Indication receiving handler callback */ |
| 87 | +void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ |
| 88 | + std::string str = (isNotify == true) ? "Notification" : "Indication"; |
| 89 | + str += " from "; |
| 90 | + str += pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress().toString(); |
| 91 | + str += ": Service = " + pRemoteCharacteristic->getRemoteService()->getUUID().toString(); |
| 92 | + str += ", Characteristic = " + pRemoteCharacteristic->getUUID().toString(); |
| 93 | + str += ", Value = " + std::string((char*)pData, length); |
| 94 | + printf("%s\n", str.c_str()); |
| 95 | +} |
| 96 | + |
| 97 | +/** Callback to process the results of the last scan or restart it */ |
| 98 | +void scanEndedCB(NimBLEScanResults results){ |
| 99 | + printf("Scan Ended\n"); |
| 100 | +} |
| 101 | + |
| 102 | + |
| 103 | +/** Create a single global instance of the callback class to be used by all clients */ |
| 104 | +static ClientCallbacks clientCB; |
| 105 | + |
| 106 | + |
| 107 | +/** Handles the provisioning of clients and connects / interfaces with the server */ |
| 108 | +bool connectToServer() { |
| 109 | + NimBLEClient* pClient = nullptr; |
| 110 | + |
| 111 | + /** Check if we have a client we should reuse first **/ |
| 112 | + if(NimBLEDevice::getClientListSize()) { |
| 113 | + /** Special case when we already know this device, we send false as the |
| 114 | + * second argument in connect() to prevent refreshing the service database. |
| 115 | + * This saves considerable time and power. |
| 116 | + */ |
| 117 | + pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress()); |
| 118 | + if(pClient){ |
| 119 | + if(!pClient->connect(advDevice, false)) { |
| 120 | + printf("Reconnect failed\n"); |
| 121 | + return false; |
| 122 | + } |
| 123 | + printf("Reconnected client\n"); |
| 124 | + } |
| 125 | + /** We don't already have a client that knows this device, |
| 126 | + * we will check for a client that is disconnected that we can use. |
| 127 | + */ |
| 128 | + else { |
| 129 | + pClient = NimBLEDevice::getDisconnectedClient(); |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + /** No client to reuse? Create a new one. */ |
| 134 | + if(!pClient) { |
| 135 | + if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { |
| 136 | + printf("Max clients reached - no more connections available\n"); |
| 137 | + return false; |
| 138 | + } |
| 139 | + |
| 140 | + pClient = NimBLEDevice::createClient(); |
| 141 | + |
| 142 | + printf("New client created\n"); |
| 143 | + |
| 144 | + pClient->setClientCallbacks(&clientCB, false); |
| 145 | + /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. |
| 146 | + * These settings are safe for 3 clients to connect reliably, can go faster if you have less |
| 147 | + * connections. Timeout should be a multiple of the interval, minimum is 100ms. |
| 148 | + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 12 * 10ms = 120ms timeout |
| 149 | + */ |
| 150 | + pClient->setConnectionParams(6,6,0,15); |
| 151 | + /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ |
| 152 | + pClient->setConnectTimeout(5); |
| 153 | + |
| 154 | + |
| 155 | + if (!pClient->connect(advDevice)) { |
| 156 | + /** Created a client but failed to connect, don't need to keep it as it has no data */ |
| 157 | + NimBLEDevice::deleteClient(pClient); |
| 158 | + printf("Failed to connect, deleted client\n"); |
| 159 | + return false; |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + if(!pClient->isConnected()) { |
| 164 | + if (!pClient->connect(advDevice)) { |
| 165 | + printf("Failed to connect\n"); |
| 166 | + return false; |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + printf("Connected to: %s RSSI: %d\n", |
| 171 | + pClient->getPeerAddress().toString().c_str(), |
| 172 | + pClient->getRssi()); |
| 173 | + |
| 174 | + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ |
| 175 | + NimBLERemoteService* pSvc = nullptr; |
| 176 | + NimBLERemoteCharacteristic* pChr = nullptr; |
| 177 | + NimBLERemoteDescriptor* pDsc = nullptr; |
| 178 | + |
| 179 | + pSvc = pClient->getService("DEAD"); |
| 180 | + if(pSvc) { /** make sure it's not null */ |
| 181 | + pChr = pSvc->getCharacteristic("BEEF"); |
| 182 | + } |
| 183 | + |
| 184 | + if(pChr) { /** make sure it's not null */ |
| 185 | + if(pChr->canRead()) { |
| 186 | + printf("%s Value: %s\n", |
| 187 | + pChr->getUUID().toString().c_str(), |
| 188 | + pChr->readValue().c_str()); |
| 189 | + } |
| 190 | + |
| 191 | + if(pChr->canWrite()) { |
| 192 | + if(pChr->writeValue("Tasty")) { |
| 193 | + printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str()); |
| 194 | + } |
| 195 | + else { |
| 196 | + /** Disconnect if write failed */ |
| 197 | + pClient->disconnect(); |
| 198 | + return false; |
| 199 | + } |
| 200 | + |
| 201 | + if(pChr->canRead()) { |
| 202 | + printf("The value of: %s is now: %s\n", |
| 203 | + pChr->getUUID().toString().c_str(), |
| 204 | + pChr->readValue().c_str()); |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). |
| 209 | + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. |
| 210 | + * Unsubscribe parameter defaults are: response=false. |
| 211 | + */ |
| 212 | + if(pChr->canNotify()) { |
| 213 | + //if(!pChr->registerForNotify(notifyCB)) { |
| 214 | + if(!pChr->subscribe(true, notifyCB)) { |
| 215 | + /** Disconnect if subscribe failed */ |
| 216 | + pClient->disconnect(); |
| 217 | + return false; |
| 218 | + } |
| 219 | + } |
| 220 | + else if(pChr->canIndicate()) { |
| 221 | + /** Send false as first argument to subscribe to indications instead of notifications */ |
| 222 | + //if(!pChr->registerForNotify(notifyCB, false)) { |
| 223 | + if(!pChr->subscribe(false, notifyCB)) { |
| 224 | + /** Disconnect if subscribe failed */ |
| 225 | + pClient->disconnect(); |
| 226 | + return false; |
| 227 | + } |
| 228 | + } |
| 229 | + } |
| 230 | + |
| 231 | + else{ |
| 232 | + printf("DEAD service not found.\n"); |
| 233 | + } |
| 234 | + |
| 235 | + pSvc = pClient->getService("BAAD"); |
| 236 | + if(pSvc) { /** make sure it's not null */ |
| 237 | + pChr = pSvc->getCharacteristic("F00D"); |
| 238 | + } |
| 239 | + |
| 240 | + if(pChr) { /** make sure it's not null */ |
| 241 | + if(pChr->canRead()) { |
| 242 | + printf("%s Value: %s\n", |
| 243 | + pChr->getUUID().toString().c_str(), |
| 244 | + pChr->readValue().c_str()); |
| 245 | + } |
| 246 | + |
| 247 | + pDsc = pChr->getDescriptor(NimBLEUUID("C01D")); |
| 248 | + if(pDsc) { /** make sure it's not null */ |
| 249 | + printf("Descriptor: %s Value: %s\n", |
| 250 | + pDsc->getUUID().toString().c_str(), |
| 251 | + pDsc->readValue().c_str()); |
| 252 | + } |
| 253 | + |
| 254 | + if(pChr->canWrite()) { |
| 255 | + if(pChr->writeValue("No tip!")) { |
| 256 | + printf("Wrote new value to: %s\n", pChr->getUUID().toString().c_str()); |
| 257 | + } |
| 258 | + else { |
| 259 | + /** Disconnect if write failed */ |
| 260 | + pClient->disconnect(); |
| 261 | + return false; |
| 262 | + } |
| 263 | + |
| 264 | + if(pChr->canRead()) { |
| 265 | + printf("The value of: %s is now: %s\n", |
| 266 | + pChr->getUUID().toString().c_str(), |
| 267 | + pChr->readValue().c_str()); |
| 268 | + } |
| 269 | + } |
| 270 | + |
| 271 | + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). |
| 272 | + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. |
| 273 | + * Unsubscribe parameter defaults are: response=false. |
| 274 | + */ |
| 275 | + if(pChr->canNotify()) { |
| 276 | + //if(!pChr->registerForNotify(notifyCB)) { |
| 277 | + if(!pChr->subscribe(true, notifyCB)) { |
| 278 | + /** Disconnect if subscribe failed */ |
| 279 | + pClient->disconnect(); |
| 280 | + return false; |
| 281 | + } |
| 282 | + } |
| 283 | + else if(pChr->canIndicate()) { |
| 284 | + /** Send false as first argument to subscribe to indications instead of notifications */ |
| 285 | + //if(!pChr->registerForNotify(notifyCB, false)) { |
| 286 | + if(!pChr->subscribe(false, notifyCB)) { |
| 287 | + /** Disconnect if subscribe failed */ |
| 288 | + pClient->disconnect(); |
| 289 | + return false; |
| 290 | + } |
| 291 | + } |
| 292 | + } |
| 293 | + |
| 294 | + else{ |
| 295 | + printf("BAAD service not found.\n"); |
| 296 | + } |
| 297 | + |
| 298 | + printf("Done with this device!\n"); |
| 299 | + return true; |
| 300 | +} |
| 301 | + |
| 302 | +void connectTask (void * parameter){ |
| 303 | + /** Loop here until we find a device we want to connect to */ |
| 304 | + for(;;) { |
| 305 | + if(doConnect) { |
| 306 | + doConnect = false; |
| 307 | + /** Found a device we want to connect to, do it now */ |
| 308 | + if(connectToServer()) { |
| 309 | + printf("Success! we should now be getting notifications, scanning for more!\n"); |
| 310 | + } else { |
| 311 | + printf("Failed to connect, starting scan\n"); |
| 312 | + } |
| 313 | + |
| 314 | + NimBLEDevice::getScan()->start(scanTime,scanEndedCB); |
| 315 | + } |
| 316 | + vTaskDelay(10/portTICK_PERIOD_MS); |
| 317 | + } |
| 318 | + |
| 319 | + vTaskDelete(NULL); |
| 320 | +} |
| 321 | + |
| 322 | +void app_main (void){ |
| 323 | + printf("Starting NimBLE Client\n"); |
| 324 | + /** Initialize NimBLE, no device name spcified as we are not advertising */ |
| 325 | + NimBLEDevice::init(""); |
| 326 | + |
| 327 | + /** Set the IO capabilities of the device, each option will trigger a different pairing method. |
| 328 | + * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing |
| 329 | + * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing |
| 330 | + * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing |
| 331 | + */ |
| 332 | + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey |
| 333 | + //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison |
| 334 | + |
| 335 | + /** 2 different ways to set security - both calls achieve the same result. |
| 336 | + * no bonding, no man in the middle protection, secure connections. |
| 337 | + * |
| 338 | + * These are the default values, only shown here for demonstration. |
| 339 | + */ |
| 340 | + //NimBLEDevice::setSecurityAuth(false, false, true); |
| 341 | + NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC); |
| 342 | + |
| 343 | + /** Optional: set the transmit power, default is -3db */ |
| 344 | + NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** 12db */ |
| 345 | + |
| 346 | + /** Optional: set any devices you don't want to get advertisments from */ |
| 347 | + // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); |
| 348 | + |
| 349 | + /** create new scan */ |
| 350 | + NimBLEScan* pScan = NimBLEDevice::getScan(); |
| 351 | + |
| 352 | + /** create a callback that gets called when advertisers are found */ |
| 353 | + pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); |
| 354 | + |
| 355 | + /** Set scan interval (how often) and window (how long) in milliseconds */ |
| 356 | + pScan->setInterval(400); |
| 357 | + pScan->setWindow(100); |
| 358 | + |
| 359 | + /** Active scan will gather scan response data from advertisers |
| 360 | + * but will use more energy from both devices |
| 361 | + */ |
| 362 | + pScan->setActiveScan(true); |
| 363 | + /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever |
| 364 | + * Optional callback for when scanning stops. |
| 365 | + */ |
| 366 | + pScan->start(scanTime, scanEndedCB); |
| 367 | + |
| 368 | + printf("Scanning for peripherals\n"); |
| 369 | + |
| 370 | + xTaskCreate(connectTask, "connectTask", 5000, NULL, 1, NULL); |
| 371 | +} |
| 372 | + |
0 commit comments