Evan Rust
Published © GPL3+

Smart Room Lighting with ESP32

Use some LED panels and strips along with an ESP32 and local webserver to control the lighting of a room from anywhere in the house.

IntermediateFull instructions provided2 hours12,223
Smart Room Lighting with ESP32

Things used in this project

Hardware components

SparkFun ESP32 Thing
SparkFun ESP32 Thing
×1
Power MOSFET N-Channel
Power MOSFET N-Channel
×3
Grove - Relay
Seeed Studio Grove - Relay
×1
RGB LED Strip- Analog
×1

Software apps and online services

Arduino IDE
Arduino IDE
VS Code
Microsoft VS Code
Node.JS
MySQL Server

Story

Read more

Schematics

Wiring

Code

webpage.html

HTML
<html>
<head>
    <title>Change Room Lighting</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="/common/jquery-3.4.1.js"></script>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"></script>
    <style type="text/css">
        .switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }
        .switch {
            position: relative;
            display: inline-block;
            width: 60px;
            height: 34px;
        }
        .slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: #ccc;
            -webkit-transition: .4s;
            transition: .4s;
        }
        .slider:before {
            position: absolute;
            content: "";
            height: 26px;
            width: 26px;
            left: 4px;
            bottom: 4px;
            background-color: white;
            -webkit-transition: .4s;
            transition: .4s;
        }

        input:checked + .slider {
            background-color: #2196F3;
        }

        input:focus + .slider {
            box-shadow: 0 0 1px #2196F3;
        }

        input:checked + .slider:before {
            -webkit-transform: translateX(26px);
            -ms-transform: translateX(26px);
            transform: translateX(26px);
        }

            /* Rounded sliders */
        .slider.round {
            border-radius: 34px;
        }

        .slider.round:before {
            border-radius: 50%;
        }

        #preset-container {
            width: 85%;
        }

        #preview-container {
            width: 150px;
            height: 150px;
            background-color: black;
        }

        .accordion {
            background-color: #eee;
            color: black;
            cursor: pointer;
            padding: 18px;
            width: 100%;
            text-align: center;
            border: none;
            outline: none;
            transition: 0.4s;
        }

            /* Add a background color to the button if it is clicked on (add the .active class with JS), and when you move the mouse over it (hover) */
        .active, .accordion:hover {
            background-color: #ccc;
        }

        .panel {
            padding: 0 18px;
            background-color: white;
            max-height: 0;
            overflow: hidden;
            transition: max-height 0.2s ease-out;
        }

        .accordion:after {
            content: '\02795'; /* Unicode character for "plus" sign (+) */
            font-size: 13px;
            color: #777;
            float: right;
            margin-left: 5px;
        }

        .active:after {
            content: "\2796"; /* Unicode character for "minus" sign (-) */
        }

        .btn {
            position: relative;
            margin: 0 auto;
        }

        .aligner {
            display: flex;
            justify-content: center;
        }

/* #region button colors */

        #neutral-black-btn
        {
            background-color: black;
            color: white;
        }

        #neutral-white-btn
        {
            color: black;
            background-color: white;
        }

        #neutral-light-gray-btn
        {
            color: black;
            background-color: lightgrey;
        }
        #neutral-dark-gray-btn
        {
            color: white;
            background-color: darkgrey;
        }
        #solid-red-btn {color:white; background-color: red;}
        #solid-green-btn {color:white; background-color: green;}
        #solid-blue-btn {color:white; background-color: blue;}
        #solid-purple-btn {color:white; background-color: purple;}

        #neon-pink-btn {color:white; background-color: rgb(255, 133, 153);}
        #neon-green-btn {color:white; background-color: rgb(118, 255, 54);}
        #neon-blue-btn {color:white; background-color: rgb(45, 101, 255);}
        #neon-purple-btn {color:white; background-color: rgb(172, 70, 255);}

/* #endregion */

    /*.slider-container {
        width: 40%;
    }*/

    .range-slider {
        -webkit-appearance: none;  /* Override default CSS styles */
        appearance: none;
        width: 100%; /* Full-width */
        height: 25px; /* Specified height */
        background: #d3d3d3; /* Grey background */
        outline: none; /* Remove outline */
        opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */
        -webkit-transition: .2s; /* 0.2 seconds transition on hover */
        transition: opacity .2s;
        margin-bottom: 15px;
    }

    .range-slider:hover {
        opacity: 1; /* Fully shown on mouse-over */
    }

        /* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */
    .range-slider::-webkit-slider-thumb {
        -webkit-appearance: none; /* Override default look */
        appearance: none;
        width: 25px; /* Set a specific slider handle width */
        height: 25px; /* Slider handle height */
        background: red; /* Green background */
        cursor: pointer; /* Cursor on hover */
    }

    .range-slider::-moz-range-thumb {
        width: 25px; /* Set a specific slider handle width */
        height: 25px; /* Slider handle height */
        background: red; /* Green background */
        cursor: pointer; /* Cursor on hover */
    }

    #slider-container {
        width: 100%;
    }

    #redRange::-webkit-slider-thumb, #redRange::-moz-range-thumb {
        background: red;
    }

    #greenRange::-webkit-slider-thumb, #greenRange::-moz-range-thumb {
        background: green;
    }

    #blueRange::-webkit-slider-thumb, #blueRange::-moz-range-thumb{
        background: blue;
    }


    </style>
    <script>
        const serverBaseURL = "http://<IP>:<port>/lighting/";

        function refreshState()
        {
            $.getJSON(serverBaseURL + "getState", (data, status, xhr) => {
                setColor(data.colors.r, data.colors.g, data.colors.b, false);
                $('#toggle-switch-primary').prop('checked', data.toggleVal);
            });
        }

        function setColor(r, g, b, send_data)
        {
            $("#preview-container").css("background-color", `rgb(${r}, ${g}, ${b})`);
            $('#preview-label').text(`Red: ${r}, Green: ${g}, Blue: ${b}`);
            $('#redRange').val(r);
            $('#greenRange').val(g);
            $('#blueRange').val(b);
            if(send_data){
                $.get({
                    url: serverBaseURL + "setColor",
                    data : $.param({
                        r: r,
                        g: g,
                        b: b
                    }),
                    success: () => {},
                });
        }
        }

        $(document).ready(() => {
            refreshState();

            $('#toggle-switch-primary').change(() => {
                let lightValue = 0;
                if($('#toggle-switch-primary').is(':checked')) lightValue = 1;
                $.get({
                    url: serverBaseURL + "setLight",
                    data: $.param({
                        value: lightValue
                    }),
                    success: () => {}
                });
            });

            window.setInterval(refreshState, 5000);

            $('#neutral-black-btn').click(() =>{
                setColor(0, 0, 0, true);
            });
            $('#neutral-white-btn').click(() =>{
                setColor(255, 255, 255, true);
            });
            $('#neutral-light-gray-btn').click(() =>{
                setColor(211,211,211, true);
            });
            $('#neutral-dark-gray-btn').click(() =>{
                setColor(169,169,169, true);
            });
            $('#solid-red-btn').click(() =>{
                setColor(255,0,0, true);
            });
            $('#solid-green-btn').click(() =>{
                setColor(0,255,0, true);
            });
            $('#solid-blue-btn').click(() =>{
                setColor(0,0,255, true);
            });
            $('#solid-purple-btn').click(() =>{
                setColor(128,0,128, true);
            });
            $('#neon-pink-btn').click(() =>{
                setColor(255, 133, 153, true);
            });
            $('#neon-green-btn').click(() =>{
                setColor(118, 255, 54, true);
            });
            $('#neon-blue-btn').click(() =>{
                setColor(45, 101, 255, true);
            });
            $('#neon-purple-btn').click(() =>{
                setColor(172, 70, 255, true);
            });
            $('#redRange').on('input', () =>{
                refreshColors();
            });
            $('#greenRange').on('input', () =>{
                refreshColors();
            });
            $('#blueRange').on('input', () =>{
                refreshColors();
            });
        });
        function refreshColors()
        {
            setColor($('#redRange').val(), $('#greenRange').val(), $('#blueRange').val(), true);
        }


    </script>
</head>

<body>

<div id="center-container" class="container">
    <div class="row">
    <div class="col-sm-4"></div>
    <div id="light-toggle-container" class="col-sm-8">
        <h2>Toggle Primary Lighting</h2>
        <label class="switch">
            <input type="checkbox" id="toggle-switch-primary">
            <span class="slider round"></span>
        </label>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-4">
        <h4>Preview the Color</h4>
        <div id="preview-container">
        </div>
        <p id="preview-label"></p>
    </div>
    <div class="col-sm-2"></div>
    <div class="col-sm-6">
        <h2>Adjust Color By RGB Values</h2>
        <div id="slider-container">
            <input type="range" min=0 max=255 class="range-slider" id="redRange">
            <input type="range" min=0 max=255 class="range-slider" id="greenRange">
            <input type="range" min=0 max=255 class="range-slider" id="blueRange">
        </div>
    </div>
    </div>
    <div class="row">
    <div class="col-sm-2"></div>
    <div class="col-sm-10">
    <h2>Or Use a Preset</h2>
    <div id="preset-container">
        <button class="accordion">Neutral Colors</button>
        <div class="panel">
        <div class="aligner">
            <button class="btn" id="neutral-black-btn">Black</button>
            <button class="btn" id="neutral-white-btn">White</button>
            <button class="btn" id="neutral-light-gray-btn">Light Gray</button>
            <button class="btn" id="neutral-dark-gray-btn">Dark Gray</button>
        </div>
        </div>
        <button class="accordion">Solid Colors</button>
        <div class="panel">
        <div class="aligner">
            <button class="btn" id="solid-red-btn">Red</button>
            <button class="btn" id="solid-green-btn">Green</button>
            <button class="btn" id="solid-blue-btn">Blue</button>
            <button class="btn" id="solid-purple-btn">Purple</button>
        </div>
        </div>
        <button class="accordion">Neon Colors</button>
        <div class="panel">
        <div class="aligner">
            <button class="btn" id="neon-pink-btn">Neon Pink</button>
            <button class="btn" id="neon-green-btn">Neon Green</button>
            <button class="btn" id="neon-blue-btn">Neon Light Blue</button>
            <button class="btn" id="neon-purple-btn">Neon Purple</button>
        </div>
        </div>
        </div>
    </div>
    </div>
</div>

<script>
    var acc = document.getElementsByClassName("accordion");
    var i;
    
    for (i = 0; i < acc.length; i++) {
      acc[i].addEventListener("click", function() {
        this.classList.toggle("active");
        var panel = this.nextElementSibling;
        if (panel.style.maxHeight) {
            panel.style.visibility = "hidden";
            panel.style.margin = "0px";
            panel.style.maxHeight = null;
          
        } else {
            panel.style.visibility = "visible";
            panel.style.margin = "15px";
            panel.style.maxHeight = panel.scrollHeight + "px";
            //panel.style.margin = "15px";
        } 
      });
    }
</script>

</body>

</html>

server.js

JavaScript
const cors = require('cors');
const parser = require('body-parser');
var express = require('express');
const config = require('./config.js');

var app = express();

app.use(parser.json({extended: true}));
app.use(cors());
app.options('*', cors());
app.use(require("./light-control.js"));

app.listen(config.port);

ESP32 Code

C/C++
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
 
const char* ssid = "ssid";
const char* password =  "pword";

const char* serverURL = "http://<IP>:<port>/lighting/getState";

#define RGB_R 12
#define RGB_G 13
#define RGB_B 14

#define RELAY_PIN 5

// Refresh every 3 seconds
#define DELAY_TIME 500
 
void setup(){
  Serial.begin(115200);
  delay(3000);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }
 
  Serial.println(WiFi.localIP());

    pinMode(RGB_R, OUTPUT);
    pinMode(RGB_G, OUTPUT);
    pinMode(RGB_B, OUTPUT);
    ledcAttachPin(RGB_R, 1);
    ledcAttachPin(RGB_G, 2);
    ledcAttachPin(RGB_B, 3);
    ledcSetup(1, 12000, 8);
    ledcSetup(2, 12000, 8);
    ledcSetup(3, 12000, 8);

    pinMode(RELAY_PIN, OUTPUT);
 
}
 
void loop()
{
  if((WiFi.status() == WL_CONNECTED))
  {
    HTTPClient http;
    http.begin(serverURL);
    int code = http.GET();
    if(code > 0)
    {
      DynamicJsonDocument doc(1024);
      String payload = http.getString();
      Serial.printf("Code = %d\n", code);
      Serial.println(payload);
      deserializeJson(doc, payload);
      JsonObject obj = doc.as<JsonObject>();
      set_led_strip(obj[String("colors")][0], obj[String("colors")][1], obj[String("colors")][2]);
      set_relay(obj[String("toggleVal")]);
    }
    http.end();
    delay(DELAY_TIME);
  }
}

void set_led_strip(uint8_t r, uint8_t g, uint8_t b){
    ledcWrite(1, r);
    ledcWrite(2, g);
    ledcWrite(3, b);
}

void set_relay(bool val)
{
    digitalWrite(RELAY_PIN, !val);
}

light-control.js

JavaScript
var config = require("./config.js");
var mysql = require("mysql");
const express = require("express");
const url = require('url');
const router = express.Router();

var pool = mysql.createPool({
    host : config.mysql.host,
    user : config.mysql.user,
    password : config.mysql.password,
    database : config.mysql.database
});

router.get("/lighting/setColor", (req, res)=>{
    const queryObj = url.parse(req.url, true).query;
    const RGBLightId = config.light.RGBLight;
    pool.getConnection((err, conn)=>{
        conn.query(`UPDATE RGBLight SET RGB_value = ? WHERE light_id = ?`, 
        [JSON.stringify([queryObj.r, queryObj.g, queryObj.b]), RGBLightId], () => {
            conn.release();
            res.send("ok");
        });
    });
});

router.get("/lighting/setLight", (req, res)=>{
    const queryObj = url.parse(req.url, true).query;
    const mainLight = config.light.main;
    pool.getConnection((err, conn)=>{
        conn.query(`UPDATE Light SET state = ? WHERE light_id = ?`, 
        [queryObj.value, mainLight], () => {
            conn.release();
            res.send("ok");
        });
    });
});

router.get("/lighting/getState", (req, res)=>{
    const RGBLightId = config.light.RGBLight;
    const mainLight = config.light.main;
    var response = {
        colors : {}
    };
    pool.getConnection((err, conn)=>{
        if(err) console.log(err);
        conn.query(`SELECT RGB_value FROM SmartHome.Light LEFT JOIN SmartHome.RGBLight ON Light.light_id = RGBLight.light_id WHERE Light.light_id = ? LIMIT 1`, [RGBLightId], (err, rows) => {
            conn.release();
            if(err) console.log(err);
            var colorArr = JSON.parse(rows[0].RGB_value);
            response.colors.r = colorArr[0]; response.colors.g = colorArr[1];
                response.colors.b = colorArr[2]; 
        });
    });
    pool.getConnection((err, conn)=>{
        conn.query(`SELECT state FROM SmartHome.Light WHERE Light.light_id = ? LIMIT 1`, [mainLight], (err, rows) => {
            conn.release();
            response.toggleVal = Number(rows[0].state);
            res.send(response);
        });
    });
    
});

module.exports = router;

config.js

JavaScript
const config = {
    mysql: {
        host: "IP",
        user: "username",
        password: "password",
        database: "DB Schema"
    },
    light: {
        RGBLight: 2,
        main: 1
    },
    port: 3000
}

module.exports = config;

Credits

Evan Rust

Evan Rust

120 projects • 1053 followers
IoT, web, and embedded systems enthusiast. Contact me for product reviews or custom project requests.

Comments