BLE Advertisement Data

All,

I would like to scan for a BLE device’s advertisement data, parse that data and then display a result (this time a distance measurement). I can see that the BLE device is indeed advertising, from my phone (passive scanning), and it has data coming through. I’m also seeing the specific MAC address of the device, so I can pick it up.

I have the following working but I’m unsure as to a few aspects;

  • parsing data as little or big endian (is there a standard option or is each manufacturer different?
  • Should I be looking in BleAdvertisingData advData = scanResults.advertisingData();
    or BleAdvertisingData scanRespData = scanResults.scanResponse()? I would guess, given the name, .advertisingData()?
  • Any tips for an easy way to parse the 31 bytes of data?
#include "Particle.h"
#define BLE_PEER_ADDRESS    {0xEA, 0xD5, 0xF7, 0xAB, 0xEF, 0x60}        //Barra BLE Radar MAC Address = 60:EF:AB:F7:D5:EA
                                                                        //Barra Advertising Name = DM1199804                
SYSTEM_MODE(AUTOMATIC);
SerialLogHandler logHandler(LOG_LEVEL_INFO);

const BleAddress            ble_peer_address = BleAddress(BLE_PEER_ADDRESS);
uint32_t serialnumber = 0;
//int BLE_MAX_ADV_DATA_LEN = 50;

void setup() {
    (void)logHandler;
}

void loop() {
    Vector<BleScanResult> scanResults = BLE.scan();
        for (int x = 0; x < scanResults.size(); x++) {
	       if (strncmp(scanResults[x].address().toString(), ble_peer_address.toString(), 17) == 0) {           //focus on the first 17 characters, as this is the length of the MAC address.
	                
	                Log.info("Found matching device %s", scanResults[x].address().toString().c_str());    
	            
	                BleAdvertisingData advData = scanResults[x].advertisingData();
                    BleAdvertisingData scanRespData = scanResults[x].scanResponse();
	            
                    uint8_t buffer[BLE_MAX_ADV_DATA_LEN];
                    
                    //size_t len = scanRespData.get(buffer, BLE_MAX_ADV_DATA_LEN);
                    size_t len = advData.get(buffer, BLE_MAX_ADV_DATA_LEN);
                    Log.info("Length of Buffer = %d bytes", sizeof(buffer));                  //confirm size of buffer (31 bytes)

                    //The buffer is normally 31 bytes in size (standard BLE maximum).
                    //All records will start with this block. Length = 11 [0 to 10] which includes Record Length, Sequence Number, RTC datetime and Log Reason. 
                    /*DM Barra Radar Length = 15 bytes [11-25] (therefore it is , tag type 20), which includes all the data with the following: 
                    
                    0 to 4 (11-14) uint32 of length 4 = Tag Serial Number
                    5 to 6 (15-16) uint16 of length 2 = Battery Voltage in mV
                    7 to 8 (17-18) uint16 of length 2 = Distance of Strongest Peak (mm)
                    9 to 10 (19-20) int16 of length 2 = Strength of strongest Peak (dB)
                    11 to 12 (21-22) uint16 of length 2 = Distance of second strongest peak (mm)
                    13 to 14 (23-24) int16 of length 2 = Stregth of second strongest peak (dB)
                    15 (25) Byte : 1 of length 0.1 = Object Detected Flag (Bit zero indicates object detected)
                    15 (25) Byte : 7 of length 0.7 = Reserved (set to 0)*/
                    
                    for (uint8_t i = 0; i < (sizeof(buffer)); i++){
                        Serial.printf("%X", buffer[i]);
                    }
                    
                    //uint16_t FieldID = buffer[0];
                    //Log.info("FieldID: %d", FieldID);
                    //delay(200);
                    
                    uint16_t length = (static_cast<uint16_t>(buffer[1]) << 8) | buffer[0];
                    Log.info("Record Length: %d", length);
                    delay(200);
                    
                    serialnumber =      ((uint32_t)buffer[11]<<24) |
                                        ((uint32_t)buffer[12]<<16) |
                                        ((uint32_t)buffer[13]<<8) |
                                        ((uint32_t)buffer[14]);
                                        
                    Log.info("Serial Number: %" PRIu32, serialnumber);
                    delay(200);
                    
                    Log.info("RSSI : %d", scanResults[x].rssi());
                    
                    uint16_t batteryvoltage = (static_cast<uint16_t>(buffer[16]) << 8) | buffer[15];
                    Log.info("Battery Voltage (mV): %hu", batteryvoltage);
                    delay(200);
                    
                    uint16_t firstdistance = (static_cast<uint16_t>(buffer[18]) << 8) | buffer[17];
                    Log.info("First Distance (mm): %.2lf", firstdistance);
                    delay(200);
                    
                    int16_t firststrength = (static_cast<int16_t>(buffer[20]) << 8) | buffer[19];
                    Log.info("First Strength (dB): %hu", firststrength);
                    delay(200);
                    
                    uint16_t seconddistance = (static_cast<uint16_t>(buffer[22]) << 8) | buffer[21];
                    Log.info("Second Distance (mm): %.2lf", seconddistance);
                    delay(200);
                    
                    int16_t secondstrength = (static_cast<int16_t>(buffer[24]) << 8) | buffer[23];
                    Log.info("Second Strength (dB): %hu", secondstrength);
                    delay(200);
                    
                    String name = scanResults[x].advertisingData().deviceName();
                    Log.info("Advertising name: %s", name.c_str());
                    
                    delay(1000);
                    memset(buffer, 0, sizeof(buffer));
                    
                    }
	           }
            //}
        //}
    delay(3000);
}

@ScruffR - you’ve previously been the GOAT on all such things. Have you tried this before and got it working?

The manufacturing data is defined by the manufacturer, so they chose the byte order. The Photon 2 is little endian.

The advertising data (31 bytes) is constantly transmitted by the BLE peripheral devices and does not require connecting to the device. You just catch it as it gets broadcast constantly using BLE.scan().

It's just an array of uint8_t bytes so it's easy to read. However you should read the int16 and int32 values by shifting and adding the bytes, taking into account byte order. Because the offsets appear to be not be aligned, you can't just cast a pointer and read the value out directly, and this would also not work with big endian byte order in the advertising data.

Thanks for the quick reply.

Fully understand the passive scanning, which is exactly the data I’m looking to acquire.

After BLE.scan(), should I be looking at the buffer from advertisingData() (seems like it) or scanResponse()? I have the manufacturer’s structure so hopefully I should be able to parse the individual sections to relevant data.

Do you know of any examples which I might lean on for some code structure help. I’ve roped together the code above, but it’s not an all together work of art.

Your code is close. The device nearby example in the BLE docs shows how to examine the advertising data.

Just FYI that I got the below code working as initially desired. I thought I’d post it here as reference for anyone looking for passive BLE advertising data being parsed and published into the cloud. Works great and am looking to test long term, outside stability.

#include "Particle.h"

#define BLE_PEER_ADDRESS {0xEA, 0xD5, 0xF7, 0xAB, 0xEF, 0x60}  // MAC: 60:EF:AB:F7:D5:EA, Name: DM1199804
SYSTEM_MODE(AUTOMATIC);
SerialLogHandler logHandler(LOG_LEVEL_INFO);

// Constants
const BleAddress ble_peer_address = BleAddress(BLE_PEER_ADDRESS);
const pin_t MY_LED = D7;
const uint16_t SCAN_DELAY_MS = 3000;
const uint16_t LOG_DELAY_MS = 200;
const uint16_t POST_PROCESS_DELAY_MS = 1000;
const uint16_t DIGITAL_MATTER_COMPANY_ID = 0x064F;
const uint8_t MANUFACTURER_DATA_TYPE = 0xFF;
const uint8_t MIN_RADAR_DATA_LENGTH = 16;
const size_t AD_STRUCTURE_START_IDX = 14;

// Global Variables
uint32_t serialnumber = 0;
uint16_t batteryVoltage_mV = 0;
uint16_t strongestPeakDistance_mm = 0;
uint16_t strongestPeakStrength_centi_dBm = 0;
uint16_t secondPeakDistance_mm = 0;
uint16_t secondPeakStrength_centi_dBm = 0;

void setup() {
    (void)logHandler;
    Serial.println("Photon 2 BLE + Digital Matter BLE Barra Radar");
    Serial.println("Starting BLE scan...");
    pinMode(MY_LED, OUTPUT);
    digitalWrite(MY_LED, LOW);
}

void parseRadarData(const uint8_t* radarData) {

    batteryVoltage_mV = (uint16_t)(radarData[9] | (radarData[10] << 8));                    // Battery Voltage (2 bytes, little endian)
    strongestPeakDistance_mm = (uint16_t)(radarData[0] | (radarData[1] << 8));              // Distance of strongest peak (2 bytes, little endian)
    strongestPeakStrength_centi_dBm = (int16_t)(radarData[2] | (radarData[3] << 8));        // Strength of strongest peak (2 bytes, little endian, signed)
    secondPeakDistance_mm = (uint16_t)(radarData[4] | (radarData[5] << 8));                 // Distance of second strongest peak (2 bytes, little endian)
    secondPeakStrength_centi_dBm = (int16_t)(radarData[6] | (radarData[7] << 8));           // Strength of second strongest peak (2 bytes, little endian, signed)
}

void logRadarData() {
    Log.info("Battery Voltage (mV): %.4f", (batteryVoltage_mV / 1000.00));
    delay(LOG_DELAY_MS);
    
    Log.info("First Distance (mm): %.2lf", (strongestPeakDistance_mm/1000.00));
    Particle.publish("Radar_Dist", String::format("%.2f", (strongestPeakDistance_mm/1000.00)));
    delay(LOG_DELAY_MS);
    
    Log.info("First Strength (dB): %hu", strongestPeakStrength_centi_dBm);
    delay(LOG_DELAY_MS);
    
    Log.info("Second Distance (mm): %.2u", secondPeakDistance_mm);
    delay(LOG_DELAY_MS);
    
    Log.info("Second Strength (dB): %hu", secondPeakStrength_centi_dBm);
    delay(LOG_DELAY_MS);
}

void printAdvertisementHeader(const BleScanResult& result, size_t dataLength) {
    String name = result.advertisingData().deviceName();
    
    Serial.println("\n========== BLE Advertisement Received ==========");
    Serial.printf("Device: %s\n", name.c_str());
    Serial.printf("RSSI: %d dBm\n", result.rssi());
    Serial.printf("Data Length: %d bytes\n", dataLength);
    Serial.printf("MAC Address: %s\n", result.address().toString().c_str());
}

void printRawData(const uint8_t* buffer, size_t length) {
    Serial.printf("Raw Data: ");
    for (uint8_t i = 0; i < length; i++) {
        Serial.printf("%02X", buffer[i]);
    }
    Serial.printf("\n");
}

bool processManufacturerData(const uint8_t* buffer, size_t idx) {
    uint8_t len = buffer[idx];
    
    if (len == 0 || idx + len >= BLE_MAX_ADV_DATA_LEN) {
        return false;
    }
    
    uint8_t type = buffer[idx + 1];
    Serial.printf("Type : %u, therefore ", type);
    
    if (type != MANUFACTURER_DATA_TYPE) {
        return true;  // Continue processing
    }
    
    if (len < MIN_RADAR_DATA_LENGTH) {
        return true;  // Not enough data
    }
    
    // Extract company ID (little endian)
    uint16_t companyId = buffer[idx + 2] | (buffer[idx + 3] << 8);
    
    if (companyId != DIGITAL_MATTER_COMPANY_ID) {
        return true;  // Not Digital Matter
    }
    
    Serial.printf("Manufacturer = Digital Matter.\n");
    
    // Parse Barra Radar data (15 bytes starting at idx + 6)
    uint8_t* radarData = (uint8_t*)&buffer[idx + 6];
    parseRadarData(radarData);
    
    return true;
}

void processAdvertisementData(const BleScanResult& result) {
    BleAdvertisingData advData = result.advertisingData();
    uint8_t buffer_ad[BLE_MAX_ADV_DATA_LEN];
    size_t len = advData.get(buffer_ad, BLE_MAX_ADV_DATA_LEN);
    
    printAdvertisementHeader(result, len);
    printRawData(buffer_ad, sizeof(buffer_ad));
    
    size_t idx = AD_STRUCTURE_START_IDX;                    // Process AD structures
    while (idx < sizeof(buffer_ad)) {
        if (!processManufacturerData(buffer_ad, idx)) {
            break;
        }
        idx += buffer_ad[idx] + 1;
    }
    
    logRadarData();
    delay(POST_PROCESS_DELAY_MS);
    memset(buffer_ad, 0, sizeof(buffer_ad));
}

void loop() {
    Vector<BleScanResult> scanResults = BLE.scan();
    
    for (int x = 0; x < scanResults.size(); x++) {
        // Check if this is our target device (compare first 17 chars of MAC address)
        if (strncmp(scanResults[x].address().toString(), ble_peer_address.toString(), 17) == 0) {
            processAdvertisementData(scanResults[x]);
        }
    }
    
    delay(SCAN_DELAY_MS);
}
3 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.