BLE between particle devices: How to get acknowledge of packet received

I’ve spent a week or so digging thru the BLE documentation, and perhaps confused myself. I think there maybe a few ways to accomplish a very simple task: I want to have a message received confirmation when sending data from a particle peripheral device to central device. I want to broadcast a message to the central device until I can confirm it’s been received.

Could I use this method BLE.scan(callback) on my central device to say “hey, got a message from backdoor node, says door is open, going to send a confirmation message back to let it know message has been received and read correctly.” I could then use “onDataReceived” in my peripheral device to go ahead and stop broadcasting that message…??

Been looking thru UART central example, but still a bit confused. Any examples or easy methods to get message acknowledged?

I have a 3 door nodes that I want to simply wake up from sleep, send a message that door has been opened, wait for confirmation of message, report door is closed & message confirmed, go back to sleep. I’ve had this implemented using Mesh but now am trying to use it w/ just the BLE. Thanks

Advertising data is only for that - to tell other devices something about “yourself”.
It is not an actual conversation. For a conversation you’d need an active connection between the peripheral and the central.
Once you have a connection the central could expose a WRITE characteristic (which is typically with acknowledgment - in contrast to WRITE_WO_RSP) or a on the peripheral side implement a NOTIFY characteristic to send your data and a WRITE_WO_RSP characteristic to receive some kind of acknowledgement.

A possible way to short-circuit the process would be to advertise your data and treat a subsequent connection (attempt) of the central as acknowledgement.
While this may work for your use-case it’s not anything the BLE standardization committee would approve of :wink:

1 Like

That really helped me with overall concepts. Based on the UART examples, I am able to now have my peripheral device broadcast its UUID and then have the central device establish a connection. I’m able to send data back and forth between devices, however, I’m having trouble being able to compare the data as strings… When using this function:
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context)

how to convert the uint8_t* data into a string? For instance I want to compare:
if (str1 ==“FrontDoor,Open!”){
//do some actions}

Should I being doing this comparison inside the onDataReceived() function, or perhaps use the to pass the data along to another function? How about the rxCharacteristic.getValue(stringdata)? If I set the Value in the central device, do I need to use the getValue() to view this data? Or should I just use the data provided by OnDataReveived()?

There are lots of BLE devices that send data out with their advertisements. This allows for connectionless broadcast of things like temperature/humiditiy or other similar measurements. Advertisements are one-way, from the advertiser to potentially many receivers, but a lot of times that is what you want. In BLE 5 you can even have secondary channels that the primary advertisement points to allowing more data.

Advertisements like this are also how iBeacon and the Android equivalent Eddystone work.

A good introduction is here:

1 Like

Thanks a bunch for the resource. I was able to use the broadcasts to send device name and door status, it works quite nicely. However, for my particular use application I thought it’d be better to establish a connection and pass some confirmation/acknowledgment along. Looks like I need a better understanding of data structures and C++. Searching for any examples where people are using BLE to communicate locally between particle devices.

1 Like

@MikeG, uint8_t* data is a unsigned char pointer to the cstring array data. You can use the cstring functions to work with this type of data which is preferred over Arduino Strings since they can make a mess of the heap.

In your case, you could use the memcmp() and strlen() functions like this:

  if (memcmp(data, "FrontDoor, Open!", strlen(data)) == 0) {
   // The strings are equal
   ...
  }
2 Likes

Excellent, I appreciate your help. After a refresher course and some digging I found some other alternatives as well as one already described on the forum:

Which uses something like:
If you want a String object you’d pass a const char* to the String constructor, e.g. like this String((const char*)myByteArray); .

However I like the approach you described so once again appreciated! When I add this snippet it won't compile for me though:
if (memcmp(data, "FrontDoor, Open!", strlen(data)) == 0) {
// The strings are equal
// ...
}

ble_uart_perph.ino:46:48: invalid conversion from 'const uint8_t* {aka const unsigned char*}' to 'const char*' [-fpermissive]

1 Like

But that is exactly what @peekay123 was warning about

The error message you received did already provide a hint how to solve the problem.
One of the functions in there expects a const char* but gets a uint8_t*.
Since memcmp() expects two void* and one size_t (neither const uint8_t* nor const char* the error message complains about) the next suspect would be strlen() which wants a const char* and hence refuses to take const int8_t*.
However, since both types are directly compatible you can safely cast the type like this

if (memcmp(data, "FrontDoor, Open!", strlen((const char*)data)) == 0)

An alternative to memcmp() when you know you are only dealing with C-strings could be strcmp() or strncmp()

When you get an error message you don't understand, you should always carefully try to figure what it wants to tell you, look at the surrounding messages (in Web IDE use SHOW RAW to see more) which may provide additional info and investigate the functions involved (e.g. by googling their definition).

3 Likes

Thanks for showing me how to fish! Sometimes the clues are staring you in the face…This is starting to make sense, but could you help clarify:
So since memcmp() expects 2 void arguments and 1 size_t; it doesn’t matter what type of data you pass into the argument? It’s just comparing 2 blocks of memory by the number of bytes you told it:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

But now for strlen() : size_t strlen ( const char * str ); so it’s looking for a const char*, which in my case “data” is a uint8_t*…hence the error.

But since an uint8_t* and const char* are both the same sized “buckets” [i.e. both data types are the size of 1byte] , it allows the cast the uint8_t into a const char* type??? So the statement above is saying "I’m going to now pass a const char* into the function strlen(), but it’s actually a pointer for the uint8_t, and since the 2 data types are physically the same size the function performs correctly? "

That’s pretty much it.

The compiler would also allow multiple other casts - valid or invalid. What this kind of casting does (among other things) signal the compiler that you as programmer vouch for the cast to be valid and that’s enough for the compiler (if you want to shoot yourself in the foot by submitting a wrong cast it wouldn’t care anymore :wink: )

For memcpy() the only requirement for the first two parameters is that they have to be pointers.

1 Like

I would actually suggest to use strcmp() instead of memcmp() to compare strings as memcmp() has some theoretical pitfalls with strings (see here).

The next thing is, are you 100% sure that the uint8_t* data you receive is always a null terminated string? If not I would check it. So my suggestion would be something like:

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
    const char* dataString = (const char*)data;
    if(strnlen(dataString,len)==len) {
        Log.warn("received data is not a null terminated string!");
        return; // data is not a null terminated string !
    }
    if(strcmp("FrontDoor, Open!", dataString) == 0) {
        onFrontDoorOpen();
        return;
    }
    if(strcmp("FrontDoor, Closed!", dataString) == 0) {
        onFrontDoorClosed();
        return;
    }
    Log.warn("received unknown message: %s\n",dataString);
}

void onFrontDoorOpen() {
    // ...
}
void onFrontDoorClosed() {
    // ...
}

You can also test all this stuff with just c++ maybe in an online c++ playground like https://onlinegdb.com/omV_KX3EZ (this is actually a link to my example above) where you can even debug it online in the browser for very quick trials.

1 Like

Awesome truly appreciate the info. I’m actually not sure if the terminating null is included when looking at the documentation below. I was using the method setValue(string), curious to see what output your code gives me. update Running your code gave returns "WARN: received data is not a null terminated string!"
When looking at the statement below it makes sense, because it’s saying the string length is equal to len. It still returns not a null even if I try to add this to the packet being sent:
peerRxCharacteristic.setValue("yougetthis?" +'\0'); or
peerRxCharacteristic.setValue("yougetthis?\0");

Interesting that this works, I was curious if the null terminating character would effect the string comparison functions:

 if (memcmp(data, "yougetthis?", strlen((const char*)data)) == 0){
    //The strings are equal
    // digitalWrite(D7,HIGH);
    //start ack
    }

setValue(string)

To set the value of the characteristic to a string value, use this method. The terminating null is not included in the characteristic; the length is set to the actual string length.

As is usual, no sooner than @ScruffR posted his comment that I realized that I should have used strcmp() :roll_eyes:

1 Like

I should also have read the reference for BleCharacteristic::setValue that states “terminating null is not included” and when looking at the implementation it is clear that it is not added/transmitted.
Therefore, I have to revisit my suggestion and suggest using strncmp() with this the received string does not need to be null terminated, as you compare only up to the length provided.

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
    const char* dataAsCharArray = (const char*)data;
    if(strncmp("FrontDoor, Open!", dataAsCharArray, len) == 0) {
        onFrontDoorOpen();
        return;
    }
    if(strncmp("FrontDoor, Closed!", dataAsCharArray, len) == 0) {
        onFrontDoorClosed();
        return;
    }
    Log.warn("received unknown message: %.*s\n",(int)len,dataAsCharArray);
}

Here again a playground code to show it.

1 Like

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
       } 



*/