node-red-contrib-huemagic 4.2.1 → 5.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 (515) hide show
  1. package/CHANGELOG.md +95 -5
  2. package/README.md +511 -122
  3. package/examples/Deutsch/Hue Automation.json +176 -0
  4. package/examples/Deutsch/Hue Bridge.json +256 -0
  5. package/examples/Deutsch/Hue Brightness.json +150 -0
  6. package/examples/Deutsch/Hue Buttons.json +88 -0
  7. package/examples/Deutsch/Hue Contact.json +150 -0
  8. package/examples/Deutsch/Hue Group.json +658 -0
  9. package/examples/Deutsch/Hue Light.json +893 -0
  10. package/examples/Deutsch/Hue Magic.json +148 -0
  11. package/examples/Deutsch/Hue Motion.json +150 -0
  12. package/examples/Deutsch/Hue Rules.json +150 -0
  13. package/examples/Deutsch/Hue Scene.json +178 -0
  14. package/examples/Deutsch/Hue Speaker.json +274 -0
  15. package/examples/Deutsch/Hue Sync Box.json +378 -0
  16. package/examples/Deutsch/Hue Temperature.json +150 -0
  17. package/examples/English/Hue Automation.json +176 -0
  18. package/examples/{Hue Bridge.json → English/Hue Bridge.json } +1 -1
  19. package/examples/{Hue Brightness.json → English/Hue Brightness.json } +1 -1
  20. package/examples/{Hue Buttons.json → English/Hue Buttons.json } +1 -1
  21. package/examples/English/Hue Contact.json +150 -0
  22. package/examples/{Hue Group.json → English/Hue Group.json } +1 -1
  23. package/examples/{Hue Light.json → English/Hue Light.json } +1 -1
  24. package/examples/{Hue Magic.json → English/Hue Magic.json } +1 -1
  25. package/examples/{Hue Motion.json → English/Hue Motion.json } +1 -1
  26. package/examples/{Hue Rules.json → English/Hue Rules.json } +1 -1
  27. package/examples/{Hue Scene.json → English/Hue Scene.json } +1 -1
  28. package/examples/English/Hue Speaker.json +274 -0
  29. package/examples/English/Hue Sync Box.json +378 -0
  30. package/examples/{Hue Temperature.json → English/Hue Temperature.json } +1 -1
  31. package/examples/Espa/303/261ol/Hue Automation.json" +176 -0
  32. package/examples/Espa/303/261ol/Hue Bridge.json" +256 -0
  33. package/examples/Espa/303/261ol/Hue Brightness.json" +150 -0
  34. package/examples/Espa/303/261ol/Hue Buttons.json" +88 -0
  35. package/examples/Espa/303/261ol/Hue Contact.json" +150 -0
  36. package/examples/Espa/303/261ol/Hue Group.json" +658 -0
  37. package/examples/Espa/303/261ol/Hue Light.json" +893 -0
  38. package/examples/Espa/303/261ol/Hue Magic.json" +148 -0
  39. package/examples/Espa/303/261ol/Hue Motion.json" +150 -0
  40. package/examples/Espa/303/261ol/Hue Rules.json" +150 -0
  41. package/examples/Espa/303/261ol/Hue Scene.json" +178 -0
  42. package/examples/Espa/303/261ol/Hue Speaker.json" +274 -0
  43. package/examples/Espa/303/261ol/Hue Sync Box.json" +378 -0
  44. package/examples/Espa/303/261ol/Hue Temperature.json" +150 -0
  45. package/examples/Fran/303/247ais/Hue Automation.json" +176 -0
  46. package/examples/Fran/303/247ais/Hue Bridge.json" +256 -0
  47. package/examples/Fran/303/247ais/Hue Brightness.json" +150 -0
  48. package/examples/Fran/303/247ais/Hue Buttons.json" +88 -0
  49. package/examples/Fran/303/247ais/Hue Contact.json" +150 -0
  50. package/examples/Fran/303/247ais/Hue Group.json" +658 -0
  51. package/examples/Fran/303/247ais/Hue Light.json" +893 -0
  52. package/examples/Fran/303/247ais/Hue Magic.json" +148 -0
  53. package/examples/Fran/303/247ais/Hue Motion.json" +150 -0
  54. package/examples/Fran/303/247ais/Hue Rules.json" +150 -0
  55. package/examples/Fran/303/247ais/Hue Scene.json" +178 -0
  56. package/examples/Fran/303/247ais/Hue Speaker.json" +274 -0
  57. package/examples/Fran/303/247ais/Hue Sync Box.json" +378 -0
  58. package/examples/Fran/303/247ais/Hue Temperature.json" +150 -0
  59. package/examples/Italiano/Hue Automation.json +176 -0
  60. package/examples/Italiano/Hue Bridge.json +256 -0
  61. package/examples/Italiano/Hue Brightness.json +150 -0
  62. package/examples/Italiano/Hue Buttons.json +88 -0
  63. package/examples/Italiano/Hue Contact.json +150 -0
  64. package/examples/Italiano/Hue Group.json +658 -0
  65. package/examples/Italiano/Hue Light.json +893 -0
  66. package/examples/Italiano/Hue Magic.json +148 -0
  67. package/examples/Italiano/Hue Motion.json +150 -0
  68. package/examples/Italiano/Hue Rules.json +150 -0
  69. package/examples/Italiano/Hue Scene.json +178 -0
  70. package/examples/Italiano/Hue Speaker.json +274 -0
  71. package/examples/Italiano/Hue Sync Box.json +378 -0
  72. package/examples/Italiano/Hue Temperature.json +150 -0
  73. package/examples/Nederlands/Hue Automation.json +176 -0
  74. package/examples/Nederlands/Hue Bridge.json +256 -0
  75. package/examples/Nederlands/Hue Brightness.json +150 -0
  76. package/examples/Nederlands/Hue Buttons.json +88 -0
  77. package/examples/Nederlands/Hue Contact.json +150 -0
  78. package/examples/Nederlands/Hue Group.json +658 -0
  79. package/examples/Nederlands/Hue Light.json +893 -0
  80. package/examples/Nederlands/Hue Magic.json +148 -0
  81. package/examples/Nederlands/Hue Motion.json +150 -0
  82. package/examples/Nederlands/Hue Rules.json +150 -0
  83. package/examples/Nederlands/Hue Scene.json +178 -0
  84. package/examples/Nederlands/Hue Speaker.json +274 -0
  85. package/examples/Nederlands/Hue Sync Box.json +378 -0
  86. package/examples/Nederlands/Hue Temperature.json +150 -0
  87. package/examples/Polski/Hue Automation.json +176 -0
  88. package/examples/Polski/Hue Bridge.json +256 -0
  89. package/examples/Polski/Hue Brightness.json +150 -0
  90. package/examples/Polski/Hue Buttons.json +88 -0
  91. package/examples/Polski/Hue Contact.json +150 -0
  92. package/examples/Polski/Hue Group.json +658 -0
  93. package/examples/Polski/Hue Light.json +893 -0
  94. package/examples/Polski/Hue Magic.json +148 -0
  95. package/examples/Polski/Hue Motion.json +150 -0
  96. package/examples/Polski/Hue Rules.json +150 -0
  97. package/examples/Polski/Hue Scene.json +178 -0
  98. package/examples/Polski/Hue Speaker.json +274 -0
  99. package/examples/Polski/Hue Sync Box.json +378 -0
  100. package/examples/Polski/Hue Temperature.json +150 -0
  101. package/examples/Portugu/303/252s/Hue Automation.json" +176 -0
  102. package/examples/Portugu/303/252s/Hue Bridge.json" +256 -0
  103. package/examples/Portugu/303/252s/Hue Brightness.json" +150 -0
  104. package/examples/Portugu/303/252s/Hue Buttons.json" +88 -0
  105. package/examples/Portugu/303/252s/Hue Contact.json" +150 -0
  106. package/examples/Portugu/303/252s/Hue Group.json" +658 -0
  107. package/examples/Portugu/303/252s/Hue Light.json" +893 -0
  108. package/examples/Portugu/303/252s/Hue Magic.json" +148 -0
  109. package/examples/Portugu/303/252s/Hue Motion.json" +150 -0
  110. package/examples/Portugu/303/252s/Hue Rules.json" +150 -0
  111. package/examples/Portugu/303/252s/Hue Scene.json" +178 -0
  112. package/examples/Portugu/303/252s/Hue Speaker.json" +274 -0
  113. package/examples/Portugu/303/252s/Hue Sync Box.json" +378 -0
  114. package/examples/Portugu/303/252s/Hue Temperature.json" +150 -0
  115. package/examples/T/303/274rk/303/247e/Hue Automation.json" +176 -0
  116. package/examples/T/303/274rk/303/247e/Hue Bridge.json" +256 -0
  117. package/examples/T/303/274rk/303/247e/Hue Brightness.json" +150 -0
  118. package/examples/T/303/274rk/303/247e/Hue Buttons.json" +88 -0
  119. package/examples/T/303/274rk/303/247e/Hue Contact.json" +150 -0
  120. package/examples/T/303/274rk/303/247e/Hue Group.json" +658 -0
  121. package/examples/T/303/274rk/303/247e/Hue Light.json" +893 -0
  122. package/examples/T/303/274rk/303/247e/Hue Magic.json" +148 -0
  123. package/examples/T/303/274rk/303/247e/Hue Motion.json" +150 -0
  124. package/examples/T/303/274rk/303/247e/Hue Rules.json" +150 -0
  125. package/examples/T/303/274rk/303/247e/Hue Scene.json" +178 -0
  126. package/examples/T/303/274rk/303/247e/Hue Speaker.json" +274 -0
  127. package/examples/T/303/274rk/303/247e/Hue Sync Box.json" +378 -0
  128. package/examples/T/303/274rk/303/247e/Hue Temperature.json" +150 -0
  129. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Automation.json" +176 -0
  130. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Bridge.json" +256 -0
  131. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Brightness.json" +150 -0
  132. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Buttons.json" +88 -0
  133. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Contact.json" +150 -0
  134. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Group.json" +658 -0
  135. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Light.json" +893 -0
  136. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Magic.json" +148 -0
  137. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Motion.json" +150 -0
  138. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Rules.json" +150 -0
  139. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Scene.json" +178 -0
  140. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Speaker.json" +274 -0
  141. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Sync Box.json" +378 -0
  142. package/examples//316/225/316/273/316/273/316/267/316/275/316/271/316/272/316/254/Hue Temperature.json" +150 -0
  143. package/huemagic/assets/huemagic-examples.gif +0 -0
  144. package/huemagic/hue-automation.html +218 -0
  145. package/huemagic/hue-automation.js +173 -0
  146. package/huemagic/hue-bridge-config.html +39 -32
  147. package/huemagic/hue-bridge-config.js +299 -106
  148. package/huemagic/hue-bridge.html +8 -8
  149. package/huemagic/hue-bridge.js +29 -4
  150. package/huemagic/hue-brightness.html +21 -9
  151. package/huemagic/hue-brightness.js +20 -10
  152. package/huemagic/hue-buttons.html +16 -9
  153. package/huemagic/hue-buttons.js +90 -27
  154. package/huemagic/hue-contact.html +218 -0
  155. package/huemagic/hue-contact.js +209 -0
  156. package/huemagic/hue-group.html +16 -9
  157. package/huemagic/hue-group.js +141 -179
  158. package/huemagic/hue-light.html +19 -12
  159. package/huemagic/hue-light.js +222 -167
  160. package/huemagic/hue-magic.html +3 -7
  161. package/huemagic/hue-motion.html +16 -9
  162. package/huemagic/hue-motion.js +37 -8
  163. package/huemagic/hue-rules.html +16 -9
  164. package/huemagic/hue-rules.js +16 -8
  165. package/huemagic/hue-scene.html +9 -5
  166. package/huemagic/hue-scene.js +52 -16
  167. package/huemagic/hue-speaker.html +218 -0
  168. package/huemagic/hue-speaker.js +257 -0
  169. package/huemagic/hue-syncbox-config.html +123 -0
  170. package/huemagic/hue-syncbox-config.js +179 -0
  171. package/huemagic/hue-syncbox.html +82 -0
  172. package/huemagic/hue-syncbox.js +274 -0
  173. package/huemagic/hue-temperature.html +21 -9
  174. package/huemagic/hue-temperature.js +18 -10
  175. package/huemagic/icons/hue-automation.png +0 -0
  176. package/huemagic/icons/hue-automation.svg +3 -0
  177. package/huemagic/icons/hue-contact.png +0 -0
  178. package/huemagic/icons/hue-contact.svg +4 -0
  179. package/huemagic/icons/hue-speaker.png +0 -0
  180. package/huemagic/icons/hue-speaker.svg +6 -0
  181. package/huemagic/icons/hue-syncbox.png +0 -0
  182. package/huemagic/icons/hue-syncbox.svg +7 -0
  183. package/huemagic/locales/de/hue-automation.html +59 -0
  184. package/huemagic/locales/de/hue-automation.json +33 -0
  185. package/huemagic/locales/de/hue-bridge-config.json +23 -12
  186. package/huemagic/locales/de/hue-bridge.html +7 -4
  187. package/huemagic/locales/de/hue-bridge.json +9 -10
  188. package/huemagic/locales/de/hue-brightness.html +9 -6
  189. package/huemagic/locales/de/hue-brightness.json +13 -9
  190. package/huemagic/locales/de/hue-buttons.html +5 -4
  191. package/huemagic/locales/de/hue-buttons.json +22 -8
  192. package/huemagic/locales/de/hue-contact.html +67 -0
  193. package/huemagic/locales/de/hue-contact.json +35 -0
  194. package/huemagic/locales/de/hue-group.html +17 -6
  195. package/huemagic/locales/de/hue-group.json +16 -9
  196. package/huemagic/locales/de/hue-light.html +6 -6
  197. package/huemagic/locales/de/hue-light.json +17 -9
  198. package/huemagic/locales/de/hue-magic.html +1 -1
  199. package/huemagic/locales/de/hue-magic.json +6 -8
  200. package/huemagic/locales/de/hue-motion.html +4 -4
  201. package/huemagic/locales/de/hue-motion.json +14 -9
  202. package/huemagic/locales/de/hue-rules.html +2 -2
  203. package/huemagic/locales/de/hue-rules.json +10 -9
  204. package/huemagic/locales/de/hue-scene.html +11 -3
  205. package/huemagic/locales/de/hue-scene.json +9 -9
  206. package/huemagic/locales/de/hue-speaker.html +80 -0
  207. package/huemagic/locales/de/hue-speaker.json +38 -0
  208. package/huemagic/locales/de/hue-syncbox-config.html +19 -0
  209. package/huemagic/locales/de/hue-syncbox-config.json +25 -0
  210. package/huemagic/locales/de/hue-syncbox.html +86 -0
  211. package/huemagic/locales/de/hue-syncbox.json +30 -0
  212. package/huemagic/locales/de/hue-temperature.html +7 -5
  213. package/huemagic/locales/de/hue-temperature.json +13 -9
  214. package/huemagic/locales/el/hue-automation.html +59 -0
  215. package/huemagic/locales/el/hue-automation.json +33 -0
  216. package/huemagic/locales/el/hue-bridge-config.html +31 -0
  217. package/huemagic/locales/el/hue-bridge-config.json +40 -0
  218. package/huemagic/locales/el/hue-bridge.html +96 -0
  219. package/huemagic/locales/el/hue-bridge.json +26 -0
  220. package/huemagic/locales/el/hue-brightness.html +70 -0
  221. package/huemagic/locales/el/hue-brightness.json +37 -0
  222. package/huemagic/locales/el/hue-buttons.html +59 -0
  223. package/huemagic/locales/el/hue-buttons.json +42 -0
  224. package/huemagic/locales/el/hue-contact.html +67 -0
  225. package/huemagic/locales/el/hue-contact.json +35 -0
  226. package/huemagic/locales/el/hue-group.html +87 -0
  227. package/huemagic/locales/el/hue-group.json +46 -0
  228. package/huemagic/locales/el/hue-light.html +97 -0
  229. package/huemagic/locales/el/hue-light.json +44 -0
  230. package/huemagic/locales/el/hue-magic.html +68 -0
  231. package/huemagic/locales/el/hue-magic.json +26 -0
  232. package/huemagic/locales/el/hue-motion.html +64 -0
  233. package/huemagic/locales/el/hue-motion.json +36 -0
  234. package/huemagic/locales/el/hue-rules.html +69 -0
  235. package/huemagic/locales/el/hue-rules.json +30 -0
  236. package/huemagic/locales/el/hue-scene.html +43 -0
  237. package/huemagic/locales/el/hue-scene.json +32 -0
  238. package/huemagic/locales/el/hue-speaker.html +80 -0
  239. package/huemagic/locales/el/hue-speaker.json +38 -0
  240. package/huemagic/locales/el/hue-syncbox-config.html +19 -0
  241. package/huemagic/locales/el/hue-syncbox-config.json +25 -0
  242. package/huemagic/locales/el/hue-syncbox.html +86 -0
  243. package/huemagic/locales/el/hue-syncbox.json +30 -0
  244. package/huemagic/locales/el/hue-temperature.html +69 -0
  245. package/huemagic/locales/el/hue-temperature.json +33 -0
  246. package/huemagic/locales/en/hue-automation.html +59 -0
  247. package/huemagic/locales/en/hue-automation.json +33 -0
  248. package/huemagic/locales/en/hue-bridge-config.json +40 -0
  249. package/huemagic/locales/{en-US → en}/hue-bridge.html +5 -2
  250. package/huemagic/locales/{en-US → en}/hue-bridge.json +8 -9
  251. package/huemagic/locales/{en-US → en}/hue-brightness.html +6 -3
  252. package/huemagic/locales/{en-US → en}/hue-brightness.json +11 -9
  253. package/huemagic/locales/{en-US → en}/hue-buttons.html +4 -3
  254. package/huemagic/locales/en/hue-buttons.json +42 -0
  255. package/huemagic/locales/en/hue-contact.html +67 -0
  256. package/huemagic/locales/en/hue-contact.json +35 -0
  257. package/huemagic/locales/{en-US → en}/hue-group.html +16 -5
  258. package/huemagic/locales/{en-US → en}/hue-group.json +15 -10
  259. package/huemagic/locales/{en-US → en}/hue-light.html +3 -3
  260. package/huemagic/locales/en/hue-light.json +44 -0
  261. package/huemagic/locales/{en-US → en}/hue-magic.html +1 -1
  262. package/huemagic/locales/{en-US → en}/hue-motion.html +2 -2
  263. package/huemagic/locales/{en-US → en}/hue-motion.json +12 -9
  264. package/huemagic/locales/{en-US → en}/hue-rules.html +2 -2
  265. package/huemagic/locales/{en-US → en}/hue-rules.json +10 -9
  266. package/huemagic/locales/{en-US → en}/hue-scene.html +11 -3
  267. package/huemagic/locales/{en-US → en}/hue-scene.json +9 -9
  268. package/huemagic/locales/en/hue-speaker.html +80 -0
  269. package/huemagic/locales/en/hue-speaker.json +38 -0
  270. package/huemagic/locales/en/hue-syncbox-config.html +19 -0
  271. package/huemagic/locales/en/hue-syncbox-config.json +25 -0
  272. package/huemagic/locales/en/hue-syncbox.html +86 -0
  273. package/huemagic/locales/en/hue-syncbox.json +30 -0
  274. package/huemagic/locales/{en-US → en}/hue-temperature.html +4 -2
  275. package/huemagic/locales/{en-US → en}/hue-temperature.json +11 -9
  276. package/huemagic/locales/es/hue-automation.html +59 -0
  277. package/huemagic/locales/es/hue-automation.json +33 -0
  278. package/huemagic/locales/es/hue-bridge-config.html +31 -0
  279. package/huemagic/locales/es/hue-bridge-config.json +40 -0
  280. package/huemagic/locales/es/hue-bridge.html +96 -0
  281. package/huemagic/locales/es/hue-bridge.json +26 -0
  282. package/huemagic/locales/es/hue-brightness.html +70 -0
  283. package/huemagic/locales/es/hue-brightness.json +37 -0
  284. package/huemagic/locales/es/hue-buttons.html +59 -0
  285. package/huemagic/locales/es/hue-buttons.json +42 -0
  286. package/huemagic/locales/es/hue-contact.html +67 -0
  287. package/huemagic/locales/es/hue-contact.json +35 -0
  288. package/huemagic/locales/es/hue-group.html +87 -0
  289. package/huemagic/locales/es/hue-group.json +46 -0
  290. package/huemagic/locales/es/hue-light.html +97 -0
  291. package/huemagic/locales/es/hue-light.json +44 -0
  292. package/huemagic/locales/es/hue-magic.html +68 -0
  293. package/huemagic/locales/es/hue-magic.json +26 -0
  294. package/huemagic/locales/es/hue-motion.html +64 -0
  295. package/huemagic/locales/es/hue-motion.json +36 -0
  296. package/huemagic/locales/es/hue-rules.html +69 -0
  297. package/huemagic/locales/es/hue-rules.json +30 -0
  298. package/huemagic/locales/es/hue-scene.html +43 -0
  299. package/huemagic/locales/es/hue-scene.json +32 -0
  300. package/huemagic/locales/es/hue-speaker.html +80 -0
  301. package/huemagic/locales/es/hue-speaker.json +38 -0
  302. package/huemagic/locales/es/hue-syncbox-config.html +19 -0
  303. package/huemagic/locales/es/hue-syncbox-config.json +25 -0
  304. package/huemagic/locales/es/hue-syncbox.html +86 -0
  305. package/huemagic/locales/es/hue-syncbox.json +30 -0
  306. package/huemagic/locales/es/hue-temperature.html +69 -0
  307. package/huemagic/locales/es/hue-temperature.json +33 -0
  308. package/huemagic/locales/fr/hue-automation.html +59 -0
  309. package/huemagic/locales/fr/hue-automation.json +33 -0
  310. package/huemagic/locales/fr/hue-bridge-config.html +31 -0
  311. package/huemagic/locales/fr/hue-bridge-config.json +40 -0
  312. package/huemagic/locales/fr/hue-bridge.html +96 -0
  313. package/huemagic/locales/fr/hue-bridge.json +26 -0
  314. package/huemagic/locales/fr/hue-brightness.html +70 -0
  315. package/huemagic/locales/fr/hue-brightness.json +37 -0
  316. package/huemagic/locales/fr/hue-buttons.html +59 -0
  317. package/huemagic/locales/fr/hue-buttons.json +42 -0
  318. package/huemagic/locales/fr/hue-contact.html +67 -0
  319. package/huemagic/locales/fr/hue-contact.json +35 -0
  320. package/huemagic/locales/fr/hue-group.html +87 -0
  321. package/huemagic/locales/fr/hue-group.json +46 -0
  322. package/huemagic/locales/fr/hue-light.html +97 -0
  323. package/huemagic/locales/fr/hue-light.json +44 -0
  324. package/huemagic/locales/fr/hue-magic.html +68 -0
  325. package/huemagic/locales/fr/hue-magic.json +26 -0
  326. package/huemagic/locales/fr/hue-motion.html +64 -0
  327. package/huemagic/locales/fr/hue-motion.json +36 -0
  328. package/huemagic/locales/fr/hue-rules.html +69 -0
  329. package/huemagic/locales/fr/hue-rules.json +30 -0
  330. package/huemagic/locales/fr/hue-scene.html +43 -0
  331. package/huemagic/locales/fr/hue-scene.json +32 -0
  332. package/huemagic/locales/fr/hue-speaker.html +80 -0
  333. package/huemagic/locales/fr/hue-speaker.json +38 -0
  334. package/huemagic/locales/fr/hue-syncbox-config.html +19 -0
  335. package/huemagic/locales/fr/hue-syncbox-config.json +25 -0
  336. package/huemagic/locales/fr/hue-syncbox.html +86 -0
  337. package/huemagic/locales/fr/hue-syncbox.json +30 -0
  338. package/huemagic/locales/fr/hue-temperature.html +69 -0
  339. package/huemagic/locales/fr/hue-temperature.json +33 -0
  340. package/huemagic/locales/it/hue-automation.html +59 -0
  341. package/huemagic/locales/it/hue-automation.json +33 -0
  342. package/huemagic/locales/it/hue-bridge-config.html +31 -0
  343. package/huemagic/locales/it/hue-bridge-config.json +40 -0
  344. package/huemagic/locales/it/hue-bridge.html +96 -0
  345. package/huemagic/locales/it/hue-bridge.json +26 -0
  346. package/huemagic/locales/it/hue-brightness.html +70 -0
  347. package/huemagic/locales/it/hue-brightness.json +37 -0
  348. package/huemagic/locales/it/hue-buttons.html +59 -0
  349. package/huemagic/locales/it/hue-buttons.json +42 -0
  350. package/huemagic/locales/it/hue-contact.html +67 -0
  351. package/huemagic/locales/it/hue-contact.json +35 -0
  352. package/huemagic/locales/it/hue-group.html +87 -0
  353. package/huemagic/locales/it/hue-group.json +46 -0
  354. package/huemagic/locales/it/hue-light.html +97 -0
  355. package/huemagic/locales/it/hue-light.json +44 -0
  356. package/huemagic/locales/it/hue-magic.html +68 -0
  357. package/huemagic/locales/it/hue-magic.json +26 -0
  358. package/huemagic/locales/it/hue-motion.html +64 -0
  359. package/huemagic/locales/it/hue-motion.json +36 -0
  360. package/huemagic/locales/it/hue-rules.html +69 -0
  361. package/huemagic/locales/it/hue-rules.json +30 -0
  362. package/huemagic/locales/it/hue-scene.html +43 -0
  363. package/huemagic/locales/it/hue-scene.json +32 -0
  364. package/huemagic/locales/it/hue-speaker.html +80 -0
  365. package/huemagic/locales/it/hue-speaker.json +38 -0
  366. package/huemagic/locales/it/hue-syncbox-config.html +19 -0
  367. package/huemagic/locales/it/hue-syncbox-config.json +25 -0
  368. package/huemagic/locales/it/hue-syncbox.html +86 -0
  369. package/huemagic/locales/it/hue-syncbox.json +30 -0
  370. package/huemagic/locales/it/hue-temperature.html +69 -0
  371. package/huemagic/locales/it/hue-temperature.json +33 -0
  372. package/huemagic/locales/nl/hue-automation.html +59 -0
  373. package/huemagic/locales/nl/hue-automation.json +33 -0
  374. package/huemagic/locales/nl/hue-bridge-config.html +31 -0
  375. package/huemagic/locales/nl/hue-bridge-config.json +40 -0
  376. package/huemagic/locales/nl/hue-bridge.html +96 -0
  377. package/huemagic/locales/nl/hue-bridge.json +26 -0
  378. package/huemagic/locales/nl/hue-brightness.html +70 -0
  379. package/huemagic/locales/nl/hue-brightness.json +37 -0
  380. package/huemagic/locales/nl/hue-buttons.html +59 -0
  381. package/huemagic/locales/nl/hue-buttons.json +42 -0
  382. package/huemagic/locales/nl/hue-contact.html +67 -0
  383. package/huemagic/locales/nl/hue-contact.json +35 -0
  384. package/huemagic/locales/nl/hue-group.html +87 -0
  385. package/huemagic/locales/nl/hue-group.json +46 -0
  386. package/huemagic/locales/nl/hue-light.html +97 -0
  387. package/huemagic/locales/nl/hue-light.json +44 -0
  388. package/huemagic/locales/nl/hue-magic.html +68 -0
  389. package/huemagic/locales/nl/hue-magic.json +26 -0
  390. package/huemagic/locales/nl/hue-motion.html +64 -0
  391. package/huemagic/locales/nl/hue-motion.json +36 -0
  392. package/huemagic/locales/nl/hue-rules.html +69 -0
  393. package/huemagic/locales/nl/hue-rules.json +30 -0
  394. package/huemagic/locales/nl/hue-scene.html +43 -0
  395. package/huemagic/locales/nl/hue-scene.json +32 -0
  396. package/huemagic/locales/nl/hue-speaker.html +80 -0
  397. package/huemagic/locales/nl/hue-speaker.json +38 -0
  398. package/huemagic/locales/nl/hue-syncbox-config.html +19 -0
  399. package/huemagic/locales/nl/hue-syncbox-config.json +25 -0
  400. package/huemagic/locales/nl/hue-syncbox.html +86 -0
  401. package/huemagic/locales/nl/hue-syncbox.json +30 -0
  402. package/huemagic/locales/nl/hue-temperature.html +69 -0
  403. package/huemagic/locales/nl/hue-temperature.json +33 -0
  404. package/huemagic/locales/pl/hue-automation.html +59 -0
  405. package/huemagic/locales/pl/hue-automation.json +33 -0
  406. package/huemagic/locales/pl/hue-bridge-config.html +31 -0
  407. package/huemagic/locales/pl/hue-bridge-config.json +40 -0
  408. package/huemagic/locales/pl/hue-bridge.html +96 -0
  409. package/huemagic/locales/pl/hue-bridge.json +26 -0
  410. package/huemagic/locales/pl/hue-brightness.html +70 -0
  411. package/huemagic/locales/pl/hue-brightness.json +37 -0
  412. package/huemagic/locales/pl/hue-buttons.html +59 -0
  413. package/huemagic/locales/pl/hue-buttons.json +42 -0
  414. package/huemagic/locales/pl/hue-contact.html +67 -0
  415. package/huemagic/locales/pl/hue-contact.json +35 -0
  416. package/huemagic/locales/pl/hue-group.html +87 -0
  417. package/huemagic/locales/pl/hue-group.json +46 -0
  418. package/huemagic/locales/pl/hue-light.html +97 -0
  419. package/huemagic/locales/pl/hue-light.json +44 -0
  420. package/huemagic/locales/pl/hue-magic.html +68 -0
  421. package/huemagic/locales/pl/hue-magic.json +26 -0
  422. package/huemagic/locales/pl/hue-motion.html +64 -0
  423. package/huemagic/locales/pl/hue-motion.json +36 -0
  424. package/huemagic/locales/pl/hue-rules.html +69 -0
  425. package/huemagic/locales/pl/hue-rules.json +30 -0
  426. package/huemagic/locales/pl/hue-scene.html +43 -0
  427. package/huemagic/locales/pl/hue-scene.json +32 -0
  428. package/huemagic/locales/pl/hue-speaker.html +80 -0
  429. package/huemagic/locales/pl/hue-speaker.json +38 -0
  430. package/huemagic/locales/pl/hue-syncbox-config.html +19 -0
  431. package/huemagic/locales/pl/hue-syncbox-config.json +25 -0
  432. package/huemagic/locales/pl/hue-syncbox.html +86 -0
  433. package/huemagic/locales/pl/hue-syncbox.json +30 -0
  434. package/huemagic/locales/pl/hue-temperature.html +69 -0
  435. package/huemagic/locales/pl/hue-temperature.json +33 -0
  436. package/huemagic/locales/pt/hue-automation.html +59 -0
  437. package/huemagic/locales/pt/hue-automation.json +33 -0
  438. package/huemagic/locales/pt/hue-bridge-config.html +31 -0
  439. package/huemagic/locales/pt/hue-bridge-config.json +40 -0
  440. package/huemagic/locales/pt/hue-bridge.html +96 -0
  441. package/huemagic/locales/pt/hue-bridge.json +26 -0
  442. package/huemagic/locales/pt/hue-brightness.html +70 -0
  443. package/huemagic/locales/pt/hue-brightness.json +37 -0
  444. package/huemagic/locales/pt/hue-buttons.html +59 -0
  445. package/huemagic/locales/pt/hue-buttons.json +42 -0
  446. package/huemagic/locales/pt/hue-contact.html +67 -0
  447. package/huemagic/locales/pt/hue-contact.json +35 -0
  448. package/huemagic/locales/pt/hue-group.html +87 -0
  449. package/huemagic/locales/pt/hue-group.json +46 -0
  450. package/huemagic/locales/pt/hue-light.html +97 -0
  451. package/huemagic/locales/pt/hue-light.json +44 -0
  452. package/huemagic/locales/pt/hue-magic.html +68 -0
  453. package/huemagic/locales/pt/hue-magic.json +26 -0
  454. package/huemagic/locales/pt/hue-motion.html +64 -0
  455. package/huemagic/locales/pt/hue-motion.json +36 -0
  456. package/huemagic/locales/pt/hue-rules.html +69 -0
  457. package/huemagic/locales/pt/hue-rules.json +30 -0
  458. package/huemagic/locales/pt/hue-scene.html +43 -0
  459. package/huemagic/locales/pt/hue-scene.json +32 -0
  460. package/huemagic/locales/pt/hue-speaker.html +80 -0
  461. package/huemagic/locales/pt/hue-speaker.json +38 -0
  462. package/huemagic/locales/pt/hue-syncbox-config.html +19 -0
  463. package/huemagic/locales/pt/hue-syncbox-config.json +25 -0
  464. package/huemagic/locales/pt/hue-syncbox.html +86 -0
  465. package/huemagic/locales/pt/hue-syncbox.json +30 -0
  466. package/huemagic/locales/pt/hue-temperature.html +69 -0
  467. package/huemagic/locales/pt/hue-temperature.json +33 -0
  468. package/huemagic/locales/tr/hue-automation.html +59 -0
  469. package/huemagic/locales/tr/hue-automation.json +33 -0
  470. package/huemagic/locales/tr/hue-bridge-config.html +31 -0
  471. package/huemagic/locales/tr/hue-bridge-config.json +40 -0
  472. package/huemagic/locales/tr/hue-bridge.html +96 -0
  473. package/huemagic/locales/tr/hue-bridge.json +26 -0
  474. package/huemagic/locales/tr/hue-brightness.html +70 -0
  475. package/huemagic/locales/tr/hue-brightness.json +37 -0
  476. package/huemagic/locales/tr/hue-buttons.html +59 -0
  477. package/huemagic/locales/tr/hue-buttons.json +42 -0
  478. package/huemagic/locales/tr/hue-contact.html +67 -0
  479. package/huemagic/locales/tr/hue-contact.json +35 -0
  480. package/huemagic/locales/tr/hue-group.html +87 -0
  481. package/huemagic/locales/tr/hue-group.json +46 -0
  482. package/huemagic/locales/tr/hue-light.html +97 -0
  483. package/huemagic/locales/tr/hue-light.json +44 -0
  484. package/huemagic/locales/tr/hue-magic.html +68 -0
  485. package/huemagic/locales/tr/hue-magic.json +26 -0
  486. package/huemagic/locales/tr/hue-motion.html +64 -0
  487. package/huemagic/locales/tr/hue-motion.json +36 -0
  488. package/huemagic/locales/tr/hue-rules.html +69 -0
  489. package/huemagic/locales/tr/hue-rules.json +30 -0
  490. package/huemagic/locales/tr/hue-scene.html +43 -0
  491. package/huemagic/locales/tr/hue-scene.json +32 -0
  492. package/huemagic/locales/tr/hue-speaker.html +80 -0
  493. package/huemagic/locales/tr/hue-speaker.json +38 -0
  494. package/huemagic/locales/tr/hue-syncbox-config.html +19 -0
  495. package/huemagic/locales/tr/hue-syncbox-config.json +25 -0
  496. package/huemagic/locales/tr/hue-syncbox.html +86 -0
  497. package/huemagic/locales/tr/hue-syncbox.json +30 -0
  498. package/huemagic/locales/tr/hue-temperature.html +69 -0
  499. package/huemagic/locales/tr/hue-temperature.json +33 -0
  500. package/huemagic/utils/api.js +162 -57
  501. package/huemagic/utils/color.js +20 -2
  502. package/huemagic/utils/merge.js +3 -2
  503. package/huemagic/utils/messages.js +506 -120
  504. package/huemagic/utils/sse.js +51 -0
  505. package/huemagic/utils/syncbox.js +84 -0
  506. package/package.json +19 -12
  507. package/.github/FUNDING.yml +0 -2
  508. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  509. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
  510. package/.github/workflows/npm.yml +0 -18
  511. package/huemagic/locales/en-US/hue-bridge-config.json +0 -29
  512. package/huemagic/locales/en-US/hue-buttons.json +0 -34
  513. package/huemagic/locales/en-US/hue-light.json +0 -38
  514. /package/huemagic/locales/{en-US → en}/hue-bridge-config.html +0 -0
  515. /package/huemagic/locales/{en-US → en}/hue-magic.json +0 -0
@@ -16,10 +16,14 @@ module.exports = function(RED)
16
16
  HueLightMessage,
17
17
  HueGroupMessage,
18
18
  HueMotionMessage,
19
+ HueContactMessage,
19
20
  HueTemperatureMessage,
20
21
  HueBrightnessMessage,
21
22
  HueButtonsMessage,
22
- HueRulesMessage
23
+ HueRulesMessage,
24
+ HueSpeakerMessage,
25
+ HueAutomationMessage,
26
+ servesType
23
27
  } = require('./utils/messages');
24
28
 
25
29
  function HueBridge(config)
@@ -33,11 +37,15 @@ module.exports = function(RED)
33
37
  this.resourcesInGroups = {};
34
38
  this.lastStates = {};
35
39
  this.events = new events.EventEmitter();
40
+ this.events.setMaxListeners(0);
36
41
  this.patchQueue = null;
37
- this.timerWatchDog = null; // 31/01/2022 Supergiovane: watchdog for the connection with the bridge
42
+ this.timerWatchDog = null;
43
+ this.watchdogFailures = 0;
44
+ this.starting = false;
45
+ this.refetchTimeout = null;
38
46
 
39
- // RESOURCE ID PATTERN
40
- this.validResourceID = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi;
47
+ // RESOURCE ID PATTERN (NEVER GLOBAL, "test" WOULD BECOME STATEFUL)
48
+ this.validResourceID = /^[a-zA-Z0-9-]+$/i;
41
49
 
42
50
  // FIRMWARE UPDATE TIMEOUT
43
51
  this.firmwareUpdateTimeout = null;
@@ -45,44 +53,55 @@ module.exports = function(RED)
45
53
  // CREATE NODE
46
54
  RED.nodes.createNode(scope, config);
47
55
 
48
- this.startWatchdog = function() {
49
- // 31/01/2022 Supergiovane: Check wether the bridge is connected.
50
- if (this.timerWatchDog !== null) clearTimeout(this.timerWatchDog);
51
- this.timerWatchDog = setTimeout(() => {
52
- try {
53
- API.request({ config: config, resource: "/config", version: 1 })
54
- .then(function(bridgeInformation)
55
- {
56
- // The bridge is still connected
57
- scope.startWatchdog();
58
- })
59
- .catch(function(error)
60
- {
61
- // Error connecting to the bridge
62
- scope.log("Error requesting info from the bridge. Reconnect in some secs. " + error.message);
63
- scope.start();
64
- });
65
- } catch (error) {
66
- scope.log("Lost connection with the bridge. Reconnect in some secs. " + error.message);
67
- scope.start();
68
- }
69
-
70
- }, 10000);
56
+ // PERIODICALLY CHECK WETHER BRIDGE IS CONNECTED
57
+ this.startWatchdog = function()
58
+ {
59
+ if(scope.timerWatchDog !== null) { clearTimeout(scope.timerWatchDog); }
60
+ if(scope.nodeActive === false) { return false; }
61
+
62
+ // THE BRIDGE SENDS NO KEEP-ALIVE ON THE EVENT STREAM, SO KEEP ASKING - JUST RARELY
63
+ scope.timerWatchDog = setTimeout(function()
64
+ {
65
+ API.request({ config: config, resource: "bridge" })
66
+ .then(function(bridgeInformation)
67
+ {
68
+ scope.watchdogFailures = 0;
69
+ scope.startWatchdog();
70
+ })
71
+ .catch(function(error)
72
+ {
73
+ // BRIDGE IS OVERLOADED (429) OR BUSY (503) BUT STILL ALIVE
74
+ if(error.status === 429 || error.status === 503)
75
+ {
76
+ return scope.startWatchdog();
77
+ }
78
+
79
+ scope.watchdogFailures += 1;
80
+ scope.log(RED._("hue-bridge-config.node.request-error", { error: JSON.stringify(error.errors ? error.errors : error) }));
81
+
82
+ // ONLY RECONNECT AFTER THREE FAILED ATTEMPTS IN A ROW
83
+ if(scope.watchdogFailures >= 3) { scope.start(); }
84
+ else { scope.startWatchdog(); }
85
+ });
86
+ }, API.connected(config) ? 60000 : 15000);
71
87
  }
88
+
72
89
  // INITIALIZE
73
90
  this.start = function()
74
91
  {
75
- this.startWatchdog(); // 31/01/2022 Supergiovane: starts the watchdog.
92
+ if(scope.starting === true || scope.nodeActive === false) { return false; }
93
+ scope.starting = true;
94
+ scope.watchdogFailures = 0;
76
95
 
77
- scope.log("Initializing the bridge ("+config.bridge+")…");
96
+ scope.log(RED._("hue-bridge-config.node.initializing", { bridge: config.bridge }));
78
97
  API.init({ config: config })
79
98
  .then(function(bridge) {
80
- scope.log("Connected to bridge");
99
+ scope.log(RED._("hue-bridge-config.node.connected"));
81
100
  return scope.getAllResources();
82
101
  })
83
102
  .then(function(allResources)
84
103
  {
85
- scope.log("Processing bridge resources…");
104
+ scope.log(RED._("hue-bridge-config.node.processing"));
86
105
  return API.processResources(allResources);
87
106
  })
88
107
  .then(function(allResources)
@@ -91,21 +110,27 @@ module.exports = function(RED)
91
110
  scope.resources = allResources;
92
111
 
93
112
  // EMIT INITIAL STATES -> NODES
94
- scope.log("Initial emit of resource states…");
113
+ scope.log(RED._("hue-bridge-config.node.initial-emit"));
95
114
  return scope.emitInitialStates();
96
115
  })
97
116
  .then(function(emitted)
98
117
  {
118
+ scope.starting = false;
119
+
99
120
  // START REFRESHING STATES
100
121
  scope.keepUpdated();
101
122
 
102
123
  // START LOOKING FOR FIRMWARE-UPDATES
103
124
  scope.autoUpdateFirmware();
125
+
126
+ // START WATCHDOG
127
+ scope.startWatchdog();
104
128
  return true;
105
129
  })
106
130
  .catch(function(error)
107
131
  {
108
132
  // RETRY AFTER 30 SECONDS
133
+ scope.starting = false;
109
134
  scope.log(error);
110
135
  if(scope.nodeActive == true) { setTimeout(function(){ scope.start(); }, 30000); }
111
136
  });
@@ -147,8 +172,13 @@ module.exports = function(RED)
147
172
  {
148
173
  var allResources = [];
149
174
 
150
- // GET BRIDGE INFORMATION
175
+ // GET BRIDGE INFORMATION (LEGACY API / MAY BE UNAVAILABLE)
151
176
  scope.getBridgeInformation()
177
+ .catch(function(error)
178
+ {
179
+ scope.log(RED._("hue-bridge-config.node.no-legacy-api"));
180
+ return { id: "bridge", id_v1: "/config", updated: dayjs().format() };
181
+ })
152
182
  .then(function(bridgeInformation)
153
183
  {
154
184
  // PUSH TO RESOURCES
@@ -162,13 +192,16 @@ module.exports = function(RED)
162
192
  // MERGE RESOURCES
163
193
  allResources = allResources.concat(v2Resources);
164
194
 
165
- // GET RULES (LEGACY API)
166
- return API.request({ config: config, resource: "/rules", version: 1 });
195
+ // GET RULES (LEGACY API / MAY BE UNAVAILABLE)
196
+ return API.request({ config: config, resource: "/rules", version: 1 }).catch(function(error) { return {}; });
167
197
  })
168
198
  .then(function(rules)
169
199
  {
170
200
  for (var [id, rule] of Object.entries(rules))
171
201
  {
202
+ // SKIP ERROR RESPONSES OF THE LEGACY API
203
+ if(!rule || typeof rule !== 'object' || rule["error"]) { continue; }
204
+
172
205
  // "RENAME" OWNER
173
206
  rule["_owner"] = rule["owner"];
174
207
  delete rule["owner"];
@@ -201,6 +234,7 @@ module.exports = function(RED)
201
234
  // PUSH ALL STATES
202
235
  for (const [id, resource] of Object.entries(scope.resources))
203
236
  {
237
+ if(id === "_groupsOf") { continue; }
204
238
  scope.pushUpdatedState(resource, resource.type, true);
205
239
  }
206
240
 
@@ -214,7 +248,7 @@ module.exports = function(RED)
214
248
  {
215
249
  if(!config.disableupdates)
216
250
  {
217
- scope.log("Keeping nodes up-to-date…");
251
+ scope.log(RED._("hue-bridge-config.node.keep-updated"));
218
252
 
219
253
  // REFRESH STATES (SSE)
220
254
  this.refreshStatesSSE();
@@ -224,11 +258,17 @@ module.exports = function(RED)
224
258
  // GET UPDATED STATES (SSE)
225
259
  this.refreshStatesSSE = function()
226
260
  {
227
- scope.log("Subscribing to bridge events…");
228
- API.subscribe(config, function(updates)
261
+ scope.log(RED._("hue-bridge-config.node.subscribing"));
262
+ API.subscribe(config, function(updates, eventType)
229
263
  {
230
264
  const currentDateTime = dayjs().format();
231
265
 
266
+ // DEVICE ADDED/REMOVED OR EVENTS MISSED? -> RE-READ ALL RESOURCES
267
+ if(eventType === "add" || eventType === "delete" || eventType === "reconnect")
268
+ {
269
+ return scope.refetchResources();
270
+ }
271
+
232
272
  for(let resource of updates)
233
273
  {
234
274
  let id = resource.id;
@@ -240,18 +280,19 @@ module.exports = function(RED)
240
280
  if(resource["owner"])
241
281
  {
242
282
  let targetId = resource["owner"]["rid"];
283
+ const services = scope.resources[targetId] ? scope.resources[targetId]["services"] : false;
243
284
 
244
- if(scope.resources[targetId])
285
+ if(services && services[type])
245
286
  {
246
287
  // GET PREVIOUS STATE
247
- previousState = scope.resources[targetId]["services"][type][id];
288
+ previousState = services[type][id];
248
289
 
249
- // IS BUTTON? -> REMOVE PREVIOUS STATES
250
- if(type === "button")
290
+ // IS BUTTON OR DIAL? -> REMOVE PREVIOUS STATES
291
+ if(type === "button" || type === "relative_rotary")
251
292
  {
252
- for (const [oneButtonID, oneButton] of Object.entries(scope.resources[targetId]["services"]["button"]))
293
+ for (const [oneServiceID, oneService] of Object.entries(services[type]))
253
294
  {
254
- delete scope.resources[targetId]["services"]["button"][oneButtonID]["button"];
295
+ delete services[type][oneServiceID][type];
255
296
  }
256
297
  }
257
298
  }
@@ -262,8 +303,8 @@ module.exports = function(RED)
262
303
  previousState = scope.resources[id];
263
304
  }
264
305
 
265
- // NO PREVIOUS STATE?
266
- if(!previousState) { return false; }
306
+ // NO PREVIOUS STATE? -> UNKNOWN RESOURCE, CONTINUE WITH THE NEXT ONE
307
+ if(!previousState) { continue; }
267
308
 
268
309
  // CHECK DIFFERENCES
269
310
  const mergedState = merge.deep(previousState, resource);
@@ -291,23 +332,59 @@ module.exports = function(RED)
291
332
  }
292
333
  }
293
334
  }
335
+ },
336
+ function(reason, seconds)
337
+ {
338
+ scope.log(RED._("hue-bridge-config.node.connection-lost", { reason: reason, seconds: seconds }));
294
339
  });
295
340
  }
296
341
 
342
+ // RE-READ ALL RESOURCES (DEVICE ADDED / REMOVED ON THE BRIDGE)
343
+ this.refetchResources = function()
344
+ {
345
+ if(scope.refetchTimeout !== null) { clearTimeout(scope.refetchTimeout); }
346
+ scope.refetchTimeout = setTimeout(function()
347
+ {
348
+ scope.refetchTimeout = null;
349
+ scope.log(RED._("hue-bridge-config.node.resources-changed"));
350
+
351
+ scope.getAllResources()
352
+ .then(function(allResources)
353
+ {
354
+ return API.processResources(allResources);
355
+ })
356
+ .then(function(allResources)
357
+ {
358
+ scope.resources = allResources;
359
+ return scope.emitInitialStates();
360
+ })
361
+ .catch(function(error) { scope.log(error); });
362
+ }, 5000);
363
+ }
364
+
297
365
  // PUSH UPDATED STATE
298
366
  this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
299
367
  {
300
- const msg = { id: resource.id, type: resource.type, updatedType: updatedType, services: resource["services"] ? Object.keys(resource["services"]) : [], suppressMessage: suppressMessage };
368
+ if(!resource || !resource.id) { return false; }
369
+
370
+ let services = resource["services"] ? Object.keys(resource["services"]) : [];
371
+
372
+ // ROOMS, ZONES AND THE BRIDGE HOME ARE ADDRESSED AS GROUPS
373
+ if(resource["type"] === "room" || resource["type"] === "zone" || resource["type"] === "bridge_home") { services.push("group"); }
374
+
375
+ const msg = { id: resource.id, type: resource.type, updatedType: updatedType, services: services, suppressMessage: suppressMessage };
301
376
  this.events.emit(config.id + "_" + resource.id, msg);
302
377
  this.events.emit(config.id + "_" + "globalResourceUpdates", msg);
303
378
 
304
379
  // RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
305
- if(this.resources["_groupsOf"][resource.id])
380
+ const groupsOfResource = this.resources["_groupsOf"] ? this.resources["_groupsOf"][resource.id] : false;
381
+
382
+ if(groupsOfResource)
306
383
  {
307
- for (var g = this.resources["_groupsOf"][resource.id].length - 1; g >= 0; g--)
384
+ for (var g = groupsOfResource.length - 1; g >= 0; g--)
308
385
  {
309
- const groupID = this.resources["_groupsOf"][resource.id][g];
310
- const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: [], suppressMessage: suppressMessage };
386
+ const groupID = groupsOfResource[g];
387
+ const groupMessage = { id: groupID, type: "group", updatedType: updatedType, services: ["group"], suppressMessage: suppressMessage };
311
388
 
312
389
  this.events.emit(config.id + "_" + groupID, groupMessage);
313
390
  this.events.emit(config.id + "_" + "globalResourceUpdates", groupMessage);
@@ -326,7 +403,7 @@ module.exports = function(RED)
326
403
  {
327
404
  // RESOLVE LINKS
328
405
  const targetResource = scope.resources[id];
329
- const lastState = scope.lastStates[type+targetResource.id] ? Object.assign({}, scope.lastStates[type+targetResource.id]) : false;
406
+ const lastState = scope.lastStates[type+targetResource.id] ? structuredClone(scope.lastStates[type+targetResource.id]) : false;
330
407
 
331
408
  if(type == "bridge")
332
409
  {
@@ -335,11 +412,11 @@ module.exports = function(RED)
335
412
 
336
413
  // GET CURRENT STATE MESSAGE
337
414
  let currentState = message.msg;
338
- return currentState;
415
+ return currentState;
339
416
  } catch (error) {
340
417
  return false;
341
418
  }
342
-
419
+
343
420
  }
344
421
  else if(type == "light")
345
422
  {
@@ -348,7 +425,7 @@ module.exports = function(RED)
348
425
 
349
426
  // GET & SAVE LAST STATE AND DIFFERENCES
350
427
  let currentState = message.msg;
351
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
428
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
352
429
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
353
430
  currentState.lastState = lastState;
354
431
 
@@ -365,7 +442,7 @@ module.exports = function(RED)
365
442
 
366
443
  // GET & SAVE LAST STATE AND DIFFERENCES
367
444
  let currentState = message.msg;
368
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
445
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
369
446
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
370
447
  currentState.lastState = lastState;
371
448
 
@@ -381,7 +458,7 @@ module.exports = function(RED)
381
458
 
382
459
  // GET & SAVE LAST STATE AND DIFFERENCES
383
460
  let currentState = message.msg;
384
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
461
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
385
462
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
386
463
  currentState.lastState = lastState;
387
464
 
@@ -397,7 +474,23 @@ module.exports = function(RED)
397
474
 
398
475
  // GET & SAVE LAST STATE AND DIFFERENCES
399
476
  let currentState = message.msg;
400
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
477
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
478
+ currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
479
+ currentState.lastState = lastState;
480
+
481
+ return currentState;
482
+ } catch (error) {
483
+ return false;
484
+ }
485
+ }
486
+ else if(type == "contact")
487
+ {
488
+ try {
489
+ const message = new HueContactMessage(targetResource, options);
490
+
491
+ // GET & SAVE LAST STATE AND DIFFERENCES
492
+ let currentState = message.msg;
493
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
401
494
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
402
495
  currentState.lastState = lastState;
403
496
 
@@ -413,7 +506,7 @@ module.exports = function(RED)
413
506
 
414
507
  // GET & SAVE LAST STATE AND DIFFERENCES
415
508
  let currentState = message.msg;
416
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
509
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
417
510
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
418
511
  currentState.lastState = lastState;
419
512
 
@@ -429,7 +522,39 @@ module.exports = function(RED)
429
522
 
430
523
  // GET & SAVE LAST STATE AND DIFFERENCES
431
524
  let currentState = message.msg;
432
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
525
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
526
+ currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
527
+ currentState.lastState = lastState;
528
+
529
+ return currentState;
530
+ } catch (error) {
531
+ return false;
532
+ }
533
+ }
534
+ else if(type == "speaker")
535
+ {
536
+ try {
537
+ const message = new HueSpeakerMessage(targetResource, options);
538
+
539
+ // GET & SAVE LAST STATE AND DIFFERENCES
540
+ let currentState = message.msg;
541
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
542
+ currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
543
+ currentState.lastState = lastState;
544
+
545
+ return currentState;
546
+ } catch (error) {
547
+ return false;
548
+ }
549
+ }
550
+ else if(type == "automation")
551
+ {
552
+ try {
553
+ const message = new HueAutomationMessage(targetResource, options);
554
+
555
+ // GET & SAVE LAST STATE AND DIFFERENCES
556
+ let currentState = message.msg;
557
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
433
558
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
434
559
  currentState.lastState = lastState;
435
560
 
@@ -445,7 +570,7 @@ module.exports = function(RED)
445
570
 
446
571
  // GET & SAVE LAST STATE AND DIFFERENCES
447
572
  let currentState = message.msg;
448
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
573
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
449
574
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
450
575
  currentState.lastState = lastState;
451
576
 
@@ -473,13 +598,15 @@ module.exports = function(RED)
473
598
  {
474
599
  const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
475
600
 
601
+ // AUTOMATIONS ARE NOT DEVICES
602
+ if(type === "automation")
603
+ {
604
+ if(resource["type"] === "behavior_instance") { allFilteredResources[rootID] = scope.get(type, rootID); }
605
+ }
476
606
  // NORMAL DEVICES
477
- if(!isGroup && resource["services"] && resource["services"][type])
607
+ else if(!isGroup && servesType(resource, type))
478
608
  {
479
- for (const [serviceID, targetDevice] of Object.entries(resource["services"][type]))
480
- {
481
- allFilteredResources[rootID] = scope.get(type, rootID);
482
- }
609
+ allFilteredResources[rootID] = scope.get(type, rootID);
483
610
  }
484
611
  // GROUPED RESOURCES
485
612
  else if(isGroup && type === "group")
@@ -497,7 +624,7 @@ module.exports = function(RED)
497
624
  {
498
625
  return new Promise(function(resolve, reject)
499
626
  {
500
- if(!scope.patchQueue) { return false; }
627
+ if(!scope.patchQueue) { return reject({ status: "ECONNRESET", errors: RED._("hue-bridge-config.node.not-connected") }); }
501
628
  scope.patchQueue.push({ type: type, id: id, patch: patch, version: version }, function (error, response)
502
629
  {
503
630
  if(error)
@@ -512,7 +639,22 @@ module.exports = function(RED)
512
639
  });
513
640
  }
514
641
 
515
- // PATCH RESOURCE (WORKER) / 7 PROCESSES IN PARALLEL
642
+ // THE BRIDGE ACCEPTS ABOUT 10 LIGHT COMMANDS BUT ONLY 1 GROUP COMMAND PER SECOND
643
+ this.rateLimits = { light: 100, group: 1000 };
644
+ this.nextSlot = { light: 0, group: 0 };
645
+
646
+ // RESERVE THE NEXT FREE TIME SLOT / GIVE BACK HOW LONG TO WAIT FOR IT
647
+ this.reserveSlot = function(type)
648
+ {
649
+ const channel = (type === "group" || type === "grouped_light" || type === "scene" || type === "smart_scene") ? "group" : "light";
650
+ const now = Date.now();
651
+ const slot = Math.max(now, scope.nextSlot[channel]);
652
+
653
+ scope.nextSlot[channel] = slot + scope.rateLimits[channel];
654
+ return slot - now;
655
+ }
656
+
657
+ // PATCH RESOURCE (WORKER)
516
658
  this.patchQueue = fastq(function({ type, id, patch, version }, callback)
517
659
  {
518
660
  // GET SERVICE ID
@@ -522,14 +664,17 @@ module.exports = function(RED)
522
664
  id = targetResource.id;
523
665
  }
524
666
 
525
- // ACTION!
526
- API.request({ config: config, method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
527
- .then(function(response) {
528
- callback(null, response);
529
- })
530
- .catch(function(error) {
531
- callback(error, null);
532
- });
667
+ // ACTION! (BUT NEVER FASTER THAN THE BRIDGE CAN TAKE IT)
668
+ setTimeout(function()
669
+ {
670
+ API.request({ config: config, method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
671
+ .then(function(response) {
672
+ callback(null, response);
673
+ })
674
+ .catch(function(error) {
675
+ callback(error, null);
676
+ });
677
+ }, scope.reserveSlot(type));
533
678
  }, config.worker ? parseInt(config.worker) : 10);
534
679
 
535
680
  // RE-FETCH RULE (RECEIVES NO UPDATES VIA SSE)
@@ -579,23 +724,34 @@ module.exports = function(RED)
579
724
  // PUSH WHITELIST
580
725
  const messageWhitelist = {
581
726
  "light": ["light", "zigbee_connectivity", "zgp_connectivity", "device"],
582
- "motion": ["motion", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
727
+ "motion": ["motion", "camera_motion", "grouped_motion", "convenience_area_motion", "security_area_motion", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
728
+ "contact": ["contact", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
583
729
  "temperature": ["temperature", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
584
- "light_level": ["light_level", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
585
- "button": ["button", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
730
+ "light_level": ["light_level", "grouped_light_level", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
731
+ "button": ["button", "bell_button", "relative_rotary", "switch_input_configuration", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
586
732
  "group": ["group", "light", "grouped_light"],
733
+ "speaker": ["speaker", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
734
+ "automation": ["behavior_instance"],
587
735
  "rule": ["rule"]
588
736
  };
589
737
 
738
+ let eventName;
739
+ let listener;
740
+
590
741
  if(!id)
591
742
  {
592
743
  // UNIVERSAL MODE
593
- this.events.on(config.id + "_" + "globalResourceUpdates", function(info)
744
+ eventName = config.id + "_" + "globalResourceUpdates";
745
+ listener = function(info)
594
746
  {
595
747
  if(type === "bridge")
596
748
  {
597
749
  callback(info);
598
750
  }
751
+ else if(!messageWhitelist[type])
752
+ {
753
+ return false;
754
+ }
599
755
  else if(info.services.includes(type) && messageWhitelist[type].includes(info.updatedType))
600
756
  {
601
757
  callback(info);
@@ -604,19 +760,27 @@ module.exports = function(RED)
604
760
  {
605
761
  callback(info);
606
762
  }
607
- });
763
+ };
608
764
  }
609
765
  else
610
766
  {
611
767
  // SPECIFIC RESOURCE MODE
612
- this.events.on(config.id + "_" + id, function(info)
768
+ eventName = config.id + "_" + id;
769
+ listener = function(info)
613
770
  {
771
+ if(type !== "bridge" && !messageWhitelist[type]) { return false; }
772
+
614
773
  if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
615
774
  {
616
775
  callback(info);
617
776
  }
618
- });
777
+ };
619
778
  }
779
+
780
+ scope.events.on(eventName, listener);
781
+
782
+ // THE CONFIG NODE OUTLIVES A REDEPLOY, SO EVERY NODE HAS TO DETACH ITSELF AGAIN
783
+ return function() { scope.events.removeListener(eventName, listener); };
620
784
  }
621
785
 
622
786
  // AUTO UPDATES?
@@ -658,7 +822,7 @@ module.exports = function(RED)
658
822
  //
659
823
  // START THE MAGIC
660
824
  this.start();
661
-
825
+
662
826
  //
663
827
  // CLOSE NODE / REMOVE EVENT LISTENER
664
828
  this.on('close', function()
@@ -666,14 +830,16 @@ module.exports = function(RED)
666
830
  scope.nodeActive = false;
667
831
 
668
832
  // UNSUBSCRIBE FROM BRIDGE EVENTS
669
- scope.log("Unsubscribing from bridge events…");
833
+ scope.log(RED._("hue-bridge-config.node.unsubscribing"));
670
834
  API.unsubscribe(config);
671
835
 
672
836
  // UNSUBSCRIBE FROM "READY" EVENTS
673
837
  scope.events.removeAllListeners();
674
838
 
675
- // REMOVE FIRMWARE UPDATE TIMEOUT
839
+ // REMOVE ALL TIMEOUTS
676
840
  if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); }
841
+ if(scope.timerWatchDog !== null) { clearTimeout(scope.timerWatchDog); }
842
+ if(scope.refetchTimeout !== null) { clearTimeout(scope.refetchTimeout); }
677
843
 
678
844
  // KILL QUEUE
679
845
  scope.patchQueue.kill();
@@ -684,7 +850,7 @@ module.exports = function(RED)
684
850
 
685
851
  //
686
852
  // DISCOVER HUE BRIDGES ON LOCAL NETWORK
687
- RED.httpAdmin.get('/hue/bridges', async function(req, res, next)
853
+ RED.httpAdmin.get('/hue/bridges', RED.auth.needsPermission('hue-bridge.read'), async function(req, res, next)
688
854
  {
689
855
  axios({
690
856
  "method": "GET",
@@ -692,6 +858,7 @@ module.exports = function(RED)
692
858
  "headers": {
693
859
  "Content-Type": "application/json; charset=utf-8"
694
860
  },
861
+ "timeout": 10000,
695
862
  })
696
863
  .then(function(response)
697
864
  {
@@ -699,20 +866,24 @@ module.exports = function(RED)
699
866
  var bridges = {};
700
867
  for (var i = response.data.length - 1; i >= 0; i--)
701
868
  {
702
- var ipAddress = response.data[i].internalipaddress;
703
- bridges[ipAddress] = { ip: ipAddress, name: ipAddress };
869
+ // THE DISCOVERY SERVICE ALSO ANSWERS WITH A PORT SINCE THE BRIDGE PRO
870
+ const ipAddress = response.data[i].internalipaddress;
871
+ const port = response.data[i].port;
872
+ const target = (port && port !== 443) ? (ipAddress + ":" + port) : ipAddress;
873
+
874
+ bridges[target] = { ip: target, name: target };
704
875
  }
705
876
 
706
877
  res.end(JSON.stringify(Object.values(bridges)));
707
878
  })
708
879
  .catch(function(error) {
709
- res.send(error);
880
+ res.status(500).send(JSON.stringify({ error: error.message }));
710
881
  });
711
882
  });
712
883
 
713
884
  //
714
885
  // GET BRIDGE NAME
715
- RED.httpAdmin.get('/hue/name', function(req, res, next)
886
+ RED.httpAdmin.get('/hue/name', RED.auth.needsPermission('hue-bridge.read'), function(req, res, next)
716
887
  {
717
888
  if(!req.query.ip)
718
889
  {
@@ -725,14 +896,14 @@ module.exports = function(RED)
725
896
  res.end(bridge.name);
726
897
  })
727
898
  .catch(function(error) {
728
- res.send(error);
899
+ res.status(500).send(error.message ? error.message : JSON.stringify(error));
729
900
  });
730
901
  }
731
902
  });
732
903
 
733
904
  //
734
905
  // REGISTER A HUE BRIDGE
735
- RED.httpAdmin.get('/hue/register', function(req, rescope, next)
906
+ RED.httpAdmin.get('/hue/register', RED.auth.needsPermission('hue-bridge.read'), function(req, rescope, next)
736
907
  {
737
908
  if(!req.query.ip)
738
909
  {
@@ -740,21 +911,27 @@ module.exports = function(RED)
740
911
  }
741
912
  else
742
913
  {
914
+ // MODERN BRIDGES (AND THE BRIDGE PRO) NO LONGER ANSWER ON PLAIN HTTP
743
915
  axios({
744
916
  "method": "POST",
745
- "url": "http://"+req.query.ip+"/api",
917
+ "url": "https://"+req.query.ip+"/api",
746
918
  "httpsAgent": new https.Agent({ rejectUnauthorized: false }),
919
+ "proxy": false, // THE BRIDGE IS ON THE LOCAL NETWORK, NEVER GO THROUGH A PROXY
747
920
  "headers": {
748
921
  "Content-Type": "application/json; charset=utf-8"
749
922
  },
923
+ "timeout": 10000,
750
924
  "data": {
751
- "devicetype": "HueMagic for Node-RED (" + Math.floor((Math.random() * 100) + 1) + ")"
925
+ "devicetype": "huemagic#node-red " + Math.floor((Math.random() * 100) + 1),
926
+ "generateclientkey": true
752
927
  }
753
928
  })
754
929
  .then(function(response)
755
930
  {
756
931
  var bridge = response.data;
757
- if(bridge[0].error)
932
+
933
+ // LINK BUTTON NOT PRESSED (ERROR TYPE 101) OR ANYTHING ELSE WENT WRONG
934
+ if(!Array.isArray(bridge) || !bridge[0] || bridge[0].error || !bridge[0].success)
758
935
  {
759
936
  rescope.end("error");
760
937
  }
@@ -764,14 +941,14 @@ module.exports = function(RED)
764
941
  }
765
942
  })
766
943
  .catch(function(error) {
767
- rescope.status(500).send(error);
944
+ rescope.status(500).send(error.message ? error.message : JSON.stringify(error));
768
945
  });
769
946
  }
770
947
  });
771
948
 
772
949
  //
773
950
  // DISCOVER RESOURCES
774
- RED.httpAdmin.get('/hue/resources', function(req, res, next)
951
+ RED.httpAdmin.get('/hue/resources', RED.auth.needsPermission('hue-bridge.read'), function(req, res, next)
775
952
  {
776
953
  const targetType = req.query.type;
777
954
 
@@ -785,6 +962,9 @@ module.exports = function(RED)
785
962
 
786
963
  for (var [id, rule] of Object.entries(rules))
787
964
  {
965
+ // SKIP ERROR RESPONSES OF THE LEGACY API
966
+ if(!rule || typeof rule !== 'object' || rule["error"]) { continue; }
967
+
788
968
  var oneDevice = {};
789
969
  oneDevice.id = id;
790
970
  oneDevice.name = rule.name;
@@ -819,19 +999,29 @@ module.exports = function(RED)
819
999
  {
820
1000
  const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
821
1001
 
822
- // NORMAL DEVICES
823
- if(!isGroup && resource["services"] && resource["services"][targetType])
1002
+ // AUTOMATIONS OF THE HUE APP
1003
+ if(targetType === "automation")
824
1004
  {
825
- for (const [deviceID, targetDevice] of Object.entries(resource["services"][targetType]))
1005
+ if(resource["type"] === "behavior_instance")
826
1006
  {
827
1007
  var oneDevice = {};
828
1008
  oneDevice.id = id;
829
- oneDevice.name = resource.metadata ? resource.metadata.name : false;
830
- oneDevice.model = resource.product_data ? resource.product_data.product_name : false;
1009
+ oneDevice.name = (resource.metadata && resource.metadata.name) ? resource.metadata.name : id;
1010
+ oneDevice.model = resource.script_id ? resource.script_id : false;
831
1011
 
832
1012
  targetDevices[id] = oneDevice;
833
1013
  }
834
1014
  }
1015
+ // NORMAL DEVICES
1016
+ else if(!isGroup && servesType(resource, targetType))
1017
+ {
1018
+ var oneDevice = {};
1019
+ oneDevice.id = id;
1020
+ oneDevice.name = resource.metadata ? resource.metadata.name : (resource.name ? resource.name : false);
1021
+ oneDevice.model = resource.product_data ? resource.product_data.product_name : false;
1022
+
1023
+ targetDevices[id] = oneDevice;
1024
+ }
835
1025
  // GROUPED (LIGHT) RESOURCES
836
1026
  else if(isGroup && targetType === "group")
837
1027
  {
@@ -846,12 +1036,15 @@ module.exports = function(RED)
846
1036
  }
847
1037
  }
848
1038
  // SCENES
849
- else if(targetType === "scene" && resource["type"] == "scene")
1039
+ else if(targetType === "scene" && (resource["type"] == "scene" || resource["type"] == "smart_scene"))
850
1040
  {
1041
+ // THE GROUP OF A SCENE MAY BE UNKNOWN OR ALREADY DELETED
1042
+ const sceneGroup = (resource["group"] && processedResources[resource["group"]["rid"]]) ? processedResources[resource["group"]["rid"]] : false;
1043
+
851
1044
  var oneDevice = {};
852
1045
  oneDevice.id = id;
853
1046
  oneDevice.name = resource.metadata ? resource.metadata.name : false;
854
- oneDevice.group = processedResources[resource["group"]["rid"]].metadata.name;
1047
+ oneDevice.group = (sceneGroup && sceneGroup.metadata) ? sceneGroup.metadata.name : "–";
855
1048
 
856
1049
  targetDevices[id] = oneDevice;
857
1050
  }
@@ -868,4 +1061,4 @@ module.exports = function(RED)
868
1061
  });
869
1062
  }
870
1063
  });
871
- };
1064
+ };