Initial commit - ip 10.0.0.74

This commit is contained in:
rasta5man 2025-08-14 22:41:23 +02:00
commit e189e5334b
24 changed files with 3518 additions and 0 deletions

6
config Normal file
View file

@ -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

View file

2
databases/settings.table Normal file
View file

@ -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|...........................................

0
databases/tbdata.nosql Normal file
View file

90
flow/code.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-3">
<div data-jc="textbox" data-jc-path="outputs" data-jc-config="type:number;validation:value > 0;increment:true;maxlength:3">@(Number of outputs)</div>
<div class="help m">@(Minimum is 1)</div>
</div>
</div>
<div data-jc="codemirror" data-jc-path="code" data-jc-config="type:javascript;required:true;height:500;tabs:true;trim:true" class="m">@(Code)</div>
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
</div>
<script>
var code_outputs_count;
ON('open.code', function(component, options) {
code_outputs_count = options.outputs = options.outputs || 1;
});
ON('save.code', function(component, options) {
if (code_outputs_count !== options.outputs) {
if (flow.version < 511) {
component.connections = {};
setState(MESSAGES.apply);
}
component.output = options.outputs || 1;
}
});
</script>`;
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();
};

11
flow/comment.js Normal file
View file

@ -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() {};

100
flow/debug.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-12">
<div data-jc="dropdown" data-jc-path="type" data-jc-config="items:Message data|data,Message repository|repository,Message data + Message repository|both;required:true" class="m">@(Output type)</div>
<div data-jc="textbox" data-jc-path="property" data-jc-config="placeholder: @(e.g. address.street)" class="m">@(Path to the property (leave empty to show the whole data object))</div>
<div data-jc="textbox" data-jc-path="group" data-jc-config="placeholder: @(e.g. Temperature)" class="m">@(A group name)</div>
<div data-jc="checkbox" data-jc-path="enabled">@(Enabled)</div>
</div>
</div>
</div>`;
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);
}
};

953
flow/designer.json Normal file
View file

@ -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
}

View file

@ -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<l; i++) {
let a = this.streams[i];
if (a.name === register && a.unitId === unitId && a.section === section)
{
let tb_value = a.tb_value;
let value;
let l = response.length;
let temp_val = 0;
if (l === 2)
{
temp_val = (response[0]*(2**16) + response[1]);
if(temp_val >= (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"]);
}

44
flow/helper/db_helper.js Normal file
View file

@ -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
}

View file

@ -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;

238
flow/httprequest.js Normal file
View file

@ -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 = `<div class="padding">
<div data-jc="textbox" data-jc-path="url" class="m" data-jc-config="required:true;maxlength:500;placeholder:@(E.g. https\\://www.totaljs.com)">@(URL address)</div>
<div class="row">
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="method" data-jc-config="required:true;items:,GET,POST,PUT,DELETE">@(HTTP method)</div>
</div>
<div class="col-md-6 m">
<div data-jc="dropdown" data-jc-path="stringify" data-jc-config="required:true;items:,URL encoded|encoded,JSON|json,RAW|raw,None|none">@(Serialization)</div>
</div>
</div>
<div data-jc="checkbox" data-jc-path="chunks">@(Download the content <b>in chunks</b>)</div>
<div data-jc="checkbox" data-jc-path="persistentcookies">@(Keep persistent cookies)</div>
<div data-jc="checkbox" data-jc-path="nodns">@(Disable DNS cache)</div>
<div data-jc="checkbox" data-jc-path="keepalive">@(Keep alive connection)</div>
<div data-jc="checkbox" data-jc-path="keepmessage">@(Keep message instance)</div>
</div>
<hr class="nmt nmb" />
<div class="padding">
<div data-jc="keyvalue" data-jc-path="headers" data-jc-config="placeholderkey:@(Header name);placeholdervalue:@(Header value and press enter)" class="m">@(Custom headers)</div>
<div data-jc="keyvalue" data-jc-path="cookies" data-jc-config="placeholderkey:@(Cookie name);placeholdervalue:@(Cookie value and press enter)">@(Cookies)</div>
</div>
<div class="padding bg-smoke">
<section>
<label><i class="fa fa-lock"></i>@(HTTP basic access authentication)</label>
<div class="padding npb">
<div class="row">
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="username">@(User)</div>
</div>
<div class="col-md-6 m">
<div data-jc="textbox" data-jc-path="userpassword">@(Password)</div>
</div>
</div>
</div>
</section>
</div>`;
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();
};

121
flow/infosender.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="edge" data-jc-config="placeholder:undefined;required:true" class="m">CSV Import</div>
</div>
</div>
</div>`;
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);
}

156
flow/monitorconsumption.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
</div>
<hr />
<div data-jc="checkbox" data-jc-path="monitorconsumption">Monitor: Consumption + uptime</div>
<div data-jc="checkbox" data-jc-path="monitorfiles">Monitor: Count of open files</div>
<div data-jc="checkbox" data-jc-path="monitorconnections">Monitor: Count of open connections</div>
<div data-jc="checkbox" data-jc-path="monitorsize">Monitor: Directory size</div>
</div>`;
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);
};

96
flow/monitordisk.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data---="textbox__interval__placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
<div class="col-md-3 m">
<div data---="textbox__path__placeholder:/;required:true">@(Path)</div>
</div>
</div>
</div>`;
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);
};

87
flow/monitormemory.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:10000;increment:true;type:number;required:true;maxlength:10;align:center">@(Interval in milliseconds)</div>
</div>
</div>
</div>`;
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);
};

87
flow/timer.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-3 m">
<div data-jc="textbox" data-jc-path="interval" data-jc-config="placeholder:1000;increment:true;type:number;required:true;align:center">@(Interval in milliseconds)</div>
</div>
</div>
<section>
<label><i class="fa fa-random"></i>@(Output data)</label>
<div class="padding">
<div data-jc="dropdown" data-jc-path="datatype" data-jc-config="items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox" data-jc-path="data" data-jc-config="placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world'])">@(Data)</div>
</div>
</section>
</div>`;
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();
};

125
flow/timesetter.js Normal file
View file

@ -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;
}
};

79
flow/trigger.js Normal file
View file

@ -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 = `<div class="padding">
<div data-jc="dropdown__datatype__items:,String|string,Integer|integer,Float|float,Boolean|boolean,Date|date,Object|object,Base64 as Buffer|buffer" class="m">@(Data type (String by default))</div>
<div data-jc="textbox__data__placeholder:@(e.g. Hello world or { hello: 'world'} or ['hello', 'world']))" class="m">@(Data)</div>
<div data-jc="checkbox__restart">Trigger 5s after initialization.</div>
<div class="help">@(Useful when there's a need to run certain flow when the app restarts, etc.)</div>
</div>`;
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);
};

0
flow/variables.txt Normal file
View file

43
flow/virtualwirein.js Normal file
View file

@ -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 = `<div class="padding">
<div data-jc="textbox" data-jc-path="wirename" data-jc-config="required:true;placeholder:@(some identifier)" class="m">@(Wire name)</div>
</div>
<script>
ON('save.virtualwirein', function(component, options) {
!component.name && (component.name = options.wirename);
});
WATCH('settings.virtualwirein.wirename', function(path, value, type){
if (type === 2)
SET('settings.virtualwirein.wirename', value.slug());
});
</script>`;
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();
};

41
flow/virtualwireout.js Normal file
View file

@ -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 = `<div class="padding">
<div data-jc="textbox" data-jc-path="wirename" class="m" data-jc-config="required:true;placeholder:@(some identifier)">@(Wire name)</div>
</div>
<script>
ON('save.virtualwireout', function(component, options) {
!component.name && (component.name = options.wirename);
});
WATCH('settings.virtualwireout.wirename', function(path, value, type){
if (type === 2)
SET('settings.virtualwireout.wirename', value.slug());
});
</script>`;
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();
};

549
flow/wsmqttpublish.js Normal file
View file

@ -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 = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="host" data-jc-config="placeholder:test.mosquitto.org;required:false" class="m">Hostname or IP address (if not empty - setting will override db setting)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="port" data-jc-config="placeholder:1883;required:true" class="m">Port</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="clientid">@(Client id)</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="username" class="m">@(Username)</div>
</div>
</div>
</div>`;
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();
};

28
package.json Normal file
View file

@ -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"
}