From e189e5334bd59337eb2457e4fe164d8eee771e0a Mon Sep 17 00:00:00 2001 From: rasta5man Date: Thu, 14 Aug 2025 22:41:23 +0200 Subject: [PATCH] Initial commit - ip 10.0.0.74 --- config | 6 + databases/backup/tbdata.nosql | 0 databases/settings.table | 2 + databases/tbdata.nosql | 0 flow/code.js | 90 +++ flow/comment.js | 11 + flow/debug.js | 100 +++ flow/designer.json | 953 ++++++++++++++++++++++++++++ flow/energomonitor_socomec.js | 455 +++++++++++++ flow/helper/db_helper.js | 44 ++ flow/helper/energo_streambuilder.js | 207 ++++++ flow/httprequest.js | 238 +++++++ flow/infosender.js | 121 ++++ flow/monitorconsumption.js | 156 +++++ flow/monitordisk.js | 96 +++ flow/monitormemory.js | 87 +++ flow/timer.js | 87 +++ flow/timesetter.js | 125 ++++ flow/trigger.js | 79 +++ flow/variables.txt | 0 flow/virtualwirein.js | 43 ++ flow/virtualwireout.js | 41 ++ flow/wsmqttpublish.js | 549 ++++++++++++++++ package.json | 28 + 24 files changed, 3518 insertions(+) create mode 100644 config create mode 100644 databases/backup/tbdata.nosql create mode 100644 databases/settings.table create mode 100644 databases/tbdata.nosql create mode 100644 flow/code.js create mode 100644 flow/comment.js create mode 100644 flow/debug.js create mode 100644 flow/designer.json create mode 100644 flow/energomonitor_socomec.js create mode 100644 flow/helper/db_helper.js create mode 100644 flow/helper/energo_streambuilder.js create mode 100644 flow/httprequest.js create mode 100644 flow/infosender.js create mode 100644 flow/monitorconsumption.js create mode 100644 flow/monitordisk.js create mode 100644 flow/monitormemory.js create mode 100644 flow/timer.js create mode 100644 flow/timesetter.js create mode 100644 flow/trigger.js create mode 100644 flow/variables.txt create mode 100644 flow/virtualwirein.js create mode 100644 flow/virtualwireout.js create mode 100644 flow/wsmqttpublish.js create mode 100644 package.json diff --git a/config b/config new file mode 100644 index 0000000..1f0b2d6 --- /dev/null +++ b/config @@ -0,0 +1,6 @@ +name : Total.js Flow + +// Packages settings +package#flow (Object) : { url: '/' } + +table.settings: rvo_name:string|lang:string|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number diff --git a/databases/backup/tbdata.nosql b/databases/backup/tbdata.nosql new file mode 100644 index 0000000..e69de29 diff --git a/databases/settings.table b/databases/settings.table new file mode 100644 index 0000000..b649808 --- /dev/null +++ b/databases/settings.table @@ -0,0 +1,2 @@ +rvo_name:string|lang:string|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number ++|Grafia 10.0.0.74|en||10_0_0_74_grafia||1883|0|29|unipi|1|30|50|........................................... diff --git a/databases/tbdata.nosql b/databases/tbdata.nosql new file mode 100644 index 0000000..e69de29 diff --git a/flow/code.js b/flow/code.js new file mode 100644 index 0000000..63b31bf --- /dev/null +++ b/flow/code.js @@ -0,0 +1,90 @@ +exports.id = 'code'; +exports.title = 'Code'; +exports.group = 'Common'; +exports.color = '#656D78'; +exports.input = true; +exports.output = 1; +exports.author = 'Peter Širka'; +exports.icon = 'code'; +exports.version = '1.2.0'; +exports.options = { outputs: 1, code: 'send(0, value);', keepmessage: true }; + +exports.html = `
+
+
+
@(Number of outputs)
+
@(Minimum is 1)
+
+
+
@(Code)
+
@(Keep message instance)
+
+`; + +exports.readme = `# Code + +This component executes custom JavaScript code as it is and it doesn't contain any secure scope. + +\`\`\`javascript +// value {Object} contains received data +// send(outputIndex, newValue) sends a new value +// error(value) sends an error +// instance {Object} a current component instance +// flowdata {Object} a current flowdata +// repository {Object} a current repository of flowdata +// Example: + +// send() can be execute multiple times +send(0, value); +\`\`\``; + +exports.install = function(instance) { + + var fn; + + instance.on('data', function(response) { + if (fn) { + try { + fn(response.data, instance, response, instance.options, response.repository, require); + } catch (e) { + response.data = e; + instance.throw(response); + } + } + }); + + instance.reconfigure = function() { + try { + if (instance.options.code) { + instance.status(''); + var code = 'var send = function(index, value) { if (options.keepmessage) { flowdata.data = value; instance.send2(index, flowdata); } else instance.send2(index, value);}; var error = function(err) { instance.throw(err); }; ' + instance.options.code; + fn = new Function('value', 'instance', 'flowdata', 'options', 'repository', 'require', code); + } else { + instance.status('Not configured', 'red'); + fn = null; + } + } catch (e) { + fn = null; + instance.error('Code: ' + e.message); + } + }; + + instance.on('options', instance.reconfigure); + instance.reconfigure(); +}; \ No newline at end of file diff --git a/flow/comment.js b/flow/comment.js new file mode 100644 index 0000000..1e0cd13 --- /dev/null +++ b/flow/comment.js @@ -0,0 +1,11 @@ +exports.id = 'comment'; +exports.title = 'Comment'; +exports.group = 'Common'; +exports.color = '#704cff'; +exports.author = 'Martin Smola'; +exports.icon = 'comment'; +exports.traffic = false; +exports.version = '1.0.0'; +exports.readme = '# Comment'; + +exports.install = function() {}; diff --git a/flow/debug.js b/flow/debug.js new file mode 100644 index 0000000..00cb259 --- /dev/null +++ b/flow/debug.js @@ -0,0 +1,100 @@ +exports.id = 'debug'; +exports.title = 'Debug'; +exports.author = 'Peter Širka'; +exports.color = '#967ADC'; +exports.click = true; +exports.input = true; +exports.icon = 'bug'; +exports.version = '2.0.4'; +exports.options = { enabled: true, repository: false, type: 'data' }; +exports.readme = `# Debug + +Prints data to the debug tab.`; + +exports.html = `
+
+
+
@(Output type)
+
@(Path to the property (leave empty to show the whole data object))
+
@(A group name)
+
@(Enabled)
+
+
+
`; + +exports.install = function(instance) { + + instance.on('data', function(response) { + if (instance.options.enabled) { + + var opt = instance.options; + var rep = response.repository; + var val = response.data; + var id = response.id; + + switch (instance.options.type){ + case 'both': + var data = {}; + data.repository = rep; + data.data = val instanceof Error ? { error: val.message, stack: val.stack } : val; + instance.debug(safeparse(opt.property ? U.get(data, opt.property) : data), undefined, opt.group, id); + break; + case 'repository': + instance.debug(safeparse(opt.property ? U.get(rep, opt.property) : rep), undefined, opt.group, id); + break; + case 'data': + default: + if (val instanceof Error) + instance.debug({ error: val.message, stack: val.stack }, undefined, opt.group, id); + else + instance.debug(safeparse(opt.property ? U.get(val, opt.property) : val), undefined, opt.group, id); + break; + } + } + }); + + instance.on('click', function() { + instance.options.enabled = !instance.options.enabled; + instance.custom.status(); + instance.save(); + }); + + instance.on('options', function() { + instance.custom.status(); + }); + + instance.custom.status = function() { + instance.status(instance.options.enabled ? 'Enabled' : 'Disabled'); + }; + + instance.custom.status(); + + function safeparse(o) { + + if (o instanceof Buffer) + return o; + + if (o === undefined) + return 'undefined'; + + if (o === null) + return 'null'; + + var cache = []; + var str = JSON.stringify(o, function(key, value) { + if (typeof value === 'object' && value !== null) { + if (cache.indexOf(value) !== -1) { + try { + return JSON.parse(JSON.stringify(value)); + } catch (error) { + return; + } + } + cache.push(value); + } + return value; + }); + cache = null; + return JSON.parse(str); + } +}; diff --git a/flow/designer.json b/flow/designer.json new file mode 100644 index 0000000..ffba053 --- /dev/null +++ b/flow/designer.json @@ -0,0 +1,953 @@ +{ + "tabs": [ + { + "id": "1636110831878", + "name": "Main", + "icon": "fa-object-ungroup", + "linker": "main", + "index": 0 + }, + { + "name": "Time setter", + "linker": "time-setter", + "id": "1667235000217", + "index": 1 + } + ], + "components": [ + { + "id": "1636110851291", + "component": "socomec", + "tab": "1636110831878", + "name": "IP - 192.168.1.11 & 12", + "x": 53, + "y": 145, + "connections": { + "0": [ + { + "index": "0", + "id": "1636110855097" + } + ], + "1": [ + { + "index": "0", + "id": "1636110863291" + }, + { + "index": "0", + "id": "1636461793375" + }, + { + "index": "0", + "id": "1643023439891" + } + ], + "2": [ + { + "index": "0", + "id": "1640097360601" + }, + { + "index": "0", + "id": "1669850052514" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#2134B0", + "notes": "", + "options": { + "host": "192.168.1.11", + "port": 502 + } + }, + { + "id": "1636110855097", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 376, + "y": 32, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1636110863291", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 377, + "y": 113, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1636461560741", + "component": "virtualwirein", + "tab": "1636110831878", + "name": "tb-prod01-push", + "x": 23, + "y": 586, + "connections": { + "0": [ + { + "index": "0", + "id": "1694077414274" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-prod01-push", + "color": "gray" + }, + "color": "#303E4D", + "notes": "", + "options": { + "wirename": "tb-prod01-push" + } + }, + { + "id": "1636461793375", + "component": "virtualwireout", + "tab": "1636110831878", + "name": "tb-prod01-push", + "x": 383, + "y": 234, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "tb-prod01-push", + "color": "gray" + }, + "color": "#303E4D", + "notes": "", + "options": { + "wirename": "tb-prod01-push" + } + }, + { + "id": "1636971481195", + "component": "monitormemory", + "tab": "1636110831878", + "name": "Memory", + "x": 37, + "y": 945, + "connections": { + "0": [ + { + "index": "0", + "id": "1641906774515" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "804.16 MB / 987.80 MB", + "color": "gray" + }, + "color": "#F6BB42", + "notes": "", + "options": { + "enabled": true, + "interval": 30000 + } + }, + { + "id": "1636971491099", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 395, + "y": 984, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1636972747889", + "component": "monitorconsumption", + "tab": "1636110831878", + "name": "CPU", + "x": 35, + "y": 850, + "connections": { + "0": [ + { + "index": "0", + "id": "1641906812467" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "3.1% / 57.85 MB", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "monitorfiles": true, + "monitorconnections": true, + "monitorsize": true, + "monitorconsumption": true, + "enabled": true, + "interval": 30000 + } + }, + { + "id": "1636972771931", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 396, + "y": 808, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1640097360601", + "component": "httprequest", + "tab": "1636110831878", + "name": "grafia-prod01.worksys.io", + "x": 380, + "y": 317, + "connections": { + "0": [ + { + "index": "0", + "id": "1640097392518" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#5D9CEC", + "notes": "", + "options": { + "stringify": "json", + "method": "POST", + "url": "http://192.168.252.2:8005/total_energy", + "type": "json" + } + }, + { + "id": "1640097392518", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 697, + "y": 317, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1641906699312", + "component": "monitordisk", + "tab": "1636110831878", + "name": "Disk", + "x": 35, + "y": 1036, + "connections": { + "0": [ + { + "index": "0", + "id": "1641906732272" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "5.67 GB / 7.26 GB", + "color": "gray" + }, + "color": "#F6BB42", + "notes": "", + "options": { + "enabled": true, + "path": "/", + "interval": 30000 + } + }, + { + "id": "1641906703877", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 396, + "y": 1073, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1641906732272", + "component": "code", + "tab": "1636110831878", + "name": "Code", + "x": 196, + "y": 1034, + "connections": { + "0": [ + { + "index": "0", + "id": "1641906703877" + }, + { + "index": "0", + "id": "1641907044563" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#656D78", + "notes": "", + "options": { + "keepmessage": true, + "code": "let response = {};\n\nresponse.hdd_total = value.total;\nresponse.hdd_free = value.free;\nresponse.hdd_used = value.used;\n\nsend(0, response);", + "outputs": 1, + "COMPONENT": "code", + "NAME": "Code" + } + }, + { + "id": "1641906774515", + "component": "code", + "tab": "1636110831878", + "name": "Code", + "x": 197, + "y": 941, + "connections": { + "0": [ + { + "index": "0", + "id": "1636971491099" + }, + { + "index": "0", + "id": "1641907044563" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#656D78", + "notes": "", + "options": { + "keepmessage": true, + "code": "let response = {};\n\nresponse.memory_total = value.total;\nresponse.memory_free = value.free;\nresponse.memory_used = value.used;\n\nsend(0, response);", + "outputs": 1 + } + }, + { + "id": "1641906812467", + "component": "code", + "tab": "1636110831878", + "name": "Code", + "x": 202, + "y": 845, + "connections": { + "0": [ + { + "index": "0", + "id": "1636972771931" + }, + { + "index": "0", + "id": "1641907044563" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#656D78", + "notes": "", + "options": { + "keepmessage": true, + "code": "let response = {};\nresponse.cpu = value.cpu;\nresponse.uptime = value.uptime;\n\nsend(0, response);", + "outputs": 1 + } + }, + { + "id": "1641907044563", + "component": "virtualwireout", + "tab": "1636110831878", + "name": "send-to-services", + "x": 398, + "y": 892, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "color": "#303E4D", + "notes": "", + "options": { + "wirename": "send-to-services" + } + }, + { + "id": "1641907104812", + "component": "virtualwirein", + "tab": "1636110831878", + "name": "send-to-services", + "x": 65, + "y": 1232, + "connections": { + "0": [ + { + "index": "0", + "id": "1641907121073" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "color": "#303E4D", + "notes": "", + "options": { + "wirename": "send-to-services" + } + }, + { + "id": "1641907121073", + "component": "infosender", + "tab": "1636110831878", + "name": "Info sender", + "x": 304, + "y": 1227, + "connections": { + "0": [ + { + "index": "0", + "id": "1641907134719" + }, + { + "index": "0", + "id": "1641907182409" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#2134B0", + "notes": "", + "options": { + "edge": "undefined" + } + }, + { + "id": "1641907134719", + "component": "httprequest", + "tab": "1636110831878", + "name": "http://192.168.252.2:8004/sentmessage", + "x": 533, + "y": 1191, + "connections": { + "0": [ + { + "index": "0", + "id": "1641907188601" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#5D9CEC", + "notes": "", + "options": { + "stringify": "json", + "method": "POST", + "url": "http://192.168.252.2:8004/sentmessage", + "type": "json" + } + }, + { + "id": "1641907182409", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 536, + "y": 1295, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1641907188601", + "component": "debug", + "tab": "1636110831878", + "name": "Debug", + "x": 906, + "y": 1189, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1643023439891", + "component": "code", + "tab": "1636110831878", + "name": "Code", + "x": 569, + "y": 176, + "connections": { + "0": [ + { + "index": "0", + "id": "1643023495930" + } + ] + }, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#656D78", + "notes": "", + "options": { + "keepmessage": true, + "code": "//if(value.hasOwnProperty('1JMYvnx2RzKEo4aWQ7D93vAL8yZV3m9NBePXbrdj'))\n//{\n\t//send(0, value);\n//}\n//if(value.hasOwnProperty('1559') || value.hasOwnProperty('1558'))\nif(value.hasOwnProperty('1559'))\n{\n\tif(value[\"1559\"][0].values.hasOwnProperty(\"total_energy\"))\n\t\t{\n\t\t\tsend(0, value);\n\t\t}\n\n}\n\nif(value.hasOwnProperty('1558'))\n{\n\tif(value[\"1558\"][0].values.hasOwnProperty(\"total_energy\"))\n\t\t{\n\t\t\tsend(0, value);\n\t\t}\n\n}", + "outputs": 1 + } + }, + { + "id": "1643023495930", + "component": "debug", + "tab": "1636110831878", + "name": "Just 1 module", + "x": 719, + "y": 169, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1660058310860", + "component": "virtualwireout", + "tab": "1636110831878", + "name": "send-to-services", + "x": 590.5, + "y": 633.4166870117188, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "send-to-services", + "color": "gray" + }, + "color": "#303E4D", + "notes": "", + "options": { + "wirename": "send-to-services" + } + }, + { + "id": "1667235009598", + "component": "timesetter", + "tab": "1667235000217", + "name": "Timesetter", + "x": 245.0833282470703, + "y": 166, + "connections": { + "0": [ + { + "index": "0", + "id": "1667235037440" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#656D78", + "notes": "", + "options": {} + }, + { + "id": "1667235012156", + "component": "timer", + "tab": "1667235000217", + "name": "Timer", + "x": 38.08332824707031, + "y": 249, + "connections": { + "0": [ + { + "index": "0", + "id": "1667235009598" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#F6BB42", + "notes": "", + "options": { + "interval": 86400000 + } + }, + { + "id": "1667235031667", + "component": "trigger", + "tab": "1667235000217", + "name": "Trigger", + "x": 41.08332824707031, + "y": 168, + "connections": { + "0": [ + { + "index": "0", + "id": "1667235009598" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#F6BB42", + "notes": "", + "options": {} + }, + { + "id": "1667235037440", + "component": "debug", + "tab": "1667235000217", + "name": "Debug", + "x": 441.0833282470703, + "y": 162, + "connections": {}, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1669850052514", + "component": "debug", + "tab": "1636110831878", + "name": "Http req data", + "x": 372.0833282470703, + "y": 422, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + }, + { + "id": "1694077414274", + "component": "httprequest", + "tab": "1636110831878", + "name": "grafia-prod01.worksys.io/rerouting74", + "x": 224, + "y": 584, + "connections": { + "0": [ + { + "index": "0", + "id": "1694077451207" + } + ] + }, + "disabledio": { + "input": [], + "output": [] + }, + "state": { + "text": "", + "color": "gray" + }, + "color": "#5D9CEC", + "notes": "", + "options": { + "type": "json", + "url": "http://192.168.252.2:8005/rerouting74", + "method": "POST", + "stringify": "json" + } + }, + { + "id": "1694077451207", + "component": "debug", + "tab": "1636110831878", + "name": "rerouting", + "x": 591.0833282470703, + "y": 529, + "connections": {}, + "disabledio": { + "input": [ + 0 + ], + "output": [] + }, + "state": { + "text": "Enabled", + "color": "gray" + }, + "color": "#967ADC", + "notes": "", + "options": { + "type": "data", + "repository": false, + "enabled": true + } + } + ], + "version": 624 +} \ No newline at end of file diff --git a/flow/energomonitor_socomec.js b/flow/energomonitor_socomec.js new file mode 100644 index 0000000..d47d995 --- /dev/null +++ b/flow/energomonitor_socomec.js @@ -0,0 +1,455 @@ +exports.id = 'socomec'; +exports.title = 'Energomonitor socomec'; +exports.version = '1.0.3'; +exports.group = 'Worksys'; +exports.color = '#2134B0'; +exports.output = ["red", "white", "yellow"]; +exports.click = false; +exports.author = 'Rastislav Kovac'; +exports.icon = 'bolt'; +exports.readme = `Modbus requests to energomonitor socomec suitcase`; + +const streamBuilder = require("./helper/energo_streambuilder"); + +const structure = { + "192.168.1.11": { + 51: "i-35", // nový merák I-35, bez zóny + 16: "i-35", // zmena adresy z 1 na 16 - 13.5.2022 + "6A": "i-60A", + "6B": "i-60B", + "7A": "i-60A", + "7B": "i-60B", + "8A": "i-60A", + "8B": "i-60B", + 9: "i-30", + "10A": "i-60A", + "10B": "i-60B", + "11A": "i-60A", + "11B": "i-60B", + "12A": "i-60A", + "12B": "i-60B", + "13A": "i-60A", + "13B": "i-60B", + "14A": "i-60A", + "14B": "i-60B", + "15A": "i-60A", + "15B": "i-60B", + 5: "i-30", + "2A": "i-60A", + "2B": "i-60B", + "3A": "i-60A", + "3B": "i-60B", + "4A": "i-60A", + "4B": "i-60B", +//novo nainstalovane 28.10.2024 + "21A": "i-60A", + "21B": "i-60B" + + // "36B": "i-60B" // vymyslene kvoli chybe + }, + "192.168.1.12": { + 18: "i-35", + "2A": "i-60A", + "2B": "i-60B", + "3A": "i-60A", + "3B": "i-60B", + "4A": "i-60A", + "4B": "i-60B", + "5A": "i-60A", + "5B": "i-60B", + "6A": "i-60A", + "6B": "i-60B", + "7A": "i-60A", + "7B": "i-60B", + "8A": "i-60A", + "8B": "i-60B", + "9A": "i-60A", + "9B": "i-60B", + "10A": "i-60A", + "10B": "i-60B", + "11A": "i-60A", + "11B": "i-60B", + "12A": "i-60A", + "12B": "i-60B", + "13A": "i-60A", + "13B": "i-60B", + "14A": "i-60A", + "14B": "i-60B", + "15A": "i-60A", + "15B": "i-60B", + "16A": "i-60A", + "16B": "i-60B", + "17A": "i-60A", + "17B": "i-60B" + } +}; + + + + +exports.install = function(instance) { + + const modbus = require('jsmodbus'); + const net = require('net'); + + require('events').EventEmitter.defaultMaxListeners = 20; + + + const allModulesEnergy = {unipi_74: ""}; // key to identify source data + const date = new Date(); + let hour = date.getHours(); + + let sendAllModulesEnergy = setInterval(() => { + const d = new Date(); + const h = d.getHours(); + if(h != hour) + { + instance.send(2, allModulesEnergy); + hour = h; + } + }, 180000); + + + instance.on('close', function(){ + clearInterval(sendAllModulesEnergy); + }); + + + const conversionTable = streamBuilder.makeStreamsTable(structure); + + // const conversionTable = + // { + // "192.168.1.11": { + // "streams": [ + // { + // "unitId": 1, + // "section": "", + // "name": 18488, + // "tb_value":"phase_1_power", + // "bytes": 2, + // "multiplier":1, + // }, + // ] + // } + // } + + + const tbNames = { + "192.168.1.11": { + "51": "1JMYvnx2RzKEo4aWQ7D93vAL8yZV3m9NBePXbrdj", // nový merák I-35, bez zóny + "16": "6lQGaY9RDywdVzObj0PZer7Pg4NBn3exEK51LWZq", // zmena adresy z 1 na 16, 13.5.2022 + "6A": "JzwxZXOvDj1bVrN4nkWeR8A8qdyBl3MRKLpGPgaQ", + "6B": "g9OxBZ5KRwNznlY6pApbGmkWXvjdEL4eGQobMDy2", + "7A": "OzNMgZ9n43qPbjXmy7zOgGA2DKdYvW5e6pxGRrVa", + "7B": "JX1ObgmqGZ54DMyYL7aJzJAEVdve38WKRzwjNrQ9", + "8A": "RvmwNz8QPblKp41GD7l4WK7JrLVYoBO92dMegn6W", + "8B": "RO8rjaBDy21qPQJzW7oKpD7pK3xmNleVZg9Ed4Gw", + "9": "3JjOWdylwgNLzxVab7NPjw0Z2vG64rq8PEB5QmDo", + "10A": "Z5KyJe9nEg1QNbWlX0wmnM7oDjBLdqzR83VGv624", + "10B": "1JMYvnx2RzKEo4aWQ7D9xzAL8yZV3m9NBePXbrdj", + "11A": "PjLblDgRBO6WQqnxmkJwga7Jv3ewZN4p5a89yKdY", + "11B": "dz4ojlpP85JMgDLZWkQ12jkaKYqQexEr62GXRV1y", + "12A": "d5xjWYMwEJon6rLlK7yl3wkqgV4DaOeNB9ZX3Gzb", + "12B": "gRoJEyXVx4qD9er287LwbOkwBzGldaPjLWQKm3Mv", + "13A": "K94XLav1glVRnyQ6r01V3Wkme3YJwBxM5oOzdP2j", + "13B": "d9x2V5LGYBzXp4mMRAOPr10PloaqJwnQj6DgrNe3", + "14A": "B5EoxeMVp4zwr8nqW0Gen57RjvD1PNamOGbLg63Z", + "14B": "aw4eELG2DlPMdn1JW0Bz4Z0qQXOZRN3xB5yp8VKr", + "15A": "ZmRwd93QL4gaezxEbAx1Xw01prn2XjlPvGyqJ6BO", + "15B": "eod9aRWLVl34Gx1Dn7VYmDk2rz6qjgmpEXwQJN5Z", + "5": "3a5oqJN1bgnx4Ol9dk8BdqAByE6jQ8mKDWMpGrLV", + "2A": "EjgWGnXaLy9opPOz20ngWQk86BlYM3w1deVQvbKr", + "2B": "wvKJdZML6mXP4DzWBAXOK87jxNloa5g23Ve9Y1ry", + "3A": "Nzp2OoJlqn6r1ZgvdA3RqE7abBwP5G4eE3RQmyxD", + "3B": "PLBJzmK1r3Gynd6OW0g2WzAe5wV4vx9bDEqNgYR8", + "4A": "52dD6ZlV1QaOpRBmbAqvWb0KnGzWMLj4eJq38Pgo", + "4B": "rDbQ84xzwgdqEoPm3kbPWWA9anOZY1RXyBv2LVM6", + "21A": "1558", + "21B": "1559" + // "36B": "vymyslene kvoli chybe" + }, + "192.168.1.12": { + "18": "m6EYyZoJ4gWexdjVPARapL7RDOq9wv2N5XzKGplr", + "2A": "E6Kg9oDnLWyzPRMva7vW8yAJxp4VG58qO2w1lZYe", + "2B": "roKgWqY95V3mXMRzyAjrW6AbLjexpJPvaGDBw826", + "3A": "nJL5lPMwBx23YpqRe0rqa4AdamXvWVbOrD4gNzy8", + "3B": "XMBbew5z4ELrZa2mRAdZW9k8vPN6gy3DdVYlpKjq", + "4A": "gYbDLqlyZVoRerQpB72GgvAWJnwM5z24POKa8Exj", + "4B": "zdQO8GwxDqjRgP4137Y5eo7NyKlpem2nL65rvVJY", + "5A": "5dBNwRp9graYJxZn409R28klVov1b2QLPDqGm6XK", + "5B": "JzwxZXOvDj1bVrN4nkWeZ8A8qdyBl3MRKLpGPgaQ", + "6A": "zrR51V2ajQ9ZLygPKkEPVW0YDq38xOJolENBXGnv", + "6B": "g9OxBZ5KRwNznlY6pApbymkWXvjdEL4eGQobMDy2", + "7A": "OzNMgZ9n43qPbjXmy7zOyGA2DKdYvW5e6pxGRrVa", + "7B": "JX1ObgmqGZ54DMyYL7aJZJAEVdve38WKRzwjNrQ9", + "8A": "RvmwNz8QPblKp41GD7l4yK7JrLVYoBO92dMegn6W", + "8B": "RO8rjaBDy21qPQJzW7oKyD7pK3xmNleVZg9Ed4Gw", + "9A": "3JjOWdylwgNLzxVab7NPxw0Z2vG64rq8PEB5QmDo", + "9B": "Z5KyJe9nEg1QNbWlX0wmyM7oDjBLdqzR83VGv624", + "10A": "1JMYvnx2RzKEo4aWQ7D9MzAL8yZV3m9NBePXbrdj", + "10B": "PjLblDgRBO6WQqnxmkJwea7Jv3ewZN4p5a89yKdY", + "11A": "WlVJBygjDZMeKX3vnAMWvLk8NqdmG2x1Y69LQ4P5", + "11B": "dz4ojlpP85JMgDLZWkQ1njkaKYqQexEr62GXRV1y", + "12A": "BaY3Xpy1EbKGjLq2O7m9W27rx8owgQz9P4dDJRmN", + "12B": "DbQY6zyveZRwK5drV0Zl4j7joE4XJM83N9xl2nWq", + "13A": "apKVJBwOyrP35m2lv7KEqd0YXbeWNd64En9GxRqg", + "13B": "o9vbeQlLMVg8j5dq4kedWy0NxZpEmnXzwYKO1ar2", + "14A": "gP1eOZVj3Q9lv5aDEk4MbP7rdpqW8yLm2BbKzJxM", + "14B": "2O14VBzl8aDmWdNw3A53vOkGyZ5qLJoEMpj6R9ng", + "15A": "pE5X8NQPaow6vlOZxk6Yjw0q42ezGBMyWgDVjR3L", + "15B": "d5xjWYMwEJon6rLlK7ylywkqgV4DaOeNB9ZX3Gzb", + "16A": "6lQGaY9RDywdVzObj0PZdr7Pg4NBn3exEK51LWZq", + "16B": "m6EYyZoJ4gWexdjVPARaYL7RDOq9wv2N5XzKGplr", + "17A": "gRoJEyXVx4qD9er287LwBOkwBzGldaPjLWQKm3Mv", + "17B": "K94XLav1glVRnyQ6r01VpWkme3YJwBxM5oOzdP2j" + } + }; + + const meter51 = ["total_energy","phase_1_power","phase_2_power","phase_3_power","phase_1_voltage","phase_2_voltage", + "phase_3_voltage","phase_1_react_power","phase_2_react_power","phase_3_react_power","phase_1_apparent_power", + "phase_2_apparent_power","phase_3_apparent_power","total_active_power","total_reactive_power","total_apparent_power"]; + + + class SocketWithClients { + + constructor (ip, data) { + + this.ip = ip; + this.data = data; + this.options = { + 'host': this.ip, + 'port': '502' + } + this.streams = data.streams; //pole + + this.startSocket(); + } + + + startSocket = () => { + + let obj = this; + this.index = 0; + this.clients = {}; + this.socket = new net.Socket(); + + this.socket.connect(this.options, function() { + console.log('Connected to socket server'); + }); + + this.socket.on('error', function(e) { + console.log('socket connection error', e); + if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') { + console.log(exports.title + ' Waiting 1 minute before trying to connect again'); + setTimeout(obj.startSocket, 60000); + } + }); + + this.socket.on('close', function() { + console.log('Socket connection closed ' + exports.title + ' Waiting 1 minute before trying to connect again'); + setTimeout(obj.startSocket, 60000); + }); + + // we create client for all modules (unitIds) and push them into dictionary + for( let i = 0; i < obj.data.streams.length; i++) + { + if(!this.clients.hasOwnProperty(obj.data.streams[i].unitId)) + { + this.clients[obj.data.streams[i].unitId] = new modbus.client.TCP(this.socket, obj.data.streams[i].unitId); + } + } + + this.socket.on('connect', function () { + console.log("socket connected"); + setTimeout(obj.readRegisters, 10000); + }); + + }; + + + readRegisters = () => { + + const lenghtOfStreams = this.streams.length; + + if(this.index >= lenghtOfStreams) + { + this.index = 0; + setTimeout(this.readRegisters, 300000); + return; + } + + let unitId = this.streams[this.index].unitId; + let section = this.streams[this.index].section; + + let register = this.streams[this.index].name; + let bytes = this.streams[this.index].bytes; + let date = Date.now(); + let tbval = this.streams[this.index].tb_value; + + // console.log("citam tieto hodnoty",unitId, register, tbval); + let obj = this; + + this.clients[unitId].readHoldingRegisters(register, bytes) + .then( function (resp) { + + resp = resp.response._body.valuesAsArray; + // console.log(unitId, register, tbval, resp); + obj.sendData(resp, register, date, unitId, section); + + obj.index++; + setTimeout(obj.readRegisters, 0); + + }).catch (function () { + + console.log("error pri citani z grafie", register, unitId, section, tbNames[obj.ip][unitId + section], tbval); + + //! IMPLEMENTOVAT POSIELANIE CHYB PODLA POSLEDNHO REGISTRA V MODULE === "total_power_factor" + if(tbval === "total_power_factor") + { + obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); + if(arguments["0"].err == "Offline") + { + obj.socket.emit("close"); + return; + } + } + + //! POSIELANIE NOK STATUSU - posle sa az pri poslednom registri z daneho unitu, nie pri kazdej chybnej hlaske + // if(obj.index + 1 == lenghtOfStreams) + // { + // obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); + // } + // else if(obj.streams[obj.index + 1].unitId != unitId || obj.streams[obj.index + 1].section != section) + // { + // obj.sendNokStatus(tbNames[obj.ip][unitId + section], date); + // } + + //console.error(require('util').inspect(arguments, { + // depth: null + //})) + + obj.index++; + setTimeout(obj.readRegisters, 0); + }) + }; + + + sendNokStatus = (tbName, date) => { + + let dataToTB = { + [tbName]: [ + { + "ts": date, + "values": { + "status": "NOK", + } + } + ] + }; + + instance.send(1, dataToTB); + // console.log("poslane do tb po chybe", dataToTB[tbName][0], dataToTB); + }; + + + sendData = (response, register, date, unitId, section) => { + + let l = this.streams.length; + + for (let i=0; i= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (temp_val & 0x80000000), ak vieš robiť logický súčin + { + //temp_val = temp_val - 2**31; // odstránim MSB bit, eventuálne sa dá použiť aj (temp_val & 0x7FFFFFFF), ak vieš robiť logický súčin + //temp_val = temp_val * (-1); // spravím z toho zápornú hodnotu + temp_val = temp_val - "0xFFFFFFFF" + 1; + } + } + else if (l === 1) + { + temp_val = response[0]; + + if(temp_val >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (temp_val & 0x8000), ak vieš robiť logický súčin + { + // temp_val = temp_val - 2**15; // odstránim MSB bit, eventuálne sa dá použiť aj (temp_val & 0x7FFF), ak vieš robiť logický súčin + // temp_val = temp_val * (-1); // spravím z toho zápornú hodnotu + temp_val = temp_val - "0xFFFF" + 1; + } + } + + value = temp_val; + value = value / a.multiplier; + + let tbName = tbNames[this.ip][unitId.toString() + section]; + // console.log(unitId, register, tbName, tb_value, response, a.multiplier, value, section); + // console.log(unitId, register, tb_value, value); + + if(tbName == undefined) return; + + // we handle multimeter 51 values: - if voltage or power, we multiply with 55 + if(tbName == '1JMYvnx2RzKEo4aWQ7D93vAL8yZV3m9NBePXbrdj' && meter51.includes(tb_value)) value = parseFloat((value * 55).toFixed(2)); + + const values = { + "status": "OK", + [tb_value]: value + }; + + + // we send "energy_last_month" value, that is equal to "total_energy" value, on first day and first minute of new month ==> it means when month has changed + if(tb_value == "total_energy") + { + const previousEnergy = a.previousEnergy; + a.previousEnergy = value; + + if(previousEnergy != null) + { + values["energy_update"] = value - previousEnergy; + } + + const d = new Date(date); + const currentMonth = d.getMonth(); + const month = a.month; + + if(month != currentMonth) + { + values["energy_last_month"] = value; + a.month = currentMonth; + } + + allModulesEnergy[this.ip + '@' + unitId + section] = value; + } + + + let dataToTB = { + [tbName]: [ + { + "ts": date, + "values": values + } + ] + }; + + instance.send(1, dataToTB); + break; + } + } + }; + } + + const newSocket = new SocketWithClients("192.168.1.11", conversionTable["192.168.1.11"]); + const newSocket2 = new SocketWithClients("192.168.1.12", conversionTable["192.168.1.12"]); + +} + diff --git a/flow/helper/db_helper.js b/flow/helper/db_helper.js new file mode 100644 index 0000000..40e796c --- /dev/null +++ b/flow/helper/db_helper.js @@ -0,0 +1,44 @@ +function promisifyBuilder(builder) +{ + return new Promise((resolve, reject) => { + + try{ + + builder.callback(function(err, response) { + + if(err != null) reject(err); + resolve(response); + }); + + } catch (error) { + reject(error); + } + }) +} + +function makeMapFromDbResult(response, ...keys) +{ + let s = "-"; + let data = {}; + + for(let i = 0; i < response.length; i++) + { + let record = response[i]; + + let k = []; + for(let j = 0; j < keys.length; j++) + { + k.push( record[keys[j]] ); + } + + let key = k.join(s); + data[ key ] = record; + } + + return data; +} + +module.exports = { + promisifyBuilder, + makeMapFromDbResult + } \ No newline at end of file diff --git a/flow/helper/energo_streambuilder.js b/flow/helper/energo_streambuilder.js new file mode 100644 index 0000000..2136ab5 --- /dev/null +++ b/flow/helper/energo_streambuilder.js @@ -0,0 +1,207 @@ +// const structure = { +// "192.168.1.11": { +// 1: "i-35", +// "6A": "i-60A", +// "6B": "i-60B", +// 7: "i-60B" +// }, +// "192.168.1.12": { +// 18: "i-35" +// }, +// }; + +/** +* @param {object} structure - main keys are IPs of installed modules. +* @param {object} structure.ip - configuration of installed modules +*/ +const makeStreamsTable = (structure) => { + + const moduleConfigStructure = { + "i-35": { + 18488: "phase_1_power", + 18490: "phase_2_power", + 18492: "phase_3_power", + 19843: "total_energy", + 18444: "phase_1_voltage", + 18446: "phase_2_voltage", + 18448: "phase_3_voltage", + 18458: "phase_1_current", + 18460: "phase_2_current", + 18462: "phase_3_current", + 18464: "neutral_wire_current", + 18494: "phase_1_react_power", + 18496: "phase_2_react_power", + 18498: "phase_3_react_power", + 18512: "phase_1_apparent_power", + 18514: "phase_2_apparent_power", + 18516: "phase_3_apparent_power", + 18518: "phase_1_pf", + 18519: "phase_2_pf", + 18520: "phase_3_pf", + 18476: "total_active_power", + 18478: "total_reactive_power", + 18484: "total_apparent_power", + 18486: "total_power_factor", + }, + "i-30": { + 19841: "total_energy", + 18444: "phase_1_voltage", + 18446: "phase_2_voltage", + 18448: "phase_3_voltage", + 18458: "phase_1_current", + 18460: "phase_2_current", + 18462: "phase_3_current", + 18464: "neutral_wire_current", + 18476: "total_active_power", + 18478: "total_reactive_power", + 18484: "total_apparent_power", + 18486: "total_power_factor", + }, + "i-60A": { + 19841: "total_energy", + 18444: "phase_1_voltage", + 18446: "phase_2_voltage", + 18448: "phase_3_voltage", + 18458: "phase_1_current", + 18460: "phase_2_current", + 18462: "phase_3_current", + 18464: "neutral_wire_current", + 18476: "total_active_power", + 18478: "total_reactive_power", + 18484: "total_apparent_power", + 18486: "total_power_factor", + }, + "i-60B": { + 21889: "total_energy", + 20492: "phase_1_voltage", + 20494: "phase_2_voltage", + 20496: "phase_3_voltage", + 20506: "phase_1_current", + 20508: "phase_2_current", + 20510: "phase_3_current", + 20512: "neutral_wire_current", + 20524: "total_active_power", + 20526: "total_reactive_power", + 20532: "total_apparent_power", + 20534: "total_power_factor", + }, + }; + + const bytes = { + "phase_1_power": 2, + "phase_2_power": 2, + "phase_3_power": 2, + "total_energy": 2, + "phase_1_voltage": 2, + "phase_2_voltage": 2, + "phase_3_voltage": 2, + "phase_1_current": 2, + "phase_2_current": 2, + "phase_3_current": 2, + "neutral_wire_current": 2, + "phase_1_react_power": 2, + "phase_2_react_power": 2, + "phase_3_react_power": 2, + "phase_1_apparent_power": 2, + "phase_2_apparent_power": 2, + "phase_3_apparent_power": 2, + "phase_1_pf": 1, + "phase_2_pf": 1, + "phase_3_pf": 1, + "total_active_power": 2, + "total_reactive_power": 2, + "total_apparent_power": 2, + "total_power_factor": 1 + } + + const multiplier = { + "phase_1_power": 1, + "phase_2_power": 1, + "phase_3_power": 1, + "total_energy": 1, + "phase_1_voltage": 100, + "phase_2_voltage": 100, + "phase_3_voltage": 100, + "phase_1_current": 1000, + "phase_2_current": 1000, + "phase_3_current": 1000, + "neutral_wire_current": 1000, + "phase_1_react_power": 1, + "phase_2_react_power": 1, + "phase_3_react_power": 1, + "phase_1_apparent_power": 1, + "phase_2_apparent_power": 1, + "phase_3_apparent_power": 1, + "phase_1_pf": 1000, + "phase_2_pf": 1000, + "phase_3_pf": 1000, + "total_active_power": 1, + "total_reactive_power": 1, + "total_apparent_power": 1, + "total_power_factor": 1000 + } + + let result = []; + const conversionTable = {}; + + Object.keys(structure).map( ip => { + + Object.keys(structure[ip]).map( item => { + + const modul = structure[ip][item]; + // console.log(modul) //i-35, i-60A ... + + let section = ""; + if(modul == "i-60A") + { + section = "A"; + } + else if(modul == "i-60B") + { + section = "B"; + } + + const allRegisters = Object.keys(moduleConfigStructure[modul]); + allRegisters.map( i => { + + const tb_value = moduleConfigStructure[modul][i]; + const b = bytes[tb_value]; + const m = multiplier[tb_value]; + + if(isNaN(item)) item = item.slice(0,-1); + + const stream = { + "unitId": parseInt(item), + "section": section, + "name": parseInt(i), + "tb_value": tb_value, + "bytes": b, + "multiplier": m, + } + + if(tb_value == "total_energy") + { + stream.month = getCurrentMonth(); + stream.previousEnergy = null; + } + + result.push(stream); + + }) + }) + + conversionTable[ip] = { streams: result }; + // console.log(conversionTable[ip]) + result = []; + + }) + + return conversionTable; +}; + +const getCurrentMonth = () => { + const date = new Date(); + return date.getMonth(); +}; + +exports.makeStreamsTable = makeStreamsTable; \ No newline at end of file diff --git a/flow/httprequest.js b/flow/httprequest.js new file mode 100644 index 0000000..f5db5cd --- /dev/null +++ b/flow/httprequest.js @@ -0,0 +1,238 @@ +exports.id = 'httprequest'; +exports.title = 'HTTP Request'; +exports.group = 'HTTP'; +exports.color = '#5D9CEC'; +exports.input = true; +exports.version = '2.0.6'; +exports.output = 1; +exports.author = 'Peter Širka'; +exports.icon = 'cloud-upload'; + +exports.html = `
+
@(URL address)
+
+
+
@(HTTP method)
+
+
+
@(Serialization)
+
+
+
@(Download the content in chunks)
+
@(Keep persistent cookies)
+
@(Disable DNS cache)
+
@(Keep alive connection)
+
@(Keep message instance)
+
+
+
+
@(Custom headers)
+
@(Cookies)
+
+
+
+ +
+
+
+
@(User)
+
+
+
@(Password)
+
+
+
+
+
`; + +exports.readme = `# Request + +This component creates a request with received data. + +__Response:__ +\`\`\`javascript +{ + data: String, + headers: Object, + status: Number, + host: String +} +\`\`\` + +__Dynamic arguments__: +Are performed via FlowData repository and can be used for URL address or for custom headers/cookies/auth. Use \`repository\` component for creating of dynamic arguments. Dynamic values are replaced in the form \`{key}\`: + +- url address e.g. \`https://.../{key}/\` +- headers values e.g. \`{token}\` +- cookies values e.g. \`{token}\``; + +exports.install = function(instance) { + + var can = false; + var flags = null; + var cookies2 = null; + + instance.on('data', function(response) { + can && instance.custom.send(response); + }); + + instance.custom.send = function(flowdata) { + + var options = instance.options; + var headers = null; + var cookies = null; + + if (options.headers) { + headers = {}; + for (var key in options.headers) + headers[key] = flowdata.arg(options.headers[key]); + } + + if (options.username && options.userpassword) { + !headers && (headers = {}); + headers.Authorization = 'Basic ' + U.createBuffer(flowdata.arg(options.username + ':' + options.userpassword)).toString('base64'); + } + + if (options.cookies) { + for (var key in options.cookies) { + !cookies && (cookies = {}); + cookies[key] = flowdata.arg(options.cookies[key]); + } + } + + if (F.is4) { + + var opt = {}; + + opt.method = options.method; + opt.url = options.url; + opt.headers = headers; + opt.cookies = cookies; + + if (options.keepalive) + opt.keepalive = true; + + opt.dnscache = options.nodns ? false : true; + + if (options.chunks) { + opt.custom = true; + opt.callback = function(err, response) { + if (err) + instance.error(err); + else if (response && response.stream) { + response.stream.on('data', function(chunks) { + if (options.keepmessage) { + flowdata.data = chunks; + instance.send2(flowdata); + } else + instance.send2(chunks); + }); + } + }; + } else { + opt.callback = function(err, response) { + if (response && !err) { + var msg = { data: response.body, status: response.status, headers: response.headers, host: response.host, cookies: response.cookies }; + if (options.keepmessage) { + flowdata.data = msg; + instance.send2(flowdata); + } else + instance.send2(msg); + } else if (err) + instance.error(err, response); + }; + } + + switch (options.stringify) { + case 'json': + opt.body = JSON.stringify(flowdata.data); + opt.type = 'json'; + break; + case 'raw': + opt.body = flowdata.data instanceof Buffer ? flowdata.data : Buffer.from(flowdata.data); + opt.type = 'raw'; + break; + case 'encoded': + if (opt.method === 'GET' || opt.method === 'HEAD') { + opt.query = U.toURLEncode(flowdata.data); + } else { + opt.body = U.toURLEncode(flowdata.data); + opt.type = 'urlencoded'; + } + break; + } + + REQUEST(opt); + + } else { + if (options.chunks) { + U.download(flowdata.arg(options.url), flags, options.stringify === 'none' ? null : flowdata.data, function(err, response) { + response.on('data', function(chunks) { + if (options.keepmessage) { + flowdata.data = chunks; + instance.send2(flowdata); + } else + instance.send2(chunks); + }); + }, cookies || cookies2, headers); + } else { + U.request(flowdata.arg(options.url), flags, options.stringify === 'none' ? null : flowdata.data, function(err, data, status, headers, host) { + if (flowdata && !err) { + var msg = { data: data, status: status, headers: headers, host: host }; + if (options.keepmessage) { + flowdata.data = msg; + instance.send2(flowdata); + } else + instance.send2(msg); + } else if (err) + instance.error(err, flowdata); + }, cookies || cookies2, headers); + } + } + }; + + instance.reconfigure = function() { + var options = instance.options; + can = options.url && options.url && options.method && options.stringify ? true : false; + instance.status(can ? '' : 'Not configured', can ? undefined : 'red'); + + if (!can) + return; + + if (F.is4) { + + flags = {}; + flags.method = options.method.toUpperCase(); + + if (!options.nodns) + flags.resolve = true; + + flags.keepalive = options.keepalive; + + if (options.stringify && options.stringify !== 'none') + options.type = options.stringify; + + if (options.persistentcookies) { + flags.cook = true; + cookies2 = {}; + } else + cookies2 = null; + + } else { + flags = []; + flags.push(options.method.toLowerCase()); + options.stringify === 'json' && flags.push('json'); + options.stringify === 'raw' && flags.push('raw'); + options.keepalive && flags.push('keepalive'); + !options.nodns && flags.push('dnscache'); + if (options.persistentcookies) { + flags.push('cookies'); + cookies2 = {}; + } else + cookies2 = null; + } + }; + + instance.on('options', instance.reconfigure); + instance.reconfigure(); +}; \ No newline at end of file diff --git a/flow/infosender.js b/flow/infosender.js new file mode 100644 index 0000000..7d0e96a --- /dev/null +++ b/flow/infosender.js @@ -0,0 +1,121 @@ +exports.id = 'infosender'; +exports.title = 'Info sender'; +exports.version = '1.0.0'; +exports.group = 'Worksys'; +exports.color = '#2134B0'; +exports.input = 1; +exports.output = 1 +exports.click = false; +exports.author = 'oms-is'; +exports.icon = 'bolt'; +exports.options = { edge: "undefined" }; + +const { networkInterfaces } = require('os'); + +exports.html = `
+
+
+
CSV Import
+
+
+
`; + +exports.readme = `# send all data to projects.worksys.io, required to monitor status of controller(unipi)`; + +const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); +const fs = require('fs'); +var path = require('path'); + +exports.install = async function(instance) { + + let id; + let allValues = {}; + let dbSettings; + + let sendAllValuesInterval; + + let now = new Date(); + console.log(exports.title, "INSTALLED", now.toLocaleString("sk-SK")); + + const nets = networkInterfaces(); + let ipAddresses = Object.create(null); // Or just '{}', an empty object + + for (const name of Object.keys(nets)) { + for (const net of nets[name]) { + // Skip over non-IPv4 and internal (i.e. 127.0.0.1) addresses + if (net.family === 'IPv4' && !net.internal) { + if (!ipAddresses[name]) { + ipAddresses[name] = []; + } + ipAddresses[name].push(net.address); + } + } + } + + try { + let p = path.join(__dirname + "/../databases/", 'settings.table'); + if (fs.existsSync(p)) { + + dbSettings = TABLE("settings"); + let responseSettings = await promisifyBuilder(dbSettings.find()); + id = responseSettings[0]["projects_id"]; + + //console.log(exports.title, responseSettings, id); + } + } catch(err) { + console.error(err); + } + + + function sendValues() + { + if(Object.keys(allValues).length > 0) + { + if(id !== undefined) + { + delete allValues.__force__; + let dataToSend = {...allValues}; + dataToSend.id = id; + dataToSend.ipAddresses = ipAddresses; + // dataToSend.notify_date = new Date().toISOString().slice(0, 19).replace('T', ' '); + + //console.log(exports.title, "------------>sendValues", dataToSend); + + instance.send(0, dataToSend); + + allValues = {}; + } + else + { + console.log(exports.title, "unable to send data, id is undefined"); + } + + } + } + + instance.on("close", () => { + clearInterval(sendAllValuesInterval); + }) + + instance.on("data", (flowdata) => { + + allValues = { ...allValues, ...flowdata.data}; + + //console.log("DATA RECEIVED", flowdata.data); + + //__force__ + if(flowdata.data.hasOwnProperty("__force__")) + { + if(flowdata.data.__force__) + { + sendValues(); + } + } + }) + + sendAllValuesInterval = setInterval(() => { + sendValues(); + }, 60000*3); + +} + diff --git a/flow/monitorconsumption.js b/flow/monitorconsumption.js new file mode 100644 index 0000000..04a70c0 --- /dev/null +++ b/flow/monitorconsumption.js @@ -0,0 +1,156 @@ +exports.id = 'monitorconsumption'; +exports.title = 'Consumption'; +exports.version = '1.0.0'; +exports.author = 'Peter Širka'; +exports.group = 'Monitoring'; +exports.color = '#967ADC'; +exports.input = 0; +exports.output = 1; +exports.icon = 'bug'; +exports.options = { interval: 5000, enabled: true, monitorconsumption: true, monitorsize: true, monitorconnections: true, monitorfiles: true }; +exports.click = true; +exports.readme = `# Consumption monitoring + +This component measure CPU and memory consumption, open files and open connections of this application. It uses these Linux commands: \`ps\`, \`lsof\`, \`netstat\` and \`df\`. + +__Data Example__: + +\`\`\`javascript +{ + cpu: 0, // percentage + memory: 4096, // in bytes + size: 34303, // directory size in bytes + files: 34, // count of open files + connections: 343, // count of connections + uptime: '1-12:34:00' +} +\`\`\``; + +exports.html = `
+
+
+
@(Interval in milliseconds)
+
+
+
+
Monitor: Consumption + uptime
+
Monitor: Count of open files
+
Monitor: Count of open connections
+
Monitor: Directory size
+
`; + +exports.install = function(instance) { + + var current = { cpu: 0, memory: 0, files: 0, connections: 0, size: 0, uptime: '', counter: 0 }; + var tproc = null; + var Exec = require('child_process').exec; + var reg_empty = /\s{2,}/g; + var reg_appdisksize = /^[\d.,]+/; + + instance.custom.run = function() { + + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + + var arr = []; + + // Get CPU and Memory consumption + instance.options.monitorconsumption && arr.push(function(next) { + Exec('ps -p {0} -o %cpu,rss,etime'.format(process.pid), function(err, response) { + + if (err) { + instance.throw(err); + } else { + var line = response.split('\n')[1]; + line = line.trim().replace(reg_empty, ' ').split(' '); + var cpu = line[0].parseFloat(); + current.cpu = cpu.floor(1); + current.memory = line[1].parseInt2() * 1024; // kB to bytes + current.uptime = line[2]; + } + + next(); + }); + }); + + // Get count of open files + instance.options.monitorfiles && arr.push(function(next) { + Exec('lsof -a -p {0} | wc -l'.format(process.pid), function(err, response) { + if (err) + instance.throw(err); + else + current.files = response.trim().parseInt2(); + next(); + }); + }); + + // Get count of opened network connections + instance.options.monitorconnections && arr.push(function(next) { + Exec('netstat -an | grep :{0} | wc -l'.format(F.port), function(err, response) { + if (err) { + instance.throw(err); + } else { + current.connections = response.trim().parseInt2() - 1; + if (current.connections < 0) + current.connections = 0; + } + next(); + }); + }); + + // Get directory size + instance.options.monitorsize && current.counter % 5 !== 0 && arr.push(function(next) { + Exec('du -hsb ' + process.cwd(), function(err, response) { + if (err) { + instance.throw(err); + } else { + var match = response.trim().match(reg_appdisksize); + match && (current.size = match.toString().trim().parseInt2()); + } + next(); + }); + }); + + arr.async(function() { + + tproc && clearTimeout(tproc); + + if (instance.options.enabled) { + tproc = setTimeout(instance.custom.run, instance.options.interval); + instance.send2(current); + } + + instance.custom.status(); + current.counter++; + }); + }; + + instance.custom.status = function() { + if (instance.options.enabled) + instance.status('{0}% / {1}'.format(current.cpu, current.memory.filesize())); + else + instance.status('Disabled', 'red'); + }; + + instance.on('click', function() { + instance.options.enabled = !instance.options.enabled; + instance.custom.status(); + + if (instance.options.enabled) { + current.counter = 0; + instance.custom.run(); + } + + }); + + instance.on('close', function() { + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + }); + + setTimeout(instance.custom.run, 1000); +}; \ No newline at end of file diff --git a/flow/monitordisk.js b/flow/monitordisk.js new file mode 100644 index 0000000..f4fdaa0 --- /dev/null +++ b/flow/monitordisk.js @@ -0,0 +1,96 @@ +exports.id = 'monitordisk'; +exports.title = 'Disk'; +exports.version = '1.0.0'; +exports.author = 'Peter Širka'; +exports.group = 'Monitoring'; +exports.color = '#F6BB42'; +exports.output = 1; +exports.icon = 'hdd-o'; +exports.click = true; +exports.options = { interval: 8000, path: '/', enabled: true }; +exports.readme = `# Disk monitoring + +This component monitors disk \`bytes\` consumption in Linux systems. It uses \`df\` command. + +__Data Example__: + +\`\`\`javascript +{ + total: 474549649408, + used: 39125245952, + free: 411294994432 +} +\`\`\``; + +exports.html = `
+
+
+
@(Interval in milliseconds)
+
+
+
@(Path)
+
+
+
`; + +exports.install = function(instance) { + + var current = { total: 0, used: 0, free: 0, path: '', type: '', percentUsed: 0 }; + var tproc = null; + + instance.custom.run = function() { + + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + + if (!instance.options.enabled) + return; + + require('child_process').exec('df -hTB1 ' + instance.options.path, function(err, response) { + + tproc = setTimeout(instance.custom.run, instance.options.interval); + + if (err) { + instance.error(err); + return; + } + + response.parseTerminal(function(line) { + if (line[0][0] !== '/') + return; + current.total = line[2].parseInt(); + current.free = line[4].parseInt(); + current.used = line[3].parseInt(); + current.path = instance.options.path || '/'; + current.type = line[1]; + current.percentUsed = line[5]; + instance.custom.status(); + instance.send2(current); + }); + }); + }; + + instance.custom.status = function() { + if (instance.options.enabled) + instance.status(current.free.filesize() + ' / ' + current.total.filesize()); + else + instance.status('Disabled', 'red'); + }; + + instance.on('click', function() { + instance.options.enabled = !instance.options.enabled; + instance.custom.status(); + instance.options.enabled && instance.custom.run(); + }); + + instance.on('close', function() { + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + }); + + setTimeout(instance.custom.run, 1000); +}; \ No newline at end of file diff --git a/flow/monitormemory.js b/flow/monitormemory.js new file mode 100644 index 0000000..98dd34b --- /dev/null +++ b/flow/monitormemory.js @@ -0,0 +1,87 @@ +exports.id = 'monitormemory'; +exports.title = 'Memory'; +exports.version = '1.0.0'; +exports.author = 'Peter Širka'; +exports.group = 'Monitoring'; +exports.color = '#F6BB42'; +exports.output = 1; +exports.click = true; +exports.icon = 'microchip'; +exports.options = { interval: 8000, enabled: true }; +exports.readme = `# Memory monitoring + +This component monitors memory \`bytes\` consumption in Linux systems. It uses \`free\` command. + +__Data Example__: + +\`\`\`javascript +{ + total: 33558769664, + used: 1998868480, + free: 2653708288 +} +\`\`\``; + +exports.html = `
+
+
+
@(Interval in milliseconds)
+
+
+
`; + +exports.install = function(instance) { + + var current = { total: 0, used: 0, free: 0 }; + var tproc = null; + + instance.custom.run = function() { + + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + + if (!instance.options.enabled) + return; + + require('child_process').exec('free -b -t', function(err, response) { + + tproc = setTimeout(instance.custom.run, instance.options.interval); + + if (err) { + instance.error(err); + return; + } + + var memory = response.split('\n')[1].match(/\d+/g); + current.total = memory[0].parseInt(); + current.used = memory[1].parseInt() - memory[3].parseInt(); + current.free = current.total - current.used; + instance.custom.status(); + instance.send2(current); + }); + }; + + instance.custom.status = function() { + if (instance.options.enabled) + instance.status(current.free.filesize() + ' / ' + current.total.filesize()); + else + instance.status('Disabled', 'red'); + }; + + instance.on('click', function() { + instance.options.enabled = !instance.options.enabled; + instance.custom.status(); + instance.options.enabled && instance.custom.run(); + }); + + instance.on('close', function() { + if (tproc) { + clearTimeout(tproc); + tproc = null; + } + }); + + setTimeout(instance.custom.run, 1000); +}; \ No newline at end of file diff --git a/flow/timer.js b/flow/timer.js new file mode 100644 index 0000000..db9fe64 --- /dev/null +++ b/flow/timer.js @@ -0,0 +1,87 @@ +exports.id = 'timer'; +exports.title = 'Timer'; +exports.version = '1.0.1'; +exports.group = 'Time'; +exports.color = '#F6BB42'; +exports.output = 1; +exports.click = true; +exports.author = 'Peter Širka'; +exports.icon = 'clock-o'; +exports.options = { interval: 1000 }; + +exports.html = `
+
+
+
@(Interval in milliseconds)
+
+
+
+ +
+
@(Data type (String by default))
+
@(Data)
+
+
+
`; + +exports.readme = `# Timer + +Timer will trigger flow in the given interval (in milliseconds). You can optionally define a data-type of the output and the data.`; + +exports.install = function(instance) { + + var value; + var id; + + instance.on('click', () => value && instance.send2(value)); + + instance.reconfigure = function() { + var options = instance.options; + + if (!options.interval) { + instance.status('Not configured', 'red'); + return; + } + + value = null; + switch (options.datatype) { + case 'string': + value = options.data; + break; + case 'integer': + value = U.parseInt(options.data); + break; + case 'float': + value = U.parseFloat(options.data); + break; + case 'date': + var num = U.parseInt(options.data); + value = num ? new Date(num) : options.data.parseDate(); + break; + case 'object': + try { + value = (new Function('return ' + options.data))(); + } catch (e) { + instance.error(e); + } + break; + case 'boolean': + value = options.data.parseBoolean(); + break; + case 'buffer': + try { + value = F.is4 ? Buffer.from(options.data) : U.createBuffer(options.data); + } catch (e) { + instance.error(e); + } + break; + } + clearInterval(id); + options.interval && (id = setInterval(() => instance.send2(value), options.interval)); + instance.status(''); + }; + + instance.on('close', () => clearInterval(id)); + instance.on('options', instance.reconfigure); + instance.reconfigure(); +}; diff --git a/flow/timesetter.js b/flow/timesetter.js new file mode 100644 index 0000000..725f56d --- /dev/null +++ b/flow/timesetter.js @@ -0,0 +1,125 @@ +exports.id = 'timesetter'; +exports.title = 'Timesetter'; +exports.group = 'Worksys'; +exports.color = '#656D78'; +exports.input = true; +exports.output = 1; +exports.author = 'Rastislav Kovac'; +exports.icon = 'code'; +exports.version = '1.0.0'; +exports.readme = ` + This component can be installed just on controllers without direct internet access! + 'project_id' variable needs to be set for every project!! + + Timesetter sends requests once a day to service-prod01.worksys.io to get + actual date and time. It sets unipi's system timedate +`; + +//! SET project_id +const project_id = 29; +const { exec } = require('child_process'); + + +exports.install = function(instance) { + + instance.on('data', function(flowdata) { + + RESTBuilder.make(function(builder) { + + if(!builder) return; + + builder.method('GET'); + //FLOW.OMS_edge_fw_version + builder.url(`http://192.168.252.2:8004/gettime?projects_id=${project_id}`); + + builder.callback(function(err, response, output) { + + if (err) { + console.log(err); + return; + } + + instance.send(0, "RESTBuilder timedatectl response"); + const res = output.response; + + try { + + const obj = JSON.parse(res); + let d = new Date(obj.date); + + const now = new Date(); + + //offset in minutes - convertUTCDateToLocalDate + let diffInMinutes = now.getTimezoneOffset(); + //d.setMinutes( d.getMinutes() + diffInMinutes ); + + //let converted = convertUTCDateToLocalDate(d); + + console.log("---->TimezoneOffset", diffInMinutes); + + if(d instanceof Date) + { + console.log("current js date:", d, d.getHours()); + + let year = d.getFullYear(); + let month = addZeroBefore(d.getMonth() + 1); + let day = addZeroBefore(d.getDate()); + + //-2 hodiny!!!! + let hours = addZeroBefore( d.getHours() ); + let minutes = addZeroBefore(d.getMinutes() ); + let seconds = addZeroBefore(d.getSeconds()); + + let timestamp = `${year}${month}${day} ${hours}:${minutes}:${seconds}`; + let dstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + + //TODO - poslat notifikaciu a nastav hw cas + //timedatectl set-timezone "Europe/Bratislava" + //hwclock --set --date="2021-08-24 15:02:00" --localtime + + //https://www.tecmint.com/set-time-timezone-and-synchronize-time-using-timedatectl-command/ + + + //timedatectl set-time "2022-04-27 09:13:00" + { + + let year = d.getUTCFullYear(); + let month = addZeroBefore(d.getUTCMonth() + 1); + let day = addZeroBefore(d.getUTCDate()); + + let hours = addZeroBefore( d.getUTCHours() ); + let minutes = addZeroBefore(d.getUTCMinutes() ); + let seconds = addZeroBefore(d.getUTCSeconds()); + + let UTCstr = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; + + exec(`sudo timedatectl set-time "${UTCstr}"`, (err, stdout, stderr) => { + if (err || stderr) { + console.error(err); + console.log(stderr); + console.log(UTCstr); + + + } + else + { + console.log(`UTC: timedatectl set-time "${UTCstr}"`); + } + + }); + } + } + } catch(e) { + console.log(e) + } + }) + }) + }) + + function addZeroBefore(n) { + return (n < 10 ? '0' : '') + n; + } + +}; + diff --git a/flow/trigger.js b/flow/trigger.js new file mode 100644 index 0000000..ea496e2 --- /dev/null +++ b/flow/trigger.js @@ -0,0 +1,79 @@ +exports.id = 'trigger'; +exports.title = 'Trigger'; +exports.group = 'Inputs'; +exports.color = '#F6BB42'; +exports.click = true; +exports.output = 1; +exports.version = '1.1.1'; +exports.author = 'Martin Smola'; +exports.icon = 'play'; + +exports.html = `
+
@(Data type (String by default))
+
@(Data)
+
Trigger 5s after initialization.
+
@(Useful when there's a need to run certain flow when the app restarts, etc.)
+
`; + +exports.readme = `# Trigger + +- Clicking on the component starts the chain +- Settings allows to set a data-type and a value`; + +exports.install = function(instance) { + + var value; + + instance.on('click', () => instance.send2(value)); + + instance.reconfigure = function() { + var options = instance.options; + value = null; + switch (options.datatype) { + case 'integer': + value = options.data.parseInt2('error'); + value = value === 'error' ? NaN : value; + break; + case 'float': + value = options.data.parseFloat2('error'); + value = value === 'error' ? NaN : value; + break; + case 'date': + options.data = options.data.toString(); + var num = options.data.parseInt('error'); + num === 'error' && (num = options.data.parseDate('error')); + num === 'error' && (num = null); + value = num ? new Date(num).toUTCString() : num; + break; + case 'object': + try { + value = (new Function('return ' + options.data))(); + } catch (e) { + instance.error(e); + } + break; + case 'boolean': + value = options.data.parseBoolean(); + break; + case 'buffer': + try { + value = F.is4 ? Buffer.from(options.data) : U.createBuffer(options.data); + } catch (e) { + instance.error(e); + } + break; + case 'string': + default: + value = '' + (options.data || ''); + break; + } + }; + + instance.on('options', instance.reconfigure); + instance.reconfigure(); + + if (instance.options.restart) + setTimeout(function(){ + instance.send2(value); + }, 5000); +}; diff --git a/flow/variables.txt b/flow/variables.txt new file mode 100644 index 0000000..e69de29 diff --git a/flow/virtualwirein.js b/flow/virtualwirein.js new file mode 100644 index 0000000..b2b8a12 --- /dev/null +++ b/flow/virtualwirein.js @@ -0,0 +1,43 @@ +exports.id = 'virtualwirein'; +exports.title = 'Virtual wire in'; +exports.version = '1.0.0'; +exports.author = 'Martin Smola'; +exports.color = '#303E4D'; +exports.icon = 'sign-in'; +exports.input = false; +exports.output = 1; +exports.options = {}; +exports.readme = `# Virtual wire in + +When the wires between the components are mess it's time to use Virtual wire.`; + +exports.html = `
+
@(Wire name)
+
+`; + +exports.install = function(instance) { + + instance.custom.reconfigure = function(){ + if (instance.options.wirename) { + instance.status(instance.options.wirename); + } else + instance.status('Not configured', 'red'); + }; + + ON('virtualwire', function(wirename, flowdata){ + if (instance.options.wirename && instance.options.wirename === wirename) + instance.send(flowdata); + }); + + instance.on('options', instance.custom.reconfigure); + instance.custom.reconfigure(); +}; diff --git a/flow/virtualwireout.js b/flow/virtualwireout.js new file mode 100644 index 0000000..94a1e4f --- /dev/null +++ b/flow/virtualwireout.js @@ -0,0 +1,41 @@ +exports.id = 'virtualwireout'; +exports.title = 'Virtual wire out'; +exports.version = '1.0.0'; +exports.author = 'Martin Smola'; +exports.color = '#303E4D'; +exports.icon = 'sign-out'; +exports.input = true; +exports.options = {}; +exports.readme = `# Virtual wire out + +When the wires between the components are mess it's time to use Virtual wire.`; + +exports.html = `
+
@(Wire name)
+
+`; + +exports.install = function(instance) { + + instance.custom.reconfigure = function(){ + if (instance.options.wirename) { + instance.status(instance.options.wirename); + } else + instance.status('Not configured', 'red'); + }; + + instance.on('data', function(flowdata) { + EMIT('virtualwire', instance.options.wirename, flowdata); + }); + + instance.on('options', instance.custom.reconfigure); + instance.custom.reconfigure(); +}; diff --git a/flow/wsmqttpublish.js b/flow/wsmqttpublish.js new file mode 100644 index 0000000..63dfc7b --- /dev/null +++ b/flow/wsmqttpublish.js @@ -0,0 +1,549 @@ +exports.id = 'wsmqttpublish'; +exports.title = 'WS MQTT publish'; +exports.group = 'MQTT'; +exports.color = '#888600'; +exports.version = '1.0.2'; +exports.icon = 'sign-out'; +exports.input = 1; +exports.output = ["red", "white", "blue"]; +exports.author = 'Daniel Segeš'; +exports.options = { host: 'tb-stage.worksys.io', port: 1883, clientid: "", username: "" }; +exports.npm = ['mqtt']; + + +exports.html = `
+
+
+
Hostname or IP address (if not empty - setting will override db setting)
+
+
+
Port
+
+
+
+
+
@(Client id)
+
+
+
@(Username)
+
+
+
`; + + +exports.readme = ` +# WS MQTT Publish + +Version 1.0.3. + +Added: +- database collections, +- rpc response +`; + +const instanceSendTo = { + debug: 0, + rpcCall: 1, + services: 2 +} + +const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); + +//CONFIG +let useLog4js = true; +let createTelemetryBackup = true; +let saveTelemetryOnError = true;//overrides backup_on_failure +//------------------------ + +var fs = require('fs'); +let rollers; +if(createTelemetryBackup) rollers = require('streamroller'); + +const noSqlFileSizeLimit = 4194304;//use 5MB - 4194304 +let insertNoSqlCounter = 0; +let insertBackupNoSqlCounter = 0; +let processingData = false; + +let backup_on_failure = true;//== saveTelemetryOnError - create backup broker send failure +let restore_from_backup = 100; //how many rows process at once? +let restore_backup_wait = 5;//wait seconds +let lastRestoreTime = 0; + +let errLogger; +let logger; +let monitor; + +if(useLog4js) +{ + var path = require('path'); + var log4js = require("log4js"); + + log4js.configure({ + appenders: { + errLogs: { type: 'file', filename: path.join(__dirname + "/../", 'err.txt') }, + monitorLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, filename: path.join(__dirname + "/../", 'monitor.txt') }, + console: { type: 'console' } + }, + categories: { + errLogs: { appenders: ['console', 'errLogs'], level: 'error' }, + monitorLogs: { appenders: ['console', 'monitorLogs'], level: 'trace' }, + //another: { appenders: ['console'], level: 'trace' }, + default: { appenders: ['console'], level: 'trace' } + } + }); + + errLogger = log4js.getLogger("errLogs"); + logger = log4js.getLogger(); + monitor = log4js.getLogger("monitorLogs"); + + //USAGE + //logger.debug("text"); + //monitor.info('info'); + //errLogger.error("some error"); +} + +process.on('uncaughtException', function (err) { + + if(errLogger) + { + errLogger.error('uncaughtException:', err.message) + errLogger.error(err.stack); + } + + //TODO + //send to service + + //process.exit(1); +}) + +const nosql = NOSQL('tbdata'); +const nosqlBackup = NOSQL('/backup/tbdata'); + +exports.install = function(instance) { + + var broker; + var opts; + var brokerready = false; + + instance.on('options', loadSettings); + + mqtt = require('mqtt'); + + // wsmqtt status for notification purposes on projects.worksys.io database + let wsmqttName = null; + let sendWsStatusVar = null; + let wsmqtt_status = 'disconnected'; + + function getWsmqttName(host) + { + if(host == "tb-demo.worksys.io" || host == '192.168.252.4') return 'wsmqtt_demo'; + else if(host == "tb-qas01.worksys.io" || host == '192.168.252.5') return 'wsmqtt_qas01'; + else if(host == "tb-prod01.worksys.io" || host == '192.168.252.1') return 'wsmqtt_prod01'; + } + + function sendWsStatus() + { + instance.send(instanceSendTo.services, {[wsmqttName]: wsmqtt_status}); + } + + sendWsStatusVar = setInterval(sendWsStatus, 180000); + + + //set opts according to db settings + async function loadSettings() + { + + if(instance.options.host !== "") + { + //override settings from database + var o = instance.options; + opts = { + host: o.host, + port: o.port, + clientId: o.clientid, + username: o.username, + rejectUnauthorized: false, + resubscribe: false + }; + + wsmqttName = getWsmqttName(o.host); + + console.log("wsmqttpublich -> loadSettings from instance.options", instance.options); + } + else + { + + const dbSettings = TABLE("settings"); + let responseSettings = await promisifyBuilder(dbSettings.find()); + + backup_on_failure = responseSettings[0]["backup_on_failure"]; + saveTelemetryOnError = backup_on_failure; + + restore_from_backup = responseSettings[0]["restore_from_backup"]; + restore_backup_wait = responseSettings[0]["restore_backup_wait"]; + + let mqtt_host = responseSettings[0]["mqtt_host"]; + let mqtt_clientid = responseSettings[0]["mqtt_clientid"]; + let mqtt_username = responseSettings[0]["mqtt_username"]; + let mqtt_port = responseSettings[0]["mqtt_port"]; + + console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]); + + opts = { + host: mqtt_host, + port: mqtt_port, + keepalive: 10, + clientId: mqtt_clientid, + username: mqtt_username, + rejectUnauthorized: false, + resubscribe: false + }; + + wsmqttName = getWsmqttName(mqtt_host); + } + + connectToTbServer(); + + } + + function connectToTbServer() + { + var url = "mqtt://" + opts.host + ":" + opts.port; + console.log("MQTT URL: ", url); + + broker = mqtt.connect(url, opts); + + broker.on('connect', function() { + instance.status("Connected", "green"); + brokerready = true; + FLOW.OMS_brokerready = brokerready; + wsmqtt_status = 'connected'; + }); + + broker.on('reconnect', function() { + instance.status("Reconnecting", "yellow"); + brokerready = false; + + FLOW.OMS_brokerready = brokerready; + }); + + broker.on('message', function(topic, message) { + // message is type of buffer + message = message.toString(); + if (message[0] === '{') { + TRY(function() { + + message = JSON.parse(message); + if (message.hasOwnProperty("device") && message.hasOwnProperty("data") && message.data.hasOwnProperty("id")) { + broker.publish(topic, `{"device": ${message.device}, "id": ${message.data.id}, "data": {"success": true}}`, {qos:1}); + instance.send(instanceSendTo.rpcCall, {"device": message.device, "id": message.data.id, "RPC response": {"success": true}}); + } + + }, () => instance.debug('MQTT: Error parsing data', message)); + } + + instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message }); + + }); + + broker.on('close', function(err) { + brokerready = false; + FLOW.OMS_brokerready = brokerready; + wsmqtt_status = 'disconnected'; + + if (err && err.toString().indexOf('Error')) { + instance.status("Err: "+err.code, "red"); + instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts }); + } else { + instance.status("Disconnected", "red"); + instance.send(instanceSendTo.debug, {"message":"Broker CLOSE signal received !", "error":err, "opt":opts }); + } + }); + + broker.on('error', function(err) { + instance.status("Err: "+ err.code, "red"); + instance.send(instanceSendTo.debug, {"message":"Broker ERROR signal received !", "error":err, "opt":opts }); + + brokerready = false; + FLOW.OMS_brokerready = brokerready; + wsmqtt_status = 'disconnected'; + + }); + + //broker = new Broker(opts); + //MQTT_BROKERS.push(broker); + + //instance.status('Ready'); + } + + //set opts accortding to options + /* + instance.reconfigure = function() { + + + var o = instance.options; + opts = { + host: o.host, + port: o.port, + keepalive: 10, + clientId: o.clientid, + username: o.username, + rejectUnauthorized: false, + resubscribe: false + }; + + //connectToTbServer(); + }; + */ + + instance.on('data', function(data) { + + if (brokerready) + { + //do we have some data in backup file? + //if any, process data from database + if(saveTelemetryOnError) + { + //read telemetry data and send back to server + if(!processingData) processDataFromDatabase(); + } + + } + + if (brokerready) + { + let stringifiedJson = JSON.stringify(data.data); + broker.publish("v1/gateway/telemetry", stringifiedJson, {qos: 1}); + + //backup telemetry + //{"2O14VBzl8aDmWdNw3A53vOkGyZ5qLJoEMpj6R9ng":[{"ts":1658619104272,"values":{"status":"OK","total_reactive_power":-31}}]} + let key = Object.keys(data.data)[0]; + let o = data.data[key][0].values; + + if(o.hasOwnProperty('total_energy')) + { + if(createTelemetryBackup) + { + data.data.id = UID(); + nosqlBackup.insert(data.data); + + insertBackupNoSqlCounter++; + if(insertBackupNoSqlCounter > 150) + { + let options = {compress: true}; + let path = __dirname + "/../databases/backup/tbdata.nosql"; + var stream = new rollers.RollingFileStream(path, noSqlFileSizeLimit, 150, options); + stream.write(""); + stream.end(); + + insertBackupNoSqlCounter = 0; + } + } + } + + } + else + { + + if(logger) logger.debug("Broker unavailable. Data not sent !", data.data); + instance.send(instanceSendTo.debug, {"message":"Broker unavailable. Data not sent !", "data": data.data }); + + if(saveTelemetryOnError) + { + //create new file from tbdata.nosql, if file size exceeds given limit, and clear tbdata.nosql + makeBackupFromDbFile(); + + //write to tb + data.data.id = UID(); + nosql.insert(data.data); + } + + } + }); + + instance.close = function(done) { + if (brokerready){ + broker.end(); + clearInterval(sendWsStatusVar); + } + }; + + + function getDbBackupFileCounter(type) + { + var files = fs.readdirSync(__dirname + "/../databases"); + + let counter = 0; + for(var i = 0; i < files.length; i++) + { + + if(files[i] == "tbdata.nosql") continue; + + if(files[i].endsWith(".nosql")) + { + + let pos = files[i].indexOf("."); + if(pos > -1) + { + + let fileCounter = counter; + let firstDigit = files[i].slice(0, pos); + + fileCounter = parseInt(firstDigit); + if (isNaN(fileCounter)) fileCounter = 0; + //console.log("getDbBackupFileCounter digit:", files[i], firstDigit, fileCounter, isNaN(fileCounter), type); + + if(type == "max") + { + if(fileCounter > counter) + { + counter = fileCounter; + } + } + else if(type == "min") + { + if(counter == 0) counter = fileCounter; + + if(fileCounter < counter) + { + counter = fileCounter; + } + } + } + } + + } + + if(type == "max") counter++; + + return counter; + } + + const makeBackupFromDbFile = async () => { + + if(!saveTelemetryOnError) return; + + //to avoid large file: tbdata.nosql + + //init value is 0! + if(insertNoSqlCounter > 0) + { + --insertNoSqlCounter; + return; + } + + insertNoSqlCounter = 100; + + let source = __dirname + "/../databases/tbdata.nosql"; + + var stats = fs.statSync(source); + var fileSizeInBytes = stats.size; + + if(fileSizeInBytes > noSqlFileSizeLimit) + { + + let counter = 1; + counter = getDbBackupFileCounter("max"); + + let destination = __dirname + "/../databases/" + counter + "." + "tbdata.nosql"; + + //make backup file + fs.copyFileSync(source, destination); + //fs.renameSync(p, p + "." + counter); + + //clear tbdata.nosql + fs.writeFileSync(source, ""); + fs.truncateSync(source, 0); + + } + + } + + const processDataFromDatabase = async () => { + + if(restore_from_backup <= 0) + { + return; + } + + //calculate diff + const now = new Date(); + let currentTime = now.getTime(); + let diff = currentTime - lastRestoreTime; + + if( (diff / 1000) < restore_backup_wait) + { + //console.log("*********restore_backup_wait", diff, restore_backup_wait); + return; + } + + processingData = true; + + //get filename to process + let counter = getDbBackupFileCounter("min"); + + //we have some backup files + let dataBase = 'tbdata'; + + var nosql; + if(counter == 0) dataBase = 'tbdata'; + else dataBase = counter + "." + 'tbdata'; + + nosql = NOSQL(dataBase); + + //select all data - use limit restore_from_backup + let records = await promisifyBuilder(nosql.find().take(restore_from_backup)); + + for(let i = 0; i < records.length; i++) + { + if (brokerready) { + + let item = records[i]; + let id = item.id; + + if(id !== undefined) + { + //console.log("------------processDataFromDatabase - remove", id, dataBase, i); + + let o = JSON.parse(JSON.stringify(item)); + delete o.id; + let message = JSON.stringify(o); + + broker.publish("v1/gateway/telemetry", message, {qos:1}); + + //remove from database + await promisifyBuilder(nosql.remove().where("id", id)); + } + + } + else + { + processingData = false; + return; + } + } + + if(records.length > 0) + { + //clean backup file + if(counter > 0) nosql.clean(); + } + + //no data in db, remove + if(records.length == 0) + { + if(counter > 0) nosql.drop(); + } + + const d = new Date(); + lastRestoreTime = d.getTime(); + + processingData = false; + + } + + loadSettings(); + + //instance.on('options', instance.reconfigure); + //instance.reconfigure(); +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..683ee26 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "totalproject", + "description": "Empty project", + "version": "1.0.0", + "main": "index.js", + "dependencies": { + "bitwise": "^2.1.0", + "easy-crc": "^0.0.2", + "jsmodbus": "^4.0.6", + "log4js": "^6.3.0", + "mosca": "^2.8.3", + "mqtt": "^4.2.8", + "node-schedule": "^2.0.0", + "nodemailer": "^6.7.0", + "serialport": "^9.2.4", + "total.js": "^3.4.10", + "total4": "^0.0.51" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "empty", + "project" + ], + "author": "Peter Širka", + "license": "MIT" +}