/*Used libraries*/
#include <ArduinoJson.h>
#include <DHT.h>
#include <Wire.h>
#include <MPU6050.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include "MQ135.h"
#include "MQ7.h"
#include "SHT31.h"

/*Def. of Arduino UNO pins*/ 
/*Analog pins*/
#define MQ135_PIN  A1   
#define MQ7_PIN    A0   
#define DHT_PIN    13   

/*Digital pins*/
#define DALLAS_PIN      5  
#define HX711_SCK_PIN   3   
#define HX711_DT_PIN    2


/*Def. of code and sensors constants*/
#define DHT_TYPE DHT22
#define TEMPERATURE_PRECISION 9     
#define BAUD_RATE 115200         
#define WEIGHT_COIL 230
#define WEIGHT_HOLDER 50
#define WEIGHT_OFFSET 8317730
#define WEIGHT_SCALE 431.61               
#define VOLTAGE 5      

DeviceAddress devices[4];
MPU6050 mpu68;
MPU6050 mpu69;
MQ135 mq135 = MQ135(MQ135_PIN);
MQ7 mq7(MQ7_PIN, VOLTAGE);
DHT dht(DHT_PIN, DHT_TYPE);
SHT31 sht; 
OneWire oneWire(DALLAS_PIN);  
DallasTemperature dallas(&oneWire);
DeviceAddress DallasAddresses[8]; 

DeviceAddress MotorX   =  { 0x28, 0x58, 0x1D, 0x21, 0x0F, 0x00, 0x00, 0xA1 };
DeviceAddress MotorY   =  { 0x28, 0xC6, 0x49, 0x20, 0x0F, 0x00, 0x00, 0x06 };
DeviceAddress MotorE   =  { 0x28, 0xF6, 0x7B, 0x1F, 0x0F, 0x00, 0x00, 0x3E };
DeviceAddress MotorZ1  =  { 0x28, 0xC5, 0x49, 0x21, 0x0F, 0x00, 0x00, 0xD0 };
DeviceAddress MotorZ3  =  { 0x28, 0x4B, 0x80, 0x1F, 0x0F, 0x00, 0x00, 0xA5 };
DeviceAddress MotorZ2  =  { 0x28, 0x8A, 0x1B, 0x21, 0x0F, 0x00, 0x00, 0x97 };
DeviceAddress MotorZ4  =  { 0x28, 0x0C, 0x4D, 0x20, 0x0F, 0x00, 0x00, 0x49 };
DeviceAddress MotorJ   =  { 0x28, 0x91, 0x7A, 0x1F, 0x0F, 0x00, 0x00, 0xB5 };


/* Def. of global variables */
long start;                    
int period_iter = 0;
   
/* Function for reading and handling data */
void Get_data_Serialize(){
   long startTime = millis();

    /* JSON file with JSON objects */
    StaticJsonDocument<400> doc; 
    JsonObject Acc1 = doc.createNestedObject("Acc1");
    JsonObject Acc2 = doc.createNestedObject("Acc2"); 

    dallas.setWaitForConversion(false); 
    

    Get_MPU_Value(Acc1,Acc2);

    if ( period_iter == 5 )
        dallas.requestTemperatures();
    else if ( period_iter == 10 )
        Get_DALLAS_Value(0, MotorX, doc);
    else if ( period_iter == 40 )
        Get_DALLAS_Value(1, MotorY, doc);
    else if ( period_iter == 70 )
        Get_DALLAS_Value(2, MotorE, doc);
    else if ( period_iter == 100 )
        Get_DALLAS_Value(3, MotorZ1, doc);
    else if ( period_iter == 130 )
        Get_DALLAS_Value(4, MotorZ2, doc);
    else if ( period_iter == 160 )
        Get_DALLAS_Value(5, MotorZ3, doc);
    else if ( period_iter == 190 )
        Get_DALLAS_Value(6, MotorZ4, doc);
    else if ( period_iter == 220 )
        Get_DALLAS_Value(7, MotorJ, doc);

    else if ( period_iter == 240 )
        Get_YZC_Value(doc);

    else if ( period_iter == 250 )
        sht.read();

    else if ( period_iter == 270 )
    {
        JsonObject temp = doc.createNestedObject("Temp");
        Get_Temp(temp); 
    } 

    else if ( period_iter == 290 )
    {
        JsonObject hum = doc.createNestedObject("Hum"); 
        Get_Hum(hum);  
    }

    else if ( period_iter == 310 )
        Get_MQ135_Value(doc); 
    
    else if ( period_iter == 330 )
    {
        Get_MQ7_Value(doc); 
        period_iter = -1; 
    }     

    /* JSON data Serialization and sending */
    serializeJson(doc, Serial);    
    Serial.print("\n");

    /* Timestamp for periodic measuring inside loop function */
    long elapsedTime = millis() - startTime;
    if (elapsedTime < 15)
      delay(15 - elapsedTime);

    period_iter++;
}


/* Function for reading indoor temperatures */ 
void Get_Temp(JsonObject &temp)
{
    float tempLower = round(dht.readTemperature() * 10) / 10.0;
    float tempUpper = round(sht.getTemperature() * 10) / 10.0;

    /* Storing data to JSON file */
    temp["Bottom"] = tempLower;
    temp["Top"] = tempUpper;
}


/* Function for reading indoor humidity */ 
void Get_Hum(JsonObject &hum)
{
    float humLower = round(dht.readHumidity() * 10) / 10.0;  
    float humUpper = round(sht.getHumidity() * 10) / 10.0; 

    /* Storing data to JSON file */ 
    hum["Bottom"] = humLower;
    hum["Top"] = humUpper;
}


/* Function for reading gas concentration from sensor MQ-135 */ 
void Get_MQ135_Value(StaticJsonDocument<400> &doc)
{
    float temp = round(sht.getTemperature() * 10) / 10.0;
    float hum = round(sht.getHumidity() * 10) / 10.0; 

    doc["MQ135_PPM"] = round(mq135.getCorrectedPPM(temp, hum) * 10) / 10.0;
}

/* Function for reading carbon monoxide concentration from sensor MQ-7 */ 
void Get_MQ7_Value(StaticJsonDocument<400> &doc)
{
    doc["MQ7_PPM"] = round(mq7.readPpm() * 10) / 10.0;
}


/* Function for reading weight value from sensor YZC-131 */ 
void Get_YZC_Value(StaticJsonDocument<400> &doc)
{ 
  unsigned long weightRaw = 0;
  float weight;

  digitalWrite(HX711_DT_PIN, 1);
  digitalWrite(HX711_SCK_PIN, 0);

  while(digitalRead(HX711_DT_PIN));   // waiting for data to be ready

  for (byte i = 0; i < 24; i++)
  {
    digitalWrite(HX711_SCK_PIN, 1); 
    weightRaw = (weightRaw << 1) | digitalRead(HX711_DT_PIN);
    digitalWrite(HX711_SCK_PIN, 0);
  }

  digitalWrite(HX711_SCK_PIN, 1);
  weightRaw = weightRaw ^ 0x800000;
  digitalWrite(HX711_SCK_PIN, 0);

  /* Data conversion to weight units(g) */
  if(weightRaw > WEIGHT_OFFSET)
  {
    weight = weightRaw - WEIGHT_OFFSET;
    weight = weight / WEIGHT_SCALE;
  }
  else
     weight = 0;
    
  weight -= WEIGHT_COIL + WEIGHT_HOLDER;

  weight = round(weight);

  doc["YZC_Weight"] = weight;
}


/*Function for reading acceleration from sensors MPU-6050*/ 
void Get_MPU_Value(JsonObject &Acc1, JsonObject &Acc2){  
    Vector normAccel = mpu68.readNormalizeAccel();
    Vector normAccel2 = mpu69.readNormalizeAccel();

    float roundedX = round(normAccel.XAxis * 1000) / 1000.0;
    float roundedY = round(normAccel.YAxis * 1000) / 1000.0;
    float roundedZ = round(normAccel.ZAxis * 1000) / 1000.0;
    Acc1["X"] = roundedX;
    Acc1["Y"] = roundedY;
    Acc1["Z"] = roundedZ;

    roundedX = round(normAccel2.XAxis * 1000) / 1000.0;
    roundedY = round(normAccel2.YAxis * 1000) / 1000.0;
    roundedZ = round(normAccel2.ZAxis * 1000) / 1000.0;
    Acc2["X"] = roundedX;
    Acc2["Y"] = roundedY;
    Acc2["Z"] = roundedZ;
}


/*Function for reading acceleration from sensors MPU-6050*/ 
void Get_DALLAS_Value(size_t i, DeviceAddress deviceAddress, StaticJsonDocument<400> &doc)
{
    float tempC = dallas.getTempC(deviceAddress);

    if ( i == 0 )    doc["M_X_temp"]  = tempC;
    if ( i == 1 )    doc["M_Y_temp"]  = tempC;
    if ( i == 2 )    doc["M_E_temp"]  = tempC;
    if ( i == 3 )    doc["M_Z1_temp"] = tempC;
    if ( i == 4 )    doc["M_Z2_temp"] = tempC;
    if ( i == 5 )    doc["M_Z3_temp"] = tempC;
    if ( i == 6 )    doc["M_Z4_temp"] = tempC;
    if ( i == 7 )    doc["M_J_temp"]  = tempC;
    
}


/*Program initialization*/
void setup(){
    Serial.begin(BAUD_RATE);
    dht.begin(); 
    dallas.begin(); 
    pinMode(HX711_SCK_PIN, OUTPUT);
    pinMode(HX711_DT_PIN, INPUT);

    /* Setting I2C communication for sensors MPU-6050 and SHT31 */
    sht.begin(0x44); 
    mpu68.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G, 0x68);
    mpu69.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G, 0x69);
    mpu68.getSleepEnabled();
    mpu69.getSleepEnabled();  

    /* Setting DS18B20 sensors */
    dallas.getAddress(DallasAddresses[0], 0);
    dallas.getAddress(DallasAddresses[1], 1);
    dallas.getAddress(DallasAddresses[2], 2);
    dallas.getAddress(DallasAddresses[3], 3);
    dallas.getAddress(DallasAddresses[4], 4);
    dallas.getAddress(DallasAddresses[5], 7);
    dallas.getAddress(DallasAddresses[6], 6);
    dallas.getAddress(DallasAddresses[7], 5);  

    dallas.setResolution(10);

    /*Calibrating MQ7 sensor*/
    mq7.calibrate();
                 
}

/*Main loop function*/ 
void loop(){ 
    //start = millis();
    Get_data_Serialize();
    //Serial.print("Elapsed time: ");
    //Serial.println(millis() - start);
}

