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