node-red-contrib-huemagic 2.8.2 → 4.0.0

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 (165) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/README.md +806 -729
  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 -319
  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 -367
  65. package/huemagic/hue-light.html +133 -59
  66. package/huemagic/hue-light.js +551 -336
  67. package/huemagic/hue-magic.html +91 -237
  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-buttons.json +28 -0
  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-buttons.json +34 -0
  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 +12 -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-switch.html +0 -119
  150. package/huemagic/hue-switch.js +0 -113
  151. package/huemagic/hue-tap.html +0 -119
  152. package/huemagic/hue-tap.js +0 -74
  153. package/huemagic/icons/hue-button.png +0 -0
  154. package/huemagic/icons/hue-switch.png +0 -0
  155. package/huemagic/icons/hue-tap.png +0 -0
  156. package/huemagic/locales/de/hue-switch.html +0 -50
  157. package/huemagic/locales/de/hue-switch.json +0 -31
  158. package/huemagic/locales/de/hue-tap.html +0 -41
  159. package/huemagic/locales/de/hue-tap.json +0 -23
  160. package/huemagic/locales/en-US/hue-switch.html +0 -50
  161. package/huemagic/locales/en-US/hue-switch.json +0 -31
  162. package/huemagic/locales/en-US/hue-tap.html +0 -41
  163. package/huemagic/locales/en-US/hue-tap.json +0 -23
  164. package/utils/messages.js +0 -571
  165. package/utils/rgb.js +0 -188
@@ -6,21 +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');
19
+ const colorUtils = require('./utils/color');
20
+ const merge = require('./utils/merge');
24
21
 
25
22
  //
26
23
  // CHECK CONFIG
@@ -30,365 +27,526 @@ module.exports = function(RED)
30
27
  return false;
31
28
  }
32
29
 
30
+ //
31
+ // UNIVERSAL MODE?
32
+ if(!config.lightid)
33
+ {
34
+ this.status({fill: "grey", shape: "dot", text: "hue-light.node.universal"});
35
+ }
36
+
33
37
  //
34
38
  // UPDATE STATE
35
- if(typeof bridge.disableupdates != 'undefined'||bridge.disableupdates == false)
39
+ if(typeof bridge.disableupdates != 'undefined' || bridge.disableupdates == false)
36
40
  {
37
41
  this.status({fill: "grey", shape: "dot", text: "hue-light.node.init"});
38
42
  }
39
43
 
40
44
  //
41
- // ON UPDATE
42
- if(config.lightid)
45
+ // SUBSCRIBE TO UPDATES FROM THE BRIDGE
46
+ bridge.subscribe("light", config.lightid, function(info)
43
47
  {
44
- 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)
45
52
  {
46
- var hueLight = new HueLightMessage(light, config, lastState);
47
- 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);
48
64
 
49
- // SEND STATUS
50
- 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)
51
71
  {
52
- if(light.on)
72
+ if(currentState.payload.reachable === true)
53
73
  {
54
- // HAS FUTURE STATE?
55
- if(futureState != null)
74
+ if(currentState.payload.on === true)
56
75
  {
57
- scope.applyCommands(futureState, null, null);
58
- }
76
+ // APPLY FUTURE STATE COMMANDS
77
+ if(Object.values(scope.futurePatchState).length > 0)
78
+ {
79
+ scope.applyCommands({}, null, null);
80
+ }
59
81
 
60
- // IS LIGHT?
61
- if(light.brightness)
62
- {
63
- brightnessPercent = hueLight.msg.payload.brightness;
64
- 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
+ }
65
90
  }
66
- // IS SMART PLUG
67
91
  else
68
92
  {
69
- brightnessPercent = -1;
70
- 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"});
71
94
  }
72
95
  }
73
96
  else
74
97
  {
75
- 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});
76
100
  }
77
101
  }
78
- else
79
- {
80
- var offNotReachableStatus = RED._("hue-light.node.turned-off") + " (" + RED._("hue-light.node.not-reachable") + ")";
81
- scope.status({fill: "red", shape: "ring", text: offNotReachableStatus});
82
- }
83
-
84
- // SEND MESSAGE
85
- if(!config.skipevents) { scope.send(hueLight.msg); }
86
-
87
- // SAVE LAST STATE
88
- lastState = light;
89
- });
90
- }
91
- else
92
- {
93
- scope.status({fill: "grey", shape: "dot", text: "hue-light.node.universal"});
94
- }
102
+ }
103
+ });
95
104
 
105
+ //
106
+ // CONTROL LIGHT
107
+ this.on('input', function(msg, send, done) { scope.applyCommands(msg, send, done); });
96
108
 
97
109
  //
98
- // TURN ON / OFF LIGHT
99
- this.on('input', function(msg, send, done)
110
+ // APPLY COMMANDS
111
+ this.applyCommands = async function(msg, send = null, done = null)
100
112
  {
101
- // Node-RED < 1.0
113
+ // REDEFINE SEND AND DONE IF NOT AVAILABLE
102
114
  send = send || function() { scope.send.apply(scope,arguments); }
103
- scope.applyCommands(msg, send, done);
104
- });
115
+ done = done || function() { scope.done.apply(scope,arguments); }
105
116
 
117
+ // SAVE LAST COMMAND
118
+ scope.lastCommand = msg;
106
119
 
107
- //
108
- // APPLY COMMANDS
109
- this.applyCommands = function(msg, send = null, done = null)
110
- {
111
- 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 = (msg.topic != null) ? msg.topic : config.lightid;
125
+ let currentState = bridge.get("light", tempLightID, { colornames: config.colornamer ? true : false });
112
126
 
113
127
  // CHECK IF LIGHT ID IS SET
114
- if(tempLightID == null)
128
+ if(!tempLightID)
115
129
  {
116
130
  scope.error(RED._("hue-light.node.error-no-id"));
117
131
  return false;
118
132
  }
119
133
 
120
134
  // GET CURRENT STATE
121
- 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") )
122
136
  {
123
- bridge.client.lights.getById(tempLightID)
124
- .then(light => {
125
- return scope.sendLightStatus(light, send, done);
126
- });
137
+ // SET LAST COMMAND
138
+ if(scope.lastCommand !== null)
139
+ {
140
+ currentState.command = scope.lastCommand;
141
+ }
142
+
143
+ // SEND STATE
144
+ scope.send(currentState);
127
145
 
146
+ // RESET LAST COMMAND
147
+ scope.lastCommand = null;
148
+
149
+ if(done) { done(); }
128
150
  return true;
129
151
  }
130
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
+
131
186
  // ALERT EFFECT
132
187
  if(typeof msg.payload != 'undefined' && typeof msg.payload.alert != 'undefined' && msg.payload.alert > 0)
133
188
  {
134
- bridge.client.lights.getById(tempLightID)
135
- .then(light => {
136
- 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);
137
191
 
138
- // SET ALERT COLOR
139
- 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')
140
217
  {
141
- if(typeof msg.payload.rgb != 'undefined')
142
- {
143
- light.xy = rgb.convertRGBtoXY(msg.payload.rgb, light.model.id);
144
- }
145
- 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))
146
228
  {
147
- var rgbResult = hexRGB((msg.payload.hex).toString());
148
- 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);
149
232
  }
150
- else if(typeof msg.payload.color != 'undefined')
233
+ else
151
234
  {
152
- if(new RegExp("random|any|whatever").test(msg.payload.color))
153
- {
154
- var randomColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
155
- var rgbResult = hexRGB(randomColor);
156
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
157
- }
158
- else
235
+ var colorHex = colorUtils.colornames(msg.payload.color);
236
+ if(colorHex)
159
237
  {
160
- var colorHex = colornames(msg.payload.color);
161
- if(colorHex)
162
- {
163
- var rgbResult = hexRGB(colorHex);
164
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
165
- }
238
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
239
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
166
240
  }
167
241
  }
168
- else
169
- {
170
- light.xy = rgb.convertRGBtoXY([255,0,0], light.model.id);
171
- }
172
- }
173
-
174
- // ACTIVATE
175
- light.on = true;
176
- light.brightness = 254;
177
- light.transitionTime = 0;
178
- return bridge.client.lights.save(light);
179
- })
180
- .then(light => {
181
- if(!config.lightid) { scope.sendLightStatus(light, send, done); }
182
- return light;
183
- })
184
- .then(light => {
185
- // ACTIVATE ALERT
186
- if(light != false)
187
- {
188
- light.alert = 'lselect';
189
- return bridge.client.lights.save(light);
190
242
  }
191
243
  else
192
244
  {
193
- return false;
245
+ XYAlertColor = colorUtils.rgbToXy(255, 0, 0, currentState.info.model.colorGamut);
194
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);
195
265
  })
196
- .then(light => {
197
- // TURN OFF ALERT
198
- if(light != false)
266
+ .then(function(status) {
267
+ // 3. RESET PREVIOUS STATE (AFTER X SECONDS)
268
+ setTimeout(function()
199
269
  {
200
- var lightPreviousState = scope.context().get('lightPreviousState');
201
- var alertSeconds = parseInt(msg.payload.alert);
270
+ const tempPreviousState = scope.context().get('lightPreviousState');
271
+ var tempPreviousStatePatch = {};
202
272
 
203
- setTimeout(function() {
204
- light.on = lightPreviousState[0];
205
- light.alert = 'none';
206
- light.brightness = lightPreviousState[1];
207
- 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
+ }
208
282
 
209
- 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)
210
291
  {
211
- light.xy = lightPreviousState[2];
292
+ bridge.patch("light", tempLightID, { on: { on: true } })
293
+ .then(function() { if(done) { done(); }});
212
294
  }
213
-
214
- bridge.client.lights.save(light);
215
- }, alertSeconds * 1000);
216
- }
295
+ });
296
+ }, parseInt(msg.payload.alert) * 1000);
217
297
  })
218
- .catch(error => {
219
- scope.error(error, msg);
298
+ .catch(function(errors) {
299
+ scope.error(errors);
220
300
  scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
221
- if(done) { done(error); }
222
301
  });
223
302
  }
224
303
  // ANIMATION STARTED?
225
304
  else if(typeof msg.animation != 'undefined' && msg.animation.status == true && msg.animation.restore == true)
226
305
  {
227
- bridge.client.lights.getById(tempLightID)
228
- .then(light => {
229
- scope.context().set('lightPreviousState', [(light.on) ? true : false, light.brightness, light.xy ? light.xy : false]);
230
- })
231
- .catch(error => {
232
- scope.error(error, msg);
233
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
234
- if(done) { done(error); }
235
- });
306
+ // SAVE PREVIOUS STATE
307
+ scope.context().set('lightPreviousState', currentState);
236
308
  }
237
309
  // ANIMATION STOPPED AND RESTORE ACTIVE?
238
310
  else if(typeof msg.animation != 'undefined' && msg.animation.status == false && msg.animation.restore == true)
239
311
  {
240
- bridge.client.lights.getById(tempLightID)
241
- .then(light => {
242
- var lightPreviousState = scope.context().get('lightPreviousState');
243
- light.on = lightPreviousState[0];
244
- light.alert = 'none';
245
- light.brightness = lightPreviousState[1];
246
- light.transitionTime = 2;
312
+ const tempPreviousState = scope.context().get('lightPreviousState');
313
+ var tempPreviousStatePatch = {};
247
314
 
248
- 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)
249
329
  {
250
- 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
+ });
251
340
  }
252
-
253
- bridge.client.lights.save(light);
254
- })
255
- .catch(error => {
256
- scope.error(error, msg);
257
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
258
- if(done) { done(error); }
259
341
  });
260
342
  }
261
- // EXTENDED TURN ON / OFF LIGHT
343
+ // EXTENDED COMMANDS
262
344
  else
263
345
  {
264
- bridge.client.lights.getById(tempLightID)
265
- .then(async (light) => {
266
- // IS LIGHT ON?
267
- var isCurrentlyOn = light.on;
268
-
269
- // SET LIGHT STATE SIMPLE MODE
270
- if(msg.payload === true||msg.payload === false)
271
- {
272
- var command = msg.payload;
273
- msg.payload = {
274
- on: command
275
- };
276
- }
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
+ }
277
351
 
278
- // HAS FUTURE STATE? -> MERGE INPUT
279
- if(futureState != null)
280
- {
281
- // MERGE
282
- 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
+ }
283
357
 
284
- // RESET
285
- futureState = null;
286
- }
358
+ // TOGGLE ON / OFF
359
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
360
+ {
361
+ patchObject["on"] = { on: !currentState.payload.on };
362
+ }
287
363
 
288
- // SET LIGHT STATE
289
- 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))
290
369
  {
291
- light.on = msg.payload.on;
292
- }
370
+ let ct = colorUtils.colorTemperature();
371
+ let autoBrightness = ((300-ct)/2)+100;
372
+ autoBrightness = (autoBrightness > 100) ? 100 : autoBrightness;
373
+ autoBrightness = (autoBrightness < 20) ? 20 : autoBrightness;
293
374
 
294
- // TOGGLE ON / OFF
295
- if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
296
- {
297
- light.on = light.on ? false : true;
375
+ // SET CALCULATED BRIGHTNESS
376
+ patchObject["dimming"] = { brightness: autoBrightness };
298
377
  }
299
-
300
- // SET BRIGHTNESS
301
- if(typeof msg.payload != 'undefined' && typeof msg.payload.brightness != 'undefined')
378
+ else
302
379
  {
303
- if(msg.payload.brightness > 100 || msg.payload.brightness < 0)
380
+ if(msg.payload.brightness > 100 || msg.payload.brightness < 0)
304
381
  {
305
382
  scope.error("Invalid brightness setting. Only 0 - 100 percent allowed");
306
383
  return false;
307
384
  }
308
385
  else if(msg.payload.brightness == 0)
309
386
  {
310
- light.on = false;
387
+ if(currentState.payload.on !== false) { patchObject["on"] = { on: false }; }
311
388
  }
312
389
  else
313
390
  {
314
- light.on = true;
315
- light.brightness = Math.round((254/100)*parseInt(msg.payload.brightness));
391
+ patchObject["dimming"] = { brightness: msg.payload.brightness };
316
392
  }
317
393
  }
318
- 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)
319
398
  {
320
- if(msg.payload.brightnessLevel > 254 || msg.payload.brightnessLevel < 0)
321
- {
322
- scope.error("Invalid brightness setting. Only 0 - 254 allowed");
323
- return false;
324
- }
325
- else if(msg.payload.brightness == 0)
326
- {
327
- light.on = false;
328
- }
329
- else
330
- {
331
- light.on = true;
332
- light.brightness = parseInt(msg.payload.brightnessLevel);
333
- }
399
+ scope.error("Invalid brightness setting. Only 0 - 254 allowed");
400
+ return false;
334
401
  }
335
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementBrightness != 'undefined')
402
+ else if(msg.payload.brightness == 0)
336
403
  {
337
- if (msg.payload.incrementBrightness > 0)
338
- {
339
- light.on = true;
340
- }
341
- light.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.incrementBrightness));
404
+ if(currentState.payload.on !== false) { patchObject["on"] = { on: false }; }
342
405
  }
343
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.decrementBrightness != 'undefined')
406
+ else
344
407
  {
345
- 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)
346
448
  {
347
- light.on = true;
449
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
450
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2], currentState.info.model.colorGamut);
348
451
  }
349
- light.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.decrementBrightness))*-1;
350
452
  }
351
453
 
352
- // SET HUMAN READABLE COLOR OR RANDOM
353
- 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')
354
490
  {
355
- if(new RegExp("random|any|whatever").test(msg.payload.color))
491
+ if(new RegExp("random|any|whatever").test(msg.payload.mixColor.color))
356
492
  {
357
- var randomColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
358
- var rgbResult = hexRGB(randomColor);
359
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
493
+ RGBColor = colorUtils.hexRgb(colorUtils.randomHexColor());
360
494
  }
361
495
  else
362
496
  {
363
- var colorHex = colornames(msg.payload.color);
497
+ var colorHex = colorUtils.colornames(msg.payload.mixColor.color);
364
498
  if(colorHex)
365
499
  {
366
- var rgbResult = hexRGB(colorHex);
367
- light.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], light.model.id);
500
+ RGBColor = colorUtils.hexRgb(colorHex);
368
501
  }
369
502
  }
370
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
+ }
371
516
 
372
- // SET RGB COLOR
373
- 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)
374
520
  {
375
- light.xy = rgb.convertRGBtoXY(msg.payload.rgb, light.model.id);
521
+ mixingAmount = msg.payload.mixColor.amount/100;
376
522
  }
377
523
 
378
- // SET HEX COLOR
379
- 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
380
533
  {
381
- var rgbResult = hexRGB((msg.payload.hex).toString());
382
- 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
+ };
383
537
  }
538
+ }
384
539
 
385
- // SET COLOR TEMPERATURE
386
- 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))
387
545
  {
388
546
  let colorTemp = parseInt(msg.payload.colorTemp);
389
547
  if(colorTemp >= 153 && colorTemp <= 500)
390
548
  {
391
- light.colorTemp = parseInt(msg.payload.colorTemp);
549
+ patchObject["color_temperature"] = { mirek: colorTemp };
392
550
  }
393
551
  else
394
552
  {
@@ -396,131 +554,188 @@ module.exports = function(RED)
396
554
  return false;
397
555
  }
398
556
  }
399
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementColorTemp != 'undefined')
400
- {
401
- light.incrementColorTemp = parseInt(msg.payload.incrementColorTemp, 10) || 0;
402
- }
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;
403
585
 
404
- // SET SATURATION
405
- 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)
406
615
  {
407
- if(msg.payload.saturation > 100 || msg.payload.saturation < 0)
408
- {
409
- scope.error("Invalid saturation setting. Only 0 - 254 allowed");
410
- return false;
411
- }
412
- else
413
- {
414
- light.saturation = Math.round((254/100)*parseInt(msg.payload.saturation));
415
- }
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
+ };
416
621
  }
622
+ }
417
623
 
418
- // SET TRANSITION TIME
419
- 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)
420
628
  {
421
- light.transitionTime = parseFloat(msg.payload.transitionTime);
629
+ scope.error("Invalid saturation setting. Only 0 - 100 allowed");
630
+ return false;
422
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);
423
637
 
424
- // SET COLORLOOP EFFECT
425
- 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)
426
656
  {
427
- if(msg.payload.colorloop === true) {
428
- light.effect = 'colorloop';
429
- }
430
- else if(msg.payload.colorloop === false) {
431
- light.effect = 'none';
432
- }
433
- // ENABLE FOR TIME INTERVAL
434
- else if(msg.payload.colorloop > 0) {
435
- light.effect = 'colorloop';
436
-
437
- // DISABLE AFTER
438
- setTimeout(function() {
439
- light.effect = 'none';
440
- bridge.client.lights.save(light);
441
- }, 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
+ });
442
665
  }
443
666
  }
444
667
 
445
- // SET DOMINANT COLORS FROM IMAGE
446
- 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)
447
669
  {
448
- var colors = await getColors(msg.payload.image);
449
- if(colors.length > 0)
670
+ XYColorSet = [];
671
+
672
+ for(let oneColor in msg.payload.gradient.rgb)
450
673
  {
451
- var colorsHEX = colors.map(color => color.hex());
452
- var rgbResult = hexRGB(colorsHEX[0]);
453
- 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
+ });
454
677
  }
455
678
  }
456
679
 
457
- // SAVE FOR LATER MODE?
458
- if((!light.on||!light.reachable)&&isCurrentlyOn==false)
680
+ if(typeof msg.payload.gradient.xyColor != 'undefined' && Array.isArray(msg.payload.gradient.xyColor) == true)
459
681
  {
460
- // SAVE FUTURE STATE
461
- futureState = msg;
682
+ XYColorSet = [];
462
683
 
463
- // IGNORE ON/OFF & TOGGLE
464
- if(typeof futureState.payload != 'undefined')
684
+ for(let oneColor in msg.payload.gradient.xyColor)
465
685
  {
466
- delete futureState.payload.on;
467
- delete futureState.payload.toggle;
686
+ XYColorSet.push({
687
+ color: { xy: oneColor }
688
+ });
468
689
  }
690
+ }
469
691
 
470
- // ANY OTHER COMMANDS?
471
- 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"])
472
703
  {
473
- return light;
704
+ scope.futurePatchState = merge.deep(scope.futurePatchState, patchObject);
705
+ return false;
474
706
  }
475
707
  }
476
708
 
477
- // SAVE STATE
478
- return bridge.client.lights.save(light);
479
- })
480
- .then(light => {
481
- scope.sendLightStatus(light, send, done);
482
- return light;
483
- })
484
- .catch(error => {
485
- scope.error(error, msg);
486
- scope.status({fill: "red", shape: "ring", text: "hue-light.node.error-input"});
487
- if(done) { done(error); }
488
- });
489
- }
490
- }
491
-
709
+ // CHANGE NODE UI STATE
710
+ if(config.lightid)
711
+ {
712
+ scope.status({fill: "grey", shape: "ring", text: "hue-light.node.command"});
713
+ }
492
714
 
493
- //
494
- // SEND LIGHT STATUS
495
- this.sendLightStatus = function(light, send, done)
496
- {
497
- 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
+ }
498
727
 
499
- // SEND STATUS
500
- if(light.on)
501
- {
502
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-light.node.turned-on-percent",{percent: hueLight.msg.payload.brightness}) });
503
- }
504
- else
505
- {
506
- scope.status({fill: "grey", shape: "dot", text: "hue-light.node.turned-off"});
507
- }
728
+ // SEND STATE
729
+ scope.send(currentState);
508
730
 
509
- // SEND MESSAGE
510
- if(!config.skipevents && send) { send(hueLight.msg); }
511
- if(done) { done(); }
731
+ // RESET LAST COMMAND
732
+ scope.lastCommand = null;
512
733
 
513
- // SAVE LAST STATE
514
- lastState = light;
734
+ if(done) { done(); }
735
+ }
736
+ }
515
737
  }
516
-
517
- //
518
- // CLOSE NODE / REMOVE EVENT LISTENER
519
- this.on('close', function()
520
- {
521
- bridge.events.removeAllListeners('light' + config.lightid);
522
- });
523
738
  }
524
739
 
525
740
  RED.nodes.registerType("hue-light", HueLight);
526
- }
741
+ }