@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,222 @@
1
+ // ============================================================================
2
+ // Point Write - Write values to remote points via network bridge
3
+ // ============================================================================
4
+ // Sends write requests to a remote point through the selected network bridge.
5
+ // Supports priority levels (1-16) for BACnet-style priority arrays.
6
+ // Configurable input property to read value from msg.
7
+ // ============================================================================
8
+
9
+ module.exports = function(RED) {
10
+ const utils = require('./utils')(RED);
11
+
12
+ function NetworkPointWriteNode(config) {
13
+ RED.nodes.createNode(this, config);
14
+ const node = this;
15
+
16
+ // ====================================================================
17
+ // Configuration
18
+ // ====================================================================
19
+ node.pointId = parseInt(config.pointId) || 0;
20
+ node.priority = parseInt(config.priority) || 16; // Default to lowest priority
21
+ node.inputProperty = config.inputProperty || "payload";
22
+ node.bridgeNodeId = config.bridgeNodeId || "";
23
+
24
+ // ====================================================================
25
+ // State tracking
26
+ // ====================================================================
27
+ let pendingWrite = null;
28
+ let lastWriteTime = null;
29
+ let lastWriteValue = null;
30
+
31
+ // ====================================================================
32
+ // Helper: Get status text
33
+ // ====================================================================
34
+ function getStatusText() {
35
+ if (lastWriteValue !== null) {
36
+ return `#${node.pointId} @${node.priority}: ${lastWriteValue}`;
37
+ }
38
+ return `#${node.pointId} @${node.priority}: (no writes)`;
39
+ }
40
+
41
+ // ====================================================================
42
+ // Response handler - receives write confirmations from bridge
43
+ // ====================================================================
44
+ function responseHandler(response) {
45
+ // Only process responses for this node
46
+ if (response.sourceNodeId !== node.id) return;
47
+ if (response.pointId !== node.pointId) return;
48
+
49
+ pendingWrite = null;
50
+
51
+ if (response.error) {
52
+ const errorText = `Write failed for point #${node.pointId}: ${response.error}`;
53
+ utils.setStatusError(node, `#${node.pointId}: ${response.error}`);
54
+ node.error(errorText); // Show in debug panel
55
+ node.send({
56
+ payload: lastWriteValue,
57
+ pointId: node.pointId,
58
+ priority: node.priority,
59
+ action: "writeError",
60
+ error: response.error,
61
+ timestamp: Date.now()
62
+ });
63
+ } else {
64
+ lastWriteTime = Date.now();
65
+ utils.setStatusChanged(node, getStatusText());
66
+ node.send({
67
+ payload: lastWriteValue,
68
+ pointId: node.pointId,
69
+ priority: node.priority,
70
+ action: "writeConfirmed",
71
+ timestamp: lastWriteTime
72
+ });
73
+ }
74
+ }
75
+
76
+ // Register response listener
77
+ RED.events.on("pointWrite:response", responseHandler);
78
+
79
+ // Initial status
80
+ utils.setStatusOK(node, getStatusText());
81
+
82
+ // ====================================================================
83
+ // Input handler
84
+ // ====================================================================
85
+ node.on("input", function(msg, send, done) {
86
+ send = send || function() { node.send.apply(node, arguments); };
87
+
88
+ // Guard against invalid msg
89
+ if (!msg) {
90
+ utils.setStatusError(node, "invalid message");
91
+ if (done) done();
92
+ return;
93
+ }
94
+
95
+ // ================================================================
96
+ // Handle configuration commands via msg.context
97
+ // ================================================================
98
+ if (msg.hasOwnProperty("context")) {
99
+ if (!msg.hasOwnProperty("payload")) {
100
+ utils.setStatusError(node, `missing payload for ${msg.context}`);
101
+ if (done) done();
102
+ return;
103
+ }
104
+
105
+ if (msg.context === "pointId") {
106
+ const newPointId = parseInt(msg.payload);
107
+ if (isNaN(newPointId) || newPointId < 0) {
108
+ utils.setStatusError(node, "invalid pointId");
109
+ if (done) done();
110
+ return;
111
+ }
112
+ node.pointId = newPointId;
113
+ utils.setStatusOK(node, getStatusText());
114
+ if (done) done();
115
+ return;
116
+ }
117
+
118
+ if (msg.context === "priority") {
119
+ const newPriority = parseInt(msg.payload);
120
+ if (isNaN(newPriority) || newPriority < 1 || newPriority > 16) {
121
+ utils.setStatusError(node, "priority must be 1-16");
122
+ if (done) done();
123
+ return;
124
+ }
125
+ node.priority = newPriority;
126
+ utils.setStatusOK(node, getStatusText());
127
+ if (done) done();
128
+ return;
129
+ }
130
+
131
+ if (msg.context === "release") {
132
+ // Release priority (write null to clear priority slot)
133
+ if (!node.bridgeNodeId) {
134
+ utils.setStatusError(node, "no bridge configured");
135
+ if (done) done();
136
+ return;
137
+ }
138
+
139
+ const requestId = `${node.id}-${Date.now()}`;
140
+ pendingWrite = requestId;
141
+ lastWriteValue = null;
142
+
143
+ utils.setStatusWarn(node, `#${node.pointId} @${node.priority}: releasing...`);
144
+
145
+ RED.events.emit("pointWrite:write", {
146
+ sourceNodeId: node.id,
147
+ bridgeNodeId: node.bridgeNodeId,
148
+ pointId: node.pointId,
149
+ priority: node.priority,
150
+ value: null, // null = release
151
+ requestId: requestId
152
+ });
153
+
154
+ if (done) done();
155
+ return;
156
+ }
157
+
158
+ utils.setStatusWarn(node, "unknown context");
159
+ if (done) done();
160
+ return;
161
+ }
162
+
163
+ // ================================================================
164
+ // Normal write - read value from configured input property
165
+ // ================================================================
166
+ if (!node.bridgeNodeId) {
167
+ utils.setStatusError(node, "no bridge configured");
168
+ if (done) done();
169
+ return;
170
+ }
171
+
172
+ // Read value from input property
173
+ let writeValue;
174
+ try {
175
+ writeValue = RED.util.getMessageProperty(msg, node.inputProperty);
176
+ } catch (err) {
177
+ utils.setStatusError(node, `invalid input property: ${node.inputProperty}`);
178
+ if (done) done();
179
+ return;
180
+ }
181
+
182
+ if (writeValue === undefined) {
183
+ utils.setStatusError(node, `missing ${node.inputProperty}`);
184
+ if (done) done();
185
+ return;
186
+ }
187
+
188
+ // Track the value we're writing
189
+ lastWriteValue = writeValue;
190
+
191
+ // Generate unique request ID
192
+ const requestId = `${node.id}-${Date.now()}`;
193
+ pendingWrite = requestId;
194
+
195
+ // Show pending status
196
+ utils.setStatusWarn(node, `#${node.pointId} @${node.priority}: writing ${writeValue}...`);
197
+
198
+ // Emit write request to bridge
199
+ RED.events.emit("pointWrite:write", {
200
+ sourceNodeId: node.id,
201
+ bridgeNodeId: node.bridgeNodeId,
202
+ pointId: node.pointId,
203
+ priority: node.priority,
204
+ value: writeValue,
205
+ requestId: requestId
206
+ });
207
+
208
+ if (done) done();
209
+ });
210
+
211
+ // ====================================================================
212
+ // Cleanup on close
213
+ // ====================================================================
214
+ node.on("close", function(done) {
215
+ RED.events.removeListener("pointWrite:response", responseHandler);
216
+ pendingWrite = null;
217
+ done();
218
+ });
219
+ }
220
+
221
+ RED.nodes.registerType("network-point-write", NetworkPointWriteNode);
222
+ };
@@ -0,0 +1,131 @@
1
+ <script type="text/html" data-template-name="network-service-bridge">
2
+ <div class="form-row">
3
+ <label for="node-input-name" title="Display name shown on canvas"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Network Bridge">
5
+ </div>
6
+ <hr style="margin: 10px 0;">
7
+ <div class="form-row">
8
+ <label for="node-input-startupDelay" title="Delay before processing requests (seconds, allows network to come online)"><i class="fa fa-clock-o"></i> Startup Delay (sec)</label>
9
+ <input type="number" id="node-input-startupDelay" placeholder="30" min="0" step="1">
10
+ </div>
11
+ </script>
12
+
13
+ <script type="text/javascript">
14
+ RED.nodes.registerType("network-service-bridge", {
15
+ category: "bldgblocks network",
16
+ color: "#3090C7",
17
+ defaults: {
18
+ name: { value: "Network Bridge" },
19
+ startupDelay: { value: 30, required: false, validate: function(v) { return !isNaN(parseInt(v)) && parseInt(v) >= 0; } }
20
+ },
21
+ inputs: 1,
22
+ outputs: 1,
23
+ inputLabels: ["read requests / responses"],
24
+ outputLabels: ["routed to/from remote"],
25
+ icon: "font-awesome/fa-exchange",
26
+ paletteLabel: "network point bridge",
27
+ label: function() {
28
+ return this.name || "network point bridge";
29
+ }
30
+ });
31
+ </script>
32
+
33
+ <script type="text/markdown" data-help-name="network-service-bridge">
34
+ Routes point read/write requests through WebSocket to remote devices and correlates responses.
35
+
36
+ Sits in the network flow between point-reference nodes and WebSocket communication, handling request/response routing and request correlation.
37
+
38
+ ### Inputs
39
+ : action (string) : Command - read, write
40
+ : pointId (number) : Target point ID for read/write operations.
41
+ : requestId (string) : Unique request identifier for correlation.
42
+ : priority (number) : Priority level for write operations.
43
+ : value (any) : Value to write (read operations ignore this).
44
+ : timestamp (number) : Request timestamp (milliseconds since epoch).
45
+
46
+ ### Outputs
47
+ : payload (object) : Response data (value for reads, status for writes).
48
+ : pointId (number) : Echo of request point ID.
49
+ : requestId (string) : Matching request ID for correlation.
50
+ : action (string) : readResponse, writeResponse, or stats.
51
+ : value (any) : Retrieved value (for reads) or winner value (for writes).
52
+ : timestamp (number) : Response timestamp.
53
+
54
+ ### Details
55
+ Request/Response Correlation:
56
+ 1. Assigns unique requestId to each request
57
+ 2. Tracks pending requests in memory
58
+ 3. Matches incoming responses to original requests
59
+ 4. Timeout: 10 seconds per request
60
+
61
+ Management Commands:
62
+ - getBridgeStats: Returns sent/received/pending counts
63
+ - clearPending: Clear all pending request timeouts
64
+ - resetStats: Reset statistics counters
65
+
66
+ Flow Architecture:
67
+ - Left input: Requests from point-read/point-write nodes
68
+ - Right input: Responses from remote via WebSocket
69
+ - Outputs routed appropriately (back to requester or to WebSocket)
70
+
71
+ ### Status
72
+ - Green (dot): Idle, no pending requests
73
+ - Blue (dot): Requests pending
74
+ - Blue (ring): Active, zero pending
75
+ - Yellow (ring): Some requests failed
76
+ - Red (ring): Configuration error
77
+
78
+ ### References
79
+ 5. Cleans up stale requests automatically
80
+
81
+ Request Flow:
82
+ ```
83
+ [point-reference] sends read request
84
+
85
+ [point-network-bridge] assigns requestId, forwards to WebSocket
86
+
87
+ [WebSocket client] sends to remote
88
+
89
+ [Remote receives, gets value]
90
+
91
+ [WebSocket client] receives response
92
+
93
+ [point-network-bridge] matches by requestId, forwards back
94
+
95
+ [point-reference] updates cache
96
+ ```
97
+
98
+ #### Architecture
99
+
100
+ This node is the **bridge** between:
101
+ - **LOCAL**: point-reference nodes (send read requests)
102
+ - **REMOTE**: WebSocket endpoint (receives/sends over network)
103
+
104
+ Enables non-blocking polling pattern:
105
+ - Logic flows request cached values from point-reference
106
+ - point-reference triggers reads via this bridge
107
+ - Responses update cache in background
108
+ - Logic flows get instant responses without network latency
109
+
110
+ #### Statistics
111
+
112
+ - **sent**: Total read requests sent to remote
113
+ - **received**: Total responses received from remote
114
+ - **errors**: Requests that timed out
115
+ - **pending**: Currently outstanding requests
116
+
117
+ #### Example Flow
118
+
119
+ ```
120
+ Network Flow:
121
+ [WebSocket client] ←→ [point-network-bridge]
122
+
123
+ [point-reference #301]
124
+ [point-reference #302]
125
+ (send read requests)
126
+ ```
127
+
128
+ #### References
129
+ - [Polling Architecture](../planning/POLLING_ARCHITECTURE.md)
130
+ - [point-reference Node](./point-reference.html)
131
+ </script>