@bldgblocks/node-red-contrib-control 0.1.33 → 0.1.36

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 (113) hide show
  1. package/nodes/accumulate-block.html +18 -8
  2. package/nodes/accumulate-block.js +39 -44
  3. package/nodes/add-block.html +1 -1
  4. package/nodes/add-block.js +18 -11
  5. package/nodes/alarm-collector.html +260 -0
  6. package/nodes/alarm-collector.js +292 -0
  7. package/nodes/alarm-config.html +129 -0
  8. package/nodes/alarm-config.js +126 -0
  9. package/nodes/alarm-service.html +96 -0
  10. package/nodes/alarm-service.js +142 -0
  11. package/nodes/analog-switch-block.js +25 -36
  12. package/nodes/and-block.js +44 -15
  13. package/nodes/average-block.js +46 -41
  14. package/nodes/boolean-switch-block.js +10 -28
  15. package/nodes/boolean-to-number-block.html +18 -5
  16. package/nodes/boolean-to-number-block.js +24 -16
  17. package/nodes/cache-block.js +24 -37
  18. package/nodes/call-status-block.html +91 -32
  19. package/nodes/call-status-block.js +398 -115
  20. package/nodes/changeover-block.html +5 -0
  21. package/nodes/changeover-block.js +167 -162
  22. package/nodes/comment-block.html +1 -1
  23. package/nodes/comment-block.js +14 -9
  24. package/nodes/compare-block.html +14 -4
  25. package/nodes/compare-block.js +23 -18
  26. package/nodes/contextual-label-block.html +5 -0
  27. package/nodes/contextual-label-block.js +6 -16
  28. package/nodes/convert-block.html +25 -39
  29. package/nodes/convert-block.js +31 -16
  30. package/nodes/count-block.html +11 -5
  31. package/nodes/count-block.js +34 -32
  32. package/nodes/delay-block.js +58 -53
  33. package/nodes/divide-block.js +43 -45
  34. package/nodes/edge-block.html +17 -10
  35. package/nodes/edge-block.js +43 -41
  36. package/nodes/enum-switch-block.js +6 -6
  37. package/nodes/frequency-block.html +6 -1
  38. package/nodes/frequency-block.js +64 -74
  39. package/nodes/global-getter.html +51 -15
  40. package/nodes/global-getter.js +74 -67
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +168 -188
  43. package/nodes/history-buffer.html +96 -0
  44. package/nodes/history-buffer.js +461 -0
  45. package/nodes/history-collector.html +29 -1
  46. package/nodes/history-collector.js +37 -16
  47. package/nodes/history-config.html +13 -1
  48. package/nodes/history-service.html +84 -0
  49. package/nodes/history-service.js +52 -0
  50. package/nodes/hysteresis-block.html +5 -0
  51. package/nodes/hysteresis-block.js +13 -16
  52. package/nodes/interpolate-block.html +20 -2
  53. package/nodes/interpolate-block.js +39 -50
  54. package/nodes/join.html +78 -0
  55. package/nodes/join.js +78 -0
  56. package/nodes/latch-block.js +12 -14
  57. package/nodes/load-sequence-block.js +102 -110
  58. package/nodes/max-block.js +26 -26
  59. package/nodes/memory-block.js +57 -58
  60. package/nodes/min-block.js +26 -25
  61. package/nodes/minmax-block.js +35 -34
  62. package/nodes/modulo-block.js +45 -43
  63. package/nodes/multiply-block.js +43 -41
  64. package/nodes/negate-block.html +17 -7
  65. package/nodes/negate-block.js +25 -19
  66. package/nodes/network-point-read.html +128 -0
  67. package/nodes/network-point-read.js +230 -0
  68. package/nodes/{network-register.html → network-point-register.html} +94 -7
  69. package/nodes/network-point-register.js +126 -0
  70. package/nodes/network-point-write.html +149 -0
  71. package/nodes/network-point-write.js +222 -0
  72. package/nodes/network-service-bridge.html +131 -0
  73. package/nodes/network-service-bridge.js +376 -0
  74. package/nodes/network-service-read.html +81 -0
  75. package/nodes/network-service-read.js +58 -0
  76. package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
  77. package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
  78. package/nodes/network-service-write.html +89 -0
  79. package/nodes/network-service-write.js +83 -0
  80. package/nodes/nullify-block.js +13 -15
  81. package/nodes/on-change-block.html +17 -9
  82. package/nodes/on-change-block.js +49 -46
  83. package/nodes/oneshot-block.html +13 -10
  84. package/nodes/oneshot-block.js +57 -75
  85. package/nodes/or-block.js +44 -15
  86. package/nodes/pid-block.html +54 -4
  87. package/nodes/pid-block.js +459 -248
  88. package/nodes/priority-block.js +24 -35
  89. package/nodes/rate-limit-block.js +70 -72
  90. package/nodes/rate-of-change-block.html +33 -14
  91. package/nodes/rate-of-change-block.js +74 -62
  92. package/nodes/round-block.html +14 -9
  93. package/nodes/round-block.js +32 -25
  94. package/nodes/saw-tooth-wave-block.js +49 -76
  95. package/nodes/scale-range-block.html +12 -6
  96. package/nodes/scale-range-block.js +46 -39
  97. package/nodes/sine-wave-block.js +49 -57
  98. package/nodes/string-builder-block.js +6 -6
  99. package/nodes/subtract-block.js +38 -34
  100. package/nodes/thermistor-block.js +44 -44
  101. package/nodes/tick-tock-block.js +32 -32
  102. package/nodes/time-sequence-block.js +30 -42
  103. package/nodes/triangle-wave-block.js +49 -69
  104. package/nodes/tstat-block.js +34 -44
  105. package/nodes/units-block.html +90 -69
  106. package/nodes/units-block.js +22 -30
  107. package/nodes/utils.js +275 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-read.js +0 -59
  111. package/nodes/network-register.js +0 -161
  112. package/nodes/network-write.html +0 -64
  113. package/nodes/network-write.js +0 -126
@@ -1,9 +1,11 @@
1
1
  module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
2
3
  function GlobalGetterNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
  node.targetNodeId = config.targetNode;
6
7
  node.outputProperty = config.outputProperty || "payload";
8
+ node.dropdownPath = config.dropdownPath || "";
7
9
  node.updates = config.updates;
8
10
  node.detail = config.detail;
9
11
 
@@ -15,148 +17,153 @@ module.exports = function(RED) {
15
17
  const retryDelays = [0, 100, 500, 1000, 2000, 4000, 8000, 16000];
16
18
  const maxRetries = retryDelays.length - 1;
17
19
 
18
- // --- Process Wrapper and Send Msg ---
19
- function sendValue(storedObject, msgToReuse) {
20
- const msg = msgToReuse || {};
20
+ // --- Output Helper ---
21
+ function sendValue(storedObject, msgToReuse, done) {
22
+ const msg = RED.util.cloneMessage(msgToReuse) || {};
21
23
 
22
24
  if (storedObject !== undefined && storedObject !== null) {
23
-
24
- // CHECK: Is this our Wrapper Format? (Created by Global Setter)
25
+ // Check if this is our custom wrapper object
25
26
  if (storedObject && typeof storedObject === 'object' && storedObject.hasOwnProperty('value')) {
26
-
27
- // Merge all attributes onto the msg root
28
- // This automatically handles priority, units, metadata, and any future fields
29
27
  if (node.detail === "getObject") {
30
28
  Object.assign(msg, storedObject);
31
29
  }
32
-
33
- // Set the Main Output (e.g. msg.payload = 75)
34
- RED.util.setMessageProperty(msg, node.outputProperty, storedObject.value);
35
-
30
+ if (config.outputPropertyType === "flow" || config.outputPropertyType === "dropdown") {
31
+ if (config.outputProperty === "sourceToFlow") {
32
+ node.context().flow.set(node.dropdownPath, storedObject.value);
33
+ } else {
34
+ node.context().flow.set(node.outputProperty, storedObject.value);
35
+ }
36
+ } else {
37
+ RED.util.setMessageProperty(msg, node.outputProperty, storedObject.value);
38
+ }
36
39
  } else {
37
- // Handle Legacy/Raw values (not created by Setter)
38
- RED.util.setMessageProperty(msg, node.outputProperty, storedObject);
40
+ // Legacy/Raw values
41
+ if (config.outputPropertyType === "flow" || config.outputPropertyType === "dropdown") {
42
+ if (config.outputProperty === "sourceToFlow") {
43
+ node.context().flow.set(node.dropdownPath, storedObject);
44
+ } else {
45
+ node.context().flow.set(node.outputProperty, storedObject);
46
+ }
47
+ } else {
48
+ RED.util.setMessageProperty(msg, node.outputProperty, storedObject);
49
+ }
39
50
  msg.metadata = { path: setterNode ? setterNode.varName : "unknown", legacy: true };
40
51
  }
41
52
 
42
- // Update Status
43
- let valDisplay = RED.util.getMessageProperty(msg, node.outputProperty);
44
- valDisplay = typeof valDisplay === "number" ? valDisplay : valDisplay;
45
- node.status({ fill: "blue", shape: "dot", text: `get: ${valDisplay}` });
53
+ let valDisplay = storedObject.value;
54
+ if (valDisplay === null) valDisplay = "null";
55
+ else if (valDisplay === undefined) valDisplay = "undefined";
56
+ else if (typeof valDisplay === "object") valDisplay = JSON.stringify(valDisplay);
57
+ else valDisplay = typeof valDisplay === "number" ? valDisplay : valDisplay;
46
58
 
47
- node.send(msg);
48
-
59
+ // Trim to 64 characters with ellipsis
60
+ if (valDisplay.length > 64) {
61
+ valDisplay = valDisplay.substring(0, 64) + "...";
62
+ }
63
+
64
+ utils.sendSuccess(node, msg, done, `get: ${valDisplay}`, null, "dot");
49
65
  } else {
50
- node.status({ fill: "red", shape: "ring", text: "global variable undefined" });
66
+ utils.sendError(node, msg, done, "global variable undefined");
51
67
  }
52
68
  }
53
69
 
54
- // --- Manage Event Subscription ---
70
+ // --- Connection Logic ---
55
71
  function establishListener() {
56
- // Look for source node
57
72
  setterNode = RED.nodes.getNode(node.targetNodeId);
58
73
 
59
- // If found, subscribe
60
74
  if (setterNode && setterNode.varName && node.updates === 'always') {
61
75
  if (updateListener) {
62
- // Remove existing listener if we're retrying
63
- RED.events.removeListener("bldgblocks-global-update", updateListener);
76
+ RED.events.removeListener("bldgblocks:global:value-changed", updateListener);
64
77
  }
65
78
 
66
79
  updateListener = function(evt) {
67
80
  if (evt.key === setterNode.varName && evt.store === setterNode.storeName) {
68
- sendValue(evt.data, {});
81
+ // Event Trigger: Pass null for done, as it's not a node input
82
+ sendValue(evt.data, {}, null);
69
83
  }
70
84
  };
71
85
 
72
- RED.events.on("bldgblocks-global-update", updateListener);
86
+ RED.events.on("bldgblocks:global:value-changed", updateListener);
73
87
 
74
- // Clear retry interval once successful
75
88
  if (retryAction) {
76
89
  clearInterval(retryAction);
77
90
  retryAction = null;
78
91
  }
79
92
 
80
- node.status({ fill: "green", shape: "dot", text: "Connected" });
93
+ utils.setStatusOK(node, "Connected");
81
94
  return true;
82
95
  }
83
96
  return false;
84
97
  }
85
98
 
86
- // --- Maintain event subscription ---
87
99
  function startHealthCheck() {
88
- const healthCheckAction = () => {
89
- const listeners = RED.events.listeners("bldgblocks-global-update");
100
+ const check = () => {
101
+ const listeners = RED.events.listeners("bldgblocks:global:value-changed");
90
102
  const hasOurListener = listeners.includes(updateListener);
91
-
92
103
  if (!hasOurListener) {
93
104
  node.warn("Event listener lost, reconnecting...");
94
105
  if (establishListener()) {
95
- node.status({ fill: "green", shape: "dot", text: "Reconnected" });
106
+ utils.setStatusOK(node, "Reconnected");
96
107
  }
97
108
  }
98
-
99
- // Schedule next health check regardless of outcome
100
- setTimeout(healthCheckAction, 30000);
109
+ setTimeout(check, 30000);
101
110
  };
102
- // Inital start
103
- setTimeout(healthCheckAction, 30000);
111
+ setTimeout(check, 30000);
104
112
  }
105
113
 
106
114
  function subscribeWithRetry() {
107
- // Recursive retry
108
115
  retryAction = () => {
109
116
  if (retryCount >= maxRetries) {
110
- node.error("Failed to connect to setter node after multiple attempts");
111
- node.status({ fill: "red", shape: "ring", text: "Connection failed" });
117
+ utils.sendError(node, null, null, "Connection failed");
112
118
  return;
113
119
  }
114
-
115
120
  if (establishListener()) {
116
121
  retryCount = 0;
117
- return; // Success
122
+ return;
118
123
  }
119
-
120
124
  retryCount++;
121
125
  setTimeout(retryAction, retryDelays[Math.min(retryCount, maxRetries - 1)]);
122
126
  };
123
-
124
127
  setTimeout(retryAction, retryDelays[0]);
125
128
  }
126
129
 
127
- // --- HANDLE MANUAL INPUT ---
128
- node.on('input', function(msg, send, done) {
130
+ // --- INPUT HANDLER ---
131
+ node.on('input', async function(msg, send, done) {
129
132
  send = send || function() { node.send.apply(node, arguments); };
130
133
 
131
- setterNode ??= RED.nodes.getNode(node.targetNodeId);
134
+ try {
135
+ setterNode ??= RED.nodes.getNode(node.targetNodeId);
132
136
 
133
- if (setterNode && setterNode.varName) {
134
- const storedObject = node.context().global.get(setterNode.varName, setterNode.storeName);
135
- sendValue(storedObject, msg);
136
- } else {
137
- node.warn("Source node not found or not configured.");
138
- node.status({ fill: "red", shape: "ring", text: "Source node not found" });
137
+ if (setterNode && setterNode.varName) {
138
+ // Async Get - required default store to keep values in memory for polled getter nodes.
139
+ // 'persistent' for cross reboot storage.
140
+ let storedObject = await utils.getGlobalState(node, setterNode.varName, 'default');
141
+ if (!storedObject) {
142
+ // Fallback to persistent store if not found in default. Should not happen normally.
143
+ storedObject = await utils.getGlobalState(node, setterNode.varName, setterNode.storeName);
144
+ }
145
+ sendValue(storedObject, msg, done);
146
+ } else {
147
+ node.warn("Source node not found or not configured.");
148
+ utils.sendError(node, msg, done, "Source node not found");
149
+ }
150
+ } catch (err) {
151
+ node.error(err);
152
+ utils.sendError(node, msg, done, `Internal Error: ${err.message}`);
139
153
  }
140
-
141
- if (done) done();
142
154
  });
143
155
 
144
- // --- HANDLE REACTIVE UPDATES ---
156
+ // --- INIT ---
145
157
  if (node.updates === 'always') {
146
158
  subscribeWithRetry();
147
159
  startHealthCheck();
148
160
  }
149
161
 
150
- // --- CLEANUP ---
151
162
  node.on('close', function(removed, done) {
152
- if (healthCheckAction) {
153
- clearInterval(healthCheckAction);
154
- }
155
- if (retryAction) {
156
- clearInterval(retryAction);
157
- }
163
+ if (healthCheckAction) clearInterval(healthCheckAction);
164
+ if (retryAction) clearInterval(retryAction);
158
165
  if (removed && updateListener) {
159
- RED.events.removeListener("bldgblocks-global-update", updateListener);
166
+ RED.events.removeListener("bldgblocks:global:value-changed", updateListener);
160
167
  }
161
168
  done();
162
169
  });
@@ -5,7 +5,7 @@
5
5
  </div>
6
6
  <div class="form-row">
7
7
  <label for="node-input-path"><i class="fa fa-sitemap"></i> Global Path</label>
8
- <input type="text" id="node-input-path" style="width: 70%;" placeholder="furnace/outputs/heat">
8
+ <input type="text" id="node-input-path" placeholder="furnace/outputs/heat">
9
9
  </div>
10
10
  <div class="form-row">
11
11
  <label for="node-input-property"><i class="fa fa-ellipsis-h"></i> Input</label>