Simple BLE Example (Scan Advertising Devices, List Details, Connect to One Sensor Device)


#1

Community,

Does anyone have a simple BLE peripheral example which might do the following -

  • Scan and display all advertising devices (sensors, watch, etc.) - could be a loop here
  • Allow one to connect to a device, knowing it’s MAC address from above
  • Allow one to understand all available Characteristics of the device one has connected to (listed out by UUid)
  • Ultimately connect to a specific UUid to get a sensor reading over BLE.
  • Upload this to the cloud / Thingspeak, etc.

The examples provided on Particle here BLE Examples are quite specific (connecting to a heart rate sensor, device nearby and changing LEDs, advertising BLE data, website connectivity etc.).

I’d hope the example I’m looking for is simple enough but seems we might still be too fresh into the BLE side of Particle to have these?

As a matter of interest I’m trying to get my Argon to read environmental data from a Thunderboard Sense 2. I’ve done this with a RPi before (pythong BLE seemed easier to understand or at least had multiple examples).

Any help or direction would be appreciated.

Neal.


#2

Not exactly what you are looking for, but this test code, I wrote to track some potential bug, may provide some more generic starting point

The portion wrapped between #if defined(CENTRAL) and #else shows how a BLE central device would scan all devices and connects to a specific one while the part between #else and #endif implements a BLE peripheral that exposes the desired service and characteristics.
#define CENTRAL or not to switch between the two.


#3

Thanks @ScruffR, appreciate the quick response. Will get on to this today / this weekend. Cheers!


#4

If you need any additional explanation about this, just ping :+1:


#5

@ScruffR - what would I need to do to connect to a specific device? I.e. which variable should I be changing to input the target MAC address of a BLE peripheral device?

I’ve tried this, from line 93…

bool attachPeer(BleAddress addr, BlePeerDevice& peer) {
  BleCharacteristic dmyCharacteristic;

  if (peer.connected())
    peer.disconnect();

  peer = BLE.connect("0xDF, 0x11, 0xFE, 0xD7, 0x6B, 0x08");

however get the following out on the log:

0000189539 [app] WARN: Couldn’t connect to device
0000189539 [app] WARN: retry to connect in 5 seconds
.0000199494 [hal.ble] ERROR: connection failed: -250

I am however seeing BLE devices on the scan (which is good news), but it’s just a matter of connecting to the actual one I want, and then we can get into the Characteristic listing, etc.

0000010505 [app] INFO: Found 0 UUIDs in advertisingData
0000010546 [app] INFO: Found 0 UUIDs in advertisingData
0000010732 [app] INFO: Found 1 UUIDs in advertisingData
0000010733 [app] INFO: 1. UUID: FE07
0000010735 [app] INFO: Found 1 UUIDs in advertisingData
0000010735 [app] INFO: 1. UUID: FE07
0000011203 [app] INFO: Found 0 UUIDs in advertisingData
0000011433 [app] INFO: Found 0 UUIDs in advertisingData
0000011701 [app] INFO: Found 0 UUIDs in advertisingData
0000015348 [app] INFO: 7 devices found

Thanks…


#6

You cannot directly use a string for the address but you need to create a BleAddress() object with a fitting constructor.

If you look in my code you see that the function takes a BleAddress addr parameter which should be used inside the function for the BLE.connect() call :wink:


#7

@ScruffR - now we’re talking. See below for output from your code based on the above address object.

So now I’m starting to recognise UUIDs for the board itself (good steps forward) and then need to start looking at the values for the Characteristics themselves.

Characteristic: 2A6E (2a6e)
6e 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00

From the above Characteristic no.13 (in the list below) is temperature. How would one convert the “6e 2a …” into a string / integer?

The below is python code for the handling of the BLE read value (just trying to see if there are similarities here). Is this the right route to follow?

temperature_data = temperature_char.read()
		temperature_data_value =(ord(temperature_data[1])<<8)+ord(temperature_data[0])
		float_temperature_data_value = (temperature_data_value / 100)

0000011291 [app] INFO: found device and connected to it
0000011291 [app] INFO: trying to use getCharacteristicByUUID()
0000011292 [app] INFO: charLong0 (00001110-0000-1000-8000-00805F9B34FB): not found
0000011293 [app] INFO: charShort0 (00002220-0000-1000-8000-00805F9B34FB): not found
0000011294 [app] INFO: charRawL0 (FB343330-8000-0080-0010-000030330000): not found
0000011295 [app] INFO: charRawM0 (00004440-0000-1000-8000-00805F9B34FB): not found
0000011296 [app] INFO: charLong1 (00005550-0000-1000-8000-00805F9B34FC): not found
0000011297 [app] INFO: charShort1 (00006660-0000-1000-8000-00805F9B34FC): not found
0000011298 [app] INFO: charRawL1 (FC347770-8000-0080-0010-000070770000): not found
0000011299 [app] INFO: charRawM1 (00008880-0000-1000-8000-00805F9B34FC): not found
0000011302 [app] INFO: Found 20 of 20 characteristics

  1. Characteristic: 2A00 (2a00)
    00 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  2. Characteristic: 2A01 (2a01)
    01 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  3. Characteristic: 2A05 (2a05)
    05 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  4. Characteristic: 2A29 (2a29)
    29 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  5. Characteristic: 2A24 (2a24)
    24 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  6. Characteristic: 2A25 (2a25)
    25 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  7. Characteristic: 2A27 (2a27)
    27 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  8. Characteristic: 2A26 (2a26)
    26 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  9. Characteristic: 2A23 (2a23)
    23 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  10. Characteristic: 2A19 (2a19)
    19 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  11. Characteristic: 2A76 (2a76)
    76 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  12. Characteristic: 2A6D (2a6d)
    6d 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  13. Characteristic: 2A6E (2a6e)
    6e 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  14. Characteristic: 2A6F (2a6f)
    6f 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  15. Characteristic: C8546913-BFD9-45EB-8DDE-9F8754F4A32E (a32e)
    2e a3 f4 54 87 9f de 8d eb 45 d9 bf 13 69 54 c8
  16. Characteristic: C8546913-BF02-45EB-8DDE-9F8754F4A32E (a32e)
    2e a3 f4 54 87 9f de 8d eb 45 02 bf 13 69 54 c8
  17. Characteristic: C8546913-BF03-45EB-8DDE-9F8754F4A32E (a32e)
    2e a3 f4 54 87 9f de 8d eb 45 03 bf 13 69 54 c8
  18. Characteristic: EC61A454-ED01-A5E8-B8F9-DE9EC026EC51 (ec51)
    51 ec 26 c0 9e de f9 b8 e8 a5 01 ed 54 a4 61 ec
  19. Characteristic: EFD658AE-C401-EF33-76E7-91B00019103B (103b)
    3b 10 19 00 b0 91 e7 76 33 ef 01 c4 ae 58 d6 ef
  20. Characteristic: EFD658AE-C402-EF33-76E7-91B00019103B (103b)
    3b 10 19 00 b0 91 e7 76 33 ef 02 c4 ae 58 d6 ef

#8

You wouldn’t. You already have the BleCharacteristicsUuid you want and hence need to keep hold of that to actually connect to this characteristics.
My code (in the first post in the linked issue report) does that here

      peerRealTimeSensorCharacteristic = chars[c];                          // when found store reference globally and hook-up callback

(peerRealTimeSensorCharacteristic is a global variable; you can name it however you want e.g. tempBleChar)

Once you have the characteristics reference you need to either poll the data via tempBleChar.getValue() or by hooking up a callback function as done in my code here

      peerRealTimeSensorCharacteristic.onDataReceived(onDataReceived, NULL);

From your Python code I gather your temp value repots 100ths of degrees as int16_t.
So my first try (assuming endiannes matches the platform) would look something like this

#include <math.h>
...
  int16_t rawTemp;
  float   temp = NAN;
  if (sizeof(rawTemp) ==  tempBleChar.getValue((uint8_t*)&rawTemp, sizeof(rawTemp)))
    temp = rawTemp / 100.0;

If the values don’t matchup, you may need to swap high and low byte of rawTemp tho’


#9

@ScruffR - more help digested and progess made, thanks.

So I’ve implemented your suggestions, including having to go back to some BLE basics through your initially linked code / issue report.

I found I was leaving out a critical stage of “tying” the Characteristic to the UUID through peer.getCharacteristicByUUID(BLE_temp, charLong0);

So putting this all together quite roughly (still within loops, etc.) I got this output (I’ve just added in a simple Serial.println(rawTemp):

  1. Characteristic: 2A6E (2a6e)
    6e 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    2897

With the discussion on the 100ths for temperature measurement direct from the board one would think this is then 28.97 degrees C. Confirming this by connecting my phone to the Thunderboard Sense (through the official app) I get the following screenshot…

Temperature at 28.8 degrees C… so I think I’m on to something, i.e. it’s working (although clunky given my mashup of your code).

I’ll look to refresh, with the other sensor data, and get it into a 5-10 min sensor data collection, all whilst keeping the BLE connection alive. Ideally then I should have 6-8 sensors collected.

More later. Thanks!


#10

This is not required when you do it the alternate way I suggested above.
Due to the (now confirmed) bug in the BLE implementation.
Due to that bug peer.getCharacteristicsByUUID() may not actually find the desired characteristics although present, but peer.discoverAllCharacteristics() always will but leaves the finding of the desired one to the application code.

Where did you add this?
Showing your actual code might calrify the confusion and also reveal some potential candidates for a cleanup :wink:

If you are using Web IDE you can just post a SHARE THIS REVISION link.


#11

@ScruffR - thanks. Seems there is indeed a bug as the Argon runs the code for a bit and then starts it’s SOS red flash.

See here for revision. Feel free to change where you feel fit. I’ve added some comments in there to help with any understanding gaps.

Neal.


#12

@ScruffR - I see another introductory example here, but I’ll not get into that until I’ve given yours a good go (given how close we are).

N.


#13

I see you have several “dummy” UUIDs in that code. In my code I only had that many UUID versions to illustrate the buggy treatment of short UUIDs and UUIDs directly based on the Bluetooth_Base_Uuid.
For your use case you should get rid of all these dummies.

With regards to this

const BleUuid charLong0("00000000-0000-0000-0000-000000002a6e");  //Is there anyway to make this the short UUID as I see this is the long version

That is exactly one of the instances where the bug comes into play.
You could use const BleUuid shortUUID(0x2A6E, ) but when comparing that with longUUID("00002A6E-0000-1000-8000-00805F9B34FB").shorted() they won’t match although they should.
It’s also wrong that you get these characteristics without the 0000-1000-8000-00805F9B34FB part but shifted 96 bits to the left.
As long these bugs exist, you need to workaround the issue as shown in my initial issue report code.


I tried to reduced your code to what’s really needed.
Since you already know the address of your desired peripheral the entire scanning block can be omitted. I also kicked out the dummy UUIDs and so I’m left with this.
Give this a try.

#include "Particle.h"
#include <math.h>

SYSTEM_MODE(MANUAL)
SYSTEM_THREAD(ENABLED)

SerialLogHandler  logger(LOG_LEVEL_INFO);

const uint16_t    vendor = 0x2A23;
const uint8_t     BLE_PEER_ADDRESS[] = {0xDF, 0x11, 0xFE, 0xD7, 0x6B, 0x08};    // MAC address of the peripheral device
const BleAddress  bleAddress(BLE_PEER_ADDRESS);
const BleUuid     serviceUuidRoot("7881CD3D-2F35-6AFA-7C3E-963A648DF526"); 
const BleUuid     charTempUuidLong("00002A6E-0000-1000-8000-00805F9B34FB");     // probably the long  UUID reported by nRF Connect App
const BleUuid     charTempUuidShort(0x2A6E);                                    // probably the short UUID reported by Bluefruit App
const uint16_t    charTempUuidNumeric(0x2A6E);                                  // number literal to work around the current bug

BlePeerDevice     peerDevice;
BleCharacteristic charTemp;

uint32_t loopDelay = 1000;                                                      // cadence for void loop()
uint16_t rawTemp   = 0;
float    temp      = 0;

bool attachPeer(BleAddress addr, BlePeerDevice& peer, int maxCharacteristics = 20);

void setup()
{
  Serial.begin();
}

void loop() {
  static uint32_t ms = 0;
  if (millis() - ms < loopDelay) return;
  ms = millis();
  
  if (!BLE.connected() && !attachPeer(bleAddress, peerDevice)) {                // first check connection, if not already connected, try to
    Log.warn("could not connect to peer device - retry in 5 seconds");          // when both checks fail log a warning
    loopDelay = 5000;
  }
  else if (charTemp.valid()) {
    loopDelay = 1000;                                                           // when connected read every second 
                                                                                //   could be modulated via delta temp 
                                                                                //   more volatile temperature may warrant a lower loop cadance
    charTemp.getValue(&rawTemp);                                                // request temperature from BLE peripheral
    temp = rawTemp / 100.0;                                                     // convert from 100ths, although too simple
    Serial.printlnf("Temp %.1f °C (raw: %d)", temp, rawTemp);
  }
}

bool attachPeer(BleAddress addr, BlePeerDevice& peer, int maxCharacteristics) {
  charTemp = BleCharacteristic();                                               // invalidate characteristic

  if (peer.connected())
    peer.disconnect();

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

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

  // *** BUG: peer.getCharacteristicByUUID() is currently not guaranteed to actually find the characteristic even when it's there ***
  // *** depending on the original UUID the long the short or neither UUID can be found, in the latter case full scan is required ***. 
  if(!peer.getCharacteristicByUUID(charTemp, charTempUuidLong) && !peer.getCharacteristicByUUID(charTemp, charTempUuidShort)) {
    BleCharacteristic chars[maxCharacteristics];
    int foundChar = peer.discoverAllCharacteristics(chars, maxCharacteristics);   // discover its exposed characteristics
    Log.info("Found %d of %d characteristics", foundChar, maxCharacteristics);
    for (int c = 0; c < foundChar; c++) {
      Serial.printlnf("%d. Characteristic: %s (%04x)", c + 1, (const char *)chars[c].UUID().toString(), chars[c].UUID().shorted());
      for (int i = 0; i < 16; i++)
        Serial.printlnf(" %02x", chars[c].UUID().rawBytes()[i]);
    
      if (chars[c].UUID()           == charTempUuidLong
      ||  chars[c].UUID().shorted() == charTempUuidLong.shorted()
      ||  chars[c].UUID().shorted() == charTempUuidShort.shorted()
      ||  chars[c].UUID().shorted() == charTempUuidNumeric)              
      {                                                                           // check against expected value
        charTemp = chars[c];
        // alternatively we could just hook up a callback handler to deal with new data
        break;                                                                    // already found what we are looking for, so we can stop here
      }
    }
  }
  return charTemp.valid();
}

#14

@ScruffR - thanks, appreciate the time to adjust your code again, to my application.

Code compiles fine, runs fine however the only output I see is the following :

0000012367 [app] INFO: Found device and connected to it
Temp °C (raw: )
Temp °C (raw: )
Temp °C (raw: )
Temp °C (raw: )
Temp °C (raw: )

I can tell it is indeed connected as the BLE peripheral is no longer advertising, however it doesn’t seem to connect to the Characteristic UUID.

I’m going to give it a couple hours today to see where it is falling short. Will message again.

N


#15

Duh! :man_facepalming:
I forgot to add the variable placeholders in the format string
The line should be like this

    Serial.printlnf("Temp %.1f °C (raw: %d)", temp, rawTemp);

(corrected above)


#16

Happiness is a slowly ticking along Particle Argon connected via BLE to a sensor peripheral device reporting sensor data!

Temp 25.4 °C (raw: 2536)

Thanks @ScruffR. I’ll slowly add the other sensors onto your code. Will message when I have them all up and running. Excellent.


#17

@ScruffR

Would your code also be good for connecting to a known BLE device but instead of looking for the Temp data I’m needing to monitor the RSSI level of the BLE device I’m connecting to as a way to determine how close the devices are to each other.

Since I’m know the RSSI signal strength is something that’s calculated by the device that is connecting to the BLE preparial I’m wondering what code would be needed to be added to also serial print the RSSI stregnth of the device that is sending the Temp data?

Any advice or help is greatly appreciated :wink:


#18

The RSSI value is part of the BleScanResult hence you shouldn’t even need to connect to the device.


#20

I guess this is diverting the original target of this thread :wink:


#21

@ScruffR - Couple of questions :

I’d like to get your view on how to expand your code revision to the other sensor UUIDs. I’ve tried various things however they’ve not worked much. Whilst normally I’d just copy paste code however the code from here on down (line 80)

if(!peer.getCharacteristicByUUID(charTemp, charTempUuidLong) && !peer.getCharacteristicByUUID(charTemp, charTempUuidShort)) {

is only for the temperature UUID, so I’m unsure if I’d have to repeat the whole if loop for each sensor UUID or are we connected to the device and so have a simpler code implementation for the other UUIDs? Do we know which of the UUIDs we’re using in the code snippet above? I see your comments RE the bug however just trying to keep things simple (without having to have 2-3 of the same UUIDs for each sensor).

FYI - I’ve included some code in the revision which is just for reference; other UUIDs for the sensors and then the calculation of each of the values.

I also understand I’d need value calculation holders as we’ve done for temperature, that’s the easy part.

uint16_t rawTemp   = 0;
float    temp      = 0;

The good news is that the code is spot on for connection to the device (i.e. takes no time at all), and immediately starts printing the temperature values.

N