prettyprint

2021年10月25日 星期一

Node-RED 進階使用筆記

一、Node-RED的設定檔存在.node-red/settings.js中。 

A、將網站改成https:

找到如下圖位置,需製作一組 public/private key,以openssl來製作,再將製作好的key放倒適當位置後更改privkey.pem與cert.pem路徑。

製作步驟如同前一篇文章(MQTT 簡介:使用Mosquitto Broker)介紹,採用self-signed方式產server private key與server CERT步驟如下:
  1. root CA使用已產生的ca.crt,不用再製作,以後對自己的建置的seft-signed系統可使用同一組root CA。
  2. 產生server private key:(取名叫做nodered.key)
    openssl genrsa -out nodered.key 2048
  3. 產生簽署憑證需求檔案(certificate request):((取名叫做nodered.crs)
     openssl req -new -out nodered.csr -key nodered.key
  4. 使用root CA與ca.key簽署nodered.crs產生CERT檔案:
    openssl x509 -req -in nodered.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out nodered.crt -days 360
  5. 假設nodered.key 與 nodered.crt放在/path下,則將https:...改成如下:
    https: {
          key: require("fs").readFileSync('/path/nodered.key'),
          cert: require("fs").readFileSync('/path/nodered.crt')
        },
  6. 執行 sudo systemctl restart nodered。
  7. Node-RED Dashboard ui網址也需改成https

B、dashboard ui驗證密碼:

  1. 以下列指令產生hash密碼
    node-red admin hash-pw
    再將產生的hash密碼複製至下列httpNodeAuth與httpStaticAuth的pass欄位,user可任意指定,但兩個httpNodeAuth與httpStaticAuth最好是一樣。
    httpNodeAuth: {user:"admin",pass:"..................."},
    httpStaticAuth: {user:"admin",pass:"............. ....."},

二、Node-RED Dashboard ui_template node探討:

在ui_template說明中(The template widget can contain any valid html and Angular/Angular-Material directives.),因此單以ui_template就可以完成很多UI功能。
ui_template與其他node msg input/output以下圖說明:

範例一,原始inject -> </>template->debug:


  1. inject 送出msg.payload 為timestamp,
  2. </>template內容
  3. <div ng-bind-html="msg.payload"></div>,所以把timestamp秀在
  4. UI網頁上,
  5. 有勾選Pass through messages from input,所以又把
  6. timestamp的 msg payload送到debug node。
範例二:message payload含有HTML markup tag:

inject輸出字串<font color="RED" size=10>測試</font>,UI網頁如上圖。

範例三:Dashboard </>template內容為HTML 含markup tag


{{msg.payload}}是以雙大括號(mustache template格式)表示,內容會則依照輸入顯示。


範例五:使用<script>以watch function 接收incoming message:


<script>
(function(scope) {
  scope.$watch('msg', function(msg) {
    if (msg) {
      // Do something when msg arrives
     
    }
  });
})(scope);
</script>
    如下影片所示,inject topic為TEST1,TEST2與TEST3,TEST1,TEST2分別更改網頁內容,TEST3 send message給下一個node。

script中變數為scope.variable在 html markup tag則為{{varialbe}}。

動手做智能插座-使用MQTT 與 Node-RED控制(Smart Socket DIY-Controlling via MQTT and Node-RED)

     實驗自己動手做智能插座,以Mosquitto MQTT Broker與Node-RED控制智能插座,安裝Node-RED Dashboard,因為Dashboard的UI-Template可以帶html與Angular/Angular-Material指令(The template widget can contain any valid html and Angular/Angular-Material directives),因此本實驗UI部分,僅使用Node-RED Dashboard中的UI-Template元件,不用事先規劃多少智能插座的畫面,讓不限數量的智能插座連上來,動態全部顯示在面板上。資料庫部分使用SQLite3,並安裝node-red-node-sqlite存取SQLite資料庫檔案。

功能需求如下:

智能插座的功能:

  1. 遠端控制電源開與關
  2. 電源開與關倒數計時
  3. 外出不在家時,在指定的時段設定隨機開關時間
  4. 電源開關時間排程

一、Node-RED(server side)與智能插座(client side) Topic Message Flows: 

伺服端使用Raspberry Pi 3,安裝mosquitto MQTT Broker, Node-RED,SQLite3。設備端使用ESP8266(ESP-01S)與繼電器模組搭配電工插座製作一個智能插座。為了網路安全,通訊使用TLS/SSL協定,設備端與MQTT端的Topic為兩大類:Network State與network socket, Smart Socket devices與Node-RED之間的Topic flow與端點的動作如下圖所示。



二、Node-RED端UI設計

    本實驗將不限制連上多少智能插座,且每個智能插座將獨立顯示一個獨立UI元件(狀態顯示與按鈕功能),所以主面板上的元件個數將是動態顯示,當有新的元件連上時,主動增加一個UI元件,如下圖所示,原來四個,新增一個後主動更新面板顯示個數。
因為Node-RED Dashboard UI-Template支援html與Angular/Angular-Material,因此本實驗將以UI-Template為主要元件來動態顯示元件個數。


如上圖所示,UI-Template以watch function接收其他node送進來的msg,以send指令送出msg,搭配html標記完成完成整個Template。Dashboard UI-Template與Node-RED其他nodes之間flows設計如下圖所示:



  • 完整Flows Code請參閱文章最後面說明。

三、智能插座端

智能插座端使用ESP8266(ESP-01S)搭配繼電器模組與電工材料組成一個智能插座,

ESP8266 WiFi工作模式以AP mode與Station Mode之間切換,不採用WIFI_AP_STA模式。AP Mode主要為設定參數用,包括AP passwd,SSID連線設定與MQTT broker連線參數,為網路安全起見,client端連線採用TLS/SSL,並取需要Client CERT與Server CERT的fingerprint。


ESP8266 WiFi mode切換如下流程:
  • 完整source code請參閱文章後面說明



四、完成影片展示

五、智能插座端程式碼 

SmartSocket.ino( main code)

#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <FS.h>
#include <ESP8266WebServer.h>
#include "webhtml.h"
unsigned long mqttConnTm = 0;
unsigned long WiFiClientConnTm = 0;
String mac_suffix = "";

#define _DEBUG



ESP8266WebServer server(80);

////  loda initial configuration data
void loadData() {
  char *buff;
  bWebPassLoaded = false;
  bMQTTServerAttrib = true;

  SPIFFS.begin();
  File fs;


  fs = SPIFFS.open("passdat.dat", "r");
  if (fs) {
    webpass = "";
    while (fs.available()) {
      webpass = webpass + (char)fs.read();
    }
    fs.close();
  } else {
    webpass = "adminxxx";
  }
  bWebPassLoaded = true;
#ifdef _DEBUG
  Serial.printf("Web Pass loaded:%d\n", bWebPassLoaded);
#endif


  // load Client Cert
  fs = SPIFFS.open("/client.crt", "r");
  if (!fs) {
#ifdef _DEBUG
    Serial.println("Couldn't load Client Cert");
#endif
    bMQTTServerAttrib = false;
  } else if (fs.size() > 0) {
    size_t certSize = fs.size();
    buff = (char *)malloc(certSize);

    if (certSize != fs.readBytes(buff, certSize)) {
#ifdef _DEBUG
      Serial.println("Loading Client cert failed");
#endif
      free(buff);
      bMQTTServerAttrib = false;
    } else {
#ifdef _DEBUG
      //delay(2000);
      Serial.println("Loaded Client cert");
#endif
      clientCert = new BearSSL::X509List(buff);
    }
    free(buff);
    fs.close();
  } else {
    bMQTTServerAttrib = false;
  }

  // load Client PrivateKey
  fs = SPIFFS.open("/client.key", "r");
  if (!fs) {
#ifdef _DEBUG
    Serial.println("Couldn't load Client Key");
#endif
    bMQTTServerAttrib = false;
  } else if (fs.size() > 0) {
    size_t tsize = fs.size();
    buff = (char *)malloc(tsize);
    if (tsize != fs.readBytes(buff, tsize)) {
#ifdef _DEBUG
      Serial.println("Loading Client PrivateKey failed");
#endif
      free(buff);
      bMQTTServerAttrib = false;
    } else {
#ifdef _DEBUG
      Serial.println("Loaded Client Private Key");
#endif
      clientPrivateKey = new BearSSL::PrivateKey(buff);
    }
    free(buff);
    fs.close();
  } else {
    bMQTTServerAttrib = false;
  }


  // load josn data
  fs = SPIFFS.open("/configu.dat", "r");
  if (!fs) {
#ifdef _DEBUG
    Serial.println("Couldn't configuration data");
#endif
    bMQTTServerAttrib = false;
  } else if (fs.size() > 0) {
    size_t tsize = fs.size();
    buff = (char *)malloc(tsize);
    if (tsize != fs.readBytes(buff, tsize)) {
#ifdef _DEBUG
      Serial.println("Loading configuration data");
#endif
      free(buff);
      bMQTTServerAttrib = false;
    } else {
#ifdef _DEBUG
      Serial.println("Loaded configuration data");
#endif
    }
    fs.close();
    DynamicJsonDocument doc(1024);
    DeserializationError error = deserializeJson(doc, buff);

    if (error) {
#ifdef _DEBUG
      Serial.println("Json parse error");
#endif
      free(buff);
      bMQTTServerAttrib = false;
    }
#ifdef _DEBUG
    serializeJson(doc, Serial);
#endif
    if (doc.containsKey("devicename")) {
      devicename = doc["devicename"].as<String>();
    }
    if (doc.containsKey("username")) {
      const char* tusername = doc["username"].as<const char*>();
      username = (char *)malloc(strlen(tusername));
      strcpy(username, tusername);
    }
    if (doc.containsKey("password")) {
      const char* tpassword = doc["password"].as<const char*>();
      password = (char *)malloc(strlen(tpassword));
      strcpy(password, tpassword);
    }
    if (doc.containsKey("ssid")) {
      const char* tssid = doc["ssid"].as<const char*>();
      ssid = (char *)malloc(strlen(tssid));
      strcpy(ssid, tssid);
    }
    if (doc.containsKey("wlpass")) {
      const char* twlpass = doc["wlpass"].as<const char*>();
      wlpass = (char *)malloc(strlen(twlpass));
      strcpy(wlpass, twlpass);
    }
    if (doc.containsKey("fingerprint")) {
      const char* tmqttCertFingerprint = doc["fingerprint"].as<const char*>();
      mqttCertFingerprint = (char *)malloc(strlen(tmqttCertFingerprint));
      strcpy(mqttCertFingerprint, tmqttCertFingerprint);
    }
    if (doc.containsKey("mqttServer")) {
      const char* tmqttServer = doc["mqttServer"].as<const char*>();
      mqttServer = (char *)malloc(strlen(tmqttServer));
      strcpy(mqttServer, tmqttServer);
    }
    if (doc.containsKey("mqttPort")) {
      int tmqttPort = doc["mqttPort"].as<int>();
      mqttPort = tmqttPort;
    }

    free(buff);
  } else {
    bMQTTServerAttrib = false;
  }

}

// softAP and webserver
bool startSoftAPServer() {
  int tryCount = 0;
  bWiFiSoftAP = false;

#ifdef _DEBUG
  Serial.println("Start SoftAP...");
#endif
  if (!bWebPassLoaded) return false;
  //WiFi.mode(WIFI_AP_STA);
  WiFi.mode(WIFI_AP);

#ifdef _DEBUG
  Serial.println(mac_suffix);
#endif
  String ap = "ESP-" + mac_suffix;
  if (WiFi.softAP(ap.c_str(), webpass.c_str())) {
    bWiFiSoftAP = true;
  } else {
#ifdef _DEBUG
    Serial.println("SoftAP Error");
#endif
    return false;
  }
#ifdef _DEBUG
  IPAddress myIP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(myIP);
#endif
  // initial web server
  server.on("/", handleRoot);
  server.on("/login", handleLogin);
  server.on("/logout", handleLogout);
  server.on("/chpass", handleChpass);
  server.on("/upload", HTTP_POST, handleAfterUpload, handleUpload);
  server.onNotFound(handleNotFound);
  //ask server to track these headers
  server.collectHeaders("User-Agent", "Cookie");
  server.begin();
#ifdef _DEBUG
  Serial.println("HTTP server started");
#endif


  return true;
}


void setCurrentTime() {
#ifdef _DEBUG
  Serial.print("Waiting for NTP time sync ");
#endif

  time_t now = time(nullptr);
  while (now < 8 * 3600 * 2) {
    delay(500);

#ifdef _DEBUG
    Serial.print(".");
#endif

    now = time(nullptr);
  }
  struct tm timeinfo;
  gmtime_r(&now, &timeinfo);

#ifdef _DEBUG
  Serial.println("");
  Serial.print("Current time: ");
  Serial.print(asctime(&timeinfo));
#endif

}

bool connectToMQTT() {
  if (!bMQTTServerAttrib) return false;
  /* Configure secure client connection */
  //espClient.setTrustAnchors(&caCertX509);         /* Load CA cert into trust store */
  espClient.allowSelfSignedCerts();               /* Enable self-signed cert support */
  espClient.setFingerprint(mqttCertFingerprint);  /* Load SHA1 mqtt cert fingerprint for connection validation */
  espClient.setClientRSACert(clientCert, clientPrivateKey);

#ifdef _DEBUG
  Serial.printf("Connect to MQTT server: %s\n", mqttServer);
#endif
  TOPIC = TOPIC_PREFIX + "/Network/State";
  char msg[256] = "";
  //String payload = "{\"ID\":\""+mqttClientID.c_str()+"\",\"Status\":\"OFFLINE\"}";
  sprintf(msg, "{\"ID\":\"%s\",\"State\":\"OFFLINE\"}", mqttClientID.c_str());
  if (!mqttClient.connected()) {
    if (mqttClient.connect(mqttClientID.c_str(), username, password, TOPIC.c_str(), 1, false, msg)) { // WillMessage:OFFICE :qos=1
      sprintf(msg, "{\"ID\":\"%s\",\"Devicename\":\"%s\",\"State\":\"CONNECTED\"}", mqttClientID.c_str(), devicename.c_str());
      mqttClient.publish(TOPIC.c_str(), msg);
      //digitalWrite(RED_LED_PIN, LOW);
      digitalWrite(GREEN_LED_PIN, HIGH);

      //// subscribe Topics when connect to server successfully  ////
      TOPIC = TOPIC_PREFIX + "/Socket/CMD";
      mqttClient.subscribe(TOPIC.c_str(), 1);
      ///////////////////////////////
    } else {
#ifdef _DEBUG
      Serial.printf("Connect to MQTT server error\n");
#endif
      return false;
    }
  }
#ifdef _DEBUG
  Serial.printf("MQTT server Connected\n");
#endif
  return true;
}

// callback function
void mqttSubCallback(char* topic, byte* payload, unsigned int length) {
  String rcvPayload = (char*)payload;
  rcvPayload = rcvPayload.substring(0, length);
  TOPIC = topic;
  // case 1: switch CMD ================
  if (TOPIC.startsWith(TOPIC_PREFIX) && TOPIC.endsWith("Socket/CMD")) {
    if (rcvPayload == "ON") {
      digitalWrite(RELAY_PIN, LOW);
    }
    if (rcvPayload == "OFF") {
      digitalWrite(RELAY_PIN, HIGH);
    }
    //======= enter AP mode=================
#ifdef _DEBUG
    Serial.println(rcvPayload);
#endif
    if (rcvPayload == "APMODE") {
      WiFi.disconnect();
      bWiFiClient = false;
      WiFi.mode(WIFI_AP);
      WiFi.reconnect();
#ifdef _DEBUG
      Serial.println("enter AP Mode");
#endif
      startSoftAPServer();
      return;
    }
    //======= network disconnect
    TOPIC = TOPIC_PREFIX + "/Socket/State";
    rcvPayload = "{\"ID\":\"" + mqttClientID + "\",\"State\":\"" + rcvPayload + "\"}";
    mqttClient.publish(TOPIC.c_str(),  rcvPayload.c_str());
  }
  // case 2: =========================

}


int pc = 0;
void reportStateFunc() {
  char buf[100];
  sprintf(buf, "This is from polling function:%d", pc++);
  mqttClient.publish("test", buf);
}

// connect to WiFi network
bool connectToWiFi() {
  int try_cnt = 0;
  bWiFiClient = false;
  // connect WiFi Client
  if (!bMQTTServerAttrib) {
    startSoftAPServer();
    return false;
  }
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, wlpass);
#ifdef _DEBUG

  Serial.printf("Connect to %s", ssid);
#endif
  while (WiFi.status() != WL_CONNECTED && try_cnt < 120) {  // must connect to WiFi in 1 minute.
#ifdef _DEBUG
    Serial.print(".");
#endif
    try_cnt++;
    delay(500);
  }
  if (try_cnt >= 120 && WiFi.status() != WL_CONNECTED) {
    WiFi.disconnect();
    WiFi.mode(WIFI_AP);
    WiFi.reconnect();
#ifdef _DEBUG
    Serial.println("Change to AP mode");
#endif
    startSoftAPServer();
    return false;
  }
  bWiFiClient = true;
  mqttClientID = "ESPSS_" + mac_suffix;
#ifdef _DEBUG
  Serial.println();
  Serial.println("connect WiFi successfully...");
  Serial.println(mqttClientID);
#endif
  TOPIC_PREFIX = "SmartSocket/" + mqttClientID;

#ifdef _DEBUG
  Serial.printf("\nConnected: \nlocalIP=%s, mqttClinetID=%s\n", WiFi.localIP().toString().c_str(), mqttClientID.c_str());
  Serial.println("Connect WiFi OK");
#endif
  setCurrentTime();
  mqttClient.setKeepAlive(15);

  return true;

}


bool setupOK = false; // Is initial setup steps OK?
////////////// setup function ////////////////////
void setup() {
  pinMode(GREEN_LED_PIN, OUTPUT);
  //pinMode(RED_LED_PIN, OUTPUT);
  pinMode(RELAY_PIN, OUTPUT);
  //digitalWrite(RED_LED_PIN, HIGH);
  digitalWrite(GREEN_LED_PIN, LOW);  
  digitalWrite(RELAY_PIN, HIGH);   //ESP-01S, RELAY_PIN IO0 LOW: NC, HIGH: NO
#ifdef _DEBUG
  Serial.begin(115200);
#endif
  loadData();
  mac_suffix = WiFi.macAddress();
  mac_suffix.replace(":", "");
  mac_suffix = mac_suffix.substring(6);

#ifdef _DEBUG
  Serial.println();
  Serial.println("Load Data ");
#endif

  if (!connectToWiFi()) return; //ESP.restart();
  setupOK = true;

}


int mqttTry = 0;
void loop() {
  //if (setupOK) {
  if (bWiFiClient) {
    if (!mqttClient.connected()) {
      unsigned long now_t = millis();
      if (now_t - mqttConnTm > 10000) {
        mqttConnTm = now_t;
        mqttTry++;
        if (mqttTry > 12) {
          WiFi.disconnect();
          bWiFiClient = false; // enter to AP mode to correct config data
          WiFi.mode(WIFI_AP);
          WiFi.reconnect();
          startSoftAPServer();
        } else {
          connectToMQTT();
        }
      } else if (now_t < mqttConnTm) {
        mqttConnTm = now_t;
      }
    } else {
      mqttClient.loop();
    }
  } else if (!bWiFiSoftAP) {
    unsigned long now_t = millis();
    if (now_t - WiFiClientConnTm > 1000) {
      WiFiClientConnTm = now_t;
      connectToWiFi();
    } else if (now_t < WiFiClientConnTm) {
      WiFiClientConnTm = now_t;
    }
  } else  if (bWebPassLoaded && bWiFiSoftAP) server.handleClient();
}
webpage.ino (AP mode as web server functions)
//Check if header is present and correct
bool is_authenticated() {
#ifdef _DEBUG
  Serial.println("Enter is_authenticated");
#endif
  if (server.hasHeader("Cookie")) {
#ifdef _DEBUG
    Serial.print("Found cookie: ");
#endif
    String cookie = server.header("Cookie");
#ifdef _DEBUG
    Serial.println(cookie);
#endif
    if (cookie.indexOf("ESPSESSIONID=1") != -1) {
#ifdef _DEBUG
      Serial.println("Authentication Successful");
#endif
      return true;
    }
  }
#ifdef _DEBUG
  Serial.println("Authentication Failed");
#endif
  return false;
}
// if no file upload set to false

//login page, also called for disconnect
void handleLogin() {
  String msg;
  if (server.hasHeader("Cookie")) {
#ifdef _DEBUG
    Serial.print("Found cookie: ");
#endif
    String cookie = server.header("Cookie");
#ifdef _DEBUG
    Serial.println(cookie);
#endif
  }

  if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) {
    if (server.arg("USERNAME") == "admin" &&  server.arg("PASSWORD") == webpass) {
      server.sendHeader("Location", "/");
      server.sendHeader("Cache-Control", "no-cache");
      server.sendHeader("Set-Cookie", "ESPSESSIONID=1");
      server.send(301);
#ifdef _DEBUG
      Serial.println("Log in Successful");
#endif
      return;
    }
    msg = "Wrong username/password! try again.";
#ifdef _DEBUG
    Serial.println("Log in Failed");
#endif
  }
  String content = loginhtml;
  server.send(200, "text/html", content);

}

//root page can be accessed only if authentication is ok
void handleRoot() {
#ifdef _DEBUG
  Serial.println("Enter handleRoot");
#endif
  String header;
  if (!is_authenticated()) {
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(301);
    return;
  }
  // set current attributes
  String content = roothtml;
  content.replace("{{devicename}}", devicename);
  content.replace("{{ssid}}", ssid);
  content.replace("{{wlpass}}", wlpass);
  content.replace("{{mqttCertFingerprint}}", mqttCertFingerprint);
  content.replace("{{username}}", username);
  content.replace("{{password}}", password);
  content.replace("{{mqttServer}}", mqttServer);
  content.replace("{{mqttPort}}", String(mqttPort));
  server.send(200, "text/html", content);
}

//logout clean session cookie
void handleLogout() {
  server.sendHeader("Location", "/login");
  server.sendHeader("Cache-Control", "no-cache");
  server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
  server.send(301);
}

//change web admin password
void handleChpass() {
  if (!is_authenticated()) {
    server.sendHeader("Location", "/login");
    server.sendHeader("Cache-Control", "no-cache");
    server.send(301);
    return;
  }
  if (!server.hasArg("newpass") || !server.hasArg("confirmnewpass")) {
    server.send(200, "text/html", chpasspage);
  } else {
    if (server.arg("newpass") != server.arg("confirmnewpass")) {
      String msg = (String)chpasspage + (String)"<p align='center'>two password not match</p>";
      server.send(200, "text/html", msg);
    } else {
      File fs = SPIFFS.open("/conf/pass.dat", "w");
      if (fs) {
        fs.print(server.arg("newpass"));
        fs.close();
        webpass = server.arg("newpass");
        // reboot device
        String msg = "<meta http-equiv='refresh' content='10; url=/login' />";
        server.sendHeader("Cache-Control", "no-cache");
        server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
        server.send(301, "text/html", msg + "Password changed. Reconnect SoftAP after rebooted.<br><br>Device is rebooting...");
        delay(2000);
        ESP.restart();
        //handleLogout();
        return;
      } else {
        server.send(200, "text/plain", "unknow error");
      }
    }

  }


}
//no need authentication
void handleNotFound() {
  String message = "File Not Found\n\n";
  server.send(404, "text/plain", message);
}
bool bFileUpload = true;
File uploadfile;
void handleUpload() {
  HTTPUpload& upload = server.upload();
  if (upload.status == UPLOAD_FILE_START) {
    if (upload.name == "clientCert") uploadfile = SPIFFS.open("/conf/client.crt", "w");
    if (upload.name == "clientPK") uploadfile = SPIFFS.open("/conf/client.key", "w");
  }
  if (!uploadfile) bFileUpload = false;
  if (upload.status == UPLOAD_FILE_WRITE) {
    if (uploadfile) {
      uploadfile.write(upload.buf, upload.currentSize);
    }
  }
  if (upload.status == UPLOAD_FILE_END) {
    if (uploadfile) uploadfile.close();
    if (upload.totalSize == 0) bFileUpload = false;
  }

}
void handleAfterUpload() {
  if (!bFileUpload) {
    String msg = "<meta http-equiv='refresh' content='3; url=/' />";
    server.send(301, "text/html", msg + "<p align='center'><font color='RED' SIZE='5'>upload file size is zero</font></p>");
    bFileUpload = true;
    return;
  }
  String conf = "{\"devicename\":\"" + server.arg("devicename") + "\",\"ssid\":\"" + server.arg("ssid") + "\",\"wlpass\":\"" + server.arg("password") + "\",\"fingerprint\":\"" + \
                server.arg("serverFP") + "\",\"username\":\"" + server.arg("mqttUsername") + "\",\"password\":\"" + server.arg("mqttPassword") + "\",\"mqttServer\":\"" +
                server.arg("mqttServer") + "\",\"mqttPort\":" + server.arg("mqttPort") + "}";
#ifdef _DEBUG
  Serial.println(conf);
#endif

  File fp = SPIFFS.open("/conf/conf.dat", "w");
  if (fp) {
    fp.print(conf);
    fp.close();
  }


  String msg = "<meta http-equiv='refresh' content='10; url=/login' />";
  server.sendHeader("Cache-Control", "no-cache");
  server.sendHeader("Set-Cookie", "ESPSESSIONID=0");
  server.send(301, "text/html", msg + "Configuration is changed. Device is rebooting...");
  delay(2000);
  ESP.restart();
}
webhtml.h (web page)
#define GREEN_LED_PIN 2
#define RED_LED_PIN   1
#define RELAY_PIN     0

bool bWebPassLoaded=false; //is webPass loaded from file 
bool bWiFiSoftAP=false;   
bool bWiFiClient=false;
bool bMQTTServerAttrib=false;  //is MQTT Server connection attrib loaded

String webpass="adminxxx"; // default web server password
String devicename="";
char *ssid="", *wlpass="";
char *mqttCertFingerprint="";
char *username="", *password="";
char* mqttServer = "mqttbroker.local";
int mqttPort = 8883;
String mqttClientID = "ESPSS_";
String TOPIC_PREFIX="SmartSocket/cid";
String TOPIC;
void mqttSubCallback(char* topic, byte* payload, unsigned int length);
String wifiMACLast6;
WiFiClientSecure espClient;
PubSubClient mqttClient(mqttServer, mqttPort, mqttSubCallback, espClient);

X509List  *clientCert;        /* X.509 parsed CA Cert */
PrivateKey *clientPrivateKey;

const char roothtml[] PROGMEM = R"HTML(
<html>
<head>
<title>ESP8266 Smart Socket configuration</title>
<meta charset="utf-8">
</head>
<body>
<form method="post" enctype="multipart/form-data" action="upload"> 
<table style="text-align: left; margin-left: auto;  margin-right: auto;" border="0" cellpadding="2" cellspacing="2">
<tbody>
<tr>
<td colspan="1"  style="text-align: right;">Device Name:</td>
<td colspan="3"  style="text-align: left;"><input name="devicename" type="text" value="{{devicename}}"></td>
</tr>
<tr>
<td colspan="4"  style="text-align: left;">WiFi:</td>
</tr>
<tr>
<td style="text-align: right; width:10%;"> SSID:</td>
<td style="align: left;"><input name="ssid" type="text" value="{{ssid}}"></td>
<td style="text-align: right; width:10%;">Password:</td>
<td style="align: left;"><input name="password" type="password" value="{{wlpass}}"></td>
</tr>
<tr>
<td colspan="4"  style="text-align: left;">MQTT:</td>
</tr>
<tr>
<td style="text-align: right; width:10%;"> Server:</td>
<td style="align: left;"><input name="mqttServer" type="text" value="{{mqttServer}}"></td>
<td style="text-align: right; width:10%;">Port:</td>
<td style="align: left;"><input name="mqttPort" type="text" value="{{mqttPort}}"></td>
</tr>
<tr>
<td style="text-align: right; width:10%;"> Username:
</td>
<td style="align: left;"><input name="mqttUsername" type="text" value="{{username}}"></td>
<td style="text-align: right; width:10%;">Password:</td>
<td style="align: left;"><input name="mqttPassword" type="password" value="{{password}}"></td>
</tr>
</tbody>
</table>
<table style="text-align: left; margin-left: auto;  margin-right: auto;" border="0"
cellpadding="2" cellspacing="2">
<tbody> 
<tr>
<td colspan="2"  style="text-align: left;">Files:</td>
</tr>
<tr>
<td style="text-align: right; width:25%;"> Server CERT fingerprint:</td>
<td style="align: left;"><input name="serverFP" type="text" value="{{mqttCertFingerprint}}">
</td>
</tr>
<tr>
<td style="text-align: right;width:25%;">Client CERT:
</td>
<td  style="align: left;"><input name="clientCert" type="file">
</td>
</tr>
<tr>
<td style="text-align: right;width:25%;">Client Primary Key:
</td>
<td  style="align: left;"><input name="clientPK" type="file">
</td>
</tr>
</tbody>
</table>
<p align="center">
<input class="button" value="Upload" type="submit" >
</p>
</form>
<p align="center"><a href="/chpass">change login password</a>&nbsp;&nbsp;&nbsp;<a href="/logout">Logout</a></p>
</body>
</html>
)HTML";

const char loginhtml[] PROGMEM = R"LOGIN(
<html><body><form action='/login' method='POST'>
<table style="text-align: left;margin-left: auto;  margin-right: auto; " border="0" cellpadding="2" cellspacing="2">
<tbody>
<tr>
<td style="text-align: right;">User:</td>
<td><input type='text' name='USERNAME' placeholder='user name'></td>
</tr>
<tr>
<td style="text-align: right;">Password:</td>
<td><input type='password' name='PASSWORD' placeholder='password'></td>
</tr>
<tr><td colspan="2"><p align="center"><input type='submit' name='SUBMIT' value='Submit'></p></td>
</tbody>
</table>
  </form>
  </body></html>
)LOGIN";

char const chpasspage[] PROGMEM = R"CHPASS(
<html><body><form action='/chpass' method='POST'>
<p align="center">new password:<input type="password" name="newpass" minlength="8" ></p>
<p align="center">confirm new password:<input type="password" name="confirmnewpass" minlength="8"></p>
<p align="center"><input class="button" value="Upload" type="submit"></p>
</form>
</body></html>
)CHPASS";

六、Node-RED flows

[
    {
        "id": "1170da0f4e4c8045",
        "type": "tab",
        "label": "root flow",
        "disabled": false,
        "info": ""
    },
    {
        "id": "30074137ff8b6e70",
        "type": "tab",
        "label": "new smart socket",
        "disabled": false,
        "info": ""
    },
    {
        "id": "4bc53f77f2c857ba",
        "type": "tab",
        "label": "sockets pannel",
        "disabled": false,
        "info": ""
    },
    {
        "id": "439e43cb848a5281",
        "type": "tab",
        "label": "settings",
        "disabled": false,
        "info": ""
    },
    {
        "id": "de64abcc7738c08d",
        "type": "mqtt-broker",
        "name": "mosquitto",
        "broker": "localhost",
        "port": "1883",
        "clientid": "",
        "usetls": false,
        "protocolVersion": "4",
        "keepalive": "60",
        "cleansession": true,
        "birthTopic": "",
        "birthQos": "0",
        "birthPayload": "",
        "birthMsg": {},
        "closeTopic": "",
        "closeQos": "0",
        "closePayload": "",
        "closeMsg": {},
        "willTopic": "",
        "willQos": "0",
        "willPayload": "",
        "willMsg": {},
        "sessionExpiry": ""
    },
    {
        "id": "0c752464ee7109c3",
        "type": "sqlitedb",
        "db": "/home/db/smartSocket.db",
        "mode": "RWC"
    },
    {
        "id": "160b9bec8625389b",
        "type": "ui_base",
        "theme": {
            "name": "theme-light",
            "lightTheme": {
                "default": "#0094CE",
                "baseColor": "#0094CE",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": true,
                "reset": false
            },
            "darkTheme": {
                "default": "#097479",
                "baseColor": "#097479",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif",
                "edited": false
            },
            "customTheme": {
                "name": "Untitled Theme 1",
                "default": "#4B7930",
                "baseColor": "#4B7930",
                "baseFont": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
            },
            "themeState": {
                "base-color": {
                    "default": "#0094CE",
                    "value": "#0094CE",
                    "edited": false
                },
                "page-titlebar-backgroundColor": {
                    "value": "#0094CE",
                    "edited": false
                },
                "page-backgroundColor": {
                    "value": "#fafafa",
                    "edited": false
                },
                "page-sidebar-backgroundColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "group-textColor": {
                    "value": "#1bbfff",
                    "edited": false
                },
                "group-borderColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "group-backgroundColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "widget-textColor": {
                    "value": "#111111",
                    "edited": false
                },
                "widget-backgroundColor": {
                    "value": "#0094ce",
                    "edited": false
                },
                "widget-borderColor": {
                    "value": "#ffffff",
                    "edited": false
                },
                "base-font": {
                    "value": "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif"
                }
            },
            "angularTheme": {
                "primary": "indigo",
                "accents": "blue",
                "warn": "red",
                "background": "grey",
                "palette": "light"
            }
        },
        "site": {
            "name": "Node-RED Dashboard",
            "hideToolbar": "true",
            "allowSwipe": "false",
            "lockMenu": "false",
            "allowTempTheme": "true",
            "dateFormat": "DD/MM/YYYY",
            "sizes": {
                "sx": 48,
                "sy": 48,
                "gx": 6,
                "gy": 6,
                "cx": 6,
                "cy": 6,
                "px": 0,
                "py": 0
            }
        }
    },
    {
        "id": "87ba6f7258ac6e27",
        "type": "ui_tab",
        "name": "Home",
        "icon": "dashboard",
        "disabled": false,
        "hidden": true
    },
    {
        "id": "fc6b92455ad3b35e",
        "type": "ui_group",
        "name": "Smart Sockets",
        "tab": "87ba6f7258ac6e27",
        "order": 1,
        "disp": true,
        "width": "14",
        "collapse": false,
        "className": ""
    },
    {
        "id": "91acea068bb95a30",
        "type": "ui_tab",
        "name": "Settings",
        "icon": "settings",
        "disabled": false,
        "hidden": true
    },
    {
        "id": "8a15ca423c637161",
        "type": "ui_group",
        "name": "Settings",
        "tab": "91acea068bb95a30",
        "order": 1,
        "disp": true,
        "width": "16",
        "collapse": false,
        "className": ""
    },
    {
        "id": "b7998b096794fc09",
        "type": "mqtt in",
        "z": "1170da0f4e4c8045",
        "name": "",
        "topic": "SmartSocket/+/Network/State",
        "qos": "1",
        "datatype": "auto",
        "broker": "de64abcc7738c08d",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 140,
        "y": 40,
        "wires": [
            [
                "fb4f1cbd4d446f98"
            ]
        ]
    },
    {
        "id": "fb4f1cbd4d446f98",
        "type": "json",
        "z": "1170da0f4e4c8045",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 190,
        "y": 100,
        "wires": [
            [
                "8a9d09d425e6e505"
            ]
        ]
    },
    {
        "id": "8a9d09d425e6e505",
        "type": "switch",
        "z": "1170da0f4e4c8045",
        "name": "",
        "property": "payload[\"State\"]",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "OFFLINE",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "CONNECTED",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 310,
        "y": 100,
        "wires": [
            [
                "da5685310d73b444"
            ],
            [
                "0a9e2d8884e966ab"
            ]
        ]
    },
    {
        "id": "da5685310d73b444",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "set Device OFFLINE",
        "func": "msg.topic = \"update device set networkState=0 where deviceID='\"+msg.payload[\"ID\"]+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 500,
        "y": 60,
        "wires": [
            [
                "b830328e6b146c68"
            ]
        ]
    },
    {
        "id": "0a9e2d8884e966ab",
        "type": "link out",
        "z": "1170da0f4e4c8045",
        "name": "is new SS?",
        "links": [
            "000740d0e2456877"
        ],
        "x": 435,
        "y": 120,
        "wires": []
    },
    {
        "id": "a06c018939fd3ba8",
        "type": "comment",
        "z": "1170da0f4e4c8045",
        "name": "Is new Smart Socket?",
        "info": "",
        "x": 500,
        "y": 160,
        "wires": []
    },
    {
        "id": "51e113dbd267bd84",
        "type": "function",
        "z": "30074137ff8b6e70",
        "name": "update or insert new socket",
        "func": "if (msg.payload[0].rs > 0) {\n   msg.topic = \"update device set networkState=1, deviceName='\"+msg.payload[0][\"deviceName\"]+\"' where deviceID='\"+msg.payload[0][\"deviceID\"]+\"'\";\n}\nelse {\n    msg.topic=\"Insert into device(deviceID, deviceName, networkState) values('\"+msg.payload[0][\"deviceID\"]+\"','\"+msg.payload[0][\"deviceName\"]+\"',1)\";\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 520,
        "y": 60,
        "wires": [
            [
                "66fd97fe8ba0ce7b"
            ]
        ]
    },
    {
        "id": "372853647801eb2b",
        "type": "sqlite",
        "z": "30074137ff8b6e70",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "device is Exits?",
        "x": 300,
        "y": 60,
        "wires": [
            [
                "51e113dbd267bd84"
            ]
        ]
    },
    {
        "id": "f041e68b229222da",
        "type": "function",
        "z": "30074137ff8b6e70",
        "name": "check device whether exist",
        "func": "msg.topic=\"select count(*) as rs, '\"+msg.payload[\"ID\"] +\"' as deviceID, '\"+msg.payload[\"Devicename\"]+\"' as deviceName from device where deviceID='\"+msg.payload[\"ID\"]+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 180,
        "y": 160,
        "wires": [
            [
                "372853647801eb2b"
            ]
        ]
    },
    {
        "id": "000740d0e2456877",
        "type": "link in",
        "z": "30074137ff8b6e70",
        "name": "is new socket",
        "links": [
            "0a9e2d8884e966ab"
        ],
        "x": 55,
        "y": 100,
        "wires": [
            [
                "f041e68b229222da"
            ]
        ]
    },
    {
        "id": "66fd97fe8ba0ce7b",
        "type": "sqlite",
        "z": "30074137ff8b6e70",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "update database",
        "x": 510,
        "y": 140,
        "wires": [
            [
                "d2c2f0caf6bf039d"
            ]
        ]
    },
    {
        "id": "b830328e6b146c68",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "update network state",
        "x": 720,
        "y": 60,
        "wires": [
            [
                "bd5ec405f98497be"
            ]
        ]
    },
    {
        "id": "7d466904dcc22a23",
        "type": "link in",
        "z": "4bc53f77f2c857ba",
        "name": "load all device(IN)",
        "links": [
            "0468df9a5a271569",
            "b82931419a746738",
            "bd5ec405f98497be",
            "d2c2f0caf6bf039d",
            "8cf0a0a0434d1a75",
            "28aca6c3e73f9b87"
        ],
        "x": 95,
        "y": 260,
        "wires": [
            [
                "7a3b9b19df4c91d3"
            ]
        ]
    },
    {
        "id": "efc6af2b1324e10d",
        "type": "ui_template",
        "z": "4bc53f77f2c857ba",
        "group": "fc6b92455ad3b35e",
        "name": "main sockets panel",
        "order": 1,
        "width": "14",
        "height": 10,
        "format": "<script>\nvar htmlTimer=[];\n(function(scope) {\n    scope.jsonobj=\"\";\n    scope.recordset={};\n    \n    \n    scope.$watch('msg', function(msg) {\n        \n    if (msg) {\n        if (msg.topic==\"RECORDSET\") \n        {\n           \n            scope.recordset=msg.recordset;\n        }\n        if (msg.topic==\"TIMER\") {\n            htmlTimer=msg.payload;\n            //alert(htmlTimer);\n            \n        }\n        \n    }\n    \n    });\n    /*\n    scope.getRecordset = function() {\n       \n        return scope.recordset;\n    }\n    */\n    scope.getTimer = function(deviceID) {\n        var ret_val=\" \";\n        \n        if (htmlTimer.length == undefined) return ret_val;\n        if (htmlTimer.length > 0) {\n             \n                for (i=0; i < htmlTimer.length; i++) {\n                    if (htmlTimer[i].deviceID==deviceID) {\n                        ret_val = htmlTimer[i].cmd+\": \"+htmlTimer[i].timeout+\" seconds\";\n                        break;\n                    } \n                } \n    \n           \n           }\n           //alert(ret_val);\n            return ret_val;\n           \n         \n    }\n    scope.sendCMD = function(sscommand) {\n        scope.jsonobj=\"{\\\"Type\\\":\\\"CMD\\\",\\\"deviceID\\\":\\\"\"+sscommand.deviceID+\"\\\",\\\"SSCMD\\\":\"+((!sscommand.lastState)?\"\\\"ON\\\"\":\"\\\"OFF\\\"\")+\"}\";\n        //alert(jsonobj);\n        return scope.jsonobj;\n    }\n    scope.gotoSetting = function(device) {\n         return \"{\\\"Type\\\":\\\"SETTINGS\\\",\\\"deviceID\\\":\\\"\"+device.deviceID+\"\\\",\\\"deviceName\\\":\\\"\"+device.deviceName+\"\\\"}\";\n    }\n    \n})(scope);\n    \n    \n   \n</script>\n\n<div  flex layout=\"row\"  layout-wrap layout-align=\"space-between\" style=\"width:100%;height:100%;\">\n<div ng-repeat=\"mp in recordset\">\n    <div flex layout=\"column\" style='background:{{(mp.networkState)?\"#FFFFFF\":\"#F0F0F0\"}}; \n    width:160px; margin:2px;  \n    box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);\n    z-index: 20;'>\n        <div>\n            <spam style=\"float:left;margin-left:4px;\"><md-icon style='color:{{(mp.networkState)?\"blue\":\"#C0C0C0\"}}'>\n                {{(mp.networkState)?\"wifi\":\"signal_wifi_0_bar\"}}\n                </md-icon></spam>\n                \n            <spam style=\"float:right;margin-right:4px;\">\n                <md-button ng-click=\"send({payload:gotoSetting(mp)})\" style='background:{{(mp.networkState)?\"#FFFFFF\":\"#F0F0F0\"}};'>\n                    <md-icon style=\"align:right\">settings</md-icon>\n                </md-button>\n            </spam>\n            <spam style=\"float:right;margin-left:4px;\"><md-icon style='color:blue'>\n                {{(mp.home)?\"home\":\"\"}}\n                </md-icon></spam>\n        </div>\n      \n        <div style=\"margin: auto;color:#008000;font-weight:bold\">{{mp.deviceName}}</div>\n       \n        <div style=\"margin: auto;padding:0px\"><img src='{{(mp.lastState)?\"/socket_connect.svg\":\"/socket_disconnect.svg\"}}' width='100px'></div>\n       <div style=\"margin:auto;height:24px;\">{{getTimer(mp.deviceID)}}</div>\n        \n        <div style=\"margin:auto;margin-bottom:4px;\">\n            <md-button  ng-click=\"send({payload:sendCMD(mp)})\" style=\"width:80px;border-radius: 12px 12px 12px 12px;\">\n                <md-icon style='color:{{(mp.lastState)?\"white\":\"blue\"}}'>settings_power</md-icon>\n                <font style='color:{{(mp.lastState)?\"white\":\"blue\"}}'>{{(mp.lastState)?\"OFF\":\"ON\"}}</font>\n            </md-button>\n            \n        </div>\n        \n    </div>\n</div>\n</div>\n\n",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 490,
        "y": 160,
        "wires": [
            [
                "d11d203a4c949cb0"
            ]
        ]
    },
    {
        "id": "bd5ec405f98497be",
        "type": "link out",
        "z": "1170da0f4e4c8045",
        "name": "update pannel",
        "links": [
            "7d466904dcc22a23"
        ],
        "x": 895,
        "y": 60,
        "wires": []
    },
    {
        "id": "d2c2f0caf6bf039d",
        "type": "link out",
        "z": "30074137ff8b6e70",
        "name": "update pannel",
        "links": [
            "7d466904dcc22a23"
        ],
        "x": 655,
        "y": 140,
        "wires": []
    },
    {
        "id": "7a3b9b19df4c91d3",
        "type": "sqlite",
        "z": "4bc53f77f2c857ba",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "fixed",
        "sql": "select deviceID, deviceName, lastState, networkState,\n    (select count(*) from notAtHome B where A.deviceID = B.deviceID) as home from device A order by deviceName",
        "name": "select all device",
        "x": 220,
        "y": 260,
        "wires": [
            [
                "35afb493804ae053"
            ]
        ]
    },
    {
        "id": "d11d203a4c949cb0",
        "type": "json",
        "z": "4bc53f77f2c857ba",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 450,
        "y": 240,
        "wires": [
            [
                "5371499fb4b4f440",
                "e473c609ee8b3d2f"
            ]
        ]
    },
    {
        "id": "9f57d0424883ef48",
        "type": "mqtt out",
        "z": "4bc53f77f2c857ba",
        "name": "",
        "topic": "",
        "qos": "1",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "de64abcc7738c08d",
        "x": 630,
        "y": 60,
        "wires": []
    },
    {
        "id": "5371499fb4b4f440",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "send to device via mqtt",
        "func": "var mqtt={};\nif (msg.payload[\"Type\"]==\"CMD\") {\n    mqtt.topic = \"SmartSocket/\"+msg.payload[\"deviceID\"]+\"/Socket/CMD\";\n    mqtt.payload=msg.payload[\"SSCMD\"];\n    return mqtt;\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 620,
        "y": 220,
        "wires": [
            [
                "9f57d0424883ef48"
            ]
        ]
    },
    {
        "id": "f923bbd30bfac7b2",
        "type": "mqtt in",
        "z": "1170da0f4e4c8045",
        "name": "",
        "topic": "SmartSocket/+/Socket/State",
        "qos": "1",
        "datatype": "auto",
        "broker": "de64abcc7738c08d",
        "nl": false,
        "rap": true,
        "rh": 0,
        "x": 140,
        "y": 200,
        "wires": [
            [
                "d17fae4a552647cd"
            ]
        ]
    },
    {
        "id": "d17fae4a552647cd",
        "type": "json",
        "z": "1170da0f4e4c8045",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 310,
        "y": 240,
        "wires": [
            [
                "ec9dc0e95e90d720"
            ]
        ]
    },
    {
        "id": "f70d640e01e7202c",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "update socket state",
        "x": 710,
        "y": 240,
        "wires": [
            [
                "0468df9a5a271569"
            ]
        ]
    },
    {
        "id": "ec9dc0e95e90d720",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "update socket state",
        "func": "var ls=0;\nif (msg.payload[\"State\"] == \"ON\") ls =1;\nmsg.topic=\"Update device set lastState=\"+ls +\" where deviceID='\"+msg.payload[\"ID\"]+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 490,
        "y": 240,
        "wires": [
            [
                "f70d640e01e7202c"
            ]
        ]
    },
    {
        "id": "0468df9a5a271569",
        "type": "link out",
        "z": "1170da0f4e4c8045",
        "name": "",
        "links": [
            "7d466904dcc22a23"
        ],
        "x": 895,
        "y": 240,
        "wires": []
    },
    {
        "id": "35afb493804ae053",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "prepare msg payload",
        "func": "var rs={};\nrs.recordset=[];\nrs.recordset=msg.payload;\nrs.topic=\"RECORDSET\";\nreturn rs;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 240,
        "y": 180,
        "wires": [
            [
                "efc6af2b1324e10d"
            ]
        ]
    },
    {
        "id": "acf62abd22b90d1b",
        "type": "inject",
        "z": "4bc53f77f2c857ba",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "1",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "TIMER",
        "payloadType": "date",
        "x": 130,
        "y": 60,
        "wires": [
            [
                "13f2fe26d8c8d9b3"
            ]
        ]
    },
    {
        "id": "21efaace9b1a2876",
        "type": "ui_ui_control",
        "z": "4bc53f77f2c857ba",
        "name": "",
        "events": "all",
        "x": 780,
        "y": 280,
        "wires": [
            [
                "46840fc0f3f2dd33"
            ]
        ]
    },
    {
        "id": "e473c609ee8b3d2f",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "go to Settings tab",
        "func": "var settingUI={};\n\nif (msg.payload[\"Type\"]==\"SETTINGS\") {\n    settingUI.deviceID=msg.payload[\"deviceID\"];\n    settingUI.payload = {};\n    settingUI.payload.tab = \"Settings\";\n    global.set(\"deviceID\", msg.payload[\"deviceID\"]);\n    global.set(\"deviceName\", msg.payload[\"deviceName\"]);\n    return settingUI;\n}\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 610,
        "y": 280,
        "wires": [
            [
                "21efaace9b1a2876"
            ]
        ]
    },
    {
        "id": "0fc3c8e3128af58d",
        "type": "ui_template",
        "z": "439e43cb848a5281",
        "group": "8a15ca423c637161",
        "name": "Settings",
        "order": 3,
        "width": 0,
        "height": 0,
        "format": "\n<script>\nscope.on_off=\"OFF\";\n    scope.hour=0;\n    scope.minute=0;\n    scope.bhour=18;\n    scope.bminute=0;\n    scope.ehour=23;\n    scope.eminute=0;\n    scope.deviceName=\"\";\n    scope.deviceID=\"\";\n    scope.buttonSetTimer=\"Set Timer\";\n    scope.noAtHomeSet=\"Set\"\n    scope.notAtHomeCancel=\"Cance\";\n    scope.schedule={};\n    scope.sch_w1=0;\n    scope.sch_w2=0;\n    scope.sch_w3=0;\n    scope.sch_w4=0;\n    scope.sch_w5=0;\n    scope.sch_w6=0;\n    scope.sch_w0=0;\n    scope.sch_hour=0;\n    scope.sch_min=0;\n    scope.sch_on_off=\"ON\";\n    scope.sch_rs=[];\n(function(scope) {\n    \n  scope.$watch('msg', function(msg) {\n    if (msg) {\n      if (msg.topic==\"setDeviceName\"){\n          scope.deviceName=msg.payload.deviceName;\n          scope.deviceID=msg.payload.deviceID;\n          \n      }\n      if (msg.topic==\"SCHEDULERS\"){\n          scope.sch_rs=msg.payload;\n          \n      }\n      \n    }\n    \n  });\n  scope.handleChangeHour = function() {\n      if (isNaN(scope.hour)) scope.hour=0;\n        \n    }\n    scope.handleChangeMinute = function() {\n        if (isNaN(scope.minute)) scope.minute=0;\n    }\n    scope.setTimer = function() {\n       var tt = (scope.hour*60+scope.minute)*1;\n       scope.buttonSetTimer=\"Timer Started\";\n        return \"{\\\"topic\\\":\\\"TIMER\\\",\\\"payload\\\":{\\\"TIMEOUT\\\":\"+tt+\",\\\"CMD\\\":\\\"\"+scope.on_off+\"\\\"}}\";\n    }\n    \n    scope.buttonNotAtHomeSet = function() {\n        var bt = scope.bhour*60+scope.bminute;\n        var et = scope.ehour*60+scope.eminute;\n        var rbt = bt+Math.floor(Math.random()*60-30);\n        var ret = et+Math.floor(Math.random()*60-30);\n        return \"{\\\"topic\\\":\\\"NOTATHOMESET\\\",\\\"payload\\\":{\\\"BTIME\\\":\"+bt+\",\\\"ETIME\\\":\"+et+\",\\\"RBTIME\\\":\"+rbt+\",\\\"RETIME\\\":\"+ret+\",\\\"deviceID\\\":\\\"\"+scope.deviceID+\"\\\"}}\";\n      \n    }\n    scope.buttonNotAtHomeCancel = function() {\n        return \"{\\\"topic\\\":\\\"NOTATHOMECANCEL\\\",\\\"payload\\\":{\\\"deviceID\\\":\\\"\"+scope.deviceID+\"\\\"}}\";\n        \n    }\n    \n    \n    scope.addSchedule = function() {\n        return \"{\\\"topic\\\":\\\"SCHEDULEADD\\\",\\\"payload\\\":{\\\"deviceID\\\":\\\"\"+scope.deviceID+\"\\\"\"+\n            \",\\\"w1\\\":\"+scope.sch_w1+\",\\\"w2\\\":\"+scope.sch_w2+\",\\\"w3\\\":\"+scope.sch_w3+\",\\\"w4\\\":\"+scope.sch_w4+\n            \",\\\"w5\\\":\"+scope.sch_w5+\",\\\"w6\\\":\"+scope.sch_w6+\",\\\"w0\\\":\"+scope.sch_w0+\n                \",\\\"stime\\\":\\\"\"+scope.sch_hour+\":\"+scope.sch_min+\"\\\",\\\"cmd\\\":\\\"\"+scope.sch_on_off+\"\\\"}}\";\n    }\n    scope.deleteOnScheduleRecord = function(id) {\n        return \"{\\\"topic\\\":\\\"SCHEDULECANCEL\\\",\\\"payload\\\":{\\\"rowid\\\":\"+id+\"}}\";\n    }\n})(scope);\n \n </script>   \n<div style=\"color:blue;font-size:large;font-weight:bold; margin:auto\">{{deviceName}}</div>\n<div style=\"display:flex;margin:auto;\">\n <!-- Set Timer start here-->   \n    <div style=\"width:350px; height:170px;padding:5px;box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);\n    z-index: 20;text-align:center;\">\n         <div style=\"margin:auto;color:blue;font-size:large;font-weight:bold\">Set Timer</div>\n<table width=\"100%\">\n<tr>\n<td align=\"center\">\n<spam><i style=\"color:blue;padding:2px;\" class=\"fa fa-clock-o fa-2x\" aria-hidden=\"true\"></i></spam>\n<spam><input type=\"number\" ng-model=\"hour\"  style=\"width:40px\" min=\"0\" max=\"59\" value=0 ng-change=\"handleChangeHour()\">minutes</spam>\n<spam><input type=\"number\" ng-model=\"minute\"  style=\"width:40px\" min=\"0\" max=\"59\" value=0 ng-change=\"handleChangeMinute()\">seconds</spam>\n</td>\n</tr>\n<tr>\n<td align=\"center\">\n\t<input type=\"radio\" ng-model=\"on_off\"  value=\"ON\">ON &nbsp;&nbsp;&nbsp;\n    <input type=\"radio\" ng-model=\"on_off\"  value=\"OFF\">OFF\n</td>\n</tr>\n<tr>\n<td align=\"center\" height=\"120%\">\n<md-button class=\"rounded\" ng-click=\"send({payload:setTimer()})\"><md-icon style=\"color:white;\">timer</md-icon>{{buttonSetTimer}}</md-button>\n</td>\n</tr>\n</table>\n   \n</div>\n<!-- set timer end -->\n\n<!-- not at home start here-->\n<div style=\"width:400px; height:170px;padding:5px;box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);\n    z-index: 20;\">\n    \n<table width=\"100%\">\n    <tr>\n        <td style=\"color:blue;font-size:large;font-weight:bold;text-align:center\">\n            Not At Home\n        </td>\n    </tr>\n    <tr>\n        <td align=\"right\">\n            <md-icon style=\"color:blue\">power</md-icon>\"Power ON\" between\n            <input type=\"number\" ng-model=\"bhour\"  style=\"width:35px\" min=\"0\" max=\"23\" value=0 ng-change=\"handleChangeHour()\">hours\n            <input type=\"number\" ng-model=\"bminute\"  style=\"width:35px\" min=\"0\" max=\"59\" value=0 ng-change=\"handleChangeMinute()\">minutes\n    </td>\n    </tr>\n\t<tr>\n\t<td style=\"text-align:right;margin-right:1em;\"> \n\tand\n\t</td>\n\t</tr>\n\t<tr>\n\t<td style=\"text-align:right;margin-right:1em;\">\n\t<spam><input type=\"number\" ng-model=\"ehour\"  style=\"width:35px\" min=\"0\" max=\"23\" value=0 ng-change=\"handleChangeHour()\">hours</spam>\n    <spam><input type=\"number\" ng-model=\"eminute\"  style=\"width:35px\" min=\"0\" max=\"59\" value=0 ng-change=\"handleChangeMinute()\">minutes</spam>\n    </td>\n\t</tr>\n\t<tr>\n\t<td style=\"text-align:center;margin-right:1em;\">\n\t<spam><md-button class=\"rounded\" ng-click=\"send({payload:buttonNotAtHomeSet()})\"><md-icon  style=\"color:white;\">query_builder</md-icon>{{noAtHomeSet}}</md-button></spam>&nbsp;&nbsp;&nbsp;\n\t<spam><md-button class=\"rounded\" ng-click=\"send({payload:buttonNotAtHomeCancel()})\"><md-icon style=\"color:red;\">cancel</md-icon>{{notAtHomeCancel}}</md-button></spam>\n\t</td>\n\t</tr>\n</table>\n\n</div>\n<!-- not at home end -->\n</div>\n\n\n<!-- schedule -->\n <div style=\"margin:auto;width:90%; padding:5px;box-shadow: 0 3px 5px -1px rgba(0, 0, 0, .2), 0 6px 10px 0 rgba(0, 0, 0, .14), 0 1px 18px 0 rgba(0, 0, 0, .12);\n    z-index: 20;text-align:center;\">\n     <div style=\"margin:auto;color:blue;font-size:large;font-weight:bold\">Schedule</div>\n <div>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w1\">MON</spam> \n    <spam><input type=\"checkbox\" ng-model=\"sch_w2\">TUE</spam>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w3\">WED</spam>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w4\">THU</spam>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w5\">FRI</spam>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w6\">SAT</spam>\n    <spam><input type=\"checkbox\" ng-model=\"sch_w0\">SUN</spam>\n    <spam><input type=\"number\" width=\"20px\" min=\"0\" max=\"23\" ng-model=\"sch_hour\">Hour</spam>\n    <spam><input type=\"number\" width=\"20px\"  min=\"0\" max=\"59\" ng-model=\"sch_min\">Min</spam>\n    <spam><input type=\"radio\" ng-model=\"sch_on_off\" value=\"ON\">ON&nbsp;\n    <input type=\"radio\" ng-model=\"sch_on_off\" value=\"OFF\">OFF</spam>\n    <spam><md-button class=\"rounded\"   ng-click=\"send({payload:addSchedule()})\"><md-icon  style=\"color:white;\">alarm_add</md-icon></md-button></spam>\n </div>   \n\n<table width=\"80%\" style=\"margin-left:auto;margin-right:auto; border:solid 1px;border-collapse:collapse\">\n   <tr style=\"border-bottom:solid 1px;\">\n\n        <th colspan=\"7\" width=\"70%\">Weekday</td>\n        <th>Time</td>\n        <th>Power</td>\n        <th> </td>\n\n    </tr>\n\n   <tr   style=\"border-bottom:dash 1px;\" ng-repeat=\"record in sch_rs\">\n       <td width=\"10%\">{{(record.w1)?\"MON\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w2)?\"TUE\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w3)?\"WED\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w4)?\"THU\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w5)?\"FRI\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w6)?\"SAT\":\"\"}}</td>\n       <td width=\"10%\">{{(record.w0)?\"SUN\":\"\"}}</td>\n       <td align=\"center\">{{record.stime}}</td>\n       <td align=\"center\">{{record.cmd}}</td>\n       <td align=\"center\"><md-button class=\"rounded\" ng-click=\"send({payload:deleteOnScheduleRecord(record.rowid)})\"><md-icon  style=\"color:white;\">delete</md-icon></md-button></td>\n       \n   </tr> \n   \n\n</table>\n</div>\n<!-- schedule -->",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 180,
        "y": 60,
        "wires": [
            [
                "15f0595c9855419e"
            ]
        ]
    },
    {
        "id": "f6a59d40a352b0d7",
        "type": "ui_button",
        "z": "439e43cb848a5281",
        "name": "",
        "group": "8a15ca423c637161",
        "order": 5,
        "width": 0,
        "height": 0,
        "passthru": false,
        "label": "Back",
        "tooltip": "",
        "color": "",
        "bgcolor": "",
        "className": "",
        "icon": "arrow_back",
        "payload": "{\"tab\":\"Home\"}",
        "payloadType": "json",
        "topic": "topic",
        "topicType": "msg",
        "x": 430,
        "y": 120,
        "wires": [
            [
                "cbef3ed26dc04669"
            ]
        ]
    },
    {
        "id": "c809ff94aedd53ff",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "set Timer",
        "func": "var m = {};\nvar device_timeout = [];\nm.deviceID=global.get(\"deviceID\");\nm.timeout=msg.payload.TIMEOUT;\nm.cmd=msg.payload.CMD;\nmsg.payload=m;\n\nif (global.get(\"device_timeout\")==undefined) global.set(\"device_timeout\",[]);\ndevice_timeout = global.get(\"device_timeout\");\nfor (i =0; i < device_timeout.length; i++) {\n    if (device_timeout[i].deviceID==m.deviceID) {\n        device_timeout.splice(i,1);\n        break;\n    }\n}\n\ndevice_timeout.push(m);\n\nglobal.set(\"device_timeout\",device_timeout);\n\n\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 340,
        "y": 160,
        "wires": [
            []
        ]
    },
    {
        "id": "a3a8ae9391861515",
        "type": "link in",
        "z": "439e43cb848a5281",
        "name": "Settings starting",
        "links": [
            "33da9a0c5c471908",
            "5184a259947998c0"
        ],
        "x": 55,
        "y": 60,
        "wires": [
            [
                "0fc3c8e3128af58d"
            ]
        ]
    },
    {
        "id": "13f2fe26d8c8d9b3",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "any Timer is set",
        "func": "if (global.get(\"device_timeout\")==undefined ) {\n    return [];\n} else {\n    if (global.get(\"device_timeout\").length==0) {\n        return [];\n    }\n}\nvar devices_msg={};\ndevices_msg.payload=[];\n\nfor (i=0; i < global.get(\"device_timeout\").length; i++) {\n    if ((global.get(\"device_timeout\")[i].timeout--)==0) {\n        devices_msg.payload.push({\"deviceID\":global.get(\"device_timeout\")[i].deviceID,\"cmd\":global.get(\"device_timeout\")[i].cmd});\n        global.get(\"device_timeout\").splice(i,1);\n    }\n}\nmsg.topic=\"TIMER\";\nmsg.payload = global.get(\"device_timeout\");\n\nreturn [devices_msg,msg];",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 280,
        "y": 120,
        "wires": [
            [
                "3e63f34496452891"
            ],
            [
                "efc6af2b1324e10d"
            ]
        ]
    },
    {
        "id": "3e63f34496452891",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "timeout to MQTT",
        "func": "if (msg.payload==null) return;\nvar devices=[];\ndevices=msg.payload;\n\nif(devices.length > 0) {\n    for (i=0; i< devices.length; i++) {\n        msg.topic=\"SmartSocket/\"+devices[i].deviceID+\"/Socket/CMD\";\n        msg.payload=devices[i].cmd;\n        node.send(msg);\n    }\n}\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 430,
        "y": 60,
        "wires": [
            [
                "9f57d0424883ef48"
            ]
        ]
    },
    {
        "id": "7d4ba8302ecf747a",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "retrive payload",
        "func": "var m={};\nm.payload = msg.payload.payload;\nm.topic = msg.payload.topic;\nreturn m;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 140,
        "y": 120,
        "wires": [
            [
                "00a07b17407a70b9"
            ]
        ]
    },
    {
        "id": "15f0595c9855419e",
        "type": "json",
        "z": "439e43cb848a5281",
        "name": "",
        "property": "payload",
        "action": "",
        "pretty": false,
        "x": 330,
        "y": 60,
        "wires": [
            [
                "7d4ba8302ecf747a"
            ]
        ]
    },
    {
        "id": "00a07b17407a70b9",
        "type": "switch",
        "z": "439e43cb848a5281",
        "name": "",
        "property": "topic",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "TIMER",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "NOTATHOMESET",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "NOTATHOMECANCEL",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "SCHEDULEADD",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "SCHEDULECANCEL",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 5,
        "x": 90,
        "y": 220,
        "wires": [
            [
                "c809ff94aedd53ff"
            ],
            [
                "08f21e319a0704b6"
            ],
            [
                "34e3bb851f803d73"
            ],
            [
                "119e1ef365315558"
            ],
            [
                "5e78b9e1042d35f6"
            ]
        ]
    },
    {
        "id": "85a329500822475d",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "load device data",
        "func": "if (msg.payload==\"change\")\n    return msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 620,
        "y": 360,
        "wires": [
            [
                "28aca6c3e73f9b87"
            ]
        ]
    },
    {
        "id": "28aca6c3e73f9b87",
        "type": "link out",
        "z": "4bc53f77f2c857ba",
        "name": "load all devices(OUT)",
        "links": [
            "7d466904dcc22a23"
        ],
        "x": 795,
        "y": 360,
        "wires": []
    },
    {
        "id": "46840fc0f3f2dd33",
        "type": "switch",
        "z": "4bc53f77f2c857ba",
        "name": "which Tab",
        "property": "name",
        "propertyType": "msg",
        "rules": [
            {
                "t": "eq",
                "v": "Home",
                "vt": "str"
            },
            {
                "t": "eq",
                "v": "Settings",
                "vt": "str"
            }
        ],
        "checkall": "true",
        "repair": false,
        "outputs": 2,
        "x": 400,
        "y": 360,
        "wires": [
            [
                "85a329500822475d"
            ],
            [
                "1a255c93d38aa3b4"
            ]
        ]
    },
    {
        "id": "1a255c93d38aa3b4",
        "type": "function",
        "z": "4bc53f77f2c857ba",
        "name": "load global deviceName",
        "func": "msg={};\nmsg.topic=\"setDeviceName\";\nmsg.payload={};\nmsg.payload.deviceName = global.get(\"deviceName\");\nmsg.payload.deviceID = global.get(\"deviceID\");\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 630,
        "y": 420,
        "wires": [
            [
                "33da9a0c5c471908",
                "c83ce79a4a41f31b"
            ]
        ]
    },
    {
        "id": "33da9a0c5c471908",
        "type": "link out",
        "z": "4bc53f77f2c857ba",
        "name": "goto Settings",
        "links": [
            "a3a8ae9391861515"
        ],
        "x": 815,
        "y": 420,
        "wires": []
    },
    {
        "id": "cbef3ed26dc04669",
        "type": "ui_ui_control",
        "z": "439e43cb848a5281",
        "name": "",
        "events": "all",
        "x": 580,
        "y": 120,
        "wires": [
            []
        ]
    },
    {
        "id": "08f21e319a0704b6",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "Not At Home DB",
        "func": "var qm = {};\n\nqm.topic = \"SELECT count(*) as rs,\"+\n    msg.payload.BTIME+\" as BTIME,\"+msg.payload.ETIME+\" as ETIME,\"+\n    msg.payload.RBTIME+\" as RBTIME,\"+msg.payload.RETIME+\" as RETIME,'\"+\n    msg.payload.deviceID+\"' as deviceID from notAtHome where deviceID='\"+msg.payload.deviceID+\"'\";\n\n\nreturn qm;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 320,
        "y": 200,
        "wires": [
            [
                "8b1d782112d40d67"
            ]
        ]
    },
    {
        "id": "8b1d782112d40d67",
        "type": "sqlite",
        "z": "439e43cb848a5281",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "record exist",
        "x": 310,
        "y": 260,
        "wires": [
            [
                "8c0704ce9f016fc5"
            ]
        ]
    },
    {
        "id": "8c0704ce9f016fc5",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "insert or update",
        "func": "if (msg.payload[0].rs > 0) {\n    msg.topic = \"update notAtHome set startTime=\"+msg.payload[0].BTIME+\n    \",endTime=\"+msg.payload[0].ETIME+\",randomStartTime=\"+msg.payload[0].RBTIME+\n    \",randomEndTime=\"+msg.payload[0].RETIME+\" where deviceID='\"+\n    msg.payload[0].deviceID+\"'\";\n} else {\n    msg.topic=\"Insert into notAtHome(deviceID, startTime,randomStartTime, endTime, randomEndTime) values('\"+\n    msg.payload[0].deviceID+\"',\"+msg.payload[0].BTIME+\",\"+msg.payload[0].RBTIME+\",\"+\n    msg.payload[0].ETIME+\",\"+msg.payload[0].RETIME+\")\";\n}\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 320,
        "y": 320,
        "wires": [
            [
                "35d803a5ac38b25f"
            ]
        ]
    },
    {
        "id": "35d803a5ac38b25f",
        "type": "sqlite",
        "z": "439e43cb848a5281",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "update notAtHome",
        "x": 530,
        "y": 280,
        "wires": [
            []
        ]
    },
    {
        "id": "34e3bb851f803d73",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "cancel notAtHomeDB",
        "func": "msg.topic = \"delete from notAtHome where deviceID='\"+\n        msg.payload.deviceID+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 320,
        "y": 360,
        "wires": [
            [
                "d62377f9cf6a2f10"
            ]
        ]
    },
    {
        "id": "d62377f9cf6a2f10",
        "type": "sqlite",
        "z": "439e43cb848a5281",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "delete notAtHome Record",
        "x": 550,
        "y": 360,
        "wires": [
            []
        ]
    },
    {
        "id": "96234fb3dffef340",
        "type": "inject",
        "z": "1170da0f4e4c8045",
        "name": "check schedule timer",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "40",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "payloadType": "date",
        "x": 120,
        "y": 340,
        "wires": [
            [
                "12b72a7ccf8b71c9",
                "51a171751b468ca0",
                "6ee23180b9c21fa6"
            ]
        ]
    },
    {
        "id": "12b72a7ccf8b71c9",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "fixed",
        "sql": "select A.deviceID, A.startTime from notAtHome A, device B where A.deviceID=B.deviceID and b.lastState = 0 and strftime(\"%H\",Time ( 'now', 'localtime' ))*60+strftime(\"%MH\",Time ( 'now', 'localtime' )) = randomStartTime\n",
        "name": "check ON notAtHome DB",
        "x": 370,
        "y": 320,
        "wires": [
            [
                "af36dd03367a2452"
            ]
        ]
    },
    {
        "id": "51a171751b468ca0",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "fixed",
        "sql": "select A.deviceID, A.endTime from notAtHome A, device B where A.deviceID=B.deviceID and b.lastState = 1 and  strftime(\"%H\",Time ( 'now', 'localtime' ))*60+strftime(\"%MH\",Time ( 'now', 'localtime' )) = randomEndTime\n",
        "name": "check OFF notAtHome",
        "x": 380,
        "y": 380,
        "wires": [
            [
                "f4ed1537f199e2c4"
            ]
        ]
    },
    {
        "id": "af36dd03367a2452",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "Power ON",
        "func": "var mqtt={};\nvar db={};\nvar randTime=0;\nif (msg.payload.length == 0) return;\nfor (i=0; i < msg.payload.length; i++) {\n    randTime = msg.payload[i].startTime+Math.floor((Math.random()*60-30));\n    db.topic = \"update notAtHome set randomStartTime=\"+randTime+\n        \" where deviceID='\"+msg.payload[i].deviceID+\"'\";\n    mqtt.topic = \"SmartSocket/\"+msg.payload[i].deviceID+\"/Socket/CMD\";\n    mqtt.payload=\"ON\";\n    node.send([db,mqtt]);\n}\n",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 570,
        "y": 320,
        "wires": [
            [
                "f0192ebdb030dd97"
            ],
            [
                "fc6287b6085264c4"
            ]
        ]
    },
    {
        "id": "f4ed1537f199e2c4",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "Power Off",
        "func": "var mqtt={};\nvar db={};\nvar randTime=0;\nif (msg.payload.length == 0) return;\nfor (i=0; i < msg.payload.length; i++) {\n    randTime = msg.payload[i].endTime+Math.floor((Math.random()*60-30));\n    db.topic = \"update notAtHome set randomEndTime=\"+randTime+\n        \" where deviceID='\"+msg.payload[i].deviceID+\"'\";\n    mqtt.topic = \"SmartSocket/\"+msg.payload[i].deviceID+\"/Socket/CMD\";\n    mqtt.payload=\"OFF\";\n    node.send([mqtt,db]);\n}\n",
        "outputs": 2,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 560,
        "y": 380,
        "wires": [
            [
                "fc6287b6085264c4"
            ],
            [
                "32fb4c7134eda469"
            ]
        ]
    },
    {
        "id": "fc6287b6085264c4",
        "type": "mqtt out",
        "z": "1170da0f4e4c8045",
        "name": "notAtHome-ON-OFF",
        "topic": "",
        "qos": "1",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "de64abcc7738c08d",
        "x": 800,
        "y": 360,
        "wires": []
    },
    {
        "id": "f0192ebdb030dd97",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "next Random ON Time",
        "x": 780,
        "y": 300,
        "wires": [
            []
        ]
    },
    {
        "id": "32fb4c7134eda469",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "next Random OFF Time",
        "x": 810,
        "y": 420,
        "wires": [
            []
        ]
    },
    {
        "id": "17d673196b252993",
        "type": "ui_template",
        "z": "439e43cb848a5281",
        "group": "8a15ca423c637161",
        "name": "css",
        "order": 1,
        "width": 0,
        "height": 0,
        "format": "<style>\n  .rounded {\n  border-radius: 12px 12px 12px 12px;\n}\n.tdcss {\n    text-align:right;\n}\n\n  \n</style>\n",
        "storeOutMessages": true,
        "fwdInMessages": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 500,
        "y": 40,
        "wires": [
            []
        ]
    },
    {
        "id": "119e1ef365315558",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "add Schedule",
        "func": "msg.topic=\"insert into schedule (deviceID,w1,w2,w3,w4,w5,w6,w0,stime,cmd) values ('\"+\n    msg.payload.deviceID+\"',\"+msg.payload.w1+\",\"+msg.payload.w2+\n    \",\"+msg.payload.w3+\",\"+msg.payload.w4+\",\"+msg.payload.w5+\n    \",\"+msg.payload.w6+\",\"+msg.payload.w0+\",'\"+msg.payload.stime+\"','\"+msg.payload.cmd+\"')\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 300,
        "y": 420,
        "wires": [
            [
                "9b8641728b6c419e"
            ]
        ]
    },
    {
        "id": "9b8641728b6c419e",
        "type": "sqlite",
        "z": "439e43cb848a5281",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "add Schedule Record",
        "x": 540,
        "y": 420,
        "wires": [
            [
                "24467af1f6ef4626"
            ]
        ]
    },
    {
        "id": "e58a44cb5a086ccb",
        "type": "sqlite",
        "z": "439e43cb848a5281",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "fetch all schedule",
        "x": 270,
        "y": 580,
        "wires": [
            [
                "ca5aa488d1183ff4"
            ]
        ]
    },
    {
        "id": "24467af1f6ef4626",
        "type": "link out",
        "z": "439e43cb848a5281",
        "name": "fetch schedule(OUT)",
        "links": [
            "4d16f19dda5aa1c3"
        ],
        "x": 695,
        "y": 440,
        "wires": []
    },
    {
        "id": "e6bef7ab409ec58e",
        "type": "link in",
        "z": "439e43cb848a5281",
        "name": "",
        "links": [],
        "x": -35,
        "y": 500,
        "wires": [
            []
        ]
    },
    {
        "id": "4d16f19dda5aa1c3",
        "type": "link in",
        "z": "439e43cb848a5281",
        "name": "fetch schedule(IN)",
        "links": [
            "24467af1f6ef4626",
            "c83ce79a4a41f31b",
            "dc3176d3a803559f"
        ],
        "x": 75,
        "y": 580,
        "wires": [
            [
                "efdd09522857585c"
            ]
        ]
    },
    {
        "id": "c02dbec01ee3a152",
        "type": "comment",
        "z": "439e43cb848a5281",
        "name": "fetch schedule",
        "info": "",
        "x": 110,
        "y": 540,
        "wires": []
    },
    {
        "id": "194f3bf15b2f34be",
        "type": "comment",
        "z": "439e43cb848a5281",
        "name": "fetch schedule",
        "info": "",
        "x": 720,
        "y": 400,
        "wires": []
    },
    {
        "id": "efdd09522857585c",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "schedule records",
        "func": "msg.topic = \"select rowid, deviceID, w1,w2,w3,w4,w5,w6,w0,stime,cmd from schedule where deviceID='\"+global.get(\"deviceID\")+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 170,
        "y": 640,
        "wires": [
            [
                "e58a44cb5a086ccb"
            ]
        ]
    },
    {
        "id": "5184a259947998c0",
        "type": "link out",
        "z": "439e43cb848a5281",
        "name": "",
        "links": [
            "a3a8ae9391861515"
        ],
        "x": 635,
        "y": 580,
        "wires": []
    },
    {
        "id": "9ed4c419cf2c134b",
        "type": "comment",
        "z": "439e43cb848a5281",
        "name": "Settings Start",
        "info": "",
        "x": 630,
        "y": 620,
        "wires": []
    },
    {
        "id": "ca5aa488d1183ff4",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "send msg to template",
        "func": "msg.topic=\"SCHEDULERS\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 480,
        "y": 580,
        "wires": [
            [
                "5184a259947998c0"
            ]
        ]
    },
    {
        "id": "c83ce79a4a41f31b",
        "type": "link out",
        "z": "4bc53f77f2c857ba",
        "name": "fetch schedule(OUT)",
        "links": [
            "4d16f19dda5aa1c3"
        ],
        "x": 815,
        "y": 460,
        "wires": []
    },
    {
        "id": "5e78b9e1042d35f6",
        "type": "function",
        "z": "439e43cb848a5281",
        "name": "delete Schedule",
        "func": "msg.topic=\"delete from schedule where rowid=\"+msg.payload.rowid;\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 300,
        "y": 480,
        "wires": [
            [
                "9b8641728b6c419e"
            ]
        ]
    },
    {
        "id": "6ee23180b9c21fa6",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "perform schedule",
        "func": "var date = new Date(msg.payload);\nvar w = date.getDay();\nvar h=date.getHours();\nvar m=date.getMinutes();\nmsg.topic=\"SELECT deviceID, cmd from schedule where w\"+w+\"=1 and stime='\"+h+\":\"+m+\"'\";\nreturn msg;",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 290,
        "y": 480,
        "wires": [
            [
                "3da5e56a4fd812b1"
            ]
        ]
    },
    {
        "id": "3da5e56a4fd812b1",
        "type": "sqlite",
        "z": "1170da0f4e4c8045",
        "mydb": "0c752464ee7109c3",
        "sqlquery": "msg.topic",
        "sql": "",
        "name": "records in schedule",
        "x": 510,
        "y": 480,
        "wires": [
            [
                "2a10906e87688185"
            ]
        ]
    },
    {
        "id": "2a10906e87688185",
        "type": "function",
        "z": "1170da0f4e4c8045",
        "name": "send  to mqtt",
        "func": "var mqtt={};\nif (msg.payload.length > 0) {\n    for (i=0; i < msg.payload.length; i++) {\n        mqtt.topic = \"SmartSocket/\"+msg.payload[i].deviceID+\"/Socket/CMD\";\n        mqtt.payload = msg.payload[i].cmd;\n        node.send(mqtt);\n    }\n}\n\n",
        "outputs": 1,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 690,
        "y": 480,
        "wires": [
            [
                "3f816e90ed9524a8"
            ]
        ]
    },
    {
        "id": "3f816e90ed9524a8",
        "type": "mqtt out",
        "z": "1170da0f4e4c8045",
        "name": "",
        "topic": "",
        "qos": "1",
        "retain": "",
        "respTopic": "",
        "contentType": "",
        "userProps": "",
        "correl": "",
        "expiry": "",
        "broker": "de64abcc7738c08d",
        "x": 870,
        "y": 480,
        "wires": []
    },
    {
        "id": "16eca474e0a71522",
        "type": "comment",
        "z": "1170da0f4e4c8045",
        "name": "To Update Socket Panel",
        "info": "",
        "x": 860,
        "y": 100,
        "wires": []
    },
    {
        "id": "c7f182c9be7ea62f",
        "type": "comment",
        "z": "1170da0f4e4c8045",
        "name": "To Update Socket Panel",
        "info": "",
        "x": 840,
        "y": 200,
        "wires": []
    },
    {
        "id": "decc1ef90f12a1fb",
        "type": "comment",
        "z": "30074137ff8b6e70",
        "name": "from root flow",
        "info": "",
        "x": 90,
        "y": 60,
        "wires": []
    },
    {
        "id": "eb0629e281b3522e",
        "type": "comment",
        "z": "30074137ff8b6e70",
        "name": "to Update Socket Panel",
        "info": "",
        "x": 600,
        "y": 180,
        "wires": []
    }
]