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, ETS group address importer, and KNX routing between interfaces. Easy to use and highly configurable.

225 lines (206 loc) 10.2 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() const ts = (node.serverKNX && typeof node.serverKNX.formatStatusTimestamp === 'function') ? node.serverKNX.formatStatusTimestamp(dDate) : `${dDate.getDate()}, ${dDate.toLocaleTimeString()}` // 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 : '') + ' (' + ts + ') ' + 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) }