Compare commits

..

15 commits

Author SHA1 Message Date
Jakub Klena
f7350c117d Merged in senica-rvo-fix-line-control (pull request #4)
Senica rvo fix line control
2024-09-09 11:47:59 +00:00
Jakub Klena
e3c68f3d86 Merged master into senica-rvo-fix-line-control 2024-09-09 11:47:05 +00:00
Jakub Klena
7e20c7d3f0 Removing duplicate 2024-09-09 13:44:39 +02:00
8929cc3a53 actual working Senica Rvo code 09/2024 2024-09-09 12:32:34 +02:00
Jakub Klena
464e74fe26 Merged in feat/Create-slack-connector (pull request #2)
Feat/Create slack connector

Approved-by: Rastislav Kovac
2024-09-06 13:00:04 +00:00
rasta5man
f4ffef94b9 kovalov part delete 2024-06-20 17:56:49 +02:00
Jakub Klena
1b7aae4590 removing some debug msgs 2024-05-24 08:44:15 +02:00
Jakub Klena
6d468acd6f proper last version 2024-05-24 08:42:21 +02:00
Jakub Klena
9e2ab3ccea api key fix 2024-05-24 01:24:08 +02:00
Jakub Klena
2f659a164b Slack connector component 2024-05-24 01:19:34 +02:00
rasta5man
54038a06f8 major updated line switching version 2024-05-10 2024-05-21 15:49:42 +02:00
rasta5man
d289a99d07 new switching line functionality - in progress 2024-05-14 16:29:11 +02:00
rasta5man
31fe341ef1 refactoring - no change 2024-05-14 16:17:58 +02:00
rasta5man
86c7b3a942 add buildTasks function to dido_controller and handle 7 lines with state_of_braker 2024-05-07 16:28:43 +02:00
rasta5man
ac2ccc1c61 senica add rebuild tasks at 11 on tuesday and saturday 2024-05-07 14:11:40 +02:00
15 changed files with 7230 additions and 8106 deletions

5
config
View file

@ -1,13 +1,12 @@
name : Total.js Flow name : Total.js Flow
default_timezone : Europe/Bratislava
default_timezone : Europe/Bratislava
// Packages settings // Packages settings
package#flow (Object) : { url: '/' } package#flow (Object) : { url: '/' }
table.relays : line:number|tbname:string|contactor:number|profile:string table.relays : line:number|tbname:string|contactor:number|profile:string
table.nodes : node:number|tbname:string|line:number|dimming:number|profile:string|processed:boolean|status:boolean table.nodes : node:number|tbname:string|line:number|profile:string|processed:boolean|status:boolean
table.settings : rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number table.settings : rvo_name:string|lang:string|temperature_adress:string|latitude:number|longitude:number|mqtt_host:string|mqtt_clientid:string|mqtt_username:string|mqtt_port:number|maintanace_mode:boolean|projects_id:number|controller_type:string|serial_port:string|backup_on_failure:boolean|restore_from_backup:number|restore_backup_wait:number
table.pins : pin:string|type:string|line:number table.pins : pin:string|type:string|line:number
table.notifications : key:string|weight:string|sk:string|en:string table.notifications : key:string|weight:string|sk:string|en:string

114
databases/modbus_config.js Normal file
View file

@ -0,0 +1,114 @@
const timeoutInterval = 150000;
const deviceConfig = [
{
device: "em340",
deviceAddress: 1,
stream: [
{
"tbAttribute": "Phase_1_voltage",
"register": 0,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_2_voltage",
"register": 2,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_3_voltage",
"register": 4,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_1_current",
"register": 12,
"size": 2,
"multiplier": 0.001
},
{
"tbAttribute": "Phase_2_current",
"register": 14,
"size": 2,
"multiplier": 0.001
},
{
"tbAttribute": "Phase_3_current",
"register": 16,
"size": 2,
"multiplier": 0.001
},
{
"tbAttribute": "Phase_1_power",
"register": 18,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_2_power",
"register": 20,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_3_power",
"register": 22,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "total_power",
"register": 40,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "total_energy",
"register": 52,
"size": 2,
"multiplier": 0.1
},
{
"tbAttribute": "Phase_1_pow_factor",
"register": 46,
"size": 1,
"multiplier": 0.001
},
{
"tbAttribute": "Phase_2_pow_factor",
"register": 47,
"size": 1,
"multiplier": 0.001
},
{
"tbAttribute": "Phase_3_pow_factor",
"register": 48,
"size": 1,
"multiplier": 0.001
},
{
"tbAttribute": "power_factor",
"register": 49,
"size": 1,
"multiplier": 0.001
}
]
},
{
device: "twilight_sensor",
deviceAddress: 2,
stream: [
{
"tbAttribute": "twilight_sensor",
"register": 60,
"size": 2,
"multiplier": 1
}
]
}
];
module.exports = { timeoutInterval, deviceConfig };

View file

@ -31,4 +31,7 @@ key:string|weight:string|sk:string|en:string
+|voltage_on_phase_restored|NOTICE|Napätie na fáze č. ${phase} bolo obnovené|Voltage on phase no. ${phase} has been restored|............... +|voltage_on_phase_restored|NOTICE|Napätie na fáze č. ${phase} bolo obnovené|Voltage on phase no. ${phase} has been restored|...............
+|flow_start|NOTICE|FLOW bol spustený|FLOW has been started |............... +|flow_start|NOTICE|FLOW bol spustený|FLOW has been started |...............
+|twilight_sensor_nok|ERROR|Sensor súmraku neodpovedá|Twilight sensor is not responding|............... +|twilight_sensor_nok|ERROR|Sensor súmraku neodpovedá|Twilight sensor is not responding|...............
+|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|............... +|twilight_sensor_ok|NOTICE|Sensor súmraku znovu odpovedá|Twilight sensor is responding again|...............
+|lamps_have_turned_on|NOTICE|Lampy sa zapli|Lamps have turned on|...............
+|lamps_have_turned_off|NOTICE|Lampy sa vypli|Lamps have turned off|...............
+|flow_restart|NOTICE|Restart flowu|Flow has been restarted|...............

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,163 +1,163 @@
class DataToTbHandler class DataToTbHandler
{ {
constructor(index) { constructor(index) {
this.index = index; this.index = index;
this.previousValues = {}; this.previousValues = {};
this.debug = false; this.debug = false;
this.messageCounter = 0; this.messageCounter = 0;
this.sender = ""; this.sender = "";
} }
dump() dump()
{ {
console.log("----------------------------"); console.log("----------------------------");
console.log("previousValues", this.previousValues); console.log("previousValues", this.previousValues);
console.log("----------------------------"); console.log("----------------------------");
} }
setSender(sender) setSender(sender)
{ {
this.sender = sender; this.sender = sender;
} }
isEmptyObject( obj ) { isEmptyObject( obj ) {
for ( var name in obj ) { for ( var name in obj ) {
return false; return false;
} }
return true; return true;
} }
sendToTb(dataToTb, instance) sendToTb(dataToTb, instance)
{ {
if(!FLOW.OMS_brokerready) if(!FLOW.OMS_brokerready)
{ {
return dataToTb; return dataToTb;
} }
let keys = Object.keys(dataToTb); let keys = Object.keys(dataToTb);
if(keys.length == 0) if(keys.length == 0)
{ {
if(this.debug) console.log("sendToTb received epty object", dataToTb); if(this.debug) console.log("sendToTb received epty object", dataToTb);
return; return;
} }
let tbname = keys[0]; let tbname = keys[0];
let ts; let ts;
let arrayOfValues = dataToTb[tbname]; let arrayOfValues = dataToTb[tbname];
let arrayOfValuesToSend = []; let arrayOfValuesToSend = [];
for(let i = 0; i < arrayOfValues.length; i++) for(let i = 0; i < arrayOfValues.length; i++)
{ {
ts = arrayOfValues[i].ts; ts = arrayOfValues[i].ts;
//console.log("sendToTb------------>before", arrayOfValues[i].values, tbname); //console.log("sendToTb------------>before", arrayOfValues[i].values, tbname);
let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values); let values = this.prepareValuesForTb(tbname, ts, arrayOfValues[i].values);
//console.log("sendToTb------------>after", values); //console.log("sendToTb------------>after", values);
if(!this.isEmptyObject(values)) if(!this.isEmptyObject(values))
{ {
arrayOfValuesToSend.push({ts: ts, values: values}); arrayOfValuesToSend.push({ts: ts, values: values});
} }
} }
if(arrayOfValuesToSend.length == 0) if(arrayOfValuesToSend.length == 0)
{ {
//if(this.debug) console.log("data not sent - empty array"); //if(this.debug) console.log("data not sent - empty array");
return; return;
} }
/* /*
let dataToTb = { let dataToTb = {
[tbname]: [ [tbname]: [
{ {
"ts": Date.now(), "ts": Date.now(),
"values": values "values": values
} }
] ]
} }
*/ */
this.messageCounter++; this.messageCounter++;
let dataToTbModified = { let dataToTbModified = {
[tbname]: arrayOfValuesToSend [tbname]: arrayOfValuesToSend
} }
//console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance); //console.log(this.sender + " DATA SEND TO TB ", tbname, this.messageCounter, new Date(ts), dataToTbModified[tbname][0].values, this.instance);
if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend); if(this.debug) console.log(this.sender + " DATA SEND TO TB ", this.index, tbname, arrayOfValuesToSend);
instance.send(this.index, dataToTbModified); instance.send(this.index, dataToTbModified);
} }
getDiffTimestamp(key) getDiffTimestamp(key)
{ {
let seconds = 60*60;//1h let seconds = 60*60;//1h
//seconds = 1;//for testing //seconds = 1;//for testing
//TODO set different value for given key!!! //TODO set different value for given key!!!
//if(key == "status") seconds = 2*60*60;//2h //if(key == "status") seconds = 2*60*60;//2h
let timestampDiffToRemoveKey = seconds*1000; let timestampDiffToRemoveKey = seconds*1000;
return timestampDiffToRemoveKey; return timestampDiffToRemoveKey;
} }
prepareValuesForTb(tbname, timestamp, values) prepareValuesForTb(tbname, timestamp, values)
{ {
let keys = Object.keys(values); let keys = Object.keys(values);
if(!this.previousValues.hasOwnProperty(tbname)) if(!this.previousValues.hasOwnProperty(tbname))
{ {
this.previousValues[tbname] = {}; this.previousValues[tbname] = {};
} }
//if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values); //if(this.debug) console.log("prepareValuesForTb", tbname, timestamp, values);
for(let i = 0; i < keys.length; i++) for(let i = 0; i < keys.length; i++)
{ {
let key = keys[i]; let key = keys[i];
let value = values[key]; let value = values[key];
if(!this.previousValues[tbname].hasOwnProperty(key)) if(!this.previousValues[tbname].hasOwnProperty(key))
{ {
this.previousValues[tbname][key] = {ts: timestamp, value: value}; this.previousValues[tbname][key] = {ts: timestamp, value: value};
continue; continue;
} }
if(this.previousValues[tbname][key].value === value) if(this.previousValues[tbname][key].value === value)
{ {
let diff = timestamp - this.previousValues[tbname][key].ts; let diff = timestamp - this.previousValues[tbname][key].ts;
let timestampDiffToRemoveKey = this.getDiffTimestamp(key); let timestampDiffToRemoveKey = this.getDiffTimestamp(key);
if(diff > timestampDiffToRemoveKey) if(diff > timestampDiffToRemoveKey)
{ {
this.previousValues[tbname][key].ts = Date.now(); this.previousValues[tbname][key].ts = Date.now();
//if(this.debug) console.log(this.sender + ": update ts for key", key, "diff is", diff, "messageCounter", this.messageCounter); //if(this.debug) console.log(this.sender + ": update ts for key", key, "diff is", diff, "messageCounter", this.messageCounter);
} }
else else
{ {
delete values[key]; delete values[key];
//if(this.debug) console.log(this.sender + ": delete key", key, "diff is", diff, "messageCounter", this.messageCounter, timestampDiffToRemoveKey); //if(this.debug) console.log(this.sender + ": delete key", key, "diff is", diff, "messageCounter", this.messageCounter, timestampDiffToRemoveKey);
} }
} }
else else
{ {
this.previousValues[tbname][key].value = value; this.previousValues[tbname][key].value = value;
this.previousValues[tbname][key].ts = timestamp; this.previousValues[tbname][key].ts = timestamp;
} }
} }
return values; return values;
} }
} }
module.exports = DataToTbHandler; module.exports = DataToTbHandler;

File diff suppressed because it is too large Load diff

348
flow/modbus_reader.js Normal file
View file

@ -0,0 +1,348 @@
exports.id = 'modbus_reader';
exports.title = 'Modbus reader';
exports.version = '2.0.0';
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 modbus devices (electromer, twilight sensor, thermometer.
Component keeps running arround deviceConfig array in "timeoutInterval" intervals. Array items are objects with single modbus devices.
Everything is sent to dido_controller. If requests to device fail (all registers must fail to send NOK status) , we send "NOK-'device'" status to dido_controller.
This device needs to be configured in dido_controller!!! Double check if it is. In dido_controller we calculate final status and all values with status are pushed to tb.
`;
const modbus = require('jsmodbus');
const SerialPort = require('serialport');
const { timeoutInterval, deviceConfig } = require("../databases/modbus_config");
const { sendNotification } = require('./helper/notification_reporter');
const DELAY_BETWEEN_DEVICES = 10000;
const SEND_TO = {
debug: 0,
dido_controller: 1,
tb: 2
};
//to handle NOK and OK sendNotifications s
const numberOfNotResponding = {};
let tbName = null;
let mainSocket;
exports.install = function(instance) {
class SocketWithClients {
constructor () {
this.stream = null;
this.socket = null;
this.clients = {};
this.allValues = {};
this.errors = 0;
this.index = 0;
this.timeoutInterval = 5000;
// we need to go always around for all devices. So we need index value, device address, as well as number of registers for single device
this.deviceAddress = null; // device address (1 - EM340 and 2 for twilight_sensor)
this.indexInDeviceConfig = 0; // first item in deviceConfig
this.lengthOfActualDeviceStream = null;
this.device = null;
// lampSwitchNotification helper variables
this.onNotificationSent = false;
this.offNotificationSent = false;
this.startSocket();
}
startSocket = () => {
let obj = this;
this.socket = new SerialPort("/dev/ttymxc0", {
baudRate: 9600,
})
// we create a client for every deviceAddress ( = address) in list and push them into dictionary
for( let i = 0; i < deviceConfig.length; i++)
{
this.clients[deviceConfig[i].deviceAddress] = new modbus.client.RTU(this.socket, deviceConfig[i].deviceAddress, 2000); // 2000 is timeout in register request, default is 5000, which is too long
}
this.socket.on('error', function(e) {
console.log('socket connection error', e);
if(e.code == 'ECONNREFUSED' || e.code == 'ECONNRESET') {
console.log(exports.title + ' Waiting 10 seconds before trying to connect again');
setTimeout(obj.startSocket, 10000);
}
});
this.socket.on('close', function() {
console.log('Socket connection closed ' + exports.title + ' Waiting 10 seconds before trying to connect again');
setTimeout(obj.startSocket, 10000);
});
this.socket.on('open', function () {
console.log("socket connected");
obj.getActualStreamAndDevice();
obj.timeoutInterval = timeoutInterval - DELAY_BETWEEN_DEVICES; // to make sure readout always runs in timeoutinterval we substract DELAY_BETWEEN_DEVICES
})
};
getActualStreamAndDevice = () => {
const dev = deviceConfig[this.indexInDeviceConfig];
this.index = 0;
this.errors = 0;
this.stream = dev.stream;
this.lengthOfActualDeviceStream = dev.stream.length;
this.deviceAddress = dev.deviceAddress; // 1 or 2 or any number
this.device = dev.device; //em340, twilight_sensor
if(this.indexInDeviceConfig == 0) setTimeout(this.readRegisters, this.timeoutInterval);
else setTimeout(this.readRegisters, DELAY_BETWEEN_DEVICES);
}
readRegisters = () => {
const str = this.stream[this.index];
const register = str.register;
const size = str.size;
const tbAttribute = str.tbAttribute;
let obj = this;
this.clients[this.deviceAddress].readHoldingRegisters(register, size)
.then( function (resp) {
resp = resp.response._body.valuesAsArray; //resp is array of length 1 or 2, f.e. [2360,0]
// console.log(deviceAddress, register, tbAttribute, resp);
//device is responding again after NOK status
if(numberOfNotResponding.hasOwnProperty(obj.device))
{
let message = "";
if(obj.device == "em340")
{
message = "electrometer_ok";
}
else if(obj.device == "twilight_sensor")
{
message = "twilight_sensor_ok";
}
message && sendNotification("modbus_reader: readRegisters", tbName, message, {}, "", SEND_TO.tb, instance);
delete numberOfNotResponding[obj.device];
}
obj.transformResponse(resp, register);
//obj.errors = 0;
obj.index++;
obj.readAnotherRegister();
}).catch (function () {
console.log("errors pri citani modbus registra", register, obj.indexInDeviceConfig, tbName, tbAttribute);
obj.errors++;
if(obj.errors == obj.lengthOfActualDeviceStream)
{
instance.send(SEND_TO.dido_controller, {status: "NOK-" + obj.device}); // NOK-em340, NOK-em111, NOK-twilight_sensor, NOK-thermometer
//todo - neposlalo notification, ked sme vypojili twilight a neposle to do tb, ale do dido ??
if(!numberOfNotResponding.hasOwnProperty(obj.device))
{
let message = "";
if(obj.device == "twilight_sensor")
{
message = "twilight_sensor_nok";
}
else if(obj.device == "em340")
{
message = "electrometer_nok";
}
message && sendNotification("modbus_reader: readingTimeouted", tbName, message, {}, "", SEND_TO.tb, instance);
numberOfNotResponding[obj.device] = 1;
}
obj.errors = 0;
numberOfNotResponding[obj.device] += 1;
}
console.error(require('util').inspect(arguments, {
depth: null
}))
// if reading out of device's last register returns error, we send accumulated allValues to dido_controller (if allValues are not an empty object)
if(obj.index + 1 >= obj.lengthOfActualDeviceStream)
{
if(!isObjectEmpty(obj.allValues)) instance.send(SEND_TO.dido_controller, {values: obj.allValues});
obj.allValues = {};
}
obj.index++;
obj.readAnotherRegister();
})
};
readAnotherRegister = () => {
if(this.index < this.lengthOfActualDeviceStream) setTimeout(this.readRegisters, 0);
else this.setNewStream();
}
transformResponse = (response, register) => {
for (let i = 0; i < this.lengthOfActualDeviceStream; i++) {
let a = this.stream[i];
if (a.register === register)
{
let tbAttribute = a.tbAttribute;
let multiplier = a.multiplier;
let value = this.calculateValue(response, multiplier);
// console.log(register, tbName, tbAttribute, response, a.multiplier, value);
// if(tbName == undefined) return;
if(this.index + 1 < this.lengthOfActualDeviceStream)
{
this.allValues[tbAttribute] = value;
return;
}
const values = {
...this.allValues,
[tbAttribute]: value,
};
this.checkNullVoltage(values);
this.lampSwitchNotification(values);
instance.send(SEND_TO.dido_controller, {values: values});
this.allValues = {};
break;
}
}
}
setNewStream = () =>
{
if(this.lengthOfActualDeviceStream == this.index)
{
if(this.indexInDeviceConfig + 1 == deviceConfig.length)
{
this.indexInDeviceConfig = 0;
}
else
{
this.indexInDeviceConfig += 1;
}
this.getActualStreamAndDevice();
}
}
calculateValue = (response, multiplier) =>
{
let value = 0;
let l = response.length;
if (l === 2)
{
value = (response[1]*(2**16) + response[0]);
if(value >= (2**31)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x80000000), ak vieš robiť logický súčin
{
value = value - "0xFFFFFFFF" + 1;
}
}
else if (l === 1)
{
value = response[0];
if(value >= (2**15)) // ak je MSB bit nastavený, eventuálne sa dá použiť aj (value & 0x8000), ak vieš robiť logický súčin
{
value = value - "0xFFFF" + 1;
}
}
return Math.round(value * multiplier * 10) / 10;
}
checkNullVoltage = (values) => {
if(!(values.hasOwnProperty("Phase_1_voltage") || values.hasOwnProperty("Phase_2_voltage") || values.hasOwnProperty("Phase_3_voltage"))) return;
Object.keys(values).map(singleValue => {
if (["Phase_1_voltage", "Phase_2_voltage", "Phase_3_voltage"].includes(singleValue))
{
let l = singleValue.split("_");
let phase = parseInt(l[1]);
if(FLOW.OMS_no_voltage == undefined) FLOW.OMS_no_voltage = new Set();
// console.log(values[singleValue], tbName);
if(values[singleValue] == 0)
{
FLOW.OMS_no_voltage.add(phase);
sendNotification("modbus_reader: checkNullVoltage", tbName, "no_voltage_on_phase", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase );
// console.log('no voltage')
}
else
{
FLOW.OMS_no_voltage.delete(phase);
// console.log('voltage detected')
sendNotification("modbus_reader: checkNullVoltage", tbName, "voltage_on_phase_restored", {phase: phase}, "", SEND_TO.tb, instance, "voltage" + phase);
}
}
})
}
/**
* function sends notification to slack and to tb, if EM total_power value changes more than 500. This should show, that RVO lamps has been switched on or off
*/
lampSwitchNotification = (values) => {
if(!values.hasOwnProperty("total_power")) return;
const actualTotalPower = values.total_power;
if(actualTotalPower > 600 && this.onNotificationSent == false)
{
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_on", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = true;
this.offNotificationSent = false;
}
else if(actualTotalPower <= 600 && this.offNotificationSent == false)
{
sendNotification("modbus_reader: lampSwitchNotification", tbName, "lamps_have_turned_off", {}, "", SEND_TO.tb, instance);
this.onNotificationSent = false;
this.offNotificationSent = true;
}
}
}
const isObjectEmpty = (objectName) => {
return Object.keys(objectName).length === 0 && objectName.constructor === Object;
}
setTimeout(() => {
mainSocket = new SocketWithClients();
tbName = FLOW.OMS_rvo_tbname;
// this notification is to show, that flow (unipi) has been restarted
sendNotification("modbus_reader", tbName, "flow_restart", {}, "", SEND_TO.slack, instance);
}, 25000);
}

124
flow/slack_connector.js Normal file
View file

@ -0,0 +1,124 @@
exports.id = 'slack_connector';
exports.title = 'Slack_Connector';
exports.version = '1.0.0';
exports.group = 'Worksys';
exports.color = '#888600';
exports.input = 1;
exports.output = 1;
exports.click = false;
exports.author = 'Jakub Klena';
exports.icon = 'sign-out';
exports.options = { slack_channel: "C071KN2Q8SK", api_key: "", bot_name: "Flow DEMO", bot_icon: ":totaljs:" };
// Slack channel - where to post the messages, can be name like "backend-alerts"
// Bot Name - Name of the "user" that will post these messages, it should be based on which server it is running on.
// Bot Icon - We can use any slack icon (even custom ones uploaded by us) as the "user" profile picture
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="slack_channel" data-jc-config="placeholder:name or id;required:true" class="m">Slack Channel</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="api_key" data-jc-config="placeholder:api key;required:true" class="m">API Key:</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="bot_name" data-jc-config="placeholder:Flow DEMO;required:false" class="m">Bot Name</div>
</div>
<div class="col-md-6">
<div data-jc="textbox" data-jc-path="bot_icon" data-jc-config="placeholder:\:totaljs\:;required:true" class="m">Bot Icon</div>
</div>
</div>
</div>`;
exports.readme = `Sends any string received on input to Slack Channel.`;
var log4js = require("log4js");
var path = require('path');
log4js.configure({
appenders: {
errLogs: { type: 'file', compress:true, daysToKeep: 2, maxLogSize: 1048576, backups: 1, keepFileExt: true, 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' }
}
});
const errLogger = log4js.getLogger("errLogs");
const logger = log4js.getLogger();
const monitor = log4js.getLogger("monitorLogs");
exports.install = function(instance) {
var can = false;
process.on('uncaughtException', function (err) {
errLogger.error('uncaughtException:', err.message);
errLogger.error(err.stack);
instance.error(err);
});
instance.on('data', function(data) {
if (!can) return;
let str = String(data.data); // Ensuring data get converted to string
let message = {
'channel': instance.options.slack_channel,
'username': instance.options.bot_name,
'icon_emoji': instance.options.bot_icon,
'text': str
};
let headers = {
'Content-type': `application/json`,
'Authorization': `Bearer ${instance.options.api_key}`
};
if (F.is4) {
let opt = {
'method': 'post',
'url': 'https://slack.com/api/chat.postMessage',
'headers': headers,
'body': JSON.stringify(message),
'type': 'json',
'callback': function(err, response) {
if (response && !err) {
var msg = { data: response.body, status: response.status, headers: response.headers, host: response.host, cookies: response.cookies };
instance.send2(msg);
} else if (err) {
errLogger.error('Slack post failed - err:', err, '\n - response was:', response);
instance.error(err, response);
}
}
};
REQUEST(opt);
} else {
U.request('https://slack.com/api/chat.postMessage', ['json', 'post'], JSON.stringify(message), function(err, data, status, headers, host) {
if (response && !err) {
response.data = { data: data, status: status, headers: headers, host: host };
instance.send2(response);
} else if (err) {
errLogger.error('Slack post failed - err:', err, '\n - response was:', response);
instance.error(err, response);
}
}, null, headers);
}
});
instance.reconfigure = function() {
var options = instance.options;
can = options.slack_channel && options.bot_name && options.bot_icon && options.api_key ? true : false;
instance.status(can ? '' : 'Not configured', can ? undefined : 'red');
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
}

187
flow/slack_filter.js Normal file
View file

@ -0,0 +1,187 @@
exports.id = 'slack_filter';
exports.title = 'Slack Filter';
exports.group = 'Citysys';
exports.color = '#30E193';
exports.input = 1;
exports.output = 1;
exports.author = 'Jakub Klena';
exports.icon = 'plug';
exports.version = '1.0.8';
exports.options = { 'name':'', 'types': '["emergency", "critical", "error", "alert"]', 'message_includes':'["is responding again"]', 'tag_on_include':'[{"user_id":"U072JE5JUQG", "includes":["Electrometer", "Twilight sensor"]}]', 'slack_channel':'' };
exports.html = `<div class="padding">
<div class="row">
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="name" data-jc-config="required:true">@(Name of this server)</div>
</div>
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="slack_channel" data-jc-config="required:false">@(Slack channel to receive the alerts)</div>
</div>
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="types" data-jc-config="required:false">@(Watch these types, comma separated names)</div>
</div>
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="message_includes" data-jc-config="required:false">@(Watch messages that include any of the following strings)</div>
</div>
<div class="col-md-12">
<div data-jc="textbox" data-jc-path="tag_on_include" data-jc-config="required:false">@(Tag people if message includes something)</div>
</div>
</div>
</div>`;
exports.readme = `# Slack Filter`;
exports.install = function(instance) {
var running = false;
instance["savedSlackMessages"] = [];
var timer = null;
instance.on('data', function(response) {
if (!running) return;
let value = response.data;
if (typeof value !== 'object') return;
let can = false
var k = Object.keys(value);
var interested = JSON.parse(instance.options.types);
var msg_incl = JSON.parse(instance.options.message_includes);
var tags = JSON.parse(instance.options.tag_on_include);
if (k.length <= 0) return;
if (value[k[0]].length <= 0) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0], 'values')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values'], '_event')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'type')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'source')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event']['source'], 'func')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message')) return;
if (!Object.prototype.hasOwnProperty.call(value[k[0]][0]['values']['_event'], 'message_data')) return;
let icon = ':totaljs:';
let type = value[k[0]][0]['values']['_event']['type'];
let source = value[k[0]][0]['values']['_event']['source']['func'];
let message = value[k[0]][0]['values']['_event']['message'];
let message_data = value[k[0]][0]['values']['_event']['message_data'];
let tag = '';
switch(type){
case 'debug':
icon = ':beetle:';
break;
case 'info':
icon = ':speech_balloon:';
break;
case 'notice':
icon = ':speech_balloon:';
break;
case 'warning':
icon = ':exclamation:';
break;
case 'alert':
icon = ':warning:';
break;
case 'error':
icon = ':no_entry:';
break;
case 'emergency':
icon = ':fire:';
break;
case 'critical':
icon = ':fire:';
break;
}
// Check if this message includes one of the strings we are watching for
for (const msg of msg_incl){
if (message.includes(msg)){
if (msg == 'is responding again') icon = ':large_green_circle:';
can = true;
break;
}
}
// Check if message is one of the types we are watching for
if (interested.includes(type)){
can = true;
}
if (!can) return;
// Check for each person tags based on what the message includes
for (const person of tags){
for (const msg of person.includes){
if (message.includes(msg)){
tag += '<@'+person.user_id+'> ';
break; // Break out from this person checks as they are already tagged now
}
}
}
// Now that all people are tagged add new line symbol
if (tag != '') tag += '\n';
let send_data = tag+instance.options.name+' '+type.toUpperCase()+'\n*Source*: '+source+'\n*Message*: '+message;
if (message_data) {
send_data += '\nData: '+message_data;
}
let ignore_msg = false
if (message.includes('Configuration of dimming profile to node no')){
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
if (FLOW["savedSlackMessages"][i].message == message){
ignore_msg = true;
break;
}
}
if (!ignore_msg){
FLOW["savedSlackMessages"].push({message, 'dateandtime': Date.now()});
if (timer === null){
timer = setTimeout(checkSavedMessages, 60*60000);
}
}
}
if (!ignore_msg){
instance.send2({'msg':send_data,'bot_name':instance.options.name+' '+type.toUpperCase(),'bot_icon':icon,'channel':instance.options.slack_channel});
}
});
function checkSavedMessages(){
var d = Date.now();
d = d - 86400000; // older then 24hr
var a = [];
//Remove msgs older then 24hr
for (let i = 0; i < FLOW["savedSlackMessages"].length; i++){
if (FLOW["savedSlackMessages"][i].dateandtime > d){
a.push(FLOW["savedSlackMessages"][i]);
}
}
FLOW["savedSlackMessages"] = a;
if (FLOW["savedSlackMessages"].length > 0) {
timer = setTimeout(checkSavedMessages, 60*60000);
} else {
timer = null;
}
}
instance.reconfigure = function() {
try {
if (!FLOW["savedSlackMessages"]){
FLOW["savedSlackMessages"] = [];
}
if (instance.options.name) {
instance.status('Running');
running = true;
} else {
instance.status('Please enter name', 'red');
running = false;
}
} catch (e) {
instance.error('Citysys connector: ' + e.message);
}
};
instance.on('options', instance.reconfigure);
instance.reconfigure();
};

View file

@ -1,18 +1,18 @@
exports.id = 'gettemperature'; exports.id = 'thermometer';
exports.title = 'Thermometer'; exports.title = 'Thermometer';
exports.group = 'Worksys'; exports.group = 'Worksys';
exports.color = '#5CB36D'; exports.color = '#5CB36D';
exports.version = '1.0.2'; exports.version = '1.0.3';
exports.output = ["red", "white", "blue"]; exports.output = ["red", "white", "blue"];
exports.author = 'Rastislav Kovac'; exports.author = 'Rastislav Kovac';
exports.icon = 'thermometer-three-quarters'; exports.icon = 'thermometer-three-quarters';
exports.readme = `# Getting temperature values from RVO`; exports.readme = `# Getting temperature values for RVO. In case of LM, you need device address. In case of unipi, evok sends values, in case thermometer is installed`;
const instanceSendTo = { const instanceSendTo = {
debug: 0, debug: 0,
tb: 1, tb: 1,
di_do_controller: 2 dido_controller: 2
} }
//read temperature - frequency //read temperature - frequency
@ -43,7 +43,7 @@ const monitor = log4js.getLogger("monitorLogs");
//monitor.info('info'); //monitor.info('info');
//errLogger.error("some error"); //errLogger.error("some error");
const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper.js'); const { promisifyBuilder, makeMapFromDbResult } = require('./helper/db_helper');
const dbSettings = TABLE("settings"); const dbSettings = TABLE("settings");
let temperatureAddress = ""; let temperatureAddress = "";
@ -60,7 +60,7 @@ loadSettings();
exports.install = function(instance) { exports.install = function(instance) {
const { exec } = require('child_process'); const { exec } = require('child_process');
const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter.js'); const { sendNotification, ERRWEIGHT } = require('./helper/notification_reporter');
let startRead; let startRead;
let dataToTb; let dataToTb;
@ -76,9 +76,9 @@ exports.install = function(instance) {
}) })
const start = function(){ const start = function() {
try{ try {
if(FLOW.OMS_controller_type === "unipi") if(FLOW.OMS_controller_type === "unipi")
{ {
@ -130,8 +130,8 @@ exports.install = function(instance) {
monitor.info("Thermometer is not responding", error, FLOW.OMS_brokerready); monitor.info("Thermometer is not responding", error, FLOW.OMS_brokerready);
instance.send(instanceSendTo.tb, dataToTb); // instance.send(instanceSendTo.tb, dataToTb); // poslat stav nok do tb, ak to handluje dido_controller ??
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: status}); instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"});
} }
else parseData(stdout); else parseData(stdout);
} }
@ -162,10 +162,9 @@ exports.install = function(instance) {
logger.debug("gettemperature", data); logger.debug("gettemperature", data);
if (!isNaN(data)){ if(!isNaN(data)) {
if(counter > 290)
//if ( counter > 290 )
{ {
instance.send(instanceSendTo.debug, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break"); instance.send(instanceSendTo.debug, "[Get temperature component] - temperature data are comming again from RVO after more than 1 day break");
@ -174,21 +173,22 @@ exports.install = function(instance) {
} }
logger.debug("gettemperature", data); logger.debug("gettemperature", data);
const values = {
"temperature": Number(data.toFixed(2)),
"status": "OK"
}
dataToTb = { dataToTb = {
[edgeName]: [ [edgeName]: [
{ {
"ts": Date.now(), "ts": Date.now(),
"values": { "values":values
"temperature": Number(data.toFixed(2)),
"status": "OK"
}
} }
] ]
} }
instance.send(instanceSendTo.tb, dataToTb); instance.send(instanceSendTo.tb, dataToTb);
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: "OK"}); instance.send(instanceSendTo.dido_controller, values);
counter = 0; counter = 0;
@ -204,7 +204,7 @@ exports.install = function(instance) {
sendNotification("parseData", edgeName, "thermometer_sends_invalid_data", {}, "", instanceSendTo.tb, instance, "thermometer"); sendNotification("parseData", edgeName, "thermometer_sends_invalid_data", {}, "", instanceSendTo.tb, instance, "thermometer");
instance.send(instanceSendTo.debug, "[Get temperature component] - no temperature data from RVO for more than 1 day"); instance.send(instanceSendTo.debug, "[Get temperature component] - no temperature data from RVO for more than 1 day");
instance.send(instanceSendTo.di_do_controller, {sender: "gettemperature", status: "NOK"}); instance.send(instanceSendTo.dido_controller, {status: "NOK-thermometer"});
} }
} }
@ -213,7 +213,7 @@ exports.install = function(instance) {
setTimeout(function(){ setTimeout(function(){
start(); start();
}, 3000); }, 15000);
startRead = setInterval(start, timeoutMin * 1000 * 60); startRead = setInterval(start, timeoutMin * 1000 * 60);

View file

@ -187,7 +187,7 @@ exports.install = function(instance) {
let mqtt_clientid = responseSettings[0]["mqtt_clientid"]; let mqtt_clientid = responseSettings[0]["mqtt_clientid"];
let mqtt_username = responseSettings[0]["mqtt_username"]; let mqtt_username = responseSettings[0]["mqtt_username"];
let mqtt_port = responseSettings[0]["mqtt_port"]; let mqtt_port = responseSettings[0]["mqtt_port"];
console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]); console.log("wsmqttpublich -> loadSettings from db", responseSettings[0]);
opts = { opts = {
@ -204,7 +204,7 @@ exports.install = function(instance) {
} }
connectToTbServer(); connectToTbServer();
} }
function connectToTbServer() function connectToTbServer()
{ {
@ -217,16 +217,16 @@ exports.install = function(instance) {
instance.status("Connected", "green"); instance.status("Connected", "green");
monitor.info("MQTT broker connected"); monitor.info("MQTT broker connected");
brokerready = true; brokerready = true;
FLOW.OMS_brokerready = brokerready; FLOW.OMS_brokerready = brokerready;
wsmqtt_status = 'connected'; wsmqtt_status = 'connected';
}); });
broker.on('reconnect', function() { broker.on('reconnect', function() {
instance.status("Reconnecting", "yellow"); instance.status("Reconnecting", "yellow");
brokerready = false; brokerready = false;
FLOW.OMS_brokerready = brokerready; FLOW.OMS_brokerready = brokerready;
}); });
broker.on('message', function(topic, message) { broker.on('message', function(topic, message) {
@ -245,7 +245,6 @@ exports.install = function(instance) {
} }
instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message }); instance.send(instanceSendTo.rpcCall, {"topic":topic, "content":message });
}); });
broker.on('close', function(err) { broker.on('close', function(err) {

View file

@ -6,10 +6,12 @@
"dependencies": { "dependencies": {
"bitwise": "^2.1.0", "bitwise": "^2.1.0",
"easy-crc": "0.0.2", "easy-crc": "0.0.2",
"jsmodbus": "^4.0.6",
"log4js": "^6.3.0", "log4js": "^6.3.0",
"mqtt": "^4.2.6", "mqtt": "^4.2.6",
"nodemailer": "^6.9.7",
"serialport": "^9.2.8", "serialport": "^9.2.8",
"total.js": "^3.4.5" "total.js": "^3.4.13"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"

View file

@ -1,174 +0,0 @@
{
"config":{
"isRunning":false,
"debug":true,
"timeoutTime":4000,
"msgWaitTime":17000,
"port":"/dev/ttymxc0",
"port_options":"stty -F /dev/ttymxc0 9600 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke"
},
"private":{
"errBuffer":[
],
"tbBuffer":[
],
"device_index":0,
"cmd_index":0,
"devices":[
{
"name":"Elektrometer 1",
"tb_name":"",
"type":"EM340",
"address":1,
"data":[
],
"cmd":[
],
"timeoutcount":0,
"status":"virtual"
},
{
"name":"Twilight sensor",
"tb_name":"",
"type":"twilight",
"address":2,
"data":[
],
"cmd":[
],
"timeoutcount":0,
"status":"virtual"
}
],
"cmd_tables":[
{
"type":"EM340",
"cmd":[
{
"name":"Voltage L1",
"tb_name":"Phase_1_voltage",
"register":0,
"size":2,
"multiplier":0.1
},
{
"name":"Voltage L2",
"tb_name":"Phase_2_voltage",
"register":2,
"size":2,
"multiplier":0.1
},
{
"name":"Voltage L3",
"tb_name":"Phase_3_voltage",
"register":4,
"size":2,
"multiplier":0.1
},
{
"name":"Current L1",
"tb_name":"Phase_1_current",
"register":12,
"size":2,
"multiplier":0.001
},
{
"name":"Current L2",
"tb_name":"Phase_2_current",
"register":14,
"size":2,
"multiplier":0.001
},
{
"name":"Current L3",
"tb_name":"Phase_3_current",
"register":16,
"size":2,
"multiplier":0.001
},
{
"name":"Power L1",
"tb_name":"Phase_1_power",
"register":18,
"size":2,
"multiplier":0.1
},
{
"name":"Power L2",
"tb_name":"Phase_2_power",
"register":20,
"size":2,
"multiplier":0.1
},
{
"name":"Power L3",
"tb_name":"Phase_3_power",
"register":22,
"size":2,
"multiplier":0.1
},
{
"name":"Power tot",
"tb_name":"total_power",
"register":40,
"size":2,
"multiplier":0.1
},
{
"name":"Energy in",
"tb_name":"total_energy",
"register":52,
"size":2,
"multiplier":0.1
},
{
"name":"PowF L1",
"tb_name":"Phase_1_pow_factor",
"register":46,
"size":1,
"multiplier":0.001
},
{
"name":"PowF L2",
"tb_name":"Phase_2_pow_factor",
"register":47,
"size":1,
"multiplier":0.001
},
{
"name":"PowF L3",
"tb_name":"Phase_3_pow_factor",
"register":48,
"size":1,
"multiplier":0.001
},
{
"name":"PowF",
"tb_name":"power_factor",
"register":49,
"size":1,
"multiplier":0.001
}
]
},
{
"type":"twilight",
"cmd":[
{
"name":"Twilight",
"tb_name":"twilight_sensor",
"register":60,
"size":2,
"multiplier":1
}
]
}
]
}
}