167 lines
5.3 KiB
JavaScript
167 lines
5.3 KiB
JavaScript
|
|
"use strict";
|
||
|
|
|
||
|
|
var url = require("url")
|
||
|
|
, Q = require("q")
|
||
|
|
, search = require("./search")
|
||
|
|
, http = require("./httpPromise")
|
||
|
|
, utils = require("./utils")
|
||
|
|
, discovery = require("./commands/discovery")
|
||
|
|
;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Will locate the Philips Hue Devices on the network. Depending upon the speed and size of the network the timeout
|
||
|
|
* may need to be adjusted to locate the Hue Bridge.
|
||
|
|
*
|
||
|
|
* @param timeout The maximum time to wait for Hue Devices to be located. If not specified will use the default of 5
|
||
|
|
* seconds.
|
||
|
|
* @return A promise that will resolve the Hue Bridges as an Array of {"id": {String}, "ipaddress": {String}} objects.
|
||
|
|
*/
|
||
|
|
module.exports.networkSearch = function (timeout) {
|
||
|
|
return search.locateBridges(timeout).then(_identifyBridges);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Uses the http://www.meethue.com/api/nupnp call to search for any bridges locally on the network. This lookup can be
|
||
|
|
* significantly faster than issuing search requests in the {locateBridges} function.
|
||
|
|
*
|
||
|
|
* @param cb An option callback function that will be informed of results.
|
||
|
|
* @returns {Q.promise} A promise that will resolve the addresses of the bridges, or {null} if a callback was provided.
|
||
|
|
*/
|
||
|
|
module.exports.locateBridges = function (cb) {
|
||
|
|
var promise = http.invoke(discovery.upnpLookup, {host: "www.meethue.com", ssl: true});
|
||
|
|
return utils.promiseOrCallback(promise, cb);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Obtains an object representation of the Description XML.
|
||
|
|
*
|
||
|
|
* @param host The host to get the description XML from.
|
||
|
|
* @return {*} The object representing some of the values in the description XML.
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
module.exports.description = function (host) {
|
||
|
|
return http.invoke(discovery.description, {"host": host})
|
||
|
|
.then(_parseDescription);
|
||
|
|
};
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Identifies the bridges based on the response from possible devices from SSDP search
|
||
|
|
* @param possibleBridges The results from the search to be processed.
|
||
|
|
* @return A promise that will process all the results and return only the valid Hue Bridges.
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
function _identifyBridges(possibleBridges) {
|
||
|
|
var lookups = []
|
||
|
|
, path
|
||
|
|
//, uri
|
||
|
|
;
|
||
|
|
|
||
|
|
for (path in possibleBridges) {
|
||
|
|
if (possibleBridges.hasOwnProperty(path)) {
|
||
|
|
//uri = parseUri(path);
|
||
|
|
lookups.push(followLocationResponse(path).then(_getHueBridgeHost));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Q.all(lookups);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Performs a GET on the provided path, the location response from the bridge.
|
||
|
|
* This function expects there to be an XML description document present at the provided path.
|
||
|
|
* @param path The path in the LOCATION response from SSDP lookup.
|
||
|
|
*/
|
||
|
|
function followLocationResponse(path) {
|
||
|
|
return http.simpleGet(path)
|
||
|
|
.then(_parseDescription)
|
||
|
|
.fail(function (err) {
|
||
|
|
// Do nothing with services that do not respond with an XML document
|
||
|
|
})
|
||
|
|
;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parses the XML Description and converts it into an Object.
|
||
|
|
* @param xml The XML to parse and convert into an object
|
||
|
|
* @return {Function|promise|promise|exports.promise}
|
||
|
|
* @private
|
||
|
|
*/
|
||
|
|
function _parseDescription(xml) {
|
||
|
|
var xml2js = require("xml2js")
|
||
|
|
, parser = new xml2js.Parser()
|
||
|
|
, deferred = Q.defer()
|
||
|
|
;
|
||
|
|
|
||
|
|
parser.parseString(xml, function (err, data) {
|
||
|
|
var result = null
|
||
|
|
, icons
|
||
|
|
;
|
||
|
|
|
||
|
|
if (err) {
|
||
|
|
deferred.reject(err);
|
||
|
|
} else {
|
||
|
|
result = {
|
||
|
|
"version": {
|
||
|
|
"major": data.root.specVersion[0].major[0],
|
||
|
|
"minor": data.root.specVersion[0].minor[0]
|
||
|
|
},
|
||
|
|
"url": data.root.URLBase[0],
|
||
|
|
"name": data.root.device[0].friendlyName[0],
|
||
|
|
"manufacturer": data.root.device[0].manufacturer[0],
|
||
|
|
"model": {
|
||
|
|
"name": data.root.device[0].modelName[0],
|
||
|
|
"description": data.root.device[0].modelDescription[0],
|
||
|
|
"number": data.root.device[0].modelNumber[0],
|
||
|
|
"serial": data.root.device[0].serialNumber[0],
|
||
|
|
"udn": data.root.device[0].UDN[0]
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
if (data.root.device[0].iconList
|
||
|
|
&& data.root.device[0].iconList[0]
|
||
|
|
&& data.root.device[0].iconList[0].icon) {
|
||
|
|
icons = [];
|
||
|
|
|
||
|
|
data.root.device[0].iconList[0].icon.forEach(function (icon) {
|
||
|
|
icons.push({
|
||
|
|
mimetype: icon.mimetype[0],
|
||
|
|
height: icon.height[0],
|
||
|
|
width: icon.width[0],
|
||
|
|
depth: icon.depth[0],
|
||
|
|
url: icon.url[0]
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
result.icons = icons;
|
||
|
|
}
|
||
|
|
|
||
|
|
deferred.resolve(result);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return deferred.promise;
|
||
|
|
}
|
||
|
|
|
||
|
|
function _isHueBridge(description) {
|
||
|
|
var name;
|
||
|
|
|
||
|
|
if (description && description.model && description.model.name) {
|
||
|
|
name = description.model.name;
|
||
|
|
if (name.toLowerCase().indexOf("philips hue bridge") >= 0) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
function _getHueBridgeHost(description) {
|
||
|
|
var uri;
|
||
|
|
|
||
|
|
if (_isHueBridge(description)) {
|
||
|
|
uri = url.parse(description.url);
|
||
|
|
return {
|
||
|
|
"id": description.model.serial,
|
||
|
|
"ipaddress": uri.hostname
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|