/*
Google Apps Script:
//________________________________________________________________________________Google Sheets and Google Apps Script Project Information.
Google Sheets Project Name : ESP32_Google_Sheets_Sheet_rev1
Google Sheets ID : 'ID' sheet
Sheet Name : ESP32_Google_Sheets_Sheet_rev1
DHT22 Sensor Data Logger
Date | Time | Sensor Reading Status | Temperature (°C) | Humidity (%) | HumiditySetpoint (°C)| TemperatureSetpoint (%)
Latest DHT22 Sensor Data
Date | Time | Sensor Reading Status | Temperature (°C) | Humidity (%) | HumiditySetpoint (°C)| TemperatureSetpoint (%)
Google Apps Script Project Name : ESP32_Google_Spreadsheet_Apps_Script
Web app URL :
Web app URL Test Write :
https://script.google.com/macros/s/'ID'/exec?sts=write&Data01=Success&Data02=32.5&Data03=95&Data04=Off&Data05=Off
Web app URL Test Read :
https://script.google.com/macros/s/'ID'/exec?sts=read
//________________________________________________________________________________
function doGet(e)
{
Logger.log(JSON.stringify(e));
var result = 'Ok';
if (e.parameter == 'undefined')
{
result = 'No Parameters';
}
else
{
var sheet_id = ''; // Spreadsheet ID.
var sheet_name = "ESP32_Google_Spreadsheet"; // Sheet Name in Google Sheets.
var sheet_open = SpreadsheetApp.openById(sheet_id);
var sheet_target = sheet_open.getSheetByName(sheet_name);
var newRow = sheet_target.getLastRow() + 1;
var rowDataLog = [];
var Data_for_I3;
var Data_for_J3;
var Data_for_K3;
var Data_for_L3;
var Data_for_M3;
var Data_for_N3;
var Data_for_O3;
var Curr_Date = Utilities.formatDate(new Date(), "Asia/Jakarta", 'dd/MM/yyyy');
rowDataLog[0] = Curr_Date; // Date will be written in column A (in the "DHT11 Sensor Data Logger" section).
Data_for_I3 = Curr_Date; // Date will be written in column I3 (in the "Latest DHT11 Sensor Data" section).
var Curr_Time = Utilities.formatDate(new Date(), "Asia/Jakarta", 'HH:mm:ss');
rowDataLog[1] = Curr_Time; // Time will be written in column B (in the "DHT11 Sensor Data Logger" section).
Data_for_J3 = Curr_Time; // Time will be written in column J3 (in the "Latest DHT11 Sensor Data" section).
var sts_val = '';
for (var param in e.parameter) {
Logger.log('In for loop, param=' + param);
var value = stripQuotes(e.parameter[param]);
Logger.log(param + ':' + e.parameter[param]);
switch (param) {
case 'sts':
sts_val = value;
break;
case 'Data01':
rowDataLog[2] = value; // Sensor Reading Status will be written in column C (in the "DHT11 Sensor Data Logger" section).
Data_for_K3 = value; // Sensor Reading Status will be written in column K3 (in the "Latest DHT11 Sensor Data" section).
result += ', Sensor Reading Status Written on column C';
break;
case 'Data02':
rowDataLog[3] = value; // The temperature value will be written in column D (in the "DHT11 Sensor Data Logger" section).
Data_for_L3 = value; // The temperature value will be written in column L3 (in the "Latest DHT11 Sensor Data" section).
result += ', Temperature Written on column D';
break;
case 'Data03':
rowDataLog[4] = value; // The humidity value will be written in column E (in the "DHT11 Sensor Data Logger" section).
Data_for_M3 = value; // The humidity value will be written in column M3 (in the "Latest DHT11 Sensor Data" section).
result += ', Humidity Written on column E';
break;
case 'Data04':
rowDataLog[5] = value; // The state of Switch_1 will be written in column F (in the "DHT11 Sensor Data Logger" section).
Data_for_N3 = value; // The state of Switch_1 will be written in column N3 (in the "Latest DHT11 Sensor Data" section).
result += ', Switch_1 Written on column F';
break;
case 'Data05':
rowDataLog[6] = value; // The state of Switch_2 will be written in column G (in the "DHT11 Sensor Data Logger" section).
Data_for_O3 = value; // The state of Switch_2 will be written in column O3 (in the "Latest DHT11 Sensor Data" section).
result += ', Switch_2 Written on column G';
break;
default:
result += ", unsupported parameter";
}
}
// Conditions for writing data received from ESP32 to Google Sheets.
if (sts_val == 'write')
{
// Writes data to the "DHT11 Sensor Data Logger" section.
Logger.log(JSON.stringify(rowDataLog));
var newRangeDataLog = sheet_target.getRange(newRow, 1, 1, rowDataLog.length);
newRangeDataLog.setValues([rowDataLog]);
// Write the data to the "Latest DHT11 Sensor Data" section.
var RangeDataLatest = sheet_target.getRange('I3:O3');
RangeDataLatest.setValues([[Data_for_I3, Data_for_J3, Data_for_K3, Data_for_L3, Data_for_M3, Data_for_N3, Data_for_O3]]);
return ContentService.createTextOutput(result);
}
// Conditions for sending data to ESP32 when ESP32 reads data from Google Sheets.
if (sts_val == 'read')
{
// Use the line of code below if you want ESP32 to read data from columns I3 to O3 (Date,Time,Sensor Reading Status,Temperature,Humidity,Switch 1, Switch 2).
// var all_Data = sheet_target.getRange('I3:O3').getDisplayValues();
// Use the line of code below if you want ESP32 to read data from columns K3 to O3 (Sensor Reading Status,Temperature,Humidity,Switch 1, Switch 2).
var all_Data = sheet_target.getRange('K3:O3').getValues();
return ContentService.createTextOutput(all_Data);
}
}
}
function stripQuotes( value )
{
return value.replace(/^["']|['"]$/g, "");
}
*/
/***************************************************************************/
/***************************************************************************/
// IMPORTANT!! add "attrs": { "fastTLS": "1" } in wokwi diagram.json for esp board
#include <DHT.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ESP32Servo.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <PubSubClient.h> //SYSTEM PARAMETER - MQTT Library (By: Adafruit)
#define DHTPIN 4 // DHT22 sensor pin
#define DHTTYPE DHT22 // DHT11/22 sensor type
#define Delay_ms (2000)
DHT dht(DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x27, 20,2);
WiFiClient espClient;
PubSubClient client(espClient);
Servo servo;
#define SWITCH_ON (18) //Pin D18 SWITCH ON
#define SWITCH_OFF (19) //Pin D19 SWITCH OFF
#define BUTTON_START digitalRead(SWITCH_ON) == 1 // Set Input Pin
#define BUTTON_STOP digitalRead(SWITCH_OFF) == 1 // Set Input Pin
const char* ssid = "Wokwi-GUEST"; // Wifi name
const char* password = ""; // Wifi password
const char* webAppUrl = "https://script.google.com/macros/s/AKfycbx86YpXQAPrHOY4U5VDNxuvzO7hkZUSop7G_I_0o018Wc1qjvtTnZFSCFggMfkqsVUrmA/exec"; // Replace with your actual web app URL
const char* mqtt_server = "broker.hivemq.com"; // ที่อยู่เซิร์ฟเวอร์ MQTT | Set your MQTT broker IP
String Switch_1_State = "";
String Switch_2_State = "";
String Status_Read_Sensor = "";
// Define the pins for RGB LED
#define redPin (27) // Pin for red LED
#define greenPin (26) // Pin for green LED
#define bluePin (25) // Pin for blue LED
#define redPin1 (33) // Pin for second red LED
#define greenPin1 (32) // Pin for second green LED
#define bluePin1 (14) // Pin for second blue LED
#define servoPin (15) // Pin for servo motor
#define adc_temp_min (-40) // Minimum temperature for ADC
#define adc_temp_max (80) // Maximum temperature for ADC
#define adc_humi_min (0) // Minimum humidity for ADC
#define adc_humi_max (80) // Maximum humidity for ADC
#define adc_res_min (0) // Minimum ADC resolution
#define adc_res_max (4095) // Maximum ADC resolution
#define adcPinA0 (34) // Analog pin for temperature set point
#define step_SetPointA0 (analogRead(adcPinA0)) // Read analog value from pin A0
#define tempSetPoint (map(step_SetPointA0, adc_res_min, adc_res_max, adc_temp_min, adc_temp_max)) // Map analog value to temperature set point
#define adcPinA1 (35) // Analog pin for humidity set point
#define step_SetPointA1 (analogRead(adcPinA1)) // Read analog value from pin A1
#define humiSetPoint (map(step_SetPointA1, adc_res_min, adc_res_max, adc_humi_min, adc_humi_max)) // Map analog value to humidity set point
// PID parameters
double Kp_temp, Ki_temp, Kd_temp, Kp_humi, Ki_humi, Kd_humi; // PID coefficients
// PID control variables
double input_temp = 0.0, output_temp = 0.0, lastInput_temp = 0.0, integral_temp = 0.0; // Variables for temperature control
double input_humi = 0.0, output_humi = 0.0, lastInput_humi = 0.0, integral_humi = 0.0; // Variables for humidity control
// PID output limits
double outputMin = 0.0, outputMax = 100.0; // Minimum and maximum PID output
// Timing variables for PID calculation
unsigned long prevTime = 0; // Previous time for PID calculation
unsigned long deltaTime = 10000; // Time interval for PID calculation
volatile unsigned long
lastime_0 = 0,
lastime_1 = 0,
lastime_2 = 0,
lastime_3 = 0,
last_time = 0;
uint16_t XY_MD02_data[10];
char XY_MD02msg[10]; // Sent data to MQTT
int val; // Variable to read the value from the analog pin
int position; // Variable to store the servo position
// CHR parameters for PID tuning
double L = 0.1; // Dead time
double T = 1.0; // Time constant
char Addr = 0x00;
/***************************************************************************/
/***************************************************************************/
void setup()
{
Serial.begin(115200);
delay(100);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(1000);
Serial.println("Connecting to WiFi...");
}
Serial.println("Connected to WiFi");
pinMode(redPin, OUTPUT); // Set red LED pin as output
pinMode(greenPin, OUTPUT); // Set green LED pin as output
pinMode(bluePin, OUTPUT); // Set blue LED pin as output
pinMode(redPin1, OUTPUT); // Set second red LED pin as output
pinMode(greenPin1, OUTPUT); // Set second green LED pin as output
pinMode(bluePin1, OUTPUT); // Set second blue LED pin as output
dht.begin();
servo.attach(servoPin, 500, 2400); // Servo instance
// ตั้งค่า MQTT
client.setServer(mqtt_server, 1883);
Addr = ScanI2Address();
lcd.init(); // initial LCD on lib
//lcd.begin(); // initial LCD on lib
lcd.setBacklight(HIGH);
lcd.setCursor(4,0);
lcd.print("DHT-22");
lcd.setCursor(2,1);
lcd.print("TempControl");
delay(1500);
lcd.clear();
// Calculate PID parameters using CHR method
Kp_temp = 0.6 * T / L; // Calculate Kp for temperature
Ki_temp = 1.2 * L; // Calculate Ki for temperature
Kd_temp = 0.5 * L; // Calculate Kd for temperature
Kp_humi = 0.6 * T / L; // Calculate Kp for humidity
Ki_humi = 1.2 * L; // Calculate Ki for humidity
Kd_humi = 0.5 * L; // Calculate Kd for humidity
delay(1500);
}
/***************************************************************************/
/***************************************************************************/
void loop()
{
float humidity = dht.readHumidity();
float temperature = dht.readTemperature();
if (isnan(humidity) || isnan(temperature))
{
Status_Read_Sensor = "Failed";
temperature = 0.00;
humidity = 0.00;
Serial.println("Failed to read from DHT sensor");
delay(2000);
return;
}
else
{
Status_Read_Sensor = "Success";
}
// Send data to Google Apps Script
//sendDataToScript(temperature, humidity,humiSetPoint ,humiSetPoint);
Read_Switches_State();
float currentHumidity = humidity;
float currentTemperature = temperature;
unsigned long currentTime = millis(); // Get current time in milliseconds
if (currentTime - prevTime >= deltaTime) // Check if it's time to update the PID controller
{
// Send data to Google Apps Script
sendDataToScript(temperature, humidity,humiSetPoint ,humiSetPoint);
double dt = (double)(currentTime - prevTime) / 1000.0; // Calculate time difference in seconds
// Temperature control
input_temp = currentTemperature; // Update input with current temperature
double error_temp = tempSetPoint - input_temp; // Calculate error
integral_temp += error_temp * dt; // Accumulate integral term
double dInput_temp = (input_temp - lastInput_temp) / dt; // Calculate derivative term
output_temp = Kp_temp * error_temp + Ki_temp * integral_temp + Kd_temp * dInput_temp; // Calculate PID output
output_temp = constrain(output_temp, outputMin, outputMax); // Constrain output to specified limits
lastInput_temp = input_temp; // Update last input
// Humidity control
input_humi = currentHumidity; // Update input with current humidity
double error_humi = humiSetPoint - input_humi; // Calculate error
integral_humi += error_humi * dt; // Accumulate integral term
double dInput_humi = (input_humi - lastInput_humi) / dt; // Calculate derivative term
output_humi = Kp_humi * error_humi + Ki_humi * integral_humi + Kd_humi * dInput_humi; // Calculate PID output
output_humi = constrain(output_humi, outputMin, outputMax); // Constrain output to specified limits
lastInput_humi = input_humi; // Update last input
prevTime = currentTime; // Update previous time
servo_control(output_temp, output_humi); // Control the servo based on PID output
// Print PID data to serial monitor
Serial.print("Temp SP: ");
Serial.print(tempSetPoint);
Serial.print(" | Input Temp: ");
Serial.print(input_temp);
Serial.print(" | Temp Output: ");
Serial.print(output_temp);
Serial.print(" | Humi SP: ");
Serial.print(humiSetPoint);
Serial.print(" | Input Humi: ");
Serial.print(input_humi);
Serial.print(" | Humi Output: ");
Serial.print(output_humi);
Serial.print(" | Temperature: ");
Serial.print(temperature);
Serial.print(" | °C, Humidity: ");
Serial.print(humidity);
Serial.println(" %");
// Control LEDs based on temperature
if (tempSetPoint < currentTemperature)
{
redLED(); // Turn on red LED if temperature is above setpoint
}
else
{
greenLED(); // Turn on green LED if temperature is below setpoint
}
// Control LEDs based on humidity
if (humiSetPoint < currentHumidity)
{
redLED1(); // Turn on red LED if humidity is above setpoint
}
else
{
greenLED1(); // Turn on green LED if humidity is below setpoint
}
}
LCDdisplay(temperature, humidity);
MQTTdataPublish(temperature, humidity,humiSetPoint ,humiSetPoint);
//delay(5000); // Delay for 5 seconds
}
/***************************************************************************/
/***************************************************************************/
char ScanI2Address(void)
{
// Leonardo: wait for serial port to connect
while (!Serial)
{
}
Serial.println ();
Serial.println ("I2C scanner. Scanning ...");
byte count = 0;
byte addr = 0;
Wire.begin();
for (byte i = 8; i < 120; i++)
{
Wire.beginTransmission (i);
if (Wire.endTransmission () == 0)
{
Serial.print ("Found address: ");
Serial.print (i, DEC);
Serial.print (" (0x");
Serial.print (i, HEX);
Serial.println (")");
count++;
addr = i;
delay (1); // maybe unneeded?
} // end of good response
} // end of for loop
Serial.println ("Done.");
Serial.print ("Found ");
Serial.print (count, DEC);
Serial.println (" device(s).");
return(addr);
}
/***************************************************************************/
/***************************************************************************/
void MQTTdataPublish(float temperature, float humidity,float TempSetPoint,float HumiSetPoint )
{
if (millis() - lastime_0 > Delay_ms)
{
lastime_0 = millis(); // รีเซ็ตตัวจับเวลา
String data = "{\"XY-MD02\":{\"Temp\":"+ String(temperature) +
",\"Humi\":" + String(humidity) +
",\"SP_Temp\":" + String(tempSetPoint) +
",\"SP_Humi\":" + String(humiSetPoint) +
"}}";
Serial.println(data);
data.toCharArray(XY_MD02msg, (data.length() + 1));
client.publish("Jakrapan", XY_MD02msg); // publish topic : Jakrapan ; data :
}
}
/***************************************************************************/
/***************************************************************************/
void sendDataToScript(float temperature, float humidity,float TempSetPoint,float HumiSetPoint )
{
HTTPClient http;
// Your server address
String serverPath = String(webAppUrl)
+ "?sts=write"
+ "&Data01=" + Status_Read_Sensor
+ "&Data02=" + String(temperature)
+ "&Data03=" + String(humidity)
+ "&Data04=" + String(TempSetPoint)
+ "&Data05=" + String(HumiSetPoint);
Serial.print("Connecting to server: ");
Serial.println(serverPath);
// Send HTTP GET request
if (http.begin(serverPath))
{
int httpCode = http.GET();
if (httpCode > 0)
{
Serial.print("Server response code: ");
Serial.println(httpCode);
}
else
{
Serial.print("HTTP GET request failed with error code: ");
Serial.println(httpCode);
}
http.end();
}
else
{
Serial.println("Unable to connect to the server");
}
}
/***************************************************************************/
/***************************************************************************/
void Read_Switches_State()
{
if (BUTTON_START == LOW) Switch_1_State = "Off";
if (BUTTON_START == HIGH) Switch_1_State = "On";
if (BUTTON_STOP == LOW) Switch_2_State = "Off";
if (BUTTON_STOP == HIGH) Switch_2_State = "On";
}
/***************************************************************************/
/***************************************************************************/
void servo_control(double output_temp, double output_humi)
{
if ((output_temp < input_temp) && (output_humi < input_humi))
{
val = output_temp; // Use PID output to control the servo
val = map(val, outputMin, outputMax, 0, 180); // Scale output to servo range (0-180)
position = val; // Set position to scaled value
servo.write(position); // Set servo position
}
}
/***************************************************************************/
/***************************************************************************/
void LCDdisplay(float temperature, float humidity)
{
if (millis() - lastime_1 > Delay_ms)
{
lastime_1 = millis(); // รีเซ็ตตัวจับเวลา
lcd.setBacklight(HIGH);
lcd.setCursor(0,0);
lcd.print("Temp:"+ String(temperature) +" C");
lcd.setCursor(0,1);
lcd.print("Humi:"+ String(humidity) +" %RH");
}
}
/***************************************************************************/
// Function to turn on the red LED and turn off others
/***************************************************************************/
void redLED()
{
digitalWrite(redPin, LOW); // Turn on red LED
digitalWrite(greenPin, HIGH); // Turn off green LED
digitalWrite(bluePin, HIGH); // Turn off blue LED
}
/***************************************************************************/
// Function to turn on the green LED and turn off others
/***************************************************************************/
void greenLED()
{
digitalWrite(redPin, HIGH); // Turn off red LED
digitalWrite(greenPin, LOW); // Turn on green LED
digitalWrite(bluePin, HIGH); // Turn off blue LED
}
/***************************************************************************/
// Function to turn on the second red LED and turn off others
/***************************************************************************/
void redLED1()
{
digitalWrite(redPin1, LOW); // Turn on red LED
digitalWrite(greenPin1, HIGH); // Turn off green LED
digitalWrite(bluePin1, HIGH); // Turn off blue LED
}
/***************************************************************************/
// Function to turn on the second green LED and turn off others
/***************************************************************************/
void greenLED1()
{
digitalWrite(redPin1, HIGH); // Turn off red LED
digitalWrite(greenPin1, LOW); // Turn on green LED
digitalWrite(bluePin1, HIGH); // Turn off blue LED
}
/***************************************************************************/
/***************************************************************************/