prisme-flow
Version:
prisme platform flow engine
343 lines (319 loc) • 15.4 kB
JavaScript
/**
* Created by prisme.io on 09/06/2017.
*/
var util = require('util'),
KnxConnectionTunneling = require('knx.js').KnxConnectionTunneling
module.exports = function (RED) {
var knxjs = require('knx.js')
/**
* ====== Knx-CONTROLLER ================
* Holds configuration for knxjs host+port,
* initializes new knxjs connections
* =======================================
*/
function KnxControllerNode (config) {
RED.nodes.createNode(this, config)
this.name = config.name
this.host = config.host
config.port = parseInt(config.port)
this.port = config.port
this.mode = config.mode
this.knxjsconn = null
var node = this
// node.log("new KnxControllerNode, config: " + util.inspect(config));
/**
* Initialize an knxjs socket, calling the handler function
* when successfully connected, passing it the knxjs connection
*/
this.initializeKnxConnection = function (handler) {
if (node.knxjsconn) {
node.log('already connected to knxjs server at ' + config.host + ':' + config.port + ' in mode[' + config.mode + ']')
if (handler && (typeof handler === 'function')) { handler(node.knxjsconn) }
return node.knxjsconn
}
node.log('connecting to knxjs server at ' + config.host + ':' + config.port + ' in mode[' + config.mode + ']')
node.knxjsconn = null
if (config.mode === 'tunnel/unicast') {
node.knxjsconn = new KnxConnectionTunneling(config.host, config.port, '0.0.0.0', 0)
node.knxjsconn.Connect(function (err) {
if (err) { node.warn('cannot connect to knxjs server at ' + config.host + ':' + config.port + ' in mode[' + config.mode + '], cause: ' + util.inspect(err)) } else { node.log('Knx: successfully connected to ' + config.host + ':' + config.port + ' in mode[' + config.mode + ']') }
handler(node.knxjsconn)
}
)
} else { throw 'Unsupported mode[' + config.mode + ']' }
return node.knxjsconn
}
this.on('close', function () {
node.log('disconnecting from knxjs server at ' + config.host + ':' + config.port + ' in mode[' + config.mode + ']')
node.knxjsconn && node.knxjsconn.Disconnect && node.knxjsconn.Disconnect()
})
}
RED.nodes.registerType('knx-controller', KnxControllerNode)
/**
* ====== Knx-OUT =======================
* Sends outgoing KNX telegrams from
* messages received via node-red flows
* =======================================
*/
function KnxOut (config) {
RED.nodes.createNode(this, config)
this.name = config.name
this.ctrl = RED.nodes.getNode(config.controller)
var node = this
// node.log('new Knx-OUT, config: ' + util.inspect(config));
this.on('input', function (msg) {
node.log('knxout.onInput, msg[' + util.inspect(msg) + ']')
if (!(msg && msg.hasOwnProperty('payload'))) return
var payload
if (typeof (msg.payload) === 'object') {
payload = msg.payload
} else if (typeof (msg.payload) === 'string') {
payload = JSON.parse(msg.payload)
}
if (payload == null) {
node.log('knxout.onInput: illegal msg.payload!')
return
}
if (payload.dstgad == null) {
node.log('knxout.onInput: illegal msg.payload.dstgad!')
return
}
var action
switch (true) {
case /read/.test(msg.topic):
action = 'read'
break
case /respon/.test(msg.topic):
action = 'response'
break
default:
action = 'write'
}
if (payload.value == null) { action = 'read' }
this.groupAddrSend(payload.dstgad, payload.value, payload.dpt, action, function (err) {
if (err) {
node.error('groupAddrSend error: ' + util.inspect(err))
}
})
})
this.on('close', function () {
node.log('knxOut.close')
})
node.status({fill: 'yellow', shape: 'dot', text: 'inactive'})
function nodeStatusConnected () {
node.status({fill: 'green', shape: 'dot', text: 'connected'})
}
function nodeStatusDisconnected () {
node.status({fill: 'red', shape: 'dot', text: 'disconnected'})
}
function nodeStatusConnecting () {
node.status({fill: 'green', shape: 'ring', text: 'connecting'})
}
/**
* send a group write telegram to a group address
* Initializes new knxjs connection per request
* dstgad: dest group address '1/2/34'
* dpt: DataPointType eg. '1' for boolean
* value: the value to write
* callback:
*
* Usage:
* groupAddrSend({ host: 'localhost', port: 6720}, '1/2/34', 1, 1, function(err) {
* if(err) console.error(err);
* });
*
* Datatypes:
*
KNX/EIB Function Information length EIS DPT Value
Switch 1 Bit EIS 1 DPT 1 0,1
Dimming (Position, Control, Value) 1 Bit, 4 Bit, 8 Bit EIS 2 DPT 3 [0,0]...[1,7]
Time 3 Byte EIS 3 DPT 10
Date 3 Byte EIS 4 DPT 11
Floating point 2 Byte EIS 5 DPT 9 -671088,64 - 670760,96
8-bit unsigned value 1 Byte EIS 6 DPT 5 0...255
8-bit unsigned value 1 Byte DPT 5.001 DPT 5.001 0...100
Blinds / Roller shutter 1 Bit EIS 7 DPT 1 0,1
Priority 2 Bit EIS 8 DPT 2 [0,0]...[1,1]
IEEE Floating point 4 Byte EIS 9 DPT 14 4-Octet Float Value IEEE 754
16-bit unsigned value 2 Byte EIS 10 DPT 7 0...65535
16-bit signed value 2 Byte DPT 8 DPT 8 -32768...32767
32-bit unsigned value 4 Byte EIS 11 DPT 12 0...4294967295
32-bit signed value 4 Byte DPT 13 DPT 13 -2147483648...2147483647
Access control 1 Byte EIS 12 DPT 15
ASCII character 1 Byte EIS 13 DPT 4
8859_1 character 1 Byte DPT 4.002 DPT 4.002
8-bit signed value 1 Byte EIS 14 DPT 6 -128...127
14 character ASCII 14 Byte EIS 15 DPT 16
14 character 8859_1 14 Byte DPT 16.001 DPT 16.001
Scene 1 Byte DPT 17 DPT 17 0...63
HVAC 1 Byte DPT 20 DPT 20 0..255
Unlimited string 8859_1 . DPT 24 DPT 24
List 3-byte value 3 Byte DPT 232 DPT 232 RGB[0,0,0]...[255,255,255]
*
*/
this.groupAddrSend = function (dstgad, value, dpt, action, callback) {
dpt = dpt ? dpt.toString() : '1'
if (action !== 'write' && action !== 'read') { throw 'Unsupported action[' + action + '] inside of groupAddrSend' }
node.log('groupAddrSend action[' + action + '] dstgad:' + dstgad + ', value:' + value + ', dpt:' + dpt)
if (action === 'write') {
switch (dpt) {
case '1': // Switch
value = (value.toString() === 'true' || value.toString() === '1')
break
case '3': // Dimmer, control bit + 3 bit value
if (typeof (value.c) !== 'undefined' && value.c !== null &&
typeof (value.amount) !== 'undefined' && value.amount !== null) {
if (value.amount <= 7) {
value = ((value.c.toString() === 'true' || value.c.toString() === '1') << 3) |
parseInt(value.amount) & 7
buf = new Buffer(1)
buf[0] = value & 15
value = buf
} else {
throw 'Value step amount too big for DPT 3'
}
} else if (!isNaN(parseInt(value))) {
buf = new Buffer(1)
buf[0] = parseInt(value) & 15
value = buf
} else if (!Buffer.isBuffer(value)) {
throw 'Value is incorrect for DPT 3 (type of value should be Buffer or Value = 0..15 or Object with fields "value.c" = 0..1 and "value.amount" = 0..7)'
}
break
case '9': // Floating point
value = parseFloat(value)
buf = new Buffer(4)
buf.writeFloatLE(value, 0)
value = buf
break
case '5': // 8-bit unsigned value 1 Byte EIS 6 DPT 5 0...255
case '5.001': // 8-bit unsigned value 1 Byte DPT 5.001 DPT 5.001 0...100
case '6': // 8-bit signed value 1 Byte EIS 14 DPT 6 -128...127
case '7': // 16-bit unsigned value 2 Byte EIS 10 DPT 7 0...65535
case '8': // 16-bit signed value 2 Byte DPT 8 DPT 8 -32768...32767
case '12': // 32-bit unsigned value 4 Byte EIS 11 DPT 12 0...4294967295
case '13': // 32-bit signed value 4 Byte DPT 13 DPT 13 -2147483648...2147483647
case '17': // Scene 1 Byte DPT 17 DPT 17 0...63
case '20': // HVAC 1 Byte DPT 20 DPT 20 0..255
value = parseInt(value)
buf = new Buffer(2)
if (value <= 255) {
buf[0] = 0x00
buf[1] = value & 255
value = buf
} else if (value <= 65535) {
buf[0] = value & 255
buf[1] = (value >> 8) & 255
value = buf
}
break
default:
throw 'Unsupported dpt[' + dpt + '] inside groupAddrSend of knx node'
}
}
if (!this.ctrl) { node.error('Cannot proceed groupAddrSend, cause no controller-node specified!') } else
// init a new one-off connection from the effectively singleton KnxController
// there seems to be no way to reuse the outgoing conn in adreek/node-knxjs
{
this.ctrl.initializeKnxConnection(function (connection) {
if (connection.connected) { nodeStatusConnected() } else { nodeStatusDisconnected() }
connection.removeListener('connecting', nodeStatusConnecting)
connection.on('connecting', nodeStatusConnecting)
connection.removeListener('connected', nodeStatusConnected)
connection.on('connected', nodeStatusConnected)
connection.removeListener('disconnected', nodeStatusDisconnected)
connection.on('disconnected', nodeStatusDisconnected)
try {
node.log('sendAPDU: ' + util.inspect(value))
if (action === 'read') { connection.RequestStatus(dstgad.toString()) } else if (action === 'write') { connection.Action(dstgad.toString(), value, null) }
callback && callback()
} catch (err) {
node.error('error calling groupAddrSend: ' + err)
callback(err)
}
})
}
}
}
//
RED.nodes.registerType('knx-out', KnxOut)
/**
* ====== KNX-IN ========================
* Handles incoming KNX events, injecting
* json into node-red flows
* =======================================
*/
function KnxIn (config) {
RED.nodes.createNode(this, config)
this.name = config.name
this.connection = null
var node = this
// node.log('new KNX-IN, config: ' + util.inspect(config));
var knxjsController = RED.nodes.getNode(config.controller)
/* ===== Node-Red events ===== */
this.on('input', function (msg) {
if (msg != null) {
}
})
var node = this
this.on('close', function () {
if (node.receiveEvent && node.connection) { node.connection.removeListener('event', node.receiveEvent) }
if (node.receiveStatus && node.connection) { node.connection.removeListener('status', node.receiveStatus) }
})
function nodeStatusConnecting () {
node.status({fill: 'green', shape: 'ring', text: 'connecting'})
}
function nodeStatusConnected () {
node.status({fill: 'green', shape: 'dot', text: 'connected'})
}
function nodeStatusDisconnected () {
node.status({fill: 'red', shape: 'dot', text: 'disconnected'})
}
node.receiveEvent = function (gad, data, datagram) {
node.log('knx event gad[' + gad + ']data[' + data.toString('hex') + ']')
node.send({
topic: 'knx:event',
payload: {
'srcphy': datagram.source_address,
'dstgad': gad,
'dpt': 'no_dpt',
'value': data.toString(),
'type': 'event'
}
})
}
node.receiveStatus = function (gad, data, datagram) {
node.log('knx status gad[' + gad + ']data[' + data.toString('hex') + ']')
node.send({
topic: 'knx:status',
payload: {
'srcphy': datagram.source_address,
'dstgad': gad,
'dpt': 'no_dpt',
'value': data.toString(),
'type': 'status'
}
})
}
// this.on("error", function(msg) {});
/* ===== knxjs events ===== */
// initialize incoming KNX event socket (openGroupSocket)
// there's only one connection for knxjs-in:
knxjsController && knxjsController.initializeKnxConnection(function (connection) {
node.connection = connection
node.connection.removeListener('event', node.receiveEvent)
node.connection.on('event', node.receiveEvent)
node.connection.removeListener('status', node.receiveStatus)
node.connection.on('status', node.receiveStatus)
if (node.connection.connected) { nodeStatusConnected() } else { nodeStatusDisconnected() }
node.connection.removeListener('connecting', nodeStatusConnecting)
node.connection.on('connecting', nodeStatusConnecting)
node.connection.removeListener('connected', nodeStatusConnected)
node.connection.on('connected', nodeStatusConnected)
node.connection.removeListener('disconnected', nodeStatusDisconnected)
node.connection.on('disconnected', nodeStatusDisconnected)
})
}
//
RED.nodes.registerType('knx-in', KnxIn)
}