/****************************************************************************************************************************** Date: 10/03/2023 Filename: LabSage_v1.1.8 Description: merged v1.1.5 and 1.1.7 to make dynamic smoke and collision thresholds Update - Collision threshold can be adjusted via the rtdb if necessary. Smoke threshold auto updates. However if smoke has been detected for more than approx 20-30 seconds, the alarm must be reset manually via the app/rtdb. False alarms can occur, but they will stop ringing within 20-30 seconds if they are false. *****************************************************************************************************************************/ // INCLUDE LIBRARIES #include // Install TaskScheduler Arduino Library #include // Install WiFiManager Library #include // Install Firebase ESP8266 library (Mobitz?) #include #include #include // Install DHT11 Library and Adafruit Unified Sensor Library #include #include #include // Install Adafruit ADXL345 accelerometer library // FIREBASE RTDB PATH AND KEY #define FIREBASE_HOST "https://smartlab-9b83e-default-rtdb.firebaseio.com/" #define FIREBASE_AUTH "AIzaSyCiO5owTmUgosDyjL0_SqpxRoAwgKtn5-o" // SETTING PINS FOR SENSORS #define LED D1 #define DHTPIN D2 // Connect Data pin of DHT to D2 #define GASPIN A0 // Gas sensor analog pin #define BUZZERPIN D7 // Connect LED to D7 #define SDA D5 // Accelerometer Serial Data #define SCL D6 // Accelerometer Serial Clock // DHT VARIABLE SET #define DHTTYPE DHT11 // OBJECT DECLARATIONS Scheduler runner; // Multi tasking object WiFiManager wm; // Wi-Fi Manager Object DHT dht(DHTPIN, DHTTYPE); // DHT Object Adafruit_ADXL345_Unified accel = Adafruit_ADXL345_Unified(12345); // Accelerometer Object //MQ2 parameter for CO Gas sensing float gasSensor_volt; float RS; const float R0 = 2.12; const float b = 1.5120222; const float m = -0.33975668; float RS_R0_ratio; float ppm_log; float ppm; // VARIABLE DEFINITION float t, h, g; // Temp, Humidity, gas value (ppm) bool buzz = false; // Smoke alarm bool coll = false; // Collision Alarm float x, y, z; // Acceleration in x, y, z directions, storage variables for previous state float sum = 0; // checksum for smoke detection - false alarm avoidance unsigned int k = 0; // smoke alarm duration counter float sThreshSum = 0; // Sum used for threshold updating unsigned int j = 0; // Counter used for threshold updating String PATH = "Devices/"; // Base path on RTDB String UID = ""; // ESP Unique ID - Device ID necessary for dynamic path allocation // THRESHOLD VALUES // Not constant because it needs to be updated every min float COLLISION_THRESHOLD = 10.0; float SMOKE_THRESHOLD = 2000; // DEFINE FIREBASE DATA OBJECTs FirebaseData firebaseData; FirebaseData buzzerData; FirebaseData collData; FirebaseJson json; // FUNCTION DECLARATION void dhtSetup(void); void gasSetup(void); void ambiRead(void); void accelSetup(void); void accelRead(void); void sensorUpdate(void); void upload(void); void uploadAlert(void); void sensorReset(void); void reconnection(void); // TASK SCHEDULE FUNCTIONS void task1Callback() { // Sensor update // code for task 1 sensorUpdate(); } void task2Callback() { // Sensor value upload // code for task 2 if (WiFi.status() == WL_CONNECTED) { upload(); } else { Serial.println("Connection Lost!"); digitalWrite(LED, !digitalRead(LED)); Serial.println("Please Reconnect to a different WiFi.\nStarting Soft API..."); reconnection(); } } void task3Callback() { // Reset receive and execute // Check if there is a fire/smoke if (WiFi.status() == WL_CONNECTED) { if (Firebase.getBool(buzzerData, PATH + "/reset") && buzzerData.boolData() == true) { //Read from firebase for application Serial.println("Call to reset"); sensorReset(); if (Firebase.setBool(firebaseData, PATH + "/reset", false)) // Reset the 'reset' value on the database { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } uploadAlert(); // Resetting buzzer and collision values on rtdb } if (Firebase.getFloat(collData, PATH + "/collThresh") && collData.floatData() != COLLISION_THRESHOLD) { //Read from firebase for application COLLISION_THRESHOLD = collData.floatData(); Serial.print("New Collision Threshold = "); Serial.println(COLLISION_THRESHOLD); } if (buzz || coll) { uploadAlert(); // Setting the buzzer and collision values on rtdb } } else { Serial.println("Connection Lost!"); digitalWrite(LED, !digitalRead(LED)); Serial.println("Please Reconnect to a different WiFi.\nStarting Soft API..."); reconnection(); } } void task4Callback() { // Update Smoke sensor threshold // code for task 4 if (sThreshSum != 0) { // To avoid accidental wrongful updates if (j == 0) { // To avoid divide-by-zero error j = 1; } SMOKE_THRESHOLD = sThreshSum / j + 2000; // Taking an average value of gas sensor readings because depending on use, the threshold changes greatly Serial.print("Smoke threshold auto updated to "); Serial.println(SMOKE_THRESHOLD); sThreshSum = 0; // Reset these values for counting again j = 0; } if (WiFi.status() == WL_CONNECTED) { if (Firebase.setFloat(firebaseData, PATH + "/smokeThresh", SMOKE_THRESHOLD)) // Update Smoke Threshold { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } } } Task t1(1000, TASK_FOREVER, &task1Callback); // Task rate: Once every second Task t2(60000, TASK_FOREVER, &task2Callback); // Task rate: Once every minute Task t3(3000, TASK_FOREVER, &task3Callback); // Task rate: Once every 3 seconds Task t4(30000, TASK_FOREVER, &task4Callback); // Task rate: Once every 30 seconds // SETUP void setup() { // Set Pin Modes pinMode(LED, OUTPUT); digitalWrite(LED, LOW); pinMode(BUZZERPIN, OUTPUT); Serial.begin(9600); dhtSetup(); accelSetup(); gasSetup(); UID = ESP.getChipId(); // Extract Unique Chip ID for authentication and identity purposes Serial.printf("ESP8266 Unique Chip id = %s\n", UID); // For hex representation, use %08X Serial.println(); Serial.println("Opening WiFi AP"); // wm.resetSettings(); // Comment out before deployment bool res; wm.setConnectTimeout(60); wm.setConfigPortalTimeoutCallback(reconnection); wm.setWiFiAutoReconnect(true); res = wm.autoConnect("SMI_LBS_ESP"); // auto generated AP name from chipid wm.setShowInfoUpdate(false); wm.setShowInfoErase(false); if (!res) { Serial.println("Failed to connect"); } else { //if you get here you have connected to the WiFi Serial.println("Connected"); } Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH); // Begin Firebase Server Firebase.reconnectWiFi(true); Serial.println(PATH.concat(UID)); // Dynamic database path allocation if (Firebase.setFloat(firebaseData, PATH + "/smokeThresh", SMOKE_THRESHOLD)) //Uploading the default smoke threshold for the first time { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/collThresh", COLLISION_THRESHOLD)) // Uploading the default collision threshold for the first time { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } digitalWrite(LED, HIGH); runner.init(); // Initialise Scheduler, add tasks to it, and then enable the tasks for multitasking runner.addTask(t1); runner.addTask(t2); runner.addTask(t3); runner.addTask(t4); t1.enable(); t2.enable(); t3.enable(); t4.enable(); } // MAIN LOOP CODE void loop() { runner.execute(); // Run the tasks simultaneously } void reconnection(void) { // Re-establishing network with new Wi-Fi using Soft AP bool res = wm.autoConnect(); if (!res) { Serial.println("Failed to connect"); } else { Serial.println("Connected"); } } void accelSetup(void) { // Setup Accelerometer Serial.println("Accelerometer Test"); Serial.println(""); // Initialise I2C communication as MASTER Wire.begin(SDA, SCL); if (!accel.begin()) { Serial.println("Failed to initialize ADXL345 sensor."); while (1); } accel.setRange(ADXL345_RANGE_16_G); // Setting Max Range // Get accelerometer data sensors_event_t event; accel.getEvent(&event); // Assign current values to states x = event.acceleration.x; y = event.acceleration.y; z = event.acceleration.z; Serial.println("Accelerometer Ready!"); Serial.println(""); } void accelRead(void) { // Read Accelerometer // Get accelerometer data sensors_event_t event; accel.getEvent(&event); // Print the accelerometer data to serial monitor Serial.print("Acceleration: "); Serial.print(event.acceleration.x); Serial.print(" m/s^2 "); Serial.print(event.acceleration.y); Serial.print(" m/s^2 "); Serial.print(event.acceleration.z); Serial.println(" m/s^2 "); if (isnan(event.acceleration.x) || isnan(event.acceleration.y) || isnan(event.acceleration.z)) { Serial.println(F("Failed to read from Accelerometer!")); return; } // Check if there is a collision if ((abs(event.acceleration.x - x)) > COLLISION_THRESHOLD || (abs(event.acceleration.y - y)) > COLLISION_THRESHOLD || (abs(event.acceleration.z - z)) > COLLISION_THRESHOLD) { Serial.println("Collision detected!"); coll = true; tone(BUZZERPIN, 100); } // Assign current values to states x = event.acceleration.x; y = event.acceleration.y; z = event.acceleration.z; } void dhtSetup(void) { // Setup DHT11 Serial.println(F("DHT11 setting up...")); dht.begin(); Serial.println("DHT11 ready!"); } void gasSetup(void) { // Setup MQ-2 Smoke Sensor Serial.println("Gas Sensor setting up..."); pinMode(GASPIN, INPUT_PULLUP); // Smoke Threshold established by taking average of the first five readings from the sensor int cnt = 5; float temp = 0; for (int i = 0; i < cnt; i++) { float gasSensorValue = analogRead(GASPIN); gasSensor_volt = gasSensorValue / 1024 * 5.0; RS = ((5 - gasSensor_volt) / gasSensor_volt); RS_R0_ratio = RS / R0; ppm_log = ((log (RS_R0_ratio) - b) / m); ppm = pow (10, ppm_log); // Check if any reads failed and exit early (to try again). if (isnan(ppm)) { Serial.println(F("Failed to read from Gas sensor!")); return; } temp = temp + ppm; delay(1000); } SMOKE_THRESHOLD = SMOKE_THRESHOLD + (temp / cnt); // Taking an average value of gas sensor readings because depending on use, the threshold changes greatly Serial.println("Gas Sensor ready!"); Serial.printf("\nSmoke Threshold: %f ppm\n", SMOKE_THRESHOLD); Serial.println(""); } void ambiRead(void) { // Temp, Humidity, Gas reading // Read relative humidity in % (the default) h = dht.readHumidity(); // Read temperature as Celsius (the default) t = dht.readTemperature(); // Read gas sensor analog data float gasSensorValue = analogRead(GASPIN); gasSensor_volt = gasSensorValue / 1024 * 5.0; RS = ((5 - gasSensor_volt) / gasSensor_volt); RS_R0_ratio = RS / R0; ppm_log = ((log (RS_R0_ratio) - b) / m); ppm = pow (10, ppm_log); g = ppm; /* IF SMOKE THRESHOLD SHOULD BE UPDATED EVEN WITH THE SMOKE VALUES, UNCOMMENT THE FOLLOWING sThreshSum += g; j++; AND COMMENT IT IN LINES 412-413 (SUBJECT TO CHANGE) */ // Check if any reads failed and exit early (to try again). if (isnan(h) || isnan(t)) { Serial.println(F("Failed to read from DHT sensor!")); return; } if (isnan(g)) { Serial.println(F("Failed to read from Gas sensor!")); return; } // Smoke Detection if (g > SMOKE_THRESHOLD) { sum += g; k++; Serial.println("Smoke detected!"); buzz = true; tone(BUZZERPIN, 400); } else { // because smoke threshold ideally should not be updated for beyond limit values sThreshSum += g; j++; if (k <= 10) { // Temporary tolerance count is 10 readings. Could increase it depending on the tolerance desired. buzz = false; if (coll == false) { noTone(BUZZERPIN); } } } Serial.print(F("Humidity: ")); Serial.print(h); Serial.print(F("% Temperature: ")); Serial.print(t); Serial.print(F("C PPM:")); Serial.print(g); Serial.println(); } void sensorUpdate(void) { ambiRead(); accelRead(); } void sensorReset(void) { coll = false; buzz = false; noTone(BUZZERPIN); sum = 0; k = 0; } void upload(void) { // Uploading Sensor values if (Firebase.setFloat(firebaseData, PATH + "/temperature", t)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/humidity", h)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/ppm", g)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/x", x)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/y", y)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setFloat(firebaseData, PATH + "/z", z)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } } void uploadAlert(void) { // Uploading Alarm status if (Firebase.setBool(firebaseData, PATH + "/buzzer", buzz)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } if (Firebase.setBool(firebaseData, PATH + "/collision", coll)) { Serial.println("PASSED"); Serial.println("PATH: " + firebaseData.dataPath()); Serial.println("TYPE: " + firebaseData.dataType()); Serial.println("ETag: " + firebaseData.ETag()); Serial.println("------------------------------------"); Serial.println(); } else { Serial.println("FAILED"); Serial.println("REASON: " + firebaseData.errorReason()); Serial.println("------------------------------------"); Serial.println(); } }