@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.
- package/nodes/accumulate-block.html +18 -8
- package/nodes/accumulate-block.js +39 -44
- package/nodes/add-block.html +1 -1
- package/nodes/add-block.js +18 -11
- package/nodes/alarm-collector.html +260 -0
- package/nodes/alarm-collector.js +292 -0
- package/nodes/alarm-config.html +129 -0
- package/nodes/alarm-config.js +126 -0
- package/nodes/alarm-service.html +96 -0
- package/nodes/alarm-service.js +142 -0
- package/nodes/analog-switch-block.js +25 -36
- package/nodes/and-block.js +44 -15
- package/nodes/average-block.js +46 -41
- package/nodes/boolean-switch-block.js +10 -28
- package/nodes/boolean-to-number-block.html +18 -5
- package/nodes/boolean-to-number-block.js +24 -16
- package/nodes/cache-block.js +24 -37
- package/nodes/call-status-block.html +91 -32
- package/nodes/call-status-block.js +398 -115
- package/nodes/changeover-block.html +5 -0
- package/nodes/changeover-block.js +167 -162
- package/nodes/comment-block.html +1 -1
- package/nodes/comment-block.js +14 -9
- package/nodes/compare-block.html +14 -4
- package/nodes/compare-block.js +23 -18
- package/nodes/contextual-label-block.html +5 -0
- package/nodes/contextual-label-block.js +6 -16
- package/nodes/convert-block.html +25 -39
- package/nodes/convert-block.js +31 -16
- package/nodes/count-block.html +11 -5
- package/nodes/count-block.js +34 -32
- package/nodes/delay-block.js +58 -53
- package/nodes/divide-block.js +43 -45
- package/nodes/edge-block.html +17 -10
- package/nodes/edge-block.js +43 -41
- package/nodes/enum-switch-block.js +6 -6
- package/nodes/frequency-block.html +6 -1
- package/nodes/frequency-block.js +64 -74
- package/nodes/global-getter.html +51 -15
- package/nodes/global-getter.js +43 -13
- package/nodes/global-setter.html +1 -1
- package/nodes/global-setter.js +40 -12
- package/nodes/history-buffer.html +96 -0
- package/nodes/history-buffer.js +461 -0
- package/nodes/history-collector.html +29 -1
- package/nodes/history-collector.js +37 -16
- package/nodes/history-config.html +13 -1
- package/nodes/history-service.html +84 -0
- package/nodes/history-service.js +52 -0
- package/nodes/hysteresis-block.html +5 -0
- package/nodes/hysteresis-block.js +13 -16
- package/nodes/interpolate-block.html +20 -2
- package/nodes/interpolate-block.js +39 -50
- package/nodes/join.html +78 -0
- package/nodes/join.js +78 -0
- package/nodes/latch-block.js +12 -14
- package/nodes/load-sequence-block.js +102 -110
- package/nodes/max-block.js +26 -26
- package/nodes/memory-block.js +57 -58
- package/nodes/min-block.js +26 -25
- package/nodes/minmax-block.js +35 -34
- package/nodes/modulo-block.js +45 -43
- package/nodes/multiply-block.js +43 -41
- package/nodes/negate-block.html +17 -7
- package/nodes/negate-block.js +25 -19
- package/nodes/network-point-read.html +128 -0
- package/nodes/network-point-read.js +230 -0
- package/nodes/{network-register.html → network-point-register.html} +94 -7
- package/nodes/{network-register.js → network-point-register.js} +18 -4
- package/nodes/network-point-write.html +149 -0
- package/nodes/network-point-write.js +222 -0
- package/nodes/network-service-bridge.html +131 -0
- package/nodes/network-service-bridge.js +376 -0
- package/nodes/network-service-read.html +81 -0
- package/nodes/{network-read.js → network-service-read.js} +4 -3
- package/nodes/{network-point-registry.html → network-service-registry.html} +19 -4
- package/nodes/{network-point-registry.js → network-service-registry.js} +7 -2
- package/nodes/network-service-write.html +89 -0
- package/nodes/{network-write.js → network-service-write.js} +3 -3
- package/nodes/nullify-block.js +13 -15
- package/nodes/on-change-block.html +17 -9
- package/nodes/on-change-block.js +49 -46
- package/nodes/oneshot-block.html +13 -10
- package/nodes/oneshot-block.js +57 -75
- package/nodes/or-block.js +44 -15
- package/nodes/pid-block.html +54 -4
- package/nodes/pid-block.js +459 -248
- package/nodes/priority-block.js +24 -35
- package/nodes/rate-limit-block.js +70 -72
- package/nodes/rate-of-change-block.html +33 -14
- package/nodes/rate-of-change-block.js +74 -62
- package/nodes/round-block.html +14 -9
- package/nodes/round-block.js +32 -25
- package/nodes/saw-tooth-wave-block.js +49 -76
- package/nodes/scale-range-block.html +12 -6
- package/nodes/scale-range-block.js +46 -39
- package/nodes/sine-wave-block.js +49 -57
- package/nodes/string-builder-block.js +6 -6
- package/nodes/subtract-block.js +38 -34
- package/nodes/thermistor-block.js +44 -44
- package/nodes/tick-tock-block.js +32 -32
- package/nodes/time-sequence-block.js +30 -42
- package/nodes/triangle-wave-block.js +49 -69
- package/nodes/tstat-block.js +34 -44
- package/nodes/units-block.html +90 -69
- package/nodes/units-block.js +22 -30
- package/nodes/utils.js +206 -3
- package/package.json +14 -6
- package/nodes/network-read.html +0 -56
- 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
|
-
|
|
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
|
-
|
|
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",
|
|
57
|
+
RED.nodes.registerType("network-service-read", NetworkServiceReadNode);
|
|
57
58
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script type="text/html" data-template-name="network-
|
|
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(
|
|
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-
|
|
79
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
|
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
|
|
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",
|
|
82
|
+
RED.nodes.registerType("network-service-write", NetworkServiceWriteNode);
|
|
83
83
|
}
|