@bldgblocks/node-red-contrib-control 0.1.34 → 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 (110) 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 +43 -13
  41. package/nodes/global-setter.html +1 -1
  42. package/nodes/global-setter.js +40 -12
  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-register.js → network-point-register.js} +18 -4
  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-read.js → network-service-read.js} +4 -3
  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-write.js → network-service-write.js} +3 -3
  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 +206 -3
  108. package/package.json +14 -6
  109. package/nodes/network-read.html +0 -56
  110. package/nodes/network-write.html +0 -65
@@ -0,0 +1,376 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+
4
+ function NetworkServiceBridgeNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+
8
+ // ====================================================================
9
+ // Initialize configuration
10
+ // ====================================================================
11
+ node.startupDelay = parseInt(config.startupDelay) || 30; // Delay in seconds
12
+ node.startupTime = Date.now(); // Track when node was deployed
13
+ node.startupComplete = false;
14
+
15
+ // ====================================================================
16
+ // Initialize state
17
+ // ====================================================================
18
+ node.pendingRequests = {}; // Track outstanding requests: { "pointId_timestamp": { pointId, timestamp, ... } }
19
+ node.stats = {
20
+ sent: 0,
21
+ received: 0
22
+ };
23
+
24
+ // ====================================================================
25
+ // Helper: Generate request ID
26
+ // ====================================================================
27
+ const generateRequestId = function(pointId) {
28
+ return `${pointId}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
29
+ };
30
+
31
+ // ====================================================================
32
+ // Helper: Update status
33
+ // ====================================================================
34
+ const updateStatus = function() {
35
+ // Check startup delay
36
+ if (!node.startupComplete) {
37
+ const elapsedSeconds = (Date.now() - node.startupTime) / 1000;
38
+ if (elapsedSeconds < node.startupDelay) {
39
+ const remainingSeconds = Math.ceil(node.startupDelay - elapsedSeconds);
40
+ utils.setStatusWarn(node, `Startup delay: ${remainingSeconds}s remaining...`);
41
+ return;
42
+ } else {
43
+ node.startupComplete = true;
44
+ }
45
+ }
46
+
47
+ const pending = Object.keys(node.pendingRequests).length;
48
+ const successRate = node.stats.sent > 0
49
+ ? Math.round((node.stats.received / node.stats.sent) * 100)
50
+ : 0;
51
+
52
+ const statusText = `Listening (pending: ${pending}, success: ${successRate}%)`;
53
+
54
+ if (pending === 0) {
55
+ utils.setStatusOK(node, statusText);
56
+ } else {
57
+ utils.setStatusUnchanged(node, statusText);
58
+ }
59
+ };
60
+
61
+ // ====================================================================
62
+ // Listen for read requests from point-reference nodes via event
63
+ // ====================================================================
64
+ const readRequestHandler = function(data) {
65
+ // Only process requests meant for this bridge
66
+ if (data.bridgeNodeId !== node.id) {
67
+ return;
68
+ }
69
+
70
+ // ================================================================
71
+ // Track if request is during startup phase (for error suppression)
72
+ // ================================================================
73
+ let isStartupPhase = false;
74
+ if (!node.startupComplete) {
75
+ const elapsedSeconds = (Date.now() - node.startupTime) / 1000;
76
+ if (elapsedSeconds < node.startupDelay) {
77
+ isStartupPhase = true;
78
+ const remainingSeconds = Math.ceil(node.startupDelay - elapsedSeconds);
79
+ utils.setStatusWarn(node, `Startup delay: ${remainingSeconds}s - allowing retries...`);
80
+ } else {
81
+ node.startupComplete = true;
82
+ updateStatus();
83
+ }
84
+ }
85
+
86
+ // Track this cross-flow request
87
+ node.pendingRequests[data.requestId] = {
88
+ requestId: data.requestId,
89
+ pointId: data.pointId,
90
+ timestamp: Date.now(),
91
+ sourceNodeId: data.sourceNodeId, // Where to send response
92
+ isStartupPhase: isStartupPhase // Mark if during startup (errors suppressed)
93
+ };
94
+
95
+ // Create outbound message for WebSocket
96
+ const outMsg = {
97
+ action: "read",
98
+ pointId: data.pointId,
99
+ requestId: data.requestId,
100
+ timestamp: Date.now()
101
+ };
102
+
103
+ // Send through WebSocket
104
+ node.send(outMsg);
105
+
106
+ node.stats.sent++;
107
+ updateStatus();
108
+
109
+ // Timeout: if no response after 10 seconds, clean up
110
+ setTimeout(() => {
111
+ if (node.pendingRequests[data.requestId]) {
112
+ const pending = node.pendingRequests[data.requestId];
113
+ delete node.pendingRequests[data.requestId];
114
+
115
+ // Suppress error notification during startup phase
116
+ // (allows network to come online without nuisance errors)
117
+ if (pending.isStartupPhase) {
118
+ // Silently drop timeout during startup
119
+ return;
120
+ }
121
+
122
+ const errorText = `Read timeout for point #${data.pointId}`;
123
+ utils.setStatusWarn(node, errorText);
124
+ node.error(errorText); // Show in debug panel
125
+
126
+ // Notify point-reference of timeout
127
+ RED.events.emit('pointReference:response', {
128
+ sourceNodeId: data.sourceNodeId,
129
+ pointId: data.pointId,
130
+ value: null,
131
+ error: true,
132
+ errorMessage: "Read timeout",
133
+ requestId: data.requestId
134
+ });
135
+ }
136
+ }, 10000);
137
+ };
138
+
139
+ RED.events.on('pointReference:read', readRequestHandler);
140
+
141
+ // ====================================================================
142
+ // Listen for write requests from point-write nodes via event
143
+ // ====================================================================
144
+ const writeRequestHandler = function(data) {
145
+ // Only process requests meant for this bridge
146
+ if (data.bridgeNodeId !== node.id) {
147
+ return;
148
+ }
149
+
150
+ // ================================================================
151
+ // Track if request is during startup phase (for error suppression)
152
+ // ================================================================
153
+ let isStartupPhase = false;
154
+ if (!node.startupComplete) {
155
+ const elapsedSeconds = (Date.now() - node.startupTime) / 1000;
156
+ if (elapsedSeconds < node.startupDelay) {
157
+ isStartupPhase = true;
158
+ const remainingSeconds = Math.ceil(node.startupDelay - elapsedSeconds);
159
+ utils.setStatusWarn(node, `Startup delay: ${remainingSeconds}s - allowing retries...`);
160
+ } else {
161
+ node.startupComplete = true;
162
+ updateStatus();
163
+ }
164
+ }
165
+
166
+ // Track this cross-flow request
167
+ node.pendingRequests[data.requestId] = {
168
+ requestId: data.requestId,
169
+ pointId: data.pointId,
170
+ timestamp: Date.now(),
171
+ sourceNodeId: data.sourceNodeId,
172
+ isWrite: true,
173
+ isStartupPhase: isStartupPhase // Mark if during startup (errors suppressed)
174
+ };
175
+
176
+ // Create outbound message for WebSocket
177
+ const outMsg = {
178
+ action: "write",
179
+ pointId: data.pointId,
180
+ priority: data.priority,
181
+ value: data.value,
182
+ requestId: data.requestId,
183
+ timestamp: Date.now()
184
+ };
185
+
186
+ // Send through WebSocket
187
+ node.send(outMsg);
188
+
189
+ node.stats.sent++;
190
+ updateStatus();
191
+
192
+ // Timeout: if no response after 10 seconds, clean up
193
+ setTimeout(() => {
194
+ if (node.pendingRequests[data.requestId]) {
195
+ const pending = node.pendingRequests[data.requestId];
196
+ delete node.pendingRequests[data.requestId];
197
+
198
+ // Suppress error notification during startup phase
199
+ // (allows network to come online without nuisance errors)
200
+ if (pending.isStartupPhase) {
201
+ // Silently drop timeout during startup
202
+ return;
203
+ }
204
+
205
+ const errorText = `Write timeout for point #${data.pointId}`;
206
+ utils.setStatusWarn(node, errorText);
207
+ node.error(errorText); // Show in debug panel
208
+
209
+ // Notify point-write of timeout
210
+ RED.events.emit('pointWrite:response', {
211
+ sourceNodeId: pending.sourceNodeId,
212
+ pointId: data.pointId,
213
+ error: "Write timeout",
214
+ requestId: data.requestId
215
+ });
216
+ }
217
+ }, 10000);
218
+ };
219
+
220
+ RED.events.on('pointWrite:write', writeRequestHandler);
221
+
222
+ // ====================================================================
223
+ // Main message handler - ONLY processes WebSocket responses
224
+ // Read/write requests come via events, not wired input
225
+ // ====================================================================
226
+ node.on("input", function(msg, send, done) {
227
+ send = send || function() { node.send.apply(node, arguments); };
228
+
229
+ // Guard against invalid msg
230
+ if (!msg) {
231
+ utils.setStatusError(node, "invalid message");
232
+ if (done) done();
233
+ return;
234
+ }
235
+
236
+ // ================================================================
237
+ // Handle response from WebSocket (network-read returns data object)
238
+ // Response has: network.pointId, value (or payload), status.code
239
+ // Route back to point-reference via event
240
+ // ================================================================
241
+
242
+ // Check if this looks like a point response (has network.pointId or status.pointId)
243
+ const responsePointId = msg.network?.pointId ?? msg.status?.pointId ?? msg.pointId;
244
+ const responseValue = msg.value ?? msg.payload;
245
+ const statusCode = msg.status?.code;
246
+ const statusMessage = msg.status?.message || "";
247
+ const isError = statusCode === "error";
248
+
249
+ // Valid response if we have a pointId (value can be null/undefined on error)
250
+ const isValidResponse = responsePointId !== undefined;
251
+
252
+ if (isValidResponse) {
253
+ // Find ALL matching pending requests by pointId
254
+ // Multiple nodes might be waiting for the same point
255
+ const matchingRequests = [];
256
+
257
+ for (const [reqId, reqData] of Object.entries(node.pendingRequests)) {
258
+ if (reqData.pointId === responsePointId) {
259
+ matchingRequests.push({
260
+ requestId: reqId,
261
+ sourceNodeId: reqData.sourceNodeId,
262
+ isWrite: reqData.isWrite,
263
+ isStartupPhase: reqData.isStartupPhase // Preserve startup phase flag
264
+ });
265
+ }
266
+ }
267
+
268
+ if (matchingRequests.length > 0) {
269
+ // Remove all matched requests from pending BEFORE notifying
270
+ // (prevents race conditions if notification triggers new requests)
271
+ for (const match of matchingRequests) {
272
+ delete node.pendingRequests[match.requestId];
273
+ }
274
+
275
+ // Now notify all waiting nodes
276
+ for (const match of matchingRequests) {
277
+ const eventName = match.isWrite ? 'pointWrite:response' : 'pointReference:response';
278
+
279
+ if (isError) {
280
+ RED.events.emit(eventName, {
281
+ sourceNodeId: match.sourceNodeId,
282
+ pointId: responsePointId,
283
+ value: null,
284
+ error: match.isWrite ? statusMessage : true,
285
+ errorMessage: statusMessage,
286
+ requestId: match.requestId,
287
+ timestamp: Date.now(),
288
+ isStartupPhase: match.isStartupPhase // Pass startup phase flag
289
+ });
290
+ } else {
291
+ // Success response
292
+ node.stats.received++;
293
+
294
+ RED.events.emit(eventName, {
295
+ sourceNodeId: match.sourceNodeId,
296
+ pointId: responsePointId,
297
+ value: responseValue,
298
+ message: msg,
299
+ error: match.isWrite ? null : false,
300
+ requestId: match.requestId,
301
+ timestamp: msg.timestamp || Date.now(),
302
+ isStartupPhase: match.isStartupPhase // Pass startup phase flag
303
+ });
304
+ }
305
+ }
306
+
307
+ // Update status once after all notifications
308
+ if (isError) {
309
+ const errorText = `Error for #${responsePointId}: ${statusMessage}`;
310
+ utils.setStatusWarn(node, errorText);
311
+ node.error(errorText); // Show in debug panel
312
+ } else {
313
+ updateStatus();
314
+ }
315
+ } else {
316
+ // Response without matching request - could be stale or unsolicited
317
+ utils.setStatusWarn(node, `Unmatched response for point #${responsePointId}`);
318
+ }
319
+
320
+ if (done) done();
321
+ return;
322
+ }
323
+
324
+ // ================================================================
325
+ // Handle statistics/status queries
326
+ // ================================================================
327
+ if (msg.action === "getBridgeStats") {
328
+ const statsMsg = {
329
+ action: "bridgeStats",
330
+ stats: node.stats,
331
+ pendingCount: Object.keys(node.pendingRequests).length,
332
+ pending: Object.keys(node.pendingRequests)
333
+ };
334
+ send(statsMsg);
335
+ if (done) done();
336
+ return;
337
+ }
338
+
339
+ if (msg.action === "clearPending") {
340
+ node.pendingRequests = {};
341
+ utils.setStatusOK(node, "Pending requests cleared");
342
+ if (done) done();
343
+ return;
344
+ }
345
+
346
+ if (msg.action === "resetStats") {
347
+ node.stats = { sent: 0, received: 0 };
348
+ utils.setStatusOK(node, "Stats reset");
349
+ if (done) done();
350
+ return;
351
+ }
352
+
353
+ // Unknown action - pass through anyway (could be for other nodes)
354
+ if (done) done();
355
+ });
356
+
357
+ // ====================================================================
358
+ // Node lifecycle
359
+ // ====================================================================
360
+ node.on("close", function(done) {
361
+ // Clear pending requests on close
362
+ node.pendingRequests = {};
363
+ // Remove event listeners
364
+ RED.events.off('pointReference:read', readRequestHandler);
365
+ RED.events.off('pointWrite:write', writeRequestHandler);
366
+ done();
367
+ });
368
+
369
+ // ====================================================================
370
+ // Initialize
371
+ // ====================================================================
372
+ updateStatus();
373
+ }
374
+
375
+ RED.nodes.registerType("network-service-bridge", NetworkServiceBridgeNode);
376
+ };
@@ -0,0 +1,81 @@
1
+ <script type="text/html" data-template-name="network-service-read">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Name">
5
+ </div>
6
+
7
+ <div class="form-row">
8
+ <label for="node-input-registry"><i class="fa fa-book"></i> Registry</label>
9
+ <input type="text" id="node-input-registry">
10
+ </div>
11
+
12
+ <div class="form-tips">
13
+ <b>Input Payload Format:</b><br>
14
+ <pre>
15
+ {
16
+ "action": "read",
17
+ "pointId": 101
18
+ }
19
+ </pre>
20
+ </div>
21
+ </script>
22
+
23
+ <script type="text/javascript">
24
+ RED.nodes.registerType('network-service-read', {
25
+ category: 'bldgblocks network',
26
+ color: '#3090C7',
27
+ defaults: {
28
+ name: { value: "" },
29
+ registry: { value: "", type: "network-service-registry", required: true }
30
+ },
31
+ inputs: 1,
32
+ outputs: 1,
33
+ icon: "font-awesome/fa-database",
34
+ label: function() {
35
+ return "network service read";
36
+ },
37
+ paletteLabel: "network service read",
38
+ oneditprepare: function() {
39
+ const nodeId = this.id;
40
+ }
41
+ });
42
+ </script>
43
+
44
+ <script type="text/markdown" data-help-name="network-service-read">
45
+ Reads a network point value from the service registry.
46
+
47
+ Looks up global variable path corresponding to pointId and returns the current value.
48
+
49
+ ### Inputs
50
+ : action (string) : Command - read to retrieve point value.
51
+ : pointId (number) : The integer ID of the point to read.
52
+ : payload (object) : Message object (optional, can include context).
53
+
54
+ ### Outputs
55
+ : payload (object) : Value of the referenced global variable.
56
+ : pointId (number) : Echo of requested point ID.
57
+ : status (string) : ok or error status code.
58
+ : message (string) : Status message describing result.
59
+
60
+ ### Details
61
+ Registry Lookup Process:
62
+ 1. Receives pointId in input message
63
+ 2. Queries Network Service Registry for point metadata (path, store)
64
+ 3. Fetches value from global context using path/store
65
+ 4. Returns value in payload
66
+
67
+ Use With:
68
+ - network-service-registry - Central point registry config
69
+ - network-service-bridge - Remote point access
70
+
71
+ ### Status
72
+ - Green (dot): Configuration update
73
+ - Blue (dot): State changed
74
+ - Blue (ring): State unchanged
75
+ - Red (ring): Error
76
+ - Yellow (ring): Warning
77
+
78
+ ### References
79
+ - [Node-RED Documentation](https://nodered.org/docs/)
80
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
81
+ </script>
@@ -1,6 +1,7 @@
1
1
  module.exports = function(RED) {
2
2
  const utils = require('./utils')(RED);
3
- function NetworkReadNode(config) {
3
+
4
+ function NetworkServiceReadNode(config) {
4
5
  RED.nodes.createNode(this, config);
5
6
  const node = this;
6
7
  node.registry = RED.nodes.getNode(config.registry);
@@ -10,7 +11,7 @@ module.exports = function(RED) {
10
11
 
11
12
  try {
12
13
  if (!node.registry) {
13
- node.status({ fill: "red", shape: "ring", text: "Registry missing" });
14
+ utils.setStatusError(node, "Registry missing");
14
15
  if (done) done();
15
16
  return;
16
17
  }
@@ -53,5 +54,5 @@ module.exports = function(RED) {
53
54
  done();
54
55
  });
55
56
  }
56
- RED.nodes.registerType("network-read", NetworkReadNode);
57
+ RED.nodes.registerType("network-service-read", NetworkServiceReadNode);
57
58
  }
@@ -1,4 +1,4 @@
1
- <script type="text/html" data-template-name="network-point-registry">
1
+ <script type="text/html" data-template-name="network-service-registry">
2
2
  <div class="form-row">
3
3
  <label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
4
4
  <input type="text" id="node-config-input-name" placeholder="Main Registry">
@@ -24,7 +24,7 @@
24
24
  </script>
25
25
 
26
26
  <script type="text/javascript">
27
- RED.nodes.registerType('network-point-registry', {
27
+ RED.nodes.registerType("network-service-registry", {
28
28
  category: 'config',
29
29
  defaults: {
30
30
  name: { value: "" }
@@ -75,12 +75,27 @@
75
75
  });
76
76
  </script>
77
77
 
78
- <script type="text/markdown" data-help-name="network-point-registry">
79
- Central database mapping Point IDs to Global Variables.
78
+ <script type="text/markdown" data-help-name="network-service-registry">
79
+ Map Point IDs to global variables.
80
80
 
81
81
  ### Details
82
+ Maintains the mapping between integer Point IDs and global variable paths. This registry is used by network-read, network-write, and network-register nodes to decouple access by ID from variable path implementation details.
83
+
84
+ Create a single registry for your network of nodes and reference it from each network-register node.
82
85
 
83
86
  **API for Developers:**
84
87
  * `register(id, meta)`: Claim an ID.
85
88
  * `lookup(id)`: Find the path/store for an ID.
89
+
90
+ ### Status
91
+ - Green (dot): Configuration update
92
+ - Blue (dot): State changed
93
+ - Blue (ring): State unchanged
94
+ - Red (ring): Error
95
+ - Yellow (ring): Warning
96
+
97
+ ### References
98
+ - [Node-RED Documentation](https://nodered.org/docs/)
99
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
100
+
86
101
  </script>
@@ -1,8 +1,13 @@
1
1
  module.exports = function(RED) {
2
- function NetworkPointRegistryNode(config) {
2
+
3
+ function NetworkServiceRegistryNode(config) {
3
4
  RED.nodes.createNode(this, config);
4
5
  const node = this;
5
6
 
7
+ // Register this registry with utils for global lookup
8
+ const utils = require('./utils')(RED);
9
+ utils.registerRegistryNode(node);
10
+
6
11
  // The Map: { 101: { nodeId: "abc.123", writable: true, ... } }
7
12
  node.points = new Map();
8
13
 
@@ -34,7 +39,7 @@ module.exports = function(RED) {
34
39
  return node.points.get(parseInt(pointId));
35
40
  };
36
41
  }
37
- RED.nodes.registerType("network-point-registry", NetworkPointRegistryNode);
42
+ RED.nodes.registerType("network-service-registry", NetworkServiceRegistryNode);
38
43
 
39
44
  // --- HTTP Endpoint for Editor Validation ---
40
45
  // Route: /network-point-registry/check/<RegistryID>/<PointID>/<CurrentNodeID>
@@ -0,0 +1,89 @@
1
+ <script type="text/html" data-template-name="network-service-write">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Name">
5
+ </div>
6
+ <div class="form-row">
7
+ <label for="node-input-registry"><i class="fa fa-book"></i> Registry</label>
8
+ <input type="text" id="node-input-registry">
9
+ </div>
10
+
11
+ <div class="form-tips">
12
+ <b>Input Payload Format:</b><br>
13
+ <pre>
14
+ {
15
+ "action": "write",
16
+ "pointId": 101,
17
+ "priority": 8,
18
+ "value": 75.5 // or null || "null" to release
19
+ }
20
+ </pre>
21
+ </div>
22
+ </script>
23
+
24
+ <script type="text/javascript">
25
+ RED.nodes.registerType('network-service-write', {
26
+ category: 'bldgblocks network',
27
+ color: '#3090C7',
28
+ defaults: {
29
+ name: { value: "" },
30
+ registry: { value: "", type: "network-service-registry", required: true }
31
+ },
32
+ inputs: 1,
33
+ outputs: 1,
34
+ icon: "font-awesome/fa-pencil",
35
+ label: function() {
36
+ return this.name || "network service write";
37
+ },
38
+ paletteLabel: "network service write",
39
+ oneditprepare: function() {
40
+
41
+ }
42
+ });
43
+ </script>
44
+
45
+ <script type="text/markdown" data-help-name="network-service-write">
46
+ Writes network commands to global variables using priority array logic.
47
+
48
+ Acts as inbound gateway, handling priority write arbitration and global state synchronization.
49
+
50
+ ### Inputs
51
+ : payload (object) : Command object with `action`, `pointId`, `priority`, `value`.
52
+ : action (string) : Command type - write for standard priority write.
53
+ : pointId (number) : The integer ID of the point target.
54
+ : priority (number) : Priority level (1-16) to write to, or 0 for release.
55
+ : value (any) : Value to set at priority level, or null to relinquish.
56
+
57
+ ### Outputs
58
+ : payload (object) : Confirmation object with status, pointId, winner value.
59
+ : pointId (number) : Point ID of write target.
60
+ : priority (number) : Priority level that was written.
61
+ : winner (any) : Current highest-priority active value.
62
+ : timestamp (number) : Write timestamp (milliseconds since epoch).
63
+ : status (string) : ok or error status.
64
+
65
+ ### Details
66
+ Priority Write Process:
67
+ 1. Looks up pointId in Registry for global variable path
68
+ 2. Fetches current state object from global context
69
+ 3. Updates specific slot in priority array
70
+ 4. Recalculates Present Value (highest priority active)
71
+ 5. Saves global variable and emits update event
72
+ 6. Triggers reactive getters immediately
73
+
74
+ Priority Rules:
75
+ - Level 1-16: Explicit priorities, higher wins
76
+ - Level 0: Release (relinquish) this priority
77
+ - null value: Clear the slot
78
+
79
+ ### Status
80
+ - Green (dot): Write successful
81
+ - Blue (dot): State changed
82
+ - Blue (ring): No change
83
+ - Red (ring): Error
84
+ - Yellow (ring): Warning
85
+
86
+ ### References
87
+ - [Node-RED Documentation](https://nodered.org/docs/)
88
+ - [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
89
+ </script>
@@ -1,6 +1,6 @@
1
1
  module.exports = function(RED) {
2
2
  const utils = require('./utils')(RED);
3
- function NetworkWriteNode(config) {
3
+ function NetworkServiceWriteNode(config) {
4
4
  RED.nodes.createNode(this, config);
5
5
  const node = this;
6
6
  node.registry = RED.nodes.getNode(config.registry);
@@ -69,7 +69,7 @@ module.exports = function(RED) {
69
69
 
70
70
  msg = { ...state, status: null };
71
71
 
72
- RED.events.emit("bldgblocks-global-update", { key: path, store: store, data: state });
72
+ RED.events.emit("bldgblocks:global:value-changed", { key: path, store: store, data: state });
73
73
 
74
74
  utils.sendSuccess(node, msg, done, statusMsg, msg.pointId, "ring");
75
75
 
@@ -79,5 +79,5 @@ module.exports = function(RED) {
79
79
  }
80
80
  });
81
81
  }
82
- RED.nodes.registerType("network-write", NetworkWriteNode);
82
+ RED.nodes.registerType("network-service-write", NetworkServiceWriteNode);
83
83
  }