@bldgblocks/node-red-contrib-control 0.2.0 → 0.2.2
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/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Search for the package name and add to your project.
|
|
|
40
40
|
```
|
|
41
41
|
# Navigate to Node-RED user directory (varies by installation)
|
|
42
42
|
- $ cd ~/.node-red
|
|
43
|
-
- $ npm install node-red-contrib-
|
|
43
|
+
- $ npm install node-red-contrib-control
|
|
44
44
|
# then restart node-red
|
|
45
45
|
```
|
|
46
46
|
|
package/nodes/global-setter.js
CHANGED
|
@@ -96,19 +96,21 @@ module.exports = function(RED) {
|
|
|
96
96
|
// 1. msg.priority (number 1-16 or "default") — explicit per-message override
|
|
97
97
|
// 2. msg.context ("priority1"–"priority16" or "default") — tagged-input pattern (matches priority-block)
|
|
98
98
|
// 3. Configured writePriority (dropdown / msg / flow typed-input)
|
|
99
|
+
// Use local variable — never mutate node.writePriority so the config default is preserved across messages
|
|
100
|
+
let activePrioritySlot = null;
|
|
99
101
|
try {
|
|
100
|
-
if (msg.hasOwnProperty("priority")) {
|
|
101
|
-
// Source 1: msg.priority (direct number or "default")
|
|
102
|
+
if (msg.hasOwnProperty("priority") && (typeof msg.priority === "number" || typeof msg.priority === "string")) {
|
|
103
|
+
// Source 1: msg.priority (direct number or "default") — skip objects (e.g. priority array from state)
|
|
102
104
|
const mp = msg.priority;
|
|
103
105
|
if (mp === "default") {
|
|
104
|
-
|
|
106
|
+
activePrioritySlot = "default";
|
|
105
107
|
} else {
|
|
106
108
|
const p = parseInt(mp, 10);
|
|
107
109
|
if (isNaN(p) || p < 1 || p > 16) {
|
|
108
110
|
node.isBusy = false;
|
|
109
111
|
return utils.sendError(node, msg, done, `Invalid msg.priority: ${mp}`);
|
|
110
112
|
}
|
|
111
|
-
|
|
113
|
+
activePrioritySlot = String(p);
|
|
112
114
|
}
|
|
113
115
|
} else if (msg.hasOwnProperty("context") && typeof msg.context === "string") {
|
|
114
116
|
// Source 2: msg.context tagged-input ("priority8", "default", etc.)
|
|
@@ -116,21 +118,20 @@ module.exports = function(RED) {
|
|
|
116
118
|
const ctx = msg.context;
|
|
117
119
|
const priorityMatch = /^priority([1-9]|1[0-6])$/.exec(ctx);
|
|
118
120
|
if (priorityMatch) {
|
|
119
|
-
|
|
121
|
+
activePrioritySlot = priorityMatch[1];
|
|
120
122
|
} else if (ctx === "default") {
|
|
121
|
-
|
|
123
|
+
activePrioritySlot = "default";
|
|
124
|
+
}
|
|
125
|
+
// Other contexts (e.g. "reload") leave activePrioritySlot null → falls to config
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Source 3: Fall back to configured typed-input when no message override matched
|
|
129
|
+
if (activePrioritySlot === null) {
|
|
130
|
+
if (utils.requiresEvaluation(config.writePriorityType)) {
|
|
131
|
+
activePrioritySlot = await utils.evaluateNodeProperty(config.writePriority, config.writePriorityType, node, msg);
|
|
132
|
+
} else {
|
|
133
|
+
activePrioritySlot = config.writePriority;
|
|
122
134
|
}
|
|
123
|
-
// Other contexts (e.g. "reload") fall through — config stays as-is
|
|
124
|
-
} else {
|
|
125
|
-
// Source 3: Configured typed-input (dropdown, msg path, flow variable)
|
|
126
|
-
const evaluations = [];
|
|
127
|
-
evaluations.push(
|
|
128
|
-
utils.requiresEvaluation(config.writePriorityType)
|
|
129
|
-
? utils.evaluateNodeProperty(config.writePriority, config.writePriorityType, node, msg)
|
|
130
|
-
: Promise.resolve(node.writePriority)
|
|
131
|
-
);
|
|
132
|
-
const results = await Promise.all(evaluations);
|
|
133
|
-
node.writePriority = results[0];
|
|
134
135
|
}
|
|
135
136
|
} catch (err) {
|
|
136
137
|
throw new Error(`Property Eval Error: ${err.message}`);
|
|
@@ -168,15 +169,15 @@ module.exports = function(RED) {
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
// Update State
|
|
171
|
-
if (
|
|
172
|
+
if (activePrioritySlot === 'default') {
|
|
172
173
|
state.defaultValue = inputValue === null || inputValue === "null" ? node.defaultValue : inputValue;
|
|
173
174
|
} else {
|
|
174
|
-
const priority = parseInt(
|
|
175
|
+
const priority = parseInt(activePrioritySlot, 10);
|
|
175
176
|
if (isNaN(priority) || priority < 1 || priority > 16) {
|
|
176
|
-
return utils.sendError(node, msg, done, `Invalid priority: ${
|
|
177
|
+
return utils.sendError(node, msg, done, `Invalid priority: ${activePrioritySlot}`);
|
|
177
178
|
}
|
|
178
179
|
if (inputValue !== undefined) {
|
|
179
|
-
state.priority[
|
|
180
|
+
state.priority[activePrioritySlot] = inputValue;
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
@@ -196,9 +197,9 @@ module.exports = function(RED) {
|
|
|
196
197
|
if (node.storeName !== 'default') {
|
|
197
198
|
await utils.setGlobalState(node, node.varName, 'default', state);
|
|
198
199
|
}
|
|
199
|
-
prefix = `${
|
|
200
|
+
prefix = `${activePrioritySlot === 'default' ? '' : 'P'}`;
|
|
200
201
|
const statePrefix = `${state.activePriority === 'default' ? '' : 'P'}`;
|
|
201
|
-
const noChangeText = `no change: ${prefix}${
|
|
202
|
+
const noChangeText = `no change: ${prefix}${activePrioritySlot}:${inputValue} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
|
|
202
203
|
utils.setStatusUnchanged(node, noChangeText);
|
|
203
204
|
// Pass message through even if no context change
|
|
204
205
|
send({ ...state });
|
|
@@ -236,9 +237,9 @@ module.exports = function(RED) {
|
|
|
236
237
|
await utils.setGlobalState(node, node.varName, 'default', state);
|
|
237
238
|
}
|
|
238
239
|
|
|
239
|
-
prefix = `${
|
|
240
|
+
prefix = `${activePrioritySlot === 'default' ? '' : 'P'}`;
|
|
240
241
|
const statePrefix = `${state.activePriority === 'default' ? '' : 'P'}`;
|
|
241
|
-
const statusText = `write: ${prefix}${
|
|
242
|
+
const statusText = `write: ${prefix}${activePrioritySlot}:${inputValue}${state.units || ''} > active: ${statePrefix}${state.activePriority}:${state.value}${state.units || ''}`;
|
|
242
243
|
|
|
243
244
|
RED.events.emit("bldgblocks:global:value-changed", {
|
|
244
245
|
key: node.varName,
|
|
@@ -105,7 +105,7 @@ module.exports = function(RED) {
|
|
|
105
105
|
|
|
106
106
|
// Passthrough
|
|
107
107
|
const prefix = msg.activePriority === 'default' ? '' : 'P';
|
|
108
|
-
const statusText = `Passthrough: ${prefix}${msg.activePriority}:${msg.value}${msg.units}`;
|
|
108
|
+
const statusText = `Passthrough: ${prefix}${msg.activePriority}:${msg.value}${msg.units === null ? "" : ` ${msg.units}`}`;
|
|
109
109
|
utils.sendSuccess(node, msg, done, statusText, node.pointId, "ring");
|
|
110
110
|
|
|
111
111
|
} catch (err) {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
<tr style="border-bottom:2px solid #ccc;text-align:left;">
|
|
30
30
|
<th style="padding:4px;width:28px;"><input type="checkbox" id="node-input-select-all" title="Select all"></th>
|
|
31
31
|
<th style="padding:4px;width:70px;">ID</th>
|
|
32
|
+
<th style="padding:4px;width:30px;" title="Writable"><i class="fa fa-pencil"></i></th>
|
|
32
33
|
<th style="padding:4px;">Path</th>
|
|
33
34
|
<th style="padding:4px;width:30px;"></th>
|
|
34
35
|
</tr>
|
|
@@ -106,7 +107,7 @@
|
|
|
106
107
|
$selectAll.prop('checked', false);
|
|
107
108
|
|
|
108
109
|
if (!points.length) {
|
|
109
|
-
$tbody.append('<tr><td colspan="
|
|
110
|
+
$tbody.append('<tr><td colspan="5" style="padding:8px;color:#999;">No points defined (deploy first if newly added)</td></tr>');
|
|
110
111
|
$status.text("");
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
@@ -141,6 +142,13 @@
|
|
|
141
142
|
|
|
142
143
|
const $tdId = $('<td>').css({ padding: '3px 4px' }).append($idInput);
|
|
143
144
|
|
|
145
|
+
// Writable checkbox
|
|
146
|
+
const $wrCb = $('<input type="checkbox" class="point-writable">')
|
|
147
|
+
.prop('checked', !!pt.writable)
|
|
148
|
+
.attr('data-original-writable', pt.writable ? '1' : '0')
|
|
149
|
+
.attr('title', 'Allow network writes to this point');
|
|
150
|
+
const $tdWr = $('<td>').css({ padding: '3px 4px', textAlign: 'center' }).append($wrCb);
|
|
151
|
+
|
|
144
152
|
// Path display
|
|
145
153
|
const displayPath = pt.path && pt.path !== "not ready" ? pt.path : (pt.editorName || 'not deployed');
|
|
146
154
|
const $tdPath = $('<td>').css({ padding: '3px 4px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '200px' })
|
|
@@ -157,7 +165,7 @@
|
|
|
157
165
|
});
|
|
158
166
|
const $tdBtn = $('<td>').css({ padding: '3px 4px' }).append($btn);
|
|
159
167
|
|
|
160
|
-
$tr.append($tdCb, $tdId, $tdPath, $tdBtn);
|
|
168
|
+
$tr.append($tdCb, $tdId, $tdWr, $tdPath, $tdBtn);
|
|
161
169
|
$tbody.append($tr);
|
|
162
170
|
});
|
|
163
171
|
|
|
@@ -267,11 +275,25 @@
|
|
|
267
275
|
changeCount++;
|
|
268
276
|
}
|
|
269
277
|
}
|
|
278
|
+
|
|
279
|
+
// Check writable change
|
|
280
|
+
const $wrCb = $(this).find('.point-writable');
|
|
281
|
+
const newWr = $wrCb.is(':checked');
|
|
282
|
+
const origWr = $wrCb.attr('data-original-writable') === '1';
|
|
283
|
+
if (newWr !== origWr) {
|
|
284
|
+
const editorNode = RED.nodes.node(nid);
|
|
285
|
+
if (editorNode) {
|
|
286
|
+
editorNode.writable = newWr;
|
|
287
|
+
editorNode.changed = true;
|
|
288
|
+
editorNode.dirty = true;
|
|
289
|
+
changeCount++;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
270
292
|
});
|
|
271
293
|
|
|
272
294
|
if (changeCount > 0) {
|
|
273
295
|
RED.nodes.dirty(true);
|
|
274
|
-
RED.notify(changeCount + "
|
|
296
|
+
RED.notify(changeCount + " change" + (changeCount !== 1 ? "s" : "") + " applied. Deploy to take effect.", "success");
|
|
275
297
|
}
|
|
276
298
|
}
|
|
277
299
|
});
|