node-red-contrib-huemagic 2.8.6 → 4.0.2

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