Have you ever thought about leveraging your DIY skills to transform a regular socket into a smart socket? Do you believe you have what it takes to create a smart socket that not only powers your devices but also thinks for them? Let’s find out.
Hello, and welcome back to yet another amazing tutorial on MaisonUp! Today, we embark on an exciting journey to create an advanced Smart Socket that can update its software, automate operations based on your schedule, and provide real-time feedback—all without the hassle of apps or RTC modules. Sounds interesting, right? Let’s dive in.
Essential Components You’ll Need
To start, you’ll need to gather some essential components. Here’s our list:
• NodeMCU WiFi Module
• OLED Display
• AC to DC Module
• Active Buzzer
• 2 Relay Modules
• 3 AC Sockets
• 3 Switches
• Connecting Wires
Building the Enclosure
Once you’ve gathered all your components, it’s time to create the housing unit for your project. Our smart socket deserves a sturdy and stylish home. We’ll use a 3D printer to craft both the front and back panels. If you’re new to 3D printing or don’t have access to one, wooden or PVC sheets are effective alternatives.
Assembling the Hardware
Assembling the hardware is like solving a jigsaw puzzle. Let’s start with the AC sockets. We’re using repurposed ones from an old extension board, but you can purchase new ones from your nearest hardware store.
Next, we’ll add 3 switches for manual control and feedback. In our case, we are using fantastic 12 MM metal switches. These have a built-in LED indicator and are water and dustproof. Feel free to choose any that are available to you.
Code Overview and Upload
Before moving on to assembling the remaining components, let’s dive into the NodeMCU and Code—the heart of our project. If you’re unfamiliar, you might want to watch our getting started video. Let’s dive in.
// This code is written for the ESP8266 microcontroller board.
// The primary purpose of this application is to act as a "smart socket" for controlling a CO2 and LIGHT or any other AC Appliance module based on time schedules,
// This also provides manual overrides through a web interface which stays impact for 30 minutes (can be customized).
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <AsyncElegantOTA.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const char* ssid = "WiFi6";
const char* password = "15081990";
String firmware = "v1.10, 09-Oct-2023";
AsyncWebServer server(80);
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, "pool.ntp.org");
String weekDays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
String months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
const int CO2 = 14; // Change this to your actual Relay pin number
const int buzzerPin = 12; // Change this to your actual buzzer pin number
const int LIGHT = 13; // Change this to your actual Relay pin number
// Define the desired activation and deactivation times
const int CO2activateHour = 15; // Set your activation hour (in 24 Hour Format)
const int CO2activateMinute = 0; // Set your activation minute
const int CO2deactivateHour = 22; // Set your deactivation hour (in 24 Hour Format)
const int CO2deactivateMinute = 30; // Set your deactivation minute
const int LIGHTactivateHour = 16; // Set your activation hour (in 24 Hour Format)
const int LIGHTactivateMinute = 0; // Set your activation minute
const int LIGHTdeactivateHour = 23; // Set your deactivation hour (in 24 Hour Format)
const int LIGHTdeactivateMinute = 30; // Set your deactivation minute
unsigned long CO2ManualTime = 0;
unsigned long LIGHTManualTime = 0;
const unsigned long MANUAL_OVERRIDE_DURATION = 1800000; // 30 minutes, you can adjust this time.
int currentHour; // global scope
int currentMinute; // global scope
int currentSecond; // global scope
char intro[]= "DIY SMART SOCKET @ maisonup"; //Change to your desired scroll Text
int x, minX;
bool CO2Active = false;
bool LIGHTActive = false;
bool CO2Manual = false;
bool LIGHTManual = false;
unsigned long CO2previousBuzzerTime = 0;
const unsigned long CO2buzzerDuration = 1000; // 1 second
unsigned long LIGHTpreviousBuzzerTime = 0;
const unsigned long LIGHTbuzzerDuration = 1000; // 1 second
void buzzerOn() {
digitalWrite(buzzerPin, HIGH);
CO2previousBuzzerTime = millis();
LIGHTpreviousBuzzerTime = millis();
}
void buzzerOff() {
digitalWrite(buzzerPin, LOW);
}
void wifiConnect() {
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
display.setCursor(0, 10);
display.println("Firmware:");
display.setCursor(0, 30);
display.println(firmware);
display.display();
delay(4000);
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.clearDisplay();
display.setCursor(0, 10);
display.println("WiFi");
display.setCursor(0, 30);
display.println("Connecting");
display.display();
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
display.clearDisplay();
display.setCursor(0, 10);
display.println("WiFi");
display.setCursor(0, 30);
display.println("Connected");
display.setTextSize(1);
display.setCursor(0, 55);
display.print("IP: ");
display.print(WiFi.localIP());
display.display();
buzzerOn();
delay(50); // 3 Small beeps indicated connected to internet
buzzerOff();
delay(200);
buzzerOn();
delay(50);
buzzerOff();
delay(200);
buzzerOn();
delay(50);
buzzerOff();
delay(5000);
}
void clockDisplay() {
timeClient.update();
currentHour = timeClient.getHours();
currentMinute = timeClient.getMinutes();
currentSecond = timeClient.getSeconds();
String am_pm = (currentHour < 12) ? "AM" : "PM";
if (currentHour == 0) {
currentHour = 12;
} else if (currentHour > 12) {
currentHour -= 12;
}
String weekDay = weekDays[timeClient.getDay()];
time_t epochTime = timeClient.getEpochTime();
struct tm *ptm = gmtime(&epochTime);
int monthDay = ptm->tm_mday;
int currentMonth = ptm->tm_mon + 1;
String currentMonthName = months[currentMonth - 1];
int currentYear = ptm->tm_year + 1900;
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 15);
display.print(String(currentHour) + ":" + String(currentMinute) + " " + am_pm);
display.setCursor(0, 35);
display.println(String(weekDay) + "," + String(monthDay) + "-" + String(currentMonthName));
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(115, 0);
display.print(currentSecond);
}
void textScroll() {
display.setTextSize(1);
display.setCursor(x,55);
display.print(intro);
x=x-1;
if(x < minX) x = display.width();
}
void LIGHTActivation() {
timeClient.update();
currentHour = timeClient.getHours();
currentMinute = timeClient.getMinutes();
// If enough time has passed since the last manual action, reset the manual flag.
if (LIGHTManual && millis() - LIGHTManualTime >= MANUAL_OVERRIDE_DURATION) {
LIGHTManual = false;
}
if (!LIGHTManual) { // Only consider automated actions if not in manual mode
if ((currentHour > LIGHTactivateHour || (currentHour == LIGHTactivateHour && currentMinute >= LIGHTactivateMinute)) &&
(currentHour < LIGHTdeactivateHour || (currentHour == LIGHTdeactivateHour && currentMinute < LIGHTdeactivateMinute))) {
LIGHTActive = true;
digitalWrite(LIGHT, LOW);
} else {
LIGHTActive = false;
digitalWrite(LIGHT, HIGH);
LIGHTManual = false; // Reset manual flag after the activation window
}
}
}
void displayLIGHTStatus(bool isLIGHTActive) {
if (isLIGHTActive) {
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(50, 0);
display.print("Light:ON");
} else {
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(50, 0);
display.print("Light:OFF");
}
}
void CO2Activation() {
timeClient.update();
currentHour = timeClient.getHours();
currentMinute = timeClient.getMinutes();
// If enough time has passed since the last manual action, reset the manual flag.
if (CO2Manual && millis() - CO2ManualTime >= MANUAL_OVERRIDE_DURATION) {
CO2Manual = false;
}
if (!CO2Manual) { // Only consider automated actions if not in manual mode
if ((currentHour > CO2activateHour || (currentHour == CO2activateHour && currentMinute >= CO2activateMinute)) &&
(currentHour < CO2deactivateHour || (currentHour == CO2deactivateHour && currentMinute < CO2deactivateMinute))) {
CO2Active = true;
digitalWrite(CO2, LOW);
} else {
CO2Active = false;
digitalWrite(CO2, HIGH);
CO2Manual = false; // Reset manual flag after the activation window
}
}
}
void buzzerStatus(){
static bool previousCO2Active = false;
static bool CO2previousBuzzerState = false;
static bool previousLIGHTActive = false;
static bool LIGHTpreviousBuzzerState = false;
if (CO2Active != previousCO2Active || LIGHTActive != previousLIGHTActive) {
previousCO2Active = CO2Active;
previousLIGHTActive = LIGHTActive;
CO2previousBuzzerState = false;
LIGHTpreviousBuzzerState = false;
buzzerOn();
}
if (!CO2previousBuzzerState && millis() - CO2previousBuzzerTime >= CO2buzzerDuration) {
buzzerOff();
CO2previousBuzzerState = true;
}
if (!LIGHTpreviousBuzzerState && millis() - LIGHTpreviousBuzzerTime >= LIGHTbuzzerDuration) {
buzzerOff();
LIGHTpreviousBuzzerState = true;
}
}
void displayCO2Status(bool isCO2Active) {
if (isCO2Active) {
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.print("CO2:ON");
} else {
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.print("CO2:OFF");
}
}
void resetmanualControl(){
if (currentHour == CO2deactivateHour && currentMinute == CO2deactivateMinute) {
CO2Manual = false; // Reset manual control
}
if (currentHour == LIGHTdeactivateHour && currentMinute == LIGHTdeactivateMinute) {
LIGHTManual = false; // Reset manual control
}
}
void setup(void) {
Serial.begin(115200);
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
String html = "<html><body>";
html += "<h2>Firmware Version " + firmware + "</h2>";
html += "<p>To Update Visit IP/update</p>";
html += "<button id='CO2onButton'>Turn CO2 ON</button>";
html += "<button id='CO2offButton'>Turn CO2 OFF</button>";
html += "<button id='LIGHTonButton'>Turn LIGHT ON</button>";
html += "<button id='LIGHToffButton'>Turn LIGHT OFF</button>";
html += "<script>";
html += "document.getElementById('CO2onButton').addEventListener('click', function() { fetch('/CO2on'); });";
html += "document.getElementById('CO2offButton').addEventListener('click', function() { fetch('/CO2off'); });";
html += "document.getElementById('LIGHTonButton').addEventListener('click', function() { fetch('/LIGHTon'); });";
html += "document.getElementById('LIGHToffButton').addEventListener('click', function() { fetch('/LIGHToff'); });";
html += "</script>";
html += "</body></html>";
request->send(200, "text/html", html);
});
// Manually Turn CO2 On
server.on("/CO2on", HTTP_GET, [](AsyncWebServerRequest *request) {
CO2Manual = true;
CO2Active = true;
digitalWrite(CO2, LOW);
CO2ManualTime = millis();
request->send(200, "application/json", "{\"status\":\"CO2 manually turned ON\"}");
});
// Manually Turn CO2 Off
server.on("/CO2off", HTTP_GET, [](AsyncWebServerRequest *request) {
CO2Manual = true;
CO2Active = false;
digitalWrite(CO2, HIGH);
CO2ManualTime = millis();
request->send(200, "application/json", "{\"status\":\"CO2 manually turned OFF\"}");
});
server.on("/LIGHTon", HTTP_GET, [](AsyncWebServerRequest *request) {
LIGHTManual = true;
LIGHTActive = true;
digitalWrite(LIGHT, LOW);
LIGHTManualTime = millis();
request->send(200, "application/json", "{\"status\":\"LIGHT manually turned ON\"}");
});
server.on("/LIGHToff", HTTP_GET, [](AsyncWebServerRequest *request) {
LIGHTManual = true;
LIGHTActive = false;
digitalWrite(LIGHT, HIGH);
LIGHTManualTime = millis();
request->send(200, "application/json", "{\"status\":\"LIGHT manually turned OFF\"}");
});
AsyncElegantOTA.begin(&server);
server.begin();
Serial.println("HTTP server started");
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.clearDisplay();
display.display();
pinMode(CO2, OUTPUT);
pinMode(buzzerPin, OUTPUT);
pinMode(LIGHT, OUTPUT);
digitalWrite(CO2, HIGH);
digitalWrite(LIGHT, HIGH);
wifiConnect(); // Connect to WiFi here
timeClient.begin();
timeClient.setTimeOffset(19800);
display.setTextSize(2);
display.setTextColor(WHITE);
display.setTextWrap(false);
x = display.width();
minX = -6 * strlen(intro);
timeClient.update();
currentHour = timeClient.getHours();
currentMinute = timeClient.getMinutes();
if ((currentHour > CO2activateHour || (currentHour == CO2activateHour && currentMinute >= CO2activateMinute)) &&
(currentHour < CO2deactivateHour || (currentHour == CO2deactivateHour && currentMinute < CO2deactivateMinute))) {
CO2Active = true;
digitalWrite(CO2, LOW); // Activate LED
}
if ((currentHour > LIGHTactivateHour || (currentHour == LIGHTactivateHour && currentMinute >= LIGHTactivateMinute)) &&
(currentHour < LIGHTdeactivateHour || (currentHour == LIGHTdeactivateHour && currentMinute < LIGHTdeactivateMinute))) {
LIGHTActive = true;
digitalWrite(LIGHT, LOW); // Activate LED
}
}
void loop(void) {
clockDisplay();
textScroll();
displayCO2Status(CO2Active);
displayLIGHTStatus(LIGHTActive);
display.display();
CO2Activation();
LIGHTActivation();
buzzerStatus();
resetmanualControl();
}
At the outset, we’ve included several essential libraries that enable our ESP8266 to connect to WiFi, host a web server, perform Over-The-Air (OTA) firmware updates, retrieve the current time from an NTP server, and interface with the OLED display.
Next, we define some constants and initialize the OLED display. Note the ssid and password, which are your WiFi credentials. Ensure you modify these to match your own network.
The NTPClient is initialized to fetch the current time from pool.ntp.org. Additionally, arrays for days and months are set up for our clock display.
Pins for CO2, buzzer, and LIGHT are defined. Customize these variables as needed for your project. We’ve also set activation and deactivation times for both CO2 and LIGHT.
The core logic activates the CO2 and LIGHT relays based on these times. However, there’s a twist! Users can manually override this automated action through a web interface. Once overridden, the automation halts for a set duration—30 minutes in our case. (Feel free to adjust as needed.)
We’ve also set up global variables to track the current time, CO2 and LIGHT statuses, manual override flags, and buzzer timings.
Utility Functions and Setup
We’ve designed several utility functions for specific tasks:
• buzzerOn and buzzerOff to manage the buzzer.
• wifiConnect to link our board to the WiFi and provide feedback on the OLED.
• clockDisplay to showcase the current time.
• textScroll to display scrolling text.
• CO2Activation and LIGHTActivation for time-based activation and deactivation.
• buzzerStatus for buzzer alerts when CO2 or LIGHT states change.
• displayCO2Status and displayLIGHTStatus to display the CO2 and LIGHT statuses.
Within the setup function, we start Serial communication, launch the web server, and establish endpoints for manual control. Users can access the root URL to control CO2 and LIGHT or even update the firmware.
For automation, the setup also initializes the OLED, sets the pin modes, connects to WiFi, and activates the NTP client.
Lastly, we call all the necessary functions within the loop.
Uploading the Code
After tweaking the code to your liking, integrate it into the NodeMCU. It’s like teaching our socket how to think and act. Choose the correct board and port, press ‘Upload,’ and wait a moment—it might take a few seconds to compile and upload.
Final Assembly and Safety
With the brain (NodeMCU) ready, we transition back to the body. Install the OLED Display, AC to DC Module, NodeMCU, and relays using hot glue or double-sided tape. Ensure secure and safe connections.
Wiring the Project:
Feel free to pause the video and follow the provided wiring diagram. Please note, working with AC Current is extremely dangerous and can be lethal. Always double-check connections, and if you’re uncertain, consult an expert.

Testing Phase
After wiring, run a quick test to ensure functionality. As you initiate, you’ll notice the relays and OLED spring to life. Once satisfied, secure the wiring with more hot glue. Add a touch of creativity by making the socket wall-mountable: drill a hole in the back and secure the back panel with screws.
Test your assembly one final time. The OLED should light up, and components should operate as programmed. If issues arise, troubleshoot them—this phase confirms your diligent efforts result in flawless operations.
Features and Use Cases
With the assembly finalized, let’s explore its incredible features. Is our DIY Smart Socket as smart as we think it is?
1. Enabling the Smart Socket:
• Turn on the primary (leftmost) switch. The first socket powers up as normal and also initializes the device’s smart features. Note the IP address displayed on the OLED screen; you’ll need it later.
2. Auto Mode for Remaining Sockets:
• The remaining two sockets stay off by default and can be activated in three different modes.
3. Over-The-Air (OTA) Updates:
• Open your Arduino IDE and make adjustments to the code. For demonstration, change the activation and deactivation times of the relays to one-minute intervals. Update the firmware version number, so you can confirm the upload was successful. To upload your changes, go to ‘Sketch’ and select ‘Export Compiled Binary.’
• Next, open your web browser and go to the IP address noted earlier, appending /update at the end. Locate and upload the saved binary file. If successful, the OLED screen will confirm the new firmware version.
4. Manual Controls:
• Access the control buttons via the IP address initially displayed on the OLED screen. This feature is accessible via both mobile and desktop devices.
5. Manual Buttons:
• Use the manual buttons located directly on the socket hardware.
6. Real-Time Clock:
• Marvel at how our Smart Socket doubles as a real-time clock. It continually fetches and displays the time on the OLED screen, making your Smart Socket not just a utility but also a unique piece of wall art.
Troubleshooting Tips
Before we wrap up, let’s discuss some common issues you might encounter and how to troubleshoot them:
1. Code Upload Issues:
• Ensure you’ve downloaded the correct board for your IDE and selected the appropriate hardware and port.
2. NodeMCU Detection:
• If your computer isn’t recognizing the NodeMCU, you might be missing CH340 drivers. Search for “Download CH340 Drivers” and install them on your PC before testing again.
3. Time Zone Discrepancies:
• If the time displayed doesn’t align with your time zone, set the correct time offset in the code. Modify the following line: timeClient.setTimeOffset(19800).
4. Relay Behavior:
• If your relays behave oppositely to the ‘on’ or ‘off’ commands, relays may close when set to HIGH status and open when set to LOW. To correct this, simply invert your relay settings in the code.
5. OLED Display Issues:
• If you’re not seeing anything on the OLED display but can hear the buzzer, the issue could be the display address in your code. Double-check the line #define SCREEN_ADDRESS 0x3C and make sure it’s correct.
6. WiFi Connectivity:
• If the screen continues to display ‘WiFi Connecting,’ ensure you’ve entered the correct WiFi credentials and that you’re within the network’s range.
Conclusion
That’s a wrap! You now own a socket that’s not just smart but also a testament to your ingenuity and creativity. We hope you relished this journey. If you enjoyed this project as much as we did making it, please hit the subscribe button and give this video a thumbs up. Questions? Want to share your masterpiece? Leave your thoughts in the comments below. Until next time, happy making, and stay innovative!