UNPKG

harmonyhubjs-client

Version:

harmonyhubjs-client is a Node.JS library which allows you to interact with your Logitech Harmony Hub.

257 lines (217 loc) 7.35 kB
var debug = require('debug')('harmonyhubjs:client:harmonyclient') var Q = require('q') var xmppUtil = require('./util') var util = require('util') var EventEmitter = require('events').EventEmitter /** * Creates a new HarmonyClient using the given xmppClient to communication. * * @param xmppClient * @constructor */ function HarmonyClient (xmppClient) { debug('create new harmony client') var self = this self._xmppClient = xmppClient self._responseHandlerQueue = [] function handleStanza (stanza) { debug('handleStanza(' + stanza.toString() + ')') // Check for state digest: var event = stanza.getChild('event') if (event && event.attr('type') === 'connect.stateDigest?notify') { onStateDigest.call(self, JSON.parse(event.getText())) } // Check for queued response handlers: self._responseHandlerQueue.forEach(function (responseHandler, index, array) { if (responseHandler.canHandleStanza(stanza)) { debug('received response stanza for queued response handler') var response = stanza.getChildText('oa') var decodedResponse if (responseHandler.responseType === 'json') { decodedResponse = JSON.parse(response) } else { decodedResponse = xmppUtil.decodeColonSeparatedResponse(response) } responseHandler.deferred.resolve(decodedResponse) array.splice(index, 1) } }) } xmppClient.on('stanza', handleStanza.bind(self)) xmppClient.on('error', function (error) { debug('XMPP Error: ' + error.message) }) EventEmitter.call(this) } util.inherits(HarmonyClient, EventEmitter) function onStateDigest (stateDigest) { debug('received state digest') this.emit('stateDigest', stateDigest) } /** * Returns the latest turned on activity from a hub. * * @returns {Q.Promise} */ function getCurrentActivity () { debug('retrieve current activity') return this.request('getCurrentActivity') .then(function (response) { var result = response.result return result }) } /** * Retrieves a list with all available activities. * * @returns {Q.Promise} */ function getActivities () { debug('retrieve activities') return this.getAvailableCommands() .then(function (availableCommands) { return availableCommands.activity }) } /** * Starts an activity with the given id. * * @param activityId * @returns {Q.Promise} */ function startActivity (activityId) { var timestamp = new Date().getTime() var body = 'activityId=' + activityId + ':timestamp=' + timestamp return this.request('startactivity', body, 'encoded', function (stanza) { // This canHandleStanzaFn waits for a stanza that confirms starting the activity. var event = stanza.getChild('event') var canHandleStanza = false if (event && event.attr('type') === 'connect.stateDigest?notify') { var digest = JSON.parse(event.getText()) if (activityId === '-1' && digest.activityId === activityId && digest.activityStatus === 0) { canHandleStanza = true } else if (activityId !== '-1' && digest.activityId === activityId && digest.activityStatus === 2) { canHandleStanza = true } } return canHandleStanza }) } /** * Turns the currently running activity off. This is implemented by "starting" an imaginary activity with the id -1. * * @returns {Q.Promise} */ function turnOff () { debug('turn off') return this.startActivity('-1') } /** * Checks if the hub has now activity turned on. This is implemented by checking the hubs current activity. If the * activities id is equal to -1, no activity is on currently. * * @returns {Q.Promise} */ function isOff () { debug('check if turned off') return this.getCurrentActivity() .then(function (activityId) { var off = (activityId === '-1') debug(off ? 'system is currently off' : 'system is currently on with activity ' + activityId) return off }) } /** * Acquires all available commands from the hub when resolving the returned promise. * * @returns {Q.Promise} */ function getAvailableCommands () { debug('retrieve available commands') return this.request('config', undefined, 'json') .then(function (response) { return response }) } /** * Builds an IQ stanza containing a specific command with given body, ready to send to the hub. * * @param command * @param body * @returns {Stanza} */ function buildCommandIqStanza (command, body) { debug('buildCommandIqStanza for command "' + command + '" with body ' + body) return xmppUtil.buildIqStanza( 'get' , 'connect.logitech.com' , 'vnd.logitech.harmony/vnd.logitech.harmony.engine?' + command , body ) } function defaultCanHandleStanzaPredicate (awaitedId, stanza) { var stanzaId = stanza.attr('id') return (stanzaId && stanzaId.toString() === awaitedId.toString()) } /** * Sends a command with the given body to the hub. The returned promise gets resolved as soon as a response for this * very request arrives. * * By specifying expectedResponseType with either "json" or "encoded", you advice the response stanza handler how you * expect the responses data encoding. See the protocol guide for further information. * * The cnaHandleStanzaFn parameter allows to define a predicate to determine if an incoming stanza is the response to * your request. This can be handy if a generic stateDigest message might be the acknowledgment to your initial * request. * * * @param command * @param body * @param expectedResponseType * @param canHandleStanzaPredicate * @returns {Q.Promise} */ function request (command, body, expectedResponseType, canHandleStanzaPredicate) { debug('request with command "' + command + '" with body ' + body) var deferred = Q.defer() var iq = buildCommandIqStanza(command, body) var id = iq.attr('id') expectedResponseType = expectedResponseType || 'encoded' canHandleStanzaPredicate = canHandleStanzaPredicate || defaultCanHandleStanzaPredicate.bind(null, id) this._responseHandlerQueue.push({ canHandleStanza: canHandleStanzaPredicate, deferred: deferred, responseType: expectedResponseType }) this._xmppClient.send(iq) return deferred.promise } /** * Sends a command with given body to the hub. The returned promise gets immediately resolved since this function does * not expect any specific response from the hub. * * @param command * @param body * @returns {Q.Promise} */ function send (command, body) { debug('send command "' + command + '" with body ' + body) this._xmppClient.send(buildCommandIqStanza(command, body)) return Q() } /** * Closes the connection the the hub. You have to create a new client if you would like to communicate again with the * hub. */ function end () { debug('close harmony client') this._xmppClient.end() return Q() } HarmonyClient.prototype.isOff = isOff HarmonyClient.prototype.turnOff = turnOff HarmonyClient.prototype.getActivities = getActivities HarmonyClient.prototype.getCurrentActivity = getCurrentActivity HarmonyClient.prototype.startActivity = startActivity HarmonyClient.prototype.getAvailableCommands = getAvailableCommands HarmonyClient.prototype.request = request HarmonyClient.prototype.send = send HarmonyClient.prototype.end = end module.exports = HarmonyClient