exports.id = 'di_do_controller'; exports.title = 'DI_DO_Controller'; exports.version = '1.0.0'; exports.group = 'Worksys'; exports.color = '#2134B0'; exports.input = 2; exports.output = ["red", "white", "yellow","green"]; exports.click = false; exports.icon = 'bolt'; exports.options = { edge: "undefined" }; exports.html = `
Edge TB Name
`; exports.readme = `# Sets RS232 port and all digital pins on device. Then it starts to receive data from sensors. It receives: rotary_switch_state, rotary_switch_state, door_condition, state_of_breaker, state_of_contactor, twilight_sensor `; /* we open rsPort "/dev/ttymxc0" and set digital input and output pins with "setRSPortData" Currently we are interested in pins no. 1,2,3,6,8,9,10,16 pins number 11, 12, 13 (we receive 10,11,12 in rsPortReceivedData) are "stykace" When port receives data, it must be exactly 4 bytes long. Second byte is pin, that changed its value, fourth byte is value itself. After that, we set this value to "previousValues[allPins[whichpin]]" variable */ /* RVO objekt: state_of_main_switch - sem sa bude reportovať stav hlavného ističa : 0-> off 1-> on (toto nie je na platforme, ale Rado to už do entity type doplnil) rotary_switch_state - sem by sa mal reportovať stav vstupov manual a auto podľa nasledovnej logiky: Manual = 1 a Auto = 0 -> vyreportuje Manual Manual = 0 a Auto = 0 -> vyreportuje Off Manual = 0 a Auto = 1 -> vyreportuje Automatic door_condition - tuto ide pin 6, dverový kontakt -> 1 -> vyreportuje Closed, 0 -> vyreportuje Open twilight_sensor - hodnotu, ktorú vracia ten analógový vstup (17) treba poslať sem ako float number. Zrejme tu potom pridáme nejaký koeficient prevodu na luxy zjavne nám v jsone chýba stav hlavného ističa. Musíme to potom doplniť Na každú líniu: state_of_breaker - podľa indexu ističa sa reportuje jeho stav, teda istič 1 na líniu 1: 0-> off 1-> on state_of_contactor - podľa indexu stykača sa reportuje jeho stav, teda stykač 1 na líniu 1: 0-> off 1-> on momentálne sa stav zmení len keď vo flow klikneš aby sa zmenil, ale tá zmena by sa mala ukázať aj na platforme */ const dbRelays = TABLE("relays"); const { errLogger, logger, monitor } = require('./helper/logger'); const SerialPort = require('serialport'); const WebSocket = require('ws'); //const { exec } = require('child_process'); const { runSyncExec } = require('./helper/serialport_helper'); const { bytesToInt, resizeArray } = require('./helper/utils'); const { sendNotification } = require('./helper/notification_reporter'); const bitwise = require('bitwise'); const DataToTbHandler = require('./helper/DataToTbHandler'); let tbHandler; const ErrorToServiceHandler = require('./helper/ErrorToServiceHandler'); const errorHandler = new ErrorToServiceHandler(); let ws = null; let rsPort = null; let pinsData; let relaysData; let rvoTbName; let GLOBALS; //FLOW global GLOBALS let SETTINGS; // GLOBALS.settings let controller_type; let alarmStatus = "OFF"; const SEND_TO = { debug: 0, tb: 1, cmd_manager: 2, init: 3 } exports.install = function(instance) { process.on('uncaughtException', function (err) { //TODO send to service errLogger.error('uncaughtException:', err.message) errLogger.error(err.stack); errorHandler.sendMessageToService(err.message + "\n" + err.stack, 0, "js_error"); //process.exit(1); }) let previousValues = {temperature: 0}; let rsPortReceivedData = []; let twilight_sensor_interval = 5;//minutes let twilight_sensor = []; const twilight_sensor_array = []; let twighlightError = false; monitor.info("DI_DO_Relay_Controller installed"); //key is PIN number , line: 0 = RVO /* let conversionTable = { "1": {tbname: "", type: "state_of_main_switch", "line": 0}, //state_of_main_switch pin1 "2": {tbname: "", type: "rotary_switch_state", "line": 0}, //rotary_switch_state - poloha manual = pin2 "3": {tbname: "", type: "rotary_switch_state", "line": 0}, //rotary_switch_state - poloha auto = pin3 "4": {tbname: "", type: "power_supply", "line": 0}, "5": {tbname: "", type: "battery", "line": 0}, "6": {tbname: "", type: "door_condition", "line": 0}, // door_condition = pin6, 1 -> vyreportuje Closed, 0 -> vyreportuje Open "8": {tbname: "", type: "state_of_breaker", "line": 1}, // state_of_breaker linia 1 0=off, 1=on "9": {tbname: "", type: "state_of_breaker", "line": 2}, // state_of_breaker linia 2 0=off, 1=on "10": {tbname: "", type: "state_of_breaker", "line": 3}, // state_of_breaker linia 3 0=off, 1=on "11": {tbname: "", type: "state_of_contactor", "line": 1}, // state_of_contactor linia 1 0=off, 1=on "12": {tbname: "", type: "state_of_contactor", "line": 2}, // state_of_contactor linia 2 0=off, 1=on "13": {tbname: "", type: "state_of_contactor", "line": 3}, // state_of_contactor linia 3 0=off, 1=on "16": {tbname: "", type: "twilight_sensor", "line": 0}, // twilight_sensor = pin16 }; */ //status for calculating Statecodes let deviceStatuses = {};//key is device name: temperature,.... deviceStatuses["state_of_main_switch"] = "Off";//Hlavný istič deviceStatuses["rotary_switch_state"] = "Off";//Prevádzkový mód deviceStatuses["door_condition"] = "closed";//Dverový kontakt deviceStatuses["rvo"] = {status: "OK"};//elektromer rvo deviceStatuses["temperature"] = "OK";//templomer deviceStatuses["battery"] = "OK";//Batéria deviceStatuses["power_supply"] = "OK";//Zdroj deviceStatuses["master_node"] = "OK";//MN - FLOW.OMS_masterNodeIsResponding deviceStatuses["no_voltage"] = "OK";//FLOW.OMS_no_voltage - výpadok napätia na fáze deviceStatuses["state_of_breaker"] = {};//"Off";//Istič deviceStatuses["state_of_contactor"] = {};//"Off";//Stykač function main() { GLOBALS = FLOW.GLOBALS; SETTINGS = FLOW.GLOBALS.settings; rvoTbName = SETTINGS.rvoTbName; pinsData = GLOBALS.pinsData; relaysData = GLOBALS.relaysData; statusData = GLOBALS.statusData; tbHandler = new DataToTbHandler(SEND_TO.tb) tbHandler.setSender(exports.title); controller_type = SETTINGS.controller_type //"lm" or "unipi" //logicMachine if(controller_type == "") controller_type = "lm"; deviceStatuses["temperature"] = statusData.thermometer; console.log(exports.title, "controller type: ", controller_type); if(controller_type === "lm") { handleRsPort(); } else if(controller_type === "unipi") { handleWebSocket(); } else { errLogger.debug("UNKNOWN controller_type:", controller_type); } } function initialSetting() { //force turn off relays let keys = Object.keys(pinsData); for(let i = 0; i < keys.length; i++) { let key = keys[i]; let line = pinsData[key].line; if(line != undefined) { if(relaysData[line] != undefined) { pinsData[key].tbname = relaysData[line].tbname; //relaysData[line].contactor = 0; } else { errLogger.error("CRITICAL!!! undefined relay", relaysData[line], line); //sendNotification("set port ", rvoTbName, ERRWEIGHT.CRITICAL, "local database is corrupted", "", SEND_TO.tb, instance, null ); sendNotification("set port ", rvoTbName, "local_database_is_corrupted", {}, "", SEND_TO.tb, instance ); } } if(pinsData[key].type == "state_of_contactor") { let pin = key - 1; if(controller_type === "unipi") pin = key; //this will modify database let forceTurnOff = true; turnOffLine(line, pin, forceTurnOff, "turn off on startup"); } } let values = {}; values["edge_fw_version"] = SETTINGS.edge_fw_version; values["maintenance_mode"] = SETTINGS.maintenance_mode; values["status"] = "OK"; sendTelemetry(values, rvoTbName); instance.send(SEND_TO.init, "_"); //instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "buildTasks"}); let time = 5*1000; setTimeout(function(){ instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "buildTasks"}); sendNotification("rsPort.open()", rvoTbName, "flow_start", {}, "", SEND_TO.tb, instance ); monitor.info("-->FLOW bol spustený", rvoTbName, SETTINGS.edge_fw_version); }, time); } function handleRsPort() { //TODO build according to pins!!! //! rsPort to open are the same for lm and unipi and electromer ("/dev/ttymxc0") const setRSPortData = [0xAA,6,6,6,6,6,6,0,6,6,6,1,1,1,1,0,0,10,10,10,10,10,10,0,10,10,10,0,0,0,0,0,0,5,0,0,0,15,15,15,15,15,15,0,15,15,15,0,0,0,0,0,0,30,0,0,0]; rsPort = new SerialPort("/dev/ttymxc0", { autoOpen: false }); rsPort.on('error', function(err) { logger.debug("rsPort opened error - failed", err.message); instance.send(SEND_TO.debug, err.message); errorHandler.sendMessageToService( exports.title + " rsPort opened error - failed: " + err.message); }) rsPort.on('open', async function() { await runSyncExec("stty -F /dev/ttymxc0 115200 min 1 time 5 ignbrk -brkint -icrnl -imaxbel -opost -isig -icanon -iexten -echo -echoe -echok -echoctl -echoke").then(function (status) { //set port rsPort.write(Buffer.from(setRSPortData), function(err) { monitor.info(exports.title + "--->Digital in_out has been set (runSyncExec was sucessfull)"); turnOffAlarm(); initialSetting(); }) }).catch(function (reason) { errLogger.error( exports.title + " runSyncExec - promise rejected:" + reason); errorHandler.sendMessageToService( exports.title + " runSyncExec - promise rejected:" + reason); }); }); rsPort.on('data', function (data){ rsPortReceivedData = [...rsPortReceivedData, ...data]; if (rsPortReceivedData[0] != 85) { rsPortReceivedData = []; return; } let l = rsPortReceivedData.length; if (l < 4 ) return; if (l > 4 ) { // if array length is greater than 4, we take first 4 byte and do the logic, second 4 bytes, do the logic and so on let i, j, temparray, chunk = 4; for ( i = 0, j = l; i < j; i += chunk ) { temparray = rsPortReceivedData.slice(i, i + chunk); if ( temparray.length < 4 ){ rsPortReceivedData = [...temparray]; return; } switchLogic(temparray); } rsPortReceivedData = []; return; } switchLogic(rsPortReceivedData); rsPortReceivedData = []; }); rsPort.on("close", () => { rsPort.close(); }) rsPort.open(); } function handleWebSocket() { console.log("handleWebSocket function called"); ws = new WebSocket('ws:/10.0.0.38:1234/ws') ws.onopen = function open() { instance.send(0, exports.title + " running"); turnOffAlarm(); // useTurnOffCounter = true; // turnOffCounter = relaysData.length - 1; initialSetting(); ws.send(JSON.stringify({"cmd":"all"})); // startRequests(); }; // SAMPLE DATA FROM WEBSOCKET // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_07', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_08', // debounce: 50, // counter: 0, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, ws.onmessage = function(data) { data = JSON.parse(data.data); // console.log(data) // data comes in array except of "temperature" ==> it comes as an object if(!Array.isArray(data)) { let value = data['value']; // temperature value comes very often. To handle it, we check if it change for more than 0.2 degrees, if yes, we send to TB if(Math.abs(previousValues["temperature"] - value) > 0.21 ) { const dataToTb = { [rvoTbName]: [ { "ts": Date.now(), "values": {temperature: value} } ] }; deviceStatuses["temperature"] = "OK"; previousValues["temperature"] = value; instance.send(SEND_TO.tb, dataToTb); } return; } data.map(item => { let value = item['value']; let pin = item["dev"] + item["circuit"]; // for example "relay1_03" or "input1_01" if (pin == undefined) return; switchLogic(pin, value); }) } ws.on('error', (err) => { instance.send(SEND_TO.debug, err.message); }) ws.onclose = function(){ // connection closed, discard old websocket and create a new one in 5s // stopRequests(); ws = null; console.log("ws is null now, reconnecting in 5 seconds"); setTimeout(handleWebSocket, 5000); } } instance.on("close", () => { if(rsPort) rsPort.close(); if(ws) ws.close(); }) function getPin(line) { //conversionTable let keys = Object.keys(pinsData); for(let i = 0; i < keys.length; i++) { let key = keys[i]; if(pinsData[key].type == "state_of_contactor" && pinsData[key].line == line) { if(rsPort) return key - 1; if(ws) return key; } } logger.debug("no pin detected"); return null; } function turnOnAlarm() { if(SETTINGS.maintenance_mode) return; alarmStatus = "ON"; if(rsPort) { let arr = [0x55]; arr.push( 13 ); arr.push( 0 ); arr.push( 1 ); rsPort.write(Buffer.from(arr), function(err) { logger.debug("sirena zapnuta"); }); } else if(ws) { let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_08", "value": 1}; ws.send(JSON.stringify(cmd)); logger.debug("sirena zapnuta"); } } function turnOffAlarm() { alarmStatus = "OFF"; if(rsPort) { let arr = [0x55]; arr.push( 13 ); arr.push( 0 ); arr.push( 0 ); rsPort.write(Buffer.from(arr), function(err) { logger.debug("sirena vypnuta"); }); } else if(ws) { let cmd = {"cmd": "set", "dev": "relay", "circuit": "1_08", "value": 0}; ws.send(JSON.stringify(cmd)); logger.debug("sirena vypnuta"); } } function reportLineStatus(line) { //Tá hodnota by mala fungovať tak že LSB bit číslo je stav ističa (1 - On, 0 - Off) a druhý bit je stav stýkača (1 - true, 0 - false). let tbname = relaysData[line].tbname; let bits = []; if(deviceStatuses["state_of_breaker"][line] == "On") { bits.push(0); } else bits.push(1); if(deviceStatuses["state_of_contactor"][line] == "On") { bits.push(0); } else bits.push(1); resizeArray(bits, 8, 0); let byte = bitwise.byte.write(bits.reverse()); //console.log("line", line, bits, byte); sendTelemetry({statecode: byte}, tbname); } function turnOnLine(line, pin, force, info) { instance.send(SEND_TO.debug, "turn on line " + line ); if(force == undefined) force = false; if(line == 0) { if(alarmStatus == "ON") turnOffAlarm(); SETTINGS.maintenance_mode = true; let values = {}; values["statecode"] = calculateStateCode(); values["power_mode"] = "maintenance"; let tbname = relaysData[line].tbname; sendTelemetry(values, tbname); monitor.info("turnOnLine (line, SETTINGS.maintenance_mode)", line, SETTINGS.maintenance_mode, info); return; } if( pin === undefined) pin = getPin(line); monitor.info("turnOnLine (line, pin, force)", line, pin, force, info); if( pin === undefined) { monitor.info("pin is undefined!", line); return; } if(!force) { if(relaysData[line].contactor == 1) { instance.send(SEND_TO.debug, "line is already on " + line ); logger.debug("turnOnLine: line is already on: ", line); return; } } // if(!rsPort.isOpen && !ws) if(!rsPort && !ws) { errLogger.error("di do controller - port or websocket is not opened"); return; } if(rsPort) { let arr = [0x55]; arr.push( pin ); arr.push( 0 ); arr.push( 1 ); rsPort.write(Buffer.from(arr), function(err) { if(err === undefined) { console.log("turnONLine zapisal do rsPortu", line, arr); switchLogic(arr); } else { monitor.info("turnOnLine WRITE error", err); } }); } else if(ws) { console.log("turnONLine pin (relay)", pin); //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 1}; ws.send(JSON.stringify(cmd)); switchLogic(pin, 1) } } function turnOffLine(line, pin, force, info) { if(force == undefined) force = false; if(line == 0) { SETTINGS.maintenance_mode = false; let values = {}; values["statecode"] = calculateStateCode(); values["power_mode"] = "Automatic"; sendTelemetry(values, rvoTbName); return; } if(pin === undefined) pin = getPin(line); monitor.info("turnOffLine (line, pin, force)", line, pin, force, info); if(pin === undefined) { errLogger.error("pin is undefined!", line); return; } if(!force) { if(relaysData[line].contactor == 0) { instance.send(SEND_TO.debug, "line is already off " + line ); logger.debug("turnOffLine: line already off:", line); return; } } // if(!rsPort.isOpen && !ws) if(!rsPort && !ws) { errLogger.error("dido controller - port or websocket is not opened"); return; } if(rsPort) { let arr = [0x55]; arr.push( pin ); arr.push( 0 ); arr.push( 0 ); rsPort.write(Buffer.from(arr), function(err) { if(err === undefined) { console.log("turnOffLine zapisal do rsPort-u", line, arr); switchLogic(arr); } else { monitor.info("turnOffLine WRITE error", err); } }); } else if(ws) { //pin = "relay1_03" or "input1_01" ... we must make just "1_01" with slice method monitor.info("turnOffLine pin (relay)", pin); let cmd = {"cmd": "set", "dev": "relay", "circuit": pin.slice(5), "value": 0}; ws.send(JSON.stringify(cmd)); switchLogic(pin, 0) } } // main opening instance.on("1", flowdata => { main(); }) // we expect array as flowdata.data instance.on("0", (flowdata) => { console.log(flowdata.data); if(flowdata.data instanceof Object) { if(flowdata.data.hasOwnProperty("sender")) { //console.log("sender", flowdata.data); if(flowdata.data.sender == "gettemperature") { deviceStatuses["temperature"] = flowdata.data.status; } else if(flowdata.data.sender == "modbus_citysys") { //elektromer rvo if(flowdata.data.tbdata.hasOwnProperty(rvoTbName)) { //rvo deviceStatuses["rvo"] = {status: flowdata.data.tbdata[rvoTbName][0]["values"]["status"], tbdata: flowdata.data.tbdata}; } else { //posli do tb - to je vyriesene na urovni modbus_citysys //instance.send(SEND_TO.tb, flowdata.data.tbdata); } } instance.send(SEND_TO.debug, flowdata.data ); return; } let obj = flowdata.data; let line = obj.line; let force = obj.force; let info = obj.info; if(obj.command == "turnOn") turnOnLine(line, undefined, force, info); else if(obj.command == "turnOff") turnOffLine(line, undefined, force, info); else if(obj.command == "turnOnAlarm") turnOnAlarm(); else if(obj.command == "turnOffAlarm") turnOffAlarm(); return; } //! ake data prichadzaju z cmd_manager.js ??? //TODO transform to websocket if (Array.isArray(flowdata.data)){ rsPort.write(Buffer.from(flowdata.data), function(err) { switchLogic(flowdata.data); instance.send(SEND_TO.debug, {"WRITE":flowdata.data} ); }); } }) function calculateStateCode() { let bytes = []; let bits = []; //Hlavný istič - state_of_main_switch if(deviceStatuses["state_of_main_switch"] == "On") bits.push(0); else if(deviceStatuses["state_of_main_switch"] == "Off") bits.push(1); //Prevádzkový mód - Manual, Off, Automatic, maintenance_mode = true/false if(!SETTINGS.maintenance_mode) { if(deviceStatuses["rotary_switch_state"] == "Manual") { bits.push(0); bits.push(1); } if(deviceStatuses["rotary_switch_state"] == "Automatic") { bits.push(0); bits.push(0); } if(deviceStatuses["rotary_switch_state"] == "Off") { bits.push(1); bits.push(0); } } else{ bits.push(1); bits.push(1); } //Dverový kontakt if(deviceStatuses["door_condition"] == "closed") { bits.push(0); } else bits.push(1); //EM if(deviceStatuses["rvo"].status == "NOK") bits.push(1); else bits.push(0); //Teplomer if(deviceStatuses["temperature"] == "NOK") bits.push(1); else bits.push(0); //Batéria if(deviceStatuses["battery"] == "NOK") bits.push(1); else bits.push(0); //Zdroj if(deviceStatuses["power_supply"] == "NOK") bits.push(1); else bits.push(0); //MN if(deviceStatuses["master_node"] == "NOK") bits.push(1); else bits.push(0); //výpadok napätia na fáze if(deviceStatuses["no_voltage"] == "NOK") bits.push(1); else bits.push(0); bits.push(0); bits.push(0); bits.push(0); bits.push(0); bits.push(0); bits.push(0); //console.log("calculateStateCode - deviceStatuses", deviceStatuses); //console.log("calculateStateCode", bits); let byte0 = bitwise.byte.write(bits.slice(0,8).reverse()); let byte1 = bitwise.byte.write(bits.slice(8).reverse()); let byte = bytesToInt([byte1, byte0]); //console.log("calculateStateCode", byte); return byte; } function checkFinalRVOStatus() { // we check if any of these pins values are 0 --> it means status RVO is "NOK" // pinIndex 6 is door_condition - if open in maintenance mode - status = OK //set RVO state let status = "OK"; if(deviceStatuses["rvo"].status == "NOK") { let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: rvo status is NOK"); if(writeToFile) errLogger.error("checkFinalRVOStatus: rvo status is NOK", deviceStatuses["rvo"].tbdata); status = "NOK"; } //ak teplomer NOK, rvo nok if(deviceStatuses["temperature"] == "NOK") { let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: temperature status is NOK"); if(writeToFile) errLogger.error("checkFinalRVOStatus: temperature status is NOK"); status = "NOK"; } if(status == "OK") { for (const pinIndex of [1, 4, 6]) { if (previousValues[pinIndex] === 0) { if (pinIndex === 6 && SETTINGS.maintenance_mode) continue; let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: value is 0"); if(writeToFile) errLogger.error("checkFinalRVOStatus: value is 0", pinsData[pinIndex].type); status = "NOK"; break; } } } // battery status. If value is 1 - battery is not ok if (previousValues[5] === 1) { let writeToFile = errorHandler.processMessage("checkFinalRVOStatus: NOK status generated by battery"); if(writeToFile) errLogger.error("checkFinalRVOStatus: NOK status generated by battery"); status = "NOK"; } //ak mame telemetriu z elektromeru, posleme if(deviceStatuses["rvo"].tbdata != undefined) { //deviceStatuses["rvo"] = {status: flowdata.data.tbdata[rvoTbName][0]["values"]["status"], tbdata: flowdata.data.tbdata}; deviceStatuses["rvo"].tbdata[rvoTbName][0]["values"]["status"] = status; instance.send(SEND_TO.tb, deviceStatuses["rvo"].tbdata); delete deviceStatuses["rvo"].tbdata; } //console.log("FLOW.OMS_masterNodeIsResponding", FLOW.OMS_masterNodeIsResponding); if(!SETTINGS.masterNodeIsResponding) { //errLogger.error("Master node is not responding"); errorHandler.sendMessageToService("Master node is not responding"); status = "NOK"; deviceStatuses["master_node"] = "NOK"; } else deviceStatuses["master_node"] = "OK"; //console.log("checkFinalRVOStatus", status); if(SETTINGS.no_voltage.size > 0) { let writeToFile = errorHandler.processMessage("no voltage detected"); if(writeToFile) errLogger.error("no voltage detected", SETTINGS.no_voltage); status = "NOK"; deviceStatuses["no_voltage"] = "NOK"; } else deviceStatuses["no_voltage"] = "OK"; if(status == "NOK") { sendTelemetry({status: "NOK"}, rvoTbName); return false; } return true; } // we pass array to function in case of rsPort [[55,3,0,1]] // we pass two values in case of websocket [3,1] const switchLogic = (...args) => { let values = {status: "OK"}; let dataToTb, pinIndex, newPinValue, twighlight; if(args.length == 1) { pinIndex = args[0][1] + 1; if (pinIndex === 17) pinIndex--; newPinValue = args[0][3]; twighlight = args[0][2]; } else { pinIndex = args[0]; newPinValue = args[1]; } let obj = pinsData[pinIndex]; if(obj == undefined) { previousValues[pinIndex] = newPinValue; return; } let type = obj.type; let line = obj.line; let tbname = obj.tbname; //default value let value = "On"; if(newPinValue === 0) value = "Off"; //Hlavný istič if(type === "state_of_main_switch") { if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) { sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_off", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); values["status"] = "NOK"; deviceStatuses["state_of_main_switch"] = "Off"; } else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { sendNotification("switchLogic", rvoTbName, "main_switch_has_been_turned_on", {}, "", SEND_TO.tb, instance , "state_of_main_switch"); deviceStatuses["state_of_main_switch"] = "On"; } } //Prevádzkový mód else if(type == "rotary_switch_state") { // combination of these two pins required to get result let pin2, pin3; if(pinIndex == 2 || pinIndex == "input1_02") { pin2 = newPinValue; pin3 = previousValues[3] || previousValues["input1_03"]; if(pin3 == undefined) { previousValues[pinIndex] = newPinValue; return; } } else if(pinIndex == 3 || pinIndex == "input1_03") { pin3 = newPinValue; pin2 = previousValues[2] || previousValues["input1_02"]; if(pin2 == undefined) { previousValues[pinIndex] = newPinValue; return; } } //console.log('***********************', pin2, pin3) if (pin2 == 1 && pin3 == 0) value = "Manual"; if (pin2 == 0 && pin3 == 0) value = "Off"; if (pin2 == 0 && pin3 == 1) value = "Automatic"; deviceStatuses["rotary_switch_state"] = value; //automatic - profilu pre nody sa vykonavaju //ak je spracovany, a automatic - tak ho zapnem //ak nie je spracovany, iba profil zapisem if(pin2 != undefined && pin3 != undefined) instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "rotary_switch_state", value: value}); //console.log("rotary_switch_state pin", pin2, pin3, value); } //Zdroj - pin 4 else if (type === "power_supply") { if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.ALERT, "Power supply is not OK", "", SEND_TO.tb, instance); sendNotification("switchLogic", rvoTbName, "power_supply_has_disconnected_input", {}, "", SEND_TO.tb, instance, "power_supply"); values["status"] = "NOK"; deviceStatuses["power_supply"] = "NOK"; } else if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.NOTICE, "Power supply is is OK", "", SEND_TO.tb, instance); sendNotification("switchLogic", rvoTbName, "power_supply_works_correctly", {}, "", SEND_TO.tb, instance, "power_supply"); deviceStatuses["power_supply"] = "OK"; } } //Batéria - pin 5 else if (type === "battery") { if (newPinValue === 1 && newPinValue !== previousValues[pinIndex]) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.ERROR, "Battery is not OK", "", SEND_TO.tb, instance); sendNotification("switchLogic", rvoTbName, "battery_level_is_low", {}, "", SEND_TO.tb, instance, "battery_level"); values["status"] = "NOK"; deviceStatuses["battery"] = "NOK"; } else if (newPinValue === 0 && newPinValue !== previousValues[pinIndex]) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.NOTICE, "Battery is OK", "", SEND_TO.tb, instance); sendNotification("switchLogic", rvoTbName, "battery_level_is_ok", {}, "", SEND_TO.tb, instance, "battery_level"); deviceStatuses["battery"] = "OK"; } } //Dverový kontakt - pin 6 else if(type == "door_condition") { newPinValue === 0 ? value = "open" : value = "closed"; if (newPinValue != previousValues[pinIndex]) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.NOTICE, `RVO door ${value}`, "", SEND_TO.tb, instance, "rvo_door"); //TODO ? sendNotification("switchLogic", rvoTbName, "door_value", {value: value}, "", SEND_TO.tb, instance, "rvo_door"); } if (value === "open" && SETTINGS.maintenance_mode) { sendNotification("switchLogic", rvoTbName, "door_opened", {}, "", SEND_TO.tb, instance, "rvo_door"); } if (value === "open" && !SETTINGS.maintenance_mode) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.WARNING, "RVO open door out of maintenance mode", "", SEND_TO.tb, instance); sendNotification("switchLogic", rvoTbName, "door_opened_without_permission", {}, "", SEND_TO.tb, instance, "rvo_door"); values["status"] = "NOK"; turnOnAlarm(); } if (value === "closed") { if(alarmStatus == "ON") turnOffAlarm(); //turnOffAlarm(); sendNotification("switchLogic", rvoTbName, "door_closed", {}, "", SEND_TO.tb, instance, "rvo_door"); } deviceStatuses["door_condition"] = value; } else if(type == "twilight_sensor") { //lux sensor value = parseFloat(newPinValue + (256*twighlight)); let now = new Date(); //new Date(dusk.getTime() let obj = {timestamp: now.getTime(), value: value}; //test //twilight_sensor_interval = 1; twilight_sensor.push(obj); twilight_sensor_array.push(value); //check if we receive just 1 constant value from lux sensor ==> error if(twilight_sensor_array.length > 10) { let set = new Set(twilight_sensor_array); if(set.size === 1 && !twighlightError) { twighlightError = true; values["status"] = "NOK"; let value = twilight_sensor_array.shift(); //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.ERROR, "Lux sensor error", {"Repeating value": value}, SEND_TO.tb, instance ); newPinValue = 0; } else if (set.size !== 1 && twighlightError) { //sendNotification("switchLogic", rvoTbName, ERRWEIGHT.NOTICE, "Lux sensor is working again", "", SEND_TO.tb, instance ); twighlightError = false; twilight_sensor_array.shift(); newPinValue = value; } else if (set.size === 1 && twighlightError) { values["status"] = "NOK"; twilight_sensor_array.shift(); newPinValue = 0; } } let diff = twilight_sensor[ twilight_sensor.length - 1 ].timestamp - twilight_sensor[0].timestamp; if(diff >= twilight_sensor_interval * 60 * 1000) { const average = twilight_sensor.reduce((acc, c) => acc + c.value, 0) / twilight_sensor.length; instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "lux_sensor", value: average}); twilight_sensor = []; //console.log("lux_sensor send", average); } //else console.log("lux_sensor", value, diff); values["status"] = "OK"; // } else if(type == "state_of_contactor") { if(!(deviceStatuses["state_of_contactor"][line] == value)) { sendNotification("switchLogic", rvoTbName, "state_of_contactor_for_line", {line: line, value: value}, "", SEND_TO.tb, instance ); } deviceStatuses["state_of_contactor"][line] = value; //true, false if(value === "On") value = true; else if(value === "Off") value = false; //TODO do we need to modify relays table with contactor value, if we do not use it on startup ?? let dataChanged = false; if(relaysData[line].contactor !== newPinValue) { dataChanged = true; relaysData[line].contactor = newPinValue; } instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, value: value, dataChanged: dataChanged}); reportLineStatus(line); //modify table relays // dbRelays.modify({ contactor: newPinValue }).where("line", line).make(function(builder) { // builder.callback(function(err, response) { // if(err == undefined) // { // let time = 0; // if(value) time = 1000 * 10;//10 sekund // let dataChanged = false; // if(relaysData[line].contactor != newPinValue) dataChanged = true; // relaysData[line].contactor = newPinValue; // //ak bola predchadzajuci stav off a novy stav je on, budu sa nastavovat nespracovane node profiles // //a budu sa odosielat commandy, tie vsak mozu zlyhat, a preto potrebujeme ich spusti trochu neskor // setTimeout(function(){ // instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "reload_relays", line: line, value: value, dataChanged: dataChanged}); // }, time); // reportLineStatus(line); // } // else // { // errLogger.error("modify table relays failed", err); // } // }); // }); } else if(type === "state_of_breaker") { let valueChanged = false; if(newPinValue != previousValues[pinIndex]) valueChanged = true; if(valueChanged) { instance.send(SEND_TO.cmd_manager, {sender: "dido_controller", cmd: "state_of_breaker", value: value, line: line}); } if(value == "Off") values["status"] = "NOK"; deviceStatuses["state_of_breaker"][line] = value; reportLineStatus(line); } values[type] = value; let result = checkFinalRVOStatus(); if(!result && line == 0) { values["status"] = "NOK"; } if(pinsData.hasOwnProperty(pinIndex)) { let valueChanged = false; if(newPinValue != previousValues[pinIndex]) valueChanged = true; if(type == "state_of_contactor") valueChanged = true; if(type == "rotary_switch_state") valueChanged = true; if(type === "state_of_breaker") { //console.log(type, values, valueChanged); } if(rvoTbName == "") { console.log("rvoTbName is EMPTY"); } if(rvoTbName == tbname) { values["statecode"] = calculateStateCode(); //console.log(type, values, valueChanged, FLOW.OMS_rvo_tbname, tbname); } if(valueChanged) { sendTelemetry(values, tbname); } if(type == "rotary_switch_state") { if(SETTINGS.maintenance_mode) value = "maintenance"; value = value.toLowerCase(); let values = {}; values["power_mode"] = value; sendTelemetry(values, tbname); } } else { logger.debug("no pinIndex", pinsData[pinIndex], pinsData); } //pin was changed previousValues[pinIndex] = newPinValue; } function sendTelemetry(values, tbname) { let dataToTb = { [tbname]: [ { "ts": Date.now(), "values": values } ] } //instance.send(SEND_TO.tb, dataToTb); tbHandler.sendToTb(dataToTb, instance); } } //! incomming data to websocket // [ // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_08', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_01', // alias: 'al_lights_kitchen', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_02', // alias: 'al_lights_bedroom', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_03', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_04', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_05', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_06', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // glob_dev_id: 1, // modes: [ 'Simple' ], // value: 0, // circuit: '1_07', // pending: false, // relay_type: 'physical', // dev: 'relay', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_08', // debounce: 50, // counter: 0, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_mode: 'Enabled', // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // dev: 'input', // modes: [ 'Simple', 'DirectSwitch' ], // debounce: 50, // counter: 1, // value: 1, // alias: 'al_main_switch', // mode: 'Simple', // circuit: '1_01' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 1, // circuit: '1_02', // debounce: 50, // counter: 2, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 1, // circuit: '1_03', // debounce: 50, // counter: 2, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_04', // debounce: 50, // counter: 1, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_05', // debounce: 50, // counter: 0, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_06', // debounce: 50, // counter: 0, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // counter_modes: [ 'Enabled', 'Disabled' ], // glob_dev_id: 1, // modes: [ 'Simple', 'DirectSwitch' ], // value: 0, // circuit: '1_07', // debounce: 50, // counter: 0, // counter_mode: 'Enabled', // dev: 'input', // mode: 'Simple' // }, // { // interval: 3, // value: 24.5, // circuit: '28744F7791180257', // address: '28744F7791180257', // time: 1631873896.48797, // typ: 'DS18B20', // lost: false, // dev: 'temp' // }, // { // bus: '/dev/i2c-2', // interval: 3, // dev: 'owbus', // scan_interval: 300, // circuit: '1', // do_scan: false, // do_reset: false // }, // { // glob_dev_id: 1, // last_comm: 0.014672994613647461, // ver2: '0.1', // sn: 42, // circuit: '1', // model: 'S207', // dev: 'neuron', // board_count: 1 // }, // { // circuit: '1_01', // value: 0, // glob_dev_id: 1, // dev: 'wd', // timeout: 5000, // was_wd_reset: 0, // nv_save: 0 // } // ] //! loaded pins_data --> from LM // { // '1': { pin: 1, type: 'state_of_main_switch', line: 0 }, // '2': { pin: 2, type: 'rotary_switch_state', line: 0 }, // '3': { pin: 3, type: 'rotary_switch_state', line: 0 }, // '4': { pin: 4, type: 'power_supply', line: 0 }, // '5': { pin: 5, type: 'battery', line: 0 }, // '6': { pin: 6, type: 'door_condition', line: 0 }, // '8': { pin: 8, type: 'state_of_breaker', line: 1 }, // '9': { pin: 9, type: 'state_of_breaker', line: 2 }, // '10': { pin: 10, type: 'state_of_breaker', line: 3 }, // '11': { pin: 11, type: 'state_of_contactor', line: 1 }, // '12': { pin: 12, type: 'state_of_contactor', line: 2 }, // '13': { pin: 13, type: 'state_of_contactor', line: 3 }, // '16': { pin: 16, type: 'twilight_sensor', line: 0 } // } //! pins.table --> from LM // pin:number|type:string|line:number // *|1|state_of_main_switch|0|........... // *|2|rotary_switch_state|0|........... // *|3|rotary_switch_state|0|........... // *|4|power_supply|0|........... // *|5|battery|0|........... // *|6|door_condition|0|........... // *|8|state_of_breaker|1|........... // *|9|state_of_breaker|2|........... // *|10|state_of_breaker|3|........... // *|11|state_of_contactor|1|........... // *|12|state_of_contactor|2|........... // *|13|state_of_contactor|3|........... // *|16|twilight_sensor|0|........... //! pins.table --> from UNIPI // pin:string|type:string|line:number // *|al_mswitch|state_of_main_switch|0|........... // *|al_rswitch1|rotary_switch_state|0|........... // *|al_rswitch2|rotary_switch_state|0|........... // *|al_power|power_supply|0|........... // *|al_battery|battery|0|........... // *|al_door|door_condition|0|........... // *|al_breaker1|state_of_breaker|1|........... // *|al_breaker2|state_of_breaker|2|........... // *|al_breaker3|state_of_breaker|3|........... // *|al_breaker4|state_of_breaker|4|........... // *|al_relay_1|state_of_contactor|1|........... // *|al_relay_2|state_of_contactor|2|........... // *|al_relay_3|state_of_contactor|3|........... // *|al_relay_4|state_of_contactor|4|........... // *|16|twilight_sensor|0|........... // *|28744F7791180257|temperature|0|........... //! pins_data --> from UNIPI // { // '16': { pin: '16', type: 'twilight_sensor', line: 0 }, // al_mswitch: { pin: 'al_mswitch', type: 'state_of_main_switch', line: 0 }, // al_rswitch1: { pin: 'al_rswitch1', type: 'rotary_switch_state', line: 0 }, // al_rswitch2: { pin: 'al_rswitch2', type: 'rotary_switch_state', line: 0 }, // al_power: { pin: 'al_power', type: 'power_supply', line: 0 }, // al_battery: { pin: 'al_battery', type: 'battery', line: 0 }, // al_door: { pin: 'al_door', type: 'door_condition', line: 0 }, // al_breaker1: { pin: 'al_breaker1', type: 'state_of_breaker', line: 1 }, // al_breaker2: { pin: 'al_breaker2', type: 'state_of_breaker', line: 2 }, // al_breaker3: { pin: 'al_breaker3', type: 'state_of_breaker', line: 3 }, // al_breaker4: { pin: 'al_breaker4', type: 'state_of_breaker', line: 4 }, // al_relay_1: { pin: 'al_relay_1', type: 'state_of_contactor', line: 1 }, // al_relay_2: { pin: 'al_relay_2', type: 'state_of_contactor', line: 2 }, // al_relay_3: { pin: 'al_relay_3', type: 'state_of_contactor', line: 3 }, // al_relay_4: { pin: 'al_relay_4', type: 'state_of_contactor', line: 4 }, // '28744F7791180257': { pin: '28744F7791180257', type: 'temperature', line: 0 } // } //! relays_data // { // '0': { // line: 0, // tbname: 'KjbN4q7JPZmexgdnz2yKQ98YAWwO0Q3BMX6ERLoV', // contactor: 1, // profile: '' // }, // '1': { // line: 1, // tbname: 'RMgnK93rkoAazbqdQ4yBG95Z1YXGx6pmwBeVEP2O', // contactor: 0, // profile: '{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"10:00","start_time":"20:00"},{"value":0,"end_time":"10:20","start_time":"10:00"},{"value":1,"end_time":"10:40","start_time":"10:20"},{"value":0,"end_time":"11:00","start_time":"10:40"},{"value":1,"end_time":"11:30","start_time":"11:00"},{"value":0,"end_time":"11:50","start_time":"11:30"},{"value":1,"end_time":"13:00","start_time":"11:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' // }, // '2': { // line: 2, // tbname: 'dlE1VQjYrNx9gZRmb38gG08oLBO4qaAk2M6JPnG7', // contactor: 0, // profile: '{"intervals":[{"value":0,"end_time":"20:00","start_time":"13:00"},{"value":1,"end_time":"10:00","start_time":"20:00"},{"value":0,"end_time":"10:20","start_time":"10:00"},{"value":1,"end_time":"10:40","start_time":"10:20"},{"value":0,"end_time":"11:00","start_time":"10:40"},{"value":1,"end_time":"11:30","start_time":"11:00"},{"value":0,"end_time":"11:50","start_time":"11:30"},{"value":1,"end_time":"13:00","start_time":"11:50"}],"astro_clock":false,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' // }, // '3': { // line: 3, // tbname: 'vnmG4kJxaXWNBgMQq0D7Aj5e9oZzOAlr6LdR3w2V', // contactor: 0, // profile: '{"intervals":[{"value":0,"end_time":"20:30","start_time":"13:00"},{"value":1,"end_time":"00:10","start_time":"20:30"},{"value":0,"end_time":"13:00","start_time":"05:40"},{"value":1,"end_time":"05:40","start_time":"00:10"}],"astro_clock":true,"dawn_lux_sensor":false,"dusk_lux_sensor":false,"dawn_lux_sensor_value":5,"dusk_lux_sensor_value":5,"dawn_astro_clock_offset":0,"dusk_astro_clock_offset":0,"dawn_lux_sensor_time_window":30,"dusk_lux_sensor_time_window":30,"dawn_astro_clock_time_window":60,"dusk_astro_clock_time_window":60}' // } // }