//
  #include <Arduino.h>
  // Math library
  #include <math.h>
  // I2C Communication
  #include <Wire.h>
  // SGP30 - CO2 Sensor
  #include "Adafruit_SGP30.h"
  Adafruit_SGP30 sgp;
  //BlueTooth MESH
  #include "painlessMesh.h"
  //JSON file creation & Reading
  #include <Arduino_JSON.h>
  //#include <ArduinoJson.h>
  //RGB LED
  #include <FastLED.h>
  // ---------- Function declaration ----------
  // VMA320 - TempSensor
  void TempS_VMA320(int); //Temperature Sensor, Pin required, To measure temperature
  int VMS320_T;
  #define VMA320_PIN A0
  // SGP30 - CO2 Sensor
  void CO2S_SGP30_START(bool); //function to initialize the CO2 Sensor - SGP30
  void CO2S_SGP30(); //CO2 Sensor - SGP30
  int SGP30_TVOC;
  int SGP30_eCO2;
  int counter = 0; // Counter for CO2 Sensor
  uint32_t getAbsoluteHumidity(float, float); // Absolute humidity correction for CO2 Sensor
  // I2C Scanner -----------
  void I2C_Scan(); //I2C Scanner, no Pin required, To check I2C devices connected to the board
  //LES SETUP
  #define LED_PIN     D9 //GPIO //D0 //or D9
  #define NUM_LEDS    1
  #define BRIGHTNESS  32
  #define LED_TYPE    WS2811
  #define COLOR_ORDER GRB
  CRGB leds[NUM_LEDS];
  #define UPDATES_PER_SECOND 100
  CRGBPalette16 currentPalette;
  TBlendType    currentBlending;
  extern CRGBPalette16 myRedWhiteBluePalette;
  extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
  int R;
  int G;
  int B;
  void RGB_Send_OFF();
  void RGB_Send_ON();
  //Button
  #define Button_PIN1 43 //GPIO //D6
  #define Button_PIN2 7 //GPIO //D8
  //#define Button_PIN2 D6 //or D8
  // MESH -----------------
  //the name of the MESH network
  #define   MESH_PREFIX     "whateverYouLike"
  //password of the MESH network
  #define   MESH_PASSWORD   "somethingSneaky"
  // This refers to the the TCP port that you want the mesh server to run on. The default is 5555
  #define   MESH_PORT       5555
  //Number for this node
  int nodeNumber = 3;
  //Node ID to send a specific message 
  int Node_ID = 1; 
  //String to send to other nodes with sensor readings
  String final_message;
  //It is recommended to avoid using delay() in the mesh network code. 
  //To maintain the mesh, some tasks need to be performed in the background. 
  //Using delay() will stop these tasks from happening and can cause the mesh to lose stability/fall apart. 
  //The following line creates a new Scheduler called userScheduler
  Scheduler userScheduler; // to control your personal task
  //Create a painlessMesh object called mesh to handle the mesh network
  painlessMesh  mesh;
  // User stub
  void sendMessage() ; // Prototype so PlatformIO doesn't complain
  String Message(); // Prototype for sending message
  //Create a task called taskSendMessage responsible for calling the sendMessage() function every X (the number) second as long as the program is running.
  Task taskSendMessage( TASK_SECOND * 5 , TASK_FOREVER, &sendMessage );
  String Message() {
  JSONVar jsonReadings;
  jsonReadings["node"] = nodeNumber;
  jsonReadings["temperature"] = VMS320_T;
  jsonReadings["humidity"] = NAN;
  jsonReadings["CO2"] = SGP30_eCO2;
  jsonReadings["TVOC"] = SGP30_TVOC;
  // Add more sensor readings as needed
  final_message = JSON.stringify(jsonReadings);
  return final_message;
  }
  //The sendMessage() function sends a message to all nodes in the message network (broadcast).
  void sendMessage() {
  RGB_Send_ON();
  String msg = Message();
  //board chip ID
  //msg += mesh.getNodeId();
  //Replaced by nodeNumber
  //To broadcast a message, simply use the sendBroadcast() method on the mesh object and pass as argument the message (msg) you want to send.
  mesh.sendBroadcast(msg);
  //Send to specific Node
  //mesh.sendSingle(Node_ID, msg);
  //Every time a new message is sent, the code changes the interval between messages (one to five seconds).
  //taskSendMessage.setInterval( random( TASK_SECOND * 1, TASK_SECOND * 5 ));
  RGB_Send_OFF();
  Serial.println("Message Sent");
  }
  //several callback functions are created that will be called when specific events happen on the mesh.
  //The receivedCallback() function prints the message sender (from) and the content of the message (msg.c_str()).
  void receivedCallback( uint32_t from, String &msg ) {
  //Serial.printf("Received from %u msg=%s\n", from, msg.c_str());
  //The message comes in JSON format, so, we can access the variables as follows:
  JSONVar myObject = JSON.parse(msg.c_str());
  if (JSON.typeof(myObject) == "undefined") {
  Serial.println("Parsing input failed!");
  return;
  }
  int node = myObject["node"];
  int temperature_in = myObject["temperature"];
  int humidity_in = myObject["humidity"];
  int CO2_in = myObject["CO2"];
  int TVOC_in = myObject["TVOC"];
  //Serial.print(M1_I);
  }
  //The newConnectionCallback() function runs whenever a new node joins the network. 
  //This function simply prints the chip ID of the new node. 
  //You can modify the function to do any other task.
  void newConnectionCallback(uint32_t nodeId) {
     Serial.printf("--> startHere: New Connection, nodeId = %u\n", nodeId);
  }
  //The changedConnectionCallback() function runs whenever a connection changes on the network (when a node joins or leaves the network).
  void changedConnectionCallback() {
  Serial.printf("Changed connections\n");
  }
  //The nodeTimeAdjustedCallback() function runs when the network adjusts the time, so that all nodes are synchronized. 
  //It prints the offset.
  void nodeTimeAdjustedCallback(int32_t offset) {
     Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
  }
  // ---------- Setup ----------
  void setup() {
  // put your setup code here, to run once:
  Wire.begin(); // Initialize I2C communication
  Serial.begin(115200);   // Initialize Serial Monitor
  while (!Serial) { delay(10); } // Wait for serial console to open!
  pinMode(LED_BUILTIN, OUTPUT); // Set the built-in LED as output
  //LED
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
  FastLED.setBrightness(  BRIGHTNESS );
  currentPalette = RainbowColors_p;
  currentBlending = LINEARBLEND;
  pinMode(VMA320_PIN, INPUT);   // Set the pin input for VMA320 Thermistor / Temp Sensor
  // Initialize the CO2 Sensor - SGP30
  CO2S_SGP30_START(true);
  //MESH
  mesh.setDebugMsgTypes( ERROR | STARTUP );  // set before init() so that you can see startup messages
  //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  //Initialize the mesh with the details defined earlier.
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT );
  //Assign all the callback functions to their corresponding events.
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
  //Finally, add the taskSendMessage function to the userScheduler. 
  //The scheduler is responsible for handling and running the tasks at the right time.
  userScheduler.addTask( taskSendMessage );
  //Finally, enable the taskSendMessage, so that the program starts sending the messages to the mesh.
  taskSendMessage.enable();
  //Upload Check
  int time = 0;
  R = random(0,255);
  G = random(0,255);
  B = random(0,255);
  while (time <= 100){
     for(int i = 0; i < NUM_LEDS; i++ ){
        leds[i].setRGB(R,G,B);  // Set Color HERE!!!
     } 
     FastLED.show();
     FastLED.delay(1000 / UPDATES_PER_SECOND);
     time++;
  }
  R = 0;
  G = 0;
  B = 0;
  for(int i = 0; i < NUM_LEDS; i++ ){
     leds[i].setRGB(R,G,B);  // Set Color HERE!!!
  } 
  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
  }
  // ---------- Loop ----------
  void loop() {
  //I2C_Scan(); // Uncomment to scan I2C devices
  //RGB_Send_OFF()
  digitalWrite(LED_BUILTIN, HIGH); //high turn off the led ...
  TempS_VMA320(VMA320_PIN);
  CO2S_SGP30();
  mesh.update();
  //RGB_Send_ON()
  }
  // ---------- Function definitions ----------
  // ---------- CO2 Sensor - Sensirion SGP30 eCO2 & TVOC Sensor
  void CO2S_SGP30_START(bool initialize){
     if (!initialize) return;  // If false, exit function
     // Initialize I2C communication for the SGP30
     Serial.println("SGP30 test");
     if (! sgp.begin()){
        Serial.println("Sensor not found :(");
        while (1);
     }
     Serial.print("Found SGP30 serial #");
     Serial.print(sgp.serialnumber[0], HEX);
     Serial.print(sgp.serialnumber[1], HEX);
     Serial.println(sgp.serialnumber[2], HEX);
     // If you have a baseline measurement from before you can assign it to start, to 'self-calibrate'
     //sgp.setIAQBaseline(0x8E68, 0x8F41);  // Will vary for each sensor!
  }
  /* return absolute humidity [mg/m^3] with approximation formula
  * @param temperature [°C]
  * @param humidity [%RH]
  */
  uint32_t getAbsoluteHumidity(float temperature, float humidity) {
     // approximation formula from Sensirion SGP30 Driver Integration chapter 3.15
     const float absoluteHumidity = 216.7f * ((humidity / 100.0f) * 6.112f * exp((17.62f * temperature) / (243.12f + temperature)) / (273.15f + temperature)); // [g/m^3]
     const uint32_t absoluteHumidityScaled = static_cast<uint32_t>(1000.0f * absoluteHumidity); // [mg/m^3]
     return absoluteHumidityScaled;
  }
  void CO2S_SGP30(){
     // If you have a temperature / humidity sensor, you can set the absolute humidity to enable the humidity compensation for the air quality signals
     //float temperature = 22.1; // [°C]
     //float humidity = 45.2; // [%RH]
     //sgp.setHumidity(getAbsoluteHumidity(temperature, humidity));
     if (! sgp.IAQmeasure()) {
        Serial.println("Measurement failed");
        //return;
     }
     Serial.print("TVOC "); 
     float TVOC = sgp.TVOC;
     Serial.print(TVOC); 
     Serial.print(" ppb\t");
     Serial.print("eCO2 "); 
     float eCO2 = sgp.eCO2;
     Serial.print(eCO2); 
     Serial.println(" ppm");
     /*
     if (! sgp.IAQmeasureRaw()) {
        Serial.println("Raw Measurement failed");
        //return;
     }
     Serial.print("Raw H2 "); 
     Serial.print(sgp.rawH2); 
     Serial.print(" \t");
     Serial.print("Raw Ethanol "); 
     Serial.print(sgp.rawEthanol); 
     Serial.println("");
     */
     delay(1000);
     /*
     counter++;
     if (counter == 30) {
        counter = 0;
        uint16_t TVOC_base, eCO2_base;
        if (! sgp.getIAQBaseline(&eCO2_base, &TVOC_base)) {
        Serial.println("Failed to get baseline readings");
        return;
        }
        Serial.print("****Baseline values: eCO2: 0x"); 
        Serial.print(eCO2_base, HEX);
        Serial.print(" & TVOC: 0x"); 
        Serial.println(TVOC_base, HEX);
     }
     */
     SGP30_eCO2 = eCO2;
     SGP30_TVOC = TVOC;
     //Code modified from Adafruit SGP30 example
  }
  // ---------- Temperature Sensor - VMA 320
  void TempS_VMA320(int pin){
     float reading;
     // The beta coefficient of the thermistor (usually 3000-4000)
     #define BCOEFFICIENT 3950
     // the value of the 'other' resistor
     #define SERIESRESISTOR 10000     
     // resistance at 25 degrees C
     #define THERMISTORNOMINAL 10000      
     // temp. for nominal resistance (almost always 25 C)
     #define TEMPERATURENOMINAL 25 
     reading  = analogRead(pin);
     reading = 4095 / reading - 1;
     reading = SERIESRESISTOR / reading;
     //Serial.print("Thermistor resistance "); 
     //Serial.println(reading);
     float steinhart;
     steinhart = reading / THERMISTORNOMINAL;     // (R/Ro)
     steinhart = log(steinhart);                  // ln(R/Ro)
     steinhart /= BCOEFFICIENT;                   // 1/B * ln(R/Ro)
     steinhart += 1.0 / (TEMPERATURENOMINAL + 273.15); // + (1/To)
     steinhart = 1.0 / steinhart;                 // Invert
     steinhart -= 273.15;                         // convert absolute temp to C
     //Serial.print("Temperature "); 
     //Serial.print(steinhart);
     //Serial.println(" *C");
     VMS320_T = steinhart;
     //Modified from: https://learn.adafruit.com/thermistor/using-a-thermistor
  }
  // ---------- Integrated LED Blinking
  void BlinkLED(int time) {
  digitalWrite(LED_BUILTIN, HIGH);
  Serial.println("ON");
  delay(time);
  digitalWrite(LED_BUILTIN, LOW);
  delay(time);
  Serial.println("OFF");
  }
  // ---------- SEND RGB LED Blinking
  void RGB_Send_ON(){
  int time = 0;
  R = random(0,255);
  G = random(0,255);
  B = random(0,255);
  while (time <= 100){
  for(int i = 0; i < NUM_LEDS; i++ ){
     leds[i].setRGB(R,G,B);  // Set Color HERE!!!
  } 
  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
  time++;
  }}
  void RGB_Send_OFF(){
  R = 0;
  G = 0;
  B = 0;
  for(int i = 0; i < NUM_LEDS; i++ ){
  leds[i].setRGB(R,G,B);  // Set Color HERE!!!
  } 
  FastLED.show();
  FastLED.delay(1000 / UPDATES_PER_SECOND);
  }
  // ---------- I2C Scanner
  void I2C_Scan() {
  byte error, address;
  int nDevices;
  Serial.println("Scanning...");
  nDevices = 0;
  for(address = 1; address < 127; address++ ) {
     Wire.beginTransmission(address);
     error = Wire.endTransmission();
     if (error == 0) {
        Serial.print("I2C device found at address 0x");
        if (address<16) {
        Serial.print("0");
        }
        Serial.println(address,HEX);
        nDevices++;
     }
     else if (error==4) {
        Serial.print("Unknown error at address 0x");
        if (address<16) {
        Serial.print("0");
        }
        Serial.println(address,HEX);
     }    
  }
  if (nDevices == 0) {
     Serial.println("No I2C devices found\n");
  }
  else {
     Serial.println("done\n");
  }
  delay(5000);          
  }