@bldgblocks/node-red-contrib-control 0.2.2 → 0.2.3
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/alarm-collector.html +2 -2
- package/nodes/alarm-collector.js +33 -50
- package/nodes/alarm-service.js +1 -1
- package/nodes/boolean-to-number-block.html +13 -4
- package/nodes/boolean-to-number-block.js +6 -2
- package/nodes/call-status-block.js +29 -3
- package/nodes/changeover-block.html +31 -25
- package/nodes/changeover-block.js +26 -7
- package/nodes/comment-block.html +48 -9
- package/nodes/comment-block.js +65 -6
- package/nodes/contextual-label-block.js +3 -2
- package/nodes/convert-block.js +1 -1
- package/nodes/edge-block.html +6 -3
- package/nodes/edge-block.js +4 -3
- package/nodes/enum-switch-block.html +1 -1
- package/nodes/global-setter.html +18 -11
- package/nodes/global-setter.js +62 -45
- package/nodes/hysteresis-block.js +1 -1
- package/nodes/interpolate-block.js +2 -2
- package/nodes/negate-block.js +2 -2
- package/nodes/network-point-register.js +11 -2
- package/nodes/network-service-write.js +7 -11
- package/nodes/on-change-block.js +1 -8
- package/nodes/priority-block.js +6 -6
- package/nodes/rate-limit-block.js +6 -6
- package/nodes/round-block.js +1 -1
- package/nodes/scale-range-block.js +1 -1
- package/nodes/tstat-block.html +21 -13
- package/nodes/tstat-block.js +32 -1
- package/nodes/units-block.js +1 -1
- package/nodes/utils.js +11 -1
- package/package.json +1 -1
package/nodes/comment-block.js
CHANGED
|
@@ -9,14 +9,26 @@ module.exports = function(RED) {
|
|
|
9
9
|
node.name = config.name;
|
|
10
10
|
node.comment = config.comment || "";
|
|
11
11
|
node.statusDisplay = config.statusDisplay;
|
|
12
|
+
node.statusProperty = config.statusProperty || "";
|
|
13
|
+
node.statusPropertyType = config.statusPropertyType || "msg";
|
|
14
|
+
|
|
15
|
+
// Pre-compile JSONata expression if configured
|
|
16
|
+
let jsonataExpr = null;
|
|
17
|
+
if (node.statusPropertyType === "jsonata" && node.statusProperty) {
|
|
18
|
+
try {
|
|
19
|
+
jsonataExpr = RED.util.prepareJSONataExpression(node.statusProperty, node);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
node.error(`Invalid JSONata expression: ${err.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
|
|
13
25
|
// Ensure comment is within 100 characters
|
|
14
26
|
if (node.comment && node.comment.length > 100) {
|
|
15
27
|
node.comment = node.comment.substring(0, 100);
|
|
16
28
|
}
|
|
17
29
|
|
|
18
|
-
// Update status based on configuration
|
|
19
|
-
const
|
|
30
|
+
// Update status based on configuration (static — no msg available)
|
|
31
|
+
const updateStaticStatus = function() {
|
|
20
32
|
switch (node.statusDisplay) {
|
|
21
33
|
case "default":
|
|
22
34
|
utils.setStatusOK(node, node.comment || "No comment set");
|
|
@@ -24,16 +36,54 @@ module.exports = function(RED) {
|
|
|
24
36
|
case "name":
|
|
25
37
|
utils.setStatusOK(node, node.name || "comment");
|
|
26
38
|
break;
|
|
39
|
+
case "property":
|
|
40
|
+
// Can't resolve msg properties without a message — show placeholder
|
|
41
|
+
utils.setStatusOK(node, "waiting for input");
|
|
42
|
+
break;
|
|
27
43
|
case "none":
|
|
28
44
|
default:
|
|
29
|
-
// No status for "none"
|
|
30
45
|
break;
|
|
31
46
|
}
|
|
32
47
|
};
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
// Resolve and display the configured status property from a message
|
|
50
|
+
function resolveStatusProperty(msg) {
|
|
51
|
+
return new Promise(function(resolve) {
|
|
52
|
+
if (node.statusPropertyType === "jsonata" && jsonataExpr) {
|
|
53
|
+
RED.util.evaluateJSONataExpression(jsonataExpr, msg, function(err, result) {
|
|
54
|
+
if (err) {
|
|
55
|
+
resolve(undefined);
|
|
56
|
+
} else {
|
|
57
|
+
resolve(result);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
// msg type — use getMessageProperty
|
|
62
|
+
try {
|
|
63
|
+
resolve(RED.util.getMessageProperty(msg, node.statusProperty));
|
|
64
|
+
} catch (err) {
|
|
65
|
+
resolve(undefined);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function formatValue(val) {
|
|
72
|
+
if (val === undefined) return "undefined";
|
|
73
|
+
if (val === null) return "null";
|
|
74
|
+
if (typeof val === "number") return val % 1 === 0 ? String(val) : val.toFixed(2);
|
|
75
|
+
if (typeof val === "boolean") return String(val);
|
|
76
|
+
if (typeof val === "string") return val.length > 40 ? val.substring(0, 40) + "…" : val;
|
|
77
|
+
if (typeof val === "object") {
|
|
78
|
+
const s = JSON.stringify(val);
|
|
79
|
+
return s.length > 40 ? s.substring(0, 40) + "…" : s;
|
|
80
|
+
}
|
|
81
|
+
return String(val);
|
|
82
|
+
}
|
|
35
83
|
|
|
36
|
-
|
|
84
|
+
updateStaticStatus();
|
|
85
|
+
|
|
86
|
+
node.on("input", async function(msg, send, done) {
|
|
37
87
|
send = send || function() { node.send.apply(node, arguments); };
|
|
38
88
|
|
|
39
89
|
// Guard against invalid msg
|
|
@@ -43,7 +93,16 @@ module.exports = function(RED) {
|
|
|
43
93
|
return;
|
|
44
94
|
}
|
|
45
95
|
|
|
46
|
-
|
|
96
|
+
if (node.statusDisplay === "property" && node.statusProperty) {
|
|
97
|
+
const val = await resolveStatusProperty(msg);
|
|
98
|
+
if (val === undefined) {
|
|
99
|
+
utils.setStatusWarn(node, `${node.statusPropertyType === "jsonata" ? "expr" : node.statusProperty}: not found`);
|
|
100
|
+
} else {
|
|
101
|
+
utils.setStatusChanged(node, formatValue(val));
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
updateStaticStatus();
|
|
105
|
+
}
|
|
47
106
|
|
|
48
107
|
send(msg);
|
|
49
108
|
if (done) done();
|
|
@@ -6,6 +6,7 @@ module.exports = function(RED) {
|
|
|
6
6
|
const node = this;
|
|
7
7
|
|
|
8
8
|
node.contextPropertyName = config.contextPropertyName || "in1";
|
|
9
|
+
node.inputProperty = config.inputProperty || "payload";
|
|
9
10
|
node.removeLabel = config.removeLabel || false;
|
|
10
11
|
|
|
11
12
|
utils.setStatusOK(node, node.removeLabel ? "remove" : `set -> ${node.contextPropertyName}`);
|
|
@@ -23,10 +24,10 @@ module.exports = function(RED) {
|
|
|
23
24
|
// Set or remove context property
|
|
24
25
|
if (node.removeLabel) {
|
|
25
26
|
delete msg.context;
|
|
26
|
-
utils.setStatusChanged(node, `${msg.
|
|
27
|
+
utils.setStatusChanged(node, `${msg[node.inputProperty]} -> removed`);
|
|
27
28
|
} else {
|
|
28
29
|
msg.context = node.contextPropertyName;
|
|
29
|
-
utils.setStatusChanged(node, `${msg.
|
|
30
|
+
utils.setStatusChanged(node, `${msg[node.inputProperty]} -> ${node.contextPropertyName}`);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
send(msg);
|
package/nodes/convert-block.js
CHANGED
|
@@ -287,7 +287,7 @@ module.exports = function(RED) {
|
|
|
287
287
|
const outDisplay = output % 1 === 0 ? output : output.toFixed(2);
|
|
288
288
|
|
|
289
289
|
// Update status and send output
|
|
290
|
-
utils.setStatusOK(node, `${
|
|
290
|
+
utils.setStatusOK(node, `${outDisplay} ${outUnit}`);
|
|
291
291
|
|
|
292
292
|
msg.payload = output;
|
|
293
293
|
send(msg);
|
package/nodes/edge-block.html
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
outputs: 1,
|
|
32
32
|
inputLabels: ["input"],
|
|
33
33
|
outputLabels: ["transition"],
|
|
34
|
-
icon: "font-awesome/fa-
|
|
34
|
+
icon: "font-awesome/fa-bolt",
|
|
35
35
|
paletteLabel: "edge",
|
|
36
36
|
label: function() {
|
|
37
37
|
return this.name || "edge";
|
|
@@ -49,7 +49,9 @@ Detects boolean state transitions (true-to-false or false-to-true).
|
|
|
49
49
|
: payload (string | boolean) : Transition type (`"true-to-false"`, `"false-to-true"`) for `"algorithm"`, true for `"reset"`.
|
|
50
50
|
|
|
51
51
|
### Outputs
|
|
52
|
-
:
|
|
52
|
+
: *entire message* : The original input message is forwarded **only** when the specified transition occurs. No output otherwise.
|
|
53
|
+
: edge (boolean) : `true` is added to the message to flag that a transition was detected.
|
|
54
|
+
: payload : Preserved from the input message (unchanged).
|
|
53
55
|
|
|
54
56
|
### Properties
|
|
55
57
|
: name (string) : Display name in editor.
|
|
@@ -58,8 +60,9 @@ Detects boolean state transitions (true-to-false or false-to-true).
|
|
|
58
60
|
|
|
59
61
|
### Details
|
|
60
62
|
Detects transitions in boolean input (read from the configured **Input Property**, default: `msg.payload`) based on the configured `algorithm`.
|
|
61
|
-
|
|
63
|
+
Forwards the **entire original message** only when the specified transition occurs (true-to-false or false-to-true), with `msg.edge = true` added.
|
|
62
64
|
No output on first input after reset, if no transition occurs, or for non-boolean inputs.
|
|
65
|
+
Because the full message passes through, this node can be used inline to conditionally gate messages based on boolean state changes while preserving all upstream properties.
|
|
63
66
|
|
|
64
67
|
Configuration via `msg.context`:
|
|
65
68
|
- `"algorithm"`: Sets transition type, no output.
|
package/nodes/edge-block.js
CHANGED
|
@@ -103,10 +103,11 @@ module.exports = function(RED) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
if (isTransition) {
|
|
106
|
-
utils.setStatusChanged(node, `
|
|
107
|
-
|
|
106
|
+
utils.setStatusChanged(node, `true`);
|
|
107
|
+
msg.edge = true;
|
|
108
|
+
send(msg);
|
|
108
109
|
} else {
|
|
109
|
-
utils.setStatusUnchanged(node, `
|
|
110
|
+
utils.setStatusUnchanged(node, `none`);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
node.lastValue = currentValue;
|
package/nodes/global-setter.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<input type="text" id="node-input-property" style="width:70%;">
|
|
13
13
|
</div>
|
|
14
14
|
<div class="form-row">
|
|
15
|
-
<label for="node-input-writePriority" title="
|
|
16
|
-
<input type="text" id="node-input-writePriority" placeholder="
|
|
15
|
+
<label for="node-input-writePriority" title="Network priority slot (1-16) for this message. Network writes from external control systems use these slots."><i class="fa fa-sort-numeric-asc"></i> Network Priority</label>
|
|
16
|
+
<input type="text" id="node-input-writePriority" placeholder="16">
|
|
17
17
|
<input type="hidden" id="node-input-writePriorityType">
|
|
18
18
|
</div>
|
|
19
19
|
|
|
@@ -29,13 +29,13 @@
|
|
|
29
29
|
|
|
30
30
|
<div class="form-row">
|
|
31
31
|
<label> </label>
|
|
32
|
-
<button type="button" id="node-btn-clear-priorities" class="editor-button" style="width: calc(70% - 3px);" title="Clear all 16 priority slots on the live node (does not affect the default
|
|
33
|
-
<i class="fa fa-eraser"></i> Clear
|
|
32
|
+
<button type="button" id="node-btn-clear-priorities" class="editor-button" style="width: calc(70% - 3px);" title="Clear all 16 network priority slots on the live node (does not affect the default or fallback values)">
|
|
33
|
+
<i class="fa fa-eraser"></i> Clear Network Priorities
|
|
34
34
|
</button>
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
37
|
<div class="form-tips">
|
|
38
|
-
<b>Note:</b> This node writes to the
|
|
38
|
+
<b>Note:</b> This node writes to the <b>Network Priority</b> slot (1-16), manually, by msg or flow. The actual value is determined by the highest active source: Priorities 1-16 → Fallback → Default.
|
|
39
39
|
</div>
|
|
40
40
|
</script>
|
|
41
41
|
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
property: { value: "payload", required: true },
|
|
50
50
|
defaultValue: { value: "", required: true },
|
|
51
51
|
defaultValueType: { value: "num", required: true },
|
|
52
|
-
writePriority: { value: "
|
|
52
|
+
writePriority: { value: "fallback", required: true },
|
|
53
53
|
writePriorityType: { value: "dropdown" }
|
|
54
54
|
},
|
|
55
55
|
inputs: 1,
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
{ value: "14", label: "14"},
|
|
102
102
|
{ value: "15", label: "15"},
|
|
103
103
|
{ value: "16", label: "16 (Network Logic)"},
|
|
104
|
-
{ value: "
|
|
104
|
+
{ value: "fallback", label: "fallback (Internal Logic)"},
|
|
105
105
|
]
|
|
106
106
|
}, "msg", "flow"],
|
|
107
107
|
typeField: "#node-input-writePriorityType"
|
|
@@ -141,12 +141,12 @@ Manage a global variable in a repeatable way.
|
|
|
141
141
|
### Inputs
|
|
142
142
|
: payload (any) : Input payload is passed through unchanged.
|
|
143
143
|
: property (string) : The input property where the value is taken from.
|
|
144
|
-
: priority (number|string) : _Optional_. Overrides the configured Priority at runtime. Accepts `1`–`16
|
|
145
|
-
: context (string) : _Optional_. Tagged-input
|
|
144
|
+
: priority (number|string) : _Optional_. Overrides the configured Network Priority at runtime. Accepts `1`–`16`. For example, `msg.priority = 8` writes to priority slot 8.
|
|
145
|
+
: context (string) : _Optional_. Tagged-input routing. Accepts `"fallback"` (writes to the fallback slot) or `"priority1"`–`"priority16"` (writes to network priority slots), or `"reload"` (reload state without writing). When both `msg.priority` and `msg.context` are present, `msg.priority` takes precedence.
|
|
146
146
|
: units (string) : The units associated with the value, if any. Also supports nested units at `msg.<inputProperty>.units`.
|
|
147
147
|
|
|
148
148
|
### Outputs
|
|
149
|
-
: payload (any) : Original payload.
|
|
149
|
+
: payload (any) : Original payload plus full state object (value, activePriority, all priority slots, fallback, metadata).
|
|
150
150
|
|
|
151
151
|
### Details
|
|
152
152
|
Global variables are meant to be retrieved in other places, this necessarily means managing the same string in multiple places.
|
|
@@ -155,7 +155,12 @@ This node allows you to set a global variable in one place, and retrieve it else
|
|
|
155
155
|
|
|
156
156
|
When this node is deleted or the flow is redeployed, it will automatically remove (prune) the variable from the selected Context Store.
|
|
157
157
|
|
|
158
|
-
**
|
|
158
|
+
**Priority Hierarchy**: The active value is determined by the highest active source:
|
|
159
|
+
1. **Network Priorities (1-16)**: Used by external automation systems (e.g., BACnet, network service). Priority 16 is typical for network logic, 1 for life safety.
|
|
160
|
+
2. **Fallback**: A dynamic input slot updated via `{context: "fallback", payload: value}`. Useful for internal logic to write a secondary value (e.g., persistent context setpoints).
|
|
161
|
+
3. **Default**: A static configuration value. Always used as the ultimate fallback when all other sources are null/empty.
|
|
162
|
+
|
|
163
|
+
**Clear Network Priorities** button (in editor): Resets all 16 priority slots to `null` on the live running node. The fallback and default values are preserved. The active value recalculates based on the hierarchy. The node must be deployed first.
|
|
159
164
|
|
|
160
165
|
### Status
|
|
161
166
|
- Green (dot): Configuration update
|
|
@@ -164,6 +169,8 @@ When this node is deleted or the flow is redeployed, it will automatically remov
|
|
|
164
169
|
- Red (ring): Error
|
|
165
170
|
- Yellow (ring): Warning
|
|
166
171
|
|
|
172
|
+
Status prefix: Empty = default active, `F` = fallback active, `P` = network priority active (e.g., `P3` = priority 3).
|
|
173
|
+
|
|
167
174
|
### References
|
|
168
175
|
- [Node-RED Documentation](https://nodered.org/docs/)
|
|
169
176
|
- [GitHub Repository](https://github.com/BldgBlocks/node-red-contrib-buildingblocks-control.git)
|
package/nodes/global-setter.js
CHANGED
|
@@ -24,6 +24,7 @@ module.exports = function(RED) {
|
|
|
24
24
|
value: node.defaultValue,
|
|
25
25
|
defaultValue: node.defaultValue,
|
|
26
26
|
activePriority: "default",
|
|
27
|
+
fallback: null,
|
|
27
28
|
units: null,
|
|
28
29
|
priority: { 1: null, 2: null, 3: null, 4: null, 5: null, 6: null, 7: null, 8: null, 9: null, 10: null, 11: null, 12: null, 13: null, 14: null, 15: null, 16: null },
|
|
29
30
|
metadata: {
|
|
@@ -93,44 +94,63 @@ module.exports = function(RED) {
|
|
|
93
94
|
node.isBusy = true;
|
|
94
95
|
|
|
95
96
|
// Resolve write priority — three sources, in order of precedence:
|
|
96
|
-
// 1. msg.priority (number 1-16
|
|
97
|
-
// 2. msg.context ("priority1"–"priority16"
|
|
97
|
+
// 1. msg.priority (number 1-16) — explicit per-message override
|
|
98
|
+
// 2. msg.context ("priority1"–"priority16", "fallback", "reload") — tagged-input pattern
|
|
98
99
|
// 3. Configured writePriority (dropdown / msg / flow typed-input)
|
|
99
100
|
// Use local variable — never mutate node.writePriority so the config default is preserved across messages
|
|
100
101
|
let activePrioritySlot = null;
|
|
102
|
+
let isFallbackWrite = false;
|
|
103
|
+
|
|
101
104
|
try {
|
|
102
|
-
if (msg.hasOwnProperty("
|
|
103
|
-
//
|
|
104
|
-
const
|
|
105
|
-
if (
|
|
106
|
-
|
|
105
|
+
if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
|
|
106
|
+
// Check for special contexts first
|
|
107
|
+
const ctx = msg.context;
|
|
108
|
+
if (ctx === "fallback") {
|
|
109
|
+
isFallbackWrite = true;
|
|
110
|
+
} else if (ctx === "reload") {
|
|
111
|
+
// Handled separately below
|
|
112
|
+
activePrioritySlot = "reload";
|
|
107
113
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
// Check for priority context
|
|
115
|
+
const priorityMatch = /^priority([1-9]|1[0-6])$/.exec(ctx);
|
|
116
|
+
if (priorityMatch) {
|
|
117
|
+
activePrioritySlot = priorityMatch[1];
|
|
112
118
|
}
|
|
113
|
-
activePrioritySlot
|
|
119
|
+
// Unknown contexts leave activePrioritySlot null → falls to config
|
|
114
120
|
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (msg.hasOwnProperty("priority") && (typeof msg.priority === "number" || typeof msg.priority === "string")) {
|
|
124
|
+
// Source 1: msg.priority (direct number 1-16) — skip objects (e.g. priority array from state)
|
|
125
|
+
const mp = msg.priority;
|
|
126
|
+
const p = parseInt(mp, 10);
|
|
127
|
+
if (isNaN(p) || p < 1 || p > 16) {
|
|
128
|
+
node.isBusy = false;
|
|
129
|
+
return utils.sendError(node, msg, done, `Invalid msg.priority: ${mp} (must be 1-16)`);
|
|
124
130
|
}
|
|
125
|
-
|
|
131
|
+
activePrioritySlot = String(p);
|
|
132
|
+
isFallbackWrite = false; // msg.priority overrides fallback context
|
|
126
133
|
}
|
|
127
134
|
|
|
128
|
-
// Source 3: Fall back to configured
|
|
129
|
-
if (activePrioritySlot === null) {
|
|
135
|
+
// Source 3: Fall back to configured writePriority only if no message override matched
|
|
136
|
+
if (!isFallbackWrite && activePrioritySlot === null) {
|
|
137
|
+
let configuredSlot;
|
|
130
138
|
if (utils.requiresEvaluation(config.writePriorityType)) {
|
|
131
|
-
|
|
139
|
+
configuredSlot = await utils.evaluateNodeProperty(config.writePriority, config.writePriorityType, node, msg);
|
|
140
|
+
} else {
|
|
141
|
+
configuredSlot = config.writePriority;
|
|
142
|
+
}
|
|
143
|
+
// Allow "fallback" as a configured value
|
|
144
|
+
if (configuredSlot === "fallback") {
|
|
145
|
+
isFallbackWrite = true;
|
|
132
146
|
} else {
|
|
133
|
-
|
|
147
|
+
// Validate configured priority (must be 1-16)
|
|
148
|
+
const cp = parseInt(configuredSlot, 10);
|
|
149
|
+
if (isNaN(cp) || cp < 1 || cp > 16) {
|
|
150
|
+
node.isBusy = false;
|
|
151
|
+
return utils.sendError(node, msg, done, `Invalid configured writePriority: ${configuredSlot} (must be 1-16 or fallback)`);
|
|
152
|
+
}
|
|
153
|
+
activePrioritySlot = String(cp);
|
|
134
154
|
}
|
|
135
155
|
}
|
|
136
156
|
} catch (err) {
|
|
@@ -147,12 +167,12 @@ module.exports = function(RED) {
|
|
|
147
167
|
}
|
|
148
168
|
|
|
149
169
|
// Handle Reload
|
|
150
|
-
if (
|
|
170
|
+
if (activePrioritySlot === "reload") {
|
|
151
171
|
RED.events.emit("bldgblocks:global:value-changed", { key: node.varName, store: node.storeName, data: state });
|
|
152
172
|
await utils.setGlobalState(node, node.varName, node.storeName, state);
|
|
153
173
|
|
|
154
|
-
|
|
155
|
-
const statusText = `reload: ${
|
|
174
|
+
const activeLabel = state.activePriority === 'default' ? 'default' : (state.activePriority === 'fallback' ? 'fallback' : `P${state.activePriority}`);
|
|
175
|
+
const statusText = `reload: ${activeLabel}:${state.value}${state.units || ''}`;
|
|
156
176
|
|
|
157
177
|
return utils.sendSuccess(node, { ...state }, done, statusText, null, "dot");
|
|
158
178
|
}
|
|
@@ -168,9 +188,9 @@ module.exports = function(RED) {
|
|
|
168
188
|
return utils.sendError(node, msg, done, `msg.${node.inputProperty} not found or invalid property path`);
|
|
169
189
|
}
|
|
170
190
|
|
|
171
|
-
// Update State
|
|
172
|
-
if (
|
|
173
|
-
state.
|
|
191
|
+
// Update State: either fallback or priority slot
|
|
192
|
+
if (isFallbackWrite) {
|
|
193
|
+
state.fallback = inputValue === null || inputValue === "null" ? null : inputValue;
|
|
174
194
|
} else {
|
|
175
195
|
const priority = parseInt(activePrioritySlot, 10);
|
|
176
196
|
if (isNaN(priority) || priority < 1 || priority > 16) {
|
|
@@ -180,26 +200,22 @@ module.exports = function(RED) {
|
|
|
180
200
|
state.priority[activePrioritySlot] = inputValue;
|
|
181
201
|
}
|
|
182
202
|
}
|
|
183
|
-
|
|
184
|
-
if (state.defaultValue === null || state.defaultValue === "null" || state.defaultValue === undefined) {
|
|
185
|
-
state.defaultValue = node.defaultValue;
|
|
186
|
-
}
|
|
187
203
|
|
|
188
|
-
// Calculate Winner
|
|
204
|
+
// Calculate Winner (includes priorities 1-16, then fallback, then default)
|
|
189
205
|
const { value, priority } = utils.getHighestPriority(state);
|
|
190
206
|
|
|
191
207
|
// Check for change
|
|
192
208
|
if (value === state.value && priority === state.activePriority) {
|
|
193
209
|
// Ensure payload stays in sync with value
|
|
194
210
|
state.payload = state.value;
|
|
195
|
-
// Persist even when output unchanged — the priority array itself changed
|
|
211
|
+
// Persist even when output unchanged — the priority/fallback array itself changed
|
|
196
212
|
await utils.setGlobalState(node, node.varName, node.storeName, state);
|
|
197
213
|
if (node.storeName !== 'default') {
|
|
198
214
|
await utils.setGlobalState(node, node.varName, 'default', state);
|
|
199
215
|
}
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
const noChangeText = `no change: ${
|
|
216
|
+
const writeSlotLabel = isFallbackWrite ? 'fallback' : `P${activePrioritySlot}`;
|
|
217
|
+
const activeLabel = state.activePriority === 'default' ? 'default' : (state.activePriority === 'fallback' ? 'fallback' : `P${state.activePriority}`);
|
|
218
|
+
const noChangeText = `no change: ${writeSlotLabel}:${inputValue} > active: ${activeLabel}:${state.value}${state.units || ''}`;
|
|
203
219
|
utils.setStatusUnchanged(node, noChangeText);
|
|
204
220
|
// Pass message through even if no context change
|
|
205
221
|
send({ ...state });
|
|
@@ -237,9 +253,9 @@ module.exports = function(RED) {
|
|
|
237
253
|
await utils.setGlobalState(node, node.varName, 'default', state);
|
|
238
254
|
}
|
|
239
255
|
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
const statusText = `write: ${
|
|
256
|
+
const writeSlotLabel = isFallbackWrite ? 'fallback' : `P${activePrioritySlot}`;
|
|
257
|
+
const activeLabel = state.activePriority === 'default' ? 'default' : (state.activePriority === 'fallback' ? 'fallback' : `P${state.activePriority}`);
|
|
258
|
+
const statusText = `write: ${writeSlotLabel}:${inputValue}${state.units || ''} > active: ${activeLabel}:${state.value}${state.units || ''}`;
|
|
243
259
|
|
|
244
260
|
RED.events.emit("bldgblocks:global:value-changed", {
|
|
245
261
|
key: node.varName,
|
|
@@ -302,7 +318,8 @@ module.exports = function(RED) {
|
|
|
302
318
|
store: targetNode.storeName,
|
|
303
319
|
data: state
|
|
304
320
|
});
|
|
305
|
-
|
|
321
|
+
const activeLabel = state.activePriority === 'default' ? 'default' : (state.activePriority === 'fallback' ? 'fallback' : `P${state.activePriority}`);
|
|
322
|
+
utils.setStatusOK(targetNode, `cleared: active: ${activeLabel}:${state.value}`);
|
|
306
323
|
targetNode.send({ ...state });
|
|
307
324
|
|
|
308
325
|
res.status(200).json({ message: "Priorities cleared", value: state.value, activePriority: state.activePriority });
|
|
@@ -180,7 +180,7 @@ module.exports = function(RED) {
|
|
|
180
180
|
{ payload: newState === "below" }
|
|
181
181
|
];
|
|
182
182
|
|
|
183
|
-
utils.setStatusChanged(node,
|
|
183
|
+
utils.setStatusChanged(node, `${inputValue.toFixed(2)} -> ${newState}`);
|
|
184
184
|
|
|
185
185
|
node.state = newState;
|
|
186
186
|
send(output);
|
|
@@ -110,8 +110,8 @@ module.exports = function(RED) {
|
|
|
110
110
|
// Check if output value has changed
|
|
111
111
|
const isUnchanged = outputValue === node.lastOutput;
|
|
112
112
|
const statusShape = isUnchanged ? "ring" : "dot";
|
|
113
|
-
utils.setStatusOK(node,
|
|
114
|
-
if (statusShape === "ring") utils.setStatusUnchanged(node,
|
|
113
|
+
utils.setStatusOK(node, `${outputValue.toFixed(2)}`);
|
|
114
|
+
if (statusShape === "ring") utils.setStatusUnchanged(node, `${outputValue.toFixed(2)}`);
|
|
115
115
|
|
|
116
116
|
if (!isUnchanged) {
|
|
117
117
|
node.lastOutput = outputValue;
|
package/nodes/negate-block.js
CHANGED
|
@@ -44,10 +44,10 @@ module.exports = function(RED) {
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
outputValue = -inputValue;
|
|
47
|
-
statusText =
|
|
47
|
+
statusText = `${inputValue.toFixed(2)} -> ${outputValue.toFixed(2)}`;
|
|
48
48
|
} else if (typeof inputValue === "boolean") {
|
|
49
49
|
outputValue = !inputValue;
|
|
50
|
-
statusText =
|
|
50
|
+
statusText = `${inputValue} -> ${outputValue}`;
|
|
51
51
|
} else {
|
|
52
52
|
utils.setStatusError(node, "Unsupported type");
|
|
53
53
|
if (done) done();
|
|
@@ -104,8 +104,17 @@ module.exports = function(RED) {
|
|
|
104
104
|
RED.events.emit("bldgblocks:network:point-update", pointUpdateData);
|
|
105
105
|
|
|
106
106
|
// Passthrough
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
let statusLabel;
|
|
108
|
+
if (msg.activePriority === 'default') {
|
|
109
|
+
statusLabel = 'default';
|
|
110
|
+
} else if (msg.activePriority === 'fallback') {
|
|
111
|
+
statusLabel = 'fallback';
|
|
112
|
+
} else if (typeof msg.activePriority === 'number' || /^\d+$/.test(msg.activePriority)) {
|
|
113
|
+
statusLabel = `P:${msg.activePriority}`;
|
|
114
|
+
} else {
|
|
115
|
+
statusLabel = String(msg.activePriority);
|
|
116
|
+
}
|
|
117
|
+
const statusText = `Passthrough: ${statusLabel}:${msg.value}${msg.units === null ? "" : ` ${msg.units}`}`;
|
|
109
118
|
utils.sendSuccess(node, msg, done, statusText, node.pointId, "ring");
|
|
110
119
|
|
|
111
120
|
} catch (err) {
|
|
@@ -43,16 +43,12 @@ module.exports = function(RED) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Update Priority Logic
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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;
|
|
46
|
+
// Update Priority Logic (only allow priorities 1-16, no default)
|
|
47
|
+
const priority = parseInt(msg.priority, 10);
|
|
48
|
+
if (isNaN(priority) || priority < 1 || priority > 16) {
|
|
49
|
+
return utils.sendError(node, msg, done, `Invalid Priority: ${msg.priority} (must be 1-16)`, msg.pointId);
|
|
55
50
|
}
|
|
51
|
+
state.priority[msg.priority] = newValue;
|
|
56
52
|
|
|
57
53
|
// Calculate Winner
|
|
58
54
|
const result = utils.getHighestPriority(state);
|
|
@@ -63,8 +59,8 @@ module.exports = function(RED) {
|
|
|
63
59
|
// Save (Async) & Emit
|
|
64
60
|
await utils.setGlobalState(node, path, store, state);
|
|
65
61
|
|
|
66
|
-
const prefixReq =
|
|
67
|
-
const prefixAct = state.activePriority === 'default' ? '' : 'P';
|
|
62
|
+
const prefixReq = 'P';
|
|
63
|
+
const prefixAct = state.activePriority === 'default' ? '' : (state.activePriority === 'fallback' ? 'F' : 'P');
|
|
68
64
|
const statusMsg = `Wrote: ${prefixReq}${msg.priority}:${newValue} > Active: ${prefixAct}${state.activePriority}:${state.value}`;
|
|
69
65
|
|
|
70
66
|
msg = { ...state, status: null };
|
package/nodes/on-change-block.js
CHANGED
|
@@ -90,14 +90,7 @@ module.exports = function(RED) {
|
|
|
90
90
|
// Ignore unknown context
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
//
|
|
94
|
-
if (!msg.hasOwnProperty("payload")) {
|
|
95
|
-
utils.setStatusError(node, "missing payload");
|
|
96
|
-
send(msg);
|
|
97
|
-
if (done) done();
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
93
|
+
// Get input value from configured property
|
|
101
94
|
let inputValue;
|
|
102
95
|
try {
|
|
103
96
|
inputValue = RED.util.getMessageProperty(msg, node.inputProperty);
|
package/nodes/priority-block.js
CHANGED
|
@@ -199,16 +199,16 @@ module.exports = function(RED) {
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
// Fall
|
|
202
|
+
// Fall through to fallback, then default (matching global-setter hierarchy)
|
|
203
203
|
if (selectedValue === null) {
|
|
204
|
-
if (
|
|
205
|
-
selectedValue = defaultValue;
|
|
206
|
-
activePriority = "default";
|
|
207
|
-
selectedMessage = messages.default;
|
|
208
|
-
} else if (fallbackValue !== null) {
|
|
204
|
+
if (fallbackValue !== null) {
|
|
209
205
|
selectedValue = fallbackValue;
|
|
210
206
|
activePriority = "fallback";
|
|
211
207
|
selectedMessage = messages.fallback;
|
|
208
|
+
} else if (defaultValue !== null) {
|
|
209
|
+
selectedValue = defaultValue;
|
|
210
|
+
activePriority = "default";
|
|
211
|
+
selectedMessage = messages.default;
|
|
212
212
|
}
|
|
213
213
|
}
|
|
214
214
|
|