@bldgblocks/node-red-contrib-control 0.1.32 → 0.1.34

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 (72) hide show
  1. package/nodes/accumulate-block.html +1 -1
  2. package/nodes/add-block.html +1 -1
  3. package/nodes/analog-switch-block.html +1 -1
  4. package/nodes/and-block.html +1 -1
  5. package/nodes/average-block.html +1 -1
  6. package/nodes/boolean-switch-block.html +1 -1
  7. package/nodes/boolean-to-number-block.html +1 -1
  8. package/nodes/cache-block.html +1 -1
  9. package/nodes/call-status-block.html +1 -1
  10. package/nodes/changeover-block.html +1 -1
  11. package/nodes/comment-block.html +1 -1
  12. package/nodes/compare-block.html +1 -1
  13. package/nodes/contextual-label-block.html +1 -1
  14. package/nodes/convert-block.html +1 -1
  15. package/nodes/count-block.html +2 -2
  16. package/nodes/delay-block.html +1 -1
  17. package/nodes/divide-block.html +1 -1
  18. package/nodes/edge-block.html +1 -1
  19. package/nodes/enum-switch-block.html +1 -1
  20. package/nodes/frequency-block.html +1 -1
  21. package/nodes/global-getter.html +34 -3
  22. package/nodes/global-getter.js +73 -70
  23. package/nodes/global-setter.html +21 -10
  24. package/nodes/global-setter.js +130 -103
  25. package/nodes/history-collector.html +283 -0
  26. package/nodes/history-collector.js +150 -0
  27. package/nodes/history-config.html +236 -0
  28. package/nodes/history-config.js +8 -0
  29. package/nodes/hysteresis-block.html +1 -1
  30. package/nodes/interpolate-block.html +1 -1
  31. package/nodes/latch-block.html +1 -1
  32. package/nodes/load-sequence-block.html +1 -1
  33. package/nodes/max-block.html +1 -1
  34. package/nodes/memory-block.html +1 -1
  35. package/nodes/min-block.html +1 -1
  36. package/nodes/minmax-block.html +1 -1
  37. package/nodes/modulo-block.html +1 -1
  38. package/nodes/multiply-block.html +1 -1
  39. package/nodes/negate-block.html +1 -1
  40. package/nodes/network-point-registry.html +86 -0
  41. package/nodes/network-point-registry.js +90 -0
  42. package/nodes/network-read.html +56 -0
  43. package/nodes/network-read.js +57 -0
  44. package/nodes/network-register.html +110 -0
  45. package/nodes/network-register.js +112 -0
  46. package/nodes/network-write.html +65 -0
  47. package/nodes/network-write.js +83 -0
  48. package/nodes/nullify-block.html +1 -1
  49. package/nodes/on-change-block.html +1 -1
  50. package/nodes/oneshot-block.html +1 -1
  51. package/nodes/or-block.html +1 -1
  52. package/nodes/pid-block.html +1 -1
  53. package/nodes/priority-block.html +1 -1
  54. package/nodes/rate-limit-block.html +2 -2
  55. package/nodes/rate-of-change-block.html +1 -1
  56. package/nodes/rate-of-change-block.js +5 -2
  57. package/nodes/round-block.html +6 -5
  58. package/nodes/round-block.js +5 -3
  59. package/nodes/saw-tooth-wave-block.html +2 -2
  60. package/nodes/scale-range-block.html +1 -1
  61. package/nodes/sine-wave-block.html +2 -2
  62. package/nodes/string-builder-block.html +1 -1
  63. package/nodes/subtract-block.html +1 -1
  64. package/nodes/thermistor-block.html +1 -1
  65. package/nodes/tick-tock-block.html +2 -2
  66. package/nodes/time-sequence-block.html +1 -1
  67. package/nodes/triangle-wave-block.html +2 -2
  68. package/nodes/tstat-block.html +1 -1
  69. package/nodes/units-block.html +8 -38
  70. package/nodes/units-block.js +3 -42
  71. package/nodes/utils.js +70 -1
  72. package/package.json +11 -4
@@ -0,0 +1,90 @@
1
+ module.exports = function(RED) {
2
+ function NetworkPointRegistryNode(config) {
3
+ RED.nodes.createNode(this, config);
4
+ const node = this;
5
+
6
+ // The Map: { 101: { nodeId: "abc.123", writable: true, ... } }
7
+ node.points = new Map();
8
+
9
+ node.register = function(pointId, meta) {
10
+ const pid = parseInt(pointId);
11
+ if (isNaN(pid)) return false;
12
+
13
+ if (node.points.has(pid)) {
14
+ const existing = node.points.get(pid);
15
+ // Allow update if it's the same node
16
+ if (existing.nodeId !== meta.nodeId) {
17
+ return false;
18
+ }
19
+ // Merge updates
20
+ meta = Object.assign({}, existing, meta);
21
+ }
22
+ node.points.set(pid, meta);
23
+ return true;
24
+ };
25
+
26
+ node.unregister = function(pointId, nodeId) {
27
+ const pid = parseInt(pointId);
28
+ if (node.points.has(pid) && node.points.get(pid).nodeId === nodeId) {
29
+ node.points.delete(pid);
30
+ }
31
+ };
32
+
33
+ node.lookup = function(pointId) {
34
+ return node.points.get(parseInt(pointId));
35
+ };
36
+ }
37
+ RED.nodes.registerType("network-point-registry", NetworkPointRegistryNode);
38
+
39
+ // --- HTTP Endpoint for Editor Validation ---
40
+ // Route: /network-point-registry/check/<RegistryID>/<PointID>/<CurrentNodeID>
41
+ RED.httpAdmin.get('/network-point-registry/check/:registryId/:pointId/:nodeId', RED.auth.needsPermission('network-point-registry.read'), function(req, res) {
42
+ const registryId = req.params.registryId;
43
+ const checkId = parseInt(req.params.pointId);
44
+ const checkNodeId = req.params.nodeId;
45
+
46
+ // Find the specific Registry Config Node
47
+ const regNode = RED.nodes.getNode(registryId);
48
+
49
+ let entry = null;
50
+ let result = "unavailable";
51
+ let collision = false;
52
+
53
+ if (!regNode) {
54
+ // Registry exists in editor but not deployed yet, or doesn't exist
55
+ return res.json({ status: result, warning: "Registry not deployed" });
56
+ }
57
+
58
+ // Check that specific registry for the ID
59
+ if (regNode.points.has(checkId)) {
60
+ entry = regNode.points.get(checkId);
61
+ // Collision if ID exists AND belongs to a different node
62
+ if (entry.nodeId !== checkNodeId) {
63
+ collision = true;
64
+ }
65
+ }
66
+
67
+ if (collision) {
68
+ result = "collision";
69
+ } else if (!collision && entry) {
70
+ result = "assigned";
71
+ } else{
72
+ result = "available";
73
+ }
74
+
75
+ res.json({ status: result, details: entry });
76
+ });
77
+
78
+
79
+ RED.httpAdmin.get('/network-point-registry/list/:registryId', RED.auth.needsPermission('network-point-registry.read'), function(req, res) {
80
+ const reg = RED.nodes.getNode(req.params.registryId);
81
+ if (!reg) return res.status(404).json({error:'not found'});
82
+
83
+ // Convert Map to array
84
+ const arr = [];
85
+ for (const [pid, meta] of reg.points.entries()) {
86
+ arr.push({ id: pid, ...meta });
87
+ }
88
+ res.json(arr);
89
+ });
90
+ };
@@ -0,0 +1,56 @@
1
+ <script type="text/html" data-template-name="network-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-read', {
25
+ category: 'bldgblocks network',
26
+ color: '#3090C7',
27
+ defaults: {
28
+ name: { value: "" },
29
+ registry: { value: "", type: "network-point-registry", required: true }
30
+ },
31
+ inputs: 1,
32
+ outputs: 1,
33
+ icon: "font-awesome/fa-database",
34
+ label: function() {
35
+ return "network read";
36
+ },
37
+ paletteLabel: "network read",
38
+ oneditprepare: function() {
39
+ const nodeId = this.id;
40
+ }
41
+ });
42
+ </script>
43
+
44
+ <script type="text/markdown" data-help-name="network-read">
45
+ Reads a network point by pointId.
46
+
47
+ ### Input
48
+ : action (string) : Not used by the node, used for routing to the correct node over the network when received.
49
+ : pointId (number) : The integer ID of the point.
50
+
51
+ ### Output
52
+ : payload (object) : Global data object
53
+
54
+ ### Details
55
+
56
+ </script>
@@ -0,0 +1,57 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+ function NetworkReadNode(config) {
4
+ RED.nodes.createNode(this, config);
5
+ const node = this;
6
+ node.registry = RED.nodes.getNode(config.registry);
7
+
8
+ node.on("input", async function(msg, send, done) {
9
+ send = send || function() { node.send.apply(node, arguments); };
10
+
11
+ try {
12
+ if (!node.registry) {
13
+ node.status({ fill: "red", shape: "ring", text: "Registry missing" });
14
+ if (done) done();
15
+ return;
16
+ }
17
+
18
+ const currentEntry = node.registry.lookup(msg.pointId);
19
+ if (!currentEntry) {
20
+ return utils.sendError(node, msg, done, `Not Registered: ${msg.pointId}`, msg.pointId);
21
+ }
22
+
23
+ const currentPath = currentEntry.path;
24
+ const currentStore = currentEntry.store || "default";
25
+
26
+ // Async Get
27
+ let globalData = await utils.getGlobalState(node, currentPath, currentStore);
28
+
29
+ if (!globalData || Object.keys(globalData).length === 0) {
30
+ return utils.sendError(node, msg, done, `Global Data Empty: ${msg.pointId}`, msg.pointId);
31
+ }
32
+
33
+ msg = { ...globalData };
34
+
35
+ const ptName = msg.metadata?.name ?? "Unknown";
36
+ const ptVal = msg.value !== undefined ? msg.value : "No Value";
37
+ const ptId = msg.network?.pointId ?? msg.pointId;
38
+
39
+ const msgText = `Data Found. pointId: ${ptId} value: ${ptVal}`;
40
+
41
+ utils.sendSuccess(node, msg, done, msgText, ptId, "ring");
42
+
43
+ } catch (err) {
44
+ node.error(err);
45
+ utils.sendError(node, msg, done, `Internal Error: ${err.message}`, msg?.pointId);
46
+ }
47
+ });
48
+
49
+ node.on('close', function(removed, done) {
50
+ if (removed && node.registry) {
51
+ node.registry.unregister(node.pointId, node.id);
52
+ }
53
+ done();
54
+ });
55
+ }
56
+ RED.nodes.registerType("network-read", NetworkReadNode);
57
+ }
@@ -0,0 +1,110 @@
1
+ <script type="text/html" data-template-name="network-register">
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" title="A group of id's for assignment organization"><i class="fa fa-book"></i> Registry</label>
8
+ <input type="text" id="node-input-registry">
9
+ </div>
10
+
11
+ <div class="form-row">
12
+ <label for="node-input-pointId" title="Simple, stable, and unique lookup id for network users."><i class="fa fa-hashtag"></i> Point ID</label>
13
+ <div style="display: inline-block; width: 70%;">
14
+ <input type="number" id="node-input-pointId" placeholder="e.g. 101" style="width: 100px;">
15
+ <span id="point-id-status" style="margin-left: 10px; display: none;"></span>
16
+ </div>
17
+ </div>
18
+
19
+ <div class="form-row">
20
+ <label for="node-input-writable"><i class="fa fa-pencil"></i> Writable</label>
21
+ <input type="checkbox" id="node-input-writable" style="display: inline-block; width: auto; vertical-align: top;">
22
+ <span style="color: #666;"> Allow network to write to this point?</span>
23
+ </div>
24
+ </script>
25
+
26
+ <script type="text/javascript">
27
+ RED.nodes.registerType('network-register', {
28
+ category: 'bldgblocks network',
29
+ color: '#3090C7',
30
+ defaults: {
31
+ name: { value: "" },
32
+ registry: { value: "", type: "network-point-registry", required: true },
33
+ pointId: { value: null, required: true, validate: RED.validators.number() },
34
+ writable: { value: false }
35
+ },
36
+ inputs: 1,
37
+ outputs: 1,
38
+ icon: "font-awesome/fa-book",
39
+ label: function() {
40
+ const id = this.pointId || "?";
41
+ return `(id:${id}) ${this.name || "network register"}`;
42
+ },
43
+ paletteLabel: "network register",
44
+ oneditprepare: function() {
45
+ const nodeId = this.id;
46
+ const statusSpan = $("#point-id-status");
47
+ const idInput = $("#node-input-pointId");
48
+ const regInput = $("#node-input-registry");
49
+
50
+ let timer = null;
51
+
52
+ function checkId() {
53
+ const node = this;
54
+ const pid = idInput.val();
55
+ const rid = regInput.val();
56
+
57
+ statusSpan.hide();
58
+ idInput.removeClass("input-error");
59
+
60
+ if (timer) clearTimeout(timer);
61
+
62
+ // Need both ID and Registry selected to validate
63
+ if (pid && rid && rid !== "_ADD_") {
64
+ timer = setTimeout(() => {
65
+ // Pass Registry ID in URL
66
+ $.getJSON(`network-point-registry/check/${rid}/${pid}/${nodeId}`, function(data) {
67
+ if (data.warning) {
68
+ // Registry exists in config but not deployed
69
+ statusSpan.html(`<i class="fa fa-question-circle" style="color: orange;" title="${data.warning}"></i> Not Deployed`).show();
70
+ } else if (data.status === "collision") {
71
+ // Conflict
72
+ statusSpan.html('<i class="fa fa-exclamation-triangle" style="color: red;"></i> ID Taken').show();
73
+ idInput.addClass("input-error");
74
+ } else if (data.status === "assigned") {
75
+ // Assigned here
76
+ statusSpan.html('<i class="fa fa-check" style="color: green;"></i> Assigned').show();
77
+ } else {
78
+ // Available
79
+ statusSpan.html('<i class="fa fa-check" style="color: green;"></i> Available').show();
80
+ }
81
+ });
82
+ }, 500);
83
+ }
84
+ }
85
+
86
+ function loadNextFreeId(registry) {
87
+ if (!registry || registry === "_ADD_") { return; }
88
+ $.getJSON(`/network-point-registry/list/${registry}`, function (data) {
89
+ const next = data.reduce((max, pt) => Math.max(max, pt.id), 0) + 1;
90
+ idInput.val(next);
91
+ }).fail(function () {
92
+ statusSpan.html('<i class="fa fa-exclamation-triangle" style="color:red;"></i> Cannot fetch points').show();
93
+ });
94
+ }
95
+
96
+ // Trigger check on ID type or Registry Change
97
+ idInput.on("input", checkId);
98
+ regInput.on("change", function () {
99
+ checkId();
100
+ if (idInput.val().trim() === "") {
101
+ loadNextFreeId(regInput.val());
102
+ }
103
+ });
104
+
105
+ if (idInput.val().trim() === "") {
106
+ loadNextFreeId(regInput.val());
107
+ }
108
+ }
109
+ });
110
+ </script>
@@ -0,0 +1,112 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+ function NetworkRegisterNode(config) {
4
+ RED.nodes.createNode(this, config);
5
+ const node = this;
6
+
7
+ node.registry = RED.nodes.getNode(config.registry);
8
+ node.pointId = parseInt(config.pointId);
9
+ node.writable = !!config.writable;
10
+ node.isRegistered = false;
11
+
12
+ // Initial Registration
13
+ if (node.registry && !isNaN(node.pointId)) {
14
+ const success = node.registry.register(node.pointId, {
15
+ nodeId: node.id,
16
+ writable: node.writable,
17
+ path: "not ready",
18
+ store: "not ready"
19
+ });
20
+
21
+ if (success) {
22
+ node.isRegistered = true;
23
+ node.status({ fill: "yellow", shape: "ring", text: `ID: ${node.pointId} (Waiting)` });
24
+ } else {
25
+ node.error(`Point ID ${node.pointId} is already in use.`);
26
+ node.status({ fill: "red", shape: "dot", text: "ID Conflict" });
27
+ }
28
+ }
29
+
30
+ node.on("input", async function(msg, send, done) {
31
+ send = send || function() { node.send.apply(node, arguments); };
32
+
33
+ try {
34
+ // Pre-flight
35
+ if (!node.isRegistered) return utils.sendError(node, null, done, "Node not registered");
36
+ if (!msg || typeof msg !== "object") return utils.sendError(node, null, done, "Invalid msg object");
37
+ if (!node.registry) return utils.sendError(node, msg, done, "Registry config missing", node.pointId);
38
+
39
+ // Validate Fields
40
+ if (!msg.activePriority || !msg.metadata?.path || !msg.metadata?.store) {
41
+ return utils.sendError(node, msg, done, "Missing required fields (metadata.path/store, activePriority)", node.pointId);
42
+ }
43
+
44
+ // Logic & State Update
45
+ let pointData = node.registry.lookup(node.pointId);
46
+
47
+ const incoming = {
48
+ writable: node.writable,
49
+ path: msg.metadata.path,
50
+ store: msg.metadata.store
51
+ };
52
+
53
+ const needsUpdate = !pointData
54
+ || pointData.nodeId !== node.id
55
+ || pointData.writable !== incoming.writable
56
+ || pointData.path !== incoming.path
57
+ || pointData.store !== incoming.store;
58
+
59
+ if (needsUpdate) {
60
+ node.registry.register(node.pointId, {
61
+ nodeId: node.id,
62
+ writable: node.writable,
63
+ path: incoming.path,
64
+ store: incoming.store
65
+ });
66
+
67
+ pointData = node.registry.lookup(node.pointId);
68
+ const currentStore = pointData.store || "default";
69
+
70
+ // Async Get
71
+ const globalData = await utils.getGlobalState(node, pointData.path, currentStore);
72
+
73
+ if (!globalData || Object.keys(globalData).length === 0) {
74
+ return utils.sendError(node, msg, done, `Global missing: (${currentStore})::${pointData.path}`, node.pointId);
75
+ }
76
+
77
+ const networkObject = {
78
+ ...globalData,
79
+ network: {
80
+ registry: node.registry.name,
81
+ pointId: node.pointId,
82
+ writable: node.writable
83
+ }
84
+ };
85
+
86
+ // Async Set
87
+ await utils.setGlobalState(node, pointData.path, currentStore, networkObject);
88
+
89
+ const statusText = `Registered: (${currentStore})::${pointData.path}::${node.pointId}`;
90
+ return utils.sendSuccess(node, networkObject, done, statusText, node.pointId, "dot");
91
+ }
92
+
93
+ // Passthrough
94
+ const prefix = msg.activePriority === 'default' ? '' : 'P';
95
+ const statusText = `Passthrough: ${prefix}${msg.activePriority}:${msg.value}${msg.units}`;
96
+ utils.sendSuccess(node, msg, done, statusText, node.pointId, "ring");
97
+
98
+ } catch (err) {
99
+ node.error(err);
100
+ utils.sendError(node, msg, done, `Internal Error: ${err.message}`, node.pointId);
101
+ }
102
+ });
103
+
104
+ node.on('close', function(removed, done) {
105
+ if (removed && node.registry && node.isRegistered) {
106
+ node.registry.unregister(node.pointId, node.pointId);
107
+ }
108
+ done();
109
+ });
110
+ }
111
+ RED.nodes.registerType("network-register", NetworkRegisterNode);
112
+ }
@@ -0,0 +1,65 @@
1
+ <script type="text/html" data-template-name="network-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-write', {
26
+ category: 'bldgblocks network',
27
+ color: '#3090C7',
28
+ defaults: {
29
+ name: { value: "" },
30
+ registry: { value: "", type: "network-point-registry", required: true }
31
+ },
32
+ inputs: 1,
33
+ outputs: 1,
34
+ icon: "font-awesome/fa-list-ol",
35
+ label: function() {
36
+ return this.name || "network write";
37
+ },
38
+ paletteLabel: "network write",
39
+ oneditprepare: function() {
40
+
41
+ }
42
+ });
43
+ </script>
44
+
45
+ <script type="text/markdown" data-help-name="network-write">
46
+ Writes network commands to Global Variables using the Priority Array logic.
47
+
48
+ ### Input
49
+ : payload (object) : A command object containing
50
+ * `action` (string): Not used by the node, used for routing to the correct node over the network when received.
51
+ * `pointId` (number): The integer ID of the point.
52
+ * `priority` (number): The priority level (1-16) to write to.
53
+ * `value` (any): The value to set. Send `null` to relinquish (clear) this priority level.
54
+
55
+ ### Output
56
+ : payload (object) : Confirmation object containing status, pointId, and the new calculated "Winner" value.
57
+
58
+ ### Details
59
+ This node acts as the inbound gateway.
60
+ 1. It looks up the `pointId` in the selected **Registry** to find the corresponding Global Variable path.
61
+ 2. It fetches the current State Object.
62
+ 3. It updates the specific slot in the `priority` array based on the command.
63
+ 4. It recalculates the "Present Value" (highest priority active).
64
+ 5. It saves the Global Variable and emits an update event, triggering any reactive Getters immediately.
65
+ </script>
@@ -0,0 +1,83 @@
1
+ module.exports = function(RED) {
2
+ const utils = require('./utils')(RED);
3
+ function NetworkWriteNode(config) {
4
+ RED.nodes.createNode(this, config);
5
+ const node = this;
6
+ node.registry = RED.nodes.getNode(config.registry);
7
+
8
+ node.on("input", async function(msg, send, done) {
9
+ send = send || function() { node.send.apply(node, arguments); };
10
+
11
+ try {
12
+ // Validation
13
+ if (!msg || !msg.pointId || !msg.priority || msg.value === undefined) {
14
+ return utils.sendError(node, msg, done, "Invalid msg properties", msg?.pointId);
15
+ }
16
+
17
+ // Registry Lookup
18
+ const entry = node.registry.lookup(msg.pointId);
19
+ if (!entry?.path) {
20
+ const store = entry?.store ?? "unknown";
21
+ return utils.sendError(node, msg, done, `Unknown ID: (${store})::${msg.pointId}`, msg.pointId);
22
+ }
23
+
24
+ const { store = "default", path, writable } = entry;
25
+
26
+ if (!writable) {
27
+ return utils.sendError(node, msg, done, `Not Writable: (${store})::${path}::${msg.pointId}`, msg.pointId);
28
+ }
29
+
30
+ // Get State (Async)
31
+ let state = await utils.getGlobalState(node, path, store);
32
+
33
+ if (!state || !state.priority) {
34
+ return utils.sendError(node, msg, done, `Point Not Found: (${store})::${path}`, msg.pointId);
35
+ }
36
+
37
+ // Type Check
38
+ let newValue = msg.value === "null" || msg.value === null ? null : msg.value;
39
+ if (newValue !== null) {
40
+ const dataType = state.metadata?.type;
41
+ if (dataType && typeof newValue !== dataType) {
42
+ return utils.sendError(node, msg, done, `Type Mismatch: Expected ${dataType}`, msg.pointId);
43
+ }
44
+ }
45
+
46
+ // Update Priority Logic
47
+ if (msg.priority === 'default') {
48
+ state.defaultValue = newValue ?? state.defaultValue;
49
+ } else {
50
+ const priority = parseInt(msg.priority, 10);
51
+ if (isNaN(priority) || priority < 1 || priority > 16) {
52
+ return utils.sendError(node, msg, done, `Invalid Priority: ${msg.priority}`, msg.pointId);
53
+ }
54
+ state.priority[msg.priority] = newValue;
55
+ }
56
+
57
+ // Calculate Winner
58
+ const result = utils.getHighestPriority(state);
59
+ state.value = result.value;
60
+ state.activePriority = result.priority;
61
+ state.metadata.lastSet = new Date().toISOString();
62
+
63
+ // Save (Async) & Emit
64
+ await utils.setGlobalState(node, path, store, state);
65
+
66
+ const prefixReq = msg.priority === 'default' ? '' : 'P';
67
+ const prefixAct = state.activePriority === 'default' ? '' : 'P';
68
+ const statusMsg = `Wrote: ${prefixReq}${msg.priority}:${newValue} > Active: ${prefixAct}${state.activePriority}:${state.value}`;
69
+
70
+ msg = { ...state, status: null };
71
+
72
+ RED.events.emit("bldgblocks-global-update", { key: path, store: store, data: state });
73
+
74
+ utils.sendSuccess(node, msg, done, statusMsg, msg.pointId, "ring");
75
+
76
+ } catch (err) {
77
+ node.error(err);
78
+ utils.sendError(node, msg, done, `Internal Error: ${err.message}`, msg?.pointId);
79
+ }
80
+ });
81
+ }
82
+ RED.nodes.registerType("network-write", NetworkWriteNode);
83
+ }
@@ -11,7 +11,7 @@
11
11
 
12
12
  <script type="text/javascript">
13
13
  RED.nodes.registerType("nullify-block", {
14
- category: "control",
14
+ category: "bldgblocks control",
15
15
  color: "#301934",
16
16
  defaults: {
17
17
  name: { value: "" },
@@ -14,7 +14,7 @@
14
14
  <!-- JavaScript Section -->
15
15
  <script type="text/javascript">
16
16
  RED.nodes.registerType("on-change-block", {
17
- category: "control",
17
+ category: "bldgblocks control",
18
18
  color: "#301934",
19
19
  defaults: {
20
20
  name: { value: "" },
@@ -26,7 +26,7 @@
26
26
  <!-- JavaScript Section -->
27
27
  <script type="text/javascript">
28
28
  RED.nodes.registerType("oneshot-block", {
29
- category: "control",
29
+ category: "bldgblocks control",
30
30
  color: "#301934",
31
31
  defaults: {
32
32
  name: { value: "" },
@@ -11,7 +11,7 @@
11
11
 
12
12
  <script type="text/javascript">
13
13
  RED.nodes.registerType("or-block", {
14
- category: "control",
14
+ category: "bldgblocks control",
15
15
  color: "#301934",
16
16
  defaults: {
17
17
  name: { value: "" },
@@ -65,7 +65,7 @@
65
65
  <!-- JavaScript Section -->
66
66
  <script type="text/javascript">
67
67
  RED.nodes.registerType("pid-block", {
68
- category: "control",
68
+ category: "bldgblocks control",
69
69
  color: "#301934",
70
70
  defaults: {
71
71
  name: { value: "" },
@@ -9,7 +9,7 @@
9
9
  <!-- JavaScript Section: Registers the node and handles editor logic -->
10
10
  <script type="text/javascript">
11
11
  RED.nodes.registerType("priority-block", {
12
- category: "control",
12
+ category: "bldgblocks control",
13
13
  color: "#301934",
14
14
  defaults: {
15
15
  name: { value: "" }
@@ -29,7 +29,7 @@
29
29
  <!-- JavaScript Section -->
30
30
  <script type="text/javascript">
31
31
  RED.nodes.registerType("rate-limit-block", {
32
- category: "control",
32
+ category: "bldgblocks control",
33
33
  color: "#301934",
34
34
  defaults: {
35
35
  name: { value: "" },
@@ -42,7 +42,7 @@
42
42
  outputs: 1,
43
43
  inputLabels: ["value"],
44
44
  outputLabels: ["processed value"],
45
- icon: "font-awesome/fa-tachometer-alt",
45
+ icon: "font-awesome/fa-circle-up",
46
46
  paletteLabel: "rate limit",
47
47
  label: function() {
48
48
  return this.name || this.mode || "rate limit";
@@ -29,7 +29,7 @@
29
29
 
30
30
  <script type="text/javascript">
31
31
  RED.nodes.registerType("rate-of-change-block", {
32
- category: "control",
32
+ category: "bldgblocks control",
33
33
  color: "#301934",
34
34
  defaults: {
35
35
  name: { value: "" },