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,21 @@ 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 { HueGroupMessage } = 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
+ // EXPORT CONFIG
13
+ this.exportedConfig = config;
14
+
15
+ // SAVE FUTURE PATCH
16
+ this.futurePatchState = {};
17
+
18
+ // SAVE LAST COMMAND
19
+ this.lastCommand = null;
17
20
 
18
21
  // 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');
22
+ const colorUtils = require('./utils/color');
23
+ const merge = require('./utils/merge');
24
24
 
25
25
  //
26
26
  // CHECK CONFIG
@@ -30,6 +30,13 @@ module.exports = function(RED)
30
30
  return false;
31
31
  }
32
32
 
33
+ //
34
+ // UNIVERSAL MODE?
35
+ if(!config.groupid)
36
+ {
37
+ this.status({fill: "grey", shape: "dot", text: "hue-group.node.universal"});
38
+ }
39
+
33
40
  //
34
41
  // UPDATE STATE
35
42
  if(typeof bridge.disableupdates != 'undefined'||bridge.disableupdates == false)
@@ -37,164 +44,231 @@ module.exports = function(RED)
37
44
  this.status({fill: "grey", shape: "dot", text: "hue-group.node.init"});
38
45
  }
39
46
 
40
-
41
- if(config.groupid)
47
+ //
48
+ // SUBSCRIBE TO UPDATES FROM THE BRIDGE
49
+ bridge.subscribe("group", config.groupid, function(info)
42
50
  {
43
- bridge.events.on('group' + config.groupid, function(group)
44
- {
45
- var hueGroup = new HueGroupMessage(group, config, lastState);
46
- var brightnessNotice = (hueGroup.msg.payload.brightness > -1) ? RED._("hue-group.node.brightness",{percent: hueGroup.msg.payload.brightness}) : "";
51
+ let currentState = bridge.get("group", info.id, { colornames: config.colornamer ? true : false });
47
52
 
48
- // HAS FUTURE STATE?
49
- if(futureState != null && group.anyOn)
53
+ // RESSOURCE FOUND?
54
+ if(currentState !== false)
55
+ {
56
+ // NOT IN UNIVERAL MODE? -> CHANGE UI STATES
57
+ if(config.groupid)
50
58
  {
51
- scope.applyCommands(futureState, null, null);
52
- }
59
+ // APPLY FUTURE STATE COMMANDS
60
+ if(Object.values(scope.futurePatchState).length > 0)
61
+ {
62
+ scope.applyCommands({}, null, null);
63
+ }
53
64
 
54
- if(group.allOn)
55
- {
56
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-group.node.all-on") + brightnessNotice});
57
- }
58
- else if(group.anyOn)
59
- {
60
- scope.status({fill: "yellow", shape: "ring", text: RED._("hue-group.node.some-on") + brightnessNotice});
61
- }
62
- else if(group.on)
63
- {
64
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-group.node.turned-on") + brightnessNotice});
65
- }
66
- else
67
- {
68
- scope.status({fill: "grey", shape: "dot", text: "hue-group.node.all-off"});
65
+ if(currentState.payload.on === true)
66
+ {
67
+ scope.status({fill: "yellow", shape: "dot", text: "hue-group.node.turned-on"});
68
+ }
69
+ else
70
+ {
71
+ scope.status({fill: "grey", shape: "dot", text: "hue-group.node.all-off"});
72
+ }
69
73
  }
70
74
 
71
75
  // SEND MESSAGE
72
- if(!config.skipevents) { scope.send(hueGroup.msg); }
76
+ if(!config.skipevents && (config.initevents || info.suppressMessage == false))
77
+ {
78
+ // SET LAST COMMAND
79
+ if(scope.lastCommand !== null)
80
+ {
81
+ currentState.command = scope.lastCommand;
82
+ }
73
83
 
74
- // SAVE LAST STATE
75
- lastState = group;
76
- });
77
- }
78
- else
79
- {
80
- scope.status({fill: "grey", shape: "dot", text: "hue-group.node.universal"});
81
- }
84
+ // SEND STATE
85
+ scope.send(currentState);
86
+
87
+ // RESET LAST COMMAND
88
+ scope.lastCommand = null;
89
+ }
90
+ }
91
+ });
82
92
 
93
+ //
94
+ // CONTROL GROUP
95
+ this.on('input', function(msg, send, done) { scope.applyCommands(msg, send, done); });
83
96
 
84
97
  //
85
- // TURN ON / OFF GROUP
86
- this.on('input', function(msg, send, done)
98
+ // APPLY COMMANDS (API v1 because CLIP/v2 does not yet support all features)
99
+ this.applyCommands = async function(msg, send = null, done = null)
87
100
  {
88
- // Node-RED < 1.0
101
+ // SET SEND
89
102
  send = send || function() { scope.send.apply(scope,arguments); }
90
- scope.applyCommands(msg, send, done);
91
- });
92
103
 
104
+ // SAVE LAST COMMAND
105
+ scope.lastCommand = msg;
93
106
 
94
- //
95
- // APPLY COMMANDS
96
- this.applyCommands = function(msg, send = null, done = null)
97
- {
98
- var context = this.context();
99
- var tempGroupID = (msg.topic != null && isNaN(msg.topic) == false && msg.topic.length > 0) ? parseInt(msg.topic) : config.groupid;
107
+ // CREATE PATCH
108
+ let patchObject = {};
109
+
110
+ // DEFINE SENSOR ID & CURRENT STATE
111
+ const tempGroupID = (msg.topic != null) ? msg.topic : config.groupid;
112
+ let currentState = bridge.get("group", tempGroupID, { colornames: config.colornamer ? true : false });
100
113
 
101
- // CHECK IF GROUP ID IS SET
102
- if(tempGroupID == null)
114
+ // CHECK IF LIGHT ID IS SET
115
+ if(!tempGroupID)
103
116
  {
104
117
  scope.error(RED._("hue-group.node.error-no-id"));
105
118
  return false;
106
119
  }
107
120
 
121
+ // GET FUTURE STATE
122
+ if(Object.values(scope.futurePatchState).length > 0)
123
+ {
124
+ patchObject = Object.assign({}, scope.futurePatchState);
125
+ scope.futurePatchState = {};
126
+ }
127
+
108
128
  // GET CURRENT STATE
109
- if(typeof msg.payload != 'undefined' && typeof msg.payload.status != 'undefined')
129
+ if( (typeof msg.payload != 'undefined' && typeof msg.payload.status != 'undefined') || (typeof msg.__user_inject_props__ != 'undefined' && msg.__user_inject_props__ == "status") )
110
130
  {
111
- bridge.client.groups.getById(tempGroupID)
112
- .then(group => {
113
- return scope.sendGroupStatus(group, send, done);
114
- });
131
+ // SET LAST COMMAND
132
+ if(scope.lastCommand !== null)
133
+ {
134
+ currentState.command = scope.lastCommand;
135
+ }
136
+
137
+ // SEND STATE
138
+ scope.send(currentState);
139
+
140
+ // RESET LAST COMMAND
141
+ scope.lastCommand = null;
115
142
 
143
+ if(done) { done(); }
116
144
  return true;
117
145
  }
118
146
 
147
+ // COLORLOOP EFFECT
148
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.colorloop != 'undefined' && msg.payload.colorloop > 0)
149
+ {
150
+ patchObject = {
151
+ "on": true,
152
+ "effect": "colorloop",
153
+ "bri": msg.payload.brightness ? Math.round((254/100)*msg.payload.brightness) : 254
154
+ };
155
+
156
+ bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
157
+ .then(function(status) {
158
+ // RESET COLORLOOP ANIMATION AFTER X SECONDS
159
+ setTimeout(function()
160
+ {
161
+ bridge.patch("group", currentState.info.idV1 + "/action", { "effect": "none" }, 1)
162
+ .then(function() { if(done) { done(); }});
163
+ }, parseInt(msg.payload.colorloop) * 1000);
164
+ })
165
+ .catch(function(errors) {
166
+ scope.error(errors);
167
+ scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
168
+ });
169
+
170
+ return false;
171
+ }
172
+
119
173
  // ALERT EFFECT
120
174
  if(typeof msg.payload != 'undefined' && typeof msg.payload.alert != 'undefined' && msg.payload.alert > 0)
121
175
  {
122
- bridge.client.groups.getById(tempGroupID)
123
- .then(group => {
124
- context.set('groupPreviousState', [group.on ? true : false, group.brightness, group.xy ? group.xy : false]);
176
+ // SAVE PREVIOUS STATE
177
+ scope.context().set('groupPreviousState', currentState);
178
+
179
+ // TURN ON LIGHT
180
+ if(currentState.payload.on === false)
181
+ {
182
+ patchObject["on"] = true;
183
+ }
184
+
185
+ // SET BRIGHTNESS
186
+ if(!msg.payload.brightness && currentState.payload.brightness != 100)
187
+ {
188
+ patchObject["bri"] = 254;
189
+ }
190
+ else if(msg.payload.brightness)
191
+ {
192
+ patchObject["bri"] = Math.round((254/100)*msg.payload.brightness);
193
+ }
125
194
 
126
- // SET ALERT COLOR
127
- if(group.xy)
195
+ // SET TRANSITION
196
+ patchObject["transitiontime"] = 0;
197
+
198
+ // CAN CHANGE COLOR?
199
+ let XYAlertColor = {};
200
+
201
+ if(typeof msg.payload.rgb != 'undefined')
202
+ {
203
+ XYAlertColor = colorUtils.rgbToXy(msg.payload.rgb[0], msg.payload.rgb[1], msg.payload.rgb[2] );
204
+ }
205
+ else if(typeof msg.payload.hex != 'undefined')
206
+ {
207
+ let rgbFromHex = colorUtils.hexRgb((msg.payload.hex).toString());
208
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2] );
209
+ }
210
+ else if(typeof msg.payload.color != 'undefined')
211
+ {
212
+ if(new RegExp("random|any|whatever").test(msg.payload.color))
128
213
  {
129
- if(typeof msg.payload.rgb != 'undefined')
130
- {
131
- group.xy = rgb.convertRGBtoXY(msg.payload.rgb, false);
132
- }
133
- else if(typeof msg.payload.hex != 'undefined')
214
+ const randomColor = colorUtils.randomHexColor();
215
+ let rgbFromHex = colorUtils.hexRgb(rgbFromHex);
216
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2] );
217
+ }
218
+ else
219
+ {
220
+ var colorHex = colorUtils.colornames(msg.payload.color);
221
+ if(colorHex)
134
222
  {
135
- var rgbResult = hexRGB((msg.payload.hex).toString());
136
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
223
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
224
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2] );
137
225
  }
138
- else if(typeof msg.payload.color != 'undefined')
226
+ }
227
+ }
228
+ else
229
+ {
230
+ XYAlertColor = colorUtils.rgbToXy(255, 0, 0 );
231
+ }
232
+
233
+ patchObject["xy"] = [XYAlertColor.x, XYAlertColor.y];
234
+
235
+ // SET ALERT EFFECT
236
+ patchObject["alert"] = "lselect";
237
+
238
+ // 1. TURN ON THE LIGHT BULB
239
+ bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
240
+ .then(function(status) {
241
+ setTimeout(function()
242
+ {
243
+ const tempPreviousState = scope.context().get('groupPreviousState');
244
+ var tempPreviousStatePatch = {};
245
+
246
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
247
+ if(tempPreviousState.payload.xyColor)
139
248
  {
140
- if(new RegExp("random|any|whatever").test(msg.payload.color))
141
- {
142
- var randomColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
143
- var rgbResult = hexRGB(randomColor);
144
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
145
- }
146
- else
147
- {
148
- var colorHex = colornames(msg.payload.color);
149
- if(colorHex)
150
- {
151
- var rgbResult = hexRGB(colorHex);
152
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
153
- }
154
- }
249
+ tempPreviousStatePatch.xy = [tempPreviousState.payload.xyColor.x, tempPreviousState.payload.xyColor.y];
155
250
  }
156
- else
251
+ else if(tempPreviousState.payload.colorTemp)
157
252
  {
158
- group.xy = rgb.convertRGBtoXY([255,0,0], false);
253
+ tempPreviousStatePatch.ct = tempPreviousState.payload.colorTemp;
159
254
  }
160
- }
161
255
 
162
- // ACTIVATE
163
- group.on = true;
164
- group.brightness = 254;
165
- group.transitionTime = 0;
166
- return bridge.client.groups.save(group);
167
- })
168
- .then(group => {
169
- // ACTIVATE ALERT
170
- group.alert = 'lselect';
171
- return bridge.client.groups.save(group);
172
- })
173
- .then(group => {
174
- if(!config.groupid) { scope.sendGroupStatus(group, send, done); }
175
- return group;
176
- })
177
- .then(group => {
178
- // TURN OFF ALERT
179
- var groupPreviousState = context.get('groupPreviousState');
180
- var alertSeconds = parseInt(msg.payload.alert);
181
-
182
- setTimeout(function() {
183
- group.on = groupPreviousState[0];
184
- group.alert = 'none';
185
- group.brightness = groupPreviousState[1];
186
- group.transitionTime = 2;
187
-
188
- if(groupPreviousState[2] != false)
256
+ bridge.patch("group", currentState.info.idV1 + "/action", tempPreviousStatePatch, 1)
257
+ .then(function(status)
189
258
  {
190
- group.xy = groupPreviousState[2];
191
- }
192
-
193
- bridge.client.groups.save(group);
194
- }, alertSeconds * 1000);
259
+ return bridge.patch("group", currentState.info.idV1 + "/action", { on: false }, 1);
260
+ })
261
+ .then(function(status) {
262
+ if(done) { done(); }
263
+ if(tempPreviousState.payload.on === true)
264
+ {
265
+ bridge.patch("group", currentState.info.idV1, { on: true }, 1);
266
+ }
267
+ });
268
+ }, parseInt(msg.payload.alert) * 1000);
195
269
  })
196
- .catch(error => {
197
- scope.error(error, msg);
270
+ .catch(function(errors) {
271
+ scope.error(errors);
198
272
  scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
199
273
  if(done) { done(error); }
200
274
  });
@@ -202,310 +276,263 @@ module.exports = function(RED)
202
276
  // ANIMATION STARTED?
203
277
  else if(typeof msg.animation != 'undefined' && msg.animation.status == true && msg.animation.restore == true)
204
278
  {
205
- bridge.client.groups.getById(tempGroupID)
206
- .then(group => {
207
- context.set('groupPreviousState', [group.on ? true : false, group.brightness, group.xy ? group.xy : false]);
208
- })
209
- .catch(error => {
210
- scope.error(error, msg);
211
- scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
212
- if(done) { done(error); }
213
- });
279
+ // SAVE PREVIOUS STATE
280
+ scope.context().set('groupPreviousState', currentState);
214
281
  }
215
282
  // ANIMATION STOPPED AND RESTORE ACTIVE?
216
283
  else if(typeof msg.animation != 'undefined' && msg.animation.status == false && msg.animation.restore == true)
217
284
  {
218
- bridge.client.groups.getById(tempGroupID)
219
- .then(group => {
220
- var groupPreviousState = context.get('groupPreviousState');
285
+ const tempPreviousState = scope.context().get('groupPreviousState');
286
+ var tempPreviousStatePatch = {};
221
287
 
222
- group.on = groupPreviousState[0];
223
- group.alert = 'none';
224
- group.brightness = groupPreviousState[1];
225
- group.transitionTime = 2;
288
+ tempPreviousStatePatch.dimming = { brightness: tempPreviousState.payload.brightness };
289
+ if(tempPreviousState.payload.xyColor)
290
+ {
291
+ tempPreviousStatePatch.xy = [tempPreviousState.payload.xyColor.x, tempPreviousState.payload.xyColor.y];
292
+ }
293
+ else if(tempPreviousState.payload.colorTemp)
294
+ {
295
+ tempPreviousStatePatch.ct = tempPreviousState.payload.colorTemp;
296
+ }
226
297
 
227
- if(groupPreviousState[2] != false)
298
+ bridge.patch("light", currentState.info.lightIds[l], tempPreviousStatePatch).
299
+ then(function(status)
300
+ {
301
+ if(tempPreviousState.payload.on === false)
228
302
  {
229
- group.xy = groupPreviousState[2];
303
+ bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
304
+ }
305
+ else
306
+ {
307
+ bridge.patch("light", currentState.info.lightIds[l], { on: { on: false } })
308
+ .then(function(status) {
309
+ return bridge.patch("light", currentState.info.lightIds[l], { on: { on: true } })
310
+ });
230
311
  }
231
-
232
- bridge.client.groups.save(group);
233
- })
234
- .catch(error => {
235
- scope.error(error, msg);
236
- scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
237
- if(done) { done(error); }
238
312
  });
239
313
  }
240
- // EXTENDED TURN ON / OFF GROUP
314
+ // EXTENDED COMMANDS
241
315
  else
242
316
  {
243
- bridge.client.groups.getById(tempGroupID)
244
- .then(async (group) =>
317
+ // SET LIGHT STATE SIMPLE MODE
318
+ if(msg.payload === true||msg.payload === false)
245
319
  {
246
- // IS GROUP ON?
247
- var isCurrentlyOn = group.on;
248
-
249
- // SET GROUP STATE SIMPLE MODE
250
- if(msg.payload === true||msg.payload === false)
320
+ if(msg.payload !== currentState.payload.on)
251
321
  {
252
- var command = msg.payload;
253
- msg.payload = {
254
- on: command
255
- };
322
+ patchObject["on"] = msg.payload;
256
323
  }
324
+ }
257
325
 
258
- // HAS FUTURE STATE? -> MERGE INPUT
259
- if(futureState != null)
326
+ // SET LIGHT STATE
327
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.on != 'undefined' && (msg.payload.on === true || msg.payload.on === false))
328
+ {
329
+ if(msg.payload.on !== currentState.payload.on)
260
330
  {
261
- // MERGE
262
- msg = merge.deep(futureState, msg);
263
-
264
- // RESET
265
- futureState = null;
331
+ patchObject["on"] = msg.payload.on;
266
332
  }
333
+ }
267
334
 
268
- // SET GROUP STATE
269
- if (typeof msg.payload != 'undefined' && typeof msg.payload.on != 'undefined')
270
- {
271
- group.on = msg.payload.on;
272
- }
273
-
274
- // TOGGLE ON / OFF
275
- if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
276
- {
277
- group.on = group.on ? false : true;
278
- }
279
-
280
- // SET BRIGHTNESS
281
- if (typeof msg.payload != 'undefined' && typeof msg.payload.brightness != 'undefined')
282
- {
283
- if(msg.payload.brightness > 100 || msg.payload.brightness < 0) {
284
- scope.error("Invalid brightness setting. Only 0 - 100 percent allowed");
285
- return false;
286
- }
287
- else if (msg.payload.brightness == 0)
288
- {
289
- group.on = false;
290
- }
291
- else {
292
- group.on = true;
293
- group.brightness = Math.round((254 / 100) * parseInt(msg.payload.brightness));
294
- }
295
- }
296
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.brightnessLevel != 'undefined')
297
- {
298
- if(msg.payload.brightnessLevel > 254 || msg.payload.brightnessLevel < 0)
299
- {
300
- scope.error("Invalid brightness setting. Only 0 - 254 allowed");
301
- return false;
302
- }
303
- else if(msg.payload.brightness == 0)
304
- {
305
- group.on = false;
306
- }
307
- else
308
- {
309
- group.on = true;
310
- group.brightness = parseInt(msg.payload.brightnessLevel);
311
- }
312
- }
313
- else if (typeof msg.payload != 'undefined' && typeof msg.payload.incrementBrightness != 'undefined')
314
- {
315
- if(msg.payload.incrementBrightness > 0 && typeof msg.payload.ignoreOffLights == 'undefined')
316
- {
317
- group.on = true;
318
- }
319
- group.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.incrementBrightness));
320
- }
321
- else if (typeof msg.payload != 'undefined' && typeof msg.payload.decrementBrightness != 'undefined')
335
+ // TOGGLE ON / OFF
336
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.toggle != 'undefined')
337
+ {
338
+ patchObject["on"] = !currentState.payload.on;
339
+ }
340
+
341
+ // SET BRIGHTNESS
342
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.brightness != 'undefined')
343
+ {
344
+ // AUTO BRIGHTNESS BASED ON DAY TIME
345
+ if(new RegExp("auto|automatic").test(msg.payload.brightness))
322
346
  {
323
- if(msg.payload.decrementBrightness > 0 && typeof msg.payload.ignoreOffLights == 'undefined')
324
- {
325
- group.on = true;
326
- }
327
- group.incrementBrightness = Math.round((254/100)*parseInt(msg.payload.decrementBrightness))*-1;
328
- }
347
+ let ct = colorUtils.colorTemperature();
348
+ let autoBrightness = ((300-ct)/2)+100;
349
+ autoBrightness = (autoBrightness > 100) ? 100 : autoBrightness;
350
+ autoBrightness = (autoBrightness < 20) ? 20 : autoBrightness;
329
351
 
330
- // SET HUMAN READABLE COLOR
331
- if(typeof msg.payload != 'undefined' && typeof msg.payload.color != 'undefined' && typeof group.xy != 'undefined')
352
+ // SET CALCULATED BRIGHTNESS
353
+ patchObject["bri"] = Math.round((254/100)*autoBrightness);
354
+ }
355
+ else
332
356
  {
333
- if(new RegExp("random|any|whatever").test(msg.payload.color))
357
+ if(msg.payload.brightness > 100 || msg.payload.brightness < 0)
334
358
  {
335
- var randomColor = '#'+(Math.random()*0xFFFFFF<<0).toString(16);
336
- var rgbResult = hexRGB(randomColor);
337
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
359
+ scope.error("Invalid brightness setting. Only 0 - 100 percent allowed");
360
+ return false;
361
+ }
362
+ else if(msg.payload.brightness == 0)
363
+ {
364
+ patchObject["on"] = false;
338
365
  }
339
366
  else
340
367
  {
341
- var colorHex = colornames(msg.payload.color);
342
- if(colorHex)
343
- {
344
- var rgbResult = hexRGB(colorHex);
345
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
346
- }
368
+ patchObject["bri"] = Math.round((254/100)*msg.payload.brightness);
347
369
  }
348
370
  }
349
-
350
- // SET RGB COLOR
351
- if(typeof msg.payload != 'undefined' && typeof msg.payload.rgb != 'undefined' && typeof group.xy != 'undefined')
371
+ }
372
+ else if(typeof msg.payload != 'undefined' && typeof msg.payload.brightnessLevel != 'undefined')
373
+ {
374
+ if(msg.payload.brightnessLevel > 254 || msg.payload.brightnessLevel < 0)
352
375
  {
353
- group.xy = rgb.convertRGBtoXY(msg.payload.rgb, false);
376
+ scope.error("Invalid brightness setting. Only 0 - 254 allowed");
377
+ return false;
354
378
  }
355
-
356
- // SET HEX COLOR
357
- if(typeof msg.payload != 'undefined' && typeof msg.payload.hex != 'undefined' && typeof group.xy != 'undefined')
379
+ else if(msg.payload.brightness == 0)
380
+ {
381
+ patchObject["on"] = false;
382
+ }
383
+ else
358
384
  {
359
- var rgbResult = hexRGB((msg.payload.hex).toString());
360
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
385
+ patchObject["bri"] = msg.payload.brightnessLevel;
361
386
  }
387
+ }
362
388
 
363
- // SET SATURATION
364
- if(typeof msg.payload != 'undefined' && typeof msg.payload.saturation != 'undefined' && typeof group.saturation != 'undefined')
389
+ // SET HUMAN READABLE COLOR OR RANDOM
390
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.color != 'undefined')
391
+ {
392
+ let XYAlertColor = {};
393
+
394
+ if(new RegExp("random|any|whatever").test(msg.payload.color))
365
395
  {
366
- if(msg.payload.saturation > 100 || msg.payload.saturation < 0)
367
- {
368
- scope.error(RED._("error-invalid-sat"), msg);
369
- return false;
370
- }
371
- else
396
+ const randomColor = colorUtils.randomHexColor();
397
+ let rgbFromHex = colorUtils.hexRgb(randomColor);
398
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2] );
399
+ }
400
+ else
401
+ {
402
+ var colorHex = colorUtils.colornames(msg.payload.color);
403
+ if(colorHex)
372
404
  {
373
- group.saturation = Math.round((254/100)*parseInt(msg.payload.saturation));
405
+ let rgbFromHex = colorUtils.hexRgb(colorHex);
406
+ XYAlertColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2] );
374
407
  }
375
408
  }
376
409
 
377
- // SET COLOR TEMPERATURE
378
- if(typeof msg.payload != 'undefined' && typeof msg.payload.colorTemp != 'undefined' && typeof group.colorTemp != 'undefined')
410
+ patchObject["xy"] = [XYAlertColor.x, XYAlertColor.y];
411
+ }
412
+
413
+ // SET HEX COLOR
414
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.hex != 'undefined')
415
+ {
416
+ let rgbFromHex = colorUtils.hexRgb((msg.payload.hex).toString());
417
+ let xyColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2])
418
+
419
+ patchObject["xy"] = [xyColor.x, xyColor.y];
420
+ }
421
+
422
+ // SET RGB COLOR
423
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.rgb != 'undefined' && msg.payload.rgb.length === 3)
424
+ {
425
+ let xyColor = colorUtils.rgbToXy(msg.payload.rgb[0], msg.payload.rgb[1], msg.payload.rgb[2] )
426
+ patchObject["xy"] = [xyColor.x, xyColor.y];
427
+ }
428
+
429
+ // SET XY COLOR
430
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.xyColor != 'undefined')
431
+ {
432
+ patchObject["xy"] = [msg.payload.xyColor.x, msg.payload.xyColor.y];
433
+ }
434
+
435
+ // SET COLOR TEMPERATURE
436
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.colorTemp != 'undefined')
437
+ {
438
+ // DETERMINE IF AUTOMATIC, WARM, COLD, INT
439
+ if(!isNaN(msg.payload.colorTemp))
379
440
  {
380
441
  let colorTemp = parseInt(msg.payload.colorTemp);
381
442
  if(colorTemp >= 153 && colorTemp <= 500)
382
443
  {
383
- group.colorTemp = parseInt(msg.payload.colorTemp);
444
+ patchObject["ct"] = colorTemp;
384
445
  }
385
446
  else
386
447
  {
387
- scope.error(RED._("error-invalid-temp"), msg);
448
+ scope.error("Invalid color temprature. Only 153 - 500 allowed");
388
449
  return false;
389
450
  }
390
451
  }
391
- else if(typeof msg.payload != 'undefined' && typeof msg.payload.incrementColorTemp != 'undefined')
392
- {
393
- group.incrementColorTemp = parseInt(msg.payload.incrementColorTemp, 10) || 0;
394
- }
395
-
396
- // SET TRANSITION TIME
397
- if(typeof msg.payload != 'undefined' && typeof msg.payload.transitionTime != 'undefined')
452
+ else if(msg.payload.colorTemp == "cold")
398
453
  {
399
- group.transitionTime = parseFloat(msg.payload.transitionTime);
454
+ patchObject["ct"] = 153;
400
455
  }
401
-
402
- // SET COLORLOOP EFFECT
403
- if(typeof msg.payload != 'undefined' && typeof msg.payload.colorloop != 'undefined' && typeof group.xy != 'undefined')
456
+ else if(msg.payload.colorTemp == "normal")
404
457
  {
405
- if(msg.payload.colorloop === true) {
406
- group.effect = 'colorloop';
407
- }
408
- else if(msg.payload.colorloop === false) {
409
- group.effect = 'none';
410
- }
411
- // ENABLE FOR TIME INTERVAL
412
- else if(msg.payload.colorloop > 0) {
413
- group.effect = 'colorloop';
414
-
415
- // DISABLE AFTER
416
- setTimeout(function() {
417
- group.effect = 'none';
418
- bridge.client.lights.save(light);
419
- }, parseFloat(msg.payload.colorloop)*1000);
420
- }
458
+ patchObject["ct"] = 240;
421
459
  }
422
-
423
- // SET DOMINANT COLORS FROM IMAGE
424
- if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined' && typeof group.xy != 'undefined')
460
+ else if(msg.payload.colorTemp == "warm")
425
461
  {
426
- var colors = await getColors(msg.payload.image);
427
- if(colors.length > 0)
428
- {
429
- var colorsHEX = colors.map(color => color.hex());
430
- var rgbResult = hexRGB(colorsHEX[0]);
431
- group.xy = rgb.convertRGBtoXY([rgbResult.red, rgbResult.green, rgbResult.blue], false);
432
- }
462
+ patchObject["ct"] = 400;
463
+ }
464
+ else if(msg.payload.colorTemp == "hot")
465
+ {
466
+ patchObject["ct"] = 500;
467
+ }
468
+ else
469
+ {
470
+ // SET TEMPERATURE
471
+ patchObject["ct"] = colorUtils.colorTemperature();
433
472
  }
473
+ }
434
474
 
435
- // SAVE FOR LATER MODE?
436
- if(!group.on&&isCurrentlyOn==false)
475
+ // SET TRANSITION TIME
476
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.transitionTime != 'undefined')
477
+ {
478
+ let targetTransitionTime = parseFloat(msg.payload.transitionTime)*1000;
479
+ targetTransitionTime = (targetTransitionTime > 6000000) ? 6000000 : targetTransitionTime;
480
+ targetTransitionTime = (targetTransitionTime < 0) ? 0 : targetTransitionTime;
481
+
482
+ patchObject["transitiontime"] = targetTransitionTime/100;
483
+ }
484
+
485
+ // SET DOMINANT COLORS FROM IMAGE
486
+ if(typeof msg.payload != 'undefined' && typeof msg.payload.image != 'undefined')
487
+ {
488
+ var colors = await colorUtils.getColors(msg.payload.image);
489
+ if(colors.length > 0)
437
490
  {
438
- futureState = msg;
491
+ var colorsHEX = colors.map(color => color.hex());
492
+ let rgbFromHex = colorUtils.hexRgb(colorsHEX[0]);
493
+ let xyColor = colorUtils.rgbToXy(rgbFromHex[0], rgbFromHex[1], rgbFromHex[2]);
439
494
 
440
- // IGNORE ON/OFF & TOGGLE
441
- if(typeof futureState.payload != 'undefined')
442
- {
443
- delete futureState.payload.on;
444
- delete futureState.payload.toggle;
445
- }
495
+ patchObject["xy"] = [xyColor.x, xyColor.y];
496
+ }
497
+ }
446
498
 
447
- // ANY OTHER COMMANDS?
448
- if(typeof futureState.payload != 'undefined' && Object.keys(futureState.payload).length > 0)
499
+ //
500
+ // SHOULD PATCH?
501
+ if(Object.values(patchObject).length > 0)
502
+ {
503
+ // IS FOR LATER?
504
+ if(currentState.payload.on === false)
505
+ {
506
+ if(!patchObject["on"])
449
507
  {
450
- return group;
508
+ scope.futurePatchState = merge.deep(scope.futurePatchState, patchObject);
509
+ return false;
451
510
  }
452
511
  }
453
512
 
454
- return bridge.client.groups.save(group);
455
- })
456
- .then(group => {
457
- scope.sendGroupStatus(group, send, done);
458
- return group;
459
- })
460
- .catch(error => {
461
- scope.error(error, msg);
462
- scope.status({fill: "red", shape: "ring", text: "hue-group.node.error-input"});
463
- if(done) { done(error); }
464
- });
465
- }
466
- }
467
-
513
+ // PATCH!
514
+ bridge.patch("group", currentState.info.idV1 + "/action", patchObject, 1)
515
+ .then(function() { if(done) { done(); }})
516
+ .catch(function(errors) { scope.error(errors); });
517
+ }
518
+ else
519
+ {
520
+ // SET LAST COMMAND
521
+ if(scope.lastCommand !== null)
522
+ {
523
+ currentState.command = scope.lastCommand;
524
+ }
468
525
 
469
- //
470
- // SEND GROUP STATUS
471
- this.sendGroupStatus = function(group, send, done)
472
- {
473
- var hueGroup = new HueGroupMessage(group, config, lastState);
474
- var brightnessNotice = (hueGroup.msg.payload.brightness > -1) ? RED._("hue-group.node.brightness",{percent: hueGroup.msg.payload.brightness}) : "";
526
+ // SEND STATE
527
+ scope.send(currentState);
475
528
 
476
- // SEND STATUS
477
- if(group.allOn)
478
- {
479
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-group.node.all-on") + brightnessNotice});
480
- }
481
- else if(group.anyOn)
482
- {
483
- scope.status({fill: "yellow", shape: "ring", text: RED._("hue-group.node.some-on") + brightnessNotice});
484
- }
485
- else if(group.on)
486
- {
487
- scope.status({fill: "yellow", shape: "dot", text: RED._("hue-group.node.turned-on") + brightnessNotice});
488
- }
489
- else
490
- {
491
- scope.status({fill: "grey", shape: "dot", text: "hue-group.node.all-off"});
529
+ // RESET LAST COMMAND
530
+ scope.lastCommand = null;
531
+ if(done) { done(); }
532
+ }
492
533
  }
493
-
494
- // SEND MESSAGE
495
- if(!config.skipevents && send) { send(hueGroup.msg); }
496
- if(done) { done(); }
497
-
498
- // SAVE LAST STATE
499
- lastState = group;
500
534
  }
501
-
502
- //
503
- // CLOSE NODE / REMOVE EVENT LISTENER
504
- this.on('close', function()
505
- {
506
- bridge.events.removeAllListeners('group' + config.groupid);
507
- });
508
535
  }
509
536
 
510
537
  RED.nodes.registerType("hue-group", HueGroup);
511
- }
538
+ }