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.
212 lines (192 loc) • 8.48 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;
if (node.serverKNX === undefined) {
node.status({ 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) { node.status({ 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();
node.status({ 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
let PIN1 = node.createPayloadPIN1();
let PIN2 = node.createPayloadPIN2();
let 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);
};