-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathautomation_engine.json
1 lines (1 loc) · 83.5 KB
/
automation_engine.json
1
[{"id":"dd535f73e60cae86","type":"tab","label":"Automation Engine","disabled":false,"info":"","env":[]},{"id":"9abb35e9b7955a5a","type":"inject","z":"dd535f73e60cae86","name":"Settings","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"{\"rulestore\":\"\",\"loglevel\":3}","payloadType":"json","x":140,"y":60,"wires":[["ddff2f6eba4946b9"]]},{"id":"ddff2f6eba4946b9","type":"change","z":"dd535f73e60cae86","name":"","rules":[{"t":"set","p":"settings","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":60,"wires":[[]]},{"id":"ced1ee2a662bfa13","type":"inject","z":"dd535f73e60cae86","name":"Sample data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"data","payload":"{ \"id\": \"temp\", \"name\": \"temperature\", \"type\": \"number\", \"value\": 15 }","payloadType":"json","x":210,"y":260,"wires":[["b7118d1dc8f62527","ef42ee4db43ae8e5"]]},{"id":"b7118d1dc8f62527","type":"function","z":"dd535f73e60cae86","name":"Automation Engine","func":"function AddMessage(priority, loglevel, message) {\n if (priority <= loglevel) {\n node.status(message);\n }\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n AddMessage(0,0,{fill:\"red\",shape:\"ring\",text:\"Missing settings\"});\n return;\n}\n// Set settings defaults\nif (settings.rulestore === undefined) settings.rulestore = \"\";\nif (settings.loglevel === undefined) settings.loglevel = 6;\n\nlet rules = flow.get(\"rules\", settings.rulestore) ?? [];\nlet datacache = flow.get(\"datacache\") ?? {};\n\nif ((msg.topic === \"data\") || (msg.topic === \"time\")) {\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"0| Input type: \" + msg.topic });\n if (msg.topic === \"data\") {\n datacache[msg.payload.id] = msg.payload;\n flow.set(\"datacache\", datacache);\n }\n\n\n for (let i = 0; i < rules.length; i++) {\n let rule = rules[i];\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"0| Checking \" + rule.name });\n if (!rule.enabled) {\n // Rule is not enabled, skip to the next rule\n AddMessage(3, settings.loglevel,{ fill: \"green\", shape: \"dot\", text: \"1a| Rule \" + rule.name + \" not enabled.\" });\n continue;\n } \n\n // Check if the current data is listed in the conditions\n if (((msg.topic === \"data\") && (rule.conditions.filter(e => e.operand === msg.payload.id).length > 0)) || ((msg.topic === \"time\") && (rule.conditions.filter(e => e.trigger === \"time\").length > 0))) {\n AddMessage(4, settings.loglevel,{fill:\"grey\",shape:\"dot\",text:\"1| Evaluating rule \"+rule.name});\n let condition_or = false;\n let condition_and = true;\n // evaluate conditions\n for (let j = 0; j < rule.conditions.length; j++) {\n let condition = rule.conditions[j];\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"2| Check condition \" + j + \", trigger:\" +condition.trigger});\n let data = datacache[condition.operand];\n switch (condition.trigger) {\n case \"time\": \n // Data validation\n if (condition.value.length !== 5) {\n AddMessage(1, settings.loglevel,{ fill: \"red\", shape: \"dot\", text: \"3| Date format incorrect (length)\" });\n condition_and = false;\n condition_or = false;\n break;\n }\n if (condition.value.split(\":\").length !== 2) {\n AddMessage(1, settings.loglevel,{ fill: \"red\", shape: \"dot\", text: \"3| Date format incorrect (hh:mm)\" });\n condition_and = false;\n condition_or = false;\n break;\n }\n // Construct the current time\n let now = new Date();\n let hour = \"00000\" + now.getHours();\n let minute = \"00000\" + now.getMinutes();\n hour = hour.substring(hour.length - 2, hour.length);\n minute = minute.substring(minute.length - 2, minute.length);\n\n data = { \"id\": \"time\", \"name\": \"time\", \"type\": \"string\", \"value\": hour + \":\" + minute };\n break;\n case \"data\":\n case \"state\":\n // if data does not exists, evaluate to true - most to allow initial states\n if (data === undefined) {\n condition_or = true;\n AddMessage(3, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"2| \" + condition.operand + \" data missing, evaluating to true\" });\n break;\n }\n break;\n default: AddMessage(1, settings.loglevel,{ fill: \"red\", shape: \"ring\", text: \"4| Unknown condition trigger \" + condition.trigger });\n\n }\n\n let breakout = false;\n if (data !== undefined) {\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"3| \" + condition.operand + \" condition: \" + data.value+ \" \" + condition.operator + \" \"+ condition.value});\n \n switch(condition.operator) {\n case \"GT\": \n if (data.value > condition.value) {\n condition_or = true;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition true\" });\n } else {\n condition_and = false;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition false\" });\n if (rule.operator === \"AND\") {\n breakout = true;\n }\n }\n break;\n case \"LT\":\n if (data.value < condition.value) {\n condition_or = true;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition true\" });\n } else {\n condition_and = false;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition false\" });\n if (rule.operator === \"AND\") {\n breakout = true;\n }\n }\n break;\n case \"EQ\":\n if (data.value === condition.value) {\n condition_or = true;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition true\" });\n } else {\n condition_and = false;\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4| \" + condition.operand + \" condition false\" });\n if (rule.operator === \"AND\") {\n breakout = true;\n }\n }\n break;\n default: AddMessage(1, settings.loglevel,{ fill: \"red\", shape: \"ring\", text: \"4| Unknown condition operator \"+ condition.operator});\n } // end switch\n }\n // Check if we need to break out from the loop (if a condition failed already and we are in AND mode)\n if (breakout) {\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"4a| Exit condition checking\" });\n break;\n }\n } // end for j loop\n // All the rules are evaluated\n let passed = false;\n if ((rule.operator === \"AND\") && (condition_and)) { passed = true }\n if ((rule.operator === \"OR\") && (condition_or)) { passed = true }\n if (passed) {\n AddMessage(3, settings.loglevel, { fill: \"green\", shape: \"dot\", text: \"5| \" + rule.name + \": Condition result: \" + passed });\n } else {\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"5| \" + rule.name + \": Condition result: \" + passed});\n }\n\n // Execute the actions\n if (passed) { \n for (let k=0; k<rule.actions.length; k++) { \n let action = rule.actions[k];\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"6| Starting action \" + action.type });\n switch (action.type) {\n case \"setstate\":\n datacache[action.state] = {\"id\": action.state, \"name\": action.name, \"type\": \"state\", \"value\": action.value}\n flow.set(\"datacache\", datacache);\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"7| Status set:\" + action.state });\n break;\n case \"control\":\n node.send({\"topic\": action.topic, \"payload\": action.value});\n AddMessage(4, settings.loglevel,{ fill: \"grey\", shape: \"dot\", text: \"7| Control sent:\" + action.topic });\n break;\n default: AddMessage(1, settings.loglevel,{ fill: \"red\", shape: \"ring\", text: \"7| Unknown action type \" + action.type });\n }\n } // End of action loop\n } // End of actions \n\n } // Condition was found\n } // End of looping through the rules\n\n\n} // End of processing data message","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":300,"wires":[["1331ca398aae3494","50e8e14346358262"]]},{"id":"1331ca398aae3494","type":"debug","z":"dd535f73e60cae86","name":"debug 76","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":790,"y":300,"wires":[]},{"id":"7033825860df0449","type":"link in","z":"dd535f73e60cae86","name":"Automation Engine Data Injection","links":["9533ba1480dc771e","708a5dbeeaf6ad79","162349ebba2dca35","593c88dfd060d2b1"],"x":275,"y":360,"wires":[["ef42ee4db43ae8e5","b7118d1dc8f62527"]]},{"id":"878426d38ba8d702","type":"inject","z":"dd535f73e60cae86","name":"Rules","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"[{\"name\":\"Date test rule\",\"enabled\":false,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"time\",\"operand\":\"\",\"operator\":\"EQ\",\"value\":\"15:00\"},{\"trigger\":\"data\",\"operand\":\"soil_moisture\",\"operator\":\"LT\",\"value\":11},{\"trigger\":\"state\",\"operand\":\"plant_state\",\"operator\":\"EQ\",\"value\":\"WATERED\"}],\"actions\":[{\"type\":\"control\",\"topic\":\"date_test\",\"value\":1},{\"type\":\"setstate\",\"state\":\"washingmachine_state\",\"name\":\"Washingmachine State\",\"value\":\"WASHING\"}]},{\"name\":\"Washing Starts\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"data\",\"operand\":\"washingmachine_power\",\"operator\":\"GT\",\"value\":1500},{\"trigger\":\"state\",\"operand\":\"washingmachine_state\",\"operator\":\"EQ\",\"value\":\"IDLE\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"washingmachine_state\",\"name\":\"Washingmachine State\",\"value\":\"WASHING\"}]},{\"name\":\"Washing Ends\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"data\",\"operand\":\"washingmachine_power\",\"operator\":\"LT\",\"value\":2},{\"trigger\":\"state\",\"operand\":\"washingmachine_state\",\"operator\":\"EQ\",\"value\":\"WASHING\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"washingmachine_state\",\"name\":\"Washingmachine State\",\"value\":\"IDLE\"},{\"type\":\"control\",\"topic\":\"washing_complete\",\"value\":1}]},{\"name\":\"DayTime Starts\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"data\",\"operand\":\"outside_light\",\"operator\":\"GT\",\"value\":400},{\"trigger\":\"state\",\"operand\":\"nighttime_state\",\"operator\":\"EQ\",\"value\":\"NIGHTTIME\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"nighttime_state\",\"name\":\"Night or Day\",\"value\":\"DAYTIME\"},{\"type\":\"control\",\"topic\":\"daytime_starts\",\"value\":1}]},{\"name\":\"NightTime Starts\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"data\",\"operand\":\"outside_light\",\"operator\":\"LT\",\"value\":270},{\"trigger\":\"state\",\"operand\":\"nighttime_state\",\"operator\":\"EQ\",\"value\":\"DAYTIME\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"nighttime_state\",\"name\":\"Night or Day\",\"value\":\"NIGHTTIME\"},{\"type\":\"control\",\"topic\":\"nighttime_starts\",\"value\":1}]},{\"name\":\"Plant Dry Reminder\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"time\",\"operand\":\"\",\"operator\":\"EQ\",\"value\":\"19:00\"},{\"trigger\":\"state\",\"operand\":\"plant_state\",\"operator\":\"EQ\",\"value\":\"DRY\"}],\"actions\":[{\"type\":\"control\",\"topic\":\"plant_dry_reminder\",\"value\":1}]},{\"name\":\"Plant Dry\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"time\",\"operand\":\"\",\"operator\":\"EQ\",\"value\":\"19:00\"},{\"trigger\":\"data\",\"operand\":\"soil_moisture\",\"operator\":\"LT\",\"value\":11},{\"trigger\":\"state\",\"operand\":\"plant_state\",\"operator\":\"EQ\",\"value\":\"WATERED\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"plant_state\",\"name\":\"Plant\",\"value\":\"DRY\"},{\"type\":\"control\",\"topic\":\"plant_dry\",\"value\":1}]},{\"name\":\"Plant watered\",\"enabled\":true,\"operator\":\"AND\",\"conditions\":[{\"trigger\":\"data\",\"operand\":\"soil_moisture\",\"operator\":\"GT\",\"value\":11},{\"trigger\":\"state\",\"operand\":\"plant_state\",\"operator\":\"EQ\",\"value\":\"DRY\"}],\"actions\":[{\"type\":\"setstate\",\"state\":\"plant_state\",\"name\":\"Plant\",\"value\":\"WATERED\"},{\"type\":\"control\",\"topic\":\"plant_watered\",\"value\":1}]}]","payloadType":"json","x":130,"y":120,"wires":[["ce0d32dd4dcc4471"]]},{"id":"ce0d32dd4dcc4471","type":"change","z":"dd535f73e60cae86","name":"","rules":[{"t":"set","p":"rules","pt":"flow","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":120,"wires":[[]]},{"id":"12a7d649080a5796","type":"function","z":"dd535f73e60cae86","name":"Data for Automation Engine","func":"msg.topic = \"data\";\nlet value = 15;\nmsg.payload = { \"id\": \"temp\", \"name\": \"temperature\", \"type\": \"number\", \"value\": value }\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":180,"y":540,"wires":[["9533ba1480dc771e"]]},{"id":"9533ba1480dc771e","type":"link out","z":"dd535f73e60cae86","name":"link out 25","mode":"link","links":["7033825860df0449"],"x":355,"y":540,"wires":[]},{"id":"194e235473bd73d7","type":"comment","z":"dd535f73e60cae86","name":"Status Logging","info":"","x":120,"y":1080,"wires":[]},{"id":"1a958a2bdce43a02","type":"debug","z":"dd535f73e60cae86","name":"Status data","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":340,"y":1120,"wires":[]},{"id":"54d90ba4d65cbbe5","type":"function","z":"dd535f73e60cae86","name":"Aggregated Status Collection","func":"let status = flow.get(\"status_summary\");\nif (status===undefined) {\n status = [];\n}\n\nif (msg.topic===\"reset\") {\n status = [];\n flow.set(\"status_summary\", status);\n msg.payload = status;\n return msg;\n}\n\nlet now = new Date();\nlet key = msg.status.fill + \"|\" + msg.status.shape + \"|\" + msg.status.source.name;\n\nlet found = false;\nfor (let i=0;i<status.length;i++) {\n if (status[i].key === key) {\n // if match is found, increase the counter and update the time\n status[i].count++;\n status[i].timestamp = now.getTime();\n status[i].time = now.toLocaleString();\n status[i].text = msg.status.text;\n found = true;\n break;\n }\n}\n\n// This error is not logged before, create it\nif (!found) {\n status.push({ \"key\": key, \"timestamp\": now.getTime(), \"fill\": msg.status.fill, \"shape\": msg.status.shape, \"text\": msg.status.text, \"source\": msg.status.source.name, \"type\": msg.status.source.type, \"count\": 1, \"time\" : now.toLocaleString()});\n}\n\nflow.set(\"status_summary\", status);\n\nmsg.payload = status;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":1180,"wires":[["0178365162bf3d41","d16a3f615b1c64dd","910439d0cf1ad93b"]]},{"id":"0178365162bf3d41","type":"debug","z":"dd535f73e60cae86","name":"Summary output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":1120,"wires":[]},{"id":"2acff4a8de655a40","type":"inject","z":"dd535f73e60cae86","name":"Reset logs","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reset","payload":"","payloadType":"date","x":120,"y":1240,"wires":[["54d90ba4d65cbbe5","6ed5bbb48cf82bbe"]]},{"id":"6ed5bbb48cf82bbe","type":"function","z":"dd535f73e60cae86","name":"Individual Status Collection","func":"const collectionsize = 100; // define the number of error to be collected. Once limit is reached, older error is deleted.\n\nlet status = flow.get(\"status_all\");\nif (status===undefined) {\n status = [];\n}\n\nif (msg.topic===\"reset\") {\n status = [];\n flow.set(\"status_all\", status);\n msg.payload = status;\n return msg;\n}\n\nlet now = new Date();\n\nif (status.length >= collectionsize) {\n status.shift();\n}\n\nstatus.push({ \"timestamp\": now.getTime(), \"fill\": msg.status.fill, \"shape\": msg.status.shape, \"text\": msg.status.text, \"source\": msg.status.source.name, \"type\": msg.status.source.type, \"time\": now.toLocaleString() });\n\nflow.set(\"status_all\", status);\n\nmsg.payload = status;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":430,"y":1240,"wires":[["b7a9ed9cf26f34c2","5878cdfa6334aa09","edad1536aed48886"]]},{"id":"b7a9ed9cf26f34c2","type":"debug","z":"dd535f73e60cae86","name":"All output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":1300,"wires":[]},{"id":"d16a3f615b1c64dd","type":"template","z":"dd535f73e60cae86","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<table>\n <tr><th>Last Occurance</th><th>Count</th><th>Fill</th><th>Shape</th><th>Text</th><th>Source</th><th>Node Type</th></tr>\n {{#payload}}\n <tr class=\"\">\n <td>{{time}}</td>\n <td>{{count}}</td>\n <td>{{fill}}</td>\n <td>{{shape}}</td>\n <td>{{text}}</td>\n <td>{{source}}</td>\n <td>{{type}}</td>\n </tr>\n {{/payload}}\n</table>\n","output":"str","x":660,"y":1180,"wires":[["c84e1a313de06b19"]]},{"id":"5878cdfa6334aa09","type":"template","z":"dd535f73e60cae86","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<table>\n <tr><th>Time</th><th>Fill</th><th>Shape</th><th>Text</th><th>Source</th><th>Node Type</th></tr>\n {{#payload}}\n <tr class=\"\">\n <td>{{time}}</td>\n <td>{{fill}}</td>\n <td>{{shape}}</td>\n <td>{{text}}</td>\n <td>{{source}}</td>\n <td>{{type}}</td>\n </tr>\n {{/payload}}\n</table>\n\n","output":"str","x":660,"y":1240,"wires":[["be98bef28ee43608"]]},{"id":"c84e1a313de06b19","type":"ui_template","z":"dd535f73e60cae86","group":"8be0a783c25b7c66","name":"","order":0,"width":"24","height":"10","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":840,"y":1180,"wires":[[]]},{"id":"be98bef28ee43608","type":"ui_template","z":"dd535f73e60cae86","group":"0bbfe8d835f43144","name":"","order":0,"width":"24","height":"10","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":840,"y":1240,"wires":[[]]},{"id":"7961c23da93a3587","type":"inject","z":"dd535f73e60cae86","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":1420,"wires":[["7ba1e10d51a0dd56"]]},{"id":"7ba1e10d51a0dd56","type":"function","z":"dd535f73e60cae86","name":"Summary file","func":"// Get the current time and convert it to text\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\n\n// Generate out file name pattern\nmsg.fname = \"status_summary_\"+ yyyy + mm + dd + \".csv\";\n// Full filename with path for the file node later\nmsg.filename = \"/home/nygma/\"+ msg.fname;\nmsg.email = {};\nmsg.email.summaryfilename = msg.filename;\nmsg.email.todaytext = now.toLocaleDateString();\n\nlet status = flow.get(\"status_summary\");\nmsg.email.summarycontent = status;\n\n// Check if status log exists at all\nif (status !== undefined) {\n // Check if we have any statuss\n if (status.length>0) {\n msg.payload = status;\n node.status({ fill: \"blue\", shape: \"ring\", text: msg.fname });\n return msg;\n }\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":1420,"wires":[["c4518252837b692a"]]},{"id":"0b15d7254c26d5b1","type":"file","z":"dd535f73e60cae86","name":"Summary file create","filename":"filename","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":680,"y":1420,"wires":[["6a3e5fce89e3225d"]]},{"id":"c4518252837b692a","type":"csv","z":"dd535f73e60cae86","name":"","sep":",","hdrin":"","hdrout":"all","multi":"one","ret":"\\n","temp":"timestamp,time,fill,shape,text,source,type,count","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":490,"y":1420,"wires":[["0b15d7254c26d5b1"]]},{"id":"6a3e5fce89e3225d","type":"function","z":"dd535f73e60cae86","name":"All statuses","func":"// Get the current time and convert it to text\nvar now = new Date();\nvar yyyy = now.getFullYear();\nvar mm = now.getMonth() < 9 ? \"0\" + (now.getMonth() + 1) : (now.getMonth() + 1); // getMonth() is zero-based\nvar dd = now.getDate() < 10 ? \"0\" + now.getDate() : now.getDate();\nvar hh = now.getHours() < 10 ? \"0\" + now.getHours() : now.getHours();\nvar mmm = now.getMinutes() < 10 ? \"0\" + now.getMinutes() : now.getMinutes();\nvar ss = now.getSeconds() < 10 ? \"0\" + now.getSeconds() : now.getSeconds();\n\n// Generate out file name pattern\nmsg.fname = \"status_all_\"+ yyyy + mm + dd + \".csv\";\n// Full filename with path for the file node later\nmsg.filename = \"/home/nygma/\"+ msg.fname;\nmsg.email.allstatusfilename = msg.filename;\nmsg.email.allstatusfilenameonly = msg.fname;\n\nlet status = flow.get(\"status_all\");\n\n// Check if status log exists at all\nif (status !== undefined) {\n // Check if we have any statuss\n if (status.length>0) {\n msg.payload = status;\n node.status({ fill: \"blue\", shape: \"ring\", text: msg.fname });\n return msg;\n }\n}\n\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":330,"y":1500,"wires":[["2b8075f9721cf747"]]},{"id":"2ad6a31d9ce27362","type":"file","z":"dd535f73e60cae86","name":"All statuses file create","filename":"filename","filenameType":"msg","appendNewline":true,"createDir":false,"overwriteFile":"true","encoding":"none","x":680,"y":1500,"wires":[["182ea9f86ed47d3e"]]},{"id":"2b8075f9721cf747","type":"csv","z":"dd535f73e60cae86","name":"","sep":",","hdrin":"","hdrout":"all","multi":"one","ret":"\\n","temp":"timestamp,time,message,source,type","skip":"0","strings":true,"include_empty_strings":"","include_null_values":"","x":490,"y":1500,"wires":[["2ad6a31d9ce27362"]]},{"id":"be3726a2aca60769","type":"change","z":"dd535f73e60cae86","name":"Set up the email","rules":[{"t":"set","p":"attachments","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"attachments.path","pt":"msg","to":"email.allstatusfilename","tot":"msg"},{"t":"set","p":"attachments.filename","pt":"msg","to":"email.allstatusfilenameonly","tot":"msg"},{"t":"set","p":"topic","pt":"msg","to":"\"Status report - \" & msg.email.todaytext","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":1580,"wires":[["1007333fd1b66489"]]},{"id":"1007333fd1b66489","type":"e-mail","z":"dd535f73e60cae86","server":"smtp.gmail.com","port":"465","secure":true,"tls":true,"name":"[email protected]","dname":"Gmail","x":730,"y":1580,"wires":[]},{"id":"182ea9f86ed47d3e","type":"template","z":"dd535f73e60cae86","name":"Email Body","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<head>\n</head>\n\n\n<body>\n <h1>Status Update Summary - {{email.todaytext}} </h1>\n <p>\n <table border=\"1\">\n <tr><th>Last Occurance</th><th>Count</th><th>Fill</th><th>Shape</th><th>Text</th><th>Source</th><th>Node Type</th></tr>\n <tbody>\n {{#email.summarycontent}}\n <tr>\n <td>{{time}}</td>\n <td>{{count}}</td>\n <td>{{fill}}</td>\n <td>{{shape}}</td>\n <td>{{text}}</td>\n <td>{{source}}</td>\n <td>{{type}}</td>\n </tr>\n {{/email.summarycontent}}\n </tbody>\n </table>\n </p>\n <p>Please find the detailed status report in the attachment</p>\n \n </body>","x":350,"y":1580,"wires":[["be3726a2aca60769"]]},{"id":"be6de02e774bbc4a","type":"change","z":"dd535f73e60cae86","name":"Reset","rules":[{"t":"set","p":"topic","pt":"msg","to":"reset","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":130,"y":1300,"wires":[["54d90ba4d65cbbe5","6ed5bbb48cf82bbe"]]},{"id":"61ba29290f717746","type":"status","z":"dd535f73e60cae86","name":"","scope":["b7118d1dc8f62527"],"x":160,"y":1180,"wires":[["54d90ba4d65cbbe5","6ed5bbb48cf82bbe","1a958a2bdce43a02"]]},{"id":"3a9b17681be3a912","type":"template","z":"dd535f73e60cae86","name":"","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<table>\n <tr><th>Name</th><th>Value</th></tr>\n {{#payload}}\n <tr class=\"\">\n <td>{{name}}</td>\n <td>{{value}}</td>\n </tr>\n {{/payload}}\n</table>\n","output":"str","x":1000,"y":380,"wires":[["7661260766daf1b0"]]},{"id":"7661260766daf1b0","type":"ui_template","z":"dd535f73e60cae86","group":"d082943d35fc7c40","name":"","order":0,"width":"6","height":"10","format":"<div ng-bind-html=\"msg.payload\"></div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1180,"y":380,"wires":[[]]},{"id":"ef42ee4db43ae8e5","type":"delay","z":"dd535f73e60cae86","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":520,"y":380,"wires":[["72e8300b2722235c"]]},{"id":"72e8300b2722235c","type":"function","z":"dd535f73e60cae86","name":"Convert datacache to table","func":"let datacache = flow.get(\"datacache\");\nif (datacache === undefined) {\n datacache = {};\n}\n\nlet output = [];\n\nfor (const [key, value] of Object.entries(datacache)) {\n output.push(value);\n}\n\nmsg.payload = output;\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":380,"wires":[["3a9b17681be3a912","4b7c9794be8932e3"]]},{"id":"50e8e14346358262","type":"function","z":"dd535f73e60cae86","name":"Test notification","func":"msg.payload = { \"service\": 1, \"type\": \"message\", \"content\": \"Automation - \"+msg.topic+\": \"+msg.payload};\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":240,"wires":[["f07c8d202f242ac0"]]},{"id":"f07c8d202f242ac0","type":"link out","z":"dd535f73e60cae86","name":"","mode":"link","links":["86deb2f58b76aa52"],"x":955,"y":240,"wires":[]},{"id":"f2a3a58f560a25ab","type":"inject","z":"dd535f73e60cae86","name":"Washing on","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"data","payload":"{\"id\":\"washingmachine_power\",\"name\":\"Washing Machine Power\",\"type\":\"number\",\"value\":2500}","payloadType":"json","x":210,"y":180,"wires":[["b7118d1dc8f62527"]]},{"id":"ca281e3988220823","type":"inject","z":"dd535f73e60cae86","name":"Washing off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"data","payload":"{\"id\":\"washingmachine_power\",\"name\":\"Washing Machine Power\",\"type\":\"number\",\"value\":1}","payloadType":"json","x":210,"y":220,"wires":[["b7118d1dc8f62527"]]},{"id":"eb42e3b69b37271a","type":"comment","z":"dd535f73e60cae86","name":"Rule documentation","info":"# Rule Documentation\n\nRules store the instructions for the Automation Engines which incoming sensor readings to check and what what to do.\nRules are stored as object in an array.\n\nBelow example shows my washing machine cycle, where a high energy consumption sets the washing machine state to \"washing\". If during the washing state the consumption drops below 2, it sets the status back to idle and issues a command which can trigger an email or notification that the washing has been completed.\nTo archieve this 2 rules are needed, the first rule looks for the increase in power consumption to determine if a washing is in progress. The second rule looks for the low consumption to triggere the \"washing completed\" message.\n\n## Rule Header\n\n```\n {\n \"name\": \"Washing Starts\",\n \"enabled\": true,\n \"operator\": \"AND\",\n```\nAbove is the header of the rule, which contains a name that will be included in the message logs.\n`enabled` attributes controls if the rule is executed or not.\nThe `operator` describes if all the conditions need to be met (AND) or only one of the condition needs to be met (OR).\n\n## Rule Conditions\n\n```\n \"conditions\": [\n```\n`conditions` attribute is an array which lists all the conditions (triggers) for the rule. Each condition has a `trigger` attribute which describes if the condition is triggered by update on data value, or for example time.\n\n### Data and State triggers\n\nThis section explains how to create a trigger on a data value or state value. This condition should be used when the condition is to be compared with a data point or state. When data update for (in the below case) `washingmachine_power` arrived to the node, the logic know which rules to check - which has a condition for this data point.\nThese conditions always contain either a `\"trigger\": \"data\"` or `\"trigger\": \"state\"` attribute.\n\n```\n {\n \"trigger\": \"data\",\n \"operand\": \"washingmachine_power\",\n \"operator\": \"GT\",\n \"value\": 270\n },\n```\nEach condition has an `operand` which is a data point id that is sent to the node, or a state id which is set by one of the rule.\n`operator` is the condition operator, and current EQ=equal, GT=greather than and LT=less than supported.\n`value` is the value to check against. \nIn the above example the condition is true if the value of the `washingmachine_power` is more than 270.\n\n```\n {\n \"trigger\": \"state\",\n \"operand\": \"washingmachine_state\",\n \"operator\": \"EQ\",\n \"value\": \"IDLE\"\n }\n ],\n```\nThis is same as above, but in this case the `operand` is a state (which will be defined below), and the value is a string not a number.\n\n### Time Conditions\n\nThese conditions should be used if the rule needs to be evaluated as a certain time of the day. For example I want to receive my \"plants are dry\" validation in the evening when I have time to water them. Of course, the time condition can be used in conjunction with other data conditions, but this ensures it is only validated at a specific time.\nTime conditions defines hour and minute of the day when the rule is evaluated.\n\n```\n {\n \"trigger\": \"time\",\n \"operand\": \"\",\n \"operator\": \"EQ\",\n \"value\": \"19:00\"\n },\n```\n\nThe `trigger` value in this case is `time`, `operand` is not required, `operator` is `EQ` (others can also be used, but may not be properly evaluated), and the `value` is the time in HH:MM format (24 hour format).\nFor performance reasons we suggest to put the time condition as the first condition in the `conditions` list, so the code does not check the rest of the conditions if the time does not match with the current time.\n\n## Rule Actions\n\n```\n \"actions\": [\n```\n`actions` is an array which contains action that are executed if the conditions are met.\n\nFew examples of supported actions:\n\n### Setting state\n\nThis example is used to set internal state that can be used in rule conditions. As in the above example power > 270 is evaluated with the state of IDLE. And power < 2 is only considered if the state is WASHING.\n\n```\n {\n \"type\": \"setstate\",\n \"state\": \"washingmachine_state\",\n \"name\": \"Washingmachine State\",\n \"value\": \"WASHING\"\n }\n```\n`type` is set to `setstate`, `state` and `name` are the id and name of the state. Id can be used on conditions, name is only for logging.\n`value` is the new state value to be set. This can be a number or text. State gets stored in the flow variable `datacache` along with other values. For this reason the state id and data id should be unique.\n\nIMPORTANT: if the state does not exists yet (because let's say Node-Red just restarted), the condition will evaluate to true. This explicitly assumes that this is the initial state. Since the rules are executed in order, make sure the rule with the \"assumed initial state\" is in the list first.\n\n### Sending Commands\n\nThis action sends a message out of the output port of the Automation Engine function node which can be used to control devices, send messages, etc.\n\n```\n {\n \"type\": \"control\",\n \"topic\": \"washing_complete\",\n \"value\": 1\n }\n```\n`type` is set to `control`, `topic` is a text and will be used as `msg.topic` of the outgoing message. `value` can be a number or text and will be used as `msg.payload` of the outgoing message.\n\n```\n ]\n },\n```\nFinally close the actions array and the rule object.","x":540,"y":120,"wires":[]},{"id":"6448dccefec677b8","type":"comment","z":"dd535f73e60cae86","name":"Version History","info":"# Version 1.3 - Rule editor\n\n- Rule editor screen\n- Show rules, select rules, select conditions, select actions\n- Edit existing conditions\n- Enable/Disable rules (buttons)\n\n# Version 1.2 - Small changes\n\n- Log level added to settings\n- Context storage can be defined in settings\n- Enable/disable flag for rules\n\n# Version 1.1 - Time conditions\n\n- Condition checking exists after a false condition - peformance improvment\n- Adding trigger attribute to conditions (date|state|time)\n- Time condition is available\n\n# Version 1.0 - First Release\n\n- Core logic\n- Supported conditions: data value, state\n- Supported operators: LT, EQ, GT\n- Supported actions: setstate, control\n- State logging for debugging\n- Simple UI to monitor data","x":840,"y":60,"wires":[]},{"id":"4df9e415c5448de6","type":"comment","z":"dd535f73e60cae86","name":"Settings Documentation","info":"## Rule Storage Context\n\nDefines the store for the rules. Default value is \"\" which is the default storage context, but change it to any of the context defined in the settings.js.\n\n## Log Level\n\nSet the logging level of the logic. Higher value means more messages are shown, lower value less messages.\n- 0: Critical messages\n- 1: Error messages\n- 2: Warning messages\n- 3: Information messages\n- 4: Debug messages\n\nE.g. if `loglevel` is set to 3, the system will show messages in priority 0-3.","x":580,"y":60,"wires":[]},{"id":"e8eeca745c649671","type":"comment","z":"dd535f73e60cae86","name":"Future Ideas","info":"- Quit condition checking if operator AND and condition fails DONE\n- Date in conditions DONE\n- Reduce logs DONE\n- Delay as an action\n- Enable/disable rules DONE\n- Storage context DONE\n- condition if state does not exist (sort of already exists)\n- Editor:\n - Add new rule\n - Change rule operator\n - Edit rule name\n - Validation on rule changes\n- Add a view on the current operands and set states","x":1070,"y":60,"wires":[]},{"id":"11c71d100a233a94","type":"inject","z":"dd535f73e60cae86","name":"Time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"60","crontab":"","once":false,"onceDelay":0.1,"topic":"time","payload":"","payloadType":"date","x":140,"y":320,"wires":[["b7118d1dc8f62527"]]},{"id":"5db602285bbb4cd4","type":"link in","z":"dd535f73e60cae86","name":"Reset Logs","links":["d53d25b55da64e34"],"x":145,"y":1360,"wires":[["54d90ba4d65cbbe5","6ed5bbb48cf82bbe"]]},{"id":"869e47cb566e960c","type":"inject","z":"dd535f73e60cae86","name":"Reset logs","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reset","payload":"","payloadType":"date","x":120,"y":500,"wires":[["d53d25b55da64e34"]]},{"id":"d53d25b55da64e34","type":"link out","z":"dd535f73e60cae86","name":"link out 29","mode":"link","links":["5db602285bbb4cd4"],"x":245,"y":500,"wires":[]},{"id":"3d9409379342ef59","type":"comment","z":"dd535f73e60cae86","name":"Rule Editor DB2","info":"","x":140,"y":620,"wires":[]},{"id":"9d0d7f0104f44d8a","type":"ui-template","z":"dd535f73e60cae86","group":"e0d58841230b2c65","page":"","ui":"","name":"Rule List","order":1,"width":"4","height":"1","head":"","format":"<template>\n <v-card class=\"mx-auto\" color=\"white\" width=\"100%\">\n <v-img class=\"text-white\" height=\"62\" src=\"https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg\"\n cover>\n\n <v-toolbar color=\"transparent\">\n <v-toolbar-title class=\"text-h6\" text=\"Rule List\"></v-toolbar-title>\n <v-tooltip text=\"Refresh rule list\" location=\"top\">\n <template v-slot:activator=\"{ props }\" v-slot:append>\n <v-btn v-bind=\"props\" icon=\"mdi-refresh\" @click=\"send({topic: 'refresh', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Enabled rule\" location=\"top\">\n <template v-slot:activator=\"{ props }\" v-slot:append>\n <v-btn v-bind=\"props\" icon=\"mdi-bell-check\" @click=\"send({topic: 'enablerule', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Disable rule\" location=\"top\">\n <template v-slot:activator=\"{ props }\" v-slot:append>\n <v-btn v-bind=\"props\" icon=\"mdi-bell-cancel\" @click=\"send({topic: 'disablerule', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Move Up\" location=\"top\">\n <template v-slot:activator=\"{ props }\" v-slot:append>\n <v-btn v-bind=\"props\" icon=\"mdi-arrow-up-bold-circle-outline\" @click=\"send({topic: 'moveup', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Move Down\" location=\"top\">\n <template v-slot:activator=\"{ props }\" v-slot:append>\n <v-btn v-bind=\"props\" icon=\"mdi-arrow-down-bold-circle-outline\" @click=\"send({topic: 'movedown', payload: null})\">\n </v-btn>\n </template>\n </v-tooltip>\n </v-toolbar>\n </v-img>\n\n\n\n\n\n\n\n <v-card-text>\n <v-list bg-color=\"white\">\n \n <v-list-item v-for=\"(item, index) in items\" :key=\"index\" :value=\"index\" :title=\"item.name\" :subtitle=\"item.subtitle\"\n :prepend-icon=\"getEnabledIcon(item)\" @click=\"send({topic: 'selected', payload: item})\">\n </v-list-item>\n </v-list>\n </v-card-text>\n\n </v-card>\n</template>\n\n\n\n\n<script>\n export default {\n data() {\n return {\n counter: 0,\n items: [],\n selected: []\n }\n },\n mounted() {\n this.counter = 0\n },\n watch: {\n msg: function(){ \n if(this.msg.payload != undefined) { \n this.items = this.msg.payload;\n this.selected = [];\n this.counter = this.items.length; \n this.send({payload:undefined});\n }\n }\n },\n methods: {\n getEnabledIcon: function (item) {\n if (item !== undefined ) {\n if (item.enabled) {\n return 'mdi-bell-check'\n } else {\n return 'mdi-bell-cancel'\n }\n }\n }\n }\n }\n</script>\n\n<style>\n .v-overlay__content .v-list {\n background: rgba(var(--v-group-background));\n color: rgba(var(--v-group-on-background), var(--v-high-emphasis-opacity));\n }\n</style>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":500,"y":800,"wires":[["e0d963d56787736b","17ea1359dbc18a68"]]},{"id":"f20c789a97cb2333","type":"ui-template","z":"dd535f73e60cae86","group":"690c33a99698055d","page":"","ui":"","name":"Rule Details","order":1,"width":"4","height":"1","head":"","format":"<template>\n\n\n<v-card class=\"mx-auto\" color=\"white\" width=\"100%\">\n <v-img class=\"text-white\" height=\"62\" src=\"https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg\" cover>\n\n <v-toolbar color=\"transparent\">\n <v-toolbar-title class=\"text-h6\" :text=\"rulename\"></v-toolbar-title>\n </v-toolbar>\n </v-img>\n\n <v-card-subtitle>\n {{ subtitle }}\n </v-card-subtitle>\n\n </v-card>\n <v-card class=\"mx-auto\" color=\"white\" width=\"100%\">\n \n <v-toolbar color=\"transparent\">\n <v-toolbar-title class=\"text-h6\" text=\"Conditions\"></v-toolbar-title>\n <v-tooltip text=\"Move condition up\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-boom-gate-arrow-up-outline\"\n @click=\"send({topic: 'conditionup', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Move condition down\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-boom-gate-arrow-down-outline\"\n @click=\"send({topic: 'conditiondown', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Add condition\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-file-document-plus-outline\"\n @click=\"send({topic: 'addcondition', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Delete condition\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-delete\"\n @click=\"send({topic: 'deletecondition', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n \n </v-toolbar>\n <v-card-text>\n <v-list bg-color=\"white\">\n <v-list-item v-for=\"(item, index) in conditions\" :key=\"index\" :value=\"index\" :title=\"item.text\"\n :prepend-icon=\"getConditionIcon(item)\" @click=\"send({topic: 'condition', payload: item})\">\n </v-list-item>\n </v-list>\n </v-card-text>\n </v-card>\n <v-card class=\"mx-auto\" color=\"white\" width=\"100%\">\n \n <v-toolbar color=\"transparent\">\n <v-toolbar-title class=\"text-h6\" text=\"Actions\"></v-toolbar-title>\n <v-tooltip text=\"Move action up\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-gesture-swipe-up\" @click=\"send({topic: 'actionup', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Move action down\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-gesture-swipe-down\" @click=\"send({topic: 'actiondown', payload: null})\">\n </v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Add SetState Action\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-book-plus-multiple\" @click=\"send({topic: 'addsetstateaction', payload: null})\">\n </v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Add Control Action\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-sign-direction-plus\" @click=\"send({topic: 'addcontrolaction', payload: null})\">\n </v-btn>\n </template>\n </v-tooltip>\n <v-tooltip text=\"Delete action\" location=\"top\">\n <template v-slot:activator=\"{ props }\">\n <v-btn v-bind=\"props\" icon=\"mdi-delete\" @click=\"send({topic: 'deleteaction', payload: null})\"></v-btn>\n </template>\n </v-tooltip>\n </v-toolbar>\n <v-card-text>\n <v-list bg-color=\"white\">\n <v-list-item v-for=\"(item, index) in actions\" :key=\"index\" :value=\"index\" :title=\"item.text\"\n :prepend-icon=\"getActionIcon(item)\" @click=\"send({topic: 'action', payload: item})\">\n </v-list-item>\n </v-list>\n </v-card-text>\n \n </v-card>\n</template>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n rulename: \"\",\n subtitle: \"\",\n conditions: [],\n actions: []\n }\n },\n watch: {\n // watch for any changes of \"count\"\n msg: function() {\n if(this.msg.payload != undefined){\n this.rulename = this.msg.payload.name;\n this.subtitle = this.msg.payload.subtitle;\n this.conditions = this.msg.payload.conditions;\n this.actions = this.msg.payload.actions;\n this.send({payload:undefined});\n }\n }\n },\n computed: {\n },\n methods: {\n getConditionIcon: function (item) {\n if (item !== undefined ) {\n switch(item.trigger) {\n case \"time\":\n return \"mdi-clock-time-eight-outline\";\n break;\n case \"data\":\n return \"mdi-matrix\";\n break;\n case \"state\":\n return \"mdi-state-machine\";\n break;\n }\n }\n },\n getActionIcon: function (item) {\n if (item !== undefined ) {\n switch(item.type) {\n case \"control\":\n return \"mdi-message-processing\";\n break;\n case \"setstate\":\n return \"mdi-bullhorn\";\n break;\n }\n }\n }\n },\n mounted() {\n },\n unmounted() {\n }\n }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1130,"y":800,"wires":[["185ceeaae6edb7c0","e2e3abaaccd62596"]]},{"id":"49369f497bdb6978","type":"inject","z":"dd535f73e60cae86","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":130,"y":800,"wires":[["f70ac1503433d89a"]]},{"id":"f70ac1503433d89a","type":"function","z":"dd535f73e60cae86","name":"Get rule list","func":"function AddMessage(priority, loglevel, message) {\n if (priority <= loglevel) {\n node.status(message);\n }\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n AddMessage(0,0,{fill:\"red\",shape:\"ring\",text:\"Missing settings\"});\n return;\n}\nlet rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\nfor (let i = 0; i < rules.length; i++) {\n rules[i].id = i;\n// if (rules[i].selected) {\n// msg.selectedrulename = rules[i].name;\n// }\n\n // generate subtitle text and index\n rules[i].subtitle=\"\";\n for (let j = 0; j < rules[i].conditions.length; j++) {\n switch (rules[i].conditions[j].trigger) {\n case \"data\":\n rules[i].subtitle += \"data \";\n break;\n case \"state\":\n rules[i].subtitle += \"state \";\n break;\n case \"time\":\n rules[i].subtitle += \"time \";\n break;\n default:\n rules[i].subtitle += \"condition \";\n }\n rules[i].conditions[j].id = j;\n }\n rules[i].subtitle += \" > \";\n // assign index to actions\n for (let j = 0; j < rules[i].actions.length; j++) {\n switch (rules[i].actions[j].type) {\n case \"setstate\":\n rules[i].subtitle += \"setstate \";\n break;\n case \"control\":\n rules[i].subtitle += \"control \";\n break;\n default:\n rules[i].subtitle += \"action \";\n }\n rules[i].actions[j].id = j;\n }\n\n\n\n\n\n}\n\nflow.set(\"editor\", {});\n\nmsg.payload = rules;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":800,"wires":[["9d0d7f0104f44d8a"]]},{"id":"9b339b3e1b38b684","type":"debug","z":"dd535f73e60cae86","name":"Rule list click","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1130,"y":860,"wires":[]},{"id":"b3375d3eb0577814","type":"link in","z":"dd535f73e60cae86","name":"Refresh Rule List","links":["8596d89d9ba39ffb","2885e5ff45f0f0a0"],"x":175,"y":760,"wires":[["f70ac1503433d89a"]]},{"id":"e0f91c6209d89106","type":"function","z":"dd535f73e60cae86","name":"Handle rule selection","func":"function AddMessage(priority, loglevel, message) {\n if (priority <= loglevel) {\n node.status(message);\n }\n}\n\nlet editor = flow.get(\"editor\") ?? {};\nif ([\"conditionup\", \"conditiondown\", \"actionup\", \"actiondown\", \"conditionchange\", \"actionchange\",\"addcondition\", \"addsetstateaction\", \"addcontrolaction\", \"deletecondition\", \"deleteaction\"].includes(msg.topic)) {\n msg.payload = editor.selected;\n msg.topic = \"selected\";\n}\n\nif (msg.topic === undefined) {\n msg.payload = {\"name\": \"\", subtitle: \"\", conditions: [], actions: []};\n return msg;\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n AddMessage(0, 0, { fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\nlet rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\nif (msg.topic === \"selected\") {\n // Re-read the data from context in case it was changed\n if (editor.selected !== undefined) {\n let i = msg.payload.id;\n msg.payload = rules[i];\n\n // generate subtitle text and index\n rules[i].subtitle = \"\";\n for (let j = 0; j < rules[i].conditions.length; j++) {\n switch (rules[i].conditions[j].trigger) {\n case \"data\":\n rules[i].subtitle += \"data \";\n break;\n case \"state\":\n rules[i].subtitle += \"state \";\n break;\n case \"time\":\n rules[i].subtitle += \"time \";\n break;\n default:\n rules[i].subtitle += \"condition \";\n }\n rules[i].conditions[j].id = j;\n }\n rules[i].subtitle += \" > \";\n // assign index to actions\n for (let j = 0; j < rules[i].actions.length; j++) {\n switch (rules[i].actions[j].type) {\n case \"setstate\":\n rules[i].subtitle += \"setstate \";\n break;\n case \"control\":\n rules[i].subtitle += \"control \";\n break;\n default:\n rules[i].subtitle += \"action \";\n }\n rules[i].actions[j].id = j;\n }\n flow.set(\"rules\", rules, settings.rulestore);\n }\n\n\n\n // Prep the subtitle as text\n msg.payload.subtitle = \"Enabled: \" + msg.payload.enabled + \", Operator: \"+ msg.payload.operator;\n \n // Prep the condition name as text\n for (let j = 0; j < msg.payload.conditions.length; j++) {\n if (msg.payload.conditions[j].trigger === \"time\") {\n msg.payload.conditions[j].text = \"Time \"+ msg.payload.conditions[j].operator + \" \" + msg.payload.conditions[j].value;\n } else {\n msg.payload.conditions[j].text = msg.payload.conditions[j].operand + \" \"+ msg.payload.conditions[j].operator + \" \" + msg.payload.conditions[j].value;\n }\n }\n // Prep the action name as text\n for (let j = 0; j < msg.payload.actions.length; j++) {\n if (msg.payload.actions[j].type === \"setstate\") {\n msg.payload.actions[j].text = \"Set \" + msg.payload.actions[j].name + \" to \" + msg.payload.actions[j].value;\n }\n if (msg.payload.actions[j].type === \"control\") {\n msg.payload.actions[j].text = \"Control \" + msg.payload.actions[j].topic + \" = \" + msg.payload.actions[j].value;\n }\n }\n \n \n \n\n editor.selected = msg.payload;\n flow.set(\"editor\", editor);\n return msg;\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":900,"y":800,"wires":[["9b339b3e1b38b684","f20c789a97cb2333"]]},{"id":"185ceeaae6edb7c0","type":"debug","z":"dd535f73e60cae86","name":"Condition/Action click","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1540,"y":740,"wires":[]},{"id":"6d73b2b78f76ae50","type":"function","z":"dd535f73e60cae86","name":"Handle selection","func":"if (msg.topic === undefined) {\n msg.payload = { title: \"\", conditioneditor: false, actionsetstateeditor: false, actioncontroleditor: false, condition: {}, action: {} };\n return msg;\n}\nif (msg.topic === \"condition\") {\n let editor = flow.get(\"editor\") ?? {};\n editor.condition = msg.payload;\n flow.set(\"editor\", editor);\n msg.payload = { title: \"Edit: \" + editor.condition.text, conditioneditor: true, actionsetstateeditor: false, actioncontroleditor: false, condition: editor.condition, action: {} };\n return msg;\n}\nif (msg.topic === \"action\") {\n let editor = flow.get(\"editor\") ?? {};\n editor.action = msg.payload;\n flow.set(\"editor\", editor);\n msg.payload = { title: \"Edit: \" + editor.action.text, conditioneditor: false, actionsetstateeditor: (editor.action.type === \"setstate\"), actioncontroleditor: (editor.action.type === \"control\"), condition: {}, action: editor.action };\n return msg;\n}\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1590,"y":800,"wires":[["d859cdcdccc010ab"]]},{"id":"cd58885964eb4a54","type":"link in","z":"dd535f73e60cae86","name":"Refresh Rule Details","links":["0276e0170cb102c9","35b1e31c6aa8e3be","db76c8f2eb7a3abf","4a9ed0aac0903b26"],"x":755,"y":760,"wires":[["e0f91c6209d89106"]]},{"id":"e0d963d56787736b","type":"switch","z":"dd535f73e60cae86","name":"","property":"topic","propertyType":"msg","rules":[{"t":"null"},{"t":"eq","v":"selected","vt":"str"},{"t":"eq","v":"refresh","vt":"str"},{"t":"eq","v":"enablerule","vt":"str"},{"t":"eq","v":"disablerule","vt":"str"},{"t":"eq","v":"moveup","vt":"str"},{"t":"eq","v":"movedown","vt":"str"}],"checkall":"true","repair":false,"outputs":7,"x":650,"y":860,"wires":[["e0f91c6209d89106"],["e0f91c6209d89106"],["2885e5ff45f0f0a0"],["37419353bb9bf0f7"],["37419353bb9bf0f7"],["f227ee31709ba23f"],["f227ee31709ba23f"]]},{"id":"2885e5ff45f0f0a0","type":"link out","z":"dd535f73e60cae86","name":"link out 70","mode":"link","links":["b3375d3eb0577814"],"x":995,"y":900,"wires":[]},{"id":"37419353bb9bf0f7","type":"function","z":"dd535f73e60cae86","name":"Enable/disable rule","func":"let settings = flow.get(\"settings\");\nif (settings === undefined) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\n\nlet editor = flow.get(\"editor\") ?? {};\nif (editor.selected !== undefined) {\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n rules[editor.selected.id].enabled = (msg.topic===\"enablerule\" ? true : false);\n flow.set(\"rules\", rules, settings.rulestore);\n return msg;\n}\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":940,"wires":[["2885e5ff45f0f0a0"]]},{"id":"e2e3abaaccd62596","type":"switch","z":"dd535f73e60cae86","name":"","property":"topic","propertyType":"msg","rules":[{"t":"eq","v":"condition","vt":"str"},{"t":"eq","v":"action","vt":"str"},{"t":"null"},{"t":"eq","v":"conditionup","vt":"str"},{"t":"eq","v":"conditiondown","vt":"str"},{"t":"eq","v":"actionup","vt":"str"},{"t":"eq","v":"actiondown","vt":"str"},{"t":"eq","v":"addcondition","vt":"str"},{"t":"eq","v":"addsetstateaction","vt":"str"},{"t":"eq","v":"addcontrolaction","vt":"str"},{"t":"eq","v":"deletecondition","vt":"str"},{"t":"eq","v":"deleteaction","vt":"str"}],"checkall":"true","repair":false,"outputs":12,"x":1330,"y":880,"wires":[["6d73b2b78f76ae50"],["6d73b2b78f76ae50"],["6d73b2b78f76ae50"],["ed57539a7519a9ae"],["ed57539a7519a9ae"],["ed57539a7519a9ae"],["ed57539a7519a9ae"],["81c3fdd2a6d5121b"],["81c3fdd2a6d5121b"],["81c3fdd2a6d5121b"],["1df97c18d664641d"],["1df97c18d664641d"]]},{"id":"ed57539a7519a9ae","type":"function","z":"dd535f73e60cae86","name":"Move condition/actions","func":"function moveup(array, from) {\n let top = array[from-1];\n array[from-1] = array[from];\n array[from] = top;\n return array;\n}\n\nfunction movedown(array, from) {\n let down = array[from + 1];\n array[from + 1] = array[from];\n array[from] = down;\n return array;\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\n\n\nlet editor = flow.get(\"editor\") ?? {};\nif (editor.condition !== undefined) {\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n if (msg.topic === \"conditionup\") {\n if (editor.condition.id > 0) {\n rules[editor.selected.id].conditions = moveup(rules[editor.selected.id].conditions, editor.condition.id);\n delete editor.condition;\n flow.set(\"editor\", editor);\n }\n }\n if (msg.topic === \"conditiondown\") {\n if (editor.condition.id < rules[editor.selected.id].conditions.length - 1) {\n rules[editor.selected.id].conditions = movedown(rules[editor.selected.id].conditions, editor.condition.id);\n delete editor.condition;\n flow.set(\"editor\", editor);\n }\n }\n for (let j = 0; j < rules[editor.selected.id].conditions.length; j++) {\n rules[editor.selected.id].conditions[j].id = j;\n }\n editor.selected = rules[editor.selected.id];\n flow.set(\"editor\", editor);\n \n // rules[editor.selected.id].conditions[editor.condition.id];\n flow.set(\"rules\", rules, settings.rulestore);\n return msg;\n}\n\nif (editor.action !== undefined) {\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n if (msg.topic === \"actionup\") {\n if (editor.action.id > 0) {\n rules[editor.selected.id].actions = moveup(rules[editor.selected.id].actions, editor.action.id);\n delete editor.action;\n flow.set(\"editor\", editor);\n }\n }\n if (msg.topic === \"actiondown\") {\n if (editor.action.id < rules[editor.selected.id].actions.length - 1) {\n rules[editor.selected.id].actions = movedown(rules[editor.selected.id].actions, editor.action.id);\n delete editor.action;\n flow.set(\"editor\", editor);\n }\n }\n for (let j = 0; j < rules[editor.selected.id].actions.length; j++) {\n rules[editor.selected.id].actions[j].id = j;\n }\n editor.selected = rules[editor.selected.id];\n flow.set(\"editor\", editor);\n\n // rules[editor.selected.id].conditions[editor.condition.id];\n flow.set(\"rules\", rules, settings.rulestore);\n return msg;\n}\n\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1540,"y":860,"wires":[["db76c8f2eb7a3abf"]]},{"id":"db76c8f2eb7a3abf","type":"link out","z":"dd535f73e60cae86","name":"link out 71","mode":"link","links":["cd58885964eb4a54"],"x":1685,"y":860,"wires":[]},{"id":"17ea1359dbc18a68","type":"debug","z":"dd535f73e60cae86","name":"debug 379","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":720,"wires":[]},{"id":"d859cdcdccc010ab","type":"ui-template","z":"dd535f73e60cae86","group":"504d51af9a92c9a8","page":"","ui":"","name":"All Editors","order":1,"width":"4","height":"1","head":"","format":"<template>\n\n\n<v-card class=\"mx-auto\" color=\"white\" width=\"100%\">\n <v-img class=\"text-white\" height=\"62\" src=\"https://cdn.vuetifyjs.com/images/backgrounds/vbanner.jpg\" cover>\n\n <v-toolbar color=\"transparent\">\n <v-toolbar-title class=\"text-h6\" :text=\"title\"></v-toolbar-title>\n \n </v-toolbar>\n </v-img>\n\n <v-card-text>\n <div v-html=\"content\"></div>\n </v-card-text>\n\n </v-card>\n <v-card class=\"mx-auto\" color=\"white\" v-if=\"conditioneditor\" width=\"100%\">\n <v-card-text>\n <v-combobox label=\"Trigger\" :items=\"['data', 'state', 'time']\" v-model=\"condition.trigger\"></v-combobox>\n <v-text-field label=\"Operand\" v-model=\"condition.operand\"></v-text-field >\n <v-combobox label=\"Operator\" :items=\"['LT', 'GT', 'EQ']\" v-model=\"condition.operator\"></v-combobox>\n <v-text-field label=\"Value\" v-model=\"condition.value\"></v-text-field>\n </v-card-text>\n <v-card-actions>\n <v-spacer></v-spacer>\n <v-btn color=\"success\" variant=\"flat\" @click=\"send({topic: 'conditionchange', payload: this.condition})\">\n Update Condition\n <v-icon icon=\"mdi-chevron-right\" end></v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n <v-card class=\"mx-auto\" color=\"white\" v-if=\"actioncontroleditor\" width=\"100%\">\n <v-card-text>\n <v-text-field label=\"Type\" model-value=\"Control\" disabled></v-text-field>\n <v-text-field label=\"Topic\" v-model=\"action.topic\"></v-text-field>\n <v-text-field label=\"Topic\" v-model=\"action.value\"></v-text-field>\n </v-card-text>\n <v-card-actions>\n <v-spacer></v-spacer>\n <v-btn color=\"success\" variant=\"flat\" @click=\"send({topic: 'actionchange', payload: this.action})\">\n Update Action\n <v-icon icon=\"mdi-chevron-right\" end></v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n <v-card class=\"mx-auto\" color=\"white\" v-if=\"actionsetstateeditor\" width=\"100%\">\n <v-card-text>\n <v-text-field label=\"Type\" model-value=\"SetState\" disabled></v-text-field>\n <v-text-field label=\"State\" v-model=\"action.state\"></v-text-field>\n <v-text-field label=\"Name\" v-model=\"action.name\"></v-text-field>\n <v-text-field label=\"Value\" v-model=\"action.value\"></v-text-field>\n </v-card-text>\n <v-card-actions>\n <v-spacer></v-spacer>\n <v-btn color=\"success\" variant=\"flat\" @click=\"send({topic: 'actionchange', payload: this.action})\">\n Update Action\n <v-icon icon=\"mdi-chevron-right\" end></v-icon>\n </v-btn>\n </v-card-actions>\n </v-card>\n\n</template>\n\n<script>\n export default {\n data() {\n // define variables available component-wide\n // (in <template> and component functions)\n return {\n title: \"\",\n conditioneditor: false,\n actionsetstateeditor: false,\n actioncontroleditor: false,\n condition: {},\n action: {}\n }\n },\n watch: {\n // watch for any new messages\n msg: function() {\n if(this.msg.payload != undefined){\n this.title = this.msg.payload.title;\n this.conditioneditor = this.msg.payload.conditioneditor ?? false;\n this.actionsetstateeditor = this.msg.payload.actionsetstateeditor ?? false;\n this.actioncontroleditor = this.msg.payload.actioncontroleditor ?? false;\n this.condition = this.msg.payload.condition ?? {};\n this.action = this.msg.payload.action ?? {};\n this.send({payload:undefined});\n }\n }\n },\n computed: {\n },\n methods: {\n getConditionIcon: function (item) {\n if (item !== undefined ) {\n switch(item.trigger) {\n case \"time\":\n return \"mdi-clock-time-eight-outline\";\n break;\n case \"data\":\n return \"mdi-matrix\";\n break;\n case \"state\":\n return \"mdi-state-machine\";\n break;\n }\n }\n },\n getActionIcon: function (item) {\n if (item !== undefined ) {\n switch(item.type) {\n case \"control\":\n return \"mdi-message-processing\";\n break;\n case \"setstate\":\n return \"mdi-bullhorn\";\n break;\n }\n }\n }\n },\n mounted() {\n },\n unmounted() {\n }\n }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1840,"y":800,"wires":[["23b85808b5d405e2","3e721d5852267663"]]},{"id":"23b85808b5d405e2","type":"debug","z":"dd535f73e60cae86","name":"debug 380","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2070,"y":740,"wires":[]},{"id":"3e721d5852267663","type":"function","z":"dd535f73e60cae86","name":"Handle form submit","func":"function AddMessage(priority, loglevel, message) {\n if (priority <= loglevel) {\n node.status(message);\n }\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n AddMessage(0, 0, { fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\nlet rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n\nif (msg.topic === undefined) {\n msg.payload = { title: \"\", conditioneditor: false, actionsetstateeditor: false, actioncontroleditor: false, condition: {}, action: {} };\n return null;\n}\n\nlet editor = flow.get(\"editor\") ?? {};\n\n// Handle condition change\nif (msg.topic === \"conditionchange\") {\n if (rules[editor.selected.id] === undefined) {\n return [null,{topic:\"Update failed\", payload: \"Cannot find the selected rule\"}];\n }\n if (rules[editor.selected.id].conditions[editor.condition.id] === undefined) {\n return [null, { topic: \"Update failed\", payload: \"Cannot find the selected condition\" }];\n }\n rules[editor.selected.id].conditions[editor.condition.id]=msg.payload;\n editor.selected = rules[editor.selected.id];\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules, settings.rulestore);\n return [msg, { topic: \"Update Successful\", payload: \"Condition updated\" }];\n}\n\n// Handle action change\nif (msg.topic === \"actionchange\") {\n if (rules[editor.selected.id] === undefined) {\n return [null, { topic: \"Update failed\", payload: \"Cannot find the selected rule\" }];\n }\n if (rules[editor.selected.id].actions[editor.action.id] === undefined) {\n return [null, { topic: \"Update failed\", payload: \"Cannot find the selected action\" }];\n }\n rules[editor.selected.id].actions[editor.action.id] = msg.payload;\n editor.selected = rules[editor.selected.id];\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules, settings.rulestore);\n return [msg, { topic: \"Update Successful\", payload: \"Action updated\" }];\n}\n\n","outputs":2,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2090,"y":800,"wires":[["4a9ed0aac0903b26","2c0c7613ed7c365e"],["34a9a75255664690"]]},{"id":"34a9a75255664690","type":"ui-notification","z":"dd535f73e60cae86","ui":"cb79bc4520925e32","position":"top right","colorDefault":true,"color":"#000000","displayTime":"3","showCountdown":true,"outputs":1,"allowDismiss":true,"dismissText":"Close","raw":false,"className":"","name":"","x":2430,"y":820,"wires":[[]]},{"id":"4a9ed0aac0903b26","type":"link out","z":"dd535f73e60cae86","name":"link out 72","mode":"link","links":["cd58885964eb4a54"],"x":2355,"y":780,"wires":[]},{"id":"2c0c7613ed7c365e","type":"debug","z":"dd535f73e60cae86","name":"debug 381","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2380,"y":700,"wires":[]},{"id":"f227ee31709ba23f","type":"function","z":"dd535f73e60cae86","name":"Move Rule","func":"function moveup(array, from) {\n let top = array[from-1];\n array[from-1] = array[from];\n array[from] = top;\n return array;\n}\n\nfunction movedown(array, from) {\n let down = array[from + 1];\n array[from + 1] = array[from];\n array[from] = down;\n return array;\n}\n\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\n\n\nlet editor = flow.get(\"editor\") ?? {};\nif (editor.selected !== undefined) {\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n if (msg.topic === \"moveup\") {\n if (editor.selected.id > 0) {\n rules = moveup(rules, editor.selected.id);\n }\n }\n if (msg.topic === \"movedown\") {\n if (editor.selected.id < rules.length - 1) {\n rules = movedown(rules, editor.selected.id);\n }\n }\n for (let j = 0; j < rules.length; j++) {\n rules.id = j;\n }\n editor.selected = rules[editor.selected.id];\n flow.set(\"editor\", editor);\n \n // rules[editor.selected.id].conditions[editor.condition.id];\n flow.set(\"rules\", rules, settings.rulestore);\n return msg;\n}\n\n\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":830,"y":980,"wires":[["2885e5ff45f0f0a0"]]},{"id":"81c3fdd2a6d5121b","type":"function","z":"dd535f73e60cae86","name":"Add condition/actions","func":"\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\n\n\nlet editor = flow.get(\"editor\") ?? {};\n\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n if (msg.topic === \"addcondition\") {\n let condition = { \"trigger\": \"\", \"operand\": \"new_condition\", \"operator\": \"\", \"value\": \"\", \"icon\": \"🕒\", \"id\": rules[editor.selected.id].conditions.length, \"text\": \"New condition\" };\n rules[editor.selected.id].conditions.push(condition);\n editor.condition = condition;\n node.status({ fill: \"green\", shape: \"ring\", text: \"Condition added\" });\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules);\n return msg;\n }\n if (msg.topic === \"addsetstateaction\") {\n let action = { \"type\": \"setstate\", \"state\": \"new_action\", \"name\": \"\", \"value\": \"\", \"icon\": \"📢\", \"id\": rules[editor.selected.id].actions.length, \"text\": \"New action\" };\n rules[editor.selected.id].actions.push(action);\n editor.action = action;\n node.status({ fill: \"green\", shape: \"ring\", text: \"SetState action added\" });\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules);\n return msg;\n }\n if (msg.topic === \"addcontrolaction\") {\n let action = { \"type\": \"control\", \"topic\": \"new_action\", \"name\": \"\", \"value\": \"\", \"icon\": \"📨\", \"id\": rules[editor.selected.id].actions.length, \"text\": \"New action\" };\n rules[editor.selected.id].actions.push(action);\n node.status({ fill: \"green\", shape: \"ring\", text: \"Control action added\" });\n editor.action = action;\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules);\n return msg;\n }\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1540,"y":900,"wires":[["db76c8f2eb7a3abf"]]},{"id":"1df97c18d664641d","type":"function","z":"dd535f73e60cae86","name":"Del condition/actions","func":"\nlet settings = flow.get(\"settings\");\nif (settings === undefined) {\n node.status({ fill: \"red\", shape: \"ring\", text: \"Missing settings\" });\n return;\n}\n\n\nlet editor = flow.get(\"editor\") ?? {};\n\n let rules = flow.get(\"rules\", settings.rulestore) ?? [];\n\n if (msg.topic === \"deletecondition\") {\n if (editor.condition !== undefined) {\n rules[editor.selected.id].conditions.splice(editor.condition.id,1);\n delete editor.condition;\n node.status({ fill: \"green\", shape: \"ring\", text: \"Condition deleted\" });\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules);\n return msg;\n }\n }\n if (msg.topic === \"deleteaction\") {\n if (editor.action !== undefined) {\n rules[editor.selected.id].actions.splice(editor.action.id, 1);\n delete editor.action;\n node.status({ fill: \"green\", shape: \"ring\", text: \"Action deleted\" });\n flow.set(\"editor\", editor);\n flow.set(\"rules\", rules);\n return msg;\n }\n }\n\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1540,"y":940,"wires":[["db76c8f2eb7a3abf"]]},{"id":"4b7c9794be8932e3","type":"ui-template","z":"dd535f73e60cae86","group":"df49fdb3e4475a5e","page":"","ui":"","name":"Data List","order":1,"width":"0","height":"0","head":"","format":"<template>\n <!-- Provide an input text box to search the content -->\n <v-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n hide-details></v-text-field>\n <v-data-table v-model:search=\"search\" :items=\"msg?.payload\">\n </v-data-table>\n</template>\n\n<script>\n export default {\n data () {\n return {\n search: ''\n }\n },\n methods: {\n // add a function to determine the color of the progress bar given the row's item\n\n\n\n }\n }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1000,"y":420,"wires":[[]]},{"id":"a0d3550574f1d4b7","type":"ui-template","z":"dd535f73e60cae86","group":"8b9c7a446afc31b8","page":"","ui":"","name":"Aggregate status logs","order":1,"width":"0","height":"0","head":"","format":"<template>\n <!-- Provide an input text box to search the content -->\n <v-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n hide-details></v-text-field>\n <v-data-table v-model:search=\"search\" :items=\"msg?.payload\" :headers=\"msg?.headers\">\n\n </v-data-table>\n</template>\n\n<script>\n export default {\n data () {\n return {\n search: ''\n }\n },\n methods: {\n // add a function to determine the color of the progress bar given the row's item\n\n\n\n }\n }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1060,"y":1200,"wires":[[]]},{"id":"33540ec4837b5354","type":"ui-template","z":"dd535f73e60cae86","group":"bad4b39c228e6485","page":"","ui":"","name":"Detailed status messages","order":1,"width":"0","height":"0","head":"","format":"<template>\n <!-- Provide an input text box to search the content -->\n <v-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n hide-details></v-text-field>\n <v-data-table v-model:search=\"search\" :items=\"msg?.payload\" :headers=\"msg?.headers\">\n\n </v-data-table>\n</template>\n\n<script>\n export default {\n data () {\n return {\n search: ''\n }\n },\n methods: {\n // add a function to determine the color of the progress bar given the row's item\n\n\n\n }\n }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1070,"y":1260,"wires":[[]]},{"id":"910439d0cf1ad93b","type":"change","z":"dd535f73e60cae86","name":"Add Headers","rules":[{"t":"set","p":"headers","pt":"msg","to":"[{\"title\":\"Time\",\"value\":\"time\"},{\"title\":\"Fill\",\"value\":\"fill\",\"sortable\":true},{\"title\":\"Shape\",\"value\":\"shape\",\"sortable\":true},{\"title\":\"Text\",\"value\":\"text\"},{\"title\":\"Count\",\"value\":\"count\",\"sortable\":true}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":1200,"wires":[["a0d3550574f1d4b7"]]},{"id":"edad1536aed48886","type":"change","z":"dd535f73e60cae86","name":"Add Headers","rules":[{"t":"set","p":"headers","pt":"msg","to":"[{\"title\":\"Time\",\"value\":\"time\"},{\"title\":\"Fill\",\"value\":\"fill\",\"sortable\":true},{\"title\":\"Shape\",\"value\":\"shape\",\"sortable\":true},{\"title\":\"Message Text\",\"value\":\"text\"},{\"title\":\"Message Source\",\"value\":\"source\",\"sortable\":true},{\"title\":\"Message Type\",\"value\":\"type\",\"sortable\":true}]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":1260,"wires":[["33540ec4837b5354"]]},{"id":"8be0a783c25b7c66","type":"ui_group","name":"Aggregate Status Messages","tab":"809332264a7eff62","order":1,"disp":true,"width":"24","collapse":false,"className":""},{"id":"0bbfe8d835f43144","type":"ui_group","name":"All Status Messages","tab":"809332264a7eff62","order":2,"disp":true,"width":"24","collapse":false,"className":""},{"id":"d082943d35fc7c40","type":"ui_group","name":"Data List","tab":"809332264a7eff62","order":3,"disp":true,"width":"6","collapse":false,"className":""},{"id":"e0d58841230b2c65","type":"ui-group","name":"Rule List","page":"cbbf8c857c4418ed","width":"4","height":"1","order":1,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"690c33a99698055d","type":"ui-group","name":"Rule Details","page":"cbbf8c857c4418ed","width":"4","height":"1","order":2,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"504d51af9a92c9a8","type":"ui-group","name":"Rule Common Editor","page":"cbbf8c857c4418ed","width":"4","height":"1","order":3,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"cb79bc4520925e32","type":"ui-base","name":"My UI","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control","ui-text"],"showPathInSidebar":false},{"id":"df49fdb3e4475a5e","type":"ui-group","name":"Data List","page":"cbbf8c857c4418ed","width":"6","height":"1","order":4,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"8b9c7a446afc31b8","type":"ui-group","name":"Aggregated Status Logs","page":"cbbf8c857c4418ed","width":"6","height":"1","order":5,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"bad4b39c228e6485","type":"ui-group","name":"Detailed Status Logs","page":"cbbf8c857c4418ed","width":"12","height":"1","order":6,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"809332264a7eff62","type":"ui_tab","name":"Automation Engine","icon":"dashboard","disabled":false,"hidden":false},{"id":"cbbf8c857c4418ed","type":"ui-page","name":"Rule Editor","ui":"cb79bc4520925e32","path":"/ruleeditor","icon":"gate-nor","layout":"grid","theme":"70c3f16306584459","order":4,"className":"","visible":"true","disabled":"false"},{"id":"70c3f16306584459","type":"ui-theme","name":"File Browser","colors":{"surface":"#5b56fc","primary":"#0094ce","bgPage":"#ffffff","groupBg":"#ffffff","groupOutline":"#ffffff"},"sizes":{"pagePadding":"0px","groupGap":"0px","groupBorderRadius":"0px","widgetGap":"6px"}}]