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, KNX AI for diagnosticsand KNX routing between interfaces. Easy to use and highly configurable.
225 lines (206 loc) • 10.2 kB
JavaScript
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)
}