ESP_IOT v2.5
IOT ESP Coding
BLEClientNetworking.cpp
Go to the documentation of this file.
1/**
2* \link BLEClientNetworking
3*/
4//
5// BLEClientNetworking.cpp
6// M5Stick
7//
8// Created by Scott Moody on 1/20/22.
9//
10
11#include "BLEClientNetworking.h"
12
13#include <NimBLEDevice.h>
14#include "../../Defines.h"
15
16// seems 20 bytes is the max..
17//https://www.esp32.com/viewtopic.php?t=4546
18//!@see https://h2zero.github.io/esp-nimble-cpp/nimconfig_8h_source.html
19//#define CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT 20
20
21//**** NIM BLE Client Code
22
23//!need to device way to change these ...
24
25// uses NimBLEUUID
26
27static NimBLEUUID _BLEClientServiceUUID(PT_SERVICE_UUID);
28//"B0E6A4BF-CCCC-FFFF-330C-0000000000F0"); //??
29static NimBLEUUID _BLEClientCharacteristicUUID(PT_CHARACTERISTIC_UUID);
30//"b0e6a4bf-cccc-ffff-330c-0000000000f1");
31static NimBLERemoteCharacteristic* _BLEClientCharacteristicFeed;
32
33//forward called on the end of the scan
34void scanEndedCB_BLEClient(NimBLEScanResults results);
35
36static NimBLEAdvertisedDevice* _advertisedDevice;
37//static uint32_t _scanTime = 0; /** 0 = scan forever */
38static bool _doConnect = false;
39static bool _isConnected_BLEClient = false;
40//try storing globally so we can disconnect..
41NimBLEClient *_pClient = nullptr;
42
43//! returns if the BLEClient is turned on.. note, if connected to a BLE device, then disconnect
44boolean useBLEClient()
45{
47 return val;
48}
49
50//!skip logic
53//!address to skip (for a number of times) .. how about 1 for now..
55//! an address or name to skip (for at least 1 time)
57{
58 SerialTemp.print("set skipAddress: ");
59 SerialTemp.println(nameOrAddress);
60
61 strcpy(_skipNameOrAddress_BLEClientNetworking, nameOrAddress);
63}
64//!stops the skip after found something..
66{
68}
69
70//!helper for skip, checking if an actuall address in the skip name or address
71boolean containsSkipAddress(String deviceInfo)
72{
73 boolean flag = false;
74
76 {
78 }
79 SerialTemp.printf("containsSkipAddress(%d): ", flag);
80 SerialTemp.print(deviceInfo);
81 SerialTemp.print(" skip=");
83 return flag;
84}
85
86
87/** None of these are required as they will be handled by the library with defaults. **
88 ** Remove as you see fit for your needs */
89class ClientCallbacks : public NimBLEClientCallbacks {
90 void onConnect(NimBLEClient* pClient) {
91 SerialMin.printf("TIME: %d: ",getTimeStamp_mainModule());
92 SerialMin.println(" BLE Connected");
93 /** After connection we should change the parameters if we don't need fast response times.
94 * These settings are 150ms interval, 0 latency, 450ms timout.
95 * Timeout should be a multiple of the interval, minimum is 100ms.
96 * I find a multiple of 3-5 * the interval works best for quick response/reconnect.
97 * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
98 */
99
100 //@see https://h2zero.github.io/esp-nimble-cpp/class_nim_b_l_e_client.html#aff7d389ec48567286ea732c54d320526
101 /**
102 [in] minInterval The minimum connection interval in 1.25ms units.
103 [in] maxInterval The maximum connection interval in 1.25ms units.
104 [in] latency The number of packets allowed to skip (extends max interval).
105 [in] timeout The timeout time in 10ms units before disconnecting.
106 */
107 pClient->updateConnParams(120, 120, 0, 60);
108 //tried: no different .. pClient->updateConnParams(120, 120, 5, 120);
109
110 //set the connected flag
111 _isConnected_BLEClient = true;
112 };
113
114 void onDisconnect(NimBLEClient* pClient) {
115 SerialMin.printf("TIME: %d: ",getTimeStamp_mainModule());
116 //SerialLots.printf("Unix Time: %d\n", now);
117
118 SerialMin.println(" BLE Disconnected - Starting scan");
119 SerialMin.print(pClient->getPeerAddress().toString().c_str());
120
122 {
123 NimBLEDevice::getScan()->start(PSCAN_TIME, scanEndedCB_BLEClient);
124 }
125 else
126 {
127 SerialMin.println("*** in OTA update . no scanning ***");
128 }
129 //print the time of day...
130
131 //set the connected flag = false
132 _isConnected_BLEClient = false;
133 };
134
135 /** Called when the peripheral requests a change to the connection parameters.
136 * Return true to accept and apply them or false to reject and keep
137 * the currently used parameters. Default will return true.
138 */
139 bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) {
140 SerialMin.printf("TIME: %d: ",getTimeStamp_mainModule());
141 SerialMin.println(" onConnParamsUpdateRequest");
142 if (params->itvl_min < 24) { /** 1.25ms units */
143 return false;
144 }
145 else if (params->itvl_max > 40) { /** 1.25ms units */
146 return false;
147 }
148 else if (params->latency > 2) { /** Number of intervals allowed to skip */
149 return false;
150 }
151 else if (params->supervision_timeout > 100) { /** 10ms units */
152 return false;
153 }
154
155 return true;
156 };
157
158 /********************* Security handled here **********************
159 ****** Note: these are the same return values as defaults ********/
160 uint32_t onPassKeyRequest() {
161 SerialLots.println("Client Passkey Request");
162 /** return the passkey to send to the server */
163 return 123456;
164 };
165
166 bool onConfirmPIN(uint32_t pass_key) {
167 SerialLots.print("The passkey YES/NO number: ");
168 SerialLots.println(pass_key);
169 /** Return false if passkeys don't match. */
170 return true;
171 };
172
173 /** Pairing process complete, we can check the results in ble_gap_conn_desc */
174 void onAuthenticationComplete(ble_gap_conn_desc* desc) {
175 if (!desc->sec_state.encrypted) {
176 SerialDebug.println("Encrypt connection failed - disconnecting");
177 /** Find the client with the connection handle provided in desc */
178 NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
179
180 //not connected
181 _isConnected_BLEClient = false;
182 return;
183 }
184 };
185};
186
187
188/** Define a class to handle the callbacks when advertisments are received */
189class AdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
190
191 void onResult(NimBLEAdvertisedDevice* advertisedDevice) {
192 SerialLots.printf(" *** onResult adv, useBLEClient = %d\n", useBLEClient());
194
195 {
196 //This helps if not already connected...
197 _doConnect = false;
198 return;
199 }
200 SerialLots.print("BLE Devices Advertised: ");
201 SerialLots.println(advertisedDevice->toString().c_str());
202 //The found service, see if its our service..
203 String deviceInfo = advertisedDevice->toString().c_str();
204
205
206 //!if the only-connect-to-GEN3 is turned on .. then wait until a GEN3 is found before connecting.
207 boolean foundGEN3 = false;
208 //temp:
210 if (advertisedDevice->isAdvertisingService(NimBLEUUID("DEAD"))) //this was the original code service called UUID "DEAD"
211 {
212 SerialMin.print("Device advertised: ");
213 SerialMin.println(advertisedDevice->getName().c_str());
214 SerialLots.print("Device URI: ");
215 SerialLots.println(advertisedDevice->getURI().c_str());
216
217 //SerialDebug.println(" **** Found a DEAD service .. meaning it's not a GEN3");
218 // if (strcmp(advertisedDevice->getName().c_str(), MAIN_BLE_CLIENT_SERVICE)!= 0)
219 if (getDiscoverM5PTClicker() && (strncmp(advertisedDevice->getName().c_str(), M5_BLE_CLIENT_SERVICE, strlen(M5_BLE_CLIENT_SERVICE)) == 0))
220 {
221 SerialDebug.printf("** DiscoverM5PTClicker && Found a PTCLICKER .. go for it.. %w\n",advertisedDevice->getName().c_str() );
222 }
223 else if (getDiscoverM5PTClicker())
224 {
225 //! the DiscoverM5PTClicker is set .. so only find PTClickers .. helps in crowded field
226 SerialDebug.printf("** DiscoverM5PTClicker && **** ESP32 Feeder (%s) but not 'PTClicker M5' skipping..\n", advertisedDevice->getName().c_str());
227 return;
228 }
229 else if (strncmp(advertisedDevice->getName().c_str(), MAIN_BLE_CLIENT_SERVICE, strlen(MAIN_BLE_CLIENT_SERVICE)) != 0)
230 {
231 SerialDebug.printf(" **** ESP32 Feeder (%s) but not 'PTFeeder' skipping..\n", advertisedDevice->getName().c_str());
232 //! look for "PTClicker"
233 return;
234 }
235 }
236 else if (advertisedDevice->isAdvertisingService(NimBLEUUID(_BLEClientServiceUUID)) || (deviceInfo.indexOf(PT_SERVICE_UUID) > 0))
237
238 {
239 SerialDebug.println(" **** Found a GEN3 Device");
240 foundGEN3 = true;
241 }
242 if (requireGEN3 and !foundGEN3)
243 {
244 SerialDebug.println(" **** Looking for GEN3 but didn't find it .. so not connecting");
245 _doConnect = false;
246 return;
247 }
248 //!look for name of service as well.... PTClicker vs PTFeeder
249 if (advertisedDevice->isAdvertisingService(NimBLEUUID(_BLEClientServiceUUID)) || (deviceInfo.indexOf(PT_SERVICE_UUID) > 0)) //this was the original code service called "DEAD"
250 {
251 SerialTemp.println("Found candidate BLE Service: ");
252 SerialTemp.println(advertisedDevice->toString().c_str());
253 String deviceInfo = advertisedDevice->toString().c_str();
254 //"Name: PTFeeder:PumpkinUno, Address: 08:3a:f2:51:7c:3e, serviceUUID: 0xdead"
255 // Name: PTFeeder, Address: 00:05:c6:75:55:15, txPower: 0 (GEN3 version)
256
257 //see if its one we are skipping..
258 if (containsSkipAddress(deviceInfo))
259 {
262 {
263 SerialTemp.print("Found device, but skip is max .. so connect");
264
265 //and the count of times
267 //fall through ..
268 }
269 else
270 {
271 SerialTemp.print("Found device, but will skip as it contains nameOrAddress: ");
272 SerialTemp.println(_skipNameOrAddress_BLEClientNetworking);
273
274 _doConnect = false;
275 return;
276 }
277 }
278
279 //! THIS CODE doesn't know what pairedDevice name is used (the :name or the :address)
280 //! But just looks for an occurance of that address in the deviceInfo string..
281 //!
282
283 // let the main know we discovered a device (full name, let them parse it)
284 // only if connected...
285
286 //!if not a GEN3 .. look for a requiring a paired device in the deviceIno
287 // if (!foundGEN3)
288 // The address part can also be used for the GEN3 now..
289 {
290 //! look for a paired device in the advertisment..
291 //!P = paired
292
293 //! This just returns that a paired device name or address is set..
294 //! The problem is that for a GEN3 that name is meaninless (except PTFeeder)
296#ifdef NO_MORE_PREFERENCE_BLE_USE_DISCOVERED_PAIRED_DEVICE_SETTING
297
298 //if (getPreferenceBoolean_mainModule(PREFERENCE_BLE_USE_DISCOVERED_PAIRED_DEVICE_SETTING))
299#endif
300 {
301 //!REQUIRE a match..
302 _doConnect = false;
303
304 //! Having a paired name but not a paired address .. breaks the GEN3 search .. fix it (10.23.22)
305
306 //! look for the device name to contain the paired device name (no string match)
307 //! NOTE: don't name your device PTFEEDER or it defeats somewhat (but look for a ":")
308 //! Name: PTFeeder, Address: 00:05:c6:75:55:15, txPower: 0 (GEN3 version)
309 if (containsSubstring(deviceInfo,":"))
310 {
311 boolean found = false;
313
314 //! syntax correct, look for the pairedDeviceName (will be false for GEN3 unless paired with Address)
315 if (containsSubstring(deviceInfo, pairedDeviceName))
316 found = true;
317 //!should be false for any where not matching..
318 SerialTemp.printf(" containsSubstring(%d, %s)\n", found, pairedDeviceName);
319 if (!found)
320 {
321 //! this only checks if we previously paired to the name or address, but not a new GEN3
323 if (containsSubstring(deviceInfo,pairedDeviceAddress))
324 found = true;
325
326 SerialTemp.printf(" containsSubstring(%d, '%s')\n", found, pairedDeviceAddress);
327
328 if (!found && foundGEN3 && strlen(pairedDeviceAddress)==0)
329 {
330 found = true;
331 SerialTemp.printf(" no paired address but a GEN3 so good(%d)\n", found);
332
333 }
334 }
335 if (found) {
336 if (foundGEN3)
337 SerialTemp.println("*** FOUND GEN3 PAIRED Device Name: ");
338 else
339 SerialTemp.println("*** FOUND BLE PAIRED Device Name: ");
340
341 SerialTemp.println(pairedDeviceName);
342 _doConnect = true;
343 }
344 else
345 {
346 SerialTemp.println(" *** Nothing paired ****");
347 }
348 }
349 if (!_doConnect)
350 {
351 SerialTemp.print("Not matching desired device name: '");
353 SerialTemp.print("' .. or address: ");
355
356 return;
357 }
358 }
359 }
360 //!continue on as we are good.. so stop scanning,
361 setConnectedBLEDevice_mainModule(&deviceInfo[0], foundGEN3);
362
363 //!invalidate the skip
365
366 /** stop scan before connecting */
367 NimBLEDevice::getScan()->stop();
368 /** Save the device reference in a global for the client to use*/
369 _advertisedDevice = advertisedDevice;
370 /** Ready to connect now */
371 _doConnect = true;
372 }
373 };
374};
375
376
377/** Notification / Indication receiving handler callback */
378void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) {
379#if (SERIAL_DEBUG_DEBUG)
380 std::string str = (isNotify == true) ? "Notification" : "Indication";
381 str += " from ";
382 /** NimBLEAddress and NimBLEUUID have std::string operators */
383 str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress());
384 str += ": Service = " + std::string(pRemoteCharacteristic->getRemoteService()->getUUID());
385 str += ", Characteristic = " + std::string(pRemoteCharacteristic->getUUID());
386 str += ", Value = " + std::string((char*)pData, length);
387 SerialDebug.println(str.c_str());
388#endif //SerialDebug
389}
390
391//!NOTE: this is called all the time... 4.22.22 --even if the "scan-stop" invoked.
392/** Callback to process the results of the last scan or restart it */
393void scanEndedCB_BLEClient(NimBLEScanResults results) {
394 SerialLots.println("Scan Ended");
395}
396
397/** Create a single global instance of the callback class to be used by all clients */
398static ClientCallbacks _clientCB_BLEClient;
399
400/** Handles the provisioning of clients and connects / interfaces with the server */
402{
403 SerialCall.println("connectToServer_BLEClient");
404 NimBLEClient* pClient = nullptr; //creates a new instance of a pointer to a client(1 for each server it connects to)
405 /** Check if we have a client we should reuse first **/
406 if (NimBLEDevice::getClientListSize()) { //this runs only if #clients>= 1...ie no client instance start yet
407 /** Special case when we already know this device, we send false as the
408 * second argument in connect() to prevent refreshing the service database.
409 * This saves considerable time and power.
410 */
411 pClient = NimBLEDevice::getClientByPeerAddress(_advertisedDevice->getAddress()); //if the server was connected to before there should be an address stored for it -wha
412 if (pClient) {
413 if (!pClient->connect(_advertisedDevice, false)) {
414 SerialLots.println("Reconnect failed");
415 return false;
416 }
417 SerialDebug.println("Reconnected pClient");
418 }
419 /** We don't already have a client that knows this device,
420 * we will check for a client that is disconnected that we can use.
421 */
422 else {
423
424 pClient = NimBLEDevice::getDisconnectedClient(); //reusing a client that was created but is disconnected -wha
425 if (pClient)
426 {
427 SerialLots.printf("Reusing a disconnected pClient: %s\n",pClient? pClient->getPeerAddress().toString().c_str():"NULL");
428 }
429 }
430 }
431
432 /** No client to reuse? Create a new one. */
433 if (!pClient) { //if a client instance is created from above this will not be executed -wha
434//go back to >= 1 (4.22.22)
435//#define ONLY_DIFFERENCE_FROM_WORKING_VERSION
436#ifdef ONLY_DIFFERENCE_FROM_WORKING_VERSION
437 if (NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS)
438#else
439 if (NimBLEDevice::getClientListSize() >= 1 ) //wha -original example code used the max configuration of 3 --> NIMBLE_MAX_CONNECTIONS) {
440#endif
441 {
442 //SerialDebug.println("Max clients reached - no more connections available");
443 return false;
444 }
445
446 pClient = NimBLEDevice::createClient();
447
448 SerialDebug.printf("***New client created(%d)\n", pClient);
449
450 pClient->setClientCallbacks(&_clientCB_BLEClient, false);
451
452 /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
453 * These settings are safe for 3 clients to connect reliably, can go faster if you have less
454 * connections. Timeout should be a multiple of the interval, minimum is 100ms.
455 * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
456 */
457 /**
458
459 [in] minInterval The minimum connection interval in 1.25ms units.
460 [in] maxInterval The maximum connection interval in 1.25ms units.
461 [in] latency The number of packets allowed to skip (extends max interval).
462 [in] timeout The timeout time in 10ms units before disconnecting.
463 [in] scanInterval The scan interval to use when attempting to connect in 0.625ms units.
464 [in] scanWindow The scan window to use when attempting to connect in 0.625ms units.
465*/
466
467 pClient->setConnectionParams(12, 12, 0, 51);
468 //try: no different pClient->setConnectionParams(12, 12, 10, 51);
469
470 /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
471 pClient->setConnectTimeout(5);
472 Serial.printf("pClient=%d\n",pClient); //_advertisedDevice == 0 !!!
473 if (!_advertisedDevice)
474 {
475 SerialMin.println(" **** ERROR: advertisedDevice == NULL");
476 return false;
477 }
478 Serial.printf("_advertisedDevice=%d\n",_advertisedDevice);
479
480 if (!pClient->connect(_advertisedDevice)) {
481
482 /** Created a client but failed to connect, don't need to keep it as it has no data */
483 NimBLEDevice::deleteClient(pClient);
484 SerialDebug.println("Failed to connect, deleted client");
485 return false;
486 }
487 }
488
489 //try .. doesn't seem to matter..
490 // deleteClient might delete ourself?
491 //_pClient = nullptr; (only useful in the code before 'return')
492 if (!pClient->isConnected()) {
493 if (!pClient->connect(_advertisedDevice)) {
494 SerialDebug.println("Failed to connect");
495
496 //not connected
497 _isConnected_BLEClient = false;
498 return false;
499 }
500 }
501 _pClient = pClient;
502
503 SerialLots.printf("number clients= %d \n\r", NimBLEDevice::getClientListSize()); //
504 SerialMin.printf("TIME: %d: ",getTimeStamp_mainModule());
505 SerialMin.print(" - Connected to pClient: ");
506 SerialMin.println(pClient->getPeerAddress().toString().c_str());
507 SerialLots.print("RSSI: ");
508 SerialLots.println(pClient->getRssi());
509
510 /** Now we can read/write/subscribe the charateristics of the services we are interested in */
511 NimBLERemoteService* pSvc = nullptr;
512 NimBLERemoteCharacteristic* pChr = nullptr;
513 NimBLERemoteDescriptor* pDsc = nullptr;
514#define NEW_WAY
515#ifdef NEW_WAY
516 //!Try to get our new service == DEAD (for now.. this is not in the GEN3)
517 pSvc = pClient->getService("DEAD");
518 if (pSvc)
519 {
520 //! THIS IS A new ESP32 feeder
521 SerialTemp.printf("*** v2.0 This is an ESP Based Feeder (not GEN3)***\n");
522 // unset the gatewayFlag
523 //! called to set a preference (which will be an identifier and a string, which can be converted to a number or boolean)
525 }
526 else
527 {
528 //! THIS IS A new ESP32 feeder
529 SerialTemp.println("*** v2.0 This is an GEN3 Feeder (not ESP32)***");
530 //set the gateway flag!!
531 //! called to set a preference (which will be an identifier and a string, which can be converted to a number or boolean)
533
534 }
535#endif // NEW_WAY
536
537 // the original was setup for service/characteristic : DEAD-BEEF ..but we are revising for BAAD/F00D
538 pSvc = pClient->getService(_BLEClientServiceUUID); // char* _BLEClientServiceUUID 36 characters
539 if (pSvc)
540 { /** make sure it's not null */
541 pChr = pSvc->getCharacteristic(_BLEClientCharacteristicUUID); //this was the original example code characteristic called "BEEF"
542 _BLEClientCharacteristicFeed = pChr;
543 if (!pChr)
544 {
545 SerialLots.println(" **** BLEClientNetworking is nil in creation...");
546 }
547 else
548 { /** make sure it's not null */
549 if (pChr->canRead())
550 {
551 SerialLots.print(pChr->getUUID().toString().c_str());
552 SerialLots.print(" Value: ");
553 SerialLots.println(pChr->readValue().c_str());
554 }
555
556 //NOTE: this string is also sent onto the ProcessClientCmd() which look at the first character
557 // and if it's say 'B' it turns on the buzzer. So let's make this a character that isn't used..
558#ifdef DO_IT_ANOTHER_WAY_WITHOUT_SENDING_VALUE
559 String sentValue = "_ESP_32";
560
561 if (pChr->canWrite())
562 {
563 //we are going to send a known string "sendValue = BLEClient"
564 // and if we read it back below, we are a new ESP32 feeder, otherwise GEN3
565 if (pChr->writeValue(sentValue)) {
566 //if (pChr->writeValue("Tasty")) {
567 SerialDebug.print("Wrote new value(1) to: ");
568 SerialDebug.println(pChr->getUUID().toString().c_str());
569 SerialDebug.printf("'%s'\n", sentValue);
570 }
571 else
572 {
573 SerialDebug.println("Disconnecting if write failed");
574 /** Disconnect if write failed */
575 pClient->disconnect();
576 return false;
577 }
578
579 //NOTE: this looks good on the Arduino C++ supported (not same, like no "compare" but "compareTo" -- now they tell me:-) 4.23.22
580 // read reply..
581 if (pChr->canRead())
582 {
583 SerialDebug.print("The value(1) of: ");
584 SerialDebug.print(pChr->getUUID().toString().c_str());
585 SerialDebug.print(" is now: '");
586 SerialDebug.print(pChr->readValue().c_str());
587 SerialDebug.println("'");
588 if (sentValue.compareTo(pChr->readValue().c_str()) == 0)
589 {
590 //! THIS IS A new ESP32 feeder
591 SerialTemp.printf("*** This is an ESP Based Feeder (not GEN3)***\n");
592 // unset the gatewayFlag
593 //! called to set a preference (which will be an identifier and a string, which can be converted to a number or boolean)
595 }
596 else
597 {
598 //! THIS IS A new ESP32 feeder
599 SerialTemp.println("*** This is an GEN3 Feeder (not ESP32)***");
600 //set the gateway flag!!
601 //! called to set a preference (which will be an identifier and a string, which can be converted to a number or boolean)
603
604 }
605 //NOTE:
606
607 }
608 }
609#endif //DO_IT_ANOTHER_WAY_WITHOUT_SENDING_VALUE
610 /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
611 * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
612 * Unsubscribe parameter defaults are: response=false.
613 */
614 if (pChr->canNotify())
615 {
616 //if(!pChr->registerForNotify(notifyCB)) {
617 if (!pChr->subscribe(true, notifyCB)) {
618 /** Disconnect if subscribe failed */
619 pClient->disconnect();
620
621 return false;
622 }
623 }
624 else if (pChr->canIndicate())
625 {
626
627 /** Send false as first argument to subscribe to indications instead of notifications */
628 //if(!pChr->registerForNotify(notifyCB, false)) {
629 if (!pChr->subscribe(false, notifyCB))
630 {
631 /** Disconnect if subscribe failed */
632 pClient->disconnect();
633 return false;
634 }
635 }
636 }
637
638 }
639 else
640 {
641 SerialLots.println("DEAD service not found.");
642 }
643
644 // original example pSvc = pClient->getService("BAAD"); // reference only---remove after testing
645 if (pSvc)
646 {
647 //SerialDebug.printf("pSvc = %s, pChr = %d\n", pSvc?pSvc:(char*)"NULL", pChr?pChr:(char*)"NULL");
648 /** make sure it's not null */
649 // origional example: pChr = pSvc->getCharacteristic("F00D"); // reference only---remove after testing
650 if (pChr)
651 {
652 /** make sure it's not null */
653 if (pChr->canRead())
654 {
655
656 SerialDebug.print(pChr->getUUID().toString().c_str());
657 SerialDebug.print(" Value: ");
658 SerialDebug.println(pChr->readValue().c_str());
659 }
660 //SerialDebug.println(" .. NEXT LINE HANGS..."); // then reboots
661 /*
662 Guru Meditation Error: Core 1 panic'ed (Unhandled debug exception)
663 Debug exception reason: Stack canary watchpoint triggered (loopTask)
664 Core 1 register dump:
665 */
666
667 //FOR SOME REASON, the getdescriptor hangs on the GEN3 but not on ESP32
668 //So this 'return true' makes things working..
669 return true;
670#ifdef NOT_USED_FOR_SOME_REASON
671 pDsc = pChr->getDescriptor(NimBLEUUID("C01D"));
672
673 SerialDebug.println(" .. DOESNT GET HERE");
674
675 if (pDsc)
676 { /** make sure it's not null */
677 SerialDebug.print("Descriptor: ");
678 SerialDebug.print(pDsc->getUUID().toString().c_str());
679 SerialDebug.print(" Value: ");
680 SerialDebug.println(pDsc->readValue().c_str());
681 }
682 SerialDebug.println(" .. 111");
683#else // try the following code again... 4.22.22
684 if (pChr->canWrite())
685 {
686 if (pChr->writeValue("No tip!")) {
687 SerialDebug.print("Wrote new value(2) to: ");
688 SerialDebug.println(pChr->getUUID().toString().c_str());
689 }
690 else {
691 /** Disconnect if write failed */
692 pClient->disconnect();
693 return false;
694 }
695
696 if (pChr->canRead()) {
697 SerialDebug.print("The value(2) of: ");
698 SerialDebug.print(pChr->getUUID().toString().c_str());
699 SerialDebug.print(" is now: ");
700 SerialDebug.println(pChr->readValue().c_str());
701 }
702 }
703 SerialDebug.println(" .. 222");
704
705 /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
706 * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
707 * Unsubscribe parameter defaults are: response=false.
708 */
709 if (pChr->canNotify()) {
710 //if(!pChr->registerForNotify(notifyCB)) {
711 if (!pChr->subscribe(true, notifyCB)) {
712 /** Disconnect if subscribe failed */
713 pClient->disconnect();
714 return false;
715 }
716 }
717 else if (pChr->canIndicate()) {
718 /** Send false as first argument to subscribe to indications instead of notifications */
719 //if(!pChr->registerForNotify(notifyCB, false)) {
720 if (!pChr->subscribe(false, notifyCB)) {
721 /** Disconnect if subscribe failed */
722 pClient->disconnect();
723 return false;
724 }
725 }
726 SerialDebug.println(" .. 333");
727#endif // not used
728 }
729
730 }
731 else {
732 SerialLots.println("BAAD service not found.");
733 }
734
735 SerialDebug.println("Done with this device!");
736 return true;
737}
738
739//! returns whether connected over BLE as a client to a server(like a ESP feeder)
741{
742 //not connected
743 //_isConnected_BLEClient = false;
744 // seems the _BLEClientCharacteristicFeed is not null for ever.. so not usable
745 //boolean isConnected = _BLEClientCharacteristicFeed?true:false;
746 boolean isConnected = _isConnected_BLEClient;
747
748 SerialLots.printf("isConnectedBLEClient: %d\n",isConnected);
749 return isConnected;
750}
751
752//!sends the "feed" command over bluetooth to the connected device..
754{
755 SerialLots.println("sendFeedCommandBLEClient()");
756 // if (FeedFlag == true)
757 //flag is set in button_task
758 // Set the characteristic's value to be the array of bytes that is actually a string.
759 std::string newValue = "s"; // this sets a value to the GATT which is the trigger value for the BLE server feeder
760 const uint8_t newValueFeed = { 0x00 };
761 if (!_BLEClientCharacteristicFeed)
762 {
763 SerialLots.println(" **** Error _BLEClientCharacteristicFeed is nil ***");
764 return;
765 }
766 if (_BLEClientCharacteristicFeed->writeValue(newValueFeed, 1)) { //force the length to 1. newValue.length() may return 0 if newValue=null
767 SerialMin.print("BLEClient writeValue: '");
768 SerialMin.print(newValue.c_str());
769 SerialMin.println("' - sent FEED");
770
771
772
773 // PrevTriggerTime = millis();
774 // FeedCount--;
775 // M5.Beep.beep(); //M5.Beep.tone(4000,200);
776 // delay(50);
777 // M5.Beep.mute();
778 // if (FeedCount <= 0){
779 // FeedCount = 0;//avoid display of negative values
780 // }
781 // M5.Lcd.setCursor(0, 80);
782 // M5.Lcd.printf("Treats= %d \n\r",FeedCount);
783 }
784 else {
785 SerialDebug.print(newValue.c_str());
786 SerialDebug.println("FAILED GATT write(1)");
788
789 }
790 // FeedFlag = false;
791 // delay(100);
792 /* check for the acknowledge from the server which is 0x01 */
793 if (_BLEClientCharacteristicFeed->canRead()) {
794 std::string value = _BLEClientCharacteristicFeed->readValue();
795 SerialTemp.printf("BLE Server response: '%s'\n",(value[0]==0x01)?(char*)"01":(char*)"");
796 //SerialTemp.print(value.c_str());
797 //SerialTemp.println("'");
798 if (value[0] == 0x01) { // server ack is 0x01....so flash client led for the ack from the server wha 9-28-21
799 // Led_ON();
800 // delay(150);
801 // Led_OFF();//
802 // delay(150);
803
804 //call what is registered
806
807 //call what is registered
809
810 }
811 else
812 {
813 SerialDebug.println("Didn't get a response 0x01");
814 //call what is registered
816
817 //call what is registered
819
820 }
821 }
822
823}
824
825//!send a string of 13 characters or less
826void sendCommandBLEClient_13orLess(String cmdString);
827
828//!FOR NOW THIS IS HERE.. but it should be more generic. eg: sendBluetoothCommand() ..
829//!send the feed command
830//!NOTE: if we are a gateway (gen3) then this won't work, as it's not talking back..
831void sendCommandBLEClient(String cmdString)
832{
833 SerialDebug.print("sendCommandBLEClient(");
834 SerialDebug.print(cmdString.length());
835 SerialDebug.print("): '");
836 SerialDebug.print(cmdString);
837 SerialDebug.print("' gateway=");
839 SerialDebug.println(isGatewayGEN3?"gen3":"esp32");
840
841 //!if GEN3 then this handshaking will not work.. so just send the cmd, if longer than 1 character, it hangs .. so don't send
842 if (isGatewayGEN3)
843 {
844 if (cmdString.length() > 0)
845 {
846 SerialDebug.println(" *** GEN3 and command longer than 1 character .. not sending");
847 }
848 else
850 return;
851 }
852
853 //Try to break the packets..
854 //send in packets of 13
855 int len = cmdString.length();
856 String startCmd = "#MSG_START";
858 int start = 0;
859 for (int i = 0; i < len;i+=13)
860 {
861 String s;
862 if (start + 13 > cmdString.length())
863 s = cmdString.substring(start);
864 else
865 s = cmdString.substring(start,start+13);
866 start += 13;
867 //never send a 1 character so the other side can exit on error
868 if (s.length()==1)
869 s += " ";
870 //send this fragment
872 }
873
874 String endCmd = "#MSG_END";
876}
877//!FOR NOW THIS IS HERE.. but it should be more generic. eg: sendBluetoothCommand() ..
878//!send the feed command
879void sendCommandBLEClient_13orLess(String cmdString)
880{
881 SerialDebug.print("sendCommandBLEClient13(");
882 SerialDebug.print(cmdString.length());
883 SerialDebug.print("): '");
884 SerialDebug.print(cmdString);
885 SerialDebug.println("'");
886
887
888 //! if (FeedFlag == true)
889 //!flag is set in button_task
890 //! Set the characteristic's value to be the array of bytes that is actually a string.
891 //! this sets a value to the GATT which is the trigger value for the BLE server feeder
892 if (!_BLEClientCharacteristicFeed)
893 {
894 SerialDebug.println(" **** Error _BLEClientCharacteristicFeed is nil ***");
895 return;
896 }
897 if (_BLEClientCharacteristicFeed->writeValue(cmdString, cmdString.length()),true) {
898 SerialDebug.print("writeValue:");
899 SerialDebug.println(cmdString.c_str());
900
901 }
902 else {
903 SerialDebug.print(cmdString.c_str());
904 SerialDebug.println("FAILED GATT write(2)");
906
907 }
908 //!delay
909 delay(100);
910 //! check for the acknowledge from the server which is 0x01
911 if (_BLEClientCharacteristicFeed->canRead()) {
912 std::string value = _BLEClientCharacteristicFeed->readValue();
913 SerialLots.print("BLE Server response: '");
914 SerialLots.print(value.c_str()); SerialDebug.println("'");
915 if (value[0] == 0x01) { // server ack is 0x01....so flash client led for the ack from the server wha 9-28-21
916 // Led_ON();
917 // delay(150);
918 // Led_OFF();//
919 // delay(150);
920
921 //call what is registered
923
924 //call what is registered
925 //callCallbackMain(CALLBACKS_BLE_CLIENT, BLE_CLIENT_CALLBACK_STATUS_MESSAGE,(char*)"#FED");
926
927 }
928 else
929 {
930 SerialDebug.println("Didn't get a response 0x01");
931 //call what is registered
933
934 //call what is registered
936
937 }
938 }
939}
940
941//!the setup() and loop() passing the serviceName to look for..
942void setup_BLEClientNetworking(char *serviceName, char *serviceUUID, char *characteristicUUID)
943{
944 //!address to skip (for a number of times) .. how about 1 for now..
946
947 //!start the bluetooth discovery..
948 SerialDebug.println("Starting NimBLE BLEClientNetworking");
949 /** Initialize NimBLE, no device name spcified as we are not advertising */
950 NimBLEDevice::init("");
951 // NimBLEDevice::init(serviceName);
952
953 //https://github.com/nkolban/esp32-snippets/issues/945
954 //https://www.esp32.com/viewtopic.php?t=4546
955 // NOTE working .. still only 16 chars (my 13 + 3)
956 //NOT WORKING...
957 //NimBLEDevice::setMTU(200);
958
959
960 /** Set the IO capabilities of the device, each option will trigger a different pairing method.
961 * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing
962 * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing
963 * BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
964 */
965 //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey
966 //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison
967
968 /** 2 different ways to set security - both calls achieve the same result.
969 * no bonding, no man in the middle protection, secure connections.
970 *
971 * These are the default values, only shown here for demonstration.
972 */
973 //NimBLEDevice::setSecurityAuth(false, false, true);
974 NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);
975
976 /** Optional: set the transmit power, default is 3db */
977 NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
978
979 /** Optional: set any devices you don't want to get advertisments from */
980 // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff"));
981
982 /** create new scan */
983 NimBLEScan* pScan = NimBLEDevice::getScan();
984
985 /** create a callback that gets called when advertisers are found */
986 pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks());
987
988 /** Set scan interval (how often) and window (how long) in milliseconds */
989// pScan->setInterval(45);
990// pScan->setWindow(15);
991
992 pScan->setInterval(PSCAN_INTERVAL);
993 pScan->setWindow(PSCAN_WINDOW);
994
995
996 /** Active scan will gather scan response data from advertisers
997 * but will use more energy from both devices
998 */
999 pScan->setActiveScan(true);
1000 /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever
1001 * Optional callback for when scanning stops.
1002 */
1003 pScan->start(PSCAN_TIME, scanEndedCB_BLEClient);
1004 //M5.Beep.tone(1000);
1005 // M5.Beep.setVolume(90); //???? not sure this call is working wha
1006}
1007
1008//! try to disconnect..
1010{
1011 SerialDebug.println("calling _pClient->disconnect()");
1012
1013 //disconnect../
1014 if (_pClient)
1015 _pClient->disconnect();
1016
1017 //!force saying it's disconnected
1018 _isConnected_BLEClient = false;
1019 _doConnect = true;
1020
1021 SerialDebug.println("DONE calling _pClient->disconnect()");
1022
1023}
1024
1025
1026//!the loop()
1028{
1029 //Try to disconnect ..
1030 // if (_isConnected_BLEClient && !useBLEClient())
1031 if (!useBLEClient())
1032 {
1033 _doConnect = false;
1034 _isConnected_BLEClient = false;
1035 if (_pClient)
1036 {
1037 //SerialDebug.println("calling _pClient->disconnect()");
1038
1039 //disconnect../
1040 _pClient->disconnect();
1041
1042 }
1043 else
1044 {
1045 //SerialDebug.println("_isConnectedBLEClient but no _pClient");
1046 }
1047 }
1048
1049 //_doConnect is a command to try and connect using connectToServer_BLEClient() wha 8-11-21
1050 if (_doConnect == true) {
1051 SerialLots.println("_doConnect == true");
1053 SerialDebug.println("!! Connected to BLE Server.");
1055
1056 //!a specialy display for now..
1057 //! 1 = C connected BLE
1058 //specialScreen_displayModule(1);
1059 _doConnect = false; // TRY 6.11.22 (Family Day)
1060 return; /// try 4.22.22
1061 }
1062 else {
1063#ifdef DONT_USE_ONLY_ONE_CONNECTED
1064 SerialDebug.println("We have failed to connect to the server; there is nothin more we will do.");
1066#endif
1067 }
1068 _doConnect = false;
1069 // 0=stop scanning after first device found
1070 NimBLEDevice::getScan()->start(PSCAN_TIME, scanEndedCB_BLEClient); //resume scanning for more BLE servers
1071 }
1072
1073 //!note: https://github.com/espressif/esp-idf/issues/5105
1074 //! Might address the error I see:
1075}
int _skipAddressCount
skip logic
char _skipNameOrAddress_BLEClientNetworking[50]
address to skip (for a number of times) .. how about 1 for now..
void loop_BLEClientNetworking()
the loop()
bool connectToServer_BLEClient()
void sendCommandBLEClient(String cmdString)
void stopSkip_BLEClientNetworking()
stops the skip after found something..
boolean isConnectedBLEClient()
returns whether connected over BLE as a client to a server(like a ESP feeder)
NimBLEClient * _pClient
void scanEndedCB_BLEClient(NimBLEScanResults results)
NOTE: this is called all the time... 4.22.22 –even if the "scan-stop" invoked.
void sendFeedCommandBLEClient()
sends the "feed" command over bluetooth to the connected device..
void notifyCB(NimBLERemoteCharacteristic *pRemoteCharacteristic, uint8_t *pData, size_t length, bool isNotify)
boolean useBLEClient()
returns if the BLEClient is turned on.. note, if connected to a BLE device, then disconnect
void setup_BLEClientNetworking(char *serviceName, char *serviceUUID, char *characteristicUUID)
the setup() and loop() passing the serviceName to look for..
void disconnect_BLEClientNetworking()
try to disconnect..
void skipNameOrAddress_BLEClientNetworking(char *nameOrAddress)
an address or name to skip (for at least 1 time)
boolean containsSkipAddress(String deviceInfo)
helper for skip, checking if an actuall address in the skip name or address
void sendCommandBLEClient_13orLess(String cmdString)
send a string of 13 characters or less
int _maxSkipAddressCount
#define PSCAN_INTERVAL
https://esp32.com/viewtopic.php?t=2291
#define BLE_CLIENT_CALLBACK_BLINK_LIGHT
the BLE code wants to blink the light
#define PSCAN_WINDOW
#define PSCAN_TIME
#define BLE_CLIENT_CALLBACK_STATUS_MESSAGE
used to send a string message back (which might be sent to MQTT for example)
void setConnectedBLEDevice_mainModule(char *deviceName, boolean isGEN3)
boolean stopAllProcesses_mainModule()
if stopped
Definition: MainModule.cpp:397
bool containsSubstring(String message, String substring)
check if the string contains the other string. This is a poor man's grammer checker
Definition: MainModule.cpp:684
void callCallbackMain(int callbacksModuleId, int callbackType, char *message)
performs the indirect callback based on the callbackType
Definition: MainModule.cpp:819
#define CALLBACKS_BLE_CLIENT
Definition: MainModule.cpp:710
int getTimeStamp_mainModule()
boolean isValidPairedDevice_mainModule()
returns if the paired device is not NONE. Note, the paired Name might be an address now (see below)
#define PT_CHARACTERISTIC_UUID
Definition: MainModule.h:59
#define PT_SERVICE_UUID
Definition: MainModule.h:58
boolean getDiscoverM5PTClicker()
get option
void savePreferenceBoolean_mainModule(int preferenceID, boolean flag)
save a boolean preference
boolean getPreferenceBoolean_mainModule(int preferenceID)
called to set a preference (which will be an identifier and a string, which can be converted to a num...
char * getPreferenceString_mainModule(int preferenceID)
returns the preference but in it's own string buffer. As long as you use it before calling getPrefere...
#define PREFERENCE_PAIRED_DEVICE_ADDRESS_SETTING
the paired device for guest device feeding (6.6.22) .. but the Address 9.3.22
#define PREFERENCE_ONLY_GEN3_CONNECT_SETTING
if true, only BLEClient connect to GEN3 feeders..
#define PREFERENCE_PAIRED_DEVICE_SETTING
the paired device for guest device feeding (6.6.22)
#define PREFERENCE_MAIN_GATEWAY_VALUE
#define PREFERENCE_MAIN_BLE_CLIENT_VALUE