Hẹn giờ tắt mở thiết bị điện điều khiển bằng Wifi

MÔ TẢ HOẠT ĐỘNG

  • Từ SmartPhone hoặc PC : kết nối Wifi với hệ thống này.
    • SSID = “TimerControl
    • Password = “12345678
  • Mở trình duyệt, nhập địa chỉ IP của hệ thống:  192.168.4.1
  • Màn hình sẽ hiển thị giao diện cho phép người dùng lập trình giờ hẹn bật tắt thiết bị điện.
  • Đồng hồ thời gian thực trên hệ thống có thể được điều chỉnh để chạy đúng ngày giờ trong thực tế.
  • Khi đến giờ hẹn bật : hệ thống sẽ kích hoạt relay đóng điện cho thiết bị chạy.
  • Khi đến giờ hẹn tắt : hệ thống sẽ ngắt relay và thiết bị sẽ ngưng chạy.

Các bạn tham khảo giao diện chính như hình

CHUẨN BỊ PHẦN CỨNG

ESP8266 NodeMCU : đây là module chính trong hệ thống cho phép kết nối Wifi với SmartPhone hoặc PC. Trên module có xây dựng một Web Server giao tiếp với Client cho phép người dùng lập trình giờ hẹn.

 

 

RTC DS3231 : module đồng hồ thời gian thực, chạy bằng pin CR2032, lưu giữ thời gian ngay cả khi không được cấp nguồn ngoài. Ngày tháng năm được tự động điều chỉnh theo tháng (30, 31 hoặc 28, 29), tự động tính năm nhuận. Module này kết nối với ESP8266 qua giao tiếp I2C.

 

Module 1 Relay 5VDC : được kích hoạt đóng ngắt qua chân điều khiển của ESP8266 NodeMCU. Thiết bị điện sẽ đấu nối với relay này.

 

 

Mạch nguồn xung AC-DC 5VDC 3W: cấp nguồn cho ESP8266 và Relay.

 

SƠ ĐỒ ĐẤU NỐI PHẦN CỨNG

PHẦN MỀM

Phần mềm demo ở đây được gọi là Hẹn giờ tưới cây tự động cho phép lập trình 2 lần tắt mở thiết bị trong ngày:

Buổi sáng :

  • 07:00:00 : mở bơm nước tưới cây.
  • 07:00:30: tắt bơm

Buổi chiều :

  • 18:00:00 : mở bơm nước tưới cây.
  • 18:00:30: tắt bơm
//---------------------------------
// Hẹn giờ tắt/mở thiết bị điện
// Điều khiển bằng WiFi
// 08/2017
// Le Xuan Duy
//---------------------------------
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <Wire.h>
#include <RtcDS3231.h>

#define SDA 0       // I2C SDA
#define SCL 2       // I2C SCL
#define pinRelay D5 // D5

ESP8266WebServer server(80);
RtcDS3231<TwoWire> Rtc(Wire);

const char* ssid = "TimerControl";    // Tên WiFi 
const char* password = "12345678";    // mật khẩu
const int PROGRAM_SIZE = 4;           // có 4 thời điểm tắt/mở
String program[PROGRAM_SIZE] = {"07:00:00:On","07:00:30:Of","18:00:00:On","18:00:30:Of"};
int DD = 9; int MM = 8; int YY = 2017; int hh = 21; int mm = 0; int ss = 0; int dow = 0;
const String dowVN[7] = {"Chủ nhật", "Thứ Hai", "Thứ Ba", "Thứ Tư", "Thứ Năm", "Thứ Sáu", "Thứ Bảy"};
bool timerChecked = false;

//------ web pages ----------
const String mainPage= "<!DOCTYPE html>"
    "<html>"
      "<head>"
        "<title>Timer Control</title>"
        "<style>"
          "body{background-color:#eee;color:#000088;font-family:Time New Roman;font-size:24pt;}"
          ".title{border-bottom:2px solid #555;padding:20px;font-size:36pt;font-weight:bold;color:#232323;text-align:center;clear:both;}"
          "#spDT{margin:20px;text-align:center;clear:both;}"
          "#content{margin:20px;padding-left:20px;}"
          "input{font-family:Time New Roman;font-size:24pt;border:none;text-align:center;}"
          "button{font-family:Time New Roman;font-size:24pt;}"
          "td{text-align:center;}"
          "table.sch,td.sch{border: 1px solid #454545;padding:5px;}"
          "table.sch{border-collapse:collapse;width:100%;background-color:#CCFFFF;}"
        "</style>"
      "</head>"
      "<body onload='displayDateTime();'>"
          "<div class='title'>HẸN GIỜ TƯỚI CÂY TỰ ĐỘNG</div>"
          "<div id='spDT' style='font-size:32pt;font-weight:bold;'>Thứ Bảy, 2017/08/12 17:00:00</div>"
          "<div id='content'>"
            "<table class='sch'>"
              "<tr>"
                "<td rowspan='2' class='sch'>Sáng</td>"
                "<td class='sch'>Giờ mở</td>"
                "<td class='sch'><input id='txtOn1_H' type='text' value='07' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOn1_M' type='text' value='00' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOn1_S' type='text' value='00' maxlength='2' size='2'/></td>"
                "<td class='sch'><button type='button' onclick='setTimer();'>Cập nhật</button></td>"
              "</tr>"
              "<tr>"
                "<td class='sch'>Giờ tắt</td>"
                "<td class='sch'><input id='txtOf1_H' type='text' value='07' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOf1_M' type='text' value='00' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOf1_S' type='text' value='10' maxlength='2' size='2' /></td>"
                "<td class='sch'><button type='button' onclick='setTimer();'>Cập nhật</button></td>"
              "</tr>"
              "<tr>"
                "<td rowspan='2' class='sch'>Chiều</td>"
                "<td class='sch'>Giờ mở</td>"
                "<td class='sch'><input id='txtOn2_H' type='text' value='16' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOn2_M' type='text' value='25' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOn2_S' type='text' value='00' maxlength='2' size='2'/></td>"
                "<td class='sch'><button type='button' onclick='setTimer();'>Cập nhật</button></td>"
              "</tr>"
              "<tr>"
                "<td class='sch'>Giờ tắt</td>"
                "<td class='sch'><input id='txtOf2_H' type='text' value='16' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOf2_M' type='text' value='25' maxlength='2' size='2' /></td>"
                "<td class='sch'><input id='txtOf2_S' type='text' value='10' maxlength='2' size='2' /></td>"
                "<td class='sch'><button type='button' onclick='setTimer();'>Cập nhật</button></td>"
              "</tr>"
            "</table>"
            "<div style='float:left;margin-top:8px;'><button type='button' onclick='testRelay();'>Thử Relay</button></div>"
            "<div style='float:right;color:red;'>Nhập vào các ô tương ứng Giờ Phút Giây<br/>rồi click Cập nhật để chỉnh giờ hẹn.</div>"
            "<div style='padding:60px 10px 10px 0px;clear:both;'>Chỉnh đồng hồ hệ thống (chỉ cần nhập những ô cần chỉnh)</div>"
            "<div style='padding:10px 50px 20px 50px;'>"
              "<table>"
                "<tr>"
                  "<td>&nbsp;Ngày&nbsp;</td>"
                  "<td><input id='txtDDs' type='text' value='' maxlength='2' size='6' placeholder='DD' /></td>"
                  "<td><input id='txtMMs' type='text' value='' maxlength='2' size='6' placeholder='MM' /></td>"
                  "<td><input id='txtYYs' type='text' value='' maxlength='4' size='6' placeholder='YYYY' /></td>"
                "</tr>"
                "<tr>"
                  "<td>&nbsp;Giờ&nbsp;</td>"
                  "<td><input id='txthhs' type='text' value='' maxlength='2' size='6' placeholder='hh' /></td>"
                  "<td><input id='txtmms' type='text' value='' maxlength='2' size='6' placeholder='mm' /></td>"
                  "<td><input id='txtsss' type='text' value='' maxlength='2' size='6' placeholder='ss' /></td>"
                "</tr>"
                "<tr><td colspan='4'>&nbsp;</td></tr>"
                "<tr>"
                  "<td></td>"
                  "<td colspan='3'><button type='button' onclick='setDT();'>Cập nhật</button></td>"
                "</tr>"
              "</table>"
            "</div>"
          "</div>"
          "<SCRIPT>"
            "var xHttp=new XMLHttpRequest();var DD,MM,YY,hh,mm,ss,Dv,Mv,Yv,hv,mv,sv;var program;"
            "var DDe=document.getElementById('txtDDs');var MMe=document.getElementById('txtMMs');var YYe=document.getElementById('txtYYs');"
            "var hhe=document.getElementById('txthhs');var mme=document.getElementById('txtmms');var sse=document.getElementById('txtsss');"
            "function displayDateTime(){"
              "xHttp.onreadystatechange=function(){"
                "if(xHttp.readyState==4 && xHttp.status==200){"
                  "var txt=xHttp.responseText;"
                  "document.getElementById('spDT').innerHTML=txt;"
                "}"
              "};"
              "xHttp.open('GET','getDT',true);xHttp.send();"
              "setTimeout('displayDateTime()',1000);"
            "}"
            "function setDT(){"
              "Dv=DDe.value;Mv=MMe.value;Yv=YYe.value;hv=hhe.value;mv=mme.value;sv=sse.value;"
              "xHttp.open('POST','setDT',true);"
              "xHttp.onreadystatechange=function(){"
                "if(xHttp.readyState==4 && xHttp.status==200 && xHttp.responseText!=''){"
                  "alert(xHttp.responseText);"
                  "if(xHttp.responseText.indexOf('Error')<0){"
                    "DDe.value='';MMe.value='';YYe.value='';hhe.value='';mme.value='';sse.value='';"
                  "}"
                "}"
              "};"
              "xHttp.send('DD='+Dv+'&MM='+Mv+'&YY='+Yv+'&hh='+hv+'&mm='+mv+'&ss='+sv);"
            "}"
            "function loadProgram(){"
              "var xHttp2=new XMLHttpRequest();"
              "xHttp2.onreadystatechange=function(){"
                "if(xHttp2.readyState==4 && xHttp2.status==200){"
                  "var txt=xHttp2.responseText; var i;"
                  "program=txt.split(',');"
                  "for(i=0;i<program.length-1;i++){"
                    "var n=Math.floor(i/2)+1;"
                    "var hh=program[i].split(':')[0];var mm=program[i].split(':')[1];"
                    "var ss=program[i].split(':')[2];var oo=program[i].split(':')[3];"
                    "var idH='txt'+oo+n+'_H';var idM='txt'+oo+n+'_M';var idS='txt'+oo+n+'_S';"
                    "document.getElementById(idH).value=hh;"
                    "document.getElementById(idM).value=mm;"
                    "document.getElementById(idS).value=ss;"
                  "}"
                "}"
              "};"
              "xHttp2.open('GET','loadPrg',true);xHttp2.send();"
            "}"
            "function setTimer(){"
              "var prgText='';var i;"
              "for(i=0;i<program.length-1;i=i+2){"
                "var n=Math.floor(i/2)+1;"
                "var idOn_H='txtOn'+n+'_H';var idOn_M='txtOn'+n+'_M';var idOn_S='txtOn'+n+'_S';"
                "var idOf_H='txtOf'+n+'_H';var idOf_M='txtOf'+n+'_M';var idOf_S='txtOf'+n+'_S';"
                "var valOnH = document.getElementById(idOn_H).value;"
                "var valOnM = document.getElementById(idOn_M).value;"
                "var valOnS = document.getElementById(idOn_S).value;"
                "var valOfH = document.getElementById(idOf_H).value;"
                "var valOfM = document.getElementById(idOf_M).value;"
                "var valOfS = document.getElementById(idOf_S).value;"
                "valOnH=valOnH.length<2?('0'+valOnH):valOnH;"
                "valOnM=valOnM.length<2?('0'+valOnM):valOnM;"
                "valOnS=valOnS.length<2?('0'+valOnS):valOnS;"
                "valOfH=valOfH.length<2?('0'+valOfH):valOfH;"
                "valOfM=valOfM.length<2?('0'+valOfM):valOfM;"
                "valOfS=valOfS.length<2?('0'+valOfS):valOfS;"
                "prgText += valOnH+':'+valOnM+':'+valOnS+':On,';"
                "prgText += valOfH+':'+valOfM+':'+valOfS+':Of,';"
              "}"
              "var xHttp3=new XMLHttpRequest();"
              "xHttp3.open('POST','setTimer',true);"
              "xHttp3.onreadystatechange=function(){"
                "if(xHttp3.readyState==4 && xHttp3.status==200 && xHttp3.responseText!=''){"
                  "alert(xHttp3.responseText);"
                  "loadProgram();"
                "}"
              "};"
              "xHttp3.send('prgText='+prgText);"
            "}"
          "</SCRIPT>"
        "</body>"
        "<script type='text/javascript'>loadProgram();</script>"
      "</html>";
//------ end web pages ------

//---------------------------
// Các hàm giao tiếp với Client
//---------------------------
void handleRoot(){
  server.send(200, "text/html; charset=UTF-8", mainPage);
}

void handleGetDatetime(){
  char dt[20];
  snprintf(dt, 20, "%02d/%02d/%04d %02d:%02d:%02d", DD, MM, YY, hh, mm, ss);
  server.send(200, "text/html; charset=UTF-8", dowVN[dow] + ", " + dt);
}

void handleSetDatetime(){
  if (server.hasArg("DD") && server.hasArg("MM") && server.hasArg("YY") && 
      server.hasArg("hh") && server.hasArg("mm") && server.hasArg("ss")){
    uint16_t Y = server.arg("YY")==""?YY:server.arg("YY").toInt();
    uint8_t M = server.arg("MM")==""?MM:server.arg("MM").toInt();
    uint8_t D = server.arg("DD")==""?DD:server.arg("DD").toInt();
    uint8_t h = server.arg("hh")==""?hh:server.arg("hh").toInt();
    uint8_t m = server.arg("mm")==""?mm:server.arg("mm").toInt();
    uint8_t s = server.arg("ss")==""?ss:server.arg("ss").toInt();
    if(Y>0 && M>0 && D>0 && h>=0 && m>=0 && s>=0){
      if(M<=12 && D<=31 && h<24 &&  m<60 && s<60){
        if(M==2 && D>29){
          server.send(200, "text/html; charset=UTF-8", "Error\nTháng 2 không được quá 29 ngày!");
          return;
        }
        if( (M==4 || M==6 || M==9 || M==11) && D>30){
          server.send(200, "text/html; charset=UTF-8", "Error\nTháng này không được quá 30 ngày!");
          return;
        }
        RtcDateTime dt = RtcDateTime(Y,M,D,h,m,s);
        Rtc.SetDateTime(dt);
        server.send(200, "text/html; charset=UTF-8", "Cập nhật đồng hồ thành công");
        return;
      }
      server.send(200, "text/html; charset=UTF-8", "Error\nVui lòng kiểm tra lại giá trị ngày giờ");
      return;
    }
    server.send(200, "text/html; charset=UTF-8", "Error\nVui lòng kiểm tra lại giá trị ngày giờ");
    return;
  }
  server.send(200, "text/html; charset=UTF-8", "Error\nVui lòng kiểm tra lại giá trị ngày giờ");
}

void handleLoadProgram(){
  String p = "";
  for(int i=0; i<PROGRAM_SIZE; i++){ p += program[i] + ","; }  
  server.send(200, "text/html; charset=UTF-8", p);
}

void handleSetTimer(){
  if (server.hasArg("prgText")){
    String prgText = server.arg("prgText");
    for(int i=0; i<PROGRAM_SIZE; i++){
      int x = i * 12;
      program[i] = prgText.substring(x, x + 11);
    }  
    server.send(200, "text/html; charset=UTF-8", "Cập nhật thành công");
    return;   
  }
  server.send(200, "text/html; charset=UTF-8", "Error\nVui lòng kiểm tra các giá trị");
}

void handleTestRelay(){
  digitalWrite(pinRelay, HIGH);
  delay(1000);
  digitalWrite(pinRelay, LOW);  
}
//----------------------------------------
// Kết thúc các hàm giao tiếp với Client
//----------------------------------------


void setup(void){
  initHardware();
  startServer();
}

void loop(void){
  server.handleClient();
  timerControl();
}

void initHardware(){
  Rtc.Begin();                  // khởi tạo đồng hồ
  Wire.begin(SDA, SCL);         // kết nối bằng I2C
  pinMode(pinRelay, OUTPUT);
  digitalWrite(pinRelay, HIGH); // test Relay
  delay(2000);
  digitalWrite(pinRelay, LOW);
}

void startServer(){  
  WiFi.mode(WIFI_AP);
  WiFi.softAP(ssid, password);
  server.on("/", handleRoot);
  server.on("/getDT", handleGetDatetime);
  server.on("/setDT", handleSetDatetime);
  server.on("/loadPrg", handleLoadProgram);
  server.on("/setTimer", handleSetTimer);
  server.on("/testRelay", handleTestRelay);
  server.begin();
}

//-----------------------------------
// lấy thời gian hiện tại từ đồng hồ
//-----------------------------------
void getCurrentDateTime(){
  RtcDateTime dt = Rtc.GetDateTime();   // đọc thời gian thực từ đồng hồ
  DD = dt.Day(); MM = dt.Month(); YY = dt.Year();
  hh = dt.Hour(); mm = dt.Minute(); ss = dt.Second();
  dow = dt.DayOfWeek();
}

//------------------------------------------
// Hàm chính: kiểm tra giờ hẹn trong program
// để đóng mở Relay
//------------------------------------------
void timerControl(){
  if(millis()%1000 == 0){
    if(timerChecked){
      return;
    }
    timerChecked = true;
    getCurrentDateTime();
    for(int i=0; i<PROGRAM_SIZE; i++){
      String pr = program[i];
      int hProg = pr.substring(0,2).toInt();
      int mProg = pr.substring(3,5).toInt();
      int sProg = pr.substring(6,8).toInt();
      String OnOff = pr.substring(9);
      if(hProg==hh && mProg==mm && sProg==ss && OnOff=="On"){
        digitalWrite(pinRelay, HIGH);
      }
      if(hProg==hh && mProg==mm && sProg==ss && OnOff=="Of"){
        digitalWrite(pinRelay, LOW);    
      }
    }
  }
  else{
    timerChecked = false;
  }
}

KẾT LUẬN

Ưu điểm

Phần này để các bạn tự đánh giá nhé

Mặt hạn chế

  • Code cứng nhắc : Phần mềm demo chỉ cho phép cố định 2 lần tắt mở trong ngày. Người dùng không thể thêm bớt số lần này. Muốn thay đổi chỉ có cách sửa code và nạp lại chương trình.
  • Không thay đổi được giờ hẹn theo ngày trong tuần hoặc ngày nghỉ, ngày lễ. Ngày nào cũng cố định 2 lần như vậy.
  • Nếu người dùng thay đổi giờ hẹn, sau đó tắt nguồn và mở lại thì chương trình quay về giờ hẹn mặc định trong code, không nhớ được những gì người dùng đã thay đổi.

Những mặt hạn chế này tất nhiên vẫn có cách giải quyết triệt để, nhưng trong phạm vi bài viết này không thể mô tả hết được vì các lý do:

  • Code xử lý rất dài
  • Bộ nhớ lưu thời gian tắt mở cũng rất dài
  • Trang HTML giao diện cũng rất dài

Do đó mình đề nghị các bạn nào quan tâm thì mình sẽ hỗ trợ tối đa trong phạm vi kiến thức có thể được.

Chúc các bạn thành công.

 

Leave a Reply

Please Login to comment
  Subscribe  
Notify of