Scanning BLE devices near Photon 2

I am trying to get a list of nearby BLE devices along with their UUIDs and characteristics but I could not find any sample codes that do just that. So I turned to ChatGPT and it came back with this:

#include "Particle.h"

SYSTEM_MODE(AUTOMATIC);

void setup() {
    Serial.begin(9600);  // Initialize serial communication for debugging
    BLE.on();            // Turn on BLE
    BLE.setScanTimeout(5000);  // Set scan timeout to 5 seconds

    Log.info("Starting BLE scan...");
    scanForDevices();    // Scan for BLE devices
}

void loop() {
    // Do nothing in the loop, just setup and scan
}

// Function to scan for nearby BLE devices
void scanForDevices() {
    BleScanResult scanResults[20];  // Array to hold scan results
    int foundDevices = BLE.scan(scanResults, 20);  // Scan for up to 20 devices

    if (foundDevices > 0) {
        Log.info("Found %d devices", foundDevices);
        for (int i = 0; i < foundDevices; i++) {
            BleAddress address = scanResults[i].address();
            Log.info("Device %d: %s", i + 1, address.toString().c_str());

            // Try connecting to the device
            BlePeerDevice peerDevice = BLE.connect(scanResults[i].address());
            if (peerDevice.connected()) {
                Log.info("Connected to device %d", i + 1);

                // Discover all services and characteristics
                discoverServicesAndCharacteristics(peerDevice);

                // Disconnect after discovering the services and characteristics
                peerDevice.disconnect();
                Log.info("Disconnected from device %d", i + 1);
            } else {
                Log.error("Failed to connect to device %d", i + 1);
            }
        }
    } else {
        Log.error("No BLE devices found");
    }
}

// Function to discover services and characteristics of a connected BLE device
void discoverServicesAndCharacteristics(BlePeerDevice& peerDevice) {
    Vector<BleService> services;
    peerDevice.discoverAllServicesAndCharacteristics(services);

    // Loop through all discovered services
    for (BleService& service : services) {
        BleUuid serviceUUID = service.UUID();
        Log.info("Service UUID: %s", serviceUUID.toString().c_str());

        // Loop through all characteristics in this service
        Vector<BleCharacteristic> characteristics = service.characteristics();
        for (BleCharacteristic& characteristic : characteristics) {
            BleUuid characteristicUUID = characteristic.UUID();
            Log.info("  Characteristic UUID: %s", characteristicUUID.toString().c_str());
        }
    }
}

Unfortunately, when I try to compile it it says:

obdii_ble_002.ino:52:16: 'class particle::BlePeerDevice' has no member named 'discoverAllServicesAndCharacteristics'; did you mean 'discoverAllCharacteristics'?

and

obdii_ble_002.ino:60:61: 'class particle::BleService' has no member named 'characteristics'

Changing "discoverAllServicesAndCharacteristics" to "discoverAllCharacteristics" did not solve the problem.

Any ideas of what I am doing wrong, or perhaps some pointers to a sample code that does what I am trying to do?

The docs are really good on BLE and I think you want BLEscanWithFilter

1 Like

I checked that documentation but could not find an example of what I need to do. Based on your suggestion to use scanWithFilter, I tried this code but it is not finding the device. I am not sure if I am using the right values for deviceName and serviceUUID. I am using what appeared to be the most likely values that were returned using a BLE sniffer app on my phone. I was hoping that the sample code in the original post would show me the right values as they are seen by the Photon.

#include "Particle.h"

SYSTEM_MODE(AUTOMATIC);

void scan() {
    BleScanFilter filter;
    filter.deviceName("00002A00-0000-1000-8000-00805F9B34FB").minRssi(-50).serviceUUID(0x1801);
    Vector<BleScanResult> scanResults = BLE.scanWithFilter(filter);

    if (scanResults.size()) {
        Log.info("%d devices found", scanResults.size());
        Serial.println(scanResults.size() + " devices found");

        for (int ii = 0; ii < scanResults.size(); ii++) {
            Log.info("MAC: %s | RSSI: %dBm", scanResults[ii].address().toString().c_str(), scanResults[ii].rssi());
            Serial.println("MAC: "  + scanResults[ii].address().toString());
            Serial.println("RSSI: " + scanResults[ii].rssi());

            String name = scanResults[ii].advertisingData().deviceName();
            if (name.length() > 0) {
                Log.info("Advertising name: %s", name.c_str());
                Serial.println("Advertising name: " + name);
            }
        }
    }
    else {
        Serial.println("Device not found");
    }
    
}
// setup() runs once, when the device is first turned on
void setup() {
    Serial.begin(9600);
    delay(1000);
}

void loop() {
    scan();
    delay(3000);
}

hi, what if you reduce the number of filters to see if something shows up?

Example:

filter.serviceUUID(0x1801);

or:

filter.minRssi(-50);

Cheers

1 Like

That helped a lot, thanks! There was some progress and the Photon is now detecting devices. Turns out I had to enter "OBDII" as device name and get rid of the other filters.

Now that the ELM327 device has been detected, how do I go about sending and receiving serial data to it?

1 Like

That's great.

Next step looks like this:

 peripheral = BLE.connect(bleScanResult->address());
  if (peripheral.connected())
  {
    Log.info("Peripheral connected");
    peripheral.getCharacteristicByUUID(rxCharacteristic, rxUuid);
    peripheral.getCharacteristicByUUID(txCharacteristic, txUuid);
  }
  else
  {
    Log.info("Failed to connect to peripheral");
  }

Best,

1 Like

Once again, thanks. I have expanded on the snippets you provided to get to this:

#include "Particle.h"

SYSTEM_MODE(AUTOMATIC);
SerialLogHandler logHandler(LOG_LEVEL_INFO);

// UUIDs for the ELM327 characteristics (example UUIDs, replace with actual)
const BleUuid txUuid("0000fff1-0000-1000-8000-00805f9b34fb"); // UUID for TX characteristic (ELM327 to Photon)
const BleUuid rxUuid("00002a00-0000-1000-8000-00805f9b34fb"); // UUID for RX characteristic (Photon to ELM327)

BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peripheral;

// Function to scan and connect to ELM327
void scanAndConnect() {
    BleScanFilter filter;
    filter.deviceName("OBDII");
    Vector<BleScanResult> scanResults = BLE.scanWithFilter(filter);

    if (scanResults.size()) {
        Log.info("%d devices found", scanResults.size());

        for (int ii = 0; ii < scanResults.size(); ii++) {
            Log.info("MAC: %s | RSSI: %dBm", scanResults[ii].address().toString().c_str(), scanResults[ii].rssi());

            String name = scanResults[ii].advertisingData().deviceName();
            if (name.length() > 0) {
                Log.info("Advertising name: %s", name.c_str());
            }

            peripheral = BLE.connect(scanResults[ii].address());
            if (peripheral.connected()) {
                Log.info("Peripheral connected");

                // Get the TX and RX characteristics
                peripheral.getCharacteristicByUUID(peerTxCharacteristic, rxUuid);
                peripheral.getCharacteristicByUUID(peerRxCharacteristic, txUuid);                

                if (peerTxCharacteristic.valid() && peerRxCharacteristic.valid()) {
                    Log.info("TX and RX characteristics found");

                    // Set callback for receiving data
                    peerRxCharacteristic.onDataReceived(onDataReceived);

                    // Send an initial command to the ELM327
                    sendCommand("ATI\r"); // Example command to get ELM327 version
                } else {
                    Log.info("Failed to find TX/RX characteristics");
                }
                break;
            } else {
                Log.info("Failed to connect to peripheral");
            }
        }
    } else {
        Log.info("Device not found");
    }
}

// Function to handle incoming data from ELM327
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
    String receivedData = String((const char*)data, len);
    Log.info("Data received: %s", receivedData.c_str());
    // You can process the received data here
}

// Function to send a command to the ELM327
void sendCommand(const char* command) {
    if (peripheral.connected() && peerTxCharacteristic.valid()) {
        peerTxCharacteristic.setValue((const uint8_t*)command, strlen(command));
        Log.info("Command sent: %s", command);
    } else {
        Log.info("Not connected or TX characteristic invalid");
    }
}

void setup() {
    Serial.begin(9600);
    delay(1000);
    BLE.on(); // Enable BLE
}

void loop() {
    if (!peripheral.connected()) {
        scanAndConnect();
    }

    // Example of sending a command periodically
    if (peripheral.connected()) {
        sendCommand("010C\r"); // Request RPM data (example OBD-II PID command)
        delay(5000); // Delay to allow response
    }

    delay(1000);
}

It works until the part where its says "Peripheral connected" but it gets stuck with the UUIDs. I am attempting to get the UUIDs using a BLE scanner as shown in the picture but I am not sure if I am getting the right ones.

Is there, by any chance, a way to get them from the information returned at the time of initial connection?

I do something like this in a loop with index ii over the scanResults.

        BleUuid foundServiceUuid;
        size_t svcCount = scanResults[ii].advertisingData().serviceUUID(&foundServiceUuid, 1);
        String advertisedName = scanResults[ii].advertisingData().deviceName();
        String responseName = scanResults[ii].scanResponse().deviceName();

For reasons I don't recall (maybe Gus does) I actually check both the advertisedName and the responseName for the device name string I am looking for.

1 Like

The original code was pretty close. Here's a modified version that compiles and runs and discovers and prints all of the characteristics like the original code was supposed to.

#include "Particle.h"

SYSTEM_MODE(AUTOMATIC);
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler(LOG_LEVEL_TRACE);

void scanForDevices();
void discoverServicesAndCharacteristics(BlePeerDevice &peerDevice);

const std::chrono::milliseconds scanInterval = 30s;
unsigned long scanLastMillis = 0;

void setup()
{
    BLE.on();                 // Turn on BLE
    BLE.setScanTimeout(5000); // Set scan timeout to 5 seconds

}

void loop()
{
    if (millis() - scanLastMillis >= scanInterval.count()) {
        scanLastMillis = millis();
        Log.info("Starting BLE scan...");
        scanForDevices(); // Scan for BLE devices
    }
}

// Function to scan for nearby BLE devices
void scanForDevices()
{
    BleScanResult scanResults[20];                // Array to hold scan results
    int foundDevices = BLE.scan(scanResults, 20); // Scan for up to 20 devices

    if (foundDevices > 0)
    {
        Log.info("Found %d devices", foundDevices);
        for (int i = 0; i < foundDevices; i++)
        {
            BleAddress address = scanResults[i].address();
            Log.info("Device %d: %s", i + 1, address.toString().c_str());

            // Try connecting to the device
            BlePeerDevice peerDevice = BLE.connect(scanResults[i].address());
            if (peerDevice.connected())
            {
                Log.info("Connected to device %d", i + 1);

                // Discover all services and characteristics
                discoverServicesAndCharacteristics(peerDevice);

                // Disconnect after discovering the services and characteristics
                peerDevice.disconnect();
                Log.info("Disconnected from device %d", i + 1);
            }
            else
            {
                Log.error("Failed to connect to device %d", i + 1);
            }
        }
    }
    else
    {
        Log.error("No BLE devices found");
    }
}

// Function to discover services and characteristics of a connected BLE device
void discoverServicesAndCharacteristics(BlePeerDevice &peerDevice)
{
    // Loop through all discovered services
    for (BleService &service : peerDevice.services())
    {
        BleUuid serviceUUID = service.UUID();
        Log.info("Service UUID: %s", serviceUUID.toString().c_str());

        Vector<BleCharacteristic> characteristics = peerDevice.discoverAllCharacteristics();

        // Loop through all characteristics in this service
        for (BleCharacteristic &characteristic : characteristics)
        {
            BleUuid characteristicUUID = characteristic.UUID();
            Log.info("  Characteristic UUID: %s", characteristicUUID.toString().c_str());
        }
    }
}
3 Likes

*** Update to post ***
I figured out which UUIDs to use. They are 0xfff1 and 0xfff2 respectively.

*** Original post ***

That snippet you gave me provided the following result:

0000086317 [app] INFO: Service UUID: 1800
0000086328 [app] INFO:   Characteristic UUID: 2A00
0000086342 [app] INFO:   Characteristic UUID: 2A05
0000086355 [app] INFO:   Characteristic UUID: FFF1
0000086366 [app] INFO:   Characteristic UUID: FFF2
0000086380 [app] INFO: Service UUID: 1801
0000086391 [app] INFO:   Characteristic UUID: 2A00
0000086402 [app] INFO:   Characteristic UUID: 2A05
0000086416 [app] INFO:   Characteristic UUID: FFF1
0000086429 [app] INFO:   Characteristic UUID: FFF2
0000086442 [app] INFO: Service UUID: FFF0
0000086453 [app] INFO:   Characteristic UUID: 2A00
0000086466 [app] INFO:   Characteristic UUID: 2A05
0000086479 [app] INFO:   Characteristic UUID: FFF1
0000086492 [app] INFO:   Characteristic UUID: FFF2

Being unable to tell which is which, I tried a number of combinations using a 0x???? format as opposed to the previous long string. There was some progress because now instead of getting stuck at "Not connected or TX characteristic invalid", I am getting to the part where it says "TX and RX characteristics found" and it appears as though AT commands are being sent. However, no response is coming. No matter what combination I use, the result is the same. Based on the results that were returned, is there a way to determine which one is the txUuid and which is the rxUuid. I feel I am getting so close and yet the destination is ever so elusive!

2 Likes

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