knx.js
Version:
KNXnetIP (KNX over IP) deriver for nodejs.
348 lines (305 loc) • 10.6 kB
JavaScript
/**
* Created by aborovsky on 24.08.2015.
*/
var CONNECT_TIMEOUT = 5000;
var KnxConnection = require('./KnxConnection');
var KnxReceiverTunneling = require('./KnxReceiverTunneling');
var KnxSenderTunneling = require('./KnxSenderTunneling');
var ConnectionErrorException = require('./ConnectionErrorException');
var util = require('util');
var dgram = require('dgram');
var Promise = require('promise');
/// <summary>
/// Initializes a new KNX tunneling connection with provided values. Make sure the local system allows
/// UDP messages to the localIpAddress and localPort provided
/// </summary>
/// <param name="remoteIpAddress">Remote gateway IP address</param>
/// <param name="remotePort">Remote gateway port</param>
/// <param name="localIpAddress">Local IP address to bind to</param>
/// <param name="localPort">Local port to bind to</param>
function KnxConnectionTunneling(remoteIpAddress, remotePort, localIpAddress, localPort) {
KnxConnectionTunneling.super_.call(this, remoteIpAddress, remotePort, localIpAddress, localPort);
this._localEndpoint = null; //IPEndPoint {host: host, port: port}
this._stateRequestTimer = null; //Timer
this._udpClient = null; //UdpClient
this._sequenceNumber = null; //byte
this._localEndpoint = {
host: localIpAddress,
port: localPort,
toBytes: function () {
if (!this.host || this.host === '')
throw 'Cannot proceed toString for localIpAddress with empy host'
if (localIpAddress.indexOf('.') === -1 || this.host.split('.').length < 4)
throw 'Cannot proceed toString for localIpAddress with host[' + this.host + '], it should contain ip address'
var result = new Buffer(4);
var arr = localIpAddress.split('.');
result[0] = parseInt(arr[0]) & 255;
result[1] = parseInt(arr[1]) & 255;
result[2] = parseInt(arr[2]) & 255;
result[3] = parseInt(arr[3]) & 255;
return result;
}
};
this.ChannelId = 0x00;
}
util.inherits(KnxConnectionTunneling, KnxConnection);
KnxConnectionTunneling.prototype.GenerateSequenceNumber = function () {
return this._sequenceNumber++;
}
KnxConnectionTunneling.prototype.RevertSingleSequenceNumber = function () {
this._sequenceNumber--;
}
KnxConnectionTunneling.prototype.ResetSequenceNumber = function () {
this._sequenceNumber = 0x00;
}
KnxConnectionTunneling.prototype.ClearReconnectTimeout = function () {
var that = this;
if (that.reConnectTimeout) {
clearTimeout(that.reConnectTimeout);
delete that.reConnectTimeout;
}
}
KnxConnectionTunneling.prototype.ClearConnectTimeout = function () {
var that = this;
if (that.connectTimeout) {
clearTimeout(that.connectTimeout);
delete that.connectTimeout;
}
}
/// <summary>
/// Start the connection
/// </summary>
KnxConnectionTunneling.prototype.Connect = function (callback) {
var that = this;
if (this.connected && this._udpClient) {
callback && callback();
return true;
}
this.connectTimeout = setTimeout(function () {
that.removeListener('connected', that.ClearConnectTimeout);
that.Disconnect(function () {
if (that.debug)
console.log('Error connecting: timeout');
callback && callback({msg: 'Error connecting: timeout', reason: 'CONNECTTIMEOUT'});
that.ClearReconnectTimeout();
this.reConnectTimeout = setTimeout(function () {
if (that.debug)
console.log('reconnecting');
that.Connect(callback);
}, 3 * CONNECT_TIMEOUT);
});
}, CONNECT_TIMEOUT);
this.once('connected', that.ClearConnectTimeout);
if (callback) {
this.removeListener('connected', callback);
this.once('connected', callback);
}
try {
if (this._udpClient != null) {
try {
this._udpClient.close();
//this._udpClient.Client.Dispose();
}
catch (e) {
// ignore
}
}
this._udpClient = dgram.createSocket("udp4");//new UdpClient(_localEndpoint)
}
catch (e) {
throw new ConnectionErrorException(ConnectionConfiguration, ex);
}
if (this.knxReceiver == null || this.knxSender == null) {
this.knxReceiver = new KnxReceiverTunneling(this, this._udpClient, this._localEndpoint);
this.knxSender = new KnxSenderTunneling(this, this._udpClient, this.RemoteEndpoint);
}
else {
this.knxReceiver.SetClient(this._udpClient);
this.knxSender.SetClient(this._udpClient);
}
var that = this;
new Promise(function (fulfill, reject) {
that.knxReceiver.Start(fulfill);
})
.
then(function () {
that.InitializeStateRequest();
})
.then(function () {
that.ConnectRequest();
})
.then(function () {
that.emit('connect');
that.emit('connecting');
});
}
/// <summary>
/// Stop the connection
/// </summary>
KnxConnectionTunneling.prototype.Disconnect = function (callback) {
var that = this;
that.ClearConnectTimeout();
that.ClearReconnectTimeout();
if (callback)
that.once('disconnect', callback);
try {
this.TerminateStateRequest();
new Promise(function (fulfill, reject) {
that.DisconnectRequest(fulfill);
})
.then(function () {
that.knxReceiver.Stop();
that._udpClient.close();
that.connected = false;
that.emit('close');
that.emit('disconnect');
that.emit('disconnected');
})
}
catch (e) {
that.emit('disconnect', e);
}
}
function delay(time) {
return new Promise(function (fulfill, reject) {
setTimeout(fulfill, time);
});
}
function timeout(func, time, timeoutFunc) {
var success = null;
var succPromise = new Promise(function (fulfill, reject) {
func(function () {
if (success === null) {
fulfill();
success = true;
}
else
reject();
});
});
var timeoutPromise = delay(time);
timeoutPromise.then(function () {
if (!success)
return timeoutFunc && timeoutFunc();
});
return Promise.race([succPromise, timeoutPromise]);
}
KnxConnectionTunneling.prototype.InitializeStateRequest = function () {
var self = this;
this._stateRequestTimer = setInterval(function () {
timeout(function (fulfill) {
self.removeAllListeners('alive');
self.StateRequest(function (err) {
if (!err)
self.once('alive', fulfill);
});
}, 2 * CONNECT_TIMEOUT, function () {
if (self.debug)
console.log('connection stale, so disconnect and then try to reconnect again');
new Promise(function (fulfill) {
self.Disconnect(fulfill);
}).then(function () {
self.Connect();
});
});
}, 60000); // same time as ETS with group monitor open
}
KnxConnectionTunneling.prototype.TerminateStateRequest = function () {
if (this._stateRequestTimer == null)
return;
clearTimeout(this._stateRequestTimer);
}
// TODO: I wonder if we can extract all these types of requests
KnxConnectionTunneling.prototype.ConnectRequest = function (callback) {
// HEADER
var datagram = new Buffer(26);
datagram[0] = 0x06;
datagram[1] = 0x10;
datagram[2] = 0x02;
datagram[3] = 0x05;
datagram[4] = 0x00;
datagram[5] = 0x1A;
datagram[6] = 0x08;
datagram[7] = 0x01;
datagram[8] = this._localEndpoint.toBytes()[0];
datagram[9] = this._localEndpoint.toBytes()[1];
datagram[10] = this._localEndpoint.toBytes()[2];
datagram[11] = this._localEndpoint.toBytes()[3];
datagram[12] = (this._localEndpoint.port >> 8) & 255;
datagram[13] = this._localEndpoint.port & 255;
datagram[14] = 0x08;
datagram[15] = 0x01;
datagram[16] = this._localEndpoint.toBytes()[0];
datagram[17] = this._localEndpoint.toBytes()[1];
datagram[18] = this._localEndpoint.toBytes()[2];
datagram[19] = this._localEndpoint.toBytes()[3];
datagram[20] = (this._localEndpoint.port >> 8) & 255;
datagram[21] = this._localEndpoint.port & 255;
datagram[22] = 0x04;
datagram[23] = 0x04;
datagram[24] = 0x02;
datagram[25] = 0x00;
try {
this.knxSender.SendDataSingle(datagram, callback);
}
catch (e) {
callback && callback();
}
}
KnxConnectionTunneling.prototype.StateRequest = function (callback) {
// HEADER
var datagram = new Buffer(16);
datagram[0] = 0x06;
datagram[1] = 0x10;
datagram[2] = 0x02;
datagram[3] = 0x07;
datagram[4] = 0x00;
datagram[5] = 0x10;
datagram[5] = this.ChannelId;
datagram[7] = 0x00;
datagram[8] = 0x08;
datagram[9] = 0x01;
datagram[10] = this._localEndpoint.toBytes()[0];
datagram[11] = this._localEndpoint.toBytes()[1];
datagram[12] = this._localEndpoint.toBytes()[2];
datagram[13] = this._localEndpoint.toBytes()[3];
datagram[14] = (this._localEndpoint.port >> 8) & 255;
datagram[15] = this._localEndpoint.port & 255;
try {
this.knxSender.SendData(datagram, callback);
}
catch (e) {
callback(e)
}
}
KnxConnectionTunneling.prototype.DisconnectRequest = function (callback) {
if(!this.connected) {
callback && callback();
return false;
}
// HEADER
var datagram = new Buffer(16);
datagram[0] = 0x06;
datagram[1] = 0x10;
datagram[2] = 0x02;
datagram[3] = 0x09;
datagram[4] = 0x00;
datagram[5] = 0x10;
datagram[6] = this.ChannelId;
datagram[7] = 0x00;
datagram[8] = 0x08;
datagram[9] = 0x01;
datagram[10] = this._localEndpoint.toBytes()[0];
datagram[11] = this._localEndpoint.toBytes()[1];
datagram[12] = this._localEndpoint.toBytes()[2];
datagram[13] = this._localEndpoint.toBytes()[3];
datagram[14] = (this._localEndpoint.port >> 8) & 255;
datagram[15] = this._localEndpoint.port & 255;
try {
this.knxSender.SendData(datagram, callback);
}
catch (e) {
callback(e)
}
}
module.exports = KnxConnectionTunneling;