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.
209 lines (194 loc) • 8.72 kB
JavaScript
module.exports = function (RED) {
// msg is:
// // Build final input message object
// return {
// topic: _outputtopic
// , payload: jsValue
// , devicename: (typeof _devicename !== 'undefined') ? _devicename : ""
// , payloadmeasureunit: sPayloadmeasureunit
// , payloadsubtypevalue: sPayloadsubtypevalue
// , knx:
// {
// event: _event
// , dpt: sInputDpt
// //, details: dpt
// , dptdesc: sDptdesc
// , source: _srcGA
// , destination: _destGA
// , rawValue: _Rawvalue
// }
// };
// The node.exposedGAs is and array of:
// {
// address,
// dpt,
// payload
// lastupdate
// lastupdateLocale
// }
function knxUltimateGlobalContext (config) {
RED.nodes.createNode(this, config)
const node = this
node.serverKNX = RED.nodes.getNode(config.server) || undefined
node.topic = node.name
node.name = config.name === undefined ? 'KNXGlobalContext' : config.name
node.outputtopic = node.name
node.dpt = ''
node.notifyreadrequest = false
node.notifyreadrequestalsorespondtobus = 'false'
node.notifyreadrequestalsorespondtobusdefaultvalueifnotinitialized = ''
node.notifyresponse = true
node.notifywrite = true
node.initialread = false
node.listenallga = true
node.outputtype = 'write'
node.outputRBE = 'false' // Apply or not RBE to the output (Messages coming from flow)
node.inputRBE = 'false' // Apply or not RBE to the input (Messages coming from BUS)
node.currentPayload = '' // Current value for the RBE input and for the .previouspayload msg
node.passthrough = 'no'
node.formatmultiplyvalue = 1
node.formatnegativevalue = 'leave'
node.formatdecimalsvalue = 999
node.writeExecutionInterval = config.writeExecutionInterval === undefined ? 1000 : config.writeExecutionInterval
node.contextStorage = config.contextStorage !== undefined ? config.contextStorage : ''
node.exposeAsVariable = config.exposeAsVariable !== undefined ? config.exposeAsVariable : 'exposeAsVariableREADONLY' // Should expose the Group Addresses to the Global Context?
node.exposedGAs = []
node.timerExposedGAs = null
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 }
GA = GA === undefined ? '' : GA
payload = payload === undefined ? '' : payload
payload = typeof payload === 'object' ? JSON.stringify(payload) : payload
const dDate = new Date()
const ts = (node.serverKNX && typeof node.serverKNX.formatStatusTimestamp === 'function')
? node.serverKNX.formatStatusTimestamp(dDate)
: `${dDate.getDate()}, ${dDate.toLocaleTimeString()}`
updateStatus({ fill, shape, text: GA + ' ' + payload + ' ' + text + ' (' + ts + ')' })
} catch (error) {
}
}
// 02/12/2022 Expose the complete ETS CSV as well
if (node.exposeAsVariable !== 'exposeAsVariableNO') {
try {
node.serverKNX.csv.forEach(element => {
node.exposedGAs.push({ address: element.ga, dpt: element.dpt, devicename: element.devicename, payload: undefined, lastupdate: undefined, lastupdateLocale: undefined })
})
} catch (error) {
}
}
// exposeAsVariableREADWRITE
// #region "WRITE TO BUS"
node.goTimerGo = function () {
if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs) // 21/03/2021
node.timerExposedGAs = setTimeout(() => {
let oContext = node.context().global.get(node.name + '_WRITE', node.contextStorage) || []
node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
for (let index = 0; index < oContext.length; index++) {
const element = oContext[index]
if (element.hasOwnProperty('address') === false) {
node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'NO Group Address set', payload: '', GA: '', dpt: '', devicename: '' })
RED.log.error('knxUltimateGlobalContext: No group address set in node ' + node.id)
oContext = null // 21/03/2022
node.goTimerGo()
return
}
if (element.hasOwnProperty('payload') === false) {
node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'NO payload set', payload: '', GA: '', dpt: '', devicename: '' })
RED.log.error('knxUltimateGlobalContext: No payload set for address ' + element.address + ' in node ' + node.id)
oContext = null // 21/03/2022
node.goTimerGo()
return
}
// 13/09/2021 retrieve the datapoint if not specified
if (element.hasOwnProperty('dpt') === false || element.dpt === undefined || element.dpt === '') {
try {
const sDPT = node.serverKNX.csv.find(item => item.ga === element.address).dpt
element.dpt = sDPT
} catch (error) {
node.setNodeStatus({ fill: 'RED', shape: 'dot', text: 'Datapoint not found in CSV for ' + element.address, payload: '', GA: '', dpt: '', devicename: '' })
RED.log.error('knxUltimateGlobalContext: Datapoint not found in CSV for address ' + element.address + ' in node ' + node.id)
oContext = null // 21/03/2022
node.goTimerGo()
return
}
}
node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Write', payload: element.payload, GA: element.address, dpt: element.dpt || '', devicename: '' })
node.serverKNX.sendKNXTelegramToKNXEngine({ grpaddr: element.address, payload: element.payload, dpt: element.dpt || '', outputtype: 'write', nodecallerid: node.id })
}
oContext = null // 21/03/2022
node.goTimerGo()
}, node.writeExecutionInterval)
}
// 21/02/2021 timer for write to BUS
if (node.exposeAsVariable === 'exposeAsVariableREADWRITE') {
node.goTimerGo()
node.setNodeStatus({ fill: 'green', shape: 'dot', text: 'Start Writing', payload: '', GA: '', dpt: '', devicename: '' })
} else {
if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs)
node.context().global.set(node.name + '_WRITE', [], node.contextStorage) // Delete the var
}
// #endregion
// This function is called by the knx-ultimate config node, to output a msg.payload.
node.handleSend = msg => {
if (node.exposeAsVariable !== 'exposeAsVariableNO') {
try {
var oGa = node.exposedGAs.find(ga => ga.address === msg.knx.destination)
} catch (error) {
console.log(error)
}
const dNow = new Date()
const lastupdate = dNow.toISOString()
const lastupdateLocale = dNow.toLocaleString()
if (oGa === undefined) {
node.exposedGAs.push({ address: msg.knx.destination, devicename: undefined, dpt: msg.knx.dpt, payload: msg.payload, lastupdate, lastupdateLocale })
} else {
oGa.dpt = msg.knx.dpt
oGa.payload = msg.payload
oGa.lastupdate = lastupdate
oGa.lastupdateLocale = lastupdateLocale
}
// Save into the global Context
try {
node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
} catch (error) {
console.log(error)
}
oGa = null // 21/03/2022
} else {
node.exposedGAs = []
node.context().global.set(node.name + '_READ', node.exposedGAs, node.contextStorage)
}
}
node.on('input', function (msg) {
})
node.on('close', function (done) {
if (node.timerExposedGAs !== null) clearTimeout(node.timerExposedGAs)
node.exposedGAs = []
if (node.serverKNX) {
node.serverKNX.removeClient(node)
}
done()
})
// On each deploy, unsubscribe+resubscribe
if (node.serverKNX) {
node.serverKNX.removeClient(node)
node.serverKNX.addClient(node)
}
}
RED.nodes.registerType('knxUltimateGlobalContext', knxUltimateGlobalContext)
}