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.

222 lines (203 loc) 10 kB
const ping = require('ping') module.exports = function (RED) { function knxUltimateWatchDog (config) { RED.nodes.createNode(this, config) const node = this node.serverKNX = RED.nodes.getNode(config.server) || undefined node.dpt = '1.001' node.notifyreadrequestalsorespondtobus = 'false' node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = '' node.notifyreadrequest = true node.notifyresponse = true node.notifywrite = false node.initialread = false node.listenallga = false node.outputtype = 'write' node.outputRBE = 'false' node.inputRBE = 'false' node.currentPayload = '' node.topic = config.topic !== undefined ? config.topic : '' node.retryInterval = config.retryInterval !== undefined ? config.retryInterval * 1000 : 10000 node.maxRetry = config.maxRetry !== undefined ? config.maxRetry : 6 node.autoStart = config.autoStart !== undefined ? config.autoStart : true node.beatNumber = 0 // Telegram counter node.timerWatchDog = null node.isWatchDog = true node.checkLevel = config.checkLevel !== undefined ? config.checkLevel : 'Ethernet' node.icountMessageInWindow = 0 node.alreadyNotifiedArray = new Array() // 20/10/2022 array of already notified errors generated by every single KNX-Ultimate node. This prevents floading with repeated errors, in case of disconnection const pushStatus = (status) => { if (!status) return const provider = node.serverKNX if (provider && typeof provider.applyStatusUpdate === 'function') { provider.applyStatusUpdate(node, status) } else { node.status(status) } } const updateStatus = (status) => { if (!status) return pushStatus(status) } // Used to call the status update from the config node. node.setNodeStatus = ({ fill, shape, text, payload, GA, dpt, devicename }) => { try { if (node.serverKNX === null) { updateStatus({ 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 updateStatus({ fill, shape, text: GA + payload + (node.listenallga === true ? ' ' + devicename : '') + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ' ' + text }) } catch (error) { } } if (!node.serverKNX) return function handleTheDog () { node.beatNumber += 1 if (node.beatNumber > node.maxRetry) { // Confirmed connection error node.beatNumber = 0 // Reset Counter const msg = { type: 'BUSError', checkPerformed: node.checkLevel, nodeid: node.id, payload: true, description: 'Watchdog elapsed with no response.' } node.send(msg) } else { if (node.checkLevel === 'Ethernet') { // 23/05/2020 using ping const cfg = { timeout: 2 } ping.sys.probe(node.serverKNX.host, function (isAlive) { if (isAlive) { node.watchDogTimerReset() } else { node.setNodeStatus({ fill: 'yellow', shape: 'dot', text: 'Check level ' + node.checkLevel + ', failed ping ' + node.beatNumber + ' of ' + node.maxRetry, payload: '', GA: '', dpt: '', devicename: '' }) }; }, cfg) } else { // Issue a read request if (node.serverKNX.knxConnection) { node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: node.topic, payload: '', dpt: '', outputtype: 'read' }) node.setNodeStatus({ fill: 'grey', shape: 'dot', text: 'Checking level ' + node.checkLevel + ', with beat telegram ' + node.beatNumber + ' of ' + node.maxRetry, payload: '', GA: '', dpt: '', devicename: '' }) }; } }; }; // This function is called by the knx-ultimate config node. node.watchDogTimerReset = () => { // Resets the watchdog, means all is OK if (node.checkLevel === 'Ethernet') { node.beatNumber = 0 // Reset counter const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Basic check level unicast ' + node.checkLevel + ' - Interface OK.', payload: '', GA: node.topic, dpt: '', devicename: '' }) }, 500) } else { // With this check level "Ethernet + KNX Twisted Pair", i need to obtain the "Response" from the physical device, otherwise the connection TP is broken. node.beatNumber = 0 // Reset counter const t = setTimeout(() => { // 21/03/2022 fixed possible memory leak. Previously was setTimeout without "let t = ". node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Full check level ' + node.checkLevel + ' - KNX BUS OK.', payload: '', GA: node.topic, dpt: '', devicename: '' }) }, 500) }; } // 16/02/2020 This function is called by the knx-ultimate config node. node.signalNodeErrorCalledByConfigNode = _oError => { // Report an error from knx-ultimate node. // let oError = {nodeid:node.id,topic:node.outputtopic,devicename:devicename,GA:GA,text:text}; const msg = { type: 'NodeError', checkPerformed: 'Self KNX-Ultimate node reporting a red color status', nodeid: _oError.nodeid, payload: true, description: _oError.text, completeError: _oError } // 22/10/2022 Find in the array of notified nodes. If found and equal, don't send. // Warning: to be optimized: the watchdog will not emit any "red" message anymore, unless the error's description changes. const oMsg = node.alreadyNotifiedArray.find(a => a.nodeid === msg.nodeid) if (oMsg === undefined) { node.alreadyNotifiedArray.push({ nodeid: msg.nodeid, description: msg.description }) node.send(msg) } else { if (oMsg.description !== msg.description) { node.send(msg) // Delete the item from the list node.alreadyNotifiedArray = node.alreadyNotifiedArray.filter(a => a.nodeid !== msg.nodeid) } } } node.StartWatchDogTimer = () => { node.beatNumber = 0 if (node.timerWatchDog !== null) clearInterval(node.timerWatchDog) node.timerWatchDog = setInterval(handleTheDog, node.retryInterval) // 02/01/2020 Start the timer that handles the queue of telegrams node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'WatchDog started.', payload: '', GA: '', dpt: '', devicename: '' }) } // This function is called by the knx-ultimate config node, to output a msg.payload. node.handleSend = msg => { node.send(msg) } node.on('input', function (msg) { if (typeof msg === 'undefined') return if (msg.hasOwnProperty('start')) { if (msg.start === true) { node.StartWatchDogTimer() } else { if (node.timerWatchDog !== null) clearInterval(node.timerWatchDog) node.setNodeStatus({ fill: 'grey', shape: 'ring', text: 'WatchDog stopped.', payload: '', GA: '', dpt: '', devicename: '' }) }; }; if (node.serverKNX === undefined) return // 01/02/2020 Dinamic change of the KNX Gateway IP, Port and Physical Address // This new thing has been requested by proServ RealKNX staff. if (msg.hasOwnProperty('setGatewayConfig')) { node.serverKNX.setGatewayConfig(msg.setGatewayConfig.IP, msg.setGatewayConfig.Port, msg.setGatewayConfig.PhysicalAddress, msg.setGatewayConfig.BindToEthernetInterface, msg.setGatewayConfig.Protocol, msg.setGatewayConfig.importCSV) const ret = { type: 'setGatewayConfig', checkPerformed: 'The Watchdog node changed the gateway configuration.', nodeid: node.id, payload: true, description: 'New Config issued to the gateway. IP:' + (msg.setGatewayConfig.IP || 'Unchanged') + ' Port:' + (msg.setGatewayConfig.Port || 'Unchanged') + ' PhysicalAddress:' + (msg.setGatewayConfig.PhysicalAddress || 'Unchanged') + ' Protocol:' + (msg.setGatewayConfig.Protocol || 'Unchanged') + ' BindLocalInterface:' + (msg.setGatewayConfig.BindToEthernetInterface || 'Unchanged' + ' importCSV:' + (msg.setGatewayConfig.importCSV || 'Unchanged')), completeError: '' } node.send(ret) }; // 05/05/2021 force connection/disconnectio of the gateway if (msg.hasOwnProperty('connectGateway')) { node.serverKNX.connectGateway(msg.connectGateway) const ret = { type: 'connectGateway', checkPerformed: 'The Watchdog issued a connection/disconnection to the gateway.', nodeid: node.id, payload: msg.connectGateway, description: 'Connection', completeError: '' } node.send(ret) } }) node.on('close', function (done) { if (node.timerWatchDog !== null) clearInterval(node.timerWatchDog) if (node.serverKNX) { node.serverKNX.removeClient(node) }; done() }) // On each deploy, unsubscribe+resubscribe // Unsubscribe(Subscribe) if (node.serverKNX) { if (node.timerWatchDog !== null) clearInterval(node.timerWatchDog) node.serverKNX.removeClient(node) if (node.topic || node.listenallga) { node.serverKNX.addClient(node) if (node.autoStart) node.StartWatchDogTimer() // Autostart watchdog } } } RED.nodes.registerType('knxUltimateWatchDog', knxUltimateWatchDog) }