node-red-contrib-huemagic 3.0.0 → 4.0.3

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