node-hca
Version:
Node.js client for HCA
278 lines (215 loc) • 8.91 kB
JavaScript
;
var util = require('util'),
EventEmitter = require('events').EventEmitter,
Connection = require('./Connection/Connection'),
DesignManager = require('./Design/DesignManager'),
DisplayManager = require('./Display/DisplayManager'),
message = require('./Messaging/message');
(function () {
var self,
reconnectTimer,
connection,
passcode,
timestamp,
handshake = 'HCA000E001000000';
var HcaClient = function (host, port, clientName, options) {
EventEmitter.call(this);
self = this;
this.host = host;
this.port = port;
this.clientId = null;
this.clientName = clientName;
this.homeModes = null;
this.homeModeIndex = null;
this.displayManager = null;
this.designManager = null;
this.version = null; // reserved for future use
this.serverProtocol = null;
this.serverVersion = null;
this.reconnectAttempts = 0;
this.topics = {
clientReady: 'HcaClient:Ready'
};
// https://github.com/joewalnes/reconnecting-websocket/blob/master/reconnecting-websocket.js
var settings = {
reconnectInterval: 1000,
maxReconnectInterval: 30000,
reconnectDecay: 1.5,
maxReconnectAttempts: null,
};
if (!options) {
options = {};
}
// Overwrite and define settings with options if they exist.
for (var key in settings) {
if (typeof options[key] !== 'undefined') {
this[key] = options[key];
} else {
this[key] = settings[key];
}
}
};
util.inherits(HcaClient, EventEmitter);
HcaClient.prototype.connect = function (password) {
passcode = password;
connection = new Connection(this.host, this.port);
connection.once(connection.topics.connectionOpened, onConnect);
connection.on(connection.topics.connectionError, onError);
connection.connect(this.host, this.port);
};
HcaClient.prototype.disconnect = function () {
var params = ["HCAApp", "Terminate"];
connection.send(params);
connection.disconnect();
}
HcaClient.prototype.send = function (params) {
connection.send(params);
};
HcaClient.prototype.refreshState = function () {
var params = ['HCAApp', 'RefreshState', timestamp[1]];
connection.send(params);
};
// https://github.com/joewalnes/reconnecting-websocket/blob/master/reconnecting-websocket.js
var reconnect = function () {
self.reconnectAttempts++;
var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts);
if (timeout > self.maxReconnectInterval)
timeout = self.maxReconnectInterval;
if (self.maxReconnectAttempts && self.reconnectAttempts > self.maxReconnectAttempts) {
util.log('[CON] Reconnect attempts exceeded. Giving up.');
return;
} else if (self.maxReconnectAttempts) {
util.log('[CON] Reconnecting in %s ms (Attempt %s/%s)...', timeout, self.reconnectAttempts, self.maxReconnectAttempts);
} else {
util.log('[CON] Reconnecting in %s ms (Attempt %s)...', timeout, self.reconnectAttempts);
}
reconnectTimer = setTimeout(function () {
connection.connect();
}, timeout);
};
var onConnect = function (e) {
clearTimeout(reconnectTimer);
self.reconnectAttempts = 0;
// Remove 'onDisconnect' listener in case of a reconnect.
connection.removeListener(connection.topics.connectionClosed, onDisconnect);
connection.once(connection.topics.connectionClosed, onDisconnect);
connection.once(connection.topics.handshakeCompleted, onHandshakeCompleted);
connection.on(connection.topics.messageSent, onMessageSent);
connection.on(connection.topics.messageReceived, onMessageReceived);
self.emit(connection.topics.connectionOpened, e);
connection.send(handshake);
};
var onDisconnect = function (e) {
clearTimeout(reconnectTimer);
var wasClean = e.wasClean;
// Clean up listeners.
connection.removeListener(connection.topics.connectionOpened, onConnect);
connection.removeListener(connection.topics.connectionClosed, onDisconnect);
connection.removeListener(connection.topics.messageSent, onMessageSent);
connection.removeListener(connection.topics.messageReceived, onMessageReceived);
self.emit(connection.topics.connectionClosed, e);
if (!wasClean) { // Await reconnect attemp.
connection.once(connection.topics.connectionOpened, onConnect);
connection.once(connection.topics.connectionClosed, onDisconnect);
reconnect(e);
} else {
connection.removeListener(connection.topics.connectionError, onError);
}
};
var onError = function (e) {
if (e.data)
util.log("[ERR] " + e.data);
};
var onMessageSent = function (params) {
self.emit(connection.topics.messageSent, params);
}
var onMessageReceived = function (hcaResponse) {
// Keep track of timestamp.
if (hcaResponse.command === 'Update') {
timestamp = hcaResponse.data[hcaResponse.data.length - 1];
}
self.emit(connection.topics.messageReceived, hcaResponse);
self.emit(hcaResponse.command, hcaResponse);
};
var onHandshakeCompleted = function (e) {
var returnCode = parseInt(e.data[3]);
var clientId = parseInt(e.data[4]);
var passwordRequired = parseInt(e.data[5]) > 0;
var serverProtocol = e.data[6];
var serverMajor = parseInt(e.data.substring(7, 10));
var serverMinor = parseInt(e.data.substring(10, 13));
var serverBuild = parseInt(e.data.substring(13, 16));
self.clientId = clientId;
self.serverProtocol = serverProtocol;
self.serverVersion = `${serverMajor}.${serverMinor}.${serverBuild}`;
if (returnCode !== 0) {
util.log('[ERR] Handshake failed (Return Code: %d).', returnCode);
connection.disconnect();
return;
}
if (passwordRequired) {
if (passcode === 'undefined' || passcode === undefined) {
util.log('[ERR] Unable to authorize. A passcode is required.');
connection.disconnect();
return;
}
var params = ['HCAObject', 'HCA.SetPassword', 4, passcode];
connection.once('HCA.SetPassword', onSetPassword);
connection.send(params);
return;
}
getTimeStamp();
}
var onSetPassword = function (hcaResponse) {
if (hcaResponse.code !== 0) {
util.log('[ERR] Invalid password.');
connection.disconnect();
return;
}
getTimeStamp();
};
var getTimeStamp = function () {
var params = ['HCAApp', 'TimeStamp'];
connection.once('TimeStamp', function (hcaResponse) {
timestamp = hcaResponse.data;
getDesign();
});
connection.send(params);
};
var getDesign = function () {
self.designManager = new DesignManager(connection);
self.designManager.once(self.designManager.topics.designReceived, onDesignReceived);
self.designManager.init();
};
var onDesignReceived = function (e) {
self.displayManager = new DisplayManager(connection);
self.displayManager.once(self.displayManager.topics.displaysReceived, onDisplaysReceived);
self.displayManager.init();
};
var onDisplaysReceived = function (e) {
var params = ['HCAApp', 'GetHomeModeNames'];
connection.once('GetHomeModeNames', onHomeModeNames);
connection.send(params);
};
var onHomeModeNames = function (hcaResponse) {
self.homeModes = hcaResponse.data;
var params = ['HCAApp', 'GetHomeMode'];
connection.once('GetHomeMode', onHomeMode);
connection.send(params);
};
var onHomeMode = function (hcaResponse) {
self.homeMode = parseInt(hcaResponse.data);
setClientOptions();
};
var setClientOptions = function () {
var params = ['HCAApp', 'SetClientOptions', '7', self.clientName];
connection.once('SetClientOptions', onSetClientOptions);
connection.send(params);
};
var onSetClientOptions = function (hcaResponse) {
self.refreshState();
// Client has completely initialized, and is ready for all requests.
self.emit(self.topics.clientReady);
};
module.exports = HcaClient;
})();