August Tweaks - Forced the buzzer to 0mA between sound bursts.



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

  10 August Added in "digitalWrite(D1, LOW);  // Ensure the Buzzer is off"
          This saves mA as "noTone(buzzer)" appears to default to leave output High.
          Tested on the bench - all working.
 
  18 July Commented/deleted out the AHT sensor and uncommented to use the SHT4x sensor
          Tested on the bench and all 3 sensors working. The buzzer also works.
          Battery sensing not checked yet.
          Not test-flown yet.

  10 June Removed sensor intialisation conditional halts.
          Sketch will run even if SDP3x or AHT45 are not ready
          (DPS310 pauses to set parameters)

  09 June Corrected Battery Voltage Read (was always reading 0V)
          Removed unused libraries
          Created Debug toggle for debugging Print.Serial. Set in Line#65
 
  Sensors in use: SDP3x | AHT45
  Sensors Commented out but ready: SHT45 | DPS310
 
  07 June added Charging notification (requires TotalVario Beta 9-2)
 
  06 June added BLE connection beeps
 
  04 June 2023 - used Bernd's sprintf sketch

  22 April 2023 - Working with XIAO nRF (ie: not XIAO mBed device).

  INFO:
  MCU Hardware - Seeed XIAO nRF52840 (not "Sense" model)
  Using Arduino IDE 2.1.1
Arduino Board Driver: Seeed nRF52 Boards v1.1.1
  Using Arduino 'Board' -> "Seeed nRF52 Boards" -> "Seeed XIAO nRF52840 1.1.1"

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

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

#include <bluefruit.h>          // for BLE - Adafruit BLE Library
#include <Wire.h>               // for I2C comms
#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;    // mS before IAS function repeats, also sends all BLE data (99)
const int IntervalDPS = 113;   // mS before DPS function repeats Atmospheric Pressure read (113)
const int IntervalSHT = 1333;  // mS before Temperature Humidity function repeats (1333)
const int IntervalBTV = 3333;  // mS before Battery Voltage function repeats (3333)


// -----------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-------------

// Debug setup for Serial Print
#define DEBUG false  //set to true for debug output, false for no debug output
#define DEBUG_SERIAL \
  if (DEBUG) Serial

// 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() for each loop()
unsigned long previousSDPMillis = 0;
long previousSHTMillis = 0;
long previousDPSMillis = 0;
long previousBTVMillis = 0;

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

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

// Ambient Environment Variables
float ambTemp = 0;
float ambHumi = 0;
float ahtValue;  // To store T/Rh result temporarily

// Battery Variables
float batVolt = 4.00;  // Battery Volts
float batPrct = 50.0;  // Battery SOC %
bool battChrg = 0;     // Battery Charging


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

void setup() {

  // Debugging
#if DEBUG == true
  Serial.begin(115200);
  while (!Serial)
    ;
#endif  //DEBUG == true

  // Initialise I2C
  Wire.begin();

  // Setup Xiao Battery Monitoring
  pinMode(VBAT_ENABLE, OUTPUT);    // Battery Voltage divider ground leg as an Output
  digitalWrite(VBAT_ENABLE, LOW);  // Ground the Voltage divider ground leg to enable read
  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
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off

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

  // Initialise Asair AHT2x Temp|Humidity Sensor
  // while (aht20.begin() != true) {
  //   delay(200);
  // }
  // aht20.begin();

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

  // Initialise Infinion 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 examples
  dps.configureTemperature(DPS310_2HZ, DPS310_2SAMPLES); // See DPS310 datasheet for examples

  // 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);
    }
    digitalWrite(D1, LOW);  // Ensure the Buzzer is off
    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);
        digitalWrite(D1, LOW);  // Ensure the Buzzer is off
        delay(400);
      }
    } else if (batVolt > 3.8) {
      // well charged ... three beeps
      for (n = 1; n <= 3; n++) {
        tone(buzzer, 880, 200);
        digitalWrite(D1, LOW);  // Ensure the Buzzer is off
        delay(400);
      }
    } else if (batVolt > 3.7) {
      // some charge ... two beeps
      for (n = 1; n <= 2; n++) {
        tone(buzzer, 880, 200);
        digitalWrite(D1, LOW);  // Ensure the Buzzer is off
        delay(400);
      }
    } else {
      // discharged ... one beep
      tone(buzzer, 440, 300);
      digitalWrite(D1, LOW);  // Ensure the Buzzer is off
      delay(400);
    }
  }
  noTone(buzzer);
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off
  delay(100);

  //BLE

  // 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. Values can be: -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();  // Calls function to read SDP, sends to variable then calls buildMSG for BLEprinting
  readDPS();  // Calls function to read DPS. Reads Atmospheric Pressure and sends to variable
  readSHT();  // Calls function to read ambient Temperature/Humidity and sends to variable
  readBTV();  // Calls function to read Battery Voltage and if Charging, sends to variable
}


//================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));
  DEBUG_SERIAL.print("Connected to ");  // For debugging
  DEBUG_SERIAL.println(central_name);   // For debugging
  tone(buzzer, 440, 400);
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off
  delay(400);
  tone(buzzer, 880, 200);
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off
}

void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
  (void)conn_handle;
  (void)reason;
  DEBUG_SERIAL.print("Disconnected, reason = 0x");  // For debugging
  DEBUG_SERIAL.println(reason, HEX);                // For debugging
  tone(buzzer, 440, 600);
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off
  delay(400);
  tone(buzzer, 330, 600);
  digitalWrite(D1, LOW);  // Ensure the Buzzer is off
}


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

// readSPD+++++++++++
void readSDP()
// read Differential Pressure from SDP sensor, append to a NEMA message, write to serial BLE
{
  if (currentMillis >= previousSDPMillis + IntervalSDP)  // run 'readIAS' function once time is up
  {
    previousSDPMillis = currentMillis;  // save the time when change was made
    int ret = sdp.readSample();
    if (ret == 0) {
      sdpPres = sdp.getDifferentialPressure();
      DEBUG_SERIAL.println("");        // For debugging
      DEBUG_SERIAL.println(sdpPres);  // Print Differential Pressure for debugging
    }
    sdpPres = sdpPres + 0.02;  // Offset if needed
    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);
       DEBUG_SERIAL.println(ambTemp); // For debugging
       DEBUG_SERIAL.println(ambHumi); // For debugging
    // ahtValue = aht20.readTemperature();  //read 6-bytes via I2C, takes 80 milliseconds
    // ambTemp = ahtValue;
    // DEBUG_SERIAL.println(ambTemp);    // For debugging
    // ahtValue = aht20.readHumidity();  //read another 6-bytes via I2C, takes 80 milliseconds
    // ambHumi = ahtValue;
    // DEBUG_SERIAL.println(ambHumi);  // For debugging
  }
}

// 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;
    }
    battChrg = !digitalRead(23);
    DEBUG_SERIAL.println(battChrg);  // For debugging
    DEBUG_SERIAL.println(batVolt);   // For debugging
    DEBUG_SERIAL.println(batPrct);   // For debugging
  }
}

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

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

// buildMSG+++++++++++
void buildMSG()  // Function to build sentence
{
  // Non-data Half of $PTVSOAR Sentence:
  String msg = F("$PTVSOAR,MNA,FlyingSilicon,MMO,ProbeS4,MSN,2023Q2001");

  // SECOND Half of $PTVSOAR Sentence:
  //String msg = F("$PTV"); // Short PTV sentence structure (if using).
  //(Note: PTV does not use 'type' characters (eg: "PIT"). Rather placeholders are used.)
 
  char buf[100];  // enough characters
 
  sprintf(buf,
          ",PIT,%06.2f"
          ",PRS,%08.3f"
          ",OAT,%+03.0f"
          ",OAH,%03.0f"
          ",PCT,%03.0f"
          ",CHG,%d"
          ,
          sdpPres  // Pitot Differential Pressure 999.99
          ,
          absPres // Atmospheric Pressure 9999.999
          ,
          ambTemp  // Air Temperature -99 .. +99
          ,
          ambHumi  // Air Relative Humidity 000 - 100
          ,
          batPrct  // Battery State of Charge % 000 - 100
          ,
          battChrg > 0 ? 1 : 0);  // Battery Charging Flag

  msg += buf;

  msg.concat(" \n");  // End of sentence notifier for TV
  printMSG(msg);      // Call the printMSG function to transmit to BLE then come back
}
// 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 ----------------------------------------