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.
224 lines (205 loc) • 8.77 kB
JavaScript
const KNXAddress = require('knxultimate').KNXAddress
const _ = require('lodash')
module.exports = function (RED) {
function knxUltimateViewer (config) {
RED.nodes.createNode(this, config)
const node = this
node.serverKNX = RED.nodes.getNode(config.server) || undefined
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)
}
if (node.serverKNX === undefined) {
updateStatus({ fill: 'red', shape: 'dot', text: '[THE GATEWAY NODE HAS BEEN DISABLED]' })
return
}
node.topic = node.name
node.name = config.name === undefined ? 'KNXViewer' : 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 = 2
node.timerPIN3 = null
node.exposedGAs = []
// 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()
updateStatus({ fill, shape, text: GA + ' ' + payload + ' ' + text + ' (' + dDate.getDate() + ', ' + dDate.toLocaleTimeString() + ')' })
} catch (error) { /* empty */ }
}
// This function is called by the knx-ultimate config node, to output a msg.payload.
node.handleSend = msg => {
try {
var oGa = node.exposedGAs.find(ga => ga.address === msg.knx.destination)
} catch (error) {
}
const sDeviceName = msg.devicename === node.name ? 'Import ETS file to view the group address name' : msg.devicename // The ETS file hasn't been imported
const sAddressRAW = KNXAddress.createFromString(msg.knx.destination, KNXAddress.TYPE_GROUP).get() // Address as number (for ordering later)
if (oGa === undefined) {
node.exposedGAs.push({ address: msg.knx.destination, addressRAW: sAddressRAW, dpt: msg.knx.dpt, payload: msg.payload, devicename: sDeviceName, lastupdate: new Date(), rawPayload: 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?', payloadmeasureunit: (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '') })
} else {
oGa.dpt = msg.knx.dpt
oGa.payload = msg.payload
oGa.devicename = sDeviceName
oGa.lastupdate = new Date()
oGa.rawPayload = 'HEX Raw: ' + msg.knx.rawValue.toString('hex') || '?'
oGa.payloadmeasureunit = (msg.payloadmeasureunit !== 'unknown' ? ' ' + msg.payloadmeasureunit : '')
}
// Output the payload
const PIN1 = node.createPayloadPIN1()
const PIN2 = node.createPayloadPIN2()
const PIN3 = node.createPayloadPIN3()
node.send([PIN1, PIN2, PIN3])
}
node.createPayloadPIN1 = () => {
const aSorted = node.exposedGAs.sort((a, b) => {
if (a.addressRAW !== undefined && b.addressRAW !== undefined) {
return a.addressRAW > b.addressRAW ? 1 : -1
} else {
return a.addressRAW !== undefined ? 1 : -1
}
})
let sPayload = ''
const sHead = `<div class="main"><table><caption>Current received KNX Group address values</caption>
<thead>
<tr>
<th> GA </th>
<th> Value </th>
<th> DPT </th>
<th> Last updated </th>
<th> Group Address Name </th>
</tr>
</thead>
<tbody>`
const sFooter = `</tbody><tfoot>
<tr>
<th scope="row">Count</th>
<td>` + aSorted.length + `</td>
</tr>
</tfoot>
</table></div>`
try {
for (let index = 0; index < aSorted.length; index++) {
const element = aSorted[index]
sPayload += '<tr><td>' + element.address + '</td>'
if (typeof element.payload === 'boolean' && element.payload === true) {
sPayload += '<td><b><font color=green>True</font></b></td>'
} else if (typeof element.payload === 'boolean' && element.payload === false) {
sPayload += '<td><font color=red>False</font></td>'
} else if (typeof element.payload === 'object' && !isNaN(Date.parse(element.payload))) {
// The payload is a datetime
sPayload += '<td>' + element.payload.toLocaleString() + '</td>'
} else if (typeof element.payload === 'object') {
// Is maybe a JSON?
try {
// sPayload += '<td>' + JSON.stringify(element.payload) + '</td>'
sPayload += '<td><i>' + element.rawPayload + '</i></td>'
} catch (error) {
sPayload += '<td>' + element.payload + '</td>'
}
} else {
sPayload += '<td>' + element.payload + element.payloadmeasureunit + '</td>'
}
sPayload += '<td>' + element.dpt + '</td>'
sPayload += '<td>' + element.lastupdate.toLocaleString() + '</td>'
sPayload += '<td><font style="font-size: smaller;">' + element.devicename + '</font></td></tr>'
}
} catch (error) {
}
return { topic: node.name, payload: sHead + sPayload + sFooter }
}
node.createPayloadPIN2 = () => {
return { topic: node.name, payload: node.exposedGAs }
}
node.createPayloadPIN3 = () => {
// Object containing the telegram in the queue
// node.writeQueueAdd({
// grpaddr: _oClient.topic,
// payload: "",
// dpt: "",
// outputtype: "read",
// nodecallerid: _oClient.id,
// });
let sHead = ''
let sFooter = ''
let sPayload = ''
try {
const aItems = _.clone(node.serverKNX.knxConnection.commandQueue)
if (aItems === undefined) return
sHead = `<div class="main"><table><caption>Queue of outgoing telegrams to the KNX BUS. The more the count,</br>the more congested is the KNX BUS.</caption>
<thead>
<tr>
<th> Channel ID </th>
<th> Sequence counter </th>
<th> Type of packet</th>
<th> Status </th>
</tr>
</thead>
<tbody>`
sFooter = `</tbody><tfoot>
<tr>
<th scope="row">Count</th>
<td>` + aItems.length + `</td>
</tr>
</tfoot>
</table></div>`
for (let index = 0; index < aItems.length; index++) {
const element = aItems[index]
sPayload += '<tr><td>' + element.knxPacket.channelID + '</td>'
sPayload += '<td><b><font color=green>' + element.knxPacket.seqCounter + '</font></b></td>'
sPayload += '<td>' + element.knxPacket.type + '</td>'
sPayload += '<td>' + element.knxPacket.status + '</td></tr>'
}
} catch (error) {
}
return { topic: node.name, payload: sHead + sPayload + sFooter }
}
// if (timerPIN3 !== null) clearInterval(timerPIN3);
// timerPIN3 = setInterval(() => {
// let PIN3 = node.createPayloadPIN3();
// node.send([null, null, PIN3]);
// }, 200);
node.on('input', function (msg) {
})
node.on('close', function (done) {
if (timerPIN3 !== null) clearInterval(timerPIN3)
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('knxUltimateViewer', knxUltimateViewer)
}