@hurenkam/hue-services 0.6.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 (162) hide show
  1. package/.github/workflows/node.js.yml +31 -0
  2. package/.nyc_output/ecb1b16f-015c-458b-96d5-32cb9569c1e8.json +1 -0
  3. package/.nyc_output/processinfo/ecb1b16f-015c-458b-96d5-32cb9569c1e8.json +1 -0
  4. package/.nyc_output/processinfo/index.json +1 -0
  5. package/.vscode/settings.json +15 -0
  6. package/.vscode/sftp.json +11 -0
  7. package/LICENSE +201 -0
  8. package/README.md +340 -0
  9. package/Todo.txt +18 -0
  10. package/coverage/BaseNode.js.html +379 -0
  11. package/coverage/base.css +224 -0
  12. package/coverage/block-navigation.js +87 -0
  13. package/coverage/favicon.png +0 -0
  14. package/coverage/index.html +101 -0
  15. package/coverage/lcov-report/base.css +224 -0
  16. package/coverage/lcov-report/block-navigation.js +87 -0
  17. package/coverage/lcov-report/favicon.png +0 -0
  18. package/coverage/lcov-report/index.html +161 -0
  19. package/coverage/lcov-report/prettify.css +1 -0
  20. package/coverage/lcov-report/prettify.js +2 -0
  21. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  22. package/coverage/lcov-report/sorter.js +196 -0
  23. package/coverage/lcov-report/src/RestApi.js.html +499 -0
  24. package/coverage/lcov-report/src/all.js.html +208 -0
  25. package/coverage/lcov-report/src/clip/ClipApi.js.html +1096 -0
  26. package/coverage/lcov-report/src/clip/Resource.js.html +481 -0
  27. package/coverage/lcov-report/src/clip/index.html +131 -0
  28. package/coverage/lcov-report/src/debug.js.html +253 -0
  29. package/coverage/lcov-report/src/files.js.html +118 -0
  30. package/coverage/lcov-report/src/index.html +161 -0
  31. package/coverage/lcov-report/src/nodes/BaseNode.js.html +381 -0
  32. package/coverage/lcov-report/src/nodes/BridgeConfigNode.js.html +817 -0
  33. package/coverage/lcov-report/src/nodes/ButtonNode.js.html +241 -0
  34. package/coverage/lcov-report/src/nodes/DevicePowerNode.js.html +199 -0
  35. package/coverage/lcov-report/src/nodes/GroupedLightNode.js.html +226 -0
  36. package/coverage/lcov-report/src/nodes/LightLevelNode.js.html +205 -0
  37. package/coverage/lcov-report/src/nodes/LightNode.js.html +226 -0
  38. package/coverage/lcov-report/src/nodes/MotionNode.js.html +205 -0
  39. package/coverage/lcov-report/src/nodes/RelativeRotaryNode.js.html +253 -0
  40. package/coverage/lcov-report/src/nodes/ResourceNode.js.html +388 -0
  41. package/coverage/lcov-report/src/nodes/SceneNode.js.html +124 -0
  42. package/coverage/lcov-report/src/nodes/ServiceNode.js.html +124 -0
  43. package/coverage/lcov-report/src/nodes/TemperatureNode.js.html +205 -0
  44. package/coverage/lcov-report/src/nodes/ZigbeeConnectivityNode.js.html +199 -0
  45. package/coverage/lcov-report/src/nodes/index.html +311 -0
  46. package/coverage/lcov-report/src/ui/BaseUI.js.html +484 -0
  47. package/coverage/lcov-report/src/ui/BridgeConfigUI.js.html +475 -0
  48. package/coverage/lcov-report/src/ui/ButtonUI.js.html +166 -0
  49. package/coverage/lcov-report/src/ui/DevicePowerUI.js.html +166 -0
  50. package/coverage/lcov-report/src/ui/GroupedLightUI.js.html +178 -0
  51. package/coverage/lcov-report/src/ui/LightLevelUI.js.html +166 -0
  52. package/coverage/lcov-report/src/ui/LightUI.js.html +178 -0
  53. package/coverage/lcov-report/src/ui/MotionUI.js.html +166 -0
  54. package/coverage/lcov-report/src/ui/RelativeRotaryUI.js.html +166 -0
  55. package/coverage/lcov-report/src/ui/ResourceUI.js.html +511 -0
  56. package/coverage/lcov-report/src/ui/SceneUI.js.html +160 -0
  57. package/coverage/lcov-report/src/ui/ServiceUI.js.html +868 -0
  58. package/coverage/lcov-report/src/ui/TemperatureUI.js.html +166 -0
  59. package/coverage/lcov-report/src/ui/ZigbeeConnectivityUI.js.html +166 -0
  60. package/coverage/lcov-report/src/ui/index.html +311 -0
  61. package/coverage/lcov.info +2217 -0
  62. package/coverage/prettify.css +1 -0
  63. package/coverage/prettify.js +2 -0
  64. package/coverage/sort-arrow-sprite.png +0 -0
  65. package/coverage/sorter.js +196 -0
  66. package/coverage/src/RestApi.js.html +499 -0
  67. package/coverage/src/all.js.html +208 -0
  68. package/coverage/src/clip/ClipApi.js.html +1183 -0
  69. package/coverage/src/clip/Resource.js.html +481 -0
  70. package/coverage/src/clip/index.html +131 -0
  71. package/coverage/src/debug.js.html +253 -0
  72. package/coverage/src/files.js.html +118 -0
  73. package/coverage/src/index.html +116 -0
  74. package/coverage/src/nodes/BaseNode.js.html +379 -0
  75. package/coverage/src/nodes/BridgeConfigNode.js.html +517 -0
  76. package/coverage/src/nodes/ButtonNode.js.html +238 -0
  77. package/coverage/src/nodes/DevicePowerNode.js.html +196 -0
  78. package/coverage/src/nodes/GroupedLightNode.js.html +223 -0
  79. package/coverage/src/nodes/LightLevelNode.js.html +202 -0
  80. package/coverage/src/nodes/LightNode.js.html +223 -0
  81. package/coverage/src/nodes/MotionNode.js.html +202 -0
  82. package/coverage/src/nodes/RelativeRotaryNode.js.html +250 -0
  83. package/coverage/src/nodes/ResourceNode.js.html +373 -0
  84. package/coverage/src/nodes/SceneNode.js.html +124 -0
  85. package/coverage/src/nodes/ServiceNode.js.html +124 -0
  86. package/coverage/src/nodes/TemperatureNode.js.html +202 -0
  87. package/coverage/src/nodes/ZigbeeConnectivityNode.js.html +199 -0
  88. package/coverage/src/nodes/index.html +311 -0
  89. package/coverage/src/ui/BaseUI.js.html +484 -0
  90. package/coverage/src/ui/BridgeConfigUI.js.html +475 -0
  91. package/coverage/src/ui/ButtonUI.js.html +166 -0
  92. package/coverage/src/ui/DevicePowerUI.js.html +166 -0
  93. package/coverage/src/ui/GroupedLightUI.js.html +178 -0
  94. package/coverage/src/ui/LightLevelUI.js.html +166 -0
  95. package/coverage/src/ui/LightUI.js.html +178 -0
  96. package/coverage/src/ui/MotionUI.js.html +166 -0
  97. package/coverage/src/ui/RelativeRotaryUI.js.html +166 -0
  98. package/coverage/src/ui/ResourceUI.js.html +511 -0
  99. package/coverage/src/ui/SceneUI.js.html +160 -0
  100. package/coverage/src/ui/ServiceUI.js.html +868 -0
  101. package/coverage/src/ui/TemperatureUI.js.html +166 -0
  102. package/coverage/src/ui/ZigbeeConnectivityUI.js.html +166 -0
  103. package/coverage/src/ui/index.html +311 -0
  104. package/examples/flows.json +959 -0
  105. package/package.json +53 -0
  106. package/screenshots/Screenshot from 2022-11-29 00-29-23.png +0 -0
  107. package/screenshots/Screenshot from 2022-11-29 00-30-16.png +0 -0
  108. package/screenshots/Screenshot from 2022-11-29 00-30-58.png +0 -0
  109. package/screenshots/Screenshot from 2022-11-29 00-31-29.png +0 -0
  110. package/src/RestApi.js +138 -0
  111. package/src/all.html +52 -0
  112. package/src/all.js +42 -0
  113. package/src/clip/ClipApi.js +367 -0
  114. package/src/clip/Resource.js +132 -0
  115. package/src/debug.js +56 -0
  116. package/src/files.js +12 -0
  117. package/src/hue.js +98 -0
  118. package/src/nodes/BaseNode.js +98 -0
  119. package/src/nodes/BridgeConfigNode.js +144 -0
  120. package/src/nodes/ButtonNode.js +51 -0
  121. package/src/nodes/DevicePowerNode.js +37 -0
  122. package/src/nodes/GroupedLightNode.js +46 -0
  123. package/src/nodes/LightLevelNode.js +39 -0
  124. package/src/nodes/LightNode.js +46 -0
  125. package/src/nodes/MotionNode.js +39 -0
  126. package/src/nodes/RelativeRotaryNode.js +55 -0
  127. package/src/nodes/ResourceNode.js +95 -0
  128. package/src/nodes/SceneNode.js +13 -0
  129. package/src/nodes/ServiceNode.js +13 -0
  130. package/src/nodes/TemperatureNode.js +39 -0
  131. package/src/nodes/ZigbeeConnectivityNode.js +38 -0
  132. package/src/ui/BaseUI.js +133 -0
  133. package/src/ui/BridgeConfigUI.js +130 -0
  134. package/src/ui/ButtonUI.js +27 -0
  135. package/src/ui/DevicePowerUI.js +27 -0
  136. package/src/ui/GroupedLightUI.js +31 -0
  137. package/src/ui/LightLevelUI.js +27 -0
  138. package/src/ui/LightUI.js +31 -0
  139. package/src/ui/MotionUI.js +27 -0
  140. package/src/ui/RelativeRotaryUI.js +27 -0
  141. package/src/ui/ResourceUI.js +142 -0
  142. package/src/ui/SceneUI.js +25 -0
  143. package/src/ui/ServiceUI.js +261 -0
  144. package/src/ui/TemperatureUI.js +27 -0
  145. package/src/ui/ZigbeeConnectivityUI.js +27 -0
  146. package/test/BaseNode_spec.js +246 -0
  147. package/test/BaseUI_spec.js +17 -0
  148. package/test/BridgeConfigNode_spec.js +184 -0
  149. package/test/ButtonNode_spec.js +178 -0
  150. package/test/ClipApi_spec.js +769 -0
  151. package/test/DevicePowerNode_spec.js +186 -0
  152. package/test/GroupedLightNode_spec.js +218 -0
  153. package/test/LightLevelNode_spec.js +154 -0
  154. package/test/LightNode_spec.js +218 -0
  155. package/test/MotionNode_spec.js +186 -0
  156. package/test/RelativeRotataryNode_spec.js +325 -0
  157. package/test/ResourceNode_spec.js +308 -0
  158. package/test/Resource_spec.js +392 -0
  159. package/test/SceneNode_spec.js +33 -0
  160. package/test/ServiceNode_spec.js +33 -0
  161. package/test/TemperatureNode_spec.js +153 -0
  162. package/test/ZigbeeConnectivityNode_spec.js +186 -0
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@hurenkam/hue-services",
3
+ "version": "0.6.0",
4
+ "description": "Custom node-red nodes for interfacing with philips hue devices",
5
+ "author": "Mark Hurenkamp",
6
+ "license": "Apache 2.0",
7
+ "maintainers": [
8
+ "Mark Hurenkamp <mark.hurenkamp@xs4all.nl>"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/hurenkam/node-red-hue-services.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/hurenkam/node-red-hue-services/issues"
16
+ },
17
+ "scripts": {
18
+ "test": "nyc --reporter=text --reporter=html mocha \"test/**/*_spec.js\""
19
+ },
20
+ "keywords": [
21
+ "node-red",
22
+ "hue",
23
+ "clip"
24
+ ],
25
+ "node-red": {
26
+ "version": ">=2.0.0",
27
+ "nodes": {
28
+ "files": "src/files.js",
29
+ "debug": "src/debug.js",
30
+ "hue": "src/hue.js",
31
+ "all": "src/all.js"
32
+ }
33
+ },
34
+ "engines": {
35
+ "node": ">=12.0.0"
36
+ },
37
+ "dependencies": {
38
+ "axios": "^1.1.3",
39
+ "debug": "^4.3.4",
40
+ "eventsource": "^2.0.2",
41
+ "limiter": "^2.1.0"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "devDependencies": {
47
+ "node-red": "^2.2.3",
48
+ "node-red-node-test-helper": "^0.2.7",
49
+ "mocha": "^10.1.0",
50
+ "nyc": "^15.1.0",
51
+ "sinon": "^15.0.0"
52
+ }
53
+ }
package/src/RestApi.js ADDED
@@ -0,0 +1,138 @@
1
+ const axios = require('axios');
2
+ const https = require('https');
3
+ const limiter = require('limiter');
4
+
5
+ const _error = require('debug')('error').extend('RestApi');
6
+ const _warn = require('debug')('warn').extend('RestApi');
7
+ const _info = require('debug')('info').extend('RestApi');
8
+ const _trace = require('debug')('trace').extend('RestApi');
9
+
10
+ class RestApi {
11
+ #ip;
12
+ #headers;
13
+ #requestQ;
14
+ #timeout;
15
+ #limiter;
16
+
17
+ #error;
18
+ #warn;
19
+ #info;
20
+ #trace;
21
+
22
+ constructor(name,ip,throttle,headers) {
23
+ this.#limiter = new limiter.RateLimiter(throttle);
24
+
25
+ this.#error = _error.extend("["+name+"]");
26
+ this.#warn = _warn. extend("["+name+"]");
27
+ this.#info = _info. extend("["+name+"]");
28
+ this.#trace = _trace.extend("["+name+"]");
29
+
30
+ this.#info("constructor()");
31
+
32
+ this.#ip = ip;
33
+ this.#headers = { "Content-Type": "application/json; charset=utf-8" };
34
+ if (headers) {
35
+ Object.keys(headers).forEach(key => {
36
+ this.#headers[key] = headers[key];
37
+ });
38
+ };
39
+
40
+ this.#requestQ = [];
41
+ this.#handleRequest();
42
+ this.#timeout = null;
43
+ }
44
+
45
+ destructor() {
46
+ this.#info("destructor()");
47
+ if (this.#timeout) {
48
+ clearTimeout(this.#timeout);
49
+ this.#timeout = null;
50
+ }
51
+
52
+ if (this.#requestQ) {
53
+ this.#requestQ.forEach(item => {
54
+ item.resolve = null;
55
+ item.reject = null;
56
+ });
57
+ }
58
+ this.#requestQ = null;
59
+ }
60
+
61
+ async #request(url, method="GET", data=null) {
62
+ var realurl = "https://" + this.#ip + url;
63
+ this.#info("_request(" + realurl + ", " + method + ",",data,")");
64
+
65
+ var request = {
66
+ "method": method,
67
+ "url": realurl,
68
+ "headers": this.#headers,
69
+ "httpsAgent": new https.Agent({ rejectUnauthorized: false })
70
+ }
71
+ if (data) {
72
+ request["data"] = data;
73
+ }
74
+
75
+ return axios(request);
76
+ }
77
+
78
+ #handleRequest() {
79
+ if ((this.#requestQ) && (this.#requestQ.length > 0)) {
80
+ this.#info("_handleRequest() pending: " + this.#requestQ.length);
81
+ var local = this;
82
+
83
+ var request = this.#requestQ.shift();
84
+ this.#request(request.url, request.method, request.data)
85
+ .then(async (result) => {
86
+ await local.#limiter.removeTokens(1,()=>{});
87
+ request.resolve(result.data);
88
+ this.#timeout = setTimeout(this.#handleRequest.bind(this), 0);
89
+ })
90
+ .catch((error) => {
91
+ this.#error("_handleRequest(" + request.url + ") error: ");
92
+ this.#error(error);
93
+ request.reject();
94
+
95
+ // back off for a few seconds, just in case we ran into a 429 error.
96
+ local.#timeout = setTimeout(local.#handleRequest.bind(local), 5000);
97
+ });
98
+
99
+ } else {
100
+ // if no request was pending, then check again soon
101
+ this.#timeout = setTimeout(this.#handleRequest.bind(this), 100);
102
+ }
103
+ }
104
+
105
+ get(url) {
106
+ this.#trace("get(" + url + ")");
107
+ var local = this;
108
+ return new Promise(function (resolve, reject) {
109
+ local.#requestQ.push({ url: url, method: "GET", data: null, resolve: resolve, reject: reject });
110
+ });
111
+ }
112
+
113
+ put(url, data) {
114
+ this.#trace("put(" + url + ")");
115
+ var local = this;
116
+ return new Promise(function (resolve, reject) {
117
+ local.#requestQ.push({ url: url, method: "PUT", data: data, resolve: resolve, reject: reject });
118
+ });
119
+ }
120
+
121
+ post(url, data) {
122
+ this.#trace(".post(" + url + ")");
123
+ var local = this;
124
+ return new Promise(function (resolve, reject) {
125
+ local.#requestQ.push({ url: url, method: "POST", data: data, resolve: resolve, reject: reject });
126
+ });
127
+ }
128
+
129
+ delete(url, data) {
130
+ this.#trace(".delete(" + url + ")");
131
+ var local = this;
132
+ return new Promise(function (resolve, reject) {
133
+ local.#requestQ.push({ url: url, method: "DELETE", data: data, resolve: resolve, reject: reject });
134
+ });
135
+ }
136
+ }
137
+
138
+ module.exports = RestApi;
package/src/all.html ADDED
@@ -0,0 +1,52 @@
1
+ <script type="module">
2
+
3
+ import { BridgeConfigUI } from "/src/ui/BridgeConfigUI.js";
4
+
5
+ import { ButtonUI } from "/src/ui/ButtonUI.js";
6
+ import { DevicePowerUI } from "/src/ui/DevicePowerUI.js";
7
+ import { GroupedLightUI } from "/src/ui/GroupedLightUI.js";
8
+ import { LightUI } from "/src/ui/LightUI.js";
9
+ import { LightLevelUI } from "/src/ui/LightLevelUI.js";
10
+ import { MotionUI } from "/src/ui/MotionUI.js";
11
+ import { RelativeRotaryUI } from "/src/ui/RelativeRotaryUI.js";
12
+ import { TemperatureUI } from "/src/ui/TemperatureUI.js";
13
+ import { SceneUI } from "/src/ui/SceneUI.js";
14
+ import { ServiceUI } from "/src/ui/ServiceUI.js";
15
+ import { ZigbeeConnectivityUI } from "/src/ui/ZigbeeConnectivityUI.js";
16
+
17
+ var nodes = {
18
+ "BridgeConfigNode": BridgeConfigUI,
19
+
20
+ "ButtonNode": ButtonUI,
21
+ "DevicePowerNode": DevicePowerUI,
22
+ "GroupedLightNode": GroupedLightUI,
23
+ "LightNode": LightUI,
24
+ "LightLevelNode": LightLevelUI,
25
+ "MotionNode": MotionUI,
26
+ "TemperatureNode": TemperatureUI,
27
+ "ServiceNode": ServiceUI,
28
+ "SceneNode": SceneUI,
29
+ "RelativeRotaryNode": RelativeRotaryUI,
30
+ "ZigbeeConnectivityNode": ZigbeeConnectivityUI,
31
+ }
32
+
33
+ Object.keys(nodes).forEach((id) => {
34
+ var node = new nodes[id]();
35
+ var typeName = "@hurenkam/hue-services/"+id
36
+
37
+ var template = document.createElement("script");
38
+ template.setAttribute("type","text/html");
39
+ template.setAttribute("data-template-name",typeName);
40
+ template.innerHTML = node.ui();
41
+ document.documentElement.appendChild(template);
42
+
43
+ var help = document.createElement("script");
44
+ help.setAttribute("type","text/markdown");
45
+ help.setAttribute("data-help-name",typeName);
46
+ help.innerHTML = node.manual();
47
+ document.documentElement.appendChild(help);
48
+
49
+ RED.nodes.registerType(typeName,node.config);
50
+ });
51
+
52
+ </script>
package/src/all.js ADDED
@@ -0,0 +1,42 @@
1
+ module.exports = function(RED) {
2
+ "use strict";
3
+
4
+ const BaseNode = require('./nodes/BaseNode');
5
+ BaseNode.nodeAPI = RED;
6
+
7
+ const BridgeConfigNode = require('./nodes/BridgeConfigNode');
8
+
9
+ const ButtonNode = require('./nodes/ButtonNode');
10
+ const DevicePowerNode = require('./nodes/DevicePowerNode');
11
+ const GroupedLightNode = require('./nodes/GroupedLightNode');
12
+ const LightNode = require('./nodes/LightNode');
13
+ const LightLevelNode = require('./nodes/LightLevelNode');
14
+ const MotionNode = require('./nodes/MotionNode');
15
+ const RelativeRotaryNode = require('./nodes/RelativeRotaryNode');
16
+ const TemperatureNode = require('./nodes/TemperatureNode');
17
+ const SceneNode = require('./nodes/SceneNode');
18
+ const ServiceNode = require('./nodes/ServiceNode');
19
+ const ZigbeeConnectivityNode = require('./nodes/ZigbeeConnectivityNode');
20
+
21
+
22
+ var nodes = {
23
+ "BridgeConfigNode": BridgeConfigNode,
24
+
25
+ "ButtonNode": ButtonNode,
26
+ "DevicePowerNode": DevicePowerNode,
27
+ "GroupedLightNode": GroupedLightNode,
28
+ "LightNode": LightNode,
29
+ "LightLevelNode": LightLevelNode,
30
+ "MotionNode": MotionNode,
31
+ "RelativeRotaryNode": RelativeRotaryNode,
32
+ "SceneNode": SceneNode,
33
+ "ServiceNode": ServiceNode,
34
+ "TemperatureNode": TemperatureNode,
35
+ "ZigbeeConnectivityNode": ZigbeeConnectivityNode,
36
+ }
37
+
38
+ Object.keys(nodes).forEach((id) => {
39
+ var typeName = "@hurenkam/hue-services/"+id
40
+ RED.nodes.registerType(typeName,nodes[id]);
41
+ });
42
+ }
@@ -0,0 +1,367 @@
1
+ const events = require('events');
2
+ const EventSource = require('eventsource');
3
+ const RestApi = require('../RestApi');
4
+
5
+ const Resource = require('./Resource');
6
+
7
+ const _error = require('debug')('error').extend('ClipApi');
8
+ const _warn = require('debug')('warn').extend('ClipApi');
9
+ const _info = require('debug')('info').extend('ClipApi');
10
+ const _trace = require('debug')('trace').extend('ClipApi');
11
+
12
+ class ClipApi extends events.EventEmitter {
13
+ #restAPI;
14
+ #resources;
15
+ #startQ;
16
+ #isStarted;
17
+ #name;
18
+ #ip;
19
+ #key;
20
+ #eventSource;
21
+
22
+ #error;
23
+ #warn;
24
+ #info;
25
+ #trace;
26
+
27
+ #factory = {
28
+ "behavior_script": Resource,
29
+ "behavior_instance": Resource,
30
+ "bridge": Resource,
31
+ "bridge_home": Resource,
32
+ "button": Resource,
33
+ "device": Resource,
34
+ "device_power": Resource,
35
+ "entertainment": Resource,
36
+ "entertainment_configuration": Resource,
37
+ "geolocation": Resource,
38
+ "grouped_light": Resource,
39
+ "homekit": Resource,
40
+ "light": Resource,
41
+ "light_level": Resource,
42
+ "matter": Resource,
43
+ "motion": Resource,
44
+ "relative_rotary": Resource,
45
+ "room": Resource,
46
+ "scene": Resource,
47
+ "temperature": Resource,
48
+ "zgp_connectivity": Resource,
49
+ "zigbee_connectivity": Resource,
50
+ "zigbee_device_discovery": Resource,
51
+ "zone": Resource
52
+ };
53
+
54
+ constructor(ip,key,name) {
55
+ super();
56
+
57
+ this.#name = name;
58
+ this.#ip = ip;
59
+ this.#key = key;
60
+ this.#resources = {};
61
+ this.#startQ = [];
62
+ this.#isStarted = false;
63
+
64
+ this.#error = _error.extend("["+name+"]");
65
+ this.#warn = _warn. extend("["+name+"]");
66
+ this.#info = _info. extend("["+name+"]");
67
+ this.#trace = _trace.extend("["+name+"]");
68
+
69
+ this.#info("constructor("+ip+","+key+","+name+")");
70
+
71
+ this.#restAPI = this._initRestApi();
72
+ this.#eventSource = this._initEventStream();
73
+ this._requestResources();
74
+ }
75
+
76
+ _initRestApi() {
77
+ var restAPI = new RestApi(
78
+ this.#name,
79
+ this.#ip,
80
+ { tokensPerInterval: 3, interval: "second" },
81
+ {"hue-application-key": this.#key}
82
+ );
83
+ return restAPI;
84
+ }
85
+
86
+ _restApi() {
87
+ return this.#restAPI;
88
+ }
89
+
90
+ _initEventStream() {
91
+ this.#info("_initEventStream()");
92
+
93
+ var url = "https://" + this.#ip + "/eventstream/clip/v2";
94
+ var eventSource = new EventSource(url, {
95
+ headers: { 'hue-application-key': this.#key },
96
+ https: { rejectUnauthorized: false },
97
+ });
98
+
99
+ eventSource.onmessage = (streammessage) => {
100
+ this._processStreamMessage(streammessage);
101
+ };
102
+
103
+ return eventSource;
104
+ }
105
+
106
+ _processStreamMessage(streammessage) {
107
+ this.#trace("_processStreamMessage()");
108
+
109
+ const messages = JSON.parse(streammessage.data);
110
+ messages.forEach((message) => {
111
+ message.data.forEach((event) => {
112
+ this._processStreamEvent(event);
113
+ });
114
+ });
115
+ }
116
+
117
+ _processStreamEvent(event) {
118
+ this.#trace("_processStreamEvent(",event,")");
119
+
120
+ if (this._isResourceRegistered(event.id)) {
121
+ this.getResource(event.id).onEvent(event);
122
+ }
123
+ }
124
+
125
+ _requestResources() {
126
+ this.#info("_requestResources()");
127
+ var instance = this;
128
+
129
+ var handleResourceList = async function(response) {
130
+ instance._processResources(response);
131
+ };
132
+
133
+ var handleError = async function(error) {
134
+ instance.#error("constructor() error:", error.message,error.stack);
135
+ }
136
+
137
+ this._restApi().get("/clip/v2/resource")
138
+ .then(handleResourceList)
139
+ .catch(handleError);
140
+ }
141
+
142
+ _processResources(response) {
143
+ this.#info("_processResources()");
144
+
145
+ response.data.forEach((item) => {
146
+ this.#trace("constructor() found resource:", item);
147
+
148
+ if (Object.keys(this.#factory).includes(item.type)) {
149
+ if (!this._isResourceRegistered(item.id)) {
150
+ var resource = new this.#factory[item.type](item,this);
151
+ this._registerResource(resource);
152
+ }
153
+ } else {
154
+ this.#warn("constructor(): Missing factory for type", item.type);
155
+ }
156
+ });
157
+
158
+ this.#startQ.forEach(resource => resource.start(this.#resources[resource.rid()]));
159
+ this.#isStarted = true;
160
+ this.#startQ = null;
161
+ }
162
+
163
+ _isResourceRegistered(uuid) {
164
+ return Object.keys(this.#resources).includes(uuid);
165
+ }
166
+
167
+ _registerResource(resource) {
168
+ this.#trace("#registerResource(",resource.id(),")");
169
+ this.#resources[resource.rid()] = resource;
170
+ }
171
+
172
+ _unregisterResource(resource) {
173
+ this.#trace("#unregisterResource()");
174
+ delete(this.#resources[resource.rid()]);
175
+ }
176
+
177
+ _stopEventStream() {
178
+ this.#info("_stopEventStream()");
179
+ if (this.#eventSource != null) {
180
+ this.#eventSource.onmessage = null;
181
+ this.#eventSource.close();
182
+ };
183
+
184
+ this.#eventSource = null;
185
+ }
186
+
187
+ requestStartup(resource) {
188
+ this.#trace("requestStartup()");
189
+ if (this.#isStarted) {
190
+ resource.start();
191
+ } else {
192
+ this.#startQ.push(resource);
193
+ }
194
+ }
195
+
196
+ destructor() {
197
+ this.#info("destructor()");
198
+ this.emit('stopped');
199
+ this.removeAllListeners();
200
+ if (this.#restAPI) {
201
+ this.#restAPI.destructor();
202
+ this.#restAPI = null;
203
+ }
204
+ this._stopEventStream();
205
+
206
+ var instance = this;
207
+ Object.keys(this.#resources).forEach((id) => {
208
+ var resource = instance.resource[id];
209
+ instance._unregisterResource(resource);
210
+ resource.destructor();
211
+ });
212
+
213
+ this.resources = null;
214
+ }
215
+
216
+ getResource(rid) {
217
+ if (this._isResourceRegistered(rid)) {
218
+ return this.#resources[rid];
219
+ } else {
220
+ this.#warn("getResource(",rid,"): Resource not found.");
221
+ }
222
+ }
223
+
224
+ get(rtype,rid) {
225
+ this.#trace("get(",rtype,",",rid,")");
226
+ return this._restApi().get("/clip/v2/resource/" + rtype + "/" + rid);
227
+ }
228
+
229
+ put(rtype,rid,data) {
230
+ this.#trace("put(",rtype,",",rid,",",data,")");
231
+ return this._restApi().put("/clip/v2/resource/" + rtype + "/" + rid, data);
232
+ }
233
+
234
+ post(rtype,rid,data) {
235
+ this.#trace("post(",rtype,",",rid,",",data,")");
236
+ return this._restApi().post("/clip/v2/resource/" + rtype + "/" + rid, data);
237
+ }
238
+
239
+ delete(rtype,rid,data) {
240
+ this.#trace("delete(",rtype,",",rid,",",data,")");
241
+ return this._restApi().delete("/clip/v2/resource/" + rtype + "/" + rid, data);
242
+ }
243
+
244
+ getSortedResourcesByTypeAndModel(type,models) {
245
+ this.#trace("getSortedResourcesByTypeAndModel(",type,",",models,")");
246
+ var instance = this;
247
+ var keys = Object.keys(instance.#resources).filter(function(key) {
248
+ var value = instance.#resources[key];
249
+ return ((value.rtype() == type))
250
+ });
251
+
252
+ var result = []
253
+ keys.forEach((key) => {
254
+ result.push(instance.#resources[key]);
255
+ });
256
+
257
+ result.sort(function (a, b) {
258
+ if (a.id() > b.id()) return 1;
259
+ if (a.id() < b.id()) return -1;
260
+ return 0;
261
+ });
262
+
263
+ //this.#trace("getSortedResourcesByTypeAndModel(",type,",",models,") options:",options);
264
+ return result.filter(function(resource) {
265
+ if (models) {
266
+ if ((resource.data().product_data) && (resource.data().product_data.model_id)) {
267
+ return models.includes(resource.data().product_data.model_id);
268
+ } else {
269
+ return false;
270
+ }
271
+ } else {
272
+ return true;
273
+ }
274
+ });
275
+ }
276
+
277
+ getSortedResourceOptions(type, models) {
278
+ this.#trace("getSortedOptions(" + type + "," + models + ")");
279
+
280
+ var options = [];
281
+ var resources = this.getSortedResourcesByTypeAndModel(type,models);
282
+ resources.forEach((resource) => {
283
+ options.push({ value: resource.rid(), label: (resource.name()? resource.name() : resource.id()) });
284
+ });
285
+
286
+ options.sort(function (a, b) {
287
+ if (a.label > b.label) return 1;
288
+ if (a.label < b.label) return -1;
289
+ return 0;
290
+ });
291
+
292
+ this.#trace("getSortedOptions(" + type + "," + models + ") options:",options);
293
+ return options;
294
+ }
295
+
296
+ getSortedTypeOptions() {
297
+ this.#trace("getSortedTypeOptions()");
298
+ var options = [];
299
+ var rtypes = [];
300
+ Object.values(this.#resources).forEach((resource)=>{
301
+ if (resource.owner()) {
302
+ if (!rtypes.includes(resource.rtype())) {
303
+ rtypes.push(resource.rtype());
304
+ options.push({ value: resource.rtype(), label: resource.rtype()});
305
+ }
306
+ }
307
+ });
308
+
309
+ options.sort(function (a, b) {
310
+ if (a.label > b.label) return 1;
311
+ return -1;
312
+ });
313
+
314
+ this.#trace("getSortedTypeOptions() options:",options);
315
+ return options;
316
+ }
317
+
318
+ getSortedOwnerOptions(rtype) {
319
+ this.#trace("getSortedOwnerOptions(" + rtype + ")");
320
+
321
+ var options = [];
322
+ var rids = [];
323
+ Object.values(this.#resources).forEach((resource)=>{
324
+ if (resource.services) {
325
+ Object.values(resource.services()).forEach((service) => {
326
+ if ((service.rtype() == rtype) && (!rids.includes(resource.rid()))) {
327
+ rids.push(resource.rid());
328
+ options.push({ value: resource.rid(), label: resource.name() });
329
+ }
330
+ });
331
+ }
332
+ });
333
+
334
+ options.sort(function (a, b) {
335
+ if (a.label > b.label) return 1;
336
+ if (a.label < b.label) return -1;
337
+ return 0;
338
+ });
339
+
340
+ this.#trace("getSortedOwnerOptions(" + rtype + ") options:",options);
341
+ return options;
342
+ }
343
+
344
+ getSortedServiceOptions(uuid,rtype) {
345
+ this.#trace("getSortedServiceOptions("+ uuid + "," + rtype + ")");
346
+
347
+ var options = [];
348
+ var owner = this.#resources[uuid];
349
+ var services = owner.services();
350
+ Object.values(services).forEach((service) => {
351
+ if (service.rtype() == rtype) {
352
+ options.push({ value: service.rid(), label: service.typeName() });
353
+ }
354
+ });
355
+
356
+ options.sort(function (a, b) {
357
+ if (a.label > b.label) return 1;
358
+ if (a.label < b.label) return -1;
359
+ return 0;
360
+ });
361
+
362
+ this.#trace("getSortedServiceOptions("+ uuid + "," + rtype + ") options:",options);
363
+ return options;
364
+ }
365
+ }
366
+
367
+ module.exports = ClipApi