Page 1 of 1

Code improvement (Beginner)

Posted: Sat Oct 05, 2024 8:26 am
by Vishvesh
Hello people,
This is my first time on this forum. I, as the title says am relatively new to ESP development. This is probably my first fairly functioning(?) sketch. I have tried to recreate a smart switch (multiple nodes) with a globally accessible control page (Firebase).

I just wanted to know how this would hold up in the real world. Any criticism/improvement ideas are welcome. :idea:
I have tired to keep the sketch simple and fairly readable.

Board used: NodeMCU ESP8266 v3 LUA + 2-ch Relay Module with optocoupler (low-active)

Had to settle on board firmware 2.7.x due to library compatibility and was facing problems with newer board firmware versions and the new Firebase library (So decided to ditch the new one)

Code (Arduino IDE v2.3.3):

Code: Select all

#include <Arduino.h>
#if defined(ESP32)
  #include <WiFi.h>
#elif defined(ESP8266)
  #include <ESP8266WiFi.h>
#endif
#include <Firebase_ESP_Client.h>

#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"

#include <TaskScheduler.h>
#include <time.h>
#include <coredecls.h> // optional settimeofday_cb() callback to check on server
#include <EEPROM.h>

time_t now;                         // this are the seconds since Epoch (1970) - UTC
tm tm;                              // the structure tm holds time information in a more convenient way
#define MY_NTP_SERVER "1.in.pool.ntp.org"           
#define MY_TZ "IST-5:30"  

#define EEPROM_SIZE 64  // Size of EEPROM memory to reserve

// Insert your network credentials
#define WIFI_SSID "*****"
#define WIFI_PASSWORD "*****"

#define API_KEY "*****"
#define USER_EMAIL "*****"
#define USER_PASSWORD "*****"
#define DATABASE_URL "*****"

bool wifiConnected = false;  // Flag to track whether WiFi is connected
const int wifiTimeout = 5 * 60 * 1000;  // 5 minutes (in milliseconds) timeout

// Define Firebase objects
FirebaseData stream;
FirebaseAuth auth;
FirebaseConfig config;

// Variables to save database paths
String listenerPath = "board1/outputs/digital/";

// Declare outputs
const int output1 = 12;
const int output2 = 13;
const int output3 = 14;
const int gpioPin = 5;  
const int LED = 2;  

Scheduler ts;

// Function declarations (forward declarations)
void turnOn();
void turnOff();
void turnOn1();
void turnOff1();
void turnOn2();
void turnOff2();
void turnOn3();
void turnOff3();
void checkTimeTaskCallback();
void bonus();

int startHour = 05;     //Hard coded relay
int startMinute = 45;   //Hard coded relay
int startHour1;
int startMinute1;
int startHour2;
int startMinute2;
int startHour3;
int startMinute3;
int trimdelay = 60000;  //Hard coded relay
int trimdelay1;
int trimdelay2;
int trimdelay3;

Task turnOffTask(0, TASK_ONCE, &turnOff); // Declare the turnOff task with no delay initially
Task turnOff1Task(0, TASK_ONCE, &turnOff1); // Declare the turnOff task with no delay initially
Task turnOff2Task(0, TASK_ONCE, &turnOff2); // Declare the turnOff task with no delay initially
Task turnOff3Task(0, TASK_ONCE, &turnOff3); // Declare the turnOff task with no delay initially

Task checkTimeTask(60000, TASK_FOREVER, &checkTimeTaskCallback); // Check every 60 seconds

Task rebootTask(86400000, TASK_ONCE, []() {
  Serial.println("Rebooting ESP due to offline mode timeout...");
  ESP.restart();  // Reboot the ESP
});

Task bonusTask(0 , TASK_ONCE, &bonus); // Bonus

void time_is_set() {     // no parameter until 2.7.4
  // Update Firebase
  Firebase.RTDB.setInt(&stream, listenerPath + "lastonlinehour", tm.tm_hour);
  Firebase.RTDB.setInt(&stream, listenerPath + "lastonlinemin", tm.tm_min);
  Serial.println("Last online update");
}

// Function to write an integer to a specific EEPROM address
void writeIntToEEPROM(int address, int value) {
  int currentValue = readIntFromEEPROM(address);
  if (currentValue != value) {
    EEPROM.put(address, value);
    EEPROM.commit();
    Serial.print("Written to EEPROM: ");
    Serial.println(value);
    Serial.println();
  } else {
    Serial.println("No EEPROM update needed for ");
    Serial.print(value);
  }
}

// Function to read an integer from a specific EEPROM address
int readIntFromEEPROM(int address) {
  int value;
  EEPROM.get(address, value);  // Read the integer value from the specified address
  return value;
}

void turnOn() {
  digitalWrite(gpioPin, LOW);
  Serial.println("GPIO is LOW ts");
  turnOffTask.restartDelayed(trimdelay); // Turn off GPIO after 5 minutes
}
void turnOn1() {
  digitalWrite(output1, LOW);
  Serial.println("GPIO1 is LOW (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "12", 1)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
  turnOff1Task.restartDelayed(trimdelay1); // Turn off GPIO after 5 minutes
}
void turnOn2() {
  digitalWrite(output2, LOW);
  Serial.println("GPIO2 is LOW (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "13", 1)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
  turnOff2Task.restartDelayed(trimdelay2); // Turn off GPIO after 5 minutes
}
void turnOn3() {
  digitalWrite(output3, LOW);
  Serial.println("GPIO3 is LOW (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "14", 1)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
  turnOff3Task.restartDelayed(trimdelay3); // Turn off GPIO after 5 minutes
}

void turnOff() {
  digitalWrite(gpioPin, HIGH);
  Serial.println("GPIO is HIGH ts");
}
void turnOff1() {
  digitalWrite(output1, HIGH);
  Serial.println("GPIO1 is HIGH (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "12", 0)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
}
void turnOff2() {
  digitalWrite(output2, HIGH);
  Serial.println("GPIO2 is HIGH (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "13", 0)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
}
void turnOff3() {
  digitalWrite(output3, HIGH);
  Serial.println("GPIO3 is HIGH (ts)");
  if (wifiConnected) {
    if (Firebase.RTDB.setInt(&stream, listenerPath + "14", 0)) {
      Serial.println("GPIO state hour update");
    } else {
      Serial.print("Failed to update GPIO state");
    }
  }
}

void bonus(){
  delay(1000);
  turnOn1();
  delay(1000);
  turnOn2();
  delay(1000);
  turnOn3();
}

void checkTimeTaskCallback() {
  time(&now);                       // read the current time
  localtime_r(&now, &tm);           // update the structure tm with the current time
  if (tm.tm_hour == startHour && tm.tm_min == startMinute) {
    turnOn();
  }
  else if (tm.tm_hour == startHour1 && tm.tm_min == startMinute1) {
    turnOn1();
  }
  else if (tm.tm_hour == startHour2 && tm.tm_min == startMinute2) {
    turnOn2();
  }
  else if (tm.tm_hour == startHour3 && tm.tm_min == startMinute3) {
    turnOn3();
  }
  else {
    Serial.println("Time check, no trigger; ");
    Serial.print("Current time: ");
    Serial.print(tm.tm_hour);
    Serial.print(":");
    Serial.print(tm.tm_min);
    Serial.print(":");
    Serial.print(tm.tm_sec);
    Serial.print(" ");
    Serial.print(tm.tm_mday);
    Serial.print("/");
    Serial.print(tm.tm_mon + 1);
    Serial.print("/");
    Serial.println(tm.tm_year + 1900);
  }
}

// Initialize WiFi
void initWiFi() {
  unsigned long startAttemptTime = millis();

  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("Connecting to WiFi");

  // Try to connect within the specified timeout
  while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < wifiTimeout) {
    Serial.print('.');
    delay(1000);
  }

  if (WiFi.status() == WL_CONNECTED) {
    wifiConnected = true;
    Serial.println();
    Serial.println("Connected to WiFi");
    long rssi = WiFi.RSSI();
    Serial.print("WiFi Signal Strength (RSSI): ");
    Serial.println(rssi);
    Serial.println(WiFi.localIP());
    digitalWrite(LED, LOW);
  } else {
    wifiConnected = false;
    Serial.println();
    Serial.println("WiFi connection failed, entering offline mode");
    digitalWrite(LED, HIGH);

    // Enable the reboot task for offline mode (will reboot after 24 hours)
    ts.addTask(rebootTask);
    rebootTask.enable();
    Serial.println("Set to reboot after 24 hours");
  }
}

// Callback function that runs on database changes
void streamCallback(FirebaseStream data){
  Serial.printf("stream path, %s\nevent path, %s\ndata type, %s\nevent type, %s\n\n",
                data.streamPath().c_str(),
                data.dataPath().c_str(),
                data.dataType().c_str(),
                data.eventType().c_str());
  printResult(data); //see addons/RTDBHelper.h
  Serial.println();

  // Get the path that triggered the function
  String streamPath = String(data.dataPath());
  

  if (streamPath == "/hour1"){
    writeIntToEEPROM(0, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/hour2"){
    writeIntToEEPROM(4, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/hour3"){
    writeIntToEEPROM(8, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/trimdelay1"){
    writeIntToEEPROM(12, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/trimdelay2"){
    writeIntToEEPROM(16, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/trimdelay3"){
    writeIntToEEPROM(20, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/minute1"){
    writeIntToEEPROM(24, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/minute2"){
    writeIntToEEPROM(28, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/minute3"){
    writeIntToEEPROM(32, data.intData());
    updatetimingsfromEEPROM();
  }
  else if (streamPath == "/bonus"){
    bonusTask.restartDelayed(1000);
    Serial.println("Bonus trigger");
  }
  else if (streamPath == "/12" || streamPath == "/13" || streamPath == "/14"){
    // if the data returned belongs to 12/13/14, there was a change on the GPIO state on the following path /{gpio_number}
    String gpio = streamPath.substring(1);
    int state = data.intData();
    Serial.print("GPIO: ");
    Serial.println(gpio);
    Serial.print("STATE: ");
    Serial.println(state);

    // Compare the current state with the desired state (state from Firebase)
    if (state != !digitalRead(gpio.toInt())) {
      digitalWrite(gpio.toInt(), !state);       // Update GPIO if states don't match
    } else {
      Serial.println("State already matches, no action taken.");
    }
  }
  else {
    Serial.println("STREAM RECEIVED BUT NOTHING DONE");
  }
    
  Serial.printf("Received stream payload size: %d (Max. %d)\n\n", data.payloadLength(), data.maxPayloadLength());
}

void streamTimeoutCallback(bool timeout){
  if (timeout)
    Serial.println("stream timeout, resuming...\n");
  if (!stream.httpConnected())
    Serial.printf("error code: %d, reason: %s\n\n", stream.httpCode(), stream.errorReason().c_str());
}

void updatetimingsfromEEPROM(){
  startHour1 = readIntFromEEPROM(0);
  startMinute1 = readIntFromEEPROM(24);
  startHour2 = readIntFromEEPROM(4);
  startMinute2 = readIntFromEEPROM(28);
  startHour3 = readIntFromEEPROM(8);
  startMinute3 = readIntFromEEPROM(32);

  trimdelay1 = readIntFromEEPROM(12);
  trimdelay2 = readIntFromEEPROM(16);
  trimdelay3 = readIntFromEEPROM(20);
  Serial.println("Ran update from EEPROM");
}


void setup(){
  Serial.begin(115200);
  EEPROM.begin(EEPROM_SIZE);
  updatetimingsfromEEPROM();

  // Initialize Outputs
  pinMode(output1, OUTPUT);
  pinMode(output2, OUTPUT);
  pinMode(output3, OUTPUT);
  pinMode(gpioPin, OUTPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(output1, HIGH);
  digitalWrite(output2, HIGH);
  digitalWrite(output3, HIGH);
  digitalWrite(gpioPin, HIGH);
  digitalWrite(LED, HIGH);

  configTime(MY_TZ, MY_NTP_SERVER, "time1.google.com", "time.windows.com");

  initWiFi();

  delay(1000);  //chill for a bit

  if (wifiConnected){
    // Firebase
    config.api_key = API_KEY;
    auth.user.email = USER_EMAIL;
    auth.user.password = USER_PASSWORD;
    config.database_url = DATABASE_URL;

    Firebase.reconnectWiFi(true);
    config.token_status_callback = tokenStatusCallback; //see addons/TokenHelper.h
    config.max_token_generation_retry = 5;
    Firebase.begin(&config, &auth);

    // Streaming (whenever data changes on a path)
    // Begin stream on a database path --> board1/outputs/digital
    if (!Firebase.RTDB.beginStream(&stream, listenerPath.c_str()))
      Serial.printf("stream begin error, %s\n\n", stream.errorReason().c_str());

    // Assign a calback function to run when it detects changes on the database
    Firebase.RTDB.setStreamCallback(&stream, streamCallback, streamTimeoutCallback);

    delay(1000);
  }
  else {
    Serial.println("Skipping Firebase setup (offline mode)");
  }

  delay(1000);  //chill for a bit

  settimeofday_cb(time_is_set); // optional: callback if time was set

  // Enable the time checking task
  ts.addTask(checkTimeTask);
  ts.addTask(turnOffTask);
  ts.addTask(turnOff1Task);
  ts.addTask(turnOff2Task);
  ts.addTask(turnOff3Task);
  ts.addTask(bonusTask);

  checkTimeTask.enable();

  delay(2000);  //chill for a bit

  time(&now);                       // read the current time
  localtime_r(&now, &tm);           // update the structure tm with the current time
  Serial.println("Setup time:");
  Serial.print(tm.tm_hour);
  Serial.print(":");
  Serial.print(tm.tm_min);
  Serial.println();

  // Update Firebase in setup, then continuously after 1hr
  Firebase.RTDB.setInt(&stream, listenerPath + "lastonlinehour", tm.tm_hour);
  Firebase.RTDB.setInt(&stream, listenerPath + "lastonlinemin", tm.tm_min);
  Serial.println("Last online update");
}

void loop(){
  if (wifiConnected) {
    if (Firebase.isTokenExpired()) {
      Firebase.refreshToken(&config);
      Serial.println("Token refreshed");
    }
  }

  // Always execute scheduled tasks (online or offline)
  ts.execute();
}
Thanks.