Files
Zos/Skills/@be/node_modules/node-hue-api/hue-api/index.js

1478 lines
49 KiB
JavaScript

"use strict";
var Q = require("q")
, deepExtend = require("deep-extend")
, http = require("./httpPromise")
, ApiError = require("./errors").ApiError
, utils = require("./utils")
, lightsApi = require("./commands/lights-api")
, sensorsApi = require("./commands/sensors-api")
, groupsApi = require("./commands/groups-api")
, schedulesApi = require("./commands/schedules-api")
, scenesApi = require("./commands/scenes-api")
, configurationApi = require("./commands/configuration-api")
, infoApi = require("./commands/info-api")
, scheduledEvent = require("./scheduledEvent")
, bridgeDiscovery = require("./bridge-discovery")
, lightState = require("./lightstate")
, rgb = require("./rgb")
;
function HueApi(config) {
this._config = config;
}
module.exports = function (host, username, timeout, port) {
var config = {
hostname: host,
username: username,
timeout: timeout || 10000,
port: port || 80
};
return new HueApi(config);
};
/**
* Gets the version data for the Philips Hue Bridge.
*
* @param cb An optional callback function if you don't want to be informed via a promise.
* @returns {Q.promise} A promise will be provided that will resolve to the version data for the bridge, or {null} if a
* callback was provided.
*/
HueApi.prototype.getVersion = function (cb) {
var promise = this.config()
.then(function (data) {
return {
name: data.name,
version: {
api: data.apiversion,
software: data.swversion
}
};
});
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.version = HueApi.prototype.getVersion;
/**
* Loads the description for the Philips Hue.
*
* @param cb An optional callback function if you don't want to be informed via a promise.
* @return {Q.promise} A promise that will be provided with a description object, or {null} if a callback was provided.
*/
HueApi.prototype.description = function (cb) {
var promise = bridgeDiscovery.description(this._config.hostname);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getDescription = HueApi.prototype.description;
/**
* Reads the bridge configuration and returns it as a JSON object.
*
* @param cb An optional callback function to use if you do not want to use the promise for results.
* @return {Q.promise} A promise with the result, or <null> if a callback function was provided.
*/
HueApi.prototype.config = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(configurationApi.getConfiguration, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getConfig = HueApi.prototype.config;
/**
* Obtains the complete state for the Bridge. This is considered to be a very expensive operation and should not be invoked
* frequently. The results detail all config, users, groups, schedules and lights for the system.
*
* @param cb An optional callback function if you don't want to be informed via a promise.
* @returns {Q.promise} A promise with the result, or {null} if a callback function was provided
*/
HueApi.prototype.getFullState = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(configurationApi.getFullState, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.fullState = HueApi.prototype.getFullState;
/**
* Allows a new user/device to be registered with the Philips Hue Bridge. This will return the name of the user that was
* created by the function call.
*
* This function does not require the HueApi to have been initialized with a host or username. It does however require
* the end user to have pressed the link button on the bridge, before invoking this function.
*
* @param host The hostname or IP Address of the Hue Bridge.
* @param deviceDescription The description for the user/device that is being registered. This is a human readable
* description of the user/device. If one is not provided then a default will be set.
* @param cb An optional callback function to use if you do not want a promise returned.
* @return {Q.promise} A promise with the result, or <null> if a callback was provided.
*/
HueApi.prototype.registerUser = function (host, deviceDescription, cb) {
var options = {
host: host,
values: {}
}
, devicetype = "Node.js API"
, promise
;
if (utils.isFunction(deviceDescription)) {
options.values.devicetype = devicetype;
cb = deviceDescription;
} else {
options.values.devicetype = deviceDescription || devicetype
}
promise = http.invoke(configurationApi.createUser, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.createUser = HueApi.prototype.registerUser;
/**
* Presses the Link Button on the Bridge (without the user actually having to do it). If successful then {true} will be
* returned as the result.
*
* @param cb An optional callback function to use if you do not want to use the promise returned.
* @return {Q.promise} A promise with the result, or <null> if a callback was provided.
*/
HueApi.prototype.pressLinkButton = function (cb) {
var options = this._defaultOptions(),
promise;
promise = _setConfigurationOptions(options, {"linkbutton": true});
if (!promise) {
promise = http.invoke(configurationApi.modifyConfiguration, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Deletes an existing user from the Phillips Hue Bridge.
*
* @param username The username of the user to delete.
* @param cb An optional callback function to use if you do not want to get the result via a promise chain.
* @returns {Q.promise} A promise with the result of the deletion, or <null> if a callback was provided.
*/
HueApi.prototype.deleteUser = function (username, cb) {
var options = this._defaultOptions(),
promise;
promise = _setDeleteUserOptions(options, username);
if (!promise) {
promise = http.invoke(configurationApi.deleteUser, options);
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.unregisterUser = HueApi.prototype.deleteUser;
/**
* Obtain a list of registered "users" or "devices" that can interact with the Philips Hue.
*
* @param cb An optional callback function if you do not want to use the promise to obtain the results.
* @return A promise that will provide the results of registered users, or <null> if a callback was provided.
*/
HueApi.prototype.registeredUsers = function (cb) {
function processUsers(result) {
var list = result.whitelist,
devices = [];
if (list) {
Object.keys(list).forEach(function (key) {
var device;
if (list.hasOwnProperty(key)) {
device = list[key];
devices.push(
{
"name": device.name,
"username": key,
"created": device["create date"],
"accessed": device["last use date"]
}
);
}
});
}
return {"devices": devices};
}
var promise = this.config().then(processUsers);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getRegisteredUsers = HueApi.prototype.registeredUsers;
/**
* Obtains the details of the individual sensors that are attached to the Philips Hue.
*
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the lights object, or {null} if a callback function was provided.
*/
HueApi.prototype.sensors = function (cb) {
var options = this._defaultOptions(),
promise;
promise = http.invoke(sensorsApi.getAllSensors, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getSensors = HueApi.prototype.sensors;
/**
* Obtains the details of the individual lights that are attached to the Philips Hue.
*
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the lights object, or {null} if a callback function was provided.
*/
HueApi.prototype.lights = function (cb) {
var options = this._defaultOptions(),
promise;
promise = http.invoke(lightsApi.getAllLights, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLights = HueApi.prototype.lights;
/**
* Obtains the status of the specified light.
*
* @param id The id of the light as an integer, this value will be parsed into an integer value so can be a {String} or
* {Number} value.
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the light status, or {null} if a callback function was provided.
*/
HueApi.prototype.lightStatus = function (id, cb) {
var options = this._defaultOptions(),
promise;
promise = _setLightIdOption(options, id);
if (!promise) {
promise = http.invoke(lightsApi.getLightAttributesAndState, options);
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLightStatus = HueApi.prototype.lightStatus;
HueApi.prototype.lightStatusWithRGB = function(id, cb) {
var promise = this.lightStatus(id);
promise = promise.then(function(light) {
var state = light.state
, x = state.xy[0]
, y = state.xy[1]
, brightness = state.bri / 254
;
return deepExtend({state: {rgb: rgb.convertXYtoRGB(x, y, brightness)}}, light);
});
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLightStatusWithRGB = HueApi.prototype.lightStatusWithRGB;
/**
* Obtains the new lights found by the bridge, dependant upon the last search.
*
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the new lights search result, or {null} if a callback function was provided.
*/
HueApi.prototype.newLights = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(lightsApi.getNewLights, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getNewLights = HueApi.prototype.newLights;
/**
* Starts a search for new lights.
*
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the new lights, or {null} if a callback function was provided.
*/
HueApi.prototype.searchForNewLights = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(lightsApi.searchForNewLights, options);
return utils.promiseOrCallback(promise, cb);
};
/**
* Sets the name of a light on the Bridge.
*
* @param id The ID of the light to set the name for.
* @param name The name to apply to the light.
* @param cb An optional callback function to use if you do not want a promise returned.
* @return A promise that will be provided with the results of setting the name, or {null} if a callback function was provided.
*/
HueApi.prototype.setLightName = function (id, name, cb) {
var options = this._defaultOptions(),
promise;
promise = _setLightIdOption(options, id);
options.values = {
"name": name
};
if (!promise) {
promise = http.invoke(lightsApi.renameLight, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Sets the light state to the provided values.
*
* @param id The id of the light which is an integer or a value that can be parsed into an integer value.
* @param stateValues {Object} containing the properties and values to set on the light.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will set the specified state on the light, or {null} if a callback was provided.
*/
HueApi.prototype.setLightState = function (id, stateValues, cb) {
var promise = this._getLightStateOptions(id, stateValues)
.then(function (options) {
return http.invoke(lightsApi.setLightState, options);
});
return utils.promiseOrCallback(promise, cb);
};
/**
* Sets the light state to the provided values for an entire group.
*
* @param id The id of the group which is an integer or a value that can be parsed into an integer value.
* @param stateValues {Object} containing the properties and values to set on the light.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {Q.promise} A promise that will set the specified state on the group, or {null} if a callback was provided.
*/
HueApi.prototype.setGroupLightState = function (id, stateValues, cb) {
var promise = this._getGroupLightStateOptions(id, stateValues)
.then(function (options) {
return http.invoke(groupsApi.setGroupState, options);
});
return utils.promiseOrCallback(promise, cb);
};
/**
* Obtains all the groups from the Hue Bridge as an Array of {id: {*}, name: {*}} objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will obtain the groups, or {null} if a callback was provided.
*/
HueApi.prototype.groups = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(groupsApi.getAllGroups, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getGroups = HueApi.prototype.groups;
HueApi.prototype.getAllGroups = HueApi.prototype.groups;
/**
* Obtains all the Luminaires from the Hue Bridge as an Array of {id: {*}, name: {*}} objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will obtain the luminaires, or {null} if a callback was provided.
*/
HueApi.prototype.luminaires = function (cb) {
var promise = this._filterGroups("Luminaire");
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLuminaires = HueApi.prototype.luminaires;
/**
* Obtains all the LightSources from the Hue Bridge as an Array of {id: {*}, name: {*}} objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will obtain the lightsources, or {null} if a callback was provided.
*/
HueApi.prototype.lightSources = function (cb) {
var promise = this._filterGroups("Lightsource");
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLightSources = HueApi.prototype.lightSources;
/**
* Obtains all the LightGroups from the Hue Bridge as an Array of {id: {*}, name: {*}} objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will obtain the LightGroups, or {null} if a callback was provided.
*/
HueApi.prototype.lightGroups = function (cb) {
var promise = this._filterGroups("LightGroup");
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getLightGroups = HueApi.prototype.lightGroups;
/**
* Obtains the details for a specified group in a format of {id: {*}, name: {*}, lights: [], lastAction: {*}}.
*
* @param id {Number} or {String} which is the id of the group to get the details for.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will set the specified state on the light, or {null} if a callback was provided.
*/
HueApi.prototype.getGroup = function (id, cb) {
var options = this._defaultOptions(),
promise;
//TODO find a way to make this a normal post processing action in the groups-api, the id from the call needs to be injected...
function processGroupResult(group) {
var result = {
id: String(id),
name: group.name,
type: group.type,
lights: group.lights,
lastAction: group.action
};
if (group.type === "Luminaire" && group.modelid) {
result.modelid = group.modelid;
}
return result;
}
promise = _setGroupIdOption(options, id);
if (!promise) {
promise = http.invoke(groupsApi.getGroupAttributes, options).then(processGroupResult);
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.group = HueApi.prototype.getGroup;
/**
* Updates a light group to the specified name and/or lights ids. The name and light ids can be specified independently or
* together when calling this function.
*
* @param id The id of the group to update the name and/or light ids associated with it.
* @param name {String} The name of the group
* @param lightIds {Array} An array of light ids to be assigned to the group. If any of the ids are not present in the
* bridge the creation will fail with an error being produced.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise with a result of <true> if the update was successful, or null if a callback was provided.
*/
HueApi.prototype.updateGroup = function (id, name, lightIds, cb) {
var options = this._defaultOptions(),
parameters = [].slice.call(arguments, 1),
promise;
options.values = {};
promise = _setGroupIdOptionForModification(options, id);
// Due to name and lightIds being "optional" we have to re-parse the arguments to get the right ones
parameters.forEach(function (param) {
if (param instanceof Function) {
cb = param;
} else if (Array.isArray(param)) {
options.values.lights = utils.createStringValueArray(param);
} else if (param === undefined || param === null) {
// Ignore it
} else {
options.values.name = param;
}
});
if (!promise && !options.values.lights && !options.values.name) {
promise = _errorPromise("A name or array of lightIds must be provided");
}
if (!promise) {
promise = http.invoke(groupsApi.setGroupAttributes, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Creates a new light Group.
*
* @param name The name of the group that we are creating, limited to 16 characters.
* @param lightIds {Array} of ids for the lights to be included in the group.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {*} A promise that will return the id of the group that was created, or null if a callback was provided.
*/
HueApi.prototype.createGroup = function (name, lightIds, cb) {
var options = this._defaultOptions(),
promise;
options.values = {
name: name,
lights: utils.createStringValueArray(lightIds)
};
promise = http.invoke(groupsApi.createGroup, options);
return utils.promiseOrCallback(promise, cb);
};
/**
* Deletes a group with the specified id, returning <true> if the action was successful.
*
* @param id The id of the group to delete.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {*} A promise that will return <true> if the deletion was successful, or null if a callback was provided.
*/
HueApi.prototype.deleteGroup = function (id, cb) {
var options = this._defaultOptions(),
promise = _setGroupIdOptionForModification(options, id);
if (!promise) {
promise = http.invoke(groupsApi.deleteGroup, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Gets the schedules on the Bridge, as an array of {"id": {String}, "name": {String}} objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will return the results or <null> if a callback was provided.
*/
HueApi.prototype.schedules = function (cb) {
var options = this._defaultOptions(),
promise = http.invoke(schedulesApi.getAllSchedules, options);
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getSchedules = HueApi.prototype.schedules;
/**
* Gets the specified schedule by id, which is in an identical format the the Hue API documentation, with the addition
* of an "id" value for the schedule.
*
* @param id The id of the schedule to retrieve.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @returns A promise that will return the results or <null> if a callback was provided.
*/
HueApi.prototype.getSchedule = function (id, cb) {
var options = this._defaultOptions()
, promise = _setScheduleIdOption(options, id)
;
function parseResults(result) {
result.id = id;
return result;
}
if (!promise) {
promise = http.invoke(schedulesApi.getSchedule, options).then(parseResults);
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.schedule = HueApi.prototype.getSchedule;
/**
* Creates a one time scheduled event. The results from this function is the id of the created schedule. The bridge only
* supports 100 schedules, so once they are triggered, they are removed from the bridge.
*
* @param schedule {ScheduledEvent}
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will return the id value of the schedule that was created, or <null> if a callback was provided.
*/
HueApi.prototype.scheduleEvent = function (schedule, cb) {
return this._createSchedule(schedule, cb);
};
HueApi.prototype.createSchedule = HueApi.prototype.scheduleEvent;
/**
* Deletes a schedule by id, returning {true} if the deletion was successful.
*
* @param id of the schedule
* @param cb An option callback function to use if you do not want to use a promise for the results.
* @return {Q.promise} A promise that will return the result of the deletion, or <null> if a callback was provided.
*/
HueApi.prototype.deleteSchedule = function (id, cb) {
var options = this._defaultOptions()
, promise = _setScheduleIdOption(options, id)
;
if (!promise) {
promise = http.invoke(schedulesApi.deleteSchedule, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Updates an existing schedule event with the provided details.
*
* @param id The id of the schedule being updated.
* @param schedule The object containing the details to update for the existing schedule event.
* @param cb An optional callback function to use if you do not want to deal with a promise for the results.
* @return {Q.promise} A promise that will return the result, or <null> if a callback was provided.
*/
HueApi.prototype.updateSchedule = function (id, schedule, cb) {
var options = this._defaultOptions()
, promise
;
promise = _setScheduleIdOption(options, id);
if (!promise) {
promise = _setScheduleOptionsForUpdate(options, schedule);
}
if (!promise) {
promise = http.invoke(schedulesApi.setScheduleAttributes, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Gets the scenes on the Bridge, as an array of {"id": {String}, "name": {String}, "lights": {Array}, "active": {Boolean}}
* objects.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will return the results or <null> if a callback was provided.
*/
HueApi.prototype.scenes = function (cb) {
var promise = this._scenes()
.then(function (result) {
var scenes = [];
Object.keys(result).forEach(function (id) {
var scene = result[id]
, enrichedScene = deepExtend({}, scene)
;
enrichedScene.id = id;
scenes.push(enrichedScene);
});
return scenes;
});
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getScenes = HueApi.prototype.scenes;
/**
* Obtains a scene by a given id.
* @param sceneId {String} The id of the scene to obtain.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will return the scene or <null> if a callback was provided.
*/
HueApi.prototype.scene = function (sceneId, cb) {
var options = this._defaultOptions()
, promise = _setSceneIdOption(options, sceneId)
;
if (!promise) {
// No errors in sceneId
promise = http.invoke(scenesApi.getScene, options)
.then(function(data) {
data.id = sceneId;
return data;
})
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.getScene = HueApi.prototype.scene;
/**
* Deletes a Scene (that is stored inside the bridge, not in the lights).
* @param sceneId The ID for the scene to delete
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @returns {*} A promise that will return the result from deleting the scene or null if a callback was provided.
*/
HueApi.prototype.deleteScene = function(sceneId, cb) {
var options = this._defaultOptions()
, promise = _setSceneIdOption(options, sceneId)
;
if (!promise) {
// No errors in sceneId
promise = http.invoke(scenesApi.deleteScene, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Creates a new Scene.
* When the scene is created, it will store the current state of the lights and will use those "current" settings
* when the scene is recalled/activated later.
*
* There are two variants to this function, one that accepts lightIds and a name and another that takes a Scene object.
* The former is to maintain backwards compatibility with the 1.2.x version of this library.
*
* @param lightIds {Array} of ids for the lights to be included in the scene.
* @param name {String} The name of the scene to be created. If one is not provided, then the id of the scene will become the name.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {*} A promise that will return the id of the scene that was created (as well as the values that make up the scene),
* or null if a callback was provided.
*/
HueApi.prototype.createScene = function (scene, cb) {
var self = this;
if (Array.isArray(arguments[0])) {
return self.createBasicScene(arguments[0], arguments[1], arguments[2]);
} else {
return self.createAdvancedScene(arguments[0], arguments[1]);
}
};
/**
* Provides backwards compatibility for < 1.11.x versions of the Hue Bridge Firmware.
* @param lightIds
* @param name
* @param cb
* @returns {*}
*/
HueApi.prototype.createBasicScene = function (lightIds, name, cb) {
var self = this
, options = self._defaultOptions()
, promise
;
options.values = {
name: name,
lights: utils.createStringValueArray(lightIds),
recycle: false
};
promise = http.invoke(scenesApi.createScene, options);
return utils.promiseOrCallback(promise, cb);
};
/**
* Provides scene creation for >= 1.11.x firmware versions of the Hue Bridge.
* @param scene The Scene object containing the details of the scene to be created.
* @param cb An optional callback function to use if you do not want to use a promise chain for the results.
* @returns {*}
*/
HueApi.prototype.createAdvancedScene = function(scene, cb) {
var self = this
, options = self._defaultOptions()
, myScene = deepExtend({recycle: false}, scene)
, promise
;
//TODO validate the options object
options.values = myScene;
promise = http.invoke(scenesApi.createScene, options);
return utils.promiseOrCallback(promise, cb);
};
//TODO scene updates are now done as two different calls one for name and lights (and possible store current state) and a second of just setting individual light states
/**
* Update the lights and/or name associated with a scene (or will create a new one if the
* sceneId is not present in the bridge).
*
* @param sceneId {String} The id for the scene in the bridge
* @param scene The configuration of the scene with the details to modify, which can be either a name or an array of
* light ids.
* @param storeLightState {Boolean} flag to save the current light state of the lights in the scene.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {*} A promise that will return the id of the scene that was updated and the light ids that are now set,
* or null if a callback was provided.
*/
HueApi.prototype.updateScene = function (sceneId, scene, storeLightState, cb) {
var self = this
, options = self._defaultOptions()
, storeState = !! storeLightState
, promise = _setSceneIdOption(options, sceneId)
;
if (!promise) {
// No errors in sceneId
//TODO validate that we have at least one parameter to modify before calling
if (utils.isFunction(storeLightState)) {
cb = storeLightState;
storeState = false;
}
options.values = {};
// Only set the storelightstate to true, as the bridge does not accept a false value for this in version 1.11.0
if (storeState) {
options.values.storelightstate = true;
}
if (scene) {
if (scene.lights) {
options.values.lights = utils.createStringValueArray(scene.lights);
}
if (scene.name) {
options.values.name = scene.name;
}
}
promise = http.invoke(scenesApi.modifyScene, options);
}
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.modifyScene = HueApi.prototype.updateScene;
/**
* Modifies the light state of one of the lights in a scene.
*
* @param sceneId The scene id, which if it does not exist a new scene will be created.
* @param lightId integer The id of light that is having the state values set.
* @param stateValues {Object} containing the properties and values to set on the light.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will return the state values on the light, or {null} if a callback was provided.
*/
HueApi.prototype.setSceneLightState = function (sceneId, lightId, stateValues, cb) {
var promise;
promise = this._getLightStateOptions(lightId, stateValues)
.then(function (options) {
// Need to set id and lightId correctly, the above call treats the lightId as the id
options.lightId = options.id;
options.id = sceneId;
return http.invoke(scenesApi.modifyLightState, options);
});
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.updateSceneLightState = HueApi.prototype.setSceneLightState;
HueApi.prototype.modifySceneLightState = HueApi.prototype.setSceneLightState;
/**
* Helper-function that recalls a scene for a group using setGroupLightState. Reason for existence is simplicity for
* user.
*
* @param sceneId The id of the scene to activate, which is an integer or a value that can be parsed into an integer value.
* @param groupIdFilter An optional group filter to apply to the scene, to select a sub set of the lights in the scene. This can
* be {null} or {undefined} to not apply a filter.
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return A promise that will set activate the scene, or {null} if a callback was provided.
*/
HueApi.prototype.activateScene = function (sceneId, groupIdFilter, cb) {
var promise;
if (utils.isFunction(groupIdFilter)) {
cb = groupIdFilter;
groupIdFilter = null;
}
try {
groupIdFilter = Number(groupIdFilter, 10);
if (isNaN(groupIdFilter)) {
groupIdFilter = 0;
}
} catch (err) {
groupIdFilter = 0;
}
promise = this.setGroupLightState(groupIdFilter, {scene: sceneId});
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.recallScene = HueApi.prototype.activateScene;
// TODO this is flawed as the name can be in multiple scenes, all of which are active...
///**
// * Helper function that recalls a scene for a group using setGroupLightState. The id is extracted from the name, if
// * multiple ids is encountered which often is the case when a scene is edited via an ios/android app the last one is
// * used. Currently this is the scene last saved this is an assumption bases on undocumented handling.
// *
// * @param id The id of the light which is an integer or a value that can be parsed into an integer value.
// * @param stateValues {Object} containing the properties and values to set on the light.
// * @param cb An optional callback function to use if you do not want to use a promise for the results.
// * @return A promise that will set the specified state on the light, or {null} if a callback was provided.
// */
////TODO rename
//HueApi.prototype.recallSceneByName = function (groupId, sceneName, cb) {
// var self = this
// , deferred = Q.defer()
// , scenes = {}
// ;
//
// //TODO this will not function as expected
// self.scenes()
// .then(function (sceneArray) {
// sceneArray.forEach(function (scene) {
// scenes[scene.name] = scene.id;
// });
//
// if (typeof scenes[sceneName] !== 'undefined') {
// self.setGroupLightState(groupId, {scene: scenes[sceneName]})
// .then(function (result) {
// deferred.resolve(result);
// });
// }
// }).done();
//
// return utils.promiseOrCallback(deferred.promise, cb);
//};
/**
* Obtains all the allowed timezones from the bridge.
*
* @param cb An optional callback function to use if you do not want to use a promise for the results.
* @return {*} A promise that will return the id of the scene that was created, or null if a callback was provided.
*/
HueApi.prototype.getTimezones = function (cb) {
var options = this._defaultOptions()
, promise = http.invoke(infoApi.getAllTimezones, options)
;
return utils.promiseOrCallback(promise, cb);
};
HueApi.prototype.timezones = HueApi.prototype.getTimezones;
////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
////////////////////////////////////////////////////////////////////////////////////////////////
HueApi.prototype._getConfig = function () {
return this._config;
};
/**
* Creates a default options object for connecting with a Hue Bridge.
*
* @returns {{host: *, username: *, timeout: *}}
* @private
*/
HueApi.prototype._defaultOptions = function () {
var config = this._getConfig();
return {
host: config.hostname,
username: config.username,
timeout: config.timeout,
port: config.port
};
};
HueApi.prototype._filterGroups = function (type) {
var self = this;
return self.groups()
.then(function (groups) {
var results = [];
if (groups) {
groups.forEach(function (group) {
if (group.type === type) {
results.push(group);
}
})
}
return results;
});
};
HueApi.prototype._scenes = function () {
var options = this._defaultOptions();
return http.invoke(scenesApi.getAllScenes, options);
};
/**
* Obtains the lights in a group and separates them into sub groups based on the model.
* @param groupId The id of the group.
* @returns {Object} A map of modelid to and array of lights that have that model.
* @private
*/
HueApi.prototype._getGroupLightsByType = function (groupId) {
var self = this;
return Q.all(
[
self.getGroup(groupId),
self.getLights()
])
.spread(function (group, allLights) {
var map = {}
, lightMap = getLightsModelMap(allLights)
;
if (group && group.lights) {
group.lights.forEach(function(lightId) {
var modelid = lightMap[lightId];
if (map[modelid]) {
map[modelid].push(lightId);
} else {
map[modelid] = [lightId];
}
});
}
return map;
});
};
/**
* Generates the light state options for a group
* @param groupId The group to apply the state values to
* @param stateValues The state of the lights to apply
* @returns {Q.promise} That will resolve to a set of options for the group or an array of options to apply subsets of
* lights in the group.
* @private
*/
HueApi.prototype._getGroupLightStateOptions = function (groupId, stateValues) {
var self = this
, options = self._defaultOptions()
, state
, deferred
, promise
;
promise = _setGroupIdOption(options, groupId);
if (!promise) {
// No errors in the group id
if (lightState.isLightState(stateValues)) {
state = stateValues;
} else {
state = lightState.create(stateValues);
}
if (state.hasRGB()) {
//TODO RGB is tricky with groups, need to break the group into types and perform conversion
// Get all lights in the group,
// separate into types based on model
// create multiple states per model
// return a map of sub groups to states required
deferred = Q.defer();
deferred.reject(new ApiError("RGB state is not supported for groups yet"));
promise = deferred.promise;
//// Separate the lights into models and apply the state to each type
//promise = self._getGroupLightsByType(groupId)
// .then(function(groupLightsMap) {
// var models = Object.keys(groupLightsMap)
// , result = []
// ;
//
// models.forEach(function(model) {
// var newState = state.copy();
// newState.applyRGB(model);
//
// result.push({
// modelid: model,
// lights: groupLightsMap[model],
// state: newState
// });
// });
//
// return result;
// })
// .then(function(subgroupsWithState) {
// //TODO need to create options
// });
} else {
options.values = state.payload();
deferred = Q.defer();
deferred.resolve(options);
promise = deferred.promise;
}
}
return promise;
};
HueApi.prototype._getLightStateOptions = function (lightId, stateValues) {
var self = this
, options = self._defaultOptions()
, deferred
, state
, promise
;
promise = _setLightIdOption(options, lightId);
if (!promise) {
// We have not errored, so check if we need to convert an rgb value
if (lightState.isLightState(stateValues)) {
state = stateValues;
} else {
state = lightState.create(stateValues);
}
if (state.hasRGB()) {
promise = self.lightStatus(lightId)
.then(function (lightDetails) {
state = state.applyRGB(lightDetails.modelid);
options.values = state.payload();
return options;
});
} else {
options.values = state.payload();
deferred = Q.defer();
deferred.resolve(options);
promise = deferred.promise;
}
}
return promise;
};
/**
* Creates a new schedule in the Hue Bridge.
*
* @param schedule The schedule object to create.
* @param cb An optional callback if you do not want to use the promise for results.
* @returns {Q.promise} A promise with the creation results, or <null> if a callback was provided.
* @private
*/
HueApi.prototype._createSchedule = function (schedule, cb) {
var options = this._defaultOptions(),
promise = _setScheduleOptionsForCreation(options, schedule);
if (!promise) {
promise = http.invoke(schedulesApi.createSchedule, options);
}
return utils.promiseOrCallback(promise, cb);
};
/**
* Validates and then injects the username to be deleted into the options.
*
* @param options The options to inject the value into.
* @param username The username to delete.
* @returns {Q.promise} A promise containing the error(s) if there were any, otherwise <null>.
* @private
*/
function _setDeleteUserOptions(options, username) {
var errorPromise = null;
//TODO perform a lookup for the user before we attempt to delete it??
if (!username) {
errorPromise = _errorPromise("A username to delete must be specified.");
}
options.username2 = username;
return errorPromise;
}
/**
* Validates and injects the configuration options to set for the Hue Bridge into the provided options.
*
* @param options The options to inject into.
* @param values The values that we wish to modify on the bridge.
* @private
*/
function _setConfigurationOptions(options, values) {
var errorPromise = null,
validOptionFound = false;
// Use the API specification to check all required values have been provided.
Object.keys(configurationApi.modifyConfiguration.bodyArguments).forEach(function (value) {
var option = configurationApi.modifyConfiguration.bodyArguments[value];
// Check to see if we have at least one option being set
if (!validOptionFound && values[value]) {
validOptionFound = true;
}
// Check that we have all the required options being provided
if (!option.optional) {
// Check that the value has been provided
if (!errorPromise && !values[value]) {
errorPromise = _errorPromise("A required configuration option '" + value + "' was not provided.");
}
}
});
if (!errorPromise) {
if (!validOptionFound) {
errorPromise = _errorPromise("No valid options for the bridge configuration were specified.");
} else {
options.values = values;
}
}
return errorPromise;
}
function getLightsModelMap(lightsArray) {
var map = {};
if (Array.isArray(lightsArray)) {
lightsArray.forEach(function(light) {
map[light.id] = light.modelid;
});
}
return map;
}
/**
* Validates and then injects the 'id' into the options for a light in the bridge.
*
* @param options The options to add the 'id' to.
* @param id The id of the light
* @return {Q.promise} A promise that will throw the error if there was one, otherwise <null>.
* @private
*/
function _setLightIdOption(options, id) {
var errorPromise = null;
if (!_isLightIdValid(id)) {
errorPromise = _errorPromise("The light id '" + id + "' is not valid for this Hue Bridge.");
} else {
options.id = id;
}
return errorPromise;
}
/**
* Validates and then injects the 'id' into the options for a group in the bridge.
*
* @param options The options to add the 'id' to.
* @param id The id of the group
* @return {Q.promise} A promise that will throw an error or null if the group id was valid.
* @private
*/
function _setGroupIdOption(options, id) {
var errorPromise = null;
if (!_isGroupIdValid(id)) {
errorPromise = _errorPromise("The group id '" + id + "' is not valid for this Hue Bridge.");
} else {
options.id = id;
}
return errorPromise;
}
/**
* Validates and then injects the 'id' into the options for a group in the bridge.
*
* @param options The options to add the 'id' to.
* @param id The id of the group
* @return {Q.promise} A promise that will throw an error or null if the group id was valid.
* @private
*/
function _setGroupIdOptionForModification(options, id) {
var errorPromise = null;
if (!_isGroupIdValidForModification(id)) {
errorPromise = _errorPromise("The group id '" + id + "' cannot be modified on this Hue Bridge.");
} else {
options.id = id;
}
return errorPromise;
}
/**
* Validates and then injects the 'id' into the options for a group in the bridge.
*
* @param options The options to add the 'id' to.
* @param id The id of the schedule
* @return {Q.promise} A promise that will throw an error or null if the schedule id was valid.
* @private
*/
function _setScheduleIdOption(options, id) {
var errorPromise = null;
if (!_isScheduleIdValid(id)) {
errorPromise = _errorPromise("The schedule id '" + id + "' is not valid for this Hue Bridge.");
} else {
options.id = id;
}
return errorPromise;
}
/**
* Validates and then injects the schedule into the 'values' of the options.
*
* @param options The options to inject into.
* @param schedule The schedule object containing the details for the schedule to create.
* @returns {Q.promise} A promise containing any errors that might have occured, or <null> if there were none.
* @private
*/
function _setScheduleOptionsForCreation(options, schedule) {
var errorPromise = null;
// Use the API specification to check all required values have been provided.
Object.keys(schedulesApi.createSchedule.bodyArguments).forEach(function (value) {
var option = schedulesApi.createSchedule.bodyArguments[value];
if (!option.optional) {
// Check that the value has been provided
if (!errorPromise && !schedule[value]) {
errorPromise = _errorPromise("A required schedule option '" + value + "' was not provided.");
}
}
});
if (!errorPromise) {
options.values = scheduledEvent.create(schedule);
}
return errorPromise;
}
/**
* Validates and then injects the schedule into the 'values' of the options.
*
* @param options The options to inject into.
* @param schedule The schedule object with the details of the updates to make.
* @returns {Q.promise} A promise with any errors, if there were any, otherwise <null>.
* @private
*/
function _setScheduleOptionsForUpdate(options, schedule) {
var errorPromise = null,
validOptionFound = false;
// Use the API specification to check all the required values have been provided, or we have at least one value
Object.keys(schedulesApi.setScheduleAttributes.bodyArguments).forEach(function (value) {
if (schedule[value]) {
validOptionFound = true;
}
});
if (!validOptionFound) {
errorPromise = _errorPromise("No valid values for updating the schedule were found in the schedule details provided.");
} else {
options.values = scheduledEvent.create(schedule);
}
return errorPromise;
}
/**
* Validates and then injects the 'id' into the options for a group in the bridge.
*
* @param options The options to add the 'id' to.
* @param sceneId The id of the scene
* @return {Q.promise} A promise that will throw an error or null if the scene id was valid.
* @private
*/
function _setSceneIdOption(options, sceneId) {
var errorPromise = null;
if (!_isSceneIdValid(sceneId)) {
errorPromise = _errorPromise("The scene id '" + sceneId + "' is not valid for this Hue Bridge.");
} else {
options.id = sceneId;
}
return errorPromise;
}
/**
* Creates a promise that will generate an ApiError with the provided message.
*
* @param message The error message
* @returns {Q.promise}
* @private
*/
function _errorPromise(message) {
var deferred = Q.defer();
deferred.reject(new ApiError(message));
return deferred.promise;
}
/**
* Validates that the light id is valid for this Hue Bridge.
*
* @param id The id of the light in the Hue Bridge.
* @returns {boolean} true if the id is valid for this bridge.
* @private
*/
function _isLightIdValid(id) {
if (parseInt(id, 10) > 0) {
//TODO check that this is a valid light id for the system
return true;
} else {
return false;
}
}
/**
* Validates that the group id is valid for this Hue Bridge.
*
* @param id The id of the group in the Hue Bridge.
* @returns {boolean} true if the id is valid for this bridge.
* @private
*/
function _isGroupIdValid(id) {
if (parseInt(id, 10) >= 0) {
//TODO check that this is a valid group id for the system
return id <= 16;
} else {
return false;
}
}
/**
* Validates that the group id is valid for modification on this Hue Bridge.
*
* @param id The id of the group in the Hue Bridge.
* @returns {boolean} true if the id is valid for this bridge.
* @private
*/
function _isGroupIdValidForModification(id) {
if (_isGroupIdValid(id)) {
return parseInt(id, 10) > 0;
}
return false;
}
/**
* Validates that the schedule id is valid for this Hue Bridge.
*
* @param id The id of the group in the Hue Bridge.
* @returns {boolean} true if the id is valid for this bridge.
* @private
*/
function _isScheduleIdValid(id) {
if (parseInt(id, 10) >= 0) {
//TODO check that this is a valid schedule id for the system
return id <= 100;
} else {
return false;
}
}
function _isSceneIdValid(id) {
return id && (String(id).length > 0);
}