實驗自己動手做智能插座,以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資料庫檔案。
功能需求如下:
智能插座的功能:
遠端控制電源開與關 電源開與關倒數計時 外出不在家時,在指定的時段設定隨機開關時間 電源開關時間排程 一、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與端點的動作如下圖所示。
本實驗將不限制連上多少智能插座,且每個智能插座將獨立顯示一個獨立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設計如下圖所示:
三、智能插座端
智能插座端使用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。
四、完成影片展示 VIDEO
五、智能插座端程式碼
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> <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 \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> \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 \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": []
}
]