node-red-contrib-huemagic 4.2.2 → 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 +89 -5
  2. package/README.md +506 -123
  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 +290 -99
  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 -5
  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 +158 -54
  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 -11
  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
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 = /[a-zA-Z0-9]/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;
@@ -48,40 +56,52 @@ module.exports = function(RED)
48
56
  // PERIODICALLY CHECK WETHER BRIDGE IS CONNECTED
49
57
  this.startWatchdog = function()
50
58
  {
51
- if (this.timerWatchDog !== null) clearTimeout(this.timerWatchDog);
52
- this.timerWatchDog = setTimeout(function()
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()
53
64
  {
54
- try
65
+ API.request({ config: config, resource: "bridge" })
66
+ .then(function(bridgeInformation)
55
67
  {
56
- API.request({ config: config, resource: "/config", version: 1 })
57
- .then(function(bridgeInformation)
58
- {
59
- scope.startWatchdog();
60
- })
61
- .catch(function(error)
62
- {
63
- scope.log("Error requesting info from the bridge. Reconnect in some secs. " + error.message);
64
- scope.start();
65
- });
66
- } catch (error) {
67
- scope.log("Lost connection with the bridge. Reconnect in some secs. " + error.message);
68
- scope.start();
69
- }
70
- }, 10000);
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
  }
72
88
 
73
89
  // INITIALIZE
74
90
  this.start = function()
75
91
  {
76
- scope.log("Initializing the bridge ("+config.bridge+")…");
92
+ if(scope.starting === true || scope.nodeActive === false) { return false; }
93
+ scope.starting = true;
94
+ scope.watchdogFailures = 0;
95
+
96
+ scope.log(RED._("hue-bridge-config.node.initializing", { bridge: config.bridge }));
77
97
  API.init({ config: config })
78
98
  .then(function(bridge) {
79
- scope.log("Connected to bridge");
99
+ scope.log(RED._("hue-bridge-config.node.connected"));
80
100
  return scope.getAllResources();
81
101
  })
82
102
  .then(function(allResources)
83
103
  {
84
- scope.log("Processing bridge resources…");
104
+ scope.log(RED._("hue-bridge-config.node.processing"));
85
105
  return API.processResources(allResources);
86
106
  })
87
107
  .then(function(allResources)
@@ -90,11 +110,13 @@ module.exports = function(RED)
90
110
  scope.resources = allResources;
91
111
 
92
112
  // EMIT INITIAL STATES -> NODES
93
- scope.log("Initial emit of resource states…");
113
+ scope.log(RED._("hue-bridge-config.node.initial-emit"));
94
114
  return scope.emitInitialStates();
95
115
  })
96
116
  .then(function(emitted)
97
117
  {
118
+ scope.starting = false;
119
+
98
120
  // START REFRESHING STATES
99
121
  scope.keepUpdated();
100
122
 
@@ -108,6 +130,7 @@ module.exports = function(RED)
108
130
  .catch(function(error)
109
131
  {
110
132
  // RETRY AFTER 30 SECONDS
133
+ scope.starting = false;
111
134
  scope.log(error);
112
135
  if(scope.nodeActive == true) { setTimeout(function(){ scope.start(); }, 30000); }
113
136
  });
@@ -149,8 +172,13 @@ module.exports = function(RED)
149
172
  {
150
173
  var allResources = [];
151
174
 
152
- // GET BRIDGE INFORMATION
175
+ // GET BRIDGE INFORMATION (LEGACY API / MAY BE UNAVAILABLE)
153
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
+ })
154
182
  .then(function(bridgeInformation)
155
183
  {
156
184
  // PUSH TO RESOURCES
@@ -164,13 +192,16 @@ module.exports = function(RED)
164
192
  // MERGE RESOURCES
165
193
  allResources = allResources.concat(v2Resources);
166
194
 
167
- // GET RULES (LEGACY API)
168
- 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 {}; });
169
197
  })
170
198
  .then(function(rules)
171
199
  {
172
200
  for (var [id, rule] of Object.entries(rules))
173
201
  {
202
+ // SKIP ERROR RESPONSES OF THE LEGACY API
203
+ if(!rule || typeof rule !== 'object' || rule["error"]) { continue; }
204
+
174
205
  // "RENAME" OWNER
175
206
  rule["_owner"] = rule["owner"];
176
207
  delete rule["owner"];
@@ -203,6 +234,7 @@ module.exports = function(RED)
203
234
  // PUSH ALL STATES
204
235
  for (const [id, resource] of Object.entries(scope.resources))
205
236
  {
237
+ if(id === "_groupsOf") { continue; }
206
238
  scope.pushUpdatedState(resource, resource.type, true);
207
239
  }
208
240
 
@@ -216,7 +248,7 @@ module.exports = function(RED)
216
248
  {
217
249
  if(!config.disableupdates)
218
250
  {
219
- scope.log("Keeping nodes up-to-date…");
251
+ scope.log(RED._("hue-bridge-config.node.keep-updated"));
220
252
 
221
253
  // REFRESH STATES (SSE)
222
254
  this.refreshStatesSSE();
@@ -226,11 +258,17 @@ module.exports = function(RED)
226
258
  // GET UPDATED STATES (SSE)
227
259
  this.refreshStatesSSE = function()
228
260
  {
229
- scope.log("Subscribing to bridge events…");
230
- API.subscribe(config, function(updates)
261
+ scope.log(RED._("hue-bridge-config.node.subscribing"));
262
+ API.subscribe(config, function(updates, eventType)
231
263
  {
232
264
  const currentDateTime = dayjs().format();
233
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
+
234
272
  for(let resource of updates)
235
273
  {
236
274
  let id = resource.id;
@@ -242,18 +280,19 @@ module.exports = function(RED)
242
280
  if(resource["owner"])
243
281
  {
244
282
  let targetId = resource["owner"]["rid"];
283
+ const services = scope.resources[targetId] ? scope.resources[targetId]["services"] : false;
245
284
 
246
- if(scope.resources[targetId])
285
+ if(services && services[type])
247
286
  {
248
287
  // GET PREVIOUS STATE
249
- previousState = scope.resources[targetId]["services"][type][id];
288
+ previousState = services[type][id];
250
289
 
251
- // IS BUTTON? -> REMOVE PREVIOUS STATES
252
- if(type === "button")
290
+ // IS BUTTON OR DIAL? -> REMOVE PREVIOUS STATES
291
+ if(type === "button" || type === "relative_rotary")
253
292
  {
254
- for (const [oneButtonID, oneButton] of Object.entries(scope.resources[targetId]["services"]["button"]))
293
+ for (const [oneServiceID, oneService] of Object.entries(services[type]))
255
294
  {
256
- delete scope.resources[targetId]["services"]["button"][oneButtonID]["button"];
295
+ delete services[type][oneServiceID][type];
257
296
  }
258
297
  }
259
298
  }
@@ -264,8 +303,8 @@ module.exports = function(RED)
264
303
  previousState = scope.resources[id];
265
304
  }
266
305
 
267
- // NO PREVIOUS STATE?
268
- if(!previousState) { return false; }
306
+ // NO PREVIOUS STATE? -> UNKNOWN RESOURCE, CONTINUE WITH THE NEXT ONE
307
+ if(!previousState) { continue; }
269
308
 
270
309
  // CHECK DIFFERENCES
271
310
  const mergedState = merge.deep(previousState, resource);
@@ -293,23 +332,59 @@ module.exports = function(RED)
293
332
  }
294
333
  }
295
334
  }
335
+ },
336
+ function(reason, seconds)
337
+ {
338
+ scope.log(RED._("hue-bridge-config.node.connection-lost", { reason: reason, seconds: seconds }));
296
339
  });
297
340
  }
298
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
+
299
365
  // PUSH UPDATED STATE
300
366
  this.pushUpdatedState = function(resource, updatedType, suppressMessage = false)
301
367
  {
302
- 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 };
303
376
  this.events.emit(config.id + "_" + resource.id, msg);
304
377
  this.events.emit(config.id + "_" + "globalResourceUpdates", msg);
305
378
 
306
379
  // RESOURCE CONTAINS SERVICES? -> SERVICE IN GROUP? -> EMIT CHANGES TO GROUPS ALSO
307
- if(this.resources["_groupsOf"][resource.id])
380
+ const groupsOfResource = this.resources["_groupsOf"] ? this.resources["_groupsOf"][resource.id] : false;
381
+
382
+ if(groupsOfResource)
308
383
  {
309
- for (var g = this.resources["_groupsOf"][resource.id].length - 1; g >= 0; g--)
384
+ for (var g = groupsOfResource.length - 1; g >= 0; g--)
310
385
  {
311
- const groupID = this.resources["_groupsOf"][resource.id][g];
312
- 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 };
313
388
 
314
389
  this.events.emit(config.id + "_" + groupID, groupMessage);
315
390
  this.events.emit(config.id + "_" + "globalResourceUpdates", groupMessage);
@@ -328,7 +403,7 @@ module.exports = function(RED)
328
403
  {
329
404
  // RESOLVE LINKS
330
405
  const targetResource = scope.resources[id];
331
- 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;
332
407
 
333
408
  if(type == "bridge")
334
409
  {
@@ -337,11 +412,11 @@ module.exports = function(RED)
337
412
 
338
413
  // GET CURRENT STATE MESSAGE
339
414
  let currentState = message.msg;
340
- return currentState;
415
+ return currentState;
341
416
  } catch (error) {
342
417
  return false;
343
418
  }
344
-
419
+
345
420
  }
346
421
  else if(type == "light")
347
422
  {
@@ -350,7 +425,7 @@ module.exports = function(RED)
350
425
 
351
426
  // GET & SAVE LAST STATE AND DIFFERENCES
352
427
  let currentState = message.msg;
353
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
428
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
354
429
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
355
430
  currentState.lastState = lastState;
356
431
 
@@ -367,7 +442,7 @@ module.exports = function(RED)
367
442
 
368
443
  // GET & SAVE LAST STATE AND DIFFERENCES
369
444
  let currentState = message.msg;
370
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
445
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
371
446
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
372
447
  currentState.lastState = lastState;
373
448
 
@@ -383,7 +458,7 @@ module.exports = function(RED)
383
458
 
384
459
  // GET & SAVE LAST STATE AND DIFFERENCES
385
460
  let currentState = message.msg;
386
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
461
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
387
462
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
388
463
  currentState.lastState = lastState;
389
464
 
@@ -399,7 +474,23 @@ module.exports = function(RED)
399
474
 
400
475
  // GET & SAVE LAST STATE AND DIFFERENCES
401
476
  let currentState = message.msg;
402
- 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);
403
494
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
404
495
  currentState.lastState = lastState;
405
496
 
@@ -415,7 +506,7 @@ module.exports = function(RED)
415
506
 
416
507
  // GET & SAVE LAST STATE AND DIFFERENCES
417
508
  let currentState = message.msg;
418
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
509
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
419
510
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
420
511
  currentState.lastState = lastState;
421
512
 
@@ -431,7 +522,39 @@ module.exports = function(RED)
431
522
 
432
523
  // GET & SAVE LAST STATE AND DIFFERENCES
433
524
  let currentState = message.msg;
434
- 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);
435
558
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
436
559
  currentState.lastState = lastState;
437
560
 
@@ -447,7 +570,7 @@ module.exports = function(RED)
447
570
 
448
571
  // GET & SAVE LAST STATE AND DIFFERENCES
449
572
  let currentState = message.msg;
450
- scope.lastStates[type+targetResource.id] = Object.assign({}, currentState);
573
+ scope.lastStates[type+targetResource.id] = structuredClone(currentState);
451
574
  currentState.updated = (lastState === false) ? {} : diff(lastState, currentState);
452
575
  currentState.lastState = lastState;
453
576
 
@@ -475,13 +598,15 @@ module.exports = function(RED)
475
598
  {
476
599
  const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
477
600
 
601
+ // AUTOMATIONS ARE NOT DEVICES
602
+ if(type === "automation")
603
+ {
604
+ if(resource["type"] === "behavior_instance") { allFilteredResources[rootID] = scope.get(type, rootID); }
605
+ }
478
606
  // NORMAL DEVICES
479
- if(!isGroup && resource["services"] && resource["services"][type])
607
+ else if(!isGroup && servesType(resource, type))
480
608
  {
481
- for (const [serviceID, targetDevice] of Object.entries(resource["services"][type]))
482
- {
483
- allFilteredResources[rootID] = scope.get(type, rootID);
484
- }
609
+ allFilteredResources[rootID] = scope.get(type, rootID);
485
610
  }
486
611
  // GROUPED RESOURCES
487
612
  else if(isGroup && type === "group")
@@ -499,7 +624,7 @@ module.exports = function(RED)
499
624
  {
500
625
  return new Promise(function(resolve, reject)
501
626
  {
502
- if(!scope.patchQueue) { return false; }
627
+ if(!scope.patchQueue) { return reject({ status: "ECONNRESET", errors: RED._("hue-bridge-config.node.not-connected") }); }
503
628
  scope.patchQueue.push({ type: type, id: id, patch: patch, version: version }, function (error, response)
504
629
  {
505
630
  if(error)
@@ -514,7 +639,22 @@ module.exports = function(RED)
514
639
  });
515
640
  }
516
641
 
517
- // 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)
518
658
  this.patchQueue = fastq(function({ type, id, patch, version }, callback)
519
659
  {
520
660
  // GET SERVICE ID
@@ -524,14 +664,17 @@ module.exports = function(RED)
524
664
  id = targetResource.id;
525
665
  }
526
666
 
527
- // ACTION!
528
- API.request({ config: config, method: "PUT", resource: (version === 2) ? (type+"/"+id) : id, data: patch, version: version })
529
- .then(function(response) {
530
- callback(null, response);
531
- })
532
- .catch(function(error) {
533
- callback(error, null);
534
- });
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));
535
678
  }, config.worker ? parseInt(config.worker) : 10);
536
679
 
537
680
  // RE-FETCH RULE (RECEIVES NO UPDATES VIA SSE)
@@ -581,23 +724,34 @@ module.exports = function(RED)
581
724
  // PUSH WHITELIST
582
725
  const messageWhitelist = {
583
726
  "light": ["light", "zigbee_connectivity", "zgp_connectivity", "device"],
584
- "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"],
585
729
  "temperature": ["temperature", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
586
- "light_level": ["light_level", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
587
- "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"],
588
732
  "group": ["group", "light", "grouped_light"],
733
+ "speaker": ["speaker", "zigbee_connectivity", "zgp_connectivity", "device_power", "device"],
734
+ "automation": ["behavior_instance"],
589
735
  "rule": ["rule"]
590
736
  };
591
737
 
738
+ let eventName;
739
+ let listener;
740
+
592
741
  if(!id)
593
742
  {
594
743
  // UNIVERSAL MODE
595
- this.events.on(config.id + "_" + "globalResourceUpdates", function(info)
744
+ eventName = config.id + "_" + "globalResourceUpdates";
745
+ listener = function(info)
596
746
  {
597
747
  if(type === "bridge")
598
748
  {
599
749
  callback(info);
600
750
  }
751
+ else if(!messageWhitelist[type])
752
+ {
753
+ return false;
754
+ }
601
755
  else if(info.services.includes(type) && messageWhitelist[type].includes(info.updatedType))
602
756
  {
603
757
  callback(info);
@@ -606,19 +760,27 @@ module.exports = function(RED)
606
760
  {
607
761
  callback(info);
608
762
  }
609
- });
763
+ };
610
764
  }
611
765
  else
612
766
  {
613
767
  // SPECIFIC RESOURCE MODE
614
- this.events.on(config.id + "_" + id, function(info)
768
+ eventName = config.id + "_" + id;
769
+ listener = function(info)
615
770
  {
771
+ if(type !== "bridge" && !messageWhitelist[type]) { return false; }
772
+
616
773
  if(type === "bridge" || messageWhitelist[type].includes(info.updatedType))
617
774
  {
618
775
  callback(info);
619
776
  }
620
- });
777
+ };
621
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); };
622
784
  }
623
785
 
624
786
  // AUTO UPDATES?
@@ -660,7 +822,7 @@ module.exports = function(RED)
660
822
  //
661
823
  // START THE MAGIC
662
824
  this.start();
663
-
825
+
664
826
  //
665
827
  // CLOSE NODE / REMOVE EVENT LISTENER
666
828
  this.on('close', function()
@@ -668,14 +830,16 @@ module.exports = function(RED)
668
830
  scope.nodeActive = false;
669
831
 
670
832
  // UNSUBSCRIBE FROM BRIDGE EVENTS
671
- scope.log("Unsubscribing from bridge events…");
833
+ scope.log(RED._("hue-bridge-config.node.unsubscribing"));
672
834
  API.unsubscribe(config);
673
835
 
674
836
  // UNSUBSCRIBE FROM "READY" EVENTS
675
837
  scope.events.removeAllListeners();
676
838
 
677
- // REMOVE FIRMWARE UPDATE TIMEOUT
839
+ // REMOVE ALL TIMEOUTS
678
840
  if(scope.firmwareUpdateTimeout !== null) { clearTimeout(scope.firmwareUpdateTimeout); }
841
+ if(scope.timerWatchDog !== null) { clearTimeout(scope.timerWatchDog); }
842
+ if(scope.refetchTimeout !== null) { clearTimeout(scope.refetchTimeout); }
679
843
 
680
844
  // KILL QUEUE
681
845
  scope.patchQueue.kill();
@@ -686,7 +850,7 @@ module.exports = function(RED)
686
850
 
687
851
  //
688
852
  // DISCOVER HUE BRIDGES ON LOCAL NETWORK
689
- 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)
690
854
  {
691
855
  axios({
692
856
  "method": "GET",
@@ -694,6 +858,7 @@ module.exports = function(RED)
694
858
  "headers": {
695
859
  "Content-Type": "application/json; charset=utf-8"
696
860
  },
861
+ "timeout": 10000,
697
862
  })
698
863
  .then(function(response)
699
864
  {
@@ -701,20 +866,24 @@ module.exports = function(RED)
701
866
  var bridges = {};
702
867
  for (var i = response.data.length - 1; i >= 0; i--)
703
868
  {
704
- var ipAddress = response.data[i].internalipaddress;
705
- 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 };
706
875
  }
707
876
 
708
877
  res.end(JSON.stringify(Object.values(bridges)));
709
878
  })
710
879
  .catch(function(error) {
711
- res.send(error);
880
+ res.status(500).send(JSON.stringify({ error: error.message }));
712
881
  });
713
882
  });
714
883
 
715
884
  //
716
885
  // GET BRIDGE NAME
717
- 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)
718
887
  {
719
888
  if(!req.query.ip)
720
889
  {
@@ -727,14 +896,14 @@ module.exports = function(RED)
727
896
  res.end(bridge.name);
728
897
  })
729
898
  .catch(function(error) {
730
- res.send(error);
899
+ res.status(500).send(error.message ? error.message : JSON.stringify(error));
731
900
  });
732
901
  }
733
902
  });
734
903
 
735
904
  //
736
905
  // REGISTER A HUE BRIDGE
737
- 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)
738
907
  {
739
908
  if(!req.query.ip)
740
909
  {
@@ -742,21 +911,27 @@ module.exports = function(RED)
742
911
  }
743
912
  else
744
913
  {
914
+ // MODERN BRIDGES (AND THE BRIDGE PRO) NO LONGER ANSWER ON PLAIN HTTP
745
915
  axios({
746
916
  "method": "POST",
747
- "url": "http://"+req.query.ip+"/api",
917
+ "url": "https://"+req.query.ip+"/api",
748
918
  "httpsAgent": new https.Agent({ rejectUnauthorized: false }),
919
+ "proxy": false, // THE BRIDGE IS ON THE LOCAL NETWORK, NEVER GO THROUGH A PROXY
749
920
  "headers": {
750
921
  "Content-Type": "application/json; charset=utf-8"
751
922
  },
923
+ "timeout": 10000,
752
924
  "data": {
753
- "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
754
927
  }
755
928
  })
756
929
  .then(function(response)
757
930
  {
758
931
  var bridge = response.data;
759
- 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)
760
935
  {
761
936
  rescope.end("error");
762
937
  }
@@ -766,14 +941,14 @@ module.exports = function(RED)
766
941
  }
767
942
  })
768
943
  .catch(function(error) {
769
- rescope.status(500).send(error);
944
+ rescope.status(500).send(error.message ? error.message : JSON.stringify(error));
770
945
  });
771
946
  }
772
947
  });
773
948
 
774
949
  //
775
950
  // DISCOVER RESOURCES
776
- 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)
777
952
  {
778
953
  const targetType = req.query.type;
779
954
 
@@ -787,6 +962,9 @@ module.exports = function(RED)
787
962
 
788
963
  for (var [id, rule] of Object.entries(rules))
789
964
  {
965
+ // SKIP ERROR RESPONSES OF THE LEGACY API
966
+ if(!rule || typeof rule !== 'object' || rule["error"]) { continue; }
967
+
790
968
  var oneDevice = {};
791
969
  oneDevice.id = id;
792
970
  oneDevice.name = rule.name;
@@ -821,19 +999,29 @@ module.exports = function(RED)
821
999
  {
822
1000
  const isGroup = (resource["type"] == "room" || resource["type"] == "zone" || resource["type"] == "bridge_home");
823
1001
 
824
- // NORMAL DEVICES
825
- if(!isGroup && resource["services"] && resource["services"][targetType])
1002
+ // AUTOMATIONS OF THE HUE APP
1003
+ if(targetType === "automation")
826
1004
  {
827
- for (const [deviceID, targetDevice] of Object.entries(resource["services"][targetType]))
1005
+ if(resource["type"] === "behavior_instance")
828
1006
  {
829
1007
  var oneDevice = {};
830
1008
  oneDevice.id = id;
831
- oneDevice.name = resource.metadata ? resource.metadata.name : false;
832
- 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;
833
1011
 
834
1012
  targetDevices[id] = oneDevice;
835
1013
  }
836
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
+ }
837
1025
  // GROUPED (LIGHT) RESOURCES
838
1026
  else if(isGroup && targetType === "group")
839
1027
  {
@@ -848,12 +1036,15 @@ module.exports = function(RED)
848
1036
  }
849
1037
  }
850
1038
  // SCENES
851
- else if(targetType === "scene" && resource["type"] == "scene")
1039
+ else if(targetType === "scene" && (resource["type"] == "scene" || resource["type"] == "smart_scene"))
852
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
+
853
1044
  var oneDevice = {};
854
1045
  oneDevice.id = id;
855
1046
  oneDevice.name = resource.metadata ? resource.metadata.name : false;
856
- oneDevice.group = processedResources[resource["group"]["rid"]].metadata.name;
1047
+ oneDevice.group = (sceneGroup && sceneGroup.metadata) ? sceneGroup.metadata.name : "–";
857
1048
 
858
1049
  targetDevices[id] = oneDevice;
859
1050
  }
@@ -870,4 +1061,4 @@ module.exports = function(RED)
870
1061
  });
871
1062
  }
872
1063
  });
873
- };
1064
+ };