(function(){
'use strict';
////////////////////////////////////////////////////////////////////////////////
//
// hue (Philips Wireless Lighting) Api interface for JavaScript
// +-> HUEPI sounds like Joepie which makes me smile during development...
//
// Requires jQuery 1.5+ for ajax calls and Deferreds
//
////////////////////////////////////////////////////////////////////////////////
/**
* huepi Object, Entry point for all interaction with Lights etc via the Bridge.
*
* @class
* @alias huepi
*/
var huepi = function() {
/** @member {string} - version of the huepi interface */
this.version = '1.2.2';
/** @member {array} - Array of all Bridges on the local network */
this.LocalBridges = [];
/** @member {bool} - get: local network scan in progress / set:proceed with scan */
this.ScanningNetwork = false;
/** @member {string} - IP address of the Current(active) Bridge */
this.BridgeIP = '';
/** @member {string} - ID (Unique, is MAC address) of the Current(active) Bridge */
this.BridgeID = '';
/** @member {string} - Username for Whitelisting, generated by the Bridge */
this.Username = '';
/** @member {object} - Cache Hashmap of huepi BridgeID and Whitelisted Username */
this.BridgeCache = {};
/** @member {boolean} - Autosave Cache Hasmap of huepi BridgeID and Whitelisted Username */
this.BridgeCacheAutosave = true;
this._BridgeCacheLoad(); // Load BridgeCache on creation by Default
/** @member {object} - Configuration of the Current(active) Bridge */
this.BridgeConfig = {};
/** @member {string} - Name of the Current(active) Bridge */
this.BridgeName = '';
/** @member {array} - Array of all Lights of the Current(active) Bridge */
this.Lights = [];
/** @member {array} - Array of all LightIds of the Current(active) Bridge */
this.LightIds = [];
/** @member {array} - Array of all Groups of the Current(active) Bridge */
this.Groups = [];
/** @member {array} - Array of all GroupIds of the Current(active) Bridge */
this.GroupIds = [];
// To Do: Add Schedules, Scenes, Sensors & Rules manupulation functions, they are read only for now
/** @member {array} - Array of all Schedules of the Current(active) Bridge, NOTE: There are no Setter functions yet */
this.Schedules = [];
/** @member {array} - Array of all Scenes of the Current(active) Bridge, NOTE: There are no Setter functions yet */
this.Scenes = [];
/** @member {array} - Array of all Sensors of the Current(active) Bridge, NOTE: There are no Setter functions yet */
this.Sensors = [];
/** @member {array} - Array of all Rules of the Current(active) Bridge, NOTE: There are no Setter functions yet */
this.Rules = [];
};
////////////////////////////////////////////////////////////////////////////////
//
// Detect Running in NodeJS; module exisists and module.exports exists
// and type of global.process = object process
//
// requires domino window to create jquery with window attached.
//
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
{
var $;
var XMLHttpRequest;
if (typeof global !== 'undefined' && typeof global.process !== 'undefined' &&
Object.prototype.toString.call(global.process) === '[object process]') {
$ = require('jquery')(require('domino').createWindow('<html>huepi</html>'));
XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
$.support.cors = true; // cross domain, Cross-origin resource sharing
$.ajaxSettings.xhr = function() {
return new XMLHttpRequest();
};
}
module.exports = huepi;
} else if (typeof define === 'function' && define.amd) {
$ = require('jquery')(require('domino').createWindow('<html>huepi</html>'));
XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
$.support.cors = true; // cross domain, Cross-origin resource sharing
$.ajaxSettings.xhr = function() {
return new XMLHttpRequest();
};
define([], function() { return huepi; });
} else {
$ = jQuery;
window.huepi = huepi;
}
////////////////////////////////////////////////////////////////////////////////
//
// Private _BridgeCache Functions, Internal Used
//
//
/**
* Loads the BridgeCache, typically on startup
*/
huepi.prototype._BridgeCacheLoad = function()
{
this.BridgeCache = { };
try {
if (typeof navigator !== 'undefined' && typeof window !== 'undefined') { // running in Browser
var huepiBridgeCache = localStorage.huepiBridgeCache || '{}';
this.BridgeCache = JSON.parse(huepiBridgeCache); // Load
} else if (typeof module !== 'undefined' && module.exports) { // running in NodeJS
var fs = require('fs');
var buffer = fs.readFileSync('huepiBridgeCache.json');
this.BridgeCache = JSON.parse(buffer.toString());
}
//console.log('huepi._BridgeCacheLoad()-ed : \n '+ JSON.stringify(this.BridgeCache));
} catch (error) {
console.log('Unable to huepi._BridgeCacheLoad() ' + error);
}
};
huepi.prototype._BridgeCacheAddCurrent = function()
{
console.log('_BridgeCacheAddCurrent ' + this.BridgeID +' '+ this.Username);
this.BridgeCache[this.BridgeID] = this.Username;
if (this.BridgeCacheAutosave) {
this._BridgeCacheSave();
}
};
huepi.prototype._BridgeCacheRemoveCurrent = function()
{
if (this.BridgeCache[this.BridgeID] === this.Username) {
console.log('_BridgeCacheRemoveCurrent ' + this.BridgeID +' '+ this.Username);
delete this.BridgeCache[this.BridgeID];
if (this.BridgeCacheAutosave) {
this._BridgeCacheSave();
}
}
};
/**
* Selects the first Bridge from LocalBridges found in BridgeCache and stores in BridgeIP
* defaults to 1st Bridge in LocalBridges if no bridge from LocalBridges is found in BridgeCache
*
* Internally called in PortalDiscoverLocalBridges and NetworkDiscoverLocalBridges
*/
huepi.prototype._BridgeCacheSelectFromLocalBridges = function()
{
if (this.LocalBridges.length > 0) { // Local Bridges are found
this.BridgeIP = this.LocalBridges[0].internalipaddress || ''; // Default to 1st Bridge Found
this.BridgeID = this.LocalBridges[0].id.toLowerCase() || '';
if (!this.BridgeCache[this.BridgeID]) { // if this.BridgeID not found in BridgeCache
for (var BridgeNr=1; BridgeNr<this.LocalBridges.length; BridgeNr++) { // Search and store Found
this.BridgeID = this.LocalBridges[BridgeNr].id.toLowerCase();
if (this.BridgeCache[this.BridgeID]) {
this.BridgeIP = this.LocalBridges[BridgeNr].internalipaddress;
break;
} else {
this.BridgeID = '';
}
}
}
}
this.Username = this.BridgeCache[this.BridgeID] || '';
};
/**
* Saves the BridgeCache, typically on Whitelist new Device or Device no longer whitelisted
* as is the case with with @BridgeCacheAutosave on @_BridgeCacheAddCurrent and @_BridgeCacheRemoveCurrent
* NOTE: Saving this cache might be considered a security issue
* To counter this security issue, arrange your own load/save code with proper encryption
*/
huepi.prototype._BridgeCacheSave = function()
{
try {
if (typeof navigator !== 'undefined' && typeof window !== 'undefined') { // running in Browser
localStorage.huepiBridgeCache = JSON.stringify(this.BridgeCache); // Save
} else if (typeof module !== 'undefined' && module.exports) { // running in NodeJS
var fs = require('fs');
fs.writeFileSync('huepiBridgeCache.json',JSON.stringify(this.BridgeCache));
}
//console.log('huepi._BridgeCacheSave()-ed : \n '+ JSON.stringify(this.BridgeCache));
} catch (error) {
console.log('Unable to huepi._BridgeCacheSave() ' + error);
}
};
////////////////////////////////////////////////////////////////////////////////
//
// Network Functions
//
//
/**
* Creates the list of hue-Bridges on the local network
*/
huepi.prototype.NetworkDiscoverLocalBridges = function()
{
var self = this;
var LocalIPs = [];
var OverallDeferred = $.Deferred();
self.ScanningNetwork = true;
self.BridgeIP =
self.BridgeID =
self.BridgeName =
self.Username = '';
self.LocalBridges = [];
function DiscoverLocalIPs() {
var IPDeferred = $.Deferred();
var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
var PeerConnection = new RTCPeerConnection({iceServers: [] });
PeerConnection.createDataChannel('');
PeerConnection.onicecandidate = function(e) {
if (!e.candidate) {
PeerConnection.close();
return IPDeferred.resolve();
}
var LocalIP = /^candidate:.+ (\S+) \d+ typ/.exec(e.candidate.candidate)[1];
if (LocalIPs.indexOf(LocalIP) === -1) {
LocalIPs.push(LocalIP);
}
};
PeerConnection.createOffer(function(sdp) {
PeerConnection.setLocalDescription(sdp);
}, function onerror() {});
return IPDeferred.promise();
}
function DiscoverLocalBridges() {
var BridgeDeferred = $.Deferred();
var Parallel = 16;
function CheckIP(IPAddress) {
self.BridgeGetConfig(IPAddress, 3000).then(function(data){
self.LocalBridges.push({'internalipaddress': IPAddress, 'id': data.bridgeid.toLowerCase()});
}).always(function(){
var Segment = IPAddress.slice(0, IPAddress.lastIndexOf('.')+1);
var Nr = parseInt(IPAddress.slice(IPAddress.lastIndexOf('.')+1, IPAddress.length));
OverallDeferred.notify(Math.floor(100*Nr/255));
if (self.ScanningNetwork === false) {
Nr = 256; // Stop scanning if (self.ScanningNetwork = false)
}
if ((Nr+Parallel)<256) {
CheckIP(Segment+(Nr+Parallel));
} else {
self.ScanningNetwork = false;
BridgeDeferred.resolve();
}
});
}
for (var IPs=0; IPs<LocalIPs.length; IPs++) {
var InitialIP = LocalIPs[IPs].slice(0, LocalIPs[IPs].lastIndexOf('.')+1);
for (var P=1; P<=Parallel; P++) {
CheckIP(InitialIP+P);
}
}
return BridgeDeferred.promise();
}
DiscoverLocalIPs().then(function() {
DiscoverLocalBridges().then(function() {
if (self.LocalBridges.length > 0) {
self._BridgeCacheSelectFromLocalBridges();
OverallDeferred.resolve();
} else {
OverallDeferred.reject();
}
});
});
return OverallDeferred.promise();
};
////////////////////////////////////////////////////////////////////////////////
//
// Portal Functions
//
//
/**
* Retreives the list of hue-Bridges on the local network from the hue Portal
*/
huepi.prototype.PortalDiscoverLocalBridges = function()
{
var self = this;
var deferred = $.Deferred();
self.BridgeIP =
self.BridgeID =
self.BridgeName =
self.Username = '';
self.LocalBridges = [];
$.ajax({ type: 'GET', url: 'https://www.meethue.com/api/nupnp', success: function(data) {
if (data.length > 0) {
if (data[0].internalipaddress) { // Bridge(s) Discovered
self.LocalBridges = data;
self._BridgeCacheSelectFromLocalBridges();
deferred.resolve();
} else {
deferred.reject();
}
} else {
deferred.reject();
}
}, error: function(/*xhr,status,error*/) {
deferred.reject();
} });
return deferred.promise();
};
////////////////////////////////////////////////////////////////////////////////
//
// Bridge Functions
//
//
/**
* Function to retreive BridgeConfig before Checking Whitelisting.
* ONCE call BridgeGetConfig Before BridgeGetData to validate we are talking to a hue Bridge
* available members (as of 'apiversion': '1.11.0'):
* name, apiversion, swversion, mac, bridgeid, replacesbridgeid, factorynew, modelid
*
* @param {string} ConfigBridgeIP - Optional BridgeIP to GetConfig from, otherwise uses huepi.BridgeIP (this/self).
* @param {string} ConfigTimeOut - Optional TimeOut for network request, otherwise uses 60 seconds.
*/
huepi.prototype.BridgeGetConfig = function(ConfigBridgeIP, ConfigTimeOut)
{ // GET /api/config -> data.config.whitelist.username
var self = this;
var deferred = $.Deferred();
ConfigBridgeIP = ConfigBridgeIP || self.BridgeIP;
ConfigTimeOut = ConfigTimeOut || 60000;
$.ajax({ type: 'GET', timeout: ConfigTimeOut, url: 'http://' + ConfigBridgeIP + '/api/config/', success: function(data) {
if (data.bridgeid) {
if (self.BridgeIP === ConfigBridgeIP) {
self.BridgeConfig = data;
if (self.BridgeConfig.bridgeid) // SteveyO/Hue-Emulator doesn't supply bridgeid as of yet.
self.BridgeID = self.BridgeConfig.bridgeid.toLowerCase();
else {
self.BridgeID = '';
}
self.BridgeName = self.BridgeConfig.name;
self.Username = self.BridgeCache[self.BridgeID];
if (typeof self.Username === 'undefined') {
self.Username = '';
}
}
deferred.resolve(data);
} else { // this BridgeIP is not a hue Bridge
deferred.reject();
}
}, error: function(/*xhr,status,error*/) { // $.ajax failed
deferred.reject();
} });
return deferred.promise();
};
/**
* Function to retreive BridgeDescription before Checking Whitelisting.
* ONCE call BridgeGetDescription Before BridgeGetData to validate we are talking to a hue Bridge
*
* REMARK: Needs a fix of the hue bridge to allow CORS on xml endpoint too, just like on json endpoints already is implemented.
*
* @param {string} ConfigBridgeIP - Optional BridgeIP to GetConfig from, otherwise uses huepi.BridgeIP (this/self).
* @param {string} ConfigTimeOut - Optional TimeOut for network request, otherwise uses 60 seconds.
*/
huepi.prototype.BridgeGetDescription = function(ConfigBridgeIP, ConfigTimeOut)
{ // GET /description.xml -> /device/serialNumber
var self = this;
var deferred = $.Deferred();
ConfigBridgeIP = ConfigBridgeIP || self.BridgeIP;
ConfigTimeOut = ConfigTimeOut || 60000;
//$.support.cors = true; // cross domain, Cross-origin resource sharing
//$.ajaxSetup( { contentType: 'text/plain', xhrFields: { withCredentials: false }, headers: { 'Origin': ConfigBridgeIP } });
$.ajax({ type: 'GET', timeout: ConfigTimeOut, url: 'http://' + ConfigBridgeIP + '/description.xml', dataType: 'json', success: function(data) {
var $data = $(data);
if ($data.find('url').text() === 'hue_logo_0.png') {
if (ConfigBridgeIP === self.BridgeIP) {
if ($data.find('serialNumber').text() !== '')
self.BridgeID = $data.find('serialNumber').text().toLowerCase();
else {
self.BridgeID = '';
}
self.BridgeName = $data.find('friendlyName').text();
self.Username = self.BridgeCache[self.BridgeID];
if (typeof self.Username === 'undefined') {
// Correct 001788[....]200xxx -> 001788FFFE200XXX short and long serialnumer difference
self.BridgeID = self.BridgeID.slice(0,6) + 'fffe' + self.BridgeID.slice(6,12);
self.Username = self.BridgeCache[self.BridgeID];
if (typeof self.Username === 'undefined') {
self.Username = '';
}
}
}
deferred.resolve(data);
} else { // this BridgeIP is not a hue Bridge
deferred.reject();
}
}, error: function(/*xhr,status,error*/) { // $.ajax failed
deferred.reject();
} });
return deferred.promise();
};
/**
* Update function to retreive Bridge data and store it in this object.
* Consider this the main 'Get' function.
* Typically used for Heartbeat or manual updates of local data.
*/
huepi.prototype.BridgeGetData = function()
{ // GET /api/username -> data.config.whitelist.username
var self = this;
var deferred = $.Deferred();
if (this.Username === '') {
deferred.reject();
} else $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username, success: function(data) {
if (typeof data.config !== 'undefined') { // if able to read Config, Username must be Whitelisted
self.BridgeConfig = data.config;
if (self.BridgeConfig.bridgeid) // SteveyO/Hue-Emulator doesn't supply bridgeid as of yet.
self.BridgeID = self.BridgeConfig.bridgeid.toLowerCase();
else {
self.BridgeID = '';
}
self.BridgeName = self.BridgeConfig.name;
self.Lights = data.lights;
self.LightIds = [];
for (var key in self.Lights)
self.LightIds.push(key);
self.Groups = data.groups;
self.GroupIds = [];
for (key in self.Groups)
self.GroupIds.push(key);
self.Schedules = data.schedules;
self.Scenes = data.scenes;
self.Sensors = data.sensors;
self.Rules = data.rules;
self.BridgeName = self.BridgeConfig.name;
deferred.resolve();
} else { // Username is no longer whitelisted
if (self.Username !== '')
self._BridgeCacheRemoveCurrent();
self.Username = '';
deferred.reject();
}
}, error: function(/*xhr,status,error*/) { // $.ajax failed
deferred.reject();
} });
return deferred.promise();
};
/**
* Whitelists the Username stored in this object.
* Note: a buttonpress on the bridge is requered max 30 sec before this to succeed.
* please only use this once per device, Username is stored in cache.
*
* @param {string} DeviceName - Optional device name to Whitelist.
*/
huepi.prototype.BridgeCreateUser = function(DeviceName)
{ // POST /api {'devicetype': 'AppName#DeviceName' }
var self = this;
var deferred = $.Deferred();
DeviceName = DeviceName || 'WebInterface';
$.ajax({ type: 'POST', dataType: 'json', contentType: 'application/json', url: 'http://' + this.BridgeIP + '/api',
data: '{"devicetype": "huepi#' + DeviceName + '"}', success: function(data) {
if (data[0]) {
if (data[0].success) {
self.Username = data[0].success.username;
self._BridgeCacheAddCurrent();
deferred.resolve();
} else {
deferred.reject();
}
} else {
deferred.reject();
}
}, error: function(/*xhr,status,error*/) { // $.ajax failed
deferred.reject();
} });
return deferred.promise();
};
/**
* @param {string} UsernameToDelete - Username that will be revoked from the Whitelist.
* Note: Username stored in this object need to be Whitelisted to succeed.
*/
huepi.prototype.BridgeDeleteUser = function(UsernameToDelete)
{ // DELETE /api/username/config/whitelist/username {'devicetype': 'iPhone', 'username': '1234567890'}
return $.ajax({
type: 'DELETE',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/config/whitelist/' + UsernameToDelete,
//data: '{'devicetype': 'WebInterface', 'username': '' + this.Username + ''}'
});
};
////////////////////////////////////////////////////////////////////////////////
//
// Helper Functions
//
//
/**
* @param {string} Model
* @returns {boolean} Model is capable of CT
*/
huepi.HelperModelCapableCT = function(Model)
{ // CT Capable LCT* LLM* LTW* LLC020 LST002
var ModelType = Model.slice(0,3);
return ((ModelType === 'LCT') || (ModelType === 'LLM') || (ModelType === 'LTW') || (Model === 'LLC020') || (Model === 'LST002'));
};
/**
* @param {string} Model
* @returns {boolean} Model is capable of XY
*/
huepi.HelperModelCapableXY = function(Model)
{ // XY Capable LCT* LLC* LST* LLM001 LLC020 LST002
var ModelType = Model.slice(0,3);
return ((ModelType === 'LCT') || (ModelType === 'LLC') || (ModelType === 'LST') || (Model === 'LLM001') || (Model === 'LLC020') || (Model === 'LST002'));
};
/**
* @param {float} Red - Range [0..1]
* @param {float} Green - Range [0..1]
* @param {float} Blue - Range [0..1]
* @returns {object} [Ang, Sat, Bri] - Ranges [0..360] [0..1] [0..1]
*/
huepi.HelperRGBtoHueAngSatBri = function(Red, Green, Blue)
{
var Ang, Sat, Bri;
var Min = Math.min(Red, Green, Blue);
var Max = Math.max(Red, Green, Blue);
if (Min !== Max) {
if (Red === Max) {
Ang = (0 + ((Green - Blue) / (Max - Min))) * 60;
} else if (Green === Max) {
Ang = (2 + ((Blue - Red) / (Max - Min))) * 60;
} else {
Ang = (4 + ((Red - Green) / (Max - Min))) * 60;
}
Sat = (Max - Min) / Max;
Bri = Max;
} else { // Max === Min
Ang = 0;
Sat = 0;
Bri = Max;
}
return {Ang: Ang, Sat: Sat, Bri: Bri};
};
/**
* @param {float} Ang - Range [0..360]
* @param {float} Sat - Range [0..1]
* @param {float} Bri - Range [0..1]
* @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
*/
huepi.HelperHueAngSatBritoRGB = function(Ang, Sat, Bri)
{ // Range 360, 1, 1, return .Red, .Green, .Blue
var Red, Green, Blue;
if (Sat === 0) {
Red = Bri;
Green = Bri;
Blue = Bri;
} else
{
var Sector = Math.floor(Ang / 60) % 6;
var Fraction = (Ang / 60) - Sector;
var p = Bri * (1 - Sat);
var q = Bri * (1 - Sat * Fraction);
var t = Bri * (1 - Sat * (1 - Fraction));
switch (Sector) {
case 0:
Red = Bri;
Green = t;
Blue = p;
break;
case 1:
Red = q;
Green = Bri;
Blue = p;
break;
case 2:
Red = p;
Green = Bri;
Blue = t;
break;
case 3:
Red = p;
Green = q;
Blue = Bri;
break;
case 4:
Red = t;
Green = p;
Blue = Bri;
break;
default: // case 5:
Red = Bri;
Green = p;
Blue = q;
break;
}
}
return {Red: Red, Green: Green, Blue: Blue};
};
/**
* @param {float} Red - Range [0..1]
* @param {float} Green - Range [0..1]
* @param {float} Blue - Range [0..1]
* @returns {number} Temperature ranges [2200..6500]
*/
huepi.HelperRGBtoColortemperature = function(Red, Green, Blue)
{ // Approximation from https://github.com/neilbartlett/color-temperature/blob/master/index.js
var Temperature;
var TestRGB;
var Epsilon = 0.4;
var MinTemperature = 2200;
var MaxTemperature = 6500;
while ( (MaxTemperature - MinTemperature) > Epsilon) {
Temperature = (MaxTemperature + MinTemperature) / 2;
TestRGB = huepi.HelperColortemperaturetoRGB(Temperature);
if ((TestRGB.Blue / TestRGB.Red) >= (Blue / Red)) {
MaxTemperature = Temperature;
} else {
MinTemperature = Temperature;
}
}
return Math.round(Temperature);
};
/**
* @param {number} Temperature ranges [1000..6600]
* @returns {object} [Red, Green, Blue] ranges [0..1] [0..1] [0..1]
*/
huepi.HelperColortemperaturetoRGB = function(Temperature)
{ // http://www.tannerhelland.com/4435/convert-temperature-rgb-algorithm-code/
// Update Available: https://github.com/neilbartlett/color-temperature/blob/master/index.js
var Red, Green, Blue;
Temperature = Temperature / 100;
if (Temperature <= 66)
Red = /*255;*/ 165+90*((Temperature)/(66));
else {
Red = Temperature - 60;
Red = 329.698727466 * Math.pow(Red, -0.1332047592);
if (Red < 0)
Red = 0;
if (Red > 255)
Red = 255;
}
if (Temperature <= 66) {
Green = Temperature;
Green = 99.4708025861 * Math.log(Green) - 161.1195681661;
if (Green < 0)
Green = 0;
if (Green > 255)
Green = 255;
} else {
Green = Temperature - 60;
Green = 288.1221695283 * Math.pow(Green, -0.0755148492);
if (Green < 0)
Green = 0;
if (Green > 255)
Green = 255;
}
if (Temperature >= 66)
Blue = 255;
else {
if (Temperature <= 19)
Blue = 0;
else {
Blue = Temperature - 10;
Blue = 138.5177312231 * Math.log(Blue) - 305.0447927307;
if (Blue < 0)
Blue = 0;
if (Blue > 255)
Blue = 255;
}
}
return {Red: Red/255, Green: Green/255, Blue: Blue/255};
};
/**
* @param {float} Red - Range [0..1]
* @param {float} Green - Range [0..1]
* @param {float} Blue - Range [0..1]
* @returns {object} [x, y] - Ranges [0..1] [0..1]
*/
huepi.HelperRGBtoXY = function(Red, Green, Blue)
{ // Source: https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
// Apply gamma correction
if (Red > 0.04045)
Red = Math.pow((Red + 0.055) / (1.055), 2.4);
else
Red = Red / 12.92;
if (Green > 0.04045)
Green = Math.pow((Green + 0.055) / (1.055), 2.4);
else
Green = Green / 12.92;
if (Blue > 0.04045)
Blue = Math.pow((Blue + 0.055) / (1.055), 2.4);
else
Blue = Blue / 12.92;
// RGB to XYZ [M] for Wide RGB D65, http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
var X = Red * 0.664511 + Green * 0.154324 + Blue * 0.162028;
var Y = Red * 0.283881 + Green * 0.668433 + Blue * 0.047685;
var Z = Red * 0.000088 + Green * 0.072310 + Blue * 0.986039;
// But we don't want Capital X,Y,Z you want lowercase [x,y] (called the color point) as per:
if ((X + Y + Z) === 0)
return {x: 0, y: 0};
return {x: X / (X + Y + Z), y: Y / (X + Y + Z)};
};
/**
* @param {float} x
* @param {float} y
* @param {float} Brightness Optional
* @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
*/
huepi.HelperXYtoRGB = function(x, y, Brightness)
{ // Source: https://github.com/PhilipsHue/PhilipsHueSDK-iOS-OSX/blob/master/ApplicationDesignNotes/RGB%20to%20xy%20Color%20conversion.md
Brightness = Brightness || 1.0; // Default full brightness
var z = 1.0 - x - y;
var Y = Brightness;
var X = (Y / y) * x;
var Z = (Y / y) * z;
// XYZ to RGB [M]-1 for Wide RGB D65, http://www.developers.meethue.com/documentation/color-conversions-rgb-xy
var Red = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
var Green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
var Blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
// Limit RGB on [0..1]
if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
Green = Green / Red;
Blue = Blue / Red;
Red = 1.0;
}
if (Red < 0)
Red = 0;
if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
Red = Red / Green;
Blue = Blue / Green;
Green = 1.0;
}
if (Green < 0)
Green = 0;
if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
Red = Red / Blue;
Green = Green / Blue;
Blue = 1.0;
}
if (Blue < 0)
Blue = 0;
// Apply reverse gamma correction
if (Red <= 0.0031308) {
Red = Red * 12.92;
} else {
Red = 1.055 * Math.pow(Red, (1.0 / 2.4)) - 0.055;
}
if (Green <= 0.0031308) {
Green = Green * 12.92;
} else {
Green = 1.055 * Math.pow(Green, (1.0 / 2.4)) - 0.055;
}
if (Blue <= 0.0031308) {
Blue = Blue * 12.92;
} else {
Blue = 1.055 * Math.pow(Blue, (1.0 / 2.4)) - 0.055;
}
// Limit RGB on [0..1]
if (Red > Blue && Red > Green && Red > 1.0) { // Red is too big
Green = Green / Red;
Blue = Blue / Red;
Red = 1.0;
}
if (Red < 0)
Red = 0;
if (Green > Blue && Green > Red && Green > 1.0) { // Green is too big
Red = Red / Green;
Blue = Blue / Green;
Green = 1.0;
}
if (Green < 0)
Green = 0;
if (Blue > Red && Blue > Green && Blue > 1.0) { // Blue is too big
Red = Red / Blue;
Green = Green / Blue;
Blue = 1.0;
}
if (Blue < 0)
Blue = 0;
return {Red: Red, Green: Green, Blue: Blue};
};
/**
* @param {float} x
* @param {float} y
* @param {float} Brightness Optional
* @param {string} Model - Modelname of the Light
* @returns {object} [Red, Green, Blue] - Ranges [0..1] [0..1] [0..1]
*/
huepi.HelperXYtoRGBforModel = function(x, y, Brightness, Model)
{
var GamutCorrected = huepi.HelperGamutXYforModel(x, y, Model);
return huepi.HelperXYtoRGB(GamutCorrected.x, GamutCorrected.y, Brightness);
};
/**
* Tests if the Px,Py resides within the Gamut for the model.
* Otherwise it will calculated the closesed point on the Gamut.
* @param {float} Px - Range [0..1]
* @param {float} Py - Range [0..1]
* @param {string} Model - Modelname of the Light to Gamutcorrect Px, Py for
* @returns {object} [x, y] - Ranges [0..1] [0..1]
*/
huepi.HelperGamutXYforModel = function(Px, Py, Model)
{ // https://developers.meethue.com/documentation/supported-lights
Model = Model || 'LCT001'; // default hue Bulb 2012
var ModelType = Model.slice(0,3);
var PRed, PGreen, PBlue;
var NormDot;
if ( ((ModelType === 'LST') || (ModelType === 'LLC')) && (Model !=='LLC020') && (Model !=='LLC002') && (Model !== 'LST002') ) { // For LivingColors Bloom, Aura and Iris etc the triangle corners are:
PRed = {x: 0.704, y: 0.296}; // Gamut A
PGreen = {x: 0.2151, y: 0.7106};
PBlue = {x: 0.138, y: 0.080};
} else if ( ((ModelType === 'LCT') || (ModelType === 'LLM')) && (Model !=='LCT010') && (Model !=='LCT014') && (Model !=='LCT011') && (Model !=='LCT012') ) { // For the hue bulb and beyond led modules etc the corners of the triangle are:
PRed = {x: 0.675, y: 0.322}; // Gamut B
PGreen = {x: 0.409, y: 0.518};
PBlue = {x: 0.167, y: 0.040};
} else { // Exceptions and Unknown default to
PRed = {x: 0.692, y: 0.308}; // Gamut C
PGreen = {x: 0.17, y: 0.7};
PBlue = {x: 0.153, y: 0.048};
}
var VBR = {x: PRed.x - PBlue.x, y: PRed.y - PBlue.y}; // Blue to Red
var VRG = {x: PGreen.x - PRed.x, y: PGreen.y - PRed.y}; // Red to Green
var VGB = {x: PBlue.x - PGreen.x, y: PBlue.y - PGreen.y}; // Green to Blue
var GBR = (PGreen.x - PBlue.x) * VBR.y - (PGreen.y - PBlue.y) * VBR.x; // Sign Green on Blue to Red
var BRG = (PBlue.x - PRed.x) * VRG.y - (PBlue.y - PRed.y) * VRG.x; // Sign Blue on Red to Green
var RGB = (PRed.x - PGreen.x) * VGB.y - (PRed.y - PGreen.y) * VGB.x; // Sign Red on Green to Blue
var VBP = {x: Px - PBlue.x, y: Py - PBlue.y}; // Blue to Point
var VRP = {x: Px - PRed.x, y: Py - PRed.y}; // Red to Point
var VGP = {x: Px - PGreen.x, y: Py - PGreen.y}; // Green to Point
var PBR = VBP.x * VBR.y - VBP.y * VBR.x; // Sign Point on Blue to Red
var PRG = VRP.x * VRG.y - VRP.y * VRG.x; // Sign Point on Red to Green
var PGB = VGP.x * VGB.y - VGP.y * VGB.x; // Sign Point on Green to Blue
if ((GBR * PBR >= 0) && (BRG * PRG >= 0) && (RGB * PGB >= 0)) // All Signs Match so Px,Py must be in triangle
return {x: Px, y: Py};
// Outside Triangle, Find Closesed point on Edge or Pick Vertice...
else if (GBR * PBR <= 0) { // Outside Blue to Red
NormDot = (VBP.x * VBR.x + VBP.y * VBR.y) / (VBR.x * VBR.x + VBR.y * VBR.y);
if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
return {x: PBlue.x + NormDot * VBR.x, y: PBlue.y + NormDot * VBR.y};
else if (NormDot < 0.0) // Outside Edge, Pick Vertice
return {x: PBlue.x, y: PBlue.y}; // Start
else
return {x: PRed.x, y: PRed.y}; // End
}
else if (BRG * PRG <= 0) { // Outside Red to Green
NormDot = (VRP.x * VRG.x + VRP.y * VRG.y) / (VRG.x * VRG.x + VRG.y * VRG.y);
if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
return {x: PRed.x + NormDot * VRG.x, y: PRed.y + NormDot * VRG.y};
else if (NormDot < 0.0) // Outside Edge, Pick Vertice
return {x: PRed.x, y: PRed.y}; // Start
else
return {x: PGreen.x, y: PGreen.y}; // End
}
else if (RGB * PGB <= 0) { // Outside Green to Blue
NormDot = (VGP.x * VGB.x + VGP.y * VGB.y) / (VGB.x * VGB.x + VGB.y * VGB.y);
if ((NormDot >= 0.0) && (NormDot <= 1.0)) // Within Edge
return {x: PGreen.x + NormDot * VGB.x, y: PGreen.y + NormDot * VGB.y};
else if (NormDot < 0.0) // Outside Edge, Pick Vertice
return {x: PGreen.x, y: PGreen.y}; // Start
else
return {x: PBlue.x, y: PBlue.y}; // End
}
};
/**
* @param {float} Ang - Range [0..360]
* @param {float} Sat - Range [0..1]
* @param {float} Bri - Range [0..1]
* @returns {number} Temperature ranges [2200..6500]
*/
huepi.HelperHueAngSatBritoColortemperature = function(Ang, Sat, Bri)
{
var RGB = huepi.HelperHueAngSatBritoRGB(Ang, Sat, Bri);
return huepi.HelperRGBtoColortemperature(RGB.Red, RGB.Green, RGB.Blue);
};
/**
* @param {number} Temperature ranges [1000..6600]
* @returns {object} [Ang, Sat, Bri] - Ranges [0..360] [0..1] [0..1]
*/
huepi.HelperColortemperaturetoHueAngSatBri = function(Temperature)
{
var RGB = huepi.HelperColortemperaturetoRGB(Temperature);
return huepi.HelperRGBtoHueAngSatBri(RGB.Red, RGB.Green, RGB.Blue);
};
/**
* @param {float} x
* @param {float} y
* @param {float} Brightness Optional
* @returns {number} Temperature ranges [1000..6600]
*/
huepi.HelperXYtoColortemperature = function(x, y, Brightness)
{
var RGB = huepi.HelperXYtoRGB(x, y, Brightness);
return huepi.HelperRGBtoColortemperature(RGB.Red, RGB.Green, RGB.Blue);
};
/**
* @param {number} Temperature ranges [1000..6600]
* @returns {object} [x, y] - Ranges [0..1] [0..1]
*/
huepi.HelperColortemperaturetoXY = function(Temperature)
{
var RGB = huepi.HelperColortemperaturetoRGB(Temperature);
return huepi.HelperRGBtoXY(RGB.Red, RGB.Green, RGB.Blue);
};
/**
* @param {number} CT in Mired (micro reciprocal degree)
* @returns {number} ColorTemperature
*/
huepi.HelperCTtoColortemperature = function(CT)
{
return Math.round(1000000 / CT);
};
/**
* @param {number} ColorTemperature
* @returns {number} CT in Mired (micro reciprocal degree)
*/
huepi.HelperColortemperaturetoCT = function(Temperature)
{
return Math.round(1000000 / Temperature);
};
/**
* @param {multiple} Items - Items to convert to StringArray
* @returns {string} String array containing Items
*/
huepi.HelperToStringArray = function(Items) {
if (typeof Items === 'number') {
return '"' + Items.toString() + '"';
} else if (Object.prototype.toString.call(Items) === '[object Array]') {
var Result = '[';
for (var ItemNr = 0; ItemNr < Items.length; ItemNr++) {
Result += huepi.HelperToStringArray(Items[ItemNr]);
if (ItemNr < Items.length - 1)
Result += ',';
}
Result = Result + ']';
return Result;
} else if (typeof Items === 'string') {
return '"' + Items + '"';
}
};
////////////////////////////////////////////////////////////////////////////////
//
// huepi.Lightstate Object
//
//
/**
* huepi.Lightstate Object.
* Internal object to recieve all settings that are about to be send to the Bridge as a string.
*
* @class
*/
huepi.Lightstate = function()
{
///** */
////this.SetOn = function(On) {
// this.on = On;
//};
/** */
this.On = function() {
this.on = true;
return this;
};
/** */
this.Off = function() {
this.on = false;
return this;
};
/*
* @param {number} Hue Range [0..65535]
* @param {float} Saturation Range [0..255]
* @param {float} Brightness Range [0..255]
*/
this.SetHSB = function(Hue, Saturation, Brightness) { // Range 65535, 255, 255
this.hue = Math.round(Hue);
this.sat = Math.round(Saturation);
this.bri = Math.round(Brightness);
return this;
};
/**
* @param {number} Hue Range [0..65535]
*/
this.SetHue = function(Hue) {
this.hue = Math.round(Hue);
return this;
};
/**
* @param {float} Saturation Range [0..255]
*/
this.SetSaturation = function(Saturation) {
this.sat = Math.round(Saturation);
return this;
};
/**
* @param {float} Brightness Range [0..255]
*/
this.SetBrightness = function(Brightness) {
this.bri = Math.round(Brightness);
return this;
};
/**
* @param {float} Ang Range [0..360]
* @param {float} Sat Range [0..1]
* @param {float} Bri Range [0..1]
*/
this.SetHueAngSatBri = function(Ang, Sat, Bri) {
// In: Hue in Deg, Saturation, Brightness 0.0-1.0 Transform To Philips Hue Range...
while (Ang < 0)
Ang = Ang + 360;
Ang = Ang % 360;
return this.SetHSB(Math.round(Ang / 360 * 65535), Math.round(Sat * 255), Math.round(Bri * 255));
};
/**
* @param {number} Red Range [0..1]
* @param {number} Green Range [0..1]
* @param {number} Blue Range [0..1]
*/
this.SetRGB = function(Red, Green, Blue) {
var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
return this.SetHueAngSatBri(HueAngSatBri.Ang, HueAngSatBri.Sat, HueAngSatBri.Bri);
};
/**
* @param {number} Ct Micro Reciprocal Degree of Colortemperature (Ct = 10^6 / Colortemperature)
*/
this.SetCT = function(Ct) {
this.ct = Math.round(Ct);
return this;
};
/**
* @param {number} Colortemperature Range [2200..6500] for the 2012 lights
*/
this.SetColortemperature = function(Colortemperature) {
return this.SetCT(huepi.HelperColortemperaturetoCT(Colortemperature));
};
/**
* @param {float} X
* @param {float} Y
*/
this.SetXY = function(X, Y) {
this.xy = [X, Y];
return this;
};
///** */
//this.SetAlert = function(Alert) {
// this.alert = Alert;
//};
/** */
this.AlertSelect = function() {
this.alert = 'select';
return this;
};
/** */
this.AlertLSelect = function() {
this.alert = 'lselect';
return this;
};
/** */
this.AlertNone = function() {
this.alert = 'none';
return this;
};
///** */
//this.SetEffect = function(Effect) {
// this.effect = Effect;
//};
/** */
this.EffectColorloop = function() {
this.effect = 'colorloop';
return this;
};
/** */
this.EffectNone = function() {
this.effect = 'none';
return this;
};
/**
* @param {number} Transitiontime Optional Transitiontime in multiple of 100ms, defaults to 4 (on bridge, meaning 400 ms)
*/
this.SetTransitiontime = function(Transitiontime) {
if (typeof Transitiontime !== 'undefined') // Optional Parameter
this.transitiontime = Transitiontime;
return this;
};
/**
* @returns {string} Stringified version of the content of LightState ready to be sent to the Bridge.
*/
this.Get = function() {
return JSON.stringify(this);
};
};
////////////////////////////////////////////////////////////////////////////////
//
// Light Functions
//
//
/**
* @param {number} LightNr - LightNr
* @returns {string} LightId
*/
huepi.prototype.LightGetId = function(LightNr)
{
if (typeof LightNr === 'number')
if (LightNr <= this.LightIds.length)
return this.LightIds[LightNr-1];
return LightNr;
};
/**
* @param {string} LightId - LightId
* @returns {number} LightNr
*/
huepi.prototype.LightGetNr = function(LightId)
{
if (typeof LightId === 'string')
return this.LightIds.indexOf(LightId) + 1;
return LightId;
};
/**
*/
huepi.prototype.LightsGetData = function()
{ // GET /api/username/lights
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights', success: function(data) {
if (data) {
self.Lights = data;
self.LightIds = [];
for (var key in self.Lights)
self.LightIds.push(key);
}
}
});
};
/**
*/
huepi.prototype.LightsSearchForNew = function()
{ // POST /api/username/lights
return $.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights'
});
};
/**
*/
huepi.prototype.LightsGetNew = function()
{ // GET /api/username/lights/new
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/new', success: function(/*data*/) {} });
};
/**
* @param {number} LightNr
* @param {string} Name New name of the light Range [1..32]
*/
huepi.prototype.LightSetName = function(LightNr, Name)
{ // PUT /api/username/lights
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/' + this.LightGetId(LightNr),
data: '{"name" : "' + Name + '"}'
});
};
/**
* @param {number} LightNr
* @param {LightState} State
*/
huepi.prototype.LightSetState = function(LightNr, State)
{ // PUT /api/username/lights/[LightNr]/state
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/lights/' + this.LightGetId(LightNr) + '/state',
data: State.Get()
});
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightOn = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.On();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightOff = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.Off();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* Sets Gamut Corrected values for HSB
* @param {number} LightNr
* @param {number} Hue Range [0..65535]
* @param {number} Saturation Range [0..255]
* @param {number} Brightness Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetHSB = function(LightNr, Hue, Saturation, Brightness, Transitiontime)
{
var HueAng = Hue * 360 / 65535;
var Sat = Saturation / 255;
var Bri = Brightness / 255;
var Color = huepi.HelperHueAngSatBritoRGB(HueAng, Sat, Bri);
var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);
return $.when(
this.LightSetBrightness(LightNr, Brightness, Transitiontime),
this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime)
);
};
/**
* @param {number} LightNr
* @param {number} Hue Range [0..65535]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetHue = function(LightNr, Hue, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetHue(Hue);
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param Saturation Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetSaturation = function(LightNr, Saturation, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetSaturation(Saturation);
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param Brightness Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetBrightness = function(LightNr, Brightness, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetBrightness(Brightness);
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param Ang Range [0..360]
* @param Sat Range [0..1]
* @param Bri Range [0..1]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetHueAngSatBri = function(LightNr, Ang, Sat, Bri, Transitiontime)
{ // In: Hue in Deg, Saturation, Brightness 0.0-1.0 Transform To Philips Hue Range...
while (Ang < 0)
Ang = Ang + 360;
Ang = Ang % 360;
return this.LightSetHSB(LightNr, Ang / 360 * 65535, Sat * 255, Bri * 255, Transitiontime);
};
/**
* @param {number} LightNr
* @param Red Range [0..1]
* @param Green Range [0..1]
* @param Blue Range [0..1]
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetRGB = function(LightNr, Red, Green, Blue, Transitiontime)
{
var Point = huepi.HelperRGBtoXY(Red, Green, Blue);
var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
return $.when(
this.LightSetBrightness(LightNr, HueAngSatBri.Bri * 255),
this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime)
);
};
/**
* @param {number} LightNr
* @param {number} CT micro reciprocal degree
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetCT = function(LightNr, CT, Transitiontime)
{
var Model = this.Lights[this.LightGetId(LightNr)].modelid;
if (huepi.HelperModelCapableCT(Model)) {
var State = new huepi.Lightstate();
State.SetCT(CT);
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
} else if (huepi.HelperModelCapableXY(Model))
{ // hue CT Incapable Lights: CT->RGB->XY to ignore Brightness in RGB}
var Color = huepi.HelperColortemperaturetoRGB(huepi.HelperCTtoColortemperature(CT));
var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);
return this.LightSetXY(LightNr, Point.x, Point.y, Transitiontime);
}
};
/**
* @param {number} LightNr
* @param {number} Colortemperature Range [2200..6500] for the 2012 model
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetColortemperature = function(LightNr, Colortemperature, Transitiontime)
{
return this.LightSetCT(LightNr, huepi.HelperColortemperaturetoCT(Colortemperature), Transitiontime);
};
/**
* @param {number} LightNr
* @param {float} X
* @param {float} Y
* @param {number} Transitiontime optional
*/
huepi.prototype.LightSetXY = function(LightNr, X, Y, Transitiontime)
{
var Model = this.Lights[this.LightGetId(LightNr)].modelid;
if (huepi.HelperModelCapableXY(Model)) {
var State = new huepi.Lightstate();
var Gamuted = huepi.HelperGamutXYforModel(X, Y, Model);
State.SetXY(Gamuted.x, Gamuted.y);
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
} else if (huepi.HelperModelCapableCT(Model)) {
// hue XY Incapable Lights: XY->RGB->CT to ignore Brightness in RGB
var Color = huepi.HelperXYtoRGB(X, Y);
var Colortemperature = huepi.HelperRGBtoColortemperature(Color.Red, Color.Green, Color.Blue);
return this.LightSetColortemperature(LightNr, Colortemperature, Transitiontime);
}
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightAlertSelect = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertSelect();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightAlertLSelect = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertLSelect();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightAlertNone = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertNone();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightEffectColorloop = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.EffectColorloop();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
/**
* @param {number} LightNr
* @param {number} Transitiontime optional
*/
huepi.prototype.LightEffectNone = function(LightNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.EffectNone();
State.SetTransitiontime(Transitiontime);
return this.LightSetState(LightNr, State);
};
////////////////////////////////////////////////////////////////////////////////
//
// Group Functions
//
//
/**
* @param {number} GroupNr - GroupNr
* @returns {string} GroupId
*/
huepi.prototype.GroupGetId = function(GroupNr)
{
if (typeof GroupNr === 'number')
if (GroupNr === 0)
return '0';
else if (GroupNr > 0)
if (GroupNr <= this.GroupIds.length)
return this.GroupIds[GroupNr-1];
return GroupNr;
};
/**
* @param {string} GroupId - GroupId
* @returns {number} GroupNr
*/
huepi.prototype.GroupGetNr = function(GroupId)
{
if (typeof GroupId === 'string')
return this.GroupIds.indexOf(GroupId) + 1;
return GroupId;
};
/**
*/
huepi.prototype.GroupsGetData = function()
{ // GET /api/username/groups
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups', success: function(data) {
if (data) {
self.Groups = data;
self.GroupIds = [];
for (var key in self.Groups)
self.GroupIds.push(key);
}
}
});
};
/**
*/
huepi.prototype.GroupsGetZero = function()
{ // GET /api/username/groups/0
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/0', success: function(data) {
if (data) {
self.Groups['0'] = data;
}
}
});
};
/**
* Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
* @param {string} Name New name of the light Range [1..32]
* @param {multiple} Lights LightNr or Array of Lights to Group
*/
huepi.prototype.GroupCreate = function(Name, Lights)
{ // POST /api/username/groups
return $.ajax({
type: 'POST',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/',
data: '{"name": "' + Name + '" , "lights":' + huepi.HelperToStringArray(Lights) + '}'
});
};
/**
* @param {number} GroupNr
* @param {string} Name New name of the light Range [1..32]
*/
huepi.prototype.GroupSetName = function(GroupNr, Name)
{ // PUT /api/username/groups/[GroupNr]
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
data: '{"name": "' + Name + '"}'
});
};
/**
* Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
* @param {number} GroupNr
* @param {multiple} Lights LightNr or Array of Lights to Group
*/
huepi.prototype.GroupSetLights = function(GroupNr, Lights)
{ // PUT /api/username/groups/[GroupNr]
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
data: '{"lights":' + huepi.HelperToStringArray(Lights) + '}'
});
};
/**
* Note: Bridge doesn't accept lights in a Group that are unreachable at moment of creation
* @param {number} GroupNr
* @param {string} Name New name of the light Range [1..32]
* @param {multiple} Lights LightNr or Array of Lights to Group
*/
huepi.prototype.GroupSetAttributes = function(GroupNr, Name, Lights)
{ // PUT /api/username/groups/[GroupNr]
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr),
data: '{"name": "' +Name + '", "lights":' + huepi.HelperToStringArray(Lights) + '}'
});
};
/**
* @param {number} GroupNr
*/
huepi.prototype.GroupDelete = function(GroupNr)
{ // DELETE /api/username/groups/[GroupNr]
return $.ajax({
type: 'DELETE',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr)
});
};
/**
* @param {number} GroupNr
* @param {LightState} State
*/
huepi.prototype.GroupSetState = function(GroupNr, State)
{ // PUT /api/username/groups/[GroupNr]/action
return $.ajax({
type: 'PUT',
dataType: 'json',
contentType: 'application/json',
url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/groups/' + this.GroupGetId(GroupNr) + '/action',
data: State.Get()
});
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupOn = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.On();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupOff = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.Off();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* Sets Gamut Corrected values for HSB
* @param {number} GroupNr
* @param {number} Hue Range [0..65535]
* @param {number} Saturation Range [0..255]
* @param {number} Brightness Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetHSB = function(GroupNr, Hue, Saturation, Brightness, Transitiontime)
{
var Ang = Hue * 360 / 65535;
var Sat = Saturation / 255;
var Bri = Brightness / 255;
var Color = huepi.HelperHueAngSatBritoRGB(Ang, Sat, Bri);
var Point = huepi.HelperRGBtoXY(Color.Red, Color.Green, Color.Blue);
return $.when(// return Deferred when of both Brightness and XY
this.GroupSetBrightness(GroupNr, Brightness, Transitiontime),
this.GroupSetXY(GroupNr, Point.x, Point.y, Transitiontime)
);
};
/**
* @param {number} GroupNr
* @param {number} Hue Range [0..65535]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetHue = function(GroupNr, Hue, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetHue(Hue);
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param Saturation Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetSaturation = function(GroupNr, Saturation, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetSaturation(Saturation);
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param Brightness Range [0..255]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetBrightness = function(GroupNr, Brightness, Transitiontime)
{
var State = new huepi.Lightstate();
State.SetBrightness(Brightness);
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param Ang Range [0..360]
* @param Sat Range [0..1]
* @param Bri Range [0..1]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetHueAngSatBri = function(GroupNr, Ang, Sat, Bri, Transitiontime)
{
while (Ang < 0)
Ang = Ang + 360;
Ang = Ang % 360;
return this.GroupSetHSB(GroupNr, Ang / 360 * 65535, Sat * 255, Bri * 255, Transitiontime);
};
/**
* @param {number} GroupNr
* @param Red Range [0..1]
* @param Green Range [0..1]
* @param Blue Range [0..1]
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetRGB = function(GroupNr, Red, Green, Blue, Transitiontime)
{
var HueAngSatBri = huepi.HelperRGBtoHueAngSatBri(Red, Green, Blue);
return this.GroupSetHueAngSatBri(GroupNr, HueAngSatBri.Ang, HueAngSatBri.Sat, HueAngSatBri.Bri, Transitiontime);
};
/**
* @param {number} GroupNr
* @param {number} CT micro reciprocal degree
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetCT = function(GroupNr, CT, Transitiontime)
{
var Lights = [];
GroupNr = this.GroupGetId(GroupNr);
if (GroupNr === '0') // All Lights
Lights = this.LightIds;
else
Lights = this.Groups[GroupNr].lights;
if (Lights.length !== 0) {
var deferreds = [];
for (var LightNr=0; LightNr<Lights.length; LightNr++)
deferreds.push(this.LightSetCT(Lights[LightNr], CT, Transitiontime));
return $.when.apply($, deferreds); // return Deferred when with array of deferreds
}
// No Lights in Group GroupNr, Set State of Group to let Bridge create the API Error and return it.
var State = new huepi.Lightstate();
State.SetCT(CT);
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Colortemperature Range [2200..6500] for the 2012 model
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetColortemperature = function(GroupNr, Colortemperature, Transitiontime)
{
return this.GroupSetCT(GroupNr, huepi.HelperColortemperaturetoCT(Colortemperature), Transitiontime);
};
/**
* @param {number} GroupNr
* @param {float} X
* @param {float} Y
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupSetXY = function(GroupNr, X, Y, Transitiontime)
{
var Lights = [];
GroupNr = this.GroupGetId(GroupNr);
if (GroupNr === '0') // All Lights
Lights = this.LightIds;
else
Lights = this.Groups[GroupNr].lights;
if (Lights.length !== 0) {
var deferreds = [];
for (var LightNr=0; LightNr<Lights.length; LightNr++)
deferreds.push(this.LightSetXY(Lights[LightNr], X, Y, Transitiontime));
return $.when.apply($, deferreds); // return Deferred when with array of deferreds
}
// No Lights in Group GroupNr, Set State of Group to let Bridge create the API Error and return it.
var State = new huepi.Lightstate();
State.SetXY(X, Y);
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupAlertSelect = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertSelect();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupAlertLSelect = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertLSelect();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupAlertNone = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.AlertNone();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupEffectColorloop = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.EffectColorloop();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
/**
* @param {number} GroupNr
* @param {number} Transitiontime optional
*/
huepi.prototype.GroupEffectNone = function(GroupNr, Transitiontime)
{
var State = new huepi.Lightstate();
State.EffectNone();
State.SetTransitiontime(Transitiontime);
return this.GroupSetState(GroupNr, State);
};
////////////////////////////////////////////////////////////////////////////////
//
// Schedule Functions
//
//
/**
*/
huepi.prototype.SchedulesGetData = function()
{ // GET /api/username/schedules
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/schedules', success: function(data) {
if (data) {
self.Schedules = data;
}
}
});
};
////////////////////////////////////////////////////////////////////////////////
//
// Scenes Functions
//
//
/**
*/
huepi.prototype.ScenesGetData = function()
{ // GET /api/username/scenes
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/scenes', success: function(data) {
if (data) {
self.Scenes = data;
}
}
});
};
////////////////////////////////////////////////////////////////////////////////
//
// Sensors Functions
//
//
/**
*/
huepi.prototype.SensorsGetData = function()
{ // GET /api/username/sensors
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/sensors', success: function(data) {
if (data) {
self.Sensors = data;
}
}
});
};
////////////////////////////////////////////////////////////////////////////////
//
// Rules Functions
//
//
/**
*/
huepi.prototype.RulesGetData = function()
{ // GET /api/username/rules
var self = this;
return $.ajax({ type: 'GET', url: 'http://' + this.BridgeIP + '/api/' + this.Username + '/rules', success: function(data) {
if (data) {
self.Rules = data;
}
}
});
};
return huepi;
})();