UNPKG

huepi

Version:

hue (Philips Wireless Lighting) Api interface for JavaScript

1,542 lines (1,428 loc) 61.7 kB
(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)