So here is some patched together code that allows several sleeping Xenons that occasionally wake up and communicate with a Central Boron Device over BLE. (I’m still thinking I would have been better off a month ago to just adopt the BLE Group library, but I thought maybe I could learn more by trying it on my own…) So I’m able to connect several different devices to the Central node, even at the same time (I’ve had 2 perps connected to the Central at the same time). However, if I have several devices speaking at the same time after a while my Central Boron starts to do 10 red SOS Blinks, followed by some more blinks (having trouble counting them!)… Maybe something to do w/ memory, and I’m also throwing a lot of Strings around. I also feel like this code approach is doing a lot of “manual labor” instead of taking advantage of objects or methods in C++ (I still need to grasp basic C++ ideologies). I’m running devices OS 1.5.0 … If I have a central device speaking w/ multiple peripherals , do I need to have separate peerTxCharacterstic's
for each device? What about separate onDataReceived
handlers for each device? What about a separate rxUUID
and txUUID
for each device? Here is the Central Device Code: ****update, after upgrading the Boron to OS 3.0 it seemed to improve performance. If I quickly connect and disconnect the periphal nodes it will still sometimes start to flash red SOS, but the application recovers afterwards.
/*
* Project BLE_DOOR_NODE
* Description:This is the Backdoor Node. Wakeup and alert door status on interrupt
* Author:
* Date:
*/
#include "Particle.h"
SYSTEM_MODE(MANUAL);
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
////////////////////////////////////Global Variables
// 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");
const size_t UART_TX_BUF_SIZE = 20;
const size_t SCAN_RESULT_COUNT = 20;
bool oneshot = false;
bool AdvMsgConfirmed1;
bool AdvMsgConfirmed2; //confirmation for Frontdoor
bool AdvMsgConfirmed3; //confirmation for Drivewaynode
int OGAdvDataMsgCnfrm1 = FALSE;
int BackDoorStatus = 0;
float BackDoorSoC = 0;
int FrontDoorStatus = 0;
float FrontDoorSoC = 0;
int OGAdvDataMsgCnfrm2 = FALSE;
String OGAdvData1;
String OGAdvData2;
BleScanResult scanResults[SCAN_RESULT_COUNT];
BleCharacteristic peer1TxCharacteristic; //Backdoor
BleCharacteristic peer1RxCharacteristic; //Backdoor
BleCharacteristic peer2TxCharacteristic; //Frontdoor
BleCharacteristic peer2RxCharacteristic; //Frontdoor
BlePeerDevice peerBackDoor;
BlePeerDevice peerFrontDoor;
BlePeerDevice peerDriveway;
BleAddress BackDoorAddress;
BleAddress FrontDoorAddress;
uint8_t txBuf[UART_TX_BUF_SIZE];
size_t txLen = 0;
const unsigned long SCAN_PERIOD_MS = 2000;
unsigned long lastScan = 0;
void msgRcvChecker1(const uint8_t* data, size_t len, const BlePeerDevice& peer);
void msgRcvChecker2(const uint8_t* data, size_t len, const BlePeerDevice& peer);
///////////////////////////////////////////functions
void onDataReceived1(const uint8_t* data, size_t len, const BlePeerDevice& peer/*peerBackDoor*/, void* context) {
// for (size_t ii = 0; ii < len; ii++) {
// Serial.write(data[ii]);
// }
// if (peer.address() == /*BackDoorAddress*/peerBackDoor.address()) {
// Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X", peer.address()[0], peer.address()[1], peer.address()[2], peer.address()[3], peer.address()[4], peer.address()[5]);
msgRcvChecker1(data,len,peer);
// }
}
void onDataReceived2(const uint8_t* data, size_t len, const BlePeerDevice& peer/*peerBackDoor*/, void* context) {
// for (size_t ii = 0; ii < len; ii++) {
// Serial.write(data[ii]);
// }
// if (peer.address() == /*BackDoorAddress*/peerBackDoor.address()) {
// Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X", peer.address()[0], peer.address()[1], peer.address()[2], peer.address()[3], peer.address()[4], peer.address()[5]);
msgRcvChecker2(data,len,peer);
// }
}
void setup() {
Serial.begin();
pinMode(D7, OUTPUT);
BLE.on();
peer1TxCharacteristic.onDataReceived(onDataReceived1, &peer1TxCharacteristic);
peer2TxCharacteristic.onDataReceived(onDataReceived2, &peer2TxCharacteristic);
// BLE.addCharacteristic(peerTxCharacteristic);//dk
// BLE.addCharacteristic(peerRxCharacteristic);//dk
}
void loop() {
//Scan and setup peer
if (millis() - lastScan >= SCAN_PERIOD_MS){
//time to scan
lastScan = millis();
Log.info("Scanning");
size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
if (count > 0){
for(uint8_t ii = 0; ii < count; ii++) {
String DeviceName = scanResults[ii].advertisingData().deviceName();
if (DeviceName == "Backdoor") { // send info to Backdoor
Log.info("Found matching DeviceName:" + DeviceName);
BackDoorAddress = scanResults[ii].address(); //save addy so we can connect
//Setup data struct to read the custom data
uint8_t buf[BLE_MAX_ADV_DATA_LEN];
size_t len;
len = scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
BackDoorScanMatch(buf,len, DeviceName); //send Adv data for further checking
}
if (DeviceName == "Frontdoor") {
Log.info("Found matching DeviceName:" + DeviceName);
FrontDoorAddress = scanResults[ii].address();//save addy so we can connect
//Setup data struct to read the custom data
uint8_t buf[BLE_MAX_ADV_DATA_LEN];
size_t len;
len = scanResults[ii].advertisingData().get(BleAdvertisingDataType::MANUFACTURER_SPECIFIC_DATA, buf, BLE_MAX_ADV_DATA_LEN);
FrontDoorScanMatch(buf,len, DeviceName); //send Adv data for further checking
}
}
}
}
}
void msgRcvChecker1(const uint8_t* data, size_t len, const BlePeerDevice& peer){
//so we enter here when after we've connected w/perp device and recvd a msg confirmation
// of the data in the advertisement data.
//If msg is confirmed, set the msgRCVD bit high to enter sleep
//perp broadcasts DeviceName, custom data (DoorStatus,SoC) -> Central scans & recv's advertising data. If devicename matches list, central makes connection,
// Central re-sends the OGAdvData to perp for confirmation. if the data recvd by perp matches the data it sent in broadcast,
//perp sends "AdvMsgConfirm" to Central. Central then waits to recv DoorClosed message -> Central then Sends "OKtoSleep" msg to perp
//adding BlePeerDevice to checker could help keep track of msg status and reset once connection is gone.
// need to
const char* dataAsCharArray = (const char*)data; //casts the uint8_t data into a char array or string
//bool AdvMsgConfirmed1; //confirmation for Backdoor
//Log.info("here");
if(strncmp("AdvMsgConfirm", dataAsCharArray, len)==0){
//peerRxCharacteristic.setValue("OKtoSleep");
AdvMsgConfirmed1 = HIGH;
// Log.info("1st confirm Backdoor");
}
// if (AdvMsgConfirmed1 == HIGH) {
if(strncmp("DoorClosed", dataAsCharArray, len) ==0){
peer1RxCharacteristic.setValue("OKtoSleep");
AdvMsgConfirmed1 = LOW; //reset
// Log.info("2nd confirm bd");
}
// }
else{
for (size_t ii = 0; ii < len; ii++) {
Serial.write(data[ii]);
}
}
}
void msgRcvChecker2(const uint8_t* data, size_t len, const BlePeerDevice& peer){
//so we enter here when after we've connected w/perp device and recvd a msg confirmation
// of the data in the advertisement data.
//If msg is confirmed, set the msgRCVD bit high to enter sleep
//perp broadcasts DeviceName, custom data (DoorStatus,SoC) -> Central scans & recv's advertising data. If devicename matches list, central makes connection,
// Central re-sends the OGAdvData to perp for confirmation. if the data recvd by perp matches the data it sent in broadcast,
//perp sends "AdvMsgConfirm" to Central. Central then waits to recv DoorClosed message -> Central then Sends "OKtoSleep" msg to perp
//adding BlePeerDevice to checker could help keep track of msg status and reset once connection is gone.
// need to
const char* dataAsCharArray = (const char*)data; //casts the uint8_t data into a char array or string
//bool AdvMsgConfirmed1; //confirmation for Backdoor
//Log.info("here");
if(strncmp("AdvMsgConfirm", dataAsCharArray, len)==0){
AdvMsgConfirmed2 = HIGH;
// Log.info("1st confirm FD");
}
// if (AdvMsgConfirmed2 == HIGH) {
if(strncmp("DoorClosed", dataAsCharArray, len) ==0){
peer2RxCharacteristic.setValue("OKtoSleep");
AdvMsgConfirmed2 = LOW; //reset
// Log.info("2nd confirmm FD");
}
// }
else {
for (size_t ii = 0; ii < len; ii++) {
Serial.write(data[ii]);
}
}
}
void BackDoorScanMatch(uint8_t* buf, size_t len, String _DeviceName){
if (!peerBackDoor.connected()){
//if we're not connected then connect
peerBackDoor = BLE.connect(BackDoorAddress);
}
if(peerBackDoor.connected()){
//we are now connected, need to parse advertising data
peerBackDoor.getCharacteristicByUUID(peer1TxCharacteristic, txUuid);
peerBackDoor.getCharacteristicByUUID(peer1RxCharacteristic, rxUuid);
bool OnConnection = TRUE;
Log.info("Connected to" + _DeviceName);
uint8_t _buf[BLE_MAX_ADV_DATA_LEN];
size_t _len;
_len = _len;
if (len ==11) { //this is the length of our custom payload from perp device
//this is the dummy company ID and data type we set from the advertiser
if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0x55) {
memcpy(&BackDoorStatus, &buf[3], 4);
memcpy(&BackDoorSoC, &buf[7],4);
OGAdvData1 = String(_DeviceName + "," + BackDoorStatus);
// Log.info("BackDoorStatus: %d" , BackDoorStatus);
// Log.info("BackDoorSoC %0.2f", BackDoorSoC);
}
}
else {
Log.info("len = %d" , _len);
}
//upon initial connection resend the Original Adv Data back to perp for checking:
if (OnConnection == TRUE) {
OnConnection = FALSE; //only send this data upon first connection
peer1RxCharacteristic.setValue(OGAdvData1); // resend og advertising data (also start of communication)
bool StartConversation = HIGH;
// Log.info("sent og data");
}
}
}
void FrontDoorScanMatch(uint8_t* buf, size_t len, String _DeviceName){
if (!peerFrontDoor.connected()){
//if we're not connected then connect
peerFrontDoor = BLE.connect(FrontDoorAddress);
}
if(peerFrontDoor.connected()){
//we are now connected, need to parse advertising data
peerFrontDoor.getCharacteristicByUUID(peer2TxCharacteristic, txUuid);
peerFrontDoor.getCharacteristicByUUID(peer2RxCharacteristic, rxUuid);
bool OnConnection = TRUE;
Log.info("Connected to" + _DeviceName);
uint8_t _buf[BLE_MAX_ADV_DATA_LEN];
size_t _len;
_len = _len;
if (len ==11) { //this is the length of our custom payload from perp device
//this is the dummy company ID and data type we set from the advertiser
if (buf[0] == 0xff && buf[1] == 0xff && buf[2] == 0x55) {
memcpy(&FrontDoorStatus, &buf[3], 4);
memcpy(&FrontDoorSoC, &buf[7],4);
OGAdvData2 = String(_DeviceName + "," + FrontDoorStatus);
// Log.info("FrontDoorStatus: %d" , FrontDoorStatus);
// Log.info("FrontDoorSoC %0.2f", FrontDoorSoC);
}
}
else {
Log.info("len = %d" , _len);
}
//upon initial connection resend the Original Adv Data back to perp for checking:
if (OnConnection == TRUE) {
OnConnection = FALSE; //only send this data upon first connection
peer2RxCharacteristic.setValue(OGAdvData2); // resend og advertising data
bool SentOGAdvData = HIGH;
// Log.info("sent og data");
}
}
}
/*
if (peer.address() == BackDoorAddress) {
Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X", peer.address()[0], peer.address()[1], peer.address()[2], peer.address()[3], peer.address()[4], peer.address()[5]);
msgRcvChecker(data,len,peer);
}
if (peer.address() == FrontDoorAddress) {
Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X", peer.address()[0], peer.address()[1], peer.address()[2], peer.address()[3], peer.address()[4], peer.address()[5]);
msgRcvChecker(data,len,peer);
}
*/
And the periphal devices:
/*
* Project BLE_DOOR_NODE
* Description:This is the Backdoor Node. Wakeup and alert door status on interrupt
* Author:
* Date:
*/
#include "Particle.h"
SYSTEM_THREAD(ENABLED);
SYSTEM_MODE(MANUAL);
SystemSleepConfiguration config;
SerialLogHandler logHandler(LOG_LEVEL_TRACE);
const size_t UART_TX_BUF_SIZE = 20;
//dk if need both these variables
const unsigned long UPDATE_INTERVAL_MS = 30000;
unsigned long lastUpdate = 0;
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context);
//Global/General Variable info
int ledPin = D7; //led on board
int inputPin = D0; //input pin for door switch
int DoorState = LOW; //we start, assuming no motion detected
int DoorStatus = 0; // 1= closed 0 = open
float SoC = 23; //holds the battery StateOfCharge
int Counter1 = 0;
String OGAdvData;
int OGAdvDataMsgCnfrm = FALSE;
//String TestData = "does this work";
String LocalDeviceName = "Frontdoor";
//String LocalDeviceName = "Backdoor";
//dk if i need these below
bool oneshot = FALSE;
int msgCount = 0;
//String str1;
//const char* dataIN = "";
bool msgRCVD = FALSE; //use this to make sure the device doesnt stop broadcastings until connection confirms msg recvd
bool OKtoSleep = FALSE;
bool MsgSent = FALSE;
////////////////////////////////////////////////////////
//BLE definition Info
// 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); //may check if it needs to be broadcasted on the serviceUuid
BleCharacteristic rxCharacteristic("rx", BleCharacteristicProperty::/*WRITE_WO_RSP*/WRITE, rxUuid, serviceUuid, onDataReceived, NULL);
//functions declarations
void updateAdvertisingData(bool updateOnly);
void msgRcvChecker(const uint8_t* data, size_t len);
// create a softDelay, safer than delay()
inline void softDelay(uint32_t t) {
for (uint32_t ms = millis(); millis() - ms < t; Particle.process());
}
//need to customize this for this application
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
//if I had custom struct being sent I could deconstruct it here,
//but since we're just going to pass string commands for
//just pass data into msgRcvChecker to keep this loop short
// const char* dataAsCharArray = (const char*)data;
Log.trace("Received data from: %02X:%02X:%02X:%02X:%02X:%02X:", peer.address()[0], peer.address()[1], peer.address()[2], peer.address()[3], peer.address()[4], peer.address()[5]);
//could compare lastdata sent to compare?
for (size_t ii = 0; ii < len; ii++) {
Serial.write(data[ii]);
// txCharacteristic.getValue(data,len);
}
msgRcvChecker(data,len);
}
// setup() runs once, when the device is first turned on.
void setup() {
// Put initialization like pinMode and begin functions here.
pinMode(ledPin, OUTPUT);
// pinMode(inputPin, INPUT);
pinMode(inputPin, INPUT_PULLDOWN);
//delete serial after debug to save batt?
config.mode(SystemSleepMode::HIBERNATE)
.gpio(D0, FALLING)
.flag(SystemSleepFlag::WAIT_CLOUD);
Serial.begin();
updateAdvertisingData(false);
BLE.addCharacteristic(txCharacteristic);
BLE.addCharacteristic(rxCharacteristic); //data is received by the peripheral with this characteristic.
}
// loop() runs over and over again, as quickly as it can execute.
void loop() {
//going into this loop means we just woke up or just powered up
// digitalWrite(D7,HIGH);
BLE.on();//turn BT on
DoorStatus = digitalRead(inputPin); //read the value of door switch
// if (val == LOW) {
digitalWrite(ledPin,HIGH);
if (DoorState == LOW){
//we only want to trigger on change of statuts?
//Serial.println("BackDoorOpen");
DoorState = HIGH;
//trigger msg
updateAdvertisingData(true);
//this means we've woken up and sent an Advertisement message
//containing: DeviceName, CustomData(){status,Battinfo,SOC}
//need to establish advertising interval so msg. keeps going until connected
}
if (BLE.connected()) {
if (oneshot == FALSE){
Log.info("BLE connected");
// Log.info("payload size %d", testpayloadsize);
oneshot = TRUE;
}
}
if (DoorStatus == HIGH){ //door is closed, we just need to confirm OKtoSleep
// DoorState = LOW;
digitalWrite(ledPin,LOW);
if (!MsgSent) {
txCharacteristic.setValue("DoorClosed");
// Log.info("DoorClosed");
msgCount++;
if( msgCount > 3){
MsgSent = HIGH;
}
}
}
if (OKtoSleep == HIGH){
digitalWrite(ledPin, LOW);
DoorState = LOW;
SystemSleepResult result = System.sleep(config); //gpes to sleep
}
}
void updateAdvertisingData(bool updateOnly)
{
Log.info("just started updateAdvData()");
//once I've jumped into this function it means:
//we've just woke up and we're trying to send a
//advertisment w/ deviceName() and Door Status(customstruct is needed)
//set advertisement inverval
//wait for the connection to happen
//the info below sets up the custom data
uint8_t buf[BLE_MAX_ADV_DATA_LEN];
// Advertising data consists of records:
// Byte: Length (including the AD Type)
// Byte: AD Type
// Data (variable length)
size_t offset = 0;
// Company ID (0xffff internal use/testing)
buf[offset++] = 0xff;
buf[offset++] = 0xff;
// Internal packet type. This is arbitrary, but provides an extra
// check to make sure the data is my data, since we use the 0xffff company
// code.
buf[offset++] = 0x55;
// Our specific data, door status
memcpy(&buf[offset], &DoorStatus, 4);
offset += 4;
//but let's go ahead and put SoC in there too
memcpy(&buf[offset], &SoC, 4); //setting SOC(4bytes long) in the buf and then moving 4 spaces
offset +=4;
BleAdvertisingData advData;
advData.appendLocalName(LocalDeviceName);
advData.appendCustomData(buf, offset);
////add our broadcast data to check later:
OGAdvData = String(LocalDeviceName + "," + DoorStatus);
if (updateOnly)
{
//BLE.setAdvertisingInterval(130); //play w/ with to find good time
BLE.advertise(&advData);
}
else
{
}
}
void msgRcvChecker(const uint8_t* data, size_t len){
//so we enter here when after we've connected w/central device and recvd a msg confirmation
// of the data in the advertisement data.
//If msg is confirmed, set the msgRCVD bit high to enter sleep
const char* dataAsCharArray = (const char*)data; //casts the uint8_t data into a char array or string
if(strncmp(OGAdvData, dataAsCharArray, len) ==0){
//data matches
OGAdvDataMsgCnfrm = HIGH;
txCharacteristic.setValue("AdvMsgConfirm");
// txCharacteristic.setValue(TestData);
// rxCharacteristic.setValue(TestData);
// txCharacteristic.setValue(OGAdvDataMsgCnfrm);//resend adv data?
Log.info("got OG msg, sent AdvMsgConfirm");
//return
}
else if(OGAdvDataMsgCnfrm ==LOW){
Log.warn("received unknown message: %.*s\n",(int)len,dataAsCharArray);
txCharacteristic.setValue(OGAdvDataMsgCnfrm);//resend adv data?
}
if( OGAdvDataMsgCnfrm == HIGH &&strncmp("OKtoSleep", dataAsCharArray, len) ==0){
//we've sent first msg, now be ready to recieve new text
// OKtoSendCloseStatus = HIGH;
OKtoSleep = HIGH;
}
}
/*
if (val == HIGH && OKtoSleep == HIGH){ //need to add && message recvd &&msgRCVD
// if (val == HIGH){
digitalWrite(ledPin, LOW);
DoorState = LOW;
DoorStatus = val;
txCharacteristic.setValue("DoorClosed");
//updateAdvertisingData(false);
softDelay(1000);
SystemSleepResult result = System.sleep(config); //gpes to sleep
}
*/