Bluetooth LE (BLE)

BLE Class

BLE.advertise()

A BLE peripheral can periodically publish data to all nearby devices using advertising. Once you've set up the BleAdvertisingData object, call BLE.advertise() to continuously advertise the data to all BLE devices scanning for it.

Optionally, you can provide a scanResponse data object. If provided, the central device can ask for it during the scan process. Both the advertising data and scan data are public - any device can see and request the data without authentication.

// PROTOTYPE
int advertise(BleAdvertisingData* advertisingData, BleAdvertisingData* scanResponse = nullptr) const;

// EXAMPLE
BleAdvertisingData advData;

// While we support both the health thermometer service and the battery service, we
// only advertise the health thermometer. The battery service will be found after
// connecting.
advData.appendServiceUUID(healthThermometerService);


// Continuously advertise when not connected
BLE.advertise(&advData);

Returns 0 on success or a non-zero error code.

You cannot use BLE advertising while in listening mode (blinking dark blue). The device will be advertising its configuration capabilities to the Particle app when in listening mode.

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.advertise(iBeacon)

You can advertise as an Apple iBeacon. See the iBeacon section for more information.

// PROTOTYPE
int advertise(const iBeacon& beacon) const;

Returns 0 on success or a non-zero error code.

You cannot use BLE advertising including iBeacon while in listening mode (blinking dark blue). The device will be advertising its configuration capabilities to the Particle app when in listening mode.

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.advertise()

The advertising data set using the previous two calls is saved. You can using BLE.stopAdvertising() to stop and BLE.advertise() to resume advertising.

// PROTOTYPE
int advertise() const;

// EXAMPLE
BLE.advertise();

Returns 0 on success or a non-zero error code.

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.stopAdvertising()

Stops advertising. You can resume advertising using BLE.advertise(). Advertising automatically stops while connected to a central device and resumes when disconnected.

// PROTOTYPE
int stopAdvertising() const;

// EXAMPLE
BLE.stopAdvertising();

BLE.advertising()

Returns true (1) if advertising is currently on or false (0) if not.

// PROTOTYPE
bool advertising() const;

BLE.getAdvertisingData()

You can get the advertising data that you previously set using getAdvertisingData(). Initially the data will be empty.

// PROTOTYPE
ssize_t getAdvertisingData(BleAdvertisingData* advertisingData) const;

See also BleAdvertisingData.

BLE.setAdvertisingData()

You can set the advertising data using setAdvertisingData. You might want to do this if you want to change the data while continuously advertising.

// PROTOTYPE
int setAdvertisingData(BleAdvertisingData* advertisingData) const;

See also BleAdvertisingData.

BLE.setAdvertisingInterval()

Sets the advertising interval. The unit is 0.625 millisecond (625 microsecond) intervals. The default value is 160, or 100 milliseconds.

The range is from 20 milliseconds (32) to 10.24 seconds (16383).

Interval Value Milliseconds Description
32 20 ms Minimum value
160 100 ms Default value
400 250 ms
800 500 ms
1600 1 sec
3200 2 sec Upper end of recommended range
16383 10.24 sec Maximum value

Returns 0 on success or a non-zero error code.

// PROTOTYPE
int setAdvertisingInterval(uint16_t interval) const;

// EXAMPLE

// Set advertising interval to 500 milliseconds
BLE.setAdvertisingInterval(800);

BLE.setAdvertisingTimeout()

Normally, advertising continues until stopAdvertising() is called or a connection is made from a central device.

You can also have advertising automatically stop after a period of time. The parameter is in units of 10 milliseconds, so if you want to advertise for 10 seconds you'd set the parameter to 1000.

Pass 0 to advertise until stopped. This is the default.

Returns 0 on success or a non-zero error code.

// PROTOTYPE
int setAdvertisingTimeout(uint16_t timeout) const;

BLE.setAdvertisingType()

The advertising type can be set with this method. This is not typically necessary.

Type Value
CONNECTABLE_SCANNABLE_UNDIRECTED 0
CONNECTABLE_UNDIRECTED 1
CONNECTABLE_DIRECTED 2
NON_CONNECTABLE_NON_SCANNABLE_UNDIRECTED 3
NON_CONNECTABLE_NON_SCANNABBLE_DIRECTED 4
SCANNABLE_UNDIRECTED 5
SCANNABLE_DIRECTED 6

The default is CONNECTABLE_SCANNABLE_UNDIRECTED (0).

// PROTOTYPE
int setAdvertisingType(BleAdvertisingEventType type) const;

See BleAdvertisingEventType for more information.

BLE.getAdvertisingParameters()

Gets the advertising parameters.

// PROTOTYPE
int getAdvertisingParameters(BleAdvertisingParams* params) const;

// EXAMPLE
BleAdvertisingParams param;
param.version = BLE_API_VERSION;
param.size = sizeof(BleAdvertisingParams);

int res = BLE.getAdvertisingParameters(&param);

See BleAdvertisingParameters for more information.

BLE.setAdvertisingParameters()

Sets the advertising parameters using individual values for interval, timeout, and type.

// PROTOTYPE
int setAdvertisingParameters(uint16_t interval, uint16_t timeout, BleAdvertisingEventType type) const;
  • interval Advertising interval in 0.625 ms units. Default is 160.
  • timeout Advertising timeout in 10 ms units. Default is 0.
  • type BleAdvertisingEventType. Default is CONNECTABLE_SCANNABLE_UNDIRECTED (0).

BLE.setAdvertisingParameters(BleAdvertisingParams)

Sets the advertising parameters from the BleAdvertisingParams struct.

// PROTOTYPE
int setAdvertisingParameters(const BleAdvertisingParams* params) const;

See BleAdvertisingParameters for more information.

BLE.setAdvertisingPhy()

Since 3.1.0:

Sets the advertising phy mode to allow for coded Phy (long range mode). Supported in Device OS 3.1 and later only. The P2 and Photon 2 do not support coded PHY mode.

// PROTOTYPE
int setAdvertisingPhy(EnumFlags<BlePhy> phy) const;

// EXAMPLE
BLE.setAdvertisingPhy(BlePhy::BLE_PHYS_CODED);

The valid values are:

  • BlePhy::BLE_PHYS_AUTO (default)
  • BlePhy::BLE_PHYS_1MBPS
  • BlePhy::BLE_PHYS_CODED (long-range)

You can only specify a single phy mode for advertising. You cannot logically OR multiple values on the advertiser as you can on the scanner.

Coded Phy mode employs redundancy and error-correction, trading off speed in favor of noise immunity. In theory it could double the range achievable, but in practice you can expect closer to a 50% increase in range.

BLE long-range (coded PHY) is not supported on the P2 and Photon 2.

For an example of using this API, see the RSSI Meter (Long Range) in the BLE tutorial.

BLE.getScanResponseData()

In addition to the advertising data, there is an additional 31 bytes of data referred to as the scan response data. This data can be requested during the scan process. It does not require connection or authentication. This is only used from scannable peripheral devices.

// PROTOTYPE
ssize_t getScanResponseData(BleAdvertisingData* scanResponse) const;

See also BleAdvertisingData.

BLE.setScanResponseData()

In addition to the advertising data, there is an additional 31 bytes of data referred to as the scan response data. This data can be requested during the scan process. It does not require connection or authentication. This is only used from scannable peripheral devices.

// PROTOTYPE
int setScanResponseData(BleAdvertisingData* scanResponse) const;

See also BleAdvertisingData.

BLE.addCharacteristic(characteristic)

Adds a characteristic to this peripheral device from a BleCharacteristic object.

// PROTOTYPE
BleCharacteristic addCharacteristic(BleCharacteristic& characteristic) const;

// EXAMPLE

// Global variables:
BleUuid serviceUuid("09b17c16-3498-4c02-beb6-3d5792528181");
BleUuid buttonCharacteristicUuid("fe0a8cd7-9f69-45c7-b7a1-3ecb0c9e97c7");
BleCharacteristic buttonCharacteristic("b", BleCharacteristicProperty::NOTIFY, buttonCharacteristicUuid, serviceUuid);

// In setup():
BLE.addCharacteristic(buttonCharacteristic);

BleAdvertisingData data;
data.appendServiceUUID(serviceUuid);
BLE.advertise(&data);

There is a limit of 20 user characteristics, with a maximum description of 20 characters.

You can have up to 20 user services, however you typically cannot advertise that many services as the advertising payload is too small. You normally only advertise your primary service that you device will searched by. Other, less commonly used services can be found after connecting to the device.

BLE.addCharacteristic(parameters)

Instead of setting the parameters in a BleCharacteristic object you can pass them directly to addCharacteristic.

The parameters are the same as the BleCharacteristic constructors and are described in more detail in the BleCharacteristic documentation.

// PROTOTYPE
BleCharacteristic addCharacteristic(const char* desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;

BleCharacteristic addCharacteristic(const String& desc, BleCharacteristicProperty properties, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;

template<typename T>
BleCharacteristic addCharacteristic(const char* desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;

template<typename T>
BleCharacteristic addCharacteristic(const String& desc, BleCharacteristicProperty properties, T charUuid, T svcUuid, BleOnDataReceivedCallback callback = nullptr, void* context = nullptr) const;

There is a limit of 20 user characteristics, with a maximum description of 20 characters.

You can have up to 20 user services, however you typically cannot advertise that many services as the advertising payload is too small. You normally only advertise your primary service that you device will searched by. Other, less commonly used services can be found after connecting to the device.

Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.

See also BleCharacteristicProperty.

BLE.scan(array)

Scan for BLE devices. This is typically used by a central device to find nearby BLE devices that can be connected to. It can also be used by an observer to find nearby beacons that continuously transmit data.

There are three overloads of BLE.scan(), however all of them are synchronous. The calls do not return until the scan is complete. The BLE.setScanTimeout() method determines how long the scan will run for.

The default is 5 seconds, however you can change it using setScanTimeout().

// PROTOTYPE
int scan(BleScanResult* results, size_t resultCount) const;

The BleScanResult is described below. It contains:

  • address The BLE address of the device
  • advertisingData The advertising data sent by the device
  • scanData The scan data (optional)
  • rssi The signal strength of the advertising message.

Prior to Device OS 3.0, these were member variables.

In Device OS 3.0 and later, they are methods, so you must access them as:

  • address() The BLE address of the device
  • advertisingData() The advertising data sent by the device
  • scanData() The scan data (optional)
  • rssi() The signal strength of the advertising message.

For example:

len = scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);

In Device OS 3.1.0 only, you may get error -270 when attempting to scan. To work around this issue, add this to setup():

#if SYSTEM_VERSION == SYSTEM_VERSION_v310
    // This is required with 3.1.0 only
    BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);
#endif

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.scan(Vector)

The Vector version of scan does not require guessing the number of scan items ahead of time. However, it does dynamically allocate memory to hold all of the scan results.

This call does not return until the scan is complete. The setScanTimeout() function determines how long the scan will run for.

The default is 5 seconds, however you can change it using setScanTimeout().

// PROTOTYPE
Vector<BleScanResult> scan() const;

// EXAMPLE - Device OS 3.0 and later
#include "Particle.h"

SYSTEM_MODE(MANUAL);

SerialLogHandler logHandler(LOG_LEVEL_TRACE);

void setup() {
    (void)logHandler; // Does nothing, just to eliminate warning for unused variable
}

void loop() {
    Vector<BleScanResult> scanResults = BLE.scan();

    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());
            }
        }
    }

    delay(3000);
}

// EXAMPLE - Device OS 2.0 and earlier
#include "Particle.h"

SYSTEM_MODE(MANUAL);

SerialLogHandler logHandler(LOG_LEVEL_TRACE);

void setup() {
    (void)logHandler; // Does nothing, just to eliminate warning for unused variable
}

void loop() {
    Vector<BleScanResult> scanResults = BLE.scan();

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

        for (int ii = 0; ii < scanResults.size(); ii++) {
            // For Device OS 2.x and earlier, use scanResults[ii].address[0], etc. without the ()
            Log.info("MAC: %02X:%02X:%02X:%02X:%02X:%02X | RSSI: %dBm",
                    scanResults[ii].address[5], scanResults[ii].address[4], scanResults[ii].address[3],
                    scanResults[ii].address[2], scanResults[ii].address[1], scanResults[ii].address[0], scanResults[ii].rssi);

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

    delay(3000);
}

The BleScanResult is described below. It contains:

  • address The BLE address of the device
  • advertisingData The advertising data sent by the device
  • scanData The scan data (optional)
  • rssi The signal strength of the advertising message.

Prior to Device OS 3.0, these were member variables.

In Device OS 3.0 and later, they are methods, so you must access them as:

  • address() The BLE address of the device
  • advertisingData() The advertising data sent by the device
  • scanData() The scan data (optional)
  • rssi() The signal strength of the advertising message.

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.scan(callback)

The callback version of scan does not return until the scan has reached the end of the scan timeout. There are still advantages of using this, however:

  • You do not need to guess how many scan results there will be ahead of time.
  • If you expect to have a lot of BLE devices in the area but only care about a few, this version can save on memory usage.
  • The callback is called as the devices are discovered - you can start to display before the timeout occurs.
  • You can stop scanning when the desired device is found instead of waiting until the timeout.

The default is 5 seconds, however you can change it using setScanTimeout().

Note: You cannot call BLE.connect() from a BLE.scan() callback! If you want to scan then connect, you must save the BleAddress and then do the connect after BLE.scan() returns.

// PROTOTYPE
int scan(BleOnScanResultCallback callback, void* context) const;
int scan(BleOnScanResultCallbackRef callback, void* context) const;

// EXAMPLE
#include "Particle.h"

SYSTEM_MODE(MANUAL);

SerialLogHandler logHandler(LOG_LEVEL_TRACE);

void scanResultCallback(const BleScanResult *scanResult, void *context);

void setup() {
    (void)logHandler; // Does nothing, just to eliminate warning for unused variable
}

void loop() {
    Log.info("starting scan");

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

    delay(3000);
}

void scanResultCallback(const BleScanResult *scanResult, void *context) {
  Log.info("MAC: %s | RSSI: %dBm", scanResult->address().toString().c_str(), scanResult->rssi());

    String name = scanResult->advertisingData().deviceName();
    if (name.length() > 0) {
        Log.info("deviceName: %s", name.c_str());
    }
}

The callback has this prototype:

void scanResultCallback(const BleScanResult *scanResult, void *context)

The BleScanResult is described below. It contains:

  • address The BLE address of the device
  • advertisingData The advertising data sent by the device
  • scanData The scan data (optional)
  • rssi The signal strength of the advertising message.

Prior to Device OS 3.0, these were member variables.

In Device OS 3.0 and later, they are methods, so you must access them as:

  • address() The BLE address of the device
  • advertisingData() The advertising data sent by the device
  • scanData() The scan data (optional)
  • rssi() The signal strength of the advertising message.

For example:

len = scanResult->advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);

The context parameter is often used if you implement your scanResultCallback in a C++ object. You can store the object instance pointer (this) in the context.

The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish() and you should not use delay(). You should beware of thread safety issues.

Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.

Since 3.0.0:

// PROTOTYPES
typedef std::function<void(const BleScanResult& result)> BleOnScanResultStdFunction;

int scan(const BleOnScanResultStdFunction& callback) const;

template<typename T>
int scan(void(T::*callback)(const BleScanResult&), T* instance) const;

In Device OS 3.0.0 and later, you can implement the BLE.scan() callback as a C++ member function or a C++11 lambda.


Since 3.0.0:

In Device OS 3.0.0 and later, it's also possible to pass to have the scan result passed by reference instead of pointer.

void scanResultCallback(const BleScanResult &scanResult, void *context)

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.scanWithFilter()

void scan() {
    BleScanFilter filter;
    filter.deviceName("MyDevice").minRssi(-50).serviceUUID(0x1234);
    Vector<BleScanResult> scanResults = BLE.scanWithFilter(filter);
}

Since 3.0.0:

You can also BLE scan with a filter with Device OS 3.0.0 and later. The result is saved in a vector.

The scanResults vector only contains the scanned devices those comply with all of the following conditions in the example above:

  • The scanned device should be broadcasting device name "MyDevice".
  • The RSSI of the scanned device should be large than -50 dBm.
  • The scanned device should be broadcasting service UUID 0x1234.

See BLEScanFilter for additional options.

BLE.stopScanning()

The stopScanning() method interrupts a BLE.scan() in progress before the end of the timeout. It's typically called from the scan callback function.

You might want to do this if you're looking for a specific device - you can stop scanning after you find it instead of waiting until the end of the scan timeout.

// PROTOTYPE
int stopScanning() const;

// EXAMPLE
void scanResultCallback(const BleScanResult *scanResult, void *context) {
    // Stop scanning if we encounter any BLE device in range
    BLE.stopScanning();
}

Scanning and advertising at the same time is not recommended. Since there is only one BLE radio, it can either transmit or receive. While it will switch between these modes if you activate both scanning and advertising at the same time, if the scanning and advertising parameters conflict, an unpredictable combination of the parameters will be used.

BLE.setScanTimeout()

Sets the length of time scan() will run for. The default is 5 seconds.

// PROTOTYPE
int setScanTimeout(uint16_t timeout) const;

// EXAMPLE

// Scan for 1 second
setScanTimeout(100);

The unit is 10 millisecond units, so the default timeout parameter is 500.

Returns 0 on success or a non-zero error code.

BLE.getScanParameters()

Gets the parameters used for scanning.

// PROTOTYPE
int getScanParameters(BleScanParams* params) const;

// EXAMPLE
BleScanParams scanParams;
scanParams.version = BLE_API_VERSION;
scanParams.size = sizeof(BleScanParams);
int res = BLE.getScanParameters(&scanParams);

See BleScanParams for more information.

BLE.setScanParameters()

Sets the parameters used for scanning. Typically you will only ever need to change the scan timeout, but if you need finer control you can use this function.

// PROTOTYPE
int setScanParameters(const BleScanParams* params) const;

See BleScanParams for more information.

BLE.setScanPhy()

Since 3.1.0:

Sets the scanning phy mode to allow for Coded Phy (long range mode). Supported in Device OS 3.1 and later only.

// PROTOTYPE
int setScanPhy(EnumFlags<BlePhy> phy) const;

// EXAMPLE
BLE.setScanPhy(BlePhy::BLE_PHYS_CODED | BlePhy::BLE_PHYS_1MBPS);

The valid values are:

  • BlePhy::BLE_PHYS_AUTO
  • BlePhy::BLE_PHYS_1MBPS
  • BlePhy::BLE_PHYS_CODED

You can logically OR the two values to scan for both phy modes. Do not combine BLE_PHYS_AUTO with other values, however.

Note: In Device OS 3.0.0 (only), a bug sets an invalid default value for the scan mode, causing no devices to be found when scanning. To work around this issue, set the scan phy mode to automatic explictly in setup():

BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);

BLE long-range (coded PHY) is not supported on the P2 and Photon 2.

For an example of using this API, see the RSSI Meter (Long Range) in the BLE tutorial.

BLE.connect()

In a central device the logic typically involves:

  • Scanning for a device
  • Selecting one, either manually from a user selection, or automatically by its capabilities.
  • Connecting to it to exchange more data (optional)

Scanning for devices provides its address as well as additional data (advertising data). With the address you can connect:

// PROTOTYPE
BlePeerDevice connect(const BleAddress& addr, bool automatic = true) const;
BlePeerDevice connect(const BleAddress& addr, const BleConnectionParams* params, bool automatic = true) const;
BlePeerDevice connect(const BleAddress& addr, uint16_t interval, uint16_t latency, uint16_t timeout, bool automatic = true) const;

This call is synchronous and will block until a connection is completed or the operation times out.

Returns a BlePeerDevice object. You typically use a construct like this:

// EXAMPLE - Device OS 3.0.0 and later:
BlePeerDevice peer = BLE.connect(scanResults[ii].address());
if (peer.connected()) {
    Log.info("successfully connected %s!", scanResults[ii].address().toString().c_str());
    // ...
}
else {
    Log.info("connection failed");
    // ...
}

// EXAMPLE - Device OS 2.x and earlier:
BlePeerDevice peer = BLE.connect(scanResults[ii].address);
if (peer.connected()) {
    Log.info("successfully connected %02X:%02X:%02X:%02X:%02X:%02X!",
                            scanResults[ii].address[5], scanResults[ii].address[4], scanResults[ii].address[3],
                            scanResults[ii].address[2], scanResults[ii].address[1], scanResults[ii].address[0]);
    // ...
}
else {
    Log.info("connection failed");
    // ...
}

It is possible to save the address and avoid scanning, however the address could change on some BLE devices, so you should be prepared to scan again if necessary.

A central device can connect to up to 3 peripheral devices at a time. (It's limited to 1 in Device OS 1.3.0.)

See the next section for the use of BleConnectionParams as well as interval, latency, and timeout.

BLE.connect(options)

This version of connect allows parameters for the connection to be set.

// PROTOTYPE
BlePeerDevice connect(const BleAddress& addr, uint16_t interval, uint16_t latency, uint16_t timeout) const;
  • addr The BleAddress to connect to.
  • interval The minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds).
  • latency Use default value 0.
  • timeout Connection timeout in units of 10 milliseconds. Default is 500 (5 seconds). Minimum value is 100 (1 second).

Note that the timeout is the timeout after the connection is established, to determine that the other side is no longer available. It does not affect the initial connection timeout.

Returns a BlePeerDevice object.

BLE.setPPCP()

Sets the connection parameter defaults so future calls to connect() without options will use these values.

// PROTOTYPE
int setPPCP(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) const;
  • minInterval the minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds).
  • maxInterval the minimum connection interval in units of 1.25 milliseconds. The default is 24 (30 milliseconds). The connect(options) call sets both minInterval and maxInterval to the interval parameter.
  • latency Use default value 0.
  • timeout Connection timeout in units of 10 milliseconds. Default is 500 (5 seconds). Minimum value is 100 (1 second).

Note that the timeout is the timeout after the connection is established, to determine that the other side is no longer available. It does not affect the initial connection timeout.

BLE.connected()

Returns true (1) if a connected to a device or false (0) if not.

// PROTOTYPE
bool connected() const;

Can be used in central or peripheral mode, however if central mode if you are supporting more than one peripheral you may want to use the connected() method of the BlePeerDevice object to find out the status of individual connections.

BLE.disconnect()

Disconnects all peers.

// PROTOTYPE
int disconnect() const;

Returns 0 on success or a non-zero error code.

BLE.disconnect() cannot be called from a BLE callback function.

BLE.disconnect(peripheral)

Typically used in central mode when making connections to multiple peripherals to disconnect a single peripheral.

// PROTOTYPE
int disconnect(const BlePeerDevice& peripheral) const;

The BlePeerDevice is described below. You typically get it from BLE.connect().

Returns 0 on success or a non-zero error code.

BLE.disconnect() cannot be called from a BLE callback function.

BLE.on()

Turns the BLE radio on. It defaults to on in SYSTEM_MODE(AUTOMATIC). In SYSTEM_MODE(SEMI_AUTOMATIC) or SYSTEM_MODE(MANUAL) you must turn on BLE.

// PROTOTYPE
int on();

// EXAMPLE
void setup() {
  BLE.on();
}

Returns 0 on success, or a non-zero error code.

BLE.off()

Turns the BLE radio off. You normally do not need to do this.

// PROTOTYPE
int off();

Returns 0 on success, or a non-zero error code.

BLE.begin()

Starts the BLE service. It defaults to started, so you normally do not need to call this unless you have stopped it.

// PROTOTYPE
int begin();

Returns 0 on success, or a non-zero error code.

BLE.end()

Stops the BLE service. You normally do not need to do this.

// PROTOTYPE
int end();

Returns 0 on success, or a non-zero error code.

BLE.onConnected()

Registers a callback function that is called when a connection is established. This is only called for peripheral devices. On central devices, BLE.connect() is synchronous.

You can use this method, or you can simply monitor BLE.connected() (for peripheral devices) or peer.connected() for central devices.

// PROTOTYPE
void onConnected(BleOnConnectedCallback callback, void* context);

The prototype for the callback function is:

void callback(const BlePeerDevice& peer, void* context);

The callback parameters are:

  • peer The BlePeerDevice object that has connected.
  • context The value you passed to onConnected when you registered the connected callback.

The callback is called from the BLE thread. It has a smaller stack than the normal loop stack, and you should avoid doing any lengthy operations that block from the callback. For example, you should not try to use functions like Particle.publish() and you should not use delay(). You should beware of thread safety issues.

Avoid using logging calls like Log.info from the BLE callbacks. While it normally works, as BLE handlers run from a worker thread, if the thread is swapped in during an existing log from the system, user, or timer threads, the resulting lock can delay BLE operations long enough for operations to fail.

Since 3.0.0:

// PROTOTYPES
typedef std::function<void(const BlePeerDevice& peer)> BleOnConnectedStdFunction;

void onConnected(const BleOnConnectedStdFunction& callback) const;

template<typename T>
void onConnected(void(T::*callback)(const BlePeerDevice& peer), T* instance) const;

in Device OS 3.0.0 and later, the onConnected() callback can be a C++ class member function or a C++11 lambda.

BLE.onDisconnected()

Registers a callback function that is called when a connection is disconnected on a peripheral device.

You can use this method, or you can simply monitor BLE.connected() (for peripheral devices) or peer.connected() for central devices.

// PROTOTYPE
void onDisconnected(BleOnDisconnectedCallback callback, void* context);
`

The prototype for the callback function is:

void callback(const BlePeerDevice& peer, void* context);

The callback parameters are the same as for onConnected().

Since 3.0.0:

// PROTOTYPES
typedef std::function<void(const BlePeerDevice& peer)> BleOnDisconnectedStdFunction;

void onDisconnected(const BleOnDisconnectedStdFunction& callback) const;

template<typename T>
void onDisconnected(void(T::*callback)(const BlePeerDevice& peer), T* instance) const;

in Device OS 3.0.0 and later, the onDisconnected() callback can be a C++ class member function or a C++11 lambda.

BLE.setTxPower()

Sets the BLE transmit power. The default is 0 dBm.

Valid values are: -20, -16, -12, -8, -4, 0, 4, 8.

-20 is the lowest power, and 8 is the highest power.

Returns 0 on success or a non-zero error code.

// PROTOTYPE
int setTxPower(int8_t txPower) const;

// EXAMPLE
BLE.setTxPower(-12); // Use lower power

Returns 0 on success or a non-zero error code.

BLE.txPower()

Gets the current txPower. The default is 0.

Returns 0 on success or a non-zero error code.

// PROTOTYPE
int txPower(int8_t* txPower) const;

// EXAMPLE
int8_t txPower;
txPower = BLE.tx(&txPower);
Log.info("txPower=%d", txPower);

BLE.address()

Get the BLE address of this device.

// PROTOTYPE
const BleAddress address() const;

See BleAddress for more information.

BLE.selectAntenna()

Since 1.3.1:

Selects which antenna is used by the BLE radio stack. This is a persistent setting.

Note: B-Series SoM devices do not have an internal (chip) antenna and require an external antenna to use BLE. It's not necessary to select the external antenna on the B-Series SoM as there is no internal option.

On the P2 and Photon 2, this method sets the antenna used for both BLE and Wi-Fi.

// Select the internal antenna
BLE.selectAntenna(BleAntennaType::INTERNAL);
// Select the external antenna
BLE.selectAntenna(BleAntennaType::EXTERNAL);

If you get this error when using BleAntennaType::EXTERNAL:

Error: .../wiring/inc/spark_wiring_arduino_constants.h:44:21: expected unqualified-id before numeric constant

After all of your #include statements at the top of your source file, add:

#undef EXTERNAL

This occurs because the Arduino compatibility modules has a #define for EXTERNAL which breaks the BleAntennaType enumeration. This will only be necessary if a library you are using enables Arduino compatibility.

BLE.setPairingIoCaps()

// PROTOTYPE
int setPairingIoCaps(BlePairingIoCaps ioCaps) const;

Since 3.0.0:

Sets the capabilities of this side of the BLE connection.

Value Description
BlePairingIoCaps::NONE This side has no display or keyboard
BlePairingIoCaps::DISPLAY_ONLY Has a display for the 6-digit passcode
BlePairingIoCaps::DISPLAY_YESNO Display and a yes-no button
BlePairingIoCaps::KEYBOARD_ONLY Keyboard or numeric keypad only
BlePairingIoCaps::KEYBOARD_DISPLAY Display and a keyboard (or a touchscreen)

Ideally you want one side to have a keyboard and one side to have a display. This allows for an authenticated connection to assure that both sides the device they say they are. This prevents man-in-the-middle attack ("MITM") where a rogue device could pretend to be the device you are trying to pair with.

Even if neither side has a display or keyboard, the connection can still be paired and encrypted, but there will be no authentication.

For more information about pairing, see BLE pairing.

BLE.setPairingAlgorithm()

// PROTOTYPE
int setPairingAlgorithm(BlePairingAlgorithm algorithm) const;

Since 3.0.0:

Value Description
BlePairingAlgorithm::AUTO Automatic selection
BlePairingAlgorithm::LEGACY_ONLY Legacy Pairing mode only
BlePairingAlgorithm::LESC_ONLY Bluetooth LE Secure Connection Pairing (LESC) only
  • LESC pairing is supported in Device OS 3.1.0 and later on the Boron, B-Series SoM, Argon, and Tracker SoM (nRF52840).
  • LESC pairing is supported in Device OS 5.1.0 and later on the P2 and Photon 2 (RTL872x).

BLE.startPairing()

// PROTOTYPE
int startPairing(const BlePeerDevice& peer) const;

Since 3.0.0:

When using BLE pairing, one side is the initiator. It's usually the BLE central device, but either side can initiate pairing if the other side is able to accept the request to pair. See BLE.onPairingEvent(), below, for how to respond to a request.

The results is 0 (SYSTEM_ERROR_NONE) on success, or a non-zero error code on failure.

For more information about pairing, see BLE pairing.

BLE.rejectPairing()

// PROTOTYPE
int rejectPairing(const BlePeerDevice& peer) const;

Since 3.0.0:

If you are the pairing responder (typically the BLE peripheral, but could be either side) and you do not support being the responder, call BLE.rejectPairing() from your BLE.onPairingEvent() handler.

The results is 0 (SYSTEM_ERROR_NONE) on success, or a non-zero error code on failure.

BLE.setPairingNumericComparison()

// PROTOTYPE
int setPairingNumericComparison(const BlePeerDevice& peer, bool equal) const;

Since 3.0.0:

This is used with BlePairingEventType::NUMERIC_COMPARISON to confirm that two LESC passcodes are identical. This requires a keypad or a yes-no button.

The results is 0 (SYSTEM_ERROR_NONE) on success, or a non-zero error code on failure.

  • LESC pairing is supported in Device OS 3.1.0 and later on the Boron, B-Series SoM, Argon, and Tracker SoM (nRF52840).
  • LESC pairing is supported in Device OS 5.1.0 and later on the P2 and Photon 2 (RTL872x).

BLE.setPairingPasskey()

// PROTOTYPE
int setPairingPasskey(const BlePeerDevice& peer, const uint8_t* passkey) const;

// EXAMPLE
BLE.setPairingPasskey(peer, (const uint8_t *)"123456");

Since 3.0.0:

When you have a keyboard and the other side has a display, you may need to prompt the user to enter a passkey on your keyboard. This is done by the BlePairingEventType::PASSKEY_INPUT event. Once they have entered it, call this function to set the code that they entered.

The passkey is BLE_PAIRING_PASSKEY_LEN bytes long (6). The passkey parameter does not need to be null terminated.

Since the parameter is not a null terminated c-string, it's declared as const uint8_t * instead of const char * but you can pass a c-string of 6 ASCII digits by casting it to a const uint8_t *.

The results is 0 (SYSTEM_ERROR_NONE) on success, or a non-zero error code on failure.

BLE.isPairing()

// PROTOTYPE
bool isPairing(const BlePeerDevice& peer) const;

Since 3.0.0:

Returns true if the pairing negotiation is still in progress. This is several steps but is asynchrnouous.

BLE.isPaired()

// PROTOTYPE
bool isPaired(const BlePeerDevice& peer) const;

Since 3.0.0:

Returns true if pairing has been successfully completed.

BLE.onPairingEvent()

// PROTOTYPES
void onPairingEvent(BleOnPairingEventCallback callback, void* context = nullptr) const;
void onPairingEvent(const BleOnPairingEventStdFunction& callback) const;

template<typename T>
void onPairingEvent(void(T::*callback)(const BlePairingEvent& event), T* instance) const {
    return onPairingEvent((callback && instance) ? std::bind(callback, instance, _1) : (BleOnPairingEventStdFunction)nullptr);
}

// BleOnPairingEventCallback declaration
typedef void (*BleOnPairingEventCallback)(const BlePairingEvent& event, void* context);

// BleOnPairingEventCallback example function
void myPairingEventCallback(const BlePairingEvent& event, void* context);

// BleOnPairingEventStdFunction declaration
typedef std::function<void(const BlePairingEvent& event)> BleOnPairingEventStdFunction;

Since 3.0.0:

For more information about pairing, see BLE pairing.

BlePairingEventType::REQUEST_RECEIVED

Informational event that indicates that pairing was requested by the other side.

event.peer is the BLE peer that requested the pairing.

If you are not able to respond to pairing requests, you should call BLE.rejectPairing().

BlePairingEventType::PASSKEY_DISPLAY

If you have specified that you have a display, the negotiation process may require that you show a passkey on your display. This is indicated to your code by this event. The passkey is specified by the other side; you just need to display it. It's in the event payload:

  • event.payload.passkey is a character array of passcode characters
  • BLE_PAIRING_PASSKEY_LEN is the length in characters. Currently 6.
// Example using USB serial as the display and keyboard
if (event.type == BlePairingEventType::PASSKEY_DISPLAY) {
  Serial.print("Passkey display: ");
  for (uint8_t i = 0; i < BLE_PAIRING_PASSKEY_LEN; i++) {
      Serial.printf("%c", event.payload.passkey[i]);
  }
  Serial.println("");
}

Note that event.payload.passkey is not a null terminated string, so you can't just print it as a c-string. It's a fixed-length array of bytes of ASCII digits (0x30 to 0x39, inclusive).

BlePairingEventType::PASSKEY_INPUT

If you specified that you have a keyboard, the negotiation process may require that the user enter the passcode that's displayed on the other side into the keyboard or keypad. When this is required, this event is passed to your callback.

// Example using USB serial as the display and keyboard
if (event.type == BlePairingEventType::PASSKEY_INPUT) {
  Serial.print("Passkey input: ");
  uint8_t i = 0;
  uint8_t passkey[BLE_PAIRING_PASSKEY_LEN];
  while (i < BLE_PAIRING_PASSKEY_LEN) {
      if (Serial.available()) {
          passkey[i] = Serial.read();
          Serial.write(passkey[i++]);
      }
  }
  Serial.println("");

  BLE.setPairingPasskey(event.peer, passkey);
}

BlePairingEventType::STATUS_UPDATED

The pairing status was updated. These fields may be of interest:

  • event.payload.status.status
  • event.payload.status.bonded
  • event.payload.status.lesc

LESC pairing is supported in Device OS 3.1 and later only.

BlePairingEventType::NUMERIC_COMPARISON

Numeric comparison mode is being used to pair devices in LESC pairing mode. You should display the passkey in the same was as BlePairingEventType::PASSKEY_DISPLAY.

  • LESC pairing is supported in Device OS 3.1.0 and later on the Boron, B-Series SoM, Argon, and Tracker SoM (nRF52840).
  • LESC pairing is supported in Device OS 5.1.0 and later on the P2 and Photon 2 (RTL872x).
BLEPairingEvent
struct BlePairingEvent {
    BlePeerDevice& peer;
    BlePairingEventType type;
    size_t payloadLen;
    BlePairingEventPayload payload;
};

The structure passed to the callback has these elements. The payload varies depending on the event type. For example, for the BlePairingEventType::PASSKEY_DISPLAY event, the payload is the passkey to display on your display.

BlePairingEventPayload

The payload is either the passkey, or a status:

union BlePairingEventPayload {
    const uint8_t* passkey;
    BlePairingStatus status;
};
BlePairingStatus

Fields of the payload used for BlePairingEventType::STATUS_UPDATED events.

struct BlePairingStatus {
    int status;
    bool bonded;
    bool lesc;
};

BLE.setProvisioningSvcUuid

Since 3.3.0:

Sets the provisioning service UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.

You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.

The T parameter is any type that can be passed to the BleUuid constructor. It's typically a UUID string but other formats are supported.

You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true).

// PROTOTYPE
template<typename T>
int setProvisioningSvcUuid(T svcUuid) const

// EXAMPLE
BLE.setProvisioningSvcUuid("6E400021-B5A3-F393-E0A9-E50E24DCCA9E");

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.setProvisioningTxUuid

Since 3.3.0:

Sets the provisioning service transmit characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.

You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.

The T parameter is any type that can be passed to the BleUuid constructor. It's typically a UUID string but other formats are supported.

You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true).

// PROTOTYPE
template<typename T>
int setProvisioningTxUuid(T svcUuid) const

// EXAMPLE
BLE.setProvisioningTxUuid("6E400022-B5A3-F393-E0A9-E50E24DCCA9E");

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.setProvisioningRxUuid

Since 3.3.0:

Sets the provisioning service receive characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.

You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.

The T parameter is any type that can be passed to the BleUuid constructor. It's typically a UUID string but other formats are supported.

You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true).

// PROTOTYPE
template<typename T>
int setProvisioningRxUuid(T svcUuid) const

// EXAMPLE
BLE.setProvisioningRxUuid("6E400023-B5A3-F393-E0A9-E50E24DCCA9E");

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.setProvisioningVerUuid

Since 3.3.0:

Sets the provisioning service version characteristic UUID. This is typically only done when using BLE provisioning mode with a product. This allows your mobile app to only find Particle devices running your product firmware and not random Particle developer kits. Conversely, changing the service UUIDs will make your device invisible to the Particle mobile app or other vendor's custom mobile apps.

You should generate your own unique UUID when using this method. There is no need to register these, as they are sufficiently long to guarantee that every UUID is unique.

The T parameter is any type that can be passed to the BleUuid constructor. It's typically a UUID string but other formats are supported.

You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true).

// PROTOTYPE
template<typename T>
int setProvisioningVerUuid(T svcUuid) const

// EXAMPLE
BLE.setProvisioningVerUuid("6E400024-B5A3-F393-E0A9-E50E24DCCA9E");

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.setDeviceName

Since 3.3.0:

Sets the provisioning mode BLE device name. This is typically only done when using BLE provisioning mode with a product. This will likely be displayed in the user interface of your mobile app, so you may want to replace the default of "ARGON" or "P2".

Note that your mobile app will typically filter on the service UUID, not the name.

You must call this before BLE setup is started. For listening mode, this is often in STARTUP(), however when using FEATURE_DISABLE_LISTENING_MODE and BLE provisioning mode, this can be in setup() before calling BLE.provisioningMode(true).

The maximum name length is BLE_MAX_DEV_NAME_LEN, or 20 ASCII characters. In practice, however, this must must be 14 or fewer characters because it will not fit in the default advertising packet (which is limited to 31 bytes) if longer.

// PROTOTYPES
int setDeviceName(const char* name, size_t len) const;
int setDeviceName(const char* name) const;
int setDeviceName(const String& name) const;

// EXAMPLE
BLE.setDeviceName("Acme Sensor"); 

BLE.getDeviceName

Since 3.3.0:

Gets the device name that is displayed when selecting a device for BLE provisioning.

// PROTOTYPES
ssize_t getDeviceName(char* name, size_t len) const;
String getDeviceName() const;

BLE.setProvisioningCompanyId

Since 3.3.0:

Sets the provisioning mode BLE company code. If not set, the Particle company ID (0x0662) is used.

The company ID is a 16-bit integer and values are assigned by the Bluetooth SIG. The value not typically prominently displayed and you will typically filter on the service UUID, not the company name, so you can skip setting this, if desired.

// PROTOTYPE
int setProvisioningCompanyId(uint16_t companyId);

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.provisioningMode

Since 3.3.0:

BLE provisioning mode is an alternative to listening mode which allows customization of the user experience. For example:

  • You will typically use custom service UUIDs, so your mobile app will only see devices with your firmware, not other Particle developer kits.
  • Likewise, other products' mobile apps won't see your devices.
  • Customizable device names and company ID.

Unlike listening mode (blinking dark blue), BLE provisioning mode:

  • Can run concurrently with your user firmware, allowing your mobile app to communicate directly with the device over BLE without putting the device in a specific mode.
  • This also means that Wi-Fi credentials can be reconfigured without requiring an external button.
  • Does not interrupt the cloud connection if used after connecting to the cloud.
// PROTOTYPE
int provisioningMode(bool enabled) const;

// EXAMPLE
BLE.provisioningMode(true);

Be sure to set all desired parameters, such as service UUIDs before enabling provisioning mode.

Since listening mode and BLE provisioning mode are mutually exclusive and listening mode takes precedence, if you are using BLE provisioning mode you will typically want to disable listening mode:

// Disable listening mode when using BLE provisioning mode
STARTUP(System.enableFeature(FEATURE_DISABLE_LISTENING_MODE));

Since BLE provisioning mode can run concurrently with your firmware while cloud connected, you do not need to disable it after setting Wi-Fi credentials. This also means your end-users will be able to reconfigure Wi-Fi credentials without having to press the MODE button, which may eliminate the need for an external button in your product.

See Wi-Fi setup options for more information about BLE provisioning mode.

BLE.getProvisioningStatus

Since 3.3.0:

Returns true if BLE provisioning mode is enabled.

bool getProvisioningStatus() const;

See Wi-Fi setup options for more information about BLE provisioning mode.