Pushing BME280 Sensor Readings between 2 Argons via BLE

Hi all,

I have been tinkering with wireless communication between 2 Argons via BLE for quite a while now. My idea is to read information from the sensor (on the peripheral node) and transmit it to the central node which will then serial print the sensor reading. Have gone through numerous forum threads, studied the BLE tutorials extensively and tried to replicate them; but to no avail.

Most importantly is how to get the central node to scan and connect with the peripheral? (this is assuming that I have correctly attached the sensor values onto the advertised data). Another issue is that I cant seem to see any sensor values on nRF Connect (apple ios)… Hereby tapping into the expertise of the Particle community to assist me on this!

Sharing the codes for the central and the peripheral nodes below.

Peripheral Node

#include "Particle.h"
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

const size_t UART_TX_BUF_SIZE = 20;
const unsigned long UPDATE_INTERVAL = 2000;
unsigned long lastUpdate = 0;

#define SEALEVELPRESSURE_HPA (1013.25)
#define BME_ADDRESS 0x77
Adafruit_BME280 bme;

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);
void setAdvertisingData();

float temp;

// These UUIDs were defined by Nordic Semiconductor and are now the defacto standard for
// UART-like services over BLE. Many apps support the UUIDs now, like the Adafruit Bluefruit app.
const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

BleCharacteristic txCharacteristic("tx", BleCharacteristicProperty::NOTIFY, txUuid, serviceUuid);
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::WRITE_WO_RSP, rxUuid, serviceUuid, onDataReceived, NULL);

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

void setup() 
{
    Wire.begin();
	Serial.begin();
	pinMode(D7,OUTPUT);
	while (!bme.begin())
  	{
   		delay(500);
   		Serial.println("Trying to connect BME280 Sensor");
  	}
	
	BLE.on();
	BLE.setTxPower(8);
    BLE.addCharacteristic(txCharacteristic);
    BLE.addCharacteristic(rxCharacteristic);

    BleAdvertisingData data;
    data.appendServiceUUID(serviceUuid);
	setAdvertisingData();
	BLE.setAdvertisingInterval(130);  
}

void loop()
{
	unsigned long currentMillis = millis();
	if (currentMillis - lastUpdate >= UPDATE_INTERVAL)
	{ 
		lastUpdate = millis();
		temp = bme.readTemperature();
		setAdvertisingData();
	}

}
//adapted from https://community.particle.io/t/need-help-sending-ble-uart-broadcast/50806
struct customData 
{
  uint16_t companyID;
  char     data[25];
};

void setAdvertisingData()
{
  int retVal = 0;
  customData cd;
  BleAdvertisingData advData;
  cd.companyID = 0xFFFF;                            // undefined company ID for custom data
  memset(&cd.data, 0x00, sizeof(cd.data));
  retVal = snprintf(cd.data, sizeof(cd.data)-1, "%.2f *C", temp);
  Serial.printlnf("%s (%d)", cd.data, strlen(cd.data));
  advData.appendCustomData((uint8_t*)&cd, 2 + strlen(cd.data));
  BLE.advertise(&advData); 
}

Central Node

#include "Particle.h"


SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);


const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); 

const size_t UART_TX_BUF_SIZE = 20;
const size_t SCAN_RESULT_COUNT = 20;

BleScanResult scanResults[SCAN_RESULT_COUNT];

BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;

uint8_t txBuf[UART_TX_BUF_SIZE];
size_t txLen = 0;
float temp = 0;

const unsigned long SCAN_PERIOD_MS = 2000;
unsigned long lastScan = 0;

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

void setup()
{
  Serial.begin();
	BLE.on();
  BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);
  peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
}

void loop() 
{
  Serial.println("Scanning for Peripheral Device");
  if (millis() - lastScan >= SCAN_PERIOD_MS) 
  {
    // Time to scan
    lastScan = millis();
    size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
		if (count > 0) 
    {
			for (uint8_t ii = 0; ii < count; ii++) 
      {
				BleUuid foundServiceUuid;
				size_t svcCount = scanResults[ii].advertisingData().serviceUUID(&foundServiceUuid, 1);
				if (svcCount > 0 && foundServiceUuid == serviceUuid) 
          {
						peer = BLE.connect(scanResults[ii].address());
						if (peer.connected()) 
            {
							peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
							peer.getCharacteristicByUUID(peerRxCharacteristic, rxUuid);
              peerTxCharacteristic.getValue(&temp); 
              Serial.printlnf("Temp %.2f °C", temp);
						}
						break;
					}
			}
		}
  }
}

Some threads that I referenced: Link 1, Link 2, Link 3

Appreciate all the help in advance!

Cheers

In your peripheral code you are defining and setting data in setup() but that variable is never used to be advertised.
Hence it’s no surprise that you cannot find any device exposing your service UUID.

You also need to consider that the room for advertising data is quite limited.

Hi @ScruffR,

Thanks for your reply. Based on your comments I have relooked at my codes and its logical and compiles. But, I still cant see it on my IOS nRF Connect App. Which mean I havent got it to work. My updated peripheral codes below. Appreciate any pointers to guide me in the right direction!

#include "Particle.h"
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include <Wire.h>

SYSTEM_MODE(MANUAL);
SYSTEM_THREAD(ENABLED);

const size_t UART_TX_BUF_SIZE = 20;
const unsigned long UPDATE_INTERVAL = 2000;
unsigned long lastUpdate = 0;

#define SEALEVELPRESSURE_HPA (1013.25)
#define BME_ADDRESS 0x77
Adafruit_BME280 bme;

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);
void setAdvertisingData();

float temp;

// These UUIDs were defined by Nordic Semiconductor and are now the defacto standard for
// UART-like services over BLE. Many apps support the UUIDs now, like the Adafruit Bluefruit app.
const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

BleCharacteristic txCharacteristic("tx", BleCharacteristicProperty::NOTIFY, txUuid, serviceUuid);
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::WRITE_WO_RSP, rxUuid, serviceUuid, onDataReceived, NULL);

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

void setup() 
{
  Wire.begin();
	Serial.begin();
	pinMode(D7,OUTPUT);
	while (!bme.begin())
  	{
   		delay(500);
   		Serial.println("Trying to connect BME280 Sensor");
  	}
	
	BLE.on();
	BLE.setTxPower(8);
  	BLE.addCharacteristic(rxCharacteristic);
	setAdvertisingData();
	BLE.setAdvertisingInterval(130);  
}

void loop()
{
	unsigned long currentMillis = millis();
	if (currentMillis - lastUpdate >= UPDATE_INTERVAL)
	{ 
		lastUpdate = millis();
		temp = bme.readTemperature();
		setAdvertisingData();
	}

}
//adapted from https://community.particle.io/t/need-help-sending-ble-uart-broadcast/50806
struct customData 
{
  uint16_t companyID;
  char     data[25];
};

void setAdvertisingData()
{
  int retVal = 0;
  customData cd;
  BleAdvertisingData advData;
  BLE.addCharacteristic(txCharacteristic);
  advData.appendServiceUUID(serviceUuid);
  cd.companyID = 0xFFFF;                            // undefined company ID for custom data
  memset(&cd.data, 0x00, sizeof(cd.data));
  retVal = snprintf(cd.data, sizeof(cd.data)-1, "%.2f *C", temp);
  Serial.printlnf("%s (%d)", cd.data, strlen(cd.data));
  advData.appendCustomData((uint8_t*)&cd, 2 + strlen(cd.data));
  BLE.advertise(&advData); 
}

My immediate attention will be to transfer data between 2 Argons, while the constraints of the advertising data can be dealt with the next time. I have seen some of your recommendations in other threads of dropping the units and sending raw data in terms of uint_8 etc.

I have stripped down your code to remove anything to do with the BME280, and reduced the length of the custom data portion to fit into the advertising data and can see the device, service and data just fine with Android nRF Connect.

Could it be that your iOS nRF Connect doesn’t show unnamed devices?

1 Like

My Argon do show up on the nRF Connect, but cant seem to see the advertised values even with the example from another thread
From Need help sending BLE UART broadcast

#include "Particle.h"

void setAdvertisingData();

float   volt;
float   amp;
float   watt;
float   temp;
uint8_t percent;

void setup() {
  setAdvertisingData();
  BLE.setAdvertisingInterval(130);                  // Advertise every 100 milliseconds. Unit is 0.625 millisecond intervals.
}

void loop() {
  static uint32_t ms = 0;
  
  if (!digitalRead(BTN))                            // use MODE button to reset the dummy data
    ms = 
    percent = 
    volt = 
    amp = 
    watt = 
    temp = 0;
  
  if (millis() - ms < 1000) return;
  ms = millis();
  
  if (percent < 100) percent += 5;
  volt    += 1.5;
  amp     += 2.5;
  watt     = volt * amp;
  temp    += 0.5;
  
  setAdvertisingData();
}

struct customData {
  uint16_t companyID;
  char     data[25];
};

void setAdvertisingData() {
  int                retVal = 0;
  customData         cd;
  BleAdvertisingData advData;

  cd.companyID = 0xFFFF;                            // undefined company ID for custom data

  memset(&cd.data, 0x00, sizeof(cd.data));
  retVal = snprintf(cd.data, sizeof(cd.data)-1, "%.1fV%.1fA%.1fW%.1fF%u", volt, amp, watt, temp, percent);
  Serial.printlnf("%s (%d)", cd.data, strlen(cd.data));
  if (retVal < 0 || sizeof(cd.data)-1 < retVal) {
    strcpy(cd.data, "too long! press MODE");
  }
  advData.appendCustomData((uint8_t*)&cd, 2 + strlen(cd.data));

  BLE.advertise(&advData);
}

Not gonna discount the possibility that the issue lies with iOS nRF Connect. But I do not have an Android device available. Ideally, I should advertise and receive on another Argon that can can serial print out the received payload.

I’ll have a look what I can do for you.

Update:

You can try this

SYSTEM_THREAD(ENABLED);

// activate the desired role by commenting/uncommenting accordingly
#define CENTRAL   
//#define PERIPHERAL

const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

const size_t UART_TX_BUF_SIZE = 64;
const unsigned long MS_PERIOD = 2000;

struct customData {
  uint16_t companyID;
  int16_t  data;
};

uint8_t txBuf[UART_TX_BUF_SIZE];

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);

#if defined(CENTRAL)
const size_t SCAN_RESULT_COUNT = 10;

bool canDisconnect = false;

BleScanResult scanResults[SCAN_RESULT_COUNT];

BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;

void setup() {
  BLE.on();
  BLE.setScanPhy(BlePhy::BLE_PHYS_AUTO);
  peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
}

void loop() {
  static uint32_t msLast = 0;
  if (millis() - msLast < MS_PERIOD) return;
  msLast = millis();
  
  size_t count = BLE.scanWithFilter(BleScanFilter().serviceUUID(serviceUuid), scanResults, SCAN_RESULT_COUNT);
  Serial.printlnf("Found %d device(s) exposing service %s", count, (const char*)serviceUuid.toString());
  for (uint8_t ii = 0; ii < count; ii++) {
    customData cd;
    scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, (uint8_t*)&cd, sizeof(cd));
    Serial.printlnf("Device %s advertises %04X: %u", (const char*)scanResults[ii].address().toString(), cd.companyID, cd.data);
    peer = BLE.connect(scanResults[ii].address());
    if (peer.connected()) {
      peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
      //peerTxCharacteristic.getValue(txBuf, sizeof(txBuf)); 
      //Serial.printlnf("txChar: %s\r\n", (char*)txBuf);
      while(!canDisconnect) Particle.process();      
      peer.disconnect();
      canDisconnect = false;;
    }
  }
  Serial.println();
}

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
  Serial.printf("receive data from %s: ", (const char*)peer.address().toString());
  for (size_t ii = 0; ii < len; ii++) {
    Serial.write(data[ii]);
  }
  Serial.println();
  canDisconnect = true;
}

// --------------------------------------------------------------------------------------------------------------------------------
#elif defined(PERIPHERAL)

void setAdvertisingData();

BleCharacteristic txCharacteristic("tx", BleCharacteristicProperty::NOTIFY, txUuid, serviceUuid);
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::WRITE_WO_RSP, rxUuid, serviceUuid, onDataReceived, NULL);

void setup() {
  BLE.on();
  BLE.addCharacteristic(rxCharacteristic);
  BLE.addCharacteristic(txCharacteristic);
  BLE.setTxPower(8);
}

void loop() {
  static uint32_t msLast = 0;
  if (millis() - msLast < MS_PERIOD) return;
  msLast = millis();
  
  float temp = random(100) / 10.0;
  setAdvertisingData(temp * 10.0);
  snprintf((char*)txBuf, sizeof(txBuf), "{ \"temp\":\"%.1f °C\" }", temp);
  //if (BLE.connected())
    txCharacteristic.setValue(txBuf, strlen((char*)txBuf)+1);
}

void setAdvertisingData(int16_t data) {
  customData cd;
  BleAdvertisingData advData;
  cd.companyID = 0xFFFF;
  cd.data = data;                                           
  advData.appendLocalName("BT");                            // no more than two characters  
  advData.appendCustomData((uint8_t*)&cd, sizeof(cd));      // otherwise 4 byte custom data won't fit
  advData.appendServiceUUID(serviceUuid);                   // alongside the name and service UUID
  BLE.advertise(&advData); 
}

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

#endif

Due to the lack of a BME280 this code just uses random numbers on the peripheral side.

Season greetings to everyone!

Thanks ScruffR. I finally got what you meant in your first reply in the thread.

Some food for thought and questions though…

  1. Still unable to see the advertised data on iOS nRF Connect and LightBlue applications. On the other hand, randomly chanced upon nRF ToolBox iOS application, and able to view the advertised data in the UART Service.

  2. Noted that there is a limitation of 31 bytes of advertising data in OS < 3.2.0. Perhaps we can take advantage of BLE.setAdvertisingPhy(BlePhy::BLE_PHYS_CODED) to increase the data to 255bytes. Are there any downsides other than the transmission data rate? Especially in the part of the energy contribution. Do we also need to change the scanning Phy (on Central) or can the Auto Phy scanning handle this?

  3. In the Peripheral section, `

float temp = random(100) / 10.0;
setAdvertisingData(temp * 10.0);`

I am quite confused on the purpose of the division and multiplication? The setting of value is based off the random float temp divided by 10. The return value of cd.data is throwing me off too. Shouldnt it return the same value as the random temp?

For the completeness of this exercise, this is a screenshot from the serial monitor of the central node. I changed the random value to be between 7 and 13 to illustrate my doubts.

image

returned value of cd.data varies wildly. In fact, due to the multiplication of 10, this value is 1 order of magnitude bigger. Based on my understanding it should be the same value as the the temp that was fed into void setAdvertisingData(int16_t data).

Since your sensor will give you a float but random() returns an int the division by 10.0 does the trick to produce a float from that int.
So with an actual source giving you a float that line can be ignored.

However, in order to save space I went with a int16_t to save two bytes over the use of float. To keep the 0.1°C accuracy I multiply the float value which would give you a possible value range between -3276.8 and +3276.7 or even two decimal places at approximately +/- 300.00 °C when multiplying by 100.

You do get the "same" value, but due to the time when the data is updated you will see the respective value in the advertisement data in the next block of data.
In a real live scenario this time deviation between the two transmission routes would be less relevant.
I'd suspect you would mostly listen for the advertising data and only sporadically actually connect (if at all).

It is, it is (reasoning above :wink: )

I have changed the temp variable to read off the sensor and performed as per intended.

Ahh.. making sense there. Its to minimize the data size as well as preserving the resolution of the value.

Im glad you brought up the topic of time, does it mean if I reduce the MS_PERIOD to a smaller period, the cd.data value will be the same as the one in the curly bracket? (Since the time deviation is minimal). I tried and this is not the case. It still varies as discussed above.

When you look at the sequence of events it should become clear why this is and that this won't change even with a shorter update period :wink:

The value gets set for the characteristic and the advertising data at the same time.
Since the central will connect with the same cadence as the peripheral exposes the data the central will typically be connected and "wait" for the data to arrive via the onDataReceived() handler before the updated data gets advertised. Only after the central disconnects from the peripheral the updated data can be advertised. By that time the central already is ready to connect again and "wait" for the next reading.

That's the "problem" with a simple test setup - you need to be aware of its limitations and put them in context with your actual use case.
Sure, I could have come up with a more life-like test setup but that would have added more complexity making it even more difficult to understand :wink:


Here is a version that employs BLE_PHYS_CODED to advertise more data.
However, not all mobile phones will be able to find that peripheral with nRF Connect.

SYSTEM_THREAD(ENABLED);

// activate the desired role by commenting/uncommenting accordingly
#define CENTRAL   
#define PERIPHERAL

const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");

const size_t UART_TX_BUF_SIZE = 64;
const unsigned long MS_PERIOD = 5000;

struct customData {
  uint16_t companyID;
  char     data[UART_TX_BUF_SIZE];
};

uint8_t txBuf[UART_TX_BUF_SIZE];

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);

#if defined(CENTRAL)
const size_t SCAN_RESULT_COUNT = 10;

bool canDisconnect = false;

BleScanResult scanResults[SCAN_RESULT_COUNT];

BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;

void setup() {
  BLE.on();
  BLE.setScanPhy(BlePhy::BLE_PHYS_CODED);
  peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
}

void loop() {
  static uint32_t msLast = 0;
  if (millis() - msLast < MS_PERIOD) return;
  msLast = millis();
  
  size_t count = BLE.scanWithFilter(BleScanFilter().serviceUUID(serviceUuid), scanResults, SCAN_RESULT_COUNT);
  Serial.printlnf("Found %d device(s) exposing service %s", count, (const char*)serviceUuid.toString());
  for (uint8_t ii = 0; ii < count; ii++) {
    customData cd;
    scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, (uint8_t*)&cd, sizeof(cd));
    Serial.printlnf("Device %s (%s) advertises %04X: %s\r\n"
                   , (const char*)scanResults[ii].advertisingData().deviceName()
                   , (const char*)scanResults[ii].address().toString()
                   , cd.companyID
                   , cd.data
                   );
    peer = BLE.connect(scanResults[ii].address());
    if (peer.connected()) {
      peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
      while(!canDisconnect) Particle.process();      
      peer.disconnect();
      canDisconnect = false;;
    }
  }
  Serial.println();
}

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
  Serial.printf("Received data from advertiser %s: ", (const char*)peer.address().toString()
               );
  for (size_t ii = 0; ii < len; ii++) {
    Serial.write(data[ii]);
  }
  canDisconnect = true;
}

// --------------------------------------------------------------------------------------------------------------------------------
#elif defined(PERIPHERAL)

void setAdvertisingData();

BleCharacteristic txCharacteristic("tx", BleCharacteristicProperty::NOTIFY, txUuid, serviceUuid);
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::WRITE_WO_RSP, rxUuid, serviceUuid, onDataReceived, NULL);

void setup() {
  BLE.on();
  BLE.addCharacteristic(rxCharacteristic);
  BLE.addCharacteristic(txCharacteristic);
  BLE.setTxPower(8);
  BLE.setAdvertisingPhy(BlePhy::BLE_PHYS_CODED);
}

void loop() {
  static uint32_t msLast = 0;
  if (millis() - msLast < MS_PERIOD) return;
  msLast = millis();
  
  
  float temp = random(100) / 10.0;
  snprintf((char*)txBuf, sizeof(txBuf), "{ \"temp\":\"%.1f °C\" }", temp);
  setFullAdvert((char*)txBuf, strlen((char*)txBuf)+1);
  if (BLE.connected())
    txCharacteristic.setValue(txBuf, strlen((char*)txBuf)+1);
}

void setFullAdvert(const char* data, int len) {
  customData cd;
  BleAdvertisingData advData;
  cd.companyID = 0xFFFF;
  memcpy(cd.data, data, len);                                           
  advData.appendLocalName("ScruffyBLE");                      
  advData.appendCustomData((uint8_t*)&cd, 2 + len);         
  advData.appendServiceUUID(serviceUuid);
  BLE.advertise(&advData); 
}

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