node-red-contrib-huemagic 2.8.6 → 4.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +807 -766
  3. package/examples/Hue Bridge.json +189 -35
  4. package/examples/Hue Brightness.json +124 -14
  5. package/examples/Hue Buttons.json +88 -0
  6. package/examples/Hue Group.json +575 -63
  7. package/examples/Hue Light.json +805 -58
  8. package/examples/Hue Magic.json +69 -24
  9. package/examples/Hue Motion.json +110 -42
  10. package/examples/Hue Rules.json +105 -37
  11. package/examples/Hue Scene.json +145 -28
  12. package/examples/Hue Temperature.json +124 -14
  13. package/huemagic/animations/001-sunny.json +22 -22
  14. package/huemagic/animations/002-cloudy.json +46 -46
  15. package/huemagic/animations/003-nightsky.json +20 -20
  16. package/huemagic/animations/004-sunrise.json +54 -54
  17. package/huemagic/animations/005-sunset.json +46 -46
  18. package/huemagic/animations/006-fireplace.json +74 -74
  19. package/huemagic/animations/007-spring.json +31 -31
  20. package/huemagic/animations/008-autmn.json +23 -23
  21. package/huemagic/animations/009-winter.json +23 -23
  22. package/huemagic/animations/010-dirtyfog.json +23 -23
  23. package/huemagic/animations/011-lgbt.json +49 -50
  24. package/huemagic/animations/012-rasta.json +28 -29
  25. package/huemagic/animations/013-strobo.json +30 -31
  26. package/huemagic/animations/014-randomrainbow.json +30 -31
  27. package/huemagic/animations/015-sos.json +158 -159
  28. package/huemagic/animations/016-milkyway.json +83 -0
  29. package/huemagic/animations/017-beach.json +40 -0
  30. package/huemagic/animations/018-forest.json +43 -0
  31. package/huemagic/animations/019-christmas.json +30 -0
  32. package/huemagic/animations/020-superhero.json +56 -0
  33. package/huemagic/animations/021-neoncity.json +52 -0
  34. package/huemagic/animations/previews/001-sunny.gif +0 -0
  35. package/huemagic/animations/previews/002-cloudy.gif +0 -0
  36. package/huemagic/animations/previews/003-nightsky.gif +0 -0
  37. package/huemagic/animations/previews/004-sunrise.gif +0 -0
  38. package/huemagic/animations/previews/005-sunset.gif +0 -0
  39. package/huemagic/animations/previews/006-fireplace.gif +0 -0
  40. package/huemagic/animations/previews/007-spring.gif +0 -0
  41. package/huemagic/animations/previews/008-autmn.gif +0 -0
  42. package/huemagic/animations/previews/009-winter.gif +0 -0
  43. package/huemagic/animations/previews/010-dirtyfog.gif +0 -0
  44. package/huemagic/animations/previews/011-lgbt.gif +0 -0
  45. package/huemagic/animations/previews/012-rasta.gif +0 -0
  46. package/huemagic/animations/previews/013-strobo.gif +0 -0
  47. package/huemagic/animations/previews/014-randomrainbow.gif +0 -0
  48. package/huemagic/animations/previews/015-sos.gif +0 -0
  49. package/huemagic/animations/previews/016-milkyway.gif +0 -0
  50. package/huemagic/animations/previews/017-beach.gif +0 -0
  51. package/huemagic/animations/previews/018-forest.gif +0 -0
  52. package/huemagic/animations/previews/019-christmas.gif +0 -0
  53. package/huemagic/animations/previews/020-superhero.gif +0 -0
  54. package/huemagic/animations/previews/021-neoncity.gif +0 -0
  55. package/huemagic/hue-bridge-config.html +105 -61
  56. package/huemagic/hue-bridge-config.js +629 -320
  57. package/huemagic/hue-bridge.html +41 -21
  58. package/huemagic/hue-bridge.js +191 -398
  59. package/huemagic/hue-brightness.html +130 -49
  60. package/huemagic/hue-brightness.js +131 -35
  61. package/huemagic/hue-buttons.html +197 -0
  62. package/huemagic/hue-buttons.js +152 -0
  63. package/huemagic/hue-group.html +137 -60
  64. package/huemagic/hue-group.js +394 -368
  65. package/huemagic/hue-light.html +133 -59
  66. package/huemagic/hue-light.js +551 -337
  67. package/huemagic/hue-magic.html +91 -240
  68. package/huemagic/hue-magic.js +57 -27
  69. package/huemagic/hue-motion.html +128 -50
  70. package/huemagic/hue-motion.js +116 -87
  71. package/huemagic/hue-rules.html +121 -50
  72. package/huemagic/hue-rules.js +84 -64
  73. package/huemagic/hue-scene.html +96 -115
  74. package/huemagic/hue-scene.js +65 -96
  75. package/huemagic/hue-temperature.html +130 -49
  76. package/huemagic/hue-temperature.js +136 -32
  77. package/huemagic/icons/hue-bridge.png +0 -0
  78. package/huemagic/icons/hue-bridge.svg +3 -0
  79. package/huemagic/icons/hue-brightness.png +0 -0
  80. package/huemagic/icons/hue-brightness.svg +11 -0
  81. package/huemagic/icons/hue-buttons.png +0 -0
  82. package/huemagic/icons/hue-buttons.svg +4 -0
  83. package/huemagic/icons/hue-group.png +0 -0
  84. package/huemagic/icons/hue-group.svg +12 -0
  85. package/huemagic/icons/hue-light.png +0 -0
  86. package/huemagic/icons/hue-light.svg +3 -0
  87. package/huemagic/icons/hue-magic.png +0 -0
  88. package/huemagic/icons/hue-magic.svg +3 -0
  89. package/huemagic/icons/hue-motion.png +0 -0
  90. package/huemagic/icons/hue-motion.svg +9 -0
  91. package/huemagic/icons/hue-rules.png +0 -0
  92. package/huemagic/icons/hue-rules.svg +4 -0
  93. package/huemagic/icons/hue-scene.png +0 -0
  94. package/huemagic/icons/hue-scene.svg +5 -0
  95. package/huemagic/icons/hue-temperature.png +0 -0
  96. package/huemagic/icons/hue-temperature.svg +7 -0
  97. package/huemagic/locales/de/hue-bridge-config.html +17 -12
  98. package/huemagic/locales/de/hue-bridge-config.json +7 -5
  99. package/huemagic/locales/de/hue-bridge.html +64 -145
  100. package/huemagic/locales/de/hue-bridge.json +6 -17
  101. package/huemagic/locales/de/hue-brightness.html +52 -38
  102. package/huemagic/locales/de/hue-brightness.json +7 -2
  103. package/huemagic/locales/de/hue-buttons.html +58 -0
  104. package/huemagic/locales/de/{hue-button.json → hue-buttons.json} +8 -8
  105. package/huemagic/locales/de/hue-group.html +46 -122
  106. package/huemagic/locales/de/hue-group.json +4 -2
  107. package/huemagic/locales/de/hue-light.html +68 -116
  108. package/huemagic/locales/de/hue-light.json +5 -2
  109. package/huemagic/locales/de/hue-magic.html +65 -57
  110. package/huemagic/locales/de/hue-magic.json +2 -1
  111. package/huemagic/locales/de/hue-motion.html +48 -44
  112. package/huemagic/locales/de/hue-motion.json +6 -3
  113. package/huemagic/locales/de/hue-rules.html +68 -73
  114. package/huemagic/locales/de/hue-rules.json +5 -2
  115. package/huemagic/locales/de/hue-scene.html +23 -32
  116. package/huemagic/locales/de/hue-scene.json +5 -2
  117. package/huemagic/locales/de/hue-temperature.html +53 -36
  118. package/huemagic/locales/de/hue-temperature.json +7 -2
  119. package/huemagic/locales/en-US/hue-bridge-config.html +17 -12
  120. package/huemagic/locales/en-US/hue-bridge-config.json +5 -3
  121. package/huemagic/locales/en-US/hue-bridge.html +64 -145
  122. package/huemagic/locales/en-US/hue-bridge.json +5 -16
  123. package/huemagic/locales/en-US/hue-brightness.html +52 -38
  124. package/huemagic/locales/en-US/hue-brightness.json +10 -3
  125. package/huemagic/locales/en-US/hue-buttons.html +58 -0
  126. package/huemagic/locales/en-US/{hue-button.json → hue-buttons.json} +11 -5
  127. package/huemagic/locales/en-US/hue-group.html +46 -122
  128. package/huemagic/locales/en-US/hue-group.json +7 -3
  129. package/huemagic/locales/en-US/hue-light.html +68 -116
  130. package/huemagic/locales/en-US/hue-light.json +8 -3
  131. package/huemagic/locales/en-US/hue-magic.html +65 -57
  132. package/huemagic/locales/en-US/hue-magic.json +3 -1
  133. package/huemagic/locales/en-US/hue-motion.html +48 -44
  134. package/huemagic/locales/en-US/hue-motion.json +9 -4
  135. package/huemagic/locales/en-US/hue-rules.html +68 -73
  136. package/huemagic/locales/en-US/hue-rules.json +5 -2
  137. package/huemagic/locales/en-US/hue-scene.html +23 -32
  138. package/huemagic/locales/en-US/hue-scene.json +5 -2
  139. package/huemagic/locales/en-US/hue-temperature.html +53 -36
  140. package/huemagic/locales/en-US/hue-temperature.json +10 -3
  141. package/huemagic/utils/api.js +290 -0
  142. package/huemagic/utils/color.js +382 -0
  143. package/{utils → huemagic/utils}/merge.js +9 -6
  144. package/huemagic/utils/messages.js +447 -0
  145. package/package.json +11 -12
  146. package/examples/Hue Switch.json +0 -40
  147. package/examples/Hue Tap.json +0 -40
  148. package/huemagic/assets/anime.js +0 -8
  149. package/huemagic/hue-button.html +0 -119
  150. package/huemagic/hue-button.js +0 -94
  151. package/huemagic/hue-switch.html +0 -119
  152. package/huemagic/hue-switch.js +0 -113
  153. package/huemagic/hue-tap.html +0 -119
  154. package/huemagic/hue-tap.js +0 -74
  155. package/huemagic/icons/hue-button.png +0 -0
  156. package/huemagic/icons/hue-switch.png +0 -0
  157. package/huemagic/icons/hue-tap.png +0 -0
  158. package/huemagic/locales/de/hue-button.html +0 -50
  159. package/huemagic/locales/de/hue-switch.html +0 -50
  160. package/huemagic/locales/de/hue-switch.json +0 -31
  161. package/huemagic/locales/de/hue-tap.html +0 -41
  162. package/huemagic/locales/de/hue-tap.json +0 -23
  163. package/huemagic/locales/en-US/hue-button.html +0 -50
  164. package/huemagic/locales/en-US/hue-switch.html +0 -50
  165. package/huemagic/locales/en-US/hue-switch.json +0 -31
  166. package/huemagic/locales/en-US/hue-tap.html +0 -41
  167. package/huemagic/locales/en-US/hue-tap.json +0 -23
  168. package/utils/color.js +0 -9
  169. package/utils/messages.js +0 -658
  170. package/utils/rgb.js +0 -188
@@ -6,22 +6,18 @@ module.exports = function(RED)
6
6
  {
7
7
  RED.nodes.createNode(this, config);
8
8
 
9
- var scope = this;
10
- let bridge = RED.nodes.getNode(config.bridge);
11
- let path = require('path');
12
- let { HueLightMessage } = require('../utils/messages');
9
+ const scope = this;
10
+ const bridge = RED.nodes.getNode(config.bridge);
13
11
 
14
- // SAVE LAST STATE
15
- var lastState = false;
16
- var futureState = null;
12
+ // SAVE FUTURE PATCH
13
+ this.futurePatchState = {};
14
+
15
+ // SAVE LAST COMMAND
16
+ this.lastCommand = null;
17
17
 
18
18
  // HELPER
19
- let rgb = require('../utils/rgb');
20
- let merge = require('../utils/merge');
21
- let hexRGB = require('hex-rgb');
22
- let colornames = require("colornames");
23
- let getColors = require('get-image-colors');
24
- let {randomHexColor} = require('../utils/color');
19
+ const colorUtils = require('./utils/color');
20
+ const merge = require('./utils/merge');
25
21
 
26
22
  //
27
23
  // CHECK CONFIG
@@ -31,365 +27,526 @@ module.exports = function(RED)
31
27
  return false;
32
28
  }
33
29
 
30
+ //
31
+ // UNIVERSAL MODE?
32
+ if(!config.lightid)
33
+ {
34
+ this.status({fill: "grey", shape: "dot", text: "hue-light.node.universal"});
35
+ }
36
+
34
37
  //
35
38
  // UPDATE STATE
36
- if(typeof bridge.disableupdates != 'undefined'||bridge.disableupdates == false)
39
+ if(typeof bridge.disableupdates != 'undefined' || bridge.disableupdates == false)
37
40
  {
38
41
  this.status({fill: "grey", shape: "dot", text: "hue-light.node.init"});
39
42
  }
40
43
 
41
44
  //
42
- // ON UPDATE
43
- if(config.lightid)
45
+ // SUBSCRIBE TO UPDATES FROM THE BRIDGE
46
+ bridge.subscribe("light", config.lightid, function(info)
44
47
  {
45
- bridge.events.on('light' + config.lightid, function(light)
48
+ let currentState = bridge.get("light", info.id, { colornames: config.colornamer ? true : false });
49
+
50
+ // RESSOURCE FOUND?
51
+ if(currentState !== false)
46
52
  {
47
- var hueLight = new HueLightMessage(light, config, lastState);
48
- var brightnessPercent = 0;
53
+ // SEND MESSAGE
54
+ if(!config.skipevents && (config.initevents || info.suppressMessage == false))
55
+ {
56
+ // SET LAST COMMAND
57
+ if(scope.lastCommand !== null)
58
+ {
59
+ currentState.command = scope.lastCommand;
60
+ }
61
+
62
+ // SEND STATE
63
+ scope.send(currentState);
49
64
 
50
- // SEND STATUS
51
- if(light.reachable)
65
+ // RESET LAST COMMAND
66
+ scope.lastCommand = null;
67
+ }
68
+
69
+ // NOT IN UNIVERAL MODE? -> CHANGE UI STATES
70
+ if(config.lightid)
52
71
  {
53
- if(light.on)
72
+ if(currentState.payload.reachable === true)
54
73
  {
55
- // HAS FUTURE STATE?
56
- if(futureState != null)
74
+ if(currentState.payload.on === true)
57
75
  {
58
- scope.applyCommands(futureState, null, null);
59
- }
76
+ // APPLY FUTURE STATE COMMANDS
77
+ if(Object.values(scope.futurePatchState).length > 0)
78
+ {
79
+ scope.applyCommands({}, null, null);
80
+ }
60
81
 
61
- // IS LIGHT?
62
- if(light.brightness)
63
- {
64
- brightnessPercent = hueLight.msg.payload.brightness;
65
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-light.node.turned-on-percent",{percent: brightnessPercent}) });
82
+ if(currentState.payload.brightness !== false)
83
+ {
84
+ scope.status({fill: "yellow", shape: "dot", text: RED._("hue-light.node.turned-on-percent",{ percent: currentState.payload.brightness })});
85
+ }
86
+ else
87
+ {
88
+ scope.status({fill: "yellow", shape: "dot", text: "hue-light.node.turned-on"});
89
+ }
66
90
  }
67
- // IS SMART PLUG
68
91
  else
69
92
  {
70
- brightnessPercent = -1;
71
- scope.status({fill: "yellow", shape: "dot", text: "hue-light.node.turned-on"});
93
+ scope.status({fill: "grey", shape: "dot", text: "hue-light.node.turned-off"});
72
94
  }
73
95
  }
74
96
  else
75
97
  {
76
- scope.status({fill: "grey", shape: "dot", text: "hue-light.node.turned-off"});
98
+ var offNotReachableStatus = RED._("hue-light.node.turned-off") + " (" + RED._("hue-light.node.not-reachable") + ")";
99
+ scope.status({fill: "red", shape: "ring", text: offNotReachableStatus});
77
100
  }
78
101
  }
79
- else
80
- {
81
- var offNotReachableStatus = RED._("hue-light.node.turned-off") + " (" + RED._("hue-light.node.not-reachable") + ")";
82
- scope.status({fill: "red", shape: "ring", text: offNotReachableStatus});
83
- }
84
-
85
- // SEND MESSAGE
86
- if(!config.skipevents) { scope.send(hueLight.msg); }
87
-
88
- // SAVE LAST STATE
89
- lastState = light;
90
- });
91
- }
92
- else
93
- {
94
- scope.status({fill: "grey", shape: "dot", text: "hue-light.node.universal"});
95
- }
102
+ }
103
+ });
96
104
 
105
+ //
106
+ // CONTROL LIGHT
107
+ this.on('input', function(msg, send, done) { scope.applyCommands(msg, send, done); });
97
108
 
98
109
  //
99
- // TURN ON / OFF LIGHT
100
- this.on('input', function(msg, send, done)
110
+ // APPLY COMMANDS
111
+ this.applyCommands = async function(msg, send = null, done = null)
101
112
  {
102
- // Node-RED < 1.0
113
+ // REDEFINE SEND AND DONE IF NOT AVAILABLE
103
114
  send = send || function() { scope.send.apply(scope,arguments); }
104
- scope.applyCommands(msg, send, done);
105
- });
115
+ done = done || function() { scope.done.apply(scope,arguments); }
106
116
 
117
+ // SAVE LAST COMMAND
118
+ scope.lastCommand = msg;
107
119
 
108
- //
109
- // APPLY COMMANDS
110
- this.applyCommands = function(msg, send = null, done = null)
111
- {
112
- var tempLightID = (msg.topic != null && isNaN(msg.topic) == false && msg.topic.length > 0) ? parseInt(msg.topic) : config.lightid;
120
+ // CREATE PATCH
121
+ let patchObject = {};
122
+
123
+ // DEFINE SENSOR ID & CURRENT STATE
124
+ const tempLightID = (typeof msg.topic != 'undefined' && msg.topic.length > 0) ? msg.topic : config.lightid;
125
+ let currentState = bridge.get("light", tempLightID, { colornames: config.colornamer ? true : false });
113
126
 
114
127
  // CHECK IF LIGHT ID IS SET
115
- if(tempLightID == null)
128
+ if(!tempLightID)
116
129
  {
117
130
  scope.error(RED._("hue-light.node.error-no-id"));
118
131
  return false;
119
132
  }
120
133
 
121
134
  // GET CURRENT STATE
122
- if(typeof msg.payload != 'undefined' && typeof msg.payload.status != 'undefined')
135
+ if( (typeof msg.payload != 'undefined' && typeof msg.payload.status != 'undefined') || (typeof msg.__user_inject_props__ != 'undefined' && msg.__user_inject_props__ == "status") )
123
136
  {
124
- bridge.client.lights.getById(tempLightID)
125
- .then(light => {
126
- return scope.sendLightStatus(light, send, done);
127
- });
137
+ // SET LAST COMMAND
138
+ if(scope.lastCommand !== null)
139
+ {
140
+ currentState.command = scope.lastCommand;
141
+ }
142
+
143
+ // SEND STATE
144
+ scope.send(currentState);
128
145
 
146
+ // RESET LAST COMMAND
147
+ scope.lastCommand = null;
148
+
149
+ if(done) { done(); }
129
150
  return true;
130
151
  }
131
152
 
153
+ // GET FUTURE STATE
154
+ if(Object.values(scope.futurePatchState).length > 0)
155
+ {
156
+ patchObject = Object.assign({}, scope.futurePatchState);
157
+ scope.futurePatchState = {};
158
+ }
159
+
160
+ // COLORLOOP EFFECT
161
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.colorloop != 'undefined' && msg.payload.colorloop > 0)
162
+ {
163
+ patchObject = {
164
+ "on": true,
165
+ "effect": "colorloop",
166
+ "bri": msg.payload.brightness ? Math.round((254/100)*msg.payload.brightness) : currentState.brightnessLevel
167
+ };
168
+
169
+ bridge.patch("light", currentState.info.idV1 + "/state", patchObject, 1)
170
+ .then(function(status) {
171
+ // RESET COLORLOOP ANIMATION AFTER X SECONDS
172
+ setTimeout(function()
173
+ {
174
+ bridge.patch("light", currentState.info.idV1 + "/state", { "effect": "none" }, 1);
175
+ if(done) { done(); }
176
+ }, parseInt(msg.payload.colorloop) * 1000);
177
+ })
178
+ .catch(function(errors) {
179
+ scope.error(errors);
180
+ scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
181
+ });
182
+
183
+ return false;
184
+ }
185
+
132
186
  // ALERT EFFECT
133
187
  if(typeof msg.payload != 'undefined' && typeof msg.payload.alert != 'undefined' && msg.payload.alert > 0)
134
188
  {
135
- bridge.client.lights.getById(tempLightID)
136
- .then(light => {
137
- scope.context().set('lightPreviousState', [light.on ? true : false, light.brightness, light.xy ? light.xy : false]);
189
+ // SAVE PREVIOUS STATE
190
+ scope.context().set('lightPreviousState', currentState);
138
191
 
139
- // SET ALERT COLOR
140
- if(light.xy)
192
+ // TURN ON LIGHT
193
+ if(currentState.payload.on === false)
194
+ {
195
+ patchObject["on"] = { on: true };
196
+ }
197
+
198
+ // SET BRIGHTNESS
199
+ if(!msg.payload.brightness && currentState.payload.brightness != 100)
200
+ {
201
+ patchObject["dimming"] = { brightness: 100 };
202
+ }
203
+ else if(msg.payload.brightness)
204
+ {
205
+ patchObject["dimming"] = { brightness: parseInt(msg.payload.brightness) };
206
+ }
207
+
208
+ // SET TRANSITION
209
+ patchObject["dynamics"] = { duration: 0 };
210
+
211
+ // CAN CHANGE COLOR?
212
+ if(currentState.payload.xyColor)
213
+ {
214
+ let XYAlertColor = {};
215
+
216
+ if(typeof msg.payload.rgb != 'undefined')
141
217
  {
142
- if(typeof msg.payload.rgb != 'undefined')
143
- {
144
- light.xy = rgb.convertRGBtoXY(msg.payload.rgb, light.model.id);
145
- }
146
- else if(typeof msg.payload.hex != 'undefined')
218
+ XYAlertColor = colorUtils.rgbToXy(msg.payload.rgb[0], msg.payload.rgb[1], msg.payload.rgb[2], currentState.info.model.colorGamut);
219
+ }
220
+ else if(typeof msg.payload.hex != 'undefined')
221
+ {
222
+ let rgbFromHex = colorUtils.hexRgb((msg.payload.hex).toString());
223
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
224
+ }
225
+ else if(typeof msg.payload.color != 'undefined')
226
+ {
227
+ if(new RegExp("random|any|whatever").test(msg.payload.color))
147
228
  {
148
- var rgbResult = hexRGB((msg.payload.hex).toString());
149
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
229
+ const randomColor = colorUtils.randomHexColor();
230
+ let rgbFromHex = colorUtils.hexRgb(rgbFromHex);
231
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
150
232
  }
151
- else if(typeof msg.payload.color != 'undefined')
233
+ else
152
234
  {
153
- if(new RegExp("random|any|whatever").test(msg.payload.color))
154
- {
155
- var randomColor = randomHexColor();
156
- var rgbResult = hexRGB(randomColor);
157
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
158
- }
159
- else
235
+ var colorHex = colorUtils.colornames(msg.payload.color);
236
+ if(colorHex)
160
237
  {
161
- var colorHex = colornames(msg.payload.color);
162
- if(colorHex)
163
- {
164
- var rgbResult = hexRGB(colorHex);
165
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
166
- }
238
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
239
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
167
240
  }
168
241
  }
169
- else
170
- {
171
- light.xy = rgb.convertRGBtoXY([255,0,0], light.model.id);
172
- }
173
- }
174
-
175
- // ACTIVATE
176
- light.on = true;
177
- light.brightness = 254;
178
- light.transitionTime = 0;
179
- return bridge.client.lights.save(light);
180
- })
181
- .then(light => {
182
- if(!config.lightid) { scope.sendLightStatus(light, send, done); }
183
- return light;
184
- })
185
- .then(light => {
186
- // ACTIVATE ALERT
187
- if(light != false)
188
- {
189
- light.alert = 'lselect';
190
- return bridge.client.lights.save(light);
191
242
  }
192
243
  else
193
244
  {
194
- return false;
245
+ XYAlertColor = colorUtils.rgbToXy(255, 0, 0, currentState.info.model.colorGamut);
195
246
  }
247
+
248
+ patchObject["color"] = {
249
+ xy: XYAlertColor
250
+ };
251
+ }
252
+
253
+ // CHANGE NODE UI STATE
254
+ if(config.lightid)
255
+ {
256
+ scope.status({fill: "grey", shape: "ring", text: "hue-light.node.command"});
257
+ }
258
+
259
+ // 1. TURN ON THE LIGHT BULB
260
+ bridge.patch("light", tempLightID, patchObject)
261
+ .then(function(status) {
262
+ // 2. APPLY ALERT EFFECT
263
+ const alertEffect = { alert: { action: "breathe" }};
264
+ return bridge.patch("light", tempLightID, alertEffect);
196
265
  })
197
- .then(light => {
198
- // TURN OFF ALERT
199
- if(light != false)
266
+ .then(function(status) {
267
+ // 3. RESET PREVIOUS STATE (AFTER X SECONDS)
268
+ setTimeout(function()
200
269
  {
201
- var lightPreviousState = scope.context().get('lightPreviousState');
202
- var alertSeconds = parseInt(msg.payload.alert);
270
+ const tempPreviousState = scope.context().get('lightPreviousState');
271
+ var tempPreviousStatePatch = {};
203
272
 
204
- setTimeout(function() {
205
- light.on = lightPreviousState[0];
206
- light.alert = 'none';
207
- light.brightness = lightPreviousState[1];
208
- light.transitionTime = 2;
273
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
274
+ if(tempPreviousState.payload.xyColor)
275
+ {
276
+ tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
277
+ }
278
+ else if(tempPreviousState.payload.colorTemp)
279
+ {
280
+ tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
281
+ }
209
282
 
210
- if(lightPreviousState[2] != false)
283
+ bridge.patch("light", tempLightID, tempPreviousStatePatch).
284
+ then(function(status)
285
+ {
286
+ return bridge.patch("light", tempLightID, { on: { on: false } })
287
+ .then(function() { if(done) { done(); }});
288
+ })
289
+ .then(function(status) {
290
+ if(tempPreviousState.payload.on === true)
211
291
  {
212
- light.xy = lightPreviousState[2];
292
+ bridge.patch("light", tempLightID, { on: { on: true } })
293
+ .then(function() { if(done) { done(); }});
213
294
  }
214
-
215
- bridge.client.lights.save(light);
216
- }, alertSeconds * 1000);
217
- }
295
+ });
296
+ }, parseInt(msg.payload.alert) * 1000);
218
297
  })
219
- .catch(error => {
220
- scope.error(error, msg);
298
+ .catch(function(errors) {
299
+ scope.error(errors);
221
300
  scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
222
- if(done) { done(error); }
223
301
  });
224
302
  }
225
303
  // ANIMATION STARTED?
226
304
  else if(typeof msg.animation != 'undefined' && msg.animation.status == true && msg.animation.restore == true)
227
305
  {
228
- bridge.client.lights.getById(tempLightID)
229
- .then(light => {
230
- scope.context().set('lightPreviousState', [(light.on) ? true : false, light.brightness, light.xy ? light.xy : false]);
231
- })
232
- .catch(error => {
233
- scope.error(error, msg);
234
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
235
- if(done) { done(error); }
236
- });
306
+ // SAVE PREVIOUS STATE
307
+ scope.context().set('lightPreviousState', currentState);
237
308
  }
238
309
  // ANIMATION STOPPED AND RESTORE ACTIVE?
239
310
  else if(typeof msg.animation != 'undefined' && msg.animation.status == false && msg.animation.restore == true)
240
311
  {
241
- bridge.client.lights.getById(tempLightID)
242
- .then(light => {
243
- var lightPreviousState = scope.context().get('lightPreviousState');
244
- light.on = lightPreviousState[0];
245
- light.alert = 'none';
246
- light.brightness = lightPreviousState[1];
247
- light.transitionTime = 2;
312
+ const tempPreviousState = scope.context().get('lightPreviousState');
313
+ var tempPreviousStatePatch = {};
248
314
 
249
- if(lightPreviousState[2] != false)
315
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
316
+ if(tempPreviousState.payload.xyColor)
317
+ {
318
+ tempPreviousStatePatch.color = { xy: tempPreviousState.payload.xyColor };
319
+ }
320
+ else if(tempPreviousState.payload.colorTemp)
321
+ {
322
+ tempPreviousStatePatch.color_temperature = { mirek: tempPreviousState.payload.colorTemp };
323
+ }
324
+
325
+ bridge.patch("light", tempLightID, tempPreviousStatePatch).
326
+ then(function(status)
327
+ {
328
+ if(tempPreviousState.payload.on === false)
250
329
  {
251
- light.xy = lightPreviousState[2];
330
+ bridge.patch("light", tempLightID, { on: { on: false } })
331
+ .then(function() { if(done) { done(); }});
332
+ }
333
+ else
334
+ {
335
+ bridge.patch("light", tempLightID, { on: { on: false } })
336
+ .then(function(status) {
337
+ if(done) { done(); }
338
+ return bridge.patch("light", tempLightID, { on: { on: true } })
339
+ });
252
340
  }
253
-
254
- bridge.client.lights.save(light);
255
- })
256
- .catch(error => {
257
- scope.error(error, msg);
258
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
259
- if(done) { done(error); }
260
341
  });
261
342
  }
262
- // EXTENDED TURN ON / OFF LIGHT
343
+ // EXTENDED COMMANDS
263
344
  else
264
345
  {
265
- bridge.client.lights.getById(tempLightID)
266
- .then(async (light) => {
267
- // IS LIGHT ON?
268
- var isCurrentlyOn = light.on;
269
-
270
- // SET LIGHT STATE SIMPLE MODE
271
- if(msg.payload === true||msg.payload === false)
272
- {
273
- var command = msg.payload;
274
- msg.payload = {
275
- on: command
276
- };
277
- }
346
+ // SET LIGHT STATE SIMPLE MODE
347
+ if(msg.payload === true||msg.payload === false)
348
+ {
349
+ if(msg.payload !== currentState.payload.on) { patchObject["on"] = { on: msg.payload }; }
350
+ }
278
351
 
279
- // HAS FUTURE STATE? -> MERGE INPUT
280
- if(futureState != null)
281
- {
282
- // MERGE
283
- msg = merge.deep(futureState, msg);
352
+ // SET LIGHT STATE
353
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.on != 'undefined' && (msg.payload.on === true || msg.payload.on === false))
354
+ {
355
+ if(msg.payload.on !== currentState.payload.on) { patchObject["on"] = { on: msg.payload.on }; }
356
+ }
284
357
 
285
- // RESET
286
- futureState = null;
287
- }
358
+ // TOGGLE ON / OFF
359
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
360
+ {
361
+ patchObject["on"] = { on: !currentState.payload.on };
362
+ }
288
363
 
289
- // SET LIGHT STATE
290
- if(typeof msg.payload != 'undefined' && typeof msg.payload.on != 'undefined')
364
+ // SET BRIGHTNESS
365
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.brightness != 'undefined')
366
+ {
367
+ // AUTO BRIGHTNESS BASED ON DAY TIME
368
+ if(new RegExp("auto|automatic").test(msg.payload.brightness))
291
369
  {
292
- light.on = msg.payload.on;
293
- }
370
+ let ct = colorUtils.colorTemperature();
371
+ let autoBrightness = ((300-ct)/2)+100;
372
+ autoBrightness = (autoBrightness > 100) ? 100 : autoBrightness;
373
+ autoBrightness = (autoBrightness < 20) ? 20 : autoBrightness;
294
374
 
295
- // TOGGLE ON / OFF
296
- if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
297
- {
298
- light.on = light.on ? false : true;
375
+ // SET CALCULATED BRIGHTNESS
376
+ patchObject["dimming"] = { brightness: autoBrightness };
299
377
  }
300
-
301
- // SET BRIGHTNESS
302
- if(typeof msg.payload != 'undefined' && typeof msg.payload.brightness != 'undefined')
378
+ else
303
379
  {
304
- if(msg.payload.brightness > 100 || msg.payload.brightness < 0)
380
+ if(msg.payload.brightness > 100 || msg.payload.brightness < 0)
305
381
  {
306
382
  scope.error("Invalid brightness setting. Only 0 - 100 percent allowed");
307
383
  return false;
308
384
  }
309
385
  else if(msg.payload.brightness == 0)
310
386
  {
311
- light.on = false;
387
+ if(currentState.payload.on !== false) { patchObject["on"] = { on: false }; }
312
388
  }
313
389
  else
314
390
  {
315
- light.on = true;
316
- light.brightness = Math.round((254/100)*parseInt(msg.payload.brightness));
391
+ patchObject["dimming"] = { brightness: msg.payload.brightness };
317
392
  }
318
393
  }
319
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.brightnessLevel != 'undefined')
394
+ }
395
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.brightnessLevel != 'undefined')
396
+ {
397
+ if(msg.payload.brightnessLevel > 254 || msg.payload.brightnessLevel < 0)
320
398
  {
321
- if(msg.payload.brightnessLevel > 254 || msg.payload.brightnessLevel < 0)
322
- {
323
- scope.error("Invalid brightness setting. Only 0 - 254 allowed");
324
- return false;
325
- }
326
- else if(msg.payload.brightness == 0)
327
- {
328
- light.on = false;
329
- }
330
- else
331
- {
332
- light.on = true;
333
- light.brightness = parseInt(msg.payload.brightnessLevel);
334
- }
399
+ scope.error("Invalid brightness setting. Only 0 - 254 allowed");
400
+ return false;
335
401
  }
336
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementBrightness != 'undefined')
402
+ else if(msg.payload.brightness == 0)
337
403
  {
338
- if (msg.payload.incrementBrightness > 0)
339
- {
340
- light.on = true;
341
- }
342
- light.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.incrementBrightness));
404
+ if(currentState.payload.on !== false) { patchObject["on"] = { on: false }; }
343
405
  }
344
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.decrementBrightness != 'undefined')
406
+ else
345
407
  {
346
- if (msg.payload.decrementBrightness > 0)
408
+ patchObject["dimming"] = { brightness: Math.round((100/254)*msg.payload.brightnessLevel) };
409
+ }
410
+ }
411
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementBrightness != 'undefined')
412
+ {
413
+ let incrementBy = (isNaN(msg.payload.incrementBrightness)) ? 10 : msg.payload.incrementBrightness;
414
+ let targetBrightness = Math.round(currentState.payload.brightness + incrementBy);
415
+ targetBrightness = (targetBrightness > 100) ? 100 : targetBrightness;
416
+
417
+ patchObject["dimming"] = { brightness: targetBrightness };
418
+ }
419
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.decrementBrightness != 'undefined')
420
+ {
421
+ let decrementBy = (isNaN(msg.payload.decrementBrightness)) ? 10 : msg.payload.decrementBrightness;
422
+ let targetBrightness = Math.round(currentState.payload.brightness - decrementBy);
423
+ targetBrightness = (targetBrightness < 0) ? 0 : targetBrightness;
424
+
425
+ if(targetBrightness < 1)
426
+ {
427
+ if(currentState.payload.on !== false) { patchObject["on"] = { on: false }; }
428
+ }
429
+
430
+ patchObject["dimming"] = { brightness: targetBrightness };
431
+ }
432
+
433
+ // SET HUMAN READABLE COLOR OR RANDOM
434
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.color != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
435
+ {
436
+ let XYAlertColor = {};
437
+
438
+ if(new RegExp("random|any|whatever").test(msg.payload.color))
439
+ {
440
+ const randomColor = colorUtils.randomHexColor();
441
+ let rgbFromHex = colorUtils.hexRgb(randomColor);
442
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
443
+ }
444
+ else
445
+ {
446
+ var colorHex = colorUtils.colornames(msg.payload.color);
447
+ if(colorHex)
347
448
  {
348
- light.on = true;
449
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
450
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
349
451
  }
350
- light.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.decrementBrightness))*-1;
351
452
  }
352
453
 
353
- // SET HUMAN READABLE COLOR OR RANDOM
354
- if(typeof msg.payload != 'undefined' && typeof msg.payload.color != 'undefined' && typeof light.xy != 'undefined')
454
+ patchObject["color"] = {
455
+ xy: XYAlertColor
456
+ };
457
+ }
458
+
459
+ // SET HEX COLOR
460
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.hex != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
461
+ {
462
+ let rgbFromHex = colorUtils.hexRgb((msg.payload.hex).toString());
463
+ patchObject["color"] = {
464
+ xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut)
465
+ };
466
+ }
467
+
468
+ // SET RGB COLOR
469
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.rgb != 'undefined' && typeof currentState.payload.xyColor != 'undefined' && msg.payload.rgb.length === 3)
470
+ {
471
+ patchObject["color"] = {
472
+ xy: colorUtils.rgbToXy(msg.payload.rgb[0], msg.payload.rgb[1], msg.payload.rgb[2], currentState.info.model.colorGamut)
473
+ };
474
+ }
475
+
476
+ // SET XY COLOR
477
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.xyColor != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
478
+ {
479
+ patchObject["color"] = {
480
+ xy: msg.payload.xyColor
481
+ };
482
+ }
483
+
484
+ // MIX CURRENT COLOR WITH NEW COLOR
485
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.mixColor != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
486
+ {
487
+ let RGBColor = [];
488
+
489
+ if(typeof msg.payload.mixColor.color != 'undefined')
355
490
  {
356
- if(new RegExp("random|any|whatever").test(msg.payload.color))
491
+ if(new RegExp("random|any|whatever").test(msg.payload.mixColor.color))
357
492
  {
358
- var randomColor = randomHexColor();
359
- var rgbResult = hexRGB(randomColor);
360
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
493
+ RGBColor = colorUtils.hexRgb(colorUtils.randomHexColor());
361
494
  }
362
495
  else
363
496
  {
364
- var colorHex = colornames(msg.payload.color);
497
+ var colorHex = colorUtils.colornames(msg.payload.mixColor.color);
365
498
  if(colorHex)
366
499
  {
367
- var rgbResult = hexRGB(colorHex);
368
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
500
+ RGBColor = colorUtils.hexRgb(colorHex);
369
501
  }
370
502
  }
371
503
  }
504
+ else if(typeof msg.payload.mixColor.rgb != 'undefined')
505
+ {
506
+ RGBColor = msg.payload.mixColor.rgb;
507
+ }
508
+ else if(typeof msg.payload.mixColor.hex != 'undefined')
509
+ {
510
+ RGBColor = colorUtils.hexRgb((msg.payload.mixColor.hex).toString());
511
+ }
512
+ else if(typeof msg.payload.mixColor.xyColor != 'undefined')
513
+ {
514
+ RGBColor = colorUtils.xyBriToRgb(msg.payload.mixColor.xyColor.x, msg.payload.mixColor.xyColor.y, 100);
515
+ }
372
516
 
373
- // SET RGB COLOR
374
- if(typeof msg.payload != 'undefined' && typeof msg.payload.rgb != 'undefined' && typeof light.xy != 'undefined')
517
+ // GET MIXING AMOUNT
518
+ let mixingAmount = 0.5;
519
+ if(typeof msg.payload.mixColor.amount != 'undefined' && msg.payload.mixColor.amount > 0 && msg.payload.mixColor.amount <= 100)
375
520
  {
376
- light.xy = rgb.convertRGBtoXY(msg.payload.rgb, light.model.id);
521
+ mixingAmount = msg.payload.mixColor.amount/100;
377
522
  }
378
523
 
379
- // SET HEX COLOR
380
- if(typeof msg.payload != 'undefined' && typeof msg.payload.hex != 'undefined' && typeof light.xy != 'undefined')
524
+ // HAS CURRENT COLOR SETTING?
525
+ if(currentState.payload.rgb !== false)
526
+ {
527
+ let mixedRGBColor = colorUtils.mixColors(currentState.payload.rgb, RGBColor, mixingAmount);
528
+ patchObject["color"] = {
529
+ xy: colorUtils.rgbToXy(mixedRGBColor[0], mixedRGBColor[1], mixedRGBColor[2], currentState.info.model.colorGamut)
530
+ };
531
+ }
532
+ else
381
533
  {
382
- var rgbResult = hexRGB((msg.payload.hex).toString());
383
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
534
+ patchObject["color"] = {
535
+ xy: colorUtils.rgbToXy(RGBColor[0], RGBColor[1], RGBColor[2], currentState.info.model.colorGamut)
536
+ };
384
537
  }
538
+ }
385
539
 
386
- // SET COLOR TEMPERATURE
387
- if(typeof msg.payload != 'undefined' && typeof msg.payload.colorTemp != 'undefined' && typeof light.colorTemp != 'undefined')
540
+ // SET COLOR TEMPERATURE
541
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.colorTemp != 'undefined' && typeof currentState.payload.colorTemp != 'undefined')
542
+ {
543
+ // DETERMINE IF AUTOMATIC, WARM, COLD, INT
544
+ if(!isNaN(msg.payload.colorTemp))
388
545
  {
389
546
  let colorTemp = parseInt(msg.payload.colorTemp);
390
547
  if(colorTemp >= 153 && colorTemp <= 500)
391
548
  {
392
- light.colorTemp = parseInt(msg.payload.colorTemp);
549
+ patchObject["color_temperature"] = { mirek: colorTemp };
393
550
  }
394
551
  else
395
552
  {
@@ -397,131 +554,188 @@ module.exports = function(RED)
397
554
  return false;
398
555
  }
399
556
  }
400
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementColorTemp != 'undefined')
401
- {
402
- light.incrementColorTemp = parseInt(msg.payload.incrementColorTemp, 10) || 0;
403
- }
557
+ else if(msg.payload.colorTemp == "cold")
558
+ {
559
+ patchObject["color_temperature"] = { mirek: 153 };
560
+ }
561
+ else if(msg.payload.colorTemp == "normal")
562
+ {
563
+ patchObject["color_temperature"] = { mirek: 240 };
564
+ }
565
+ else if(msg.payload.colorTemp == "warm")
566
+ {
567
+ patchObject["color_temperature"] = { mirek: 400 };
568
+ }
569
+ else if(msg.payload.colorTemp == "hot")
570
+ {
571
+ patchObject["color_temperature"] = { mirek: 500 };
572
+ }
573
+ else
574
+ {
575
+ // SET TEMPERATURE
576
+ patchObject["color_temperature"] = { mirek: colorUtils.colorTemperature() };
577
+ }
578
+ }
579
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementColorTemp != 'undefined' && typeof currentState.payload.colorTemp != 'undefined')
580
+ {
581
+ let incrementBy = (isNaN(msg.payload.incrementColorTemp)) ? 50 : msg.payload.incrementColorTemp;
582
+ let targetColorTemperature = currentState.payload.colorTemp + parseInt(incrementBy);
583
+ targetColorTemperature = (targetColorTemperature > 500) ? 500 : targetColorTemperature;
584
+ targetColorTemperature = (targetColorTemperature < 153) ? 153 : targetColorTemperature;
404
585
 
405
- // SET SATURATION
406
- if(typeof msg.payload != 'undefined' && typeof msg.payload.saturation != 'undefined' && typeof light.saturation != 'undefined')
586
+ // SET TEMPERATURE
587
+ patchObject["color_temperature"] = { mirek: targetColorTemperature };
588
+ }
589
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.decrementColorTemp != 'undefined' && typeof currentState.payload.colorTemp != 'undefined')
590
+ {
591
+ let decrementBy = (isNaN(msg.payload.decrementColorTemp)) ? 50 : msg.payload.decrementColorTemp;
592
+ let targetColorTemperature = currentState.payload.colorTemp - parseInt(decrementBy);
593
+ targetColorTemperature = (targetColorTemperature > 500) ? 500 : targetColorTemperature;
594
+ targetColorTemperature = (targetColorTemperature < 153) ? 153 : targetColorTemperature;
595
+
596
+ // SET TEMPERATURE
597
+ patchObject["color_temperature"] = { mirek: targetColorTemperature };
598
+ }
599
+
600
+ // SET TRANSITION TIME
601
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.transitionTime != 'undefined')
602
+ {
603
+ let targetTransitionTime = parseFloat(msg.payload.transitionTime)*1000;
604
+ targetTransitionTime = (targetTransitionTime > 6000000) ? 6000000 : targetTransitionTime;
605
+ targetTransitionTime = (targetTransitionTime < 0) ? 0 : targetTransitionTime;
606
+
607
+ patchObject["dynamics"] = { duration: targetTransitionTime };
608
+ }
609
+
610
+ // SET DOMINANT COLORS FROM IMAGE
611
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
612
+ {
613
+ var colors = await colorUtils.getColors(msg.payload.image);
614
+ if(colors.length > 0)
407
615
  {
408
- if(msg.payload.saturation > 100 || msg.payload.saturation < 0)
409
- {
410
- scope.error("Invalid saturation setting. Only 0 - 254 allowed");
411
- return false;
412
- }
413
- else
414
- {
415
- light.saturation = Math.round((254/100)*parseInt(msg.payload.saturation));
416
- }
616
+ var colorsHEX = colors.map(color => color.hex());
617
+ let rgbFromHex = colorUtils.hexRgb(colorsHEX[0]);
618
+ patchObject["color"] = {
619
+ xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut)
620
+ };
417
621
  }
622
+ }
418
623
 
419
- // SET TRANSITION TIME
420
- if(typeof msg.payload != 'undefined' && typeof msg.payload.transitionTime != 'undefined')
624
+ // SET SATURATION
625
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.saturation != 'undefined' && typeof currentState.payload.xyColor != 'undefined')
626
+ {
627
+ if(msg.payload.saturation > 100 || msg.payload.saturation < 0)
421
628
  {
422
- light.transitionTime = parseFloat(msg.payload.transitionTime);
629
+ scope.error("Invalid saturation setting. Only 0 - 100 allowed");
630
+ return false;
423
631
  }
632
+ else
633
+ {
634
+ let currentColor = patchObject["color"] ? colorUtils.xyBriToRgb(patchObject["color"].xy.x, patchObject["color"].xy.y, 100) : currentState.payload.rgb;
635
+ let currentColorInHSL = colorUtils.rgbToHsl(currentColor[0], currentColor[1], currentColor[2]);
636
+ let saturationFactor = (msg.payload.saturation/100);
424
637
 
425
- // SET COLORLOOP EFFECT
426
- if(typeof msg.payload != 'undefined' && typeof msg.payload.colorloop != 'undefined' && typeof light.xy != 'undefined')
638
+ // CHANGE SATURATION
639
+ currentColorInHSL[1] = currentColorInHSL[1]*saturationFactor;
640
+
641
+ // CONVERT BACK TO RGB
642
+ let saturatedRGBColor = colorUtils.hslToRgb(currentColorInHSL[0], currentColorInHSL[1], currentColorInHSL[2]);
643
+
644
+ patchObject["color"] = {
645
+ xy: colorUtils.rgbToXy(saturatedRGBColor[0], saturatedRGBColor[1], saturatedRGBColor[2], currentState.info.model.colorGamut)
646
+ };
647
+ }
648
+ }
649
+
650
+ // SET GRADIENT
651
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.gradient != 'undefined' && typeof currentState.payload.gradient != 'undefined')
652
+ {
653
+ let XYColorSet = [];
654
+
655
+ if(typeof msg.payload.gradient.hex != 'undefined' && Array.isArray(msg.payload.gradient.hex) == true)
427
656
  {
428
- if(msg.payload.colorloop === true) {
429
- light.effect = 'colorloop';
430
- }
431
- else if(msg.payload.colorloop === false) {
432
- light.effect = 'none';
433
- }
434
- // ENABLE FOR TIME INTERVAL
435
- else if(msg.payload.colorloop > 0) {
436
- light.effect = 'colorloop';
437
-
438
- // DISABLE AFTER
439
- setTimeout(function() {
440
- light.effect = 'none';
441
- bridge.client.lights.save(light);
442
- }, parseFloat(msg.payload.colorloop)*1000);
657
+ XYColorSet = [];
658
+
659
+ for(let oneColor in msg.payload.gradient.hex)
660
+ {
661
+ let rgbFromHex = colorUtils.hexRgb(oneColor);
662
+ XYColorSet.push({
663
+ color: { xy: colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut) }
664
+ });
443
665
  }
444
666
  }
445
667
 
446
- // SET DOMINANT COLORS FROM IMAGE
447
- if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined' && typeof light.xy != 'undefined')
668
+ if(typeof msg.payload.gradient.rgb != 'undefined' && Array.isArray(msg.payload.gradient.rgb) == true)
448
669
  {
449
- var colors = await getColors(msg.payload.image);
450
- if(colors.length > 0)
670
+ XYColorSet = [];
671
+
672
+ for(let oneColor in msg.payload.gradient.rgb)
451
673
  {
452
- var colorsHEX = colors.map(color => color.hex());
453
- var rgbResult = hexRGB(colorsHEX[0]);
454
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
674
+ XYColorSet.push({
675
+ color: { xy: colorUtils.rgbToXy(oneColor[0], oneColor[1], oneColor[2], currentState.info.model.colorGamut) }
676
+ });
455
677
  }
456
678
  }
457
679
 
458
- // SAVE FOR LATER MODE?
459
- if((!light.on||!light.reachable)&&isCurrentlyOn==false)
680
+ if(typeof msg.payload.gradient.xyColor != 'undefined' && Array.isArray(msg.payload.gradient.xyColor) == true)
460
681
  {
461
- // SAVE FUTURE STATE
462
- futureState = msg;
682
+ XYColorSet = [];
463
683
 
464
- // IGNORE ON/OFF & TOGGLE
465
- if(typeof futureState.payload != 'undefined')
684
+ for(let oneColor in msg.payload.gradient.xyColor)
466
685
  {
467
- delete futureState.payload.on;
468
- delete futureState.payload.toggle;
686
+ XYColorSet.push({
687
+ color: { xy: oneColor }
688
+ });
469
689
  }
690
+ }
470
691
 
471
- // ANY OTHER COMMANDS?
472
- if(typeof futureState.payload != 'undefined' && Object.keys(futureState.payload).length > 0)
692
+ patchObject["gradient"] = { points: XYColorSet };
693
+ }
694
+
695
+ //
696
+ // SHOULD PATCH?
697
+ if(Object.values(patchObject).length > 0)
698
+ {
699
+ // IS FOR LATER?
700
+ if(currentState.payload.on === false || currentState.payload.reachable === false)
701
+ {
702
+ if(!patchObject["on"] || !patchObject["on"]["on"])
473
703
  {
474
- return light;
704
+ scope.futurePatchState = merge.deep(scope.futurePatchState, patchObject);
705
+ return false;
475
706
  }
476
707
  }
477
708
 
478
- // SAVE STATE
479
- return bridge.client.lights.save(light);
480
- })
481
- .then(light => {
482
- scope.sendLightStatus(light, send, done);
483
- return light;
484
- })
485
- .catch(error => {
486
- scope.error(error, msg);
487
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
488
- if(done) { done(error); }
489
- });
490
- }
491
- }
492
-
709
+ // CHANGE NODE UI STATE
710
+ if(config.lightid)
711
+ {
712
+ scope.status({fill: "grey", shape: "ring", text: "hue-light.node.command"});
713
+ }
493
714
 
494
- //
495
- // SEND LIGHT STATUS
496
- this.sendLightStatus = function(light, send, done)
497
- {
498
- var hueLight = new HueLightMessage(light, config, lastState);
715
+ // PATCH!
716
+ bridge.patch("light", tempLightID, patchObject)
717
+ .then(function() { if(done) { done(); }})
718
+ .catch(function(errors) { scope.error(errors); });
719
+ }
720
+ else
721
+ {
722
+ // JUST SEND CURRENT STATE
723
+ if(scope.lastCommand !== null)
724
+ {
725
+ currentState.command = scope.lastCommand;
726
+ }
499
727
 
500
- // SEND STATUS
501
- if(light.on)
502
- {
503
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-light.node.turned-on-percent",{percent: hueLight.msg.payload.brightness}) });
504
- }
505
- else
506
- {
507
- scope.status({fill: "grey", shape: "dot", text: "hue-light.node.turned-off"});
508
- }
728
+ // SEND STATE
729
+ scope.send(currentState);
509
730
 
510
- // SEND MESSAGE
511
- if(!config.skipevents && send) { send(hueLight.msg); }
512
- if(done) { done(); }
731
+ // RESET LAST COMMAND
732
+ scope.lastCommand = null;
513
733
 
514
- // SAVE LAST STATE
515
- lastState = light;
734
+ if(done) { done(); }
735
+ }
736
+ }
516
737
  }
517
-
518
- //
519
- // CLOSE NODE / REMOVE EVENT LISTENER
520
- this.on('close', function()
521
- {
522
- bridge.events.removeAllListeners('light' + config.lightid);
523
- });
524
738
  }
525
739
 
526
740
  RED.nodes.registerType("hue-light", HueLight);
527
- }
741
+ }