UNPKG

node-red-contrib-knx-ultimate

Version:

Control your KNX and KNX Secure intallation via Node-Red! A bunch of KNX nodes, with integrated Philips HUE control and ETS group address importer. Easy to use and highly configurable.

608 lines (571 loc) 33.7 kB
const loggerClass = require('./utils/sysLogger') /* eslint-disable max-len */ module.exports = function (RED) { const _ = require('lodash'); const KNXUtils = require('knxultimate'); const payloadRounder = require('./utils/payloadManipulation'); const dptlib = require('knxultimate').dptlib; function knxUltimate(config) { RED.nodes.createNode(this, config); const node = this; node.serverKNX = RED.nodes.getNode(config.server) || undefined; if (node.serverKNX === undefined) { node.status({ fill: 'red', shape: 'dot', text: '[THE GATEWAY NODE HAS BEEN DISABLED]' }); return; } // Used to call the status update from the config node. node.setNodeStatus = ({ fill, shape, text, payload, GA, dpt, devicename, }) => { try { if (node.serverKNX === null) { node.status({ fill: 'red', shape: 'dot', text: '[NO GATEWAY SELECTED]' }); return; } if (node.icountMessageInWindow == -999) return; // Locked out, doesn't change status. const dDate = new Date(); // 30/08/2019 Display only the things selected in the config GA = (typeof GA === 'undefined' || GA == '') ? '' : `(${GA}) `; devicename = devicename || ''; dpt = (typeof dpt === 'undefined' || dpt == '') ? '' : ` DPT${dpt}`; payload = typeof payload === 'object' ? JSON.stringify(payload) : payload; node.status({ fill, shape, text: `${GA + payload + (node.listenallga === true ? ` ${devicename}` : '')} (day ${dDate.getDate()}, ${dDate.toLocaleTimeString()}) ${text}` }); // 16/02/2020 signal errors to the server if (fill.toUpperCase() === 'RED') { if (node.serverKNX) { const oError = { nodeid: node.id, topic: node.outputtopic, devicename, GA, text, }; node.serverKNX.reportToWatchdogCalledByKNXUltimateNode(oError); } } // Validate the Address to advise the user. The address can be undefined, because the // group address can be set via setConfig if (node.listenallga === false) { try { KNXUtils.validateKNXAddress(node.topic, true); } catch (error) { node.setNodeStatus({ fill: 'grey', shape: 'ring', text: "DISABLED: " + error.message, payload: '', GA: node.topic, dpt: '', devicename: '', }); } } } catch (error) { } }; // Get the Group Address from various sources if (config.setTopicType === undefined || config.setTopicType === 'str') { node.topic = config.topic; node.dpt = config.dpt || '1.001'; } else if (config.setTopicType === 'flow') { try { node.topic = node.context().flow.get(config.topic); node.dpt = 'auto'; payloadRounder.KNXULtimateChangeConfigByInputMSG({ setConfig: { setGroupAddress: node.topic, setDPT: node.dpt } }, node, config); } catch (error) { node.topic = undefined; } } else if (config.setTopicType === 'global') { try { node.topic = node.context().global.get(config.topic); node.dpt = 'auto'; payloadRounder.KNXULtimateChangeConfigByInputMSG({ setConfig: { setGroupAddress: node.topic, setDPT: node.dpt } }, node, config); } catch (error) { node.topic = undefined; } } else if (config.setTopicType === 'env') { try { node.topic = RED.util.getSetting(node, config.topic); // takes care of the subflow's env vairables node.dpt = 'auto'; payloadRounder.KNXULtimateChangeConfigByInputMSG({ setConfig: { setGroupAddress: node.topic, setDPT: node.dpt } }, node, config); } catch (error) { node.topic = undefined; } } node.outputtopic = (config.outputtopic === undefined || config.outputtopic === '') ? node.topic : config.outputtopic; // 07/02/2020 Importante, per retrocompatibilità node.name = config.name; node.notifyreadrequest = config.notifyreadrequest || false; node.notifyreadrequestalsorespondtobus = config.notifyreadrequestalsorespondtobus || 'false'; // Auto respond if notifireadrequest is true node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = config.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized || ''; node.notifyresponse = config.notifyresponse || false; node.notifywrite = config.notifywrite; node.initialread = config.initialread || 0; if (node.initialread === true) node.initialread = 1; // 04/04/2021 Backward compatibility if (node.initialread === false) node.initialread = 0; // 04/04/2021 Backward compatibility node.initialread = Number(config.initialread); node.listenallga = config.listenallga || false; node.outputtype = config.outputtype || 'write';// When the node is used as output node.outputRBE = config.outputRBE || 'false'; // Apply or not RBE to the output (Messages coming from flow) node.inputRBE = config.inputRBE || 'false'; // Apply or not RBE to the input (Messages coming from BUS) // Backward compatibility if (node.outputRBE === true) node.outputRBE = 'true'; if (node.outputRBE === false) node.outputRBE = 'false'; if (node.inputRBE === true) node.inputRBE = 'true'; if (node.inputRBE === false) node.inputRBE = 'false'; node.currentPayload = ''; // Current value for the RBE input and for the .previouspayload msg node.icountMessageInWindow = 0; // Used to prevent looping messages node.messageQueue = []; // 01/01/2020 All messages from the flow to the node, will be queued and will be sent separated by 60 milliseconds each. Use uf the underlying api "minimumDelay" is not possible because the telegram order isn't mantained. node.formatmultiplyvalue = (typeof config.formatmultiplyvalue === 'undefined' ? 1 : config.formatmultiplyvalue); node.formatnegativevalue = (typeof config.formatnegativevalue === 'undefined' ? 'leave' : config.formatnegativevalue); node.formatdecimalsvalue = (typeof config.formatdecimalsvalue === 'undefined' ? 999 : config.formatdecimalsvalue); node.passthrough = (typeof config.passthrough === 'undefined' ? 'no' : config.passthrough); node.inputmessage = {}; // Stores the input message to be passed through node.timerTTLInputMessage = null; // The stored node.inputmessage has a ttl. try { node.sysLogger = new loggerClass({ loglevel: node.serverKNX.loglevel || 'error', setPrefix: node.type + " <" + (node.name || node.id || '') + ">" }); } catch (error) { console.log(error.stack) } node.sendMsgToKNXCode = config.sendMsgToKNXCode || undefined; node.receiveMsgFromKNXCode = config.receiveMsgFromKNXCode || undefined; if (node.sendMsgToKNXCode === '') node.sendMsgToKNXCode = undefined if (node.receiveMsgFromKNXCode === '') node.receiveMsgFromKNXCode = undefined // Check if the node has a valid dpt if (node.listenallga === false) { if (node.dpt === undefined || node.dpt === '') { node.setNodeStatus({ fill: 'red', shape: 'dot', text: 'The Datapoint cannot be empty.', payload: '', GA: '', dpt: '', devicename: '', }); return; } } // Used in the KNX Function TAB let getGAValue = function getGAValue(_ga = undefined, _dpt = undefined) { try { if (_ga === undefined) return; // The GA can have the devicename as well, separated by a blank space (1/1/0 light table ovest), // I must take the GA only const blankSpacePosition = _ga.indexOf(" "); if (blankSpacePosition > -1) _ga = _ga.substring(0, blankSpacePosition); // Is there a GA in the server's exposedGAs? const found = node.serverKNX.exposedGAs.find(a => a.ga === _ga); if (found !== undefined) { if (_dpt === undefined && found.dpt === undefined) { const errM = 'getGaValue: node ID:' + node.id + ' ' + 'No CSV file imported. Please provide the dpt manually'; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); return; }; return dptlib.fromBuffer(found.rawValue, dptlib.resolve(_dpt || found.dpt)); } else { const errM = 'getGaValue: node ID:' + node.id + ' ' + 'Group Address not yet read, try later.'; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); return; } } catch (error) { const errM = 'getGaValue: node ID:' + node.id + ' ' + error.stack; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); } } // Used in the KNX Function TAB let setGAValue = function setGAValue(_ga = undefined, _value = undefined, _dpt = undefined) { try { if (_ga === undefined) return; // The GA can have the devicename as well, separated by a blank space (1/1/0 light table ovest), // I must take the GA only const blankSpacePosition = _ga.indexOf(" "); if (blankSpacePosition > -1) _ga = _ga.substring(0, blankSpacePosition); if (_dpt === undefined) { // Try getting dpt from ETS CSV const found = node.serverKNX.exposedGAs.find(a => a.ga === _ga); if (found === undefined || found.dpt === undefined) { const errM = 'setGAValue: node ID:' + node.id + ' ' + 'No CSV file imported. Please provide the dpt manually'; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); return; } } node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: _ga, payload: _value, dpt: _dpt, outputtype: 'write', nodecallerid: node.id, }); } catch (error) { const errM = 'setGAValue: node ID:' + node.id + ' ' + error.stack; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); } } // Used in the KNX Function TAB let self = function self(_value) { try { node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: node.topic, payload: _value, dpt: node.dpt, outputtype: 'write', nodecallerid: node.id, }); } catch (error) { const errM = 'self: node ID:' + node.id + ' ' + error.stack; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); } } // Used in the KNX Function TAB let toggle = function toggle() { if (node.currentPayload === true || node.currentPayload === false) { try { node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: node.topic, payload: !node.currentPayload, dpt: node.dpt, outputtype: 'write', nodecallerid: node.id, }); } catch (error) { const errM = 'toggle: node ID:' + node.id + ' ' + error.stack; RED.log.error(errM); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(errM); } } } // This function is called by the knx-ultimate config node, to output a msg.payload. node.handleSend = (msg) => { // 27/03/2020 can i merge the last input msg arrived, with the output? try { if (node.passthrough === 'yes') { // Respect the order! Object.assign(target, master). On master will be copied to target and properties of master will overwrite the same properties on target! if (node.timerTTLInputMessage !== null) clearTimeout(node.timerTTLInputMessage); msg = Object.assign(RED.util.cloneMessage(node.inputmessage), msg); node.inputmessage = {}; } else if (node.passthrough === 'yesownprop') { // Yes, but in an own prop if (node.timerTTLInputMessage !== null) clearTimeout(node.timerTTLInputMessage); msg.inputmessage = RED.util.cloneMessage(node.inputmessage); node.inputmessage = {}; } } catch (error) { } // #region "Inject the msg to the JS code, then output msg to the flow" // -+++++++++++++++++++++++++++++++++++++++++++ if (node.receiveMsgFromKNXCode !== undefined) { try { let receiveMsgFromKNXCode = new Function('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.receiveMsgFromKNXCode) msg = receiveMsgFromKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue); } catch (error) { RED.log.error('knxUltimate: receiveMsgFromKNXCode: node ID:' + node.id + ' ' + error.message); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`receiveMsgFromKNXCode: node id ${node.id} ` || ' ' + error.stack); return; } } // -+++++++++++++++++++++++++++++++++++++++++++ //#endregion // if (msg.echoed !== undefined && msg.echoed === true) { // node.setNodeStatus({ // fill: 'grey', shape: 'dot', text: 'Output echoed msg', payload: '', GA: node.topic, dpt: '', devicename: '', // }); // } if (msg !== undefined) node.send(msg); }; node.on('input', (msg) => { if (typeof msg === 'undefined') return; if (!node.serverKNX) return; // 29/08/2019 Server not instantiate // 11/01/2021 Accept properties change from msg // ********************************* if (msg.hasOwnProperty('setConfig')) { payloadRounder.KNXULtimateChangeConfigByInputMSG(msg, node, config); return; } // ********************************* // 16/06/2024 Check wether the node has a group address set. // Validate the Address if (node.listenallga === false) { try { KNXUtils.validateKNXAddress(node.topic, true); } catch (error) { node.setNodeStatus({ fill: 'red', shape: 'dot', text: error.message, payload: '', GA: node.topic, dpt: '', devicename: '', }); return; } } // 19/06/2022 Reset the RBE filter https://github.com/Supergiovane/node-red-contrib-knx-ultimate/issues/191 // ********************************* if (msg.hasOwnProperty('resetRBE')) { node.currentPayload = ''; node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'Reset RBE filter on this node.', payload: '', GA: '', dpt: '', devicename: '', }); return; } // ********************************* if (node.passthrough !== 'no') { // 27/03/2020 Save the input message to be passed out to msg output // The msg has a TTL of 3 seconds if (node.timerTTLInputMessage !== null) clearTimeout(node.timerTTLInputMessage); node.timerTTLInputMessage = setTimeout(() => { node.inputmessage = {}; }, 3000); node.inputmessage = RED.util.cloneMessage(msg); // 28/03/2020 Store the message to be passed through. } // #region "Inject the msg to the JS code, then output msg to the flow" // -+++++++++++++++++++++++++++++++++++++++++++ if (node.sendMsgToKNXCode !== undefined) { try { let sendMsgToKNXCode = new Function('msg', 'getGAValue', 'node', 'RED', 'self', 'toggle', 'setGAValue', node.sendMsgToKNXCode) msg = sendMsgToKNXCode(msg, getGAValue, node, RED, self, toggle, setGAValue); if (msg === undefined) return; } catch (error) { RED.log.error('knxUltimate: sendMsgToKNXCode: node ID:' + node.id + ' ' + error.message); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`sendMsgToKNXCode: node id ${node.id} ` || ' ' + error.stack); return; } } // -+++++++++++++++++++++++++++++++++++++++++++ //#endregion // 25/07/2019 if payload is read or the Telegram type is set to "read", do a read, otherwise, write to the bus if ((msg.hasOwnProperty('readstatus') && msg.readstatus === true) || node.outputtype === 'read') { // READ: Send a Read request to the bus let grpaddr = ''; if (node.listenallga == false) { grpaddr = node.topic; if (msg.hasOwnProperty('destination')) grpaddr = msg.destination; // 29/12/2020 Protection over circular references (for example, if you link two Ultimate Nodes toghether with the same group address), to prevent infinite loops if (msg.hasOwnProperty('knx')) { if (msg.knx.destination == grpaddr && ((msg.knx.event === 'GroupValue_Response' || msg.knx.event === 'GroupValue_Read'))) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Circular reference protection during READ. The node ${node.id} has been temporary disabled. Two nodes with same group address and reaction/Telegram type are linked. See the FAQ in the Wiki. Msg:${JSON.stringify(msg)}`); const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.setNodeStatus({ fill: 'red', shape: 'ring', text: `DISABLED due to a circulare reference while READ (${grpaddr}).`, payload: '', GA: '', dpt: '', devicename: '', }); }, 1000); return; } } node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Read', payload: '', GA: grpaddr, dpt: '', devicename: '', }); node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr, payload: '', dpt: '', outputtype: 'read', nodecallerid: node.id, }); } else { // Listen all GAs if (msg.hasOwnProperty('destination')) { // listenallga is true, but the user specified own group address grpaddr = msg.destination; // 29/12/2020 Protection over circular references (for example, if you link two Ultimate Nodes toghether with the same group address), to prevent infinite loops if (msg.hasOwnProperty('knx')) { if (msg.knx.destination == grpaddr && ((msg.knx.event === 'GroupValue_Response' || msg.knx.event === 'GroupValue_Read'))) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Circular reference protection during READ-2. The node ${node.id} has been temporary disabled. Two nodes with same group address and reaction/Telegram type are linked. See the FAQ in the Wiki. Msg:${JSON.stringify(msg)}`); const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.setNodeStatus({ fill: 'red', shape: 'ring', text: `DISABLED due to a circulare reference while READ-2 (${grpaddr}).`, payload: '', GA: '', dpt: '', devicename: '', }); }, 1000); return; } } node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr, payload: '', dpt: '', outputtype: 'read', nodecallerid: node.id, }); } else { // Issue read to all group addresses // 25/10/2019 the user is able not import the csv, so i need to check for it. This option should be unckecked by the knxUltimate html config, but.. if (typeof node.serverKNX.csv !== 'undefined') { let delay = 0; for (let index = 0; index < node.serverKNX.csv.length; index++) { const element = node.serverKNX.csv[index]; const grpaddr = element.ga; // 29/12/2020 Protection over circular references (for example, if you link two Ultimate Nodes toghether with the same group address), to prevent infinite loops if (msg.hasOwnProperty('knx')) { if (msg.knx.destination == grpaddr && ((msg.knx.event === 'GroupValue_Response' || msg.knx.event === 'GroupValue_Read'))) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Circular reference protection during READ-3. Node ${node.id} The read request hasn't been sent. Two nodes with same group address and reaction/Telegram type are linked. See the FAQ in the Wiki. Msg:${JSON.stringify(msg)}`); node.setNodeStatus({ fill: 'red', shape: 'ring', text: `NOT SENT due to a circulare reference while READ-3 (${grpaddr}).`, payload: '', GA: '', dpt: '', devicename: '', }); } } else { node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr, payload: '', dpt: '', outputtype: 'read', nodecallerid: node.id, }); const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". // Timeout is only for the status update. node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Add Read to queue...', payload: '', GA: grpaddr, dpt: element.dpt, devicename: element.devicename, }); }, delay); delay += 10; } } } else { // No csv. A chi cavolo dovrei mandare la richiesta read? const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". // Timeout is only for the status update. node.setNodeStatus({ fill: 'red', shape: 'dot', text: "Read: ETS file not set, i don't know where to send the read request.", payload: '', GA: '', dpt: '', devicename: node.name, }); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`KNX-Ultimate: ETS file not set, i don't know where to send the read request. I'm the node ${node.id}`); }, 100); } } } } else { if (node.listenallga === false) { // 23/12/2020 Applying RBE filter if (node.outputRBE === "true") { // 19/01/2023 CHECKING THE INPUT PAYLOAD (ROUND, ETC) BASED ON THE NODE CONFIG //* ******************************************************** const pTest = payloadRounder.Manipulate(node, msg.payload); //* ******************************************************** if (_.isEqual(node.currentPayload, pTest)) { // RBE kicks in, doesn't send the payload node.setNodeStatus({ fill: 'grey', shape: 'ring', text: `rbe block (${msg.payload}) to KNX`, payload: '', GA: '', dpt: '', devicename: '', }); return; } } } // 07/02/2020 Revamped flood protection (avoid accepting too many messages as input) if (node.icountMessageInWindow == -999) return; // Locked out if (node.icountMessageInWindow == 0) { const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". if (node.icountMessageInWindow >= 120) { // Looping detected node.setNodeStatus({ fill: 'red', shape: 'ring', text: 'DISABLED! Flood protection! Too many msg at the same time.', payload: '', GA: '', dpt: '', devicename: '', }); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Node ${node.id} has been disabled due to Flood Protection. Too many messages in a timeframe. Check your flow's design or use RBE option.`); node.icountMessageInWindow = -999; // Lock out node } else { node.icountMessageInWindow = -1; } }, 1000); } node.icountMessageInWindow += 1; // OUTPUT: Send message to the bus (write/response) if (node.serverKNX.knxConnection) { let { outputtype } = node; let grpaddr = ''; let dpt = ''; // 29/12/2020 Check wheter the input message contains the "event" property, that overwrite the node's outputtype if (msg.hasOwnProperty('event')) { if (msg.event === 'GroupValue_Write') outputtype = 'write'; if (msg.event === 'GroupValue_Response') outputtype = 'response'; if (msg.event === 'Update_NoWrite') outputtype = 'update'; // 05/01/2021 Doesn't send anything to the bus. Only updates the node currentPayload } if (node.listenallga === true) { // The node is set to Universal mode (listen to all Group Addresses). Some fields are needed if (msg.hasOwnProperty('destination')) { grpaddr = msg.destination; } else { node.setNodeStatus({ fill: 'red', shape: 'dot', text: 'msg.destination not set!', payload: '', GA: '', dpt: '', devicename: '', }); return; } if (msg.hasOwnProperty('dpt') && msg.dpt !== undefined && msg.dpt !== '') { dpt = msg.dpt; } else { // No datapoint set. If the CSV is loaded, try to get it from there. if (!msg.hasOwnProperty('writeraw')) { // In raw mode, Datapoint is useless // Get the datapoint from the CSV if (typeof node.serverKNX.csv !== 'undefined') { const oGA = node.serverKNX.csv.filter((sga) => sga.ga == grpaddr)[0]; if (oGA !== undefined) { dpt = oGA.dpt; } else { node.setNodeStatus({ fill: 'red', shape: 'dot', text: 'msg.dpt not set and not found in the CSV!', payload: '', GA: '', dpt: '', devicename: '', }); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`node id: ${node.id} ` + 'msg.dpt not set and not found in the CSV!'); return; } } else { node.setNodeStatus({ fill: 'red', shape: 'dot', text: "msg.dpt not set and there's no CSV to search for!", payload: '', GA: '', dpt: '', devicename: '', }); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`node id: ${node.id} ` + 'msg.dpt not set and there\'s no CSV to search for!'); return; } } } } else { grpaddr = msg.hasOwnProperty('destination') ? msg.destination : node.topic; dpt = (msg.hasOwnProperty('dpt') && msg.dpt !== undefined && msg.dpt !== '') ? msg.dpt : node.dpt; } // Protection over circular references (for example, if you link two Ultimate Nodes toghether with the same group address), to prevent infinite loops if (msg.hasOwnProperty('knx')) { if (msg.knx.destination == grpaddr && ((msg.knx.event === 'GroupValue_Write' && outputtype === 'write') || (msg.knx.event === 'GroupValue_Response' && outputtype === 'response') || (msg.knx.event === 'GroupValue_Response' && outputtype === 'read') || (msg.knx.event === 'GroupValue_Read' && outputtype === 'read'))) { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.error(`Circular reference protection. The node ${node.id} has been temporarely disabled. Two nodes with same group address and reaction/Telegram type are linked. See the FAQ in the Wiki. Msg:${JSON.stringify(msg)}`); const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.setNodeStatus({ fill: 'red', shape: 'ring', text: `DISABLED due to a circulare reference (${grpaddr}).`, payload: '', GA: '', dpt: '', devicename: '', }); }, 1000); return; } } // 01/12/2020 Write RAW added. // If you encode the values by yourself, you can write raw buffers with writeRaw(groupaddress: string, buffer: Buffer, bitlength?: Number, callback?: () => void). // The third (optional) parameter bitlength is necessary for datapoint types where the bitlength does not equal the buffers bytelength * 8. This is the case for dpt 1 (bitlength 1), 2 (bitlength 2) and 3 (bitlength 4). For other dpts the paramter can be omitted. // // Write raw buffer to a groupaddress with dpt 1 (e.g light on = value true = Buffer<01>) with a bitlength of 1 // connection.writeRaw('1/0/0', Buffer.from('01', 'hex'), 1) // // Write raw buffer to a groupaddress with dpt 9 (e.g temperature 18.4 °C = Buffer<0730>) without bitlength // connection.writeRaw('1/0/0', Buffer.from('0730', 'hex')) if (msg.hasOwnProperty('writeraw') && msg.hasOwnProperty('writeraw') !== null) { try { if (msg.hasOwnProperty('bitlenght') && msg.bitlenght !== null) { node.serverKNX.knxConnection.writeRaw(grpaddr, msg.writeraw, msg.bitlenght); } else { node.serverKNX.knxConnection.writeRaw(grpaddr, msg.writeraw); } node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'RAW Write', payload: '', GA: grpaddr, dpt: '', devicename: '', }); } catch (error) { node.setNodeStatus({ fill: 'red', shape: 'dot', text: `Error RAW Write: ${error}`, payload: '', GA: grpaddr, dpt: '', devicename: '', }); } return; } if (outputtype == 'response') { try { node.currentPayload = msg.payload;// 31/12/2019 Set the current value (because, if the node is a virtual device, then it'll never fire "GroupValue_Write" in the server node, causing the currentPayload to never update) node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr, payload: msg.payload, dpt, outputtype, nodecallerid: node.id, }); node.setNodeStatus({ fill: 'blue', shape: 'dot', text: 'Responding', payload: msg.payload, GA: grpaddr, dpt, devicename: '', }); } catch (error) { } } else if (outputtype == 'update') { // 05/01/2021 Updates only the internal currentPayload value. try { node.currentPayload = msg.payload; node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr, payload: msg.payload, dpt, outputtype, nodecallerid: node.id, }); node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Updating internal value', payload: msg.payload, GA: grpaddr, dpt, devicename: '', }); } catch (error) { } } else { try { node.currentPayload = msg.payload;// 31/12/2019 Set the current value (because, if the node is a virtual device, then it'll never fire "GroupValue_Write" in the server node, causing the currentPayload to never update) node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Writing', payload: msg.payload, GA: grpaddr, dpt, devicename: '', }); // if (node.serverKNX.linkStatus === "connected") { node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: grpaddr, payload: msg.payload, dpt: dpt, outputtype: outputtype, nodecallerid: node.id, }); } catch (error) { } } } } }); node.on('close', (done) => { if (node.timerTTLInputMessage !== null) clearTimeout(node.timerTTLInputMessage); node.inputmessage = {}; if (node.serverKNX) { node.serverKNX.removeClient(node); try { if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`Close: node id ${node.id} with topic ${node.topic || ''} has been removed from the server.`); } catch (error) { } } done(); }); // On each deploy, add the node to the server list if (node.serverKNX) { node.serverKNX.addClient(node); if (node.sysLogger !== undefined && node.sysLogger !== null) node.sysLogger.info(`addClient: node id ${node.id}` || '' + ` with topic ${node.topic || ''} has been added to the server.`); // 05/11/2021 if the node is set to read from bus, issue a read. // "node-input-initialread0": "No", // "node-input-initialread1": "Leggi dal BUS KNX", // "node-input-initialread2": "Leggi l'ultimo valore salvato su file prima della disconnessione.", // "node-input-initialread3": "Leggi l'ultimo valore salvato su file prima della disconnessione. Se inesistente, leggi dal BUS KNX", if (node.serverKNX.linkStatus === 'connected' && node.initialread === 1 || node.initialread === 3) { node.setNodeStatus({ fill: 'yellow', shape: 'dot', text: 'Get value from BUS.', payload: '', GA: node.topic || '', dpt: '', devicename: '', }); const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.emit('input', { readstatus: true }); }, 3000); } } } RED.nodes.registerType('knxUltimate', knxUltimate); };