BLE - Getting started help

I figured it out. The missing i. I have never seen a “for” written without curly braces like that.

Okay now I’m getting this:

Found 1 services
0000037269 [app] TRACE: Service 0, UUID: FE95

That is the 16-bit UUID for Xiaomi, the maker of the thing, but I don’t know how to check for the service I’m looking for.

Another road block. This is crazy man. I’m just a maker that wants to read a BLE sensor. I can’t believe it is so insanely hard to do this.

So now I have a UUID of FE95. I totally expected this function to return a 128 bit UUID that I could match against my target. 0xFE95 is the code for the maker of the device, not the service I want to read.

I assume I need to scan this UUID to see if it has the service I want, then I need to scan the service to see if it has the characteristic I want but I don’t know how to do this. :neutral_face:

BLE advertising data is limited to 31 bytes with another (optional) 31 bytes for the scan result info.
If Xiaomi has opted to only advertise their general service you’d need to connect to the device for a complete list of services available.
But to be sure we’d need to see what your nRF Connect app reports before it learnt about the capabilities of the device - your screenshot appears to be further along the list I linked above (after step 4).

But that is not an issue around the object orientedness or the syntax, that’s the BLE communication process that’d be exactly the same with any other platform too.

I guess now you appreciate when you "simply" pair your phone with a BLE device the work that is going on invisibly to make it all happen so magically!

Reading back through this topic I would suggest you need to up skill on C++ as it is difficult to read the code and understand what is going on otherwise - also the bluetooth library is quite a task to get into as well as device manufacturers implement/support different features/services!

Encouragingly, you have made a lot of progress with help from @ScruffR .

2 Likes

I'm not sure how to use the nRF connect to interact with the device, but I did pull it up in the display. Screen shots below. I don't know if they are helpful.

you’d need to connect to the device for a complete list of services available.

Okay this is the problem and I can't stress this enough, I do not know what line of C++ code is required to do that.

I do not know where I would look to even find that. There is NO document I have found that says "to connect to a device, do this". There are splats of example code with no comments. Unless the user can wade backward through an extremely complicated library, it is very hard to know what syntax to write.

This is the workflow I've been doing for weeks, randomly clicking and searching, feeling totally lost:

  1. I know I need to "connect" so let me go see how Particle can help me do that. I went to the Reference Document page, expanded the BLE part. I used their search to look for "connect"

^^^ searching for that returns nothing. If the user doesn't have the tribal knowledge to search for something else, they are now totally lost.

^^^ but I know I need to "connect" to something, so that's the word I search for in my normal browser. Which finds all kinds of things that aren't useful.

^^^ but I know from tribal knowledge that the code "BLE.connect" is often used in the examples, so I search for this.

There is a page of text talking ABOUT BLE.connect, and there is a single example that reads an address. The page does not give me enough information to understand how to apply BLE.connect to something else.

Do I connect to the UUID 0xFE95 because that's the service the device is broadcasting? I have absolutely no idea, and even if I wanted to, I have absolutely no idea how to modify BLE.connect to connect to the address 0xFE95.

And once I finally "connect" to this 0xFE95, is it now a new peer object, and if so how do I access the new object with the C++ dot notation? Or is the peer still related to the original array of scanResults[ii]? I don't know. If I saw a complete SIMPLE example that steps through all this, I think I would totally get it.

^^^ So I searched the "Tutorials" instead, for BLE.connect examples. Unfortunately most of these "tutorials" are just blocks of code with no comments, or very few comments.

This above is the best I can find. That is a very cryptic block of code and there is literally ONE comment in the whole thing. Great tutorial.

I have no idea how to go from this cryptic block of BLE.connect code to "get a list of characteristic UUIDs from service 0xFE95"

I think I need to do something like this, but I don't know what code to write or how to begin constructing the long object oriented stuff.

  1. As I iterate through the scanResults[ii] from BLE.scan, I must some how "Connect" to 0xFE95 and I guess scan it for something.
//The code I'm after is something like this, but no idea which.
BLE.connect(0xFE95); //???

// this is straight from a tut example, but it is trying to connect to the
// device ADDRESS, I want it to connect to the 0xFE95 service instead, right?
BLE.connect(scanResults[ii].address);  // just connects to the address, not the service

Trying to consider how I may get at a UUID list....

// random guesses to get at a UUID list....
// something like this would get the UUID list?? I don't know how to scan for UUIDs.
// I searched entire "Reference" for "scan", found 199 results, none were useful.
someDataType myResults;
myResults = BLE.connect(scanResults[ii].address).peer.scanUUIDList(*cosnst &char &somevalue); // I have no idea how to write this.

myResults = peer.address().BleCharacteristicScan(&peer); //???

^^^ again, this is where I am at. I literally just need to know what lines of code to type to access the values I'm after.

I'm trying to get the long 128bit "service" UUID from the sensor so I match it to the one I want.

  1. Once I can get a 128 value into a string or value I can compare to the the 128 bit Service UUID I am searching for, then I can compare them, and if true, try to get the next step of reading the Characteristic UUID. But again, I have no idea how to actually do this. The reference doc talks ABOUT this but it doesn't show a step by step HOW with complete code and comments.

^^^ I know I need to "scan" for UUIDs, so I went through the whole frustrating mess again as above, trying to figure out how to scan for a list of UUIDs, or a human readable 4 paragraph explanation of how to do so. All the information on this page is not usable. It tells ABOUT the subject, but doesn't show HOW the subject.

^^^ I know I need to scan for a UUID service list, so I'm randomly going through all the particle docs reading everything with UUID on it now trying to randomly guess and find a usable line of code I can modify or fit into the bigger picture.

I have now typed thousands of words trying to ask these questions. I have spent over 8 hours just composing forum posts and DM's asking about this over weeks. I've been posting on this forum for over a month, asking what I think is the same question over and over.

I just don't know what else to do except abandon Particle. This is for an actual production product that would probably sell a couple hundred Boron units a year. I am just the PCB guy on this project. I can normally handle the low level code on projects.

I thought I was the person Particle was designed for, but that's just not the case. I have over 30 products on the market, have designed hundreds of boards over 10 years, and written code for most of them but I'm defeated on this. I can understand just about anything given some explanation, and I just find that 99% lacking with Particle on the BLE stuff anyway.

My customer just wants to measure moisture as an afterthought and this is going to trigger a redesign now. Customer is about to scrap Particle and go to another engineer for the code side, and that engineer is going to scrap Particle and go to ESP32.

I also have another large project I'm about to start hardware for, that will use 50 or so Xenon units per site, but that too needs basic Bluetooth on some nodes. That project is now being re-designed away from Particle/Xenon for this very reason. They are going to XBee for mesh and prob some other BLE module that some other developer will handle and I'll just solder it down.

Huge opportunities lost here because Particle can't put out a 3 page Sparkfun style how-to guide, or add a few comments to their examples. Just really really disappointed. I wanted to love Particle so much. I was so excited to start this project early on.

.
Here are the nRF screens from above....


Just one basic point on the BLE objects in the Particle world:
In BLE.xxxx the BLE represents a reference to the local bluetooth device and any method invoked on that object will cause the bluetooth device to do something.

Hence BLE.connect(address) will connect the local device to another device with that stated address (MAC if you will), so passing a UUID will not work as this is not an address.
But as you found in the example when provided with an address this function will give you back the reference to a communication channel (an object) to the other device you just connected to (in the example called peer and hence I also used the term peer in my posts further up).
With that reference you can now invoke methods on the peer object which will in turn perform the actual communication between the local device and the connected device.

Why would it - you need the address to connect to any device and cannot use something else and won't need to. Every device will have an address and a BLE.scan() will give you that address (as part of the scan response - along with advertised services and other info) as shown in the UART Central example I referred you multiple times.
Or you could just directly feed this address from your nRF Connect app into the BLE.connect() call
image

The UART Central example is exactly that.

BTW, this part of your screenshot
image
seems to line up quite well with what I said in one of my earlier posts

I tried to help but I guess it wasn't good enough :man_shrugging:

^^^ That right there is EXTREMELY useful. Someone new to the platform would have no conceivable way of knowing this unless they can reverse engineer a very complicated library. Particle needs to write a guide with the 10 or so paragraphs that naturally follow what you just wrote.

This stuff is obvious to anyone who is a C++ Jedi. Just like I know solder paste formulas and I can spot a QFN IC package from across a room. But without EXPLICITLY stating some of this, someone like me has no idea. I'm just feeling around blind.

For a guy who just purchased a Particle device and wants to read a light sensor, Particle is a barrier of entry to BLE, and it doesn't have to be.

Thank you this is helpful, but how do I do that. You're using words to describe what to do, but I do not understand the string of code to do what you are saying. I'm not trying to be difficult.

When I find a device I can connect to, and I use the BLE.connect to get a peer device, what code do I write to get a list of the 128 bit service UUIDs offered by the device into a variable, or any array, or anything else I can work with?

I don't know what to do next after the line of code BLE.connect. Again, I'm totally lost. I know I need to read a list of UUIDs then try to getValue of one, but I don't know how to do that. This process will be all of 4 lines of code when it is done, thus why I am so frustrated. I could have written this 10,000 times by now if I just understood the next step and the exact syntax needed to do the next step.

availableServiceUUID = peer.getUUID();  // something like this???

There is nothing that says "once you connect to a device and you have a peer, use the following 3 lines of code to iterate through the service UUIDs available", which IMO is what is required here for anyone that's not a master of C++.

"along with advertised services and other info". That sounds awesome. Is there a step by step guide anywhere on the internet that will show me how to print these things to a serial port? Because I have no idea how I would write code to access these values, what format they will be in, are they an array, are they part of the BLE object, are they part of peer object, are they part of scanResults[ii]. I have no idea because nothing tells us.

What 3 lines of code must be pasted into an IDE so that these services will print to a screen, populate into a variable, or be delivered by carrier pigeon so I can then use them in a simple "IF" statement, to do something useful with them?

That is what I'm asking. I'm looking for a few lines of actual code.

This is where a simple guide spelling out this process would be more valuable than you can imagine. It would make this whole process human understandable and accessible to the average high school kid learning Arduino.


^^^ I'm sorry I have to respectfully disagree. This example does not qualify as a tutorial in my mind. There are two whole sentences introducing it, it has very few comments, and it's almost impossible to read.

It appears to show how a scan result works.

size_t svcCount = scanResults[ii].advertisingData.serviceUUID(&foundServiceUuid, 1);

^^^ I spent over a half a day digging on this one line of code last week to try to understand what it did and why it was being used. I think it tells me if a device advertises services, but I don't know. That is a total guess because it doesn't tell me what it does.

And what if more than one service is offered by the device? I have no idea what happens then.

// Our serial peripheral only supports one service, so we only look for one here.
// In some cases, you may want to get all of the service UUIDs and scan the list
// looking to see if the serviceUuid is anywhere in the list.

^^^ The only vaguely useful comment says this. Would it kill them to show us HOW to "scan the list looking to see if the serviceUuid is anywhere on the list"??? I would love to do that. It would probably solve a million problems right now if I knew what to type into an IDE to make that list spit out in a serial monitor window.

I don't know how to logically connect the 0xFE95 with anything you have previously written. 0xFE95 is a manufacturer code for Xiaomi, that is all I know.

I don't know if this is a UUID. I don't know if I can get characteristics from this UUID. I don't know if this UUID is an umbrella for some other step that must be taken to connect. I don't know if the UUID I want is a sub-characteristc of the 0xFE95. I don't know if 0xFE95 is part of a connection, or part of a peer, or part of a scanResults[ii], or part of a peer.scanResults[ii] or part of a "channel" associated with any of the above. I don't know if this function will need any modifiers along the way, various cryptic (cast) statements, .toString(), modifiers to the dot notation of all the object oriented parts being used.

I have absolutely no idea how that 0xFE95 relates to anything else except I need one magical line of code that somehow points the BLE peripheral of Boron at that 0xFE95, and somehow a list of services magically appears in a variable I can pass to an "if" statement.

I need some line of code that says "hey connect to the device with 0xFE95", then I need some way to see if that characteristicUUID I keep posting is advertised by the device. That is all. Connect, query. I do not know how to do those things. The example is not helpful.

"you’d need to connect to the device for a complete list of services available".

Agreed. And if I knew what 1 or 2 lines of code to paste into VSCode to make that happen, I would be doing exactly that right now.

You are being helpful I just feel I'm not getting specific answers to my questions. I am asking you to show me "how" to do something but I feel you keep telling me "about" it instead.

I learn by examples and doing, but there aren't enough fragments of examples available for me to put all the pieces on the table and try to make sense of them. I've been trying for weeks. It's like I have a pile of car parts on a desk and can't figure out how they fit together without some instructions.

I showed above how I am trying to search this stuff out, but it is just fragments of knowledge in every direction with nothing to really pull them all together with a simple example that connects, queries, sets, disconnects, with comments and a few pages of explanation. That would allow ANYONE to use Particle to read BLE devices. The example allows a person to use Particle with UART, but if a high school kid wants to read a BLE light sensor, they are completely out of luck.

Arduino wouldn't have any customers if it just threw people a link to the SPI registers reference of the Atmel data sheet and a print of assembly code. I would be able to understand that fine, but nobody else without years of education would have a chance.

I think we both need to give up and admit defeat on this. We're both just wasting time crashing into a wall. Maybe you can forward this to the people at Particle. Huge opportunities lost. And I REALLY wanted to love Particle. I'm so bummed right now you have no idea. I spent a few months developing Boron into this project and I've been planning a large Xenon system on paper. I will now probably be spending the next week designing Particle out of our designs over literally 3 to 5 lines of code.

The definition of the peer device object can be found here which is part of the header file in the open source repo I already provided and here are the docs dealing with that object and its methods (although I don't really consult the docs but rather the code for complete and native info).
Unfortunately the method you are looking for is not documented (yet) but can be found in the class definition and since the method names are intentionally kept speaking it shouldn't be surprising that the method is called getServiceByUuid().
Additionally the class also features a method discoverAllServices() to retrieve a vector which holds a "list" of the services provided by the device to iterate over and search for what you want "manually".

But, since you ultimately are not really after the service but rather the characteristics of that service you could also use "shorthand" getCharactersiticByUUID() which is documented and also used in the UART Central example that way.

Yup, but only characteristics feature that method - services don't.
Hence the "shorthand" mentioned above.

Sure, it is complicated, but that's owed to the complexity of BLE and its demand to be versatile and flexible.
However, BLE.scan() will give you "list" of BleScanResults which in turn contain one or two BleAdvertisingData entries which can hold different kinds of info (speaking of BLE versatility/complexity) but do provide methods to get the data you are looking for (when available).

Having said all that, while it may at the end boil down to only a few lines of code but due to the versatility/complexity it's not that simple to provide you with a code that does what you want. As I said, with your sensor at hand I would have wipped that up in no time but since I don't have such a device I also have spent lots of time trying to explain how the general idea of BLE works and how to find the way through the jungle and I don't even get anything off it once your project were to be finished.

It may be a simple guide for one purpose but a creating a simple guide for litterally thousands of thinkable combinations of circumstances is not a simple task anymore.

But I mentioned this in my (admittedly brief) breakdown of the process

To which you (at first) asked but then deleted the question


Hence I figured (obviously wrongly) that you may have read the comment in my code and realised the answer was there already - at least in parts.

Following that sentence I quoted myself saying that I suspect Xiaomi to only advertise one "service" (which seems to be their brand UUID) indicating that there are more (hence "Incomplete List ...") to be requested when you are looking for one of their devices (which you obviously do). To do that you need to connect to that device and investigate - as I said in that quote too.

Your screenshot I took that from tells you so: "Incomplete List of 16-bit Service UUIDs: 0xFE95"
That incomplete list contains exactly one entry 0xFE95.

Nope, that's not one line but an entire process governed by the BLE standard which I roughly broke down earlier

That process would be just the same even on an ESP32.

As I said: That's owed to the complexity/versatility of BLE and the lack of access to that very sensor you want a solution for.
In maths a list of all results for any given possible combination of numbers and operators is neither helpful nor possible but explaining the underlying concept to be used on any of these is.

The risk with that is to mistake copy/pasting with minor tweaking for actual understanding which is the ultimate goal of learning.

Comparing SPI with BLE is like comparing a steam powered loom with a CNC machine.
Have you seen an Arduino or ESP32 3-5 line solution for this problem that doesn't require some understanding the fundamental BLE concepts?


Just to illustrate that this is not a 3-5 line endeavour I cobbled together this sample implementation

#define CENTRAL             // comment out to create a mockup sensor with ModeChange and LiveSensorData characteristics
#define CORRECT_BEHAVIOUR   // comment out if not working correctly

#include "Particle.h"

SYSTEM_MODE(MANUAL)
SYSTEM_THREAD(ENABLED)

SerialLogHandler log(LOG_LEVEL_ALL);

const uint16_t vendor = 0xFE95;
const BleUuid serviceUuidRoot("0000FE95-0000-1000-8000-00805F9B34FB");

const BleUuid serviceUuidGeneric("00001800-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_r0x0003("00002800-0000-1000-8000-00805F9B34FB");

const BleUuid serviceUuidData("00001204-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_w0x0033("00001A00-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_r0x0035("00001A01-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_r0x0038("00001A02-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_r0x003c("00001A11-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_w0x003e("00001A10-0000-1000-8000-00805F9B34FB");
const BleUuid charUuid_r0x0041("00001A12-0000-1000-8000-00805F9B34FB");

#if defined(CENTRAL)
BleAddress        peerAddress;
BlePeerDevice     peerDevice;
BleCharacteristic peerModeChangeCharacteristic;
BleCharacteristic peerRealTimeSensorCharacteristic;

bool rescan = true;
bool readyToConnect = false;

void setup()
{
  Serial.begin();
  System.on(button_click, clickHandler);

  pinMode(D7, OUTPUT);
}

void loop() {
  static uint32_t ms = 0;

  if (readyToConnect) {
    if (attachPeer(peerAddress, peerDevice))
      readyToConnect = false;                                               // already connected 
    else {
      Log.warn("retry to connect in 5 seconds");
      delay(5000);
    }
  }

  if (millis() - ms < 1000)
    return;
  ms = millis();

  if (rescan)
  {
    rescan = false;
    int count;
    Log.info("rescan");
    if ((count = BLE.scan(scanResultCallback, NULL)))
      Log.info("%d devices found", count);
  }

  Serial.print('.');
  digitalWrite(D7, !digitalRead(D7));
}

void scanResultCallback(const BleScanResult *scanResult, void *context) {
  if (BLE.connected())
    BLE.disconnectAll();

  int countServ = 5;                                                          // how many do we expect
  BleUuid services[countServ];                                                // reserve that many service UUIDs
  countServ = scanResult->advertisingData.serviceUUID(services, countServ);   // request up to countUuid services from advertisingData
  Log.info("Found %d UUIDs in advertisingData", countServ);
  for (int s = 0; s < countServ; s++)
  { // iterate over found services
    Log.info("%d. UUID: %s", s + 1, (const char *)services[s].toString());
    if (services[s] == serviceUuidRoot || services[s].shorted() == vendor)
    {                                                                         // check against expected values
      Log.trace("%s ?= %s || %04x ?= %04x", (const char*)services[s].toString(), (const char*)serviceUuidRoot.toString(), services[s].shorted(), vendor);
      peerAddress = scanResult->address;
      readyToConnect = true;
      BLE.stopScanning();
      break;
    }
  }
}

bool attachPeer(BleAddress addr, BlePeerDevice& peer) {
  if (peer.connected())
    peer.disconnect();

  peer = BLE.connect(addr); // connect to the peer device
  if (!peer.connected()) {
    Log.warn("Couldn't connect to device");
    return false;
  }

  Log.info("found device and connected to it");

#if defined(CORRECT_BEHAVIOUR)
  // due to some potential quirk with the test device implementation this does not work for me 
  if (peer.getCharacteristicByUUID(peerModeChangeCharacteristic, charUuid_r0x0035))
  {
    Log.info("Found LiveSensorDataCharacteristic - hooking up NOTIFY callback");
    peerRealTimeSensorCharacteristic.onDataReceived(onDataReceived, NULL);
  }
  else
    Log.warn("LiveSensorDataCharacteristic (%s) not found", (const char *)charUuid_r0x0035.toString());

  if (peer.getCharacteristicByUUID(peerModeChangeCharacteristic, charUuid_w0x0033)) {
    uint8_t val[2];
    peerModeChangeCharacteristic.getValue((uint8_t *)&val, sizeof(val));  // request the current value (into a buffer of fitting size)
    Log.info("Found ModeChangeCharacteristic, current value %04x", *(uint16_t *)&val);
    val[0] = 0xa0;                                                        // prepare endiannes-agnostic buffer for new value
    val[1] = 0x1f;
    peerModeChangeCharacteristic.setValue(val, sizeof(val));              // set new value
    peerModeChangeCharacteristic.getValue((uint8_t *)&val, sizeof(val));  // read back set value
    Log.info("new value %04x", *(uint16_t *)&val);
  }
  else
    Log.warn("ModeChangeCharacteristic (%s) not found", (const char *)charUuid_w0x0033.toString());
#else
  const int countChar = 10;
  BleCharacteristic chars[countChar];
  int foundChar = peer.discoverAllCharacteristics(chars, countChar);        // discover its exposed characteristics

  Log.info("Found %d of %d characteristics", foundChar, countChar);

  for (int c = 0; c < foundChar; c++)
  {
    Log.info("%d. Characteristic: %s", c + 1, (const char *)chars[c].UUID().toString());

    Log.info("%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x", chars[c].UUID().rawBytes()[0], chars[c].UUID().rawBytes()[1], chars[c].UUID().rawBytes()[2], chars[c].UUID().rawBytes()[3], chars[c].UUID().rawBytes()[4], chars[c].UUID().rawBytes()[5], chars[c].UUID().rawBytes()[6], chars[c].UUID().rawBytes()[7], chars[c].UUID().rawBytes()[8], chars[c].UUID().rawBytes()[9], chars[c].UUID().rawBytes()[10], chars[c].UUID().rawBytes()[11], chars[c].UUID().rawBytes()[12], chars[c].UUID().rawBytes()[13], chars[c].UUID().rawBytes()[14], chars[c].UUID().rawBytes()[15]);
    Log.trace("LifeSensorDataCharacteristic: %s ?= %s || %04x ?= %04x", (const char *)chars[c].UUID().toString(), (const char *)charUuid_r0x0035.toString(), chars[c].UUID().shorted(), charUuid_r0x0035.shorted());

    if ((const BleUuid)chars[c].UUID() == charUuid_w0x0033 
    || chars[c].UUID().shorted() == charUuid_w0x0033.shorted() 
    || chars[c].UUID().shorted() == 0x1A00)                                 // hardcoded for some quirky behaviour of .shorted()
    {                                                                       // check against expected value
      uint8_t val[2];
      peerModeChangeCharacteristic = chars[c];                              // store characteristics reference globally
      peerModeChangeCharacteristic.getValue((uint8_t *)&val, sizeof(val));  // request the current value (into a buffer of fitting size)
      Log.info("Found ModeChangeCharacteristic, current value %04x", *(uint16_t *)&val);
      val[0] = 0xa0;                                                        // prepare endiannes-agnostic buffer for new value
      val[1] = 0x1f;
      peerModeChangeCharacteristic.setValue(val, sizeof(val));              // set new value
      peerModeChangeCharacteristic.getValue((uint8_t *)&val, sizeof(val));  // read back set value
      Log.info("new value %04x", *(uint16_t *)&val);
    }
    else if ((const BleUuid)chars[c].UUID() == charUuid_r0x0035
    || chars[c].UUID().shorted() == charUuid_r0x0035.shorted()
    || chars[c].UUID().shorted() == 0x1A01)                                 // hardcoded for some quirky behaviour of .shorted()
    { // check aganist other expected value
      Log.info("Found LiveSensorDataCharacteristic - hooking up NOTIFY callback");
      peerRealTimeSensorCharacteristic = chars[c];                          // when found store reference globally and hook-up callback
      peerRealTimeSensorCharacteristic.onDataReceived(onDataReceived, NULL);
    }
  }
#endif

  return true;
}

void onDataReceived(const uint8_t *data, size_t len, const BlePeerDevice &peer, void *context) {
  for (size_t ii = 0; ii < len; ii++) {
    Serial.write(data[ii]);
  }
}

void clickHandler(system_event_t event, int param)
{
  rescan = true;
}

#else

// test device
BleCharacteristic charModeChange("mode", BleCharacteristicProperty::READ | BleCharacteristicProperty::WRITE_WO_RSP, charUuid_w0x0033, serviceUuidData, onDataReceived, NULL);
BleCharacteristic charLiveSensorData("temp", BleCharacteristicProperty::NOTIFY, charUuid_r0x0035, serviceUuidData);
const uint32_t UPDATE_INTERVAL_MS = 2000;
uint32_t lastUpdate = 0;
uint16_t mode = 0xCAFE;

void setup()
{
  BLE.addCharacteristic(charModeChange);
  BLE.addCharacteristic(charLiveSensorData);

  BleAdvertisingData advData;
  BleAdvertisingData srData;
  advData.appendLocalName("TD");
  advData.appendServiceUUID(BleUuid(serviceUuidData.rawBytes(), serviceUuidData.shorted()));

  advData.appendServiceUUID(vendor);
  srData.appendServiceUUID(serviceUuidData);
  BLE.setScanResponseData(&srData);
  BLE.advertise(&advData);

  charModeChange.setValue((uint8_t *)&mode, sizeof(mode));
}

void loop()
{
  if (millis() - lastUpdate >= UPDATE_INTERVAL_MS)
  {
    lastUpdate = millis();
    if (BLE.connected())
    {
      uint8_t buf[6];
      snprintf((char*)buf, sizeof(buf), "%5lu", millis());
      charLiveSensorData.setValue(buf, sizeof(buf));
      Log.info((char*)buf);
    }
  }
}

void onDataReceived(const uint8_t *data, size_t len, const BlePeerDevice &peer, void *context) {
  Log.info("Received %d bytes from %s (context: %08x) ", len, (const char *)peer.address().toString(), context);
  for (size_t i=0; i < len; i++)
    Log.printf("%02x", data[i]);
  Log.print("\r\n");

  mode = *(uint16_t *)data;
}
#endif

This central device works as far as I can tell.
For testing I have also attached a peripheral implementation which exposes the ChangeMode and LiveSensorData characteristics for the central to attach to.
While putting this together I came across some aparent quirks in the Particle BLE framework for which I had to come up with some workarounds.
The current version expects the sensor to behave better than the test peripheral. If it doesn't work with the default implementation comment out the #define CORRECT_BEHAVIOUR line to enable the workaround.

I think I found a bug in the implementation of the BLE framework and filed an issue about it here
https://github.com/particle-iot/device-os/issues/1993

Really sorry to hear that you are struggling with this - I am going to repeat what I mentioned above. That to understand and use BLE (whether on a Particle or Arduino device) you need to understand the architecture of Bluetooth and the modes of operation AND you need to have a pretty good grasp of C++ to understand what is going on since there is a (IMHO) good deal of advanced C++ used. I am not an expert in C++ nor in Bluetooth - I read all documents (Adafruit guide is good) and worked through from the simple examples provided in the Particle documentation - which because I reviewed the original and Particle listened to my feedback is much better than I have seen elsewhere.

I put the application below together based upon the basic scan central example. This is tested. It is not as sophisticated as the example that @ScruffR has provided above and does not get into trying to expose Services and Characteristics. Try to build this and see when it is working whether you can understand what is going on - you can then try changing or adding what you want to achieve (i.e. add scanning of Service and Characteristic) and see if that gets you towards your end goal.

#include "Particle.h"
// This example does not require the cloud so you can run it in manual mode or
// normal cloud-connected mode
SYSTEM_MODE(MANUAL);
// define variables and constants
const size_t SCAN_RESULT_MAX = 30;
const size_t STORED_DEVICE_MAX = 32;
const int MANUFACTURERS = 6;
int ii;
int count;
//Define the specific data objects require to hold the scan results and BLE address
BleScanResult scanResults[SCAN_RESULT_MAX];
BleAddress storedDeviceMAC[STORED_DEVICE_MAX];
//define a custom data type 'struct' to hold the Bluetooth Manufacturer ID code and name pair
struct manufID{
    uint16_t code;
    char     name[21];
};
//create an array of device manufacturers
manufID manufacturers[MANUFACTURERS] = {{0x0006,"Microsoft"},{0x0008,"Motorola"},{0x004C,"Apple"},{0x0075,"Samsung"},{0x041E,"Dell"}, {0xFE95,"Xiaomi"}};
//
//SerialLogHandler logHandler(LOG_LEVEL_TRACE);

//Forward declaration of the scanResultcallback function
void scanResultCallback(const BleScanResult *scanResult, void *context);
//
void setup()
{
    selectExternalMeshAntenna();                //found that using an external antenna improves device BLE sensitivity
    Serial.begin();
    while (!Serial.available()) delay(10);      //waits until entry made in serial terminal 
}
//
void loop()
{
    Serial.println("starting scan...");
    ii = 0;
    count = 0;
    (void) BLE.scan(scanResultCallback, NULL);
    if (count > 0)  {Serial.printf("\n %i device(s) found", count);}
    else            {Serial.print("\n No devices found");}
    Serial.print("\n...finished scan\n\n");
    delay(5000);                                //repeat every 5 seconds
}
//applies to BT as well as mesh
void selectExternalMeshAntenna()
{
    BLE.selectAntenna((particle::BleAntennaType)BLE_ANT_EXTERNAL);
}
//check if scanned device MAC is in stored device list return true if found false if not - uses inefficient scan to search
bool isDeviceListed(BleAddress addr)
{
    bool result = false;
    for (int i = 0; i < STORED_DEVICE_MAX; i++)
    {
        if (addr == storedDeviceMAC[i]) {result = true; break;}
    }
    return result;
}
//add discovered BLE device MAC to stored list return true if added and false if not (already there)
bool isDeviceMACput(BleAddress MACaddr)
{
    bool result = false;
    int i = 0;
    while(i < STORED_DEVICE_MAX && !result)
    {
        if (storedDeviceMAC[i][0] == 0 && storedDeviceMAC[i][1] == 0 && storedDeviceMAC[i][2] == 0 && storedDeviceMAC[i][3] == 0) {storedDeviceMAC[i] = MACaddr; result = true;}    //if empty MAC 0:0:0:0 then store here
        else if (storedDeviceMAC[i] == MACaddr) break;                                                                                                                              //else if already stored break out
        else i++;                                                                                                                                                                   //else increment array index
    }
    return result;
}
//helper to find index to manufacturer name in manufactures struct array - return -1 if not found
int searchManufacturerName(uint16_t id)
{
    int result = -1;
    if (id != 0)
    {
        int i = 0;
        while(i < MANUFACTURERS)
        {
            if (id == manufacturers[i].code) {result = i; break;}
            i++;
        }
    }
    return result;
}
//
void scanResultCallback(const BleScanResult *scanResult, void *context)
{
    uint8_t buf[BLE_MAX_ADV_DATA_LEN] = {0};    //initialise the BLE advertising data buffer to 0
    size_t len;
    count++;
    Serial.printf("\nScan Result %i MAC: %02X:%02X:%02X:%02X:%02X:%02X | RSSI: %dBm", count, 
            scanResult->address[0], scanResult->address[1], scanResult->address[2],
            scanResult->address[3], scanResult->address[4], scanResult->address[5], scanResult->rssi); //uses arrow notation to access members
    String name = scanResult->advertisingData.deviceName();
    if (name.length() > 0) {Serial.printf(" | deviceName: %s", name.c_str());}
    else {Serial.print(" | No Device Name");}
    BleAddress scannedDeviceMAC; 
    scannedDeviceMAC = scanResult->address;

    if (isDeviceListed(scannedDeviceMAC))   Serial.print(" | Device in list already");
    else                                   {Serial.print(" | Device not in list - add"); (void) isDeviceMACput(scannedDeviceMAC);}
    //get manufacturer data
    Serial.print(" | Manufacturer Specific Data:");
    len = scanResult->advertisingData.get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
    if (len == 0) {
        len = scanResult->scanResponse.get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
    }
    uint16_t manufCode;
    manufCode = (uint16_t) 256 * buf[1] + buf[0];
    int index = searchManufacturerName(manufCode);
    if (index >= 0) {Serial.printlnf(" LEN: %d MANUF ID: %04X  %s %02X:%02X  %02X:%02X %02X:%02X %02X ", len, manufCode, index>=0?manufacturers[index].name:"Not Recognised", buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]);}
    else            {Serial.println(" No Data Available");}
}

I have now updated my code above but it should only illustrate how to scan and connect to a desired device and use the exposed chracteristics. It won't deal with multiple sensors of the same type nor auto-reconnect when the connection gets dropped.

IIRC this will only initialise the first element. memset(buf, 0, sizeof(buf)) will reset all elements.

There is also a toString() option to shorten this

Serial.printf("\nScan Result %i MAC: %s | RSSI: %dBm", count, (const char*)scanResult->address.toString(), scanResult->rssi); 

There is also a shorter (but somewhat hacky) way to do above check :wink:

if (*(uint32_t*)storedDeviceMAC[i].halAddress().addr == 0)

This will treat the first four bytes of the raw address buffer as one 32bit number to compare against.
Alternatively a less hacky version

  uint32_t zero = 0; // could reuse the already present `bool result = false` too
  if (memcmp(storedDeviceMAC[i].halAddress().addr, &zero, sizeof(zero)) == 0)

Thanks for the improvements. Generally try to not use string object. Agree that the mac address check is clunky - clever use of pointers and dereferencing. The array initialisation works because you can partially initialise an array and when you supply just the first value the compiler automatically fills the rest with 0. Clearly, it wouldn’t work if the initialisation was some other value than 0 and in which case I would use memset().

This wasn’t the point of my post which was to help @kkeng to get something working that he can understand how it is working - seamed to me that C++ complexities weren’t helping! He is also highlighting that there is a high barrier of entry to using BLE.

1 Like

I'm so used to "dumb" compilers that I just got into the habit and totally missed the memo :blush:

There is also a toString() overload that populates a char array

@kkeng, have you seen my code?

@ScruffR

Hello and thanks for all your contributions I can read on this forum, always very clear.

I’ve seen the code you posted in this thread and I have one question :
Like you when I tried to get the Caracteristic of my device directly by UUID (the 128-bit version) it never worked and when I use the 16-bit UUID or when I did a discoverAllCharacteristics I can find it and use it …

My question is : do you know if the “bug” you reported on this subject has been fixed and if yes in what version of the firmware ?

Once again, thanks for the help you offer to all the user of particle’s devices.

Sébastien

I haven’t (re)tested this specifically but I heard it should be fixed (at least the github issue has been closed) in February and should be part of 1.5.0+

@kkeng Kevin what did you end up doing?

Your frustration / confusion / and desire for simplicity when it comes to trying to take advantage of the BLE features is something I can relate to.

I gave up on it. I was never able to put the fragments from the docs together in a way that worked.

@kkeng I think something like this guide is what you were looking for. It is helping me understand what’s going on better.

He has a few other good BLE toutorials on his blog and the forum also.

Thank you. Much appreciated. :+1: