New Sketch using Adafruit BLE Library - UNTESTED!

/*********************************************************************

21 May 23 - TO DO
DONE | TESTED | Add Adafruit BLE
DONE | NOT-TESTED | Add DPS310 sensor (Atmospheric Pressure)
DONE | NOT-TESTED | Add SDP31 sensor (Diff Pressure)
DONE | NOT-TESTED | Add SHT40 sensor (Temp/Hum)
DONE | TESTED | Add 100mA charging
DONE | TESTED | Add Battery Voltage
DONE | TESTED | Add Charging sense
DONE | NOT-TESTED | Add sensor values to MSG function
    NOT-TESTED    |  Flight Testing

May 2023 - BLE print functionality is now broken out to a separate Function.
           Ie: Build Sentence part-1 -> BLE Print ->
            back to Build Sentence part-2 -> BLE Print (again) -> Loop
 
22 April 2023 - Working with XIAO nRF (ie: not XIAO mBed device).

INFO:
  MCU Hardware - Seeed XIAO nRF52840 (not "Sense" model)
  Using Arduino IDE 1.8.19
  Using Arduino 'Board' -> "Seeed nRF52 Boards" -> "Seeed XIAO nRF52840"

*********************************************************************/

// ----------LIBRARIES--------------

#include <bluefruit.h>          // for BLE - Adafruit BLE Library
//#include <Adafruit_LittleFS.h>
//#include <InternalFileSystem.h>
#include <Wire.h>               // for I2C comms
#include <avr/dtostrf.h>        // for dtostrf
#include <sdpsensor.h>          // Sensirion SDP3x library
#include <SensirionI2CSht4x.h>  // Sensirion SHT4x library
#include <Adafruit_DPS310.h>    // Adafruit DPS310 library


// ---------CONSTANTS---------------

// Function Timing
const int IntervalSDP = 99;   // number of mS before IAS function repeats and also sends all BLE data
const int IntervalDPS = 113;   // number of mS before DPS function repeats and collects Atmospheric Pressure
const int IntervalSHT = 4800;  // number of mS before Temperature Humidity function repeats
const int IntervalBTV = 9963;  // number of mS before Battery Voltage function repeats
const int IntervalLED = 3313;  // number of mS before the Battery Charging function repeats (turns on Red LED)
const int LightLED = 500;      // millisecs that the (red) LED, indicating charging, is on


// -----------CLASSES---------------

// BLE Services
BLEDfu bledfu;    // OTA DFU service
BLEDis bledis;    // device information
BLEUart bleuart;  // uart over ble

// Dual Pressure Sensor
SDP3XSensor sdp;

// Temperature & Humidity Sensor
SensirionI2CSht4x sht4x;

// Absolute Pressure Sensor
Adafruit_DPS310 dps;


//------------VARIABLES-------------

// Startup Melody Notes & Variables
#define NOTE_C6 1047
#define NOTE_E6 1319
#define NOTE_G6 1568
#define REST 0
int tempo = 90;
int buzzer = 1;
int melody[] = {
  NOTE_C6,
  16,
  NOTE_G6,
  16,
  NOTE_E6,
  16,
  NOTE_C6,
  32,
  NOTE_G6,
  -16,
  NOTE_E6,
  8,
};
int notes = sizeof(melody) / sizeof(melody[0]) / 2;
int wholenote = (60000 * 4) / tempo;
int divider = 0, noteDuration = 0;

// Function Timing
unsigned long currentMillis = 0;  // Stores the value of millis() in each iteration of loop()
unsigned long previousSDPMillis = 0;
long previousSHTMillis = 0;
long previousDPSMillis = 0;
long previousBTVMillis = 0;
long previousLEDMillis = 0;
bool flashLEDstate = 0;  // Used to record whether the LED is on or off

// Sensirion Differential Pressure Variable
float sdpPres;  // Variable for the differential pressure

// Infineon Atmospheric Pressure Variable
float absPres = 966.0;  // Variable for the Atmospheric absolute pressure

// Ambient Environment Variables
float ambTemp = 0;
float ambHumi = 0;

// // Battery Voltage Variables
float batVolt = 4.00;  // Battery Volts
float batPrct = 50.0;  // Battery SOC %


//===================SETUP========================

void setup() {

  //Serial.begin(115200);

  // Initialise I2C
  Wire.begin();

  // Setup Xiao Battery Monitoring
  pinMode(VBAT_ENABLE, OUTPUT);  // Set the battery Voltage divider ground leg as an Output
  pinMode(23, INPUT);            // Set monitoring leg as an Input

  // Setup Xiao Battery Charging Rate
  pinMode(22, OUTPUT);    // Set the charger mA selection pin as an Output
  digitalWrite(22, LOW);  // Charge Current = 100mA (22 High = 50mA)

  // Setup Buzzer Output
  pinMode(D1, OUTPUT);  // D1 output to Buzzer

  // Initialise Sensirion SDP
  int ret = sdp.init();
  if (ret == 0) {
  } else {
    while (true) {
      delay(500);
    }
  }

  // Initialise Sensirion SHT
  sht4x.begin(Wire);

  // Initialise Infineon DPS sensor
  if (!dps.begin_I2C()) {  // Can pass in I2C address here
    while (1) yield();
  }
  dps.configurePressure(DPS310_16HZ, DPS310_32SAMPLES);  // See DPS310 datasheet for combinations
  dps.configureTemperature(DPS310_2HZ, DPS310_2SAMPLES);

  // Startup Melody
  {
    for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
      divider = melody[thisNote + 1];
      if (divider > 0) {
        // regular note, just proceed
        noteDuration = (wholenote) / divider;
      } else if (divider < 0) {
        // dotted notes are represented with negative durations!!
        noteDuration = (wholenote) / abs(divider);
        noteDuration *= 1.5;  // increases the duration in half for dotted notes
      }
      tone(buzzer, melody[thisNote], noteDuration * 0.9);
      delay(noteDuration);
      noTone(buzzer);
    }
    delay(200);
  }

  // Battery SoC Beeps
  batVolt = analogRead(PIN_VBAT) * (0.003515625F) * (1510.0 / 510.0);
  int n;
  {
    if (batVolt > 3.9) {
      // fully charged ... four beeps
      for (n = 1; n <= 4; n++) {
        tone(buzzer, 880, 200);
        delay(400);
      }
    } else if (batVolt > 3.8) {
      // well charged ... three beeps
      for (n = 1; n <= 3; n++) {
        tone(buzzer, 880, 200);
        delay(400);
      }
    } else if (batVolt > 3.7) {
      // some charge ... two beeps
      for (n = 1; n <= 2; n++) {
        tone(buzzer, 880, 200);
        delay(400);
      }
    } else {
      // discharged ... one beep
      tone(buzzer, 440, 300);
      delay(400);
    }
  }
  noTone(buzzer);
  delay(100);

  //BLE

#if CFG_DEBUG
  // Blocking wait for connection when debug mode is enabled via IDE
  while (!Serial) yield();
#endif

  // Config the peripheral connection with maximum bandwidth
  // more SRAM required by SoftDevice
  // Note: All config***() function must be called before begin()
  Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);

  Bluefruit.begin();
  Bluefruit.setTxPower(-12);  // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
  //Bluefruit.setName(getMcuUniqueID()); // useful testing with multiple central connections or (eg):  Bluefruit.setName("Name");
  Bluefruit.setName("ProbeS4");
  Bluefruit.Periph.setConnectCallback(connect_callback);
  Bluefruit.Periph.setDisconnectCallback(disconnect_callback);

  // To be consistent OTA DFU should be added first if it exists
  bledfu.begin();

  // Configure and Start Device Information Service
  bledis.setManufacturer("Seeed");
  bledis.setModel("XIAOnRF");
  bledis.begin();

  // Configure and Start BLE Uart Service
  bleuart.begin();

  // Set up and start advertising
  startAdv();
}

void startAdv(void) {
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();
  // Include bleuart 128-bit uuid
  Bluefruit.Advertising.addService(bleuart);
  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);  // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);    // number of seconds in fast mode
  Bluefruit.Advertising.start(0);              // 0 = Don't stop advertising after n seconds
}


//===================LOOP========================

void loop() {
  currentMillis = millis();  // Capture the latest value of millis()
  readSDP();                 // This function reads SDP, sends to variable then calls buildMSG for BLEprinting
  readDPS();                 // This function reads DPS function reads Atmospheric Pressure and sends to variable
  readSHT();                 // This function reads ambient Temperature/Humidity and sends to variable
  readBTV();                 // This function reads Battery Voltage and sends to variable
  flashLED();                // This function flashes the LED if USB is plugged (Battery charging)
}


//================BLE FUNCTIONS=====================

// Callback invoked when central connects
void connect_callback(uint16_t conn_handle) {
  // Get the reference to current connection
  BLEConnection* connection = Bluefruit.Connection(conn_handle);

  char central_name[32] = { 0 };
  connection->getPeerName(central_name, sizeof(central_name));
  // Serial.print("Connected to ");
  // Serial.println(central_name);
}

void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
  (void)conn_handle;
  (void)reason;
  // Serial.print("Disconnected, reason = 0x");
  // Serial.println(reason, HEX);
}


//================ SENSOR FUNCTIONS =====================

// readSPD+++++++++++
void readSDP()  // read Differential Pressure from SDP snsor, append to a NEMA message, then print / write to serial / BLE
{
  if (currentMillis >= previousSDPMillis + IntervalSDP)  // run 'readIAS' function only once time is up
  {
    previousSDPMillis = currentMillis;  // save the time when change was made
    int ret = sdp.readSample();
    if (ret == 0) {
      sdpPres = sdp.getDifferentialPressure();
      //      Serial.println(sdpPres);  // Print Differential Pressure for debugging
    }
    //sdpPres = sdpPres - 0.02;
    sdpPres = sdpPres * (966 / absPres);  // Correction of presssure differential wrt altitude/QNH
    if (sdpPres < 0) {                    // If there is a negative pressure set Differential pressure to zero
      sdpPres = 0;
    }
    buildMSG();  // Call function to build the TotalVario sentence, then transmit to BLE
  }
}

// readDPS+++++++++++
void readDPS()  // read Atmospheric Pressure from DPS sensor
{
  if (currentMillis >= previousDPSMillis + IntervalDPS)  // run 'readDPS' function only once time is up
  {
    previousDPSMillis = currentMillis;  // save the time when change was made
    sensors_event_t temp_event, pressure_event;
    while (!dps.temperatureAvailable() || !dps.pressureAvailable()) {
      return;  // wait until there's something to read
    }
    dps.getEvents(&temp_event, &pressure_event);
    absPres = pressure_event.pressure;
  }
}

// readSHT+++++++++++
void readSHT()  // read Temperature & Humidity
{
  if (currentMillis >= previousSHTMillis + IntervalSHT)  // run 'readTH' function only once time is up
  {
    previousSHTMillis = currentMillis;  // save the time when change was made
    sht4x.measureHighPrecision(ambTemp, ambHumi);
  }
}

// readBTV+++++++++++
void readBTV()  // read battery Volts
{
  if (currentMillis >= previousBTVMillis + IntervalBTV)  // run 'readBV' function only once time is up
  {
    previousBTVMillis = currentMillis;  // save the time when change was made
    batVolt = batVolt = analogRead(PIN_VBAT) * (0.003515625F) * (1510.0 / 510.0);
    batPrct = -4370 + (2105 * batVolt) - (247.5144 * sq(batVolt));  // approximates battery SOC from Volts. Uses quadratic to approx.
    if (batPrct < 0)                                                // If battery Voltage is less than 0% set batPrct to 0%
    {
      batPrct = 0;
    }
    if (batPrct > 100)  // If battery Voltage is more than 100% set batPrct to 100%
    {
      batPrct = 100;
    }
  }
}

// flashLED+++++++++++
void flashLED()  // flash the LED if battery is charging
{
  // If battery is charging go to next step flash LED
  if (flashLEDstate == 0)  // if the LED is off, wait for the interval to expire before turning it on
  {
    if (currentMillis - previousLEDMillis >= IntervalLED)  // time is up, so turn LED on
    {
      // Test to see if battery is charging
      bool IsChargingBattery = !digitalRead(23);
      if (IsChargingBattery > 0) {
        digitalWrite(LED_RED, LOW);        // LED on
        flashLEDstate = 1;                 // Set the LED state to 1 or "on"
        previousLEDMillis += IntervalLED;  // save the time when change was made
      }
    }
  } else  // ie if LED is on. If on, we must wait for the duration to expire before turning it off
  {
    if (currentMillis - previousLEDMillis >= LightLED)  // time is up, so turn LED off
    {
      digitalWrite(LED_RED, HIGH);    // LED off
      flashLEDstate = 0;              // Set the LED state to 0 or "off"
      previousLEDMillis += LightLED;  // save the time when change was made
    }
  }
}


//================ MESSAGE BUILD FUNCTIONS =====================

// $PTVSOAR,<type>,<value>,<type>,<value>,...*<checksum>
// Note: Checksum is optional

// buildMSG+++++++++++
void buildMSG()  // Function to build sentence
{
  char strSDP[8];
  float sdp = sdpPres;         // Differential Pressure from SDP sensor
  dtostrf(sdp, 6, 2, strSDP);  // format float value to string

  char strDPS[8];
  float dps = absPres;         // Atmospheric Pressure from DPS sensor
  dtostrf(dps, 6, 2, strDPS);  // format float value to string

  char strSHT[8];
  float sht = ambTemp;         // Temparature from SHT sensor
  dtostrf(sht, 6, 2, strSHT);  // format float value to string

  char strSHH[8];
  float shh = ambHumi;         // Humidity from SHT sensor
  dtostrf(shh, 6, 2, strSHH);  // format float value to string

  char strBPC[8];
  float bpc = batPrct;         // Humidity from SHT sensor
  dtostrf(bpc, 6, 2, strBPC);  // format float value to string

  String msg = "$PTVSOAR,MNA,FlyingSilicon,MMO,ProbeS4,MSN,2023Q2001,";  // FIRST Half of $PTVSOAR Sentence
  printMSG(msg);                                                         // Call the printMSG function to transmit to BLE then come back here ...

// SECOND Half of $PTVSOAR Sentence
  msg += F("PIT");
  msg += F(",");
  msg += (strSDP);    // Pitot Differential Pressure
  msg += F(",");
  msg += F("PRS");
  msg += F(",");
  msg += (strDPS);    // Atmospheric Pressure
  msg += F(",");
  msg += F("OAT");
  msg += F(",");
  msg += (strSHT);    // Air Temperature
  msg += F(",");
  msg += F("OAH");
  msg += F(",");
  msg += (strSHH);    // Air Relative Humidity
  msg += F(",");
  msg += F("PCT");
  msg += F(",");
  msg += (strBPC);    // Battery State of Charge %
  msg.concat(" \n");  // End of sentence notifier for TV

  printMSG(msg);  // Call the printMSG function to transmit to BLE then come back here ...
}
// END OF BUILD MSG ---------------------------------------------------------

// printMSG+++++++++++
void printMSG(String msg)  // Function to transmit to BLE
{
  uint8_t buf[64];                       // create a variable 'buf1' of size 64
  msg.getBytes(buf, msg.length() + 1);   // move msg1 characters into 'buf', for length of msg
  bleuart.write(buf, msg.length() + 0);  // write 'buf2' characters to BLE, for length of msg
  msg = "";  // Clear the msg buffer for next read and print
}
// END OF BLE UART WRITE MSG FUNCTION ----------------------------------------