The ESP32-C3 Super Mini is a compact powerhouse for IoT projects. This guide will walk you through creating an advanced DC load control system, perfect for home automation enthusiasts and IoT professionals. We’ll explore everything from basic setup to advanced features like OTA updates and smart home integration.

Key Features

  • Dual Activation Times: Set two different times for load activation.
  • Adjustable Duration: Customize how long the load stays on.
  • Web Interface: Control and configure the system remotely.
  • NTP Synchronization: Ensure accurate timekeeping.
  • Persistent Settings: Save configurations to EEPROM.
  • Wi-Fi Optimization: Maximize Wi-Fi transmit power for better connectivity.

Hardware Setup

For this ESP32-C3 Super Mini project, you’ll need:

ESP32-C3 Super Mini Board

The important pins for this project are:

  • GPIO0 – Responsible of controlling the relay board and in turning the load on and off.

Important Steps

  • First upload the code to the Esp32-C3 by it self

5_76

  • When it is programmed, open the Serial Monitor and you will see the IP Address. Store it in a *.txt file if you want. If you donยดt see it, reset the board.

1_76

  • Now place the Esp32-C3 Super Mini on the breadboard and make the connections to power the load using the Diagram.

Diagram_76_2

  • Power everything and use the browser to initiate the Webserver using the IP Address.

4_76

Diagram

 

Diagram_76_2

Software Implementation

Let’s break down the key components of our ESP32-C3 Super Mini DC Load Control system:

Wi-Fi Connection

We start by connecting to Wi-Fi, crucial for web interface access and time synchronization:

Accurate timekeeping is essential for scheduled operations:

Timezone

 

To change the timezone to New York in the provided code, you need to modify the following lines in the setup() function:

// Initialize NTP Client
timeClient.begin();
timeClient.setTimeOffset(-18000); // New York is UTC-5 (winter time)

// Set timezone for New York
configTime(-18000, 0, "pool.ntp.org");
setenv("TZ", "EST5EDT,M3.2.0,M11.1.0", 1);
tzset();

Here’s what each change does:

  1. timeClient.setTimeOffset(-18000); sets the offset to -5 hours (-5 * 3600 seconds) for Eastern Standard Time.
  2. configTime(-18000, 0, "pool.ntp.org"); configures the time with the correct offset for New York.
  3. setenv("TZ", "EST5EDT,M3.2.0,M11.1.0", 1); sets the timezone environment variable for New York, including Daylight Saving Time rules

Remember that New York observes Daylight Saving Time, so the actual offset may vary between UTC-5 (winter) and UTC-4 (summer). The timezone string “EST5EDT,M3.2.0,M11.1.0” automatically handles this transition.

Load Control Logic

The system allows for two daily activation times:

void checkAndActivateLoad() {
  // Get current time
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  
  // Check for activation times and control the load
  if (currentHour == startHour1 && currentMinute == startMinute1 && currentSecond == 0 && !loadActivated1) {
    digitalWrite(loadPin, HIGH);
    loadActivated1 = true;
    loadStartTime = millis();
  }
  // Similar check for second activation time
  
  // Turn off load after specified duration
  if ((loadActivated1 || loadActivated2) && (millis() - loadStartTime >= loadOnTime * 1000)) {
    digitalWrite(loadPin, LOW);
    loadActivated1 = false;
    loadActivated2 = false;
  }
}

Web Interface

A user-friendly web interface allows remote control and configuration:

  • Or we can set times for the load to activate for a period of time throughout the day:

We use EEPROM to persist settings across power cycles:

Complete Code

 

#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <EEPROM.h>
#include <esp_wifi.h>

const char* ssid = "YOUR SSID";
const char* password = "YOUR NETWORK PASSWORD";

const int loadPin = 0; // GPIO0 for the load
WiFiServer server(80);

// NTP Client setup
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");

// Time settings (stored in EEPROM)
int startHour1 = 0, startMinute1 = 0;
int startHour2 = 0, startMinute2 = 0;
int loadOnTime = 60; // Default load on time in seconds

// EEPROM addresses
const int EEPROM_SIZE = 20;
const int ADDR_HOUR1 = 0;
const int ADDR_MINUTE1 = 1;
const int ADDR_HOUR2 = 2;
const int ADDR_MINUTE2 = 3;
const int ADDR_LOAD_ON_TIME = 4;

// Variables to track load activation
bool loadActivated1 = false;
bool loadActivated2 = false;
unsigned long loadStartTime = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);

  pinMode(loadPin, OUTPUT);
  
  // Initialize EEPROM
  EEPROM.begin(EEPROM_SIZE);
  
  // Load times from EEPROM
  loadTimesFromEEPROM();

  // Connect to WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  
  // Set WiFi mode and adjust transmit power
  WiFi.mode(WIFI_STA);
  esp_wifi_set_max_tx_power(80); 
  
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  
  // Start the server
  server.begin();
  Serial.println("Server started");

  // Print the IP address
  Serial.print("Use this URL to connect: ");
  Serial.print("http://");
  Serial.print(WiFi.localIP());
  Serial.println("/");

  // Initialize NTP Client
  timeClient.begin();
  timeClient.setTimeOffset(0); // Portugal is UTC+0 in winter, UTC+1 in summer

  // Set timezone for Portugal
  configTime(0, 0, "pool.ntp.org");
  setenv("TZ", "WET0WEST,M3.5.0/1,M10.5.0", 1);
  tzset();

  // Now set GPIO0 as OUTPUT and ensure it's LOW (load off)
  digitalWrite(loadPin, LOW);
}

void loop() {
  timeClient.update();

  // Check if it's time to activate the load
  checkAndActivateLoad();

  // Handle web server clients
  WiFiClient client = server.available();
  if (client) {
    handleClient(client);
  }
}

void checkAndActivateLoad() {
  time_t now;
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  
  int currentHour = timeinfo.tm_hour;
  int currentMinute = timeinfo.tm_min;
  int currentSecond = timeinfo.tm_sec;

  // Check for first activation time
  if (currentHour == startHour1 && currentMinute == startMinute1 && currentSecond == 0 && !loadActivated1) {
    digitalWrite(loadPin, HIGH); // Turn on the load
    loadActivated1 = true;
    loadStartTime = millis();
    Serial.println("Load activated at time 1");
  }

  // Check for second activation time
  if (currentHour == startHour2 && currentMinute == startMinute2 && currentSecond == 0 && !loadActivated2) {
    digitalWrite(loadPin, HIGH); // Turn on the load
    loadActivated2 = true;
    loadStartTime = millis();
    Serial.println("Load activated at time 2");
  }

  // Check if loadOnTime seconds have passed since load activation
  if ((loadActivated1 || loadActivated2) && (millis() - loadStartTime >= loadOnTime * 1000)) {
    digitalWrite(loadPin, LOW); // Turn off the load
    loadActivated1 = false;
    loadActivated2 = false;
    Serial.println("Load deactivated after set time");
  }

  // Reset activation flags at the start of a new day
  if (currentHour == 0 && currentMinute == 0 && currentSecond == 0) {
    loadActivated1 = false;
    loadActivated2 = false;
    digitalWrite(loadPin, LOW); // Ensure the load is off at midnight
    Serial.println("Flags reset for a new day");
  }
}

void handleClient(WiFiClient client) {
  String request = client.readStringUntil('\r');
  client.flush();

  // Handle load control via web requests
  if (request.indexOf("/LOAD=ON") != -1) {
    digitalWrite(loadPin, HIGH); // Turn on the load
  } 
  if (request.indexOf("/LOAD=OFF") != -1) {
    digitalWrite(loadPin, LOW); // Turn off the load
  }

  // Handle time setting
  if (request.indexOf("/setTimes") != -1) {
    int pos = request.indexOf("?");
    if (pos != -1) {
      String params = request.substring(pos + 1);
      updateTimesFromParams(params);
    }
  }

  // Get current time
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return;
  }
  
  // Prepare the response
  String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\n";
  s += "<h1>ESP32-C3 DC Load Control</h1>";
  s += "<p>Current time: ";
  s += String(timeinfo.tm_hour) + ":" + String(timeinfo.tm_min) + ":" + String(timeinfo.tm_sec);
  s += "</p>";
  s += "<p>Load is currently: ";
  s += (digitalRead(loadPin) == HIGH) ? "ON" : "OFF";
  s += "</p>";
  s += "<a href=\"/LOAD=ON\">Turn On Load</a><br>";
  s += "<a href=\"/LOAD=OFF\">Turn Off Load</a><br><br>";
  
  s += "<form action='/setTimes'>";
  s += "Time 1: <input type='number' name='h1' min='0' max='23' value='" + String(startHour1) + "'> : ";
  s += "<input type='number' name='m1' min='0' max='59' value='" + String(startMinute1) + "'><br>";
  s += "Time 2: <input type='number' name='h2' min='0' max='23' value='" + String(startHour2) + "'> : ";
  s += "<input type='number' name='m2' min='0' max='59' value='" + String(startMinute2) + "'><br>";
  s += "Load On Time (seconds): <input type='number' name='ontime' min='1' max='3600' value='" + String(loadOnTime) + "'><br>";
  s += "<input type='submit' value='Set Times'>";
  s += "</form>";
  
  s += "</html>\n";

  client.print(s);
  delay(1);
}

void updateTimesFromParams(String params) {
  int h1 = params.indexOf("h1=");
  int m1 = params.indexOf("m1=");
  int h2 = params.indexOf("h2=");
  int m2 = params.indexOf("m2=");
  int ontime = params.indexOf("ontime=");

  if (h1 != -1) startHour1 = params.substring(h1 + 3, params.indexOf("&", h1)).toInt();
  if (m1 != -1) startMinute1 = params.substring(m1 + 3, params.indexOf("&", m1)).toInt();
  if (h2 != -1) startHour2 = params.substring(h2 + 3, params.indexOf("&", h2)).toInt();
  if (m2 != -1) startMinute2 = params.substring(m2 + 3, params.indexOf("&", m2)).toInt();
  if (ontime != -1) loadOnTime = params.substring(ontime + 7, params.indexOf("&", ontime)).toInt();

  // Ensure values are within valid ranges
  startHour1 = constrain(startHour1, 0, 23);
  startMinute1 = constrain(startMinute1, 0, 59);
  startHour2 = constrain(startHour2, 0, 23);
  startMinute2 = constrain(startMinute2, 0, 59);
  loadOnTime = constrain(loadOnTime, 1, 3600); // 1 second to 1 hour

  saveTimesToEEPROM();
}

void saveTimesToEEPROM() {
  EEPROM.write(ADDR_HOUR1, startHour1);
  EEPROM.write(ADDR_MINUTE1, startMinute1);
  EEPROM.write(ADDR_HOUR2, startHour2);
  EEPROM.write(ADDR_MINUTE2, startMinute2);
  EEPROM.write(ADDR_LOAD_ON_TIME, loadOnTime);
  EEPROM.commit();
}

void loadTimesFromEEPROM() {
  startHour1 = EEPROM.read(ADDR_HOUR1);
  startMinute1 = EEPROM.read(ADDR_MINUTE1);
  startHour2 = EEPROM.read(ADDR_HOUR2);
  startMinute2 = EEPROM.read(ADDR_MINUTE2);
  loadOnTime = EEPROM.read(ADDR_LOAD_ON_TIME);
  
  // Ensure loaded values are within valid ranges
  startHour1 = constrain(startHour1, 0, 23);
  startMinute1 = constrain(startMinute1, 0, 59);
  startHour2 = constrain(startHour2, 0, 23);
  startMinute2 = constrain(startMinute2, 0, 59);
  loadOnTime = constrain(loadOnTime, 1, 3600);
}

Video Demonstration

 

FAQ

 

  1. Q: Can the ESP32-C3 Super Mini control multiple loads?
    A: Yes, you can control multiple loads by utilizing additional GPIO pins and expanding the control logic.
  2. Q: How does this project compare to commercial smart plugs?
    A: This DIY solution offers greater customization and integration possibilities at a lower cost, but requires more technical knowledge.
  3. Q: Is this system suitable for high-power appliances?
    A: The ESP32-C3 itself should control relays or contactors for high-power applications, not directly switch high currents.

Conclusion

The ESP32-C3 Super Mini proves to be a versatile and powerful platform for advanced IoT projects. This DC load control system showcases its capabilities in home automation, demonstrating precise timing, remote management, and smart home integration. Whether you’re automating your home lighting, managing energy consumption, or developing a custom IoT solution, the ESP32-C3 Super Mini provides the perfect foundation for your next project.

Related Resources