UNPKG

node-hca

Version:

Node.js client for HCA

278 lines (215 loc) 8.91 kB
'use strict'; 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; })();