node-red-contrib-uos-nats 0.2.97 → 0.2.99
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/lib/payloads.js +14 -5
- package/nodes/datahub-write.html +32 -15
- package/nodes/datahub-write.js +109 -25
- package/package.json +1 -1
package/lib/payloads.js
CHANGED
|
@@ -130,22 +130,31 @@ export function encodeWriteVariablesCommand(variables) {
|
|
|
130
130
|
// If targetType is provided, force specific flatbuffer encoding
|
|
131
131
|
if (targetType) {
|
|
132
132
|
targetType = String(targetType).toUpperCase();
|
|
133
|
-
|
|
133
|
+
|
|
134
|
+
if (targetType === 'BOOLEAN' || targetType === 'BOOL') {
|
|
134
135
|
const boolVal = new VariableValueBooleanT();
|
|
135
136
|
boolVal.value = Boolean(val);
|
|
136
137
|
varT.value = boolVal;
|
|
137
138
|
varT.valueType = VariableValue.Boolean;
|
|
138
|
-
}
|
|
139
|
+
}
|
|
140
|
+
else if (['INT8', 'INT16', 'INT32', 'INT64', 'UINT8', 'UINT16', 'UINT32', 'UINT64', 'BYTE', 'SINT', 'USINT', 'INT', 'DINT', 'LINT', 'UDINT', 'ULINT', 'WORD', 'DWORD', 'LWORD'].some(t => targetType.includes(t))) {
|
|
139
141
|
const intVal = new VariableValueInt64T();
|
|
140
|
-
|
|
142
|
+
// Handle BigInt for large values, fallback to parseInt
|
|
143
|
+
try {
|
|
144
|
+
intVal.value = BigInt(val);
|
|
145
|
+
} catch {
|
|
146
|
+
intVal.value = BigInt(parseInt(val) || 0);
|
|
147
|
+
}
|
|
141
148
|
varT.value = intVal;
|
|
142
149
|
varT.valueType = VariableValue.Int64;
|
|
143
|
-
}
|
|
150
|
+
}
|
|
151
|
+
else if (['FLOAT', 'DOUBLE', 'REAL', 'LREAL'].some(t => targetType.includes(t))) {
|
|
144
152
|
const floatVal = new VariableValueFloat64T();
|
|
145
153
|
floatVal.value = Number(val);
|
|
146
154
|
varT.value = floatVal;
|
|
147
155
|
varT.valueType = VariableValue.Float64;
|
|
148
|
-
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
149
158
|
// STRING or default
|
|
150
159
|
const strVal = new VariableValueStringT();
|
|
151
160
|
strVal.value = (typeof val === 'object') ? JSON.stringify(val) : String(val);
|
package/nodes/datahub-write.html
CHANGED
|
@@ -157,12 +157,19 @@
|
|
|
157
157
|
const hasId = $inputId.val();
|
|
158
158
|
const hasKey = $inputKey.val();
|
|
159
159
|
if (!hasId && !hasKey) {
|
|
160
|
-
|
|
160
|
+
// Valid in Batch Mode?
|
|
161
|
+
// We can show a hint that batch mode is active if nothing selected.
|
|
162
|
+
$('#var-validation-error').hide();
|
|
163
|
+
$('#batch-mode-hint').show();
|
|
161
164
|
} else {
|
|
162
165
|
$('#var-validation-error').hide();
|
|
166
|
+
$('#batch-mode-hint').hide();
|
|
163
167
|
}
|
|
164
168
|
};
|
|
165
169
|
$inputId.on('change keyup', validateVar);
|
|
170
|
+
|
|
171
|
+
// Initial check
|
|
172
|
+
validateVar();
|
|
166
173
|
},
|
|
167
174
|
oneditsave: function () {
|
|
168
175
|
// Nothing special to save, inputs are handled automatically
|
|
@@ -219,7 +226,12 @@
|
|
|
219
226
|
<input type="hidden" id="node-input-variableKey">
|
|
220
227
|
|
|
221
228
|
<div id="var-validation-error" class="form-tips" style="display:none; color:#c00; margin-top:10px;">
|
|
222
|
-
<i class="fa fa-warning"></i> <b>Please select a variable
|
|
229
|
+
<i class="fa fa-warning"></i> <b>Please select a variable (or rely on Batch Mode via JSON).</b>
|
|
230
|
+
</div>
|
|
231
|
+
|
|
232
|
+
<div id="batch-mode-hint" class="form-tips" style="display:none; color:#666; margin-top:10px;">
|
|
233
|
+
<i class="fa fa-info-circle"></i> <b>No variable selected.</b> Node enters <b>Batch Mode</b>.<br>
|
|
234
|
+
Send a JSON object (<code>{"key": value}</code>) to write variables dynamically.
|
|
223
235
|
</div>
|
|
224
236
|
|
|
225
237
|
<div class="form-tips" style="margin-top:10px;">
|
|
@@ -244,24 +256,29 @@
|
|
|
244
256
|
<p>The target provider to write to (e.g., <code>u_os_adm</code>, <code>u_os_sbm</code>).</p>
|
|
245
257
|
<p><b>Find it:</b> u-OS Web UI → Data Hub → Providers</p>
|
|
246
258
|
|
|
247
|
-
<h4>Select Variable</h4>
|
|
248
|
-
<p>Click <b>Load Variables</b> to browse
|
|
249
|
-
<p>
|
|
259
|
+
<h4>Select Variable (Single Mode)</h4>
|
|
260
|
+
<p>Click <b>Load Variables</b> to browse. Select a variable to target it specifically.</p>
|
|
261
|
+
<p>If selected, <code>msg.payload</code> is written as the value.</p>
|
|
262
|
+
|
|
263
|
+
<h4>Batch Mode (Dynamic)</h4>
|
|
264
|
+
<p>If <b>NO variable is selected</b>, you can write multiple variables by sending a JSON object:</p>
|
|
265
|
+
<pre><code>msg.payload = {
|
|
266
|
+
"machine.status": true,
|
|
267
|
+
"machine.speed": 100
|
|
268
|
+
};</code></pre>
|
|
269
|
+
<p>The node resolves keys to IDs automatically.</p>
|
|
250
270
|
|
|
251
271
|
<h3>Input Format</h3>
|
|
252
272
|
<p>Send the new value as <code>msg.payload</code>:</p>
|
|
253
273
|
|
|
254
|
-
<h4>Example:
|
|
255
|
-
<pre><code>msg.payload = false
|
|
256
|
-
return msg;</code></pre>
|
|
257
|
-
|
|
258
|
-
<h4>Example: Update Number</h4>
|
|
259
|
-
<pre><code>msg.payload = 42.5;
|
|
260
|
-
return msg;</code></pre>
|
|
274
|
+
<h4>Example: Single Write</h4>
|
|
275
|
+
<pre><code>msg.payload = false;</code></pre>
|
|
261
276
|
|
|
262
|
-
<h4>Example:
|
|
263
|
-
<pre><code>msg.payload =
|
|
264
|
-
|
|
277
|
+
<h4>Example: Batch Write (JSON)</h4>
|
|
278
|
+
<pre><code>msg.payload = {
|
|
279
|
+
"channel_0.do": true,
|
|
280
|
+
"channel_1.do": false
|
|
281
|
+
};</code></pre>
|
|
265
282
|
|
|
266
283
|
<h3>Output</h3>
|
|
267
284
|
<p>Outputs a confirmation message when the write command is sent:</p>
|
package/nodes/datahub-write.js
CHANGED
|
@@ -125,9 +125,9 @@ module.exports = function (RED) {
|
|
|
125
125
|
|
|
126
126
|
// Handle incoming messages
|
|
127
127
|
node.on('input', async function (msg) {
|
|
128
|
-
const
|
|
128
|
+
const rawPayload = msg.payload;
|
|
129
129
|
|
|
130
|
-
if (
|
|
130
|
+
if (rawPayload === undefined || rawPayload === null) {
|
|
131
131
|
node.warn('msg.payload is empty, nothing to write');
|
|
132
132
|
return;
|
|
133
133
|
}
|
|
@@ -146,44 +146,128 @@ module.exports = function (RED) {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
//
|
|
150
|
-
let
|
|
151
|
-
|
|
149
|
+
// Prepare list of variables to write
|
|
150
|
+
let varsToWrite = [];
|
|
151
|
+
|
|
152
|
+
// MODE 1: Batch Write (Object or Array) AND no configured single variable
|
|
153
|
+
// If the user configured a Provider but NOT a Variable ID/Key, we assume Dynamic Mode.
|
|
154
|
+
// OR if the user configured a Variable, we strictly use that (Single Mode).
|
|
155
|
+
const isSingleMode = (node.variableId !== null || !!node.variableKey);
|
|
156
|
+
|
|
157
|
+
if (!isSingleMode && typeof rawPayload === 'object' && !Buffer.isBuffer(rawPayload)) {
|
|
158
|
+
// Normalize input to Array of { key/id, value }
|
|
159
|
+
let items = [];
|
|
160
|
+
if (Array.isArray(rawPayload)) {
|
|
161
|
+
// Array: [{id:1, value:val}, {key:'k', value:val}]
|
|
162
|
+
items = rawPayload;
|
|
163
|
+
} else {
|
|
164
|
+
// Object: { "key": val, "id:1": val }
|
|
165
|
+
items = Object.entries(rawPayload).map(([k, v]) => {
|
|
166
|
+
// Detect if key is actually an ID (e.g. "5" or "id:5")?
|
|
167
|
+
// Easier: treat all keys as Keys, unless user passes explicit Array.
|
|
168
|
+
// But user map might be { "1": val, "machine.temp": val }.
|
|
169
|
+
// We can check if k is Int.
|
|
170
|
+
const asInt = parseInt(k, 10);
|
|
171
|
+
if (!isNaN(asInt) && String(asInt) === k) {
|
|
172
|
+
return { id: asInt, value: v };
|
|
173
|
+
}
|
|
174
|
+
return { key: k, value: v };
|
|
175
|
+
});
|
|
176
|
+
}
|
|
152
177
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
// Process Items
|
|
179
|
+
for (const item of items) {
|
|
180
|
+
let targetId = item.id;
|
|
181
|
+
let targetType = item.dataType; // Optional explicit type
|
|
182
|
+
|
|
183
|
+
if (targetId === undefined) {
|
|
184
|
+
if (item.key) {
|
|
185
|
+
// Resolve Key
|
|
186
|
+
try {
|
|
187
|
+
const resolved = await resolveVariableKey(nc, node.providerId, item.key, node, node.payloads);
|
|
188
|
+
targetId = resolved.id;
|
|
189
|
+
// Use resolved type if not explicit
|
|
190
|
+
if (!targetType) targetType = resolved.dataType;
|
|
191
|
+
} catch (e) {
|
|
192
|
+
node.warn(`Skipping key '${item.key}': ${e.message}`);
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
node.warn(`Skipping item without id or key: ${JSON.stringify(item)}`);
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
varsToWrite.push({
|
|
202
|
+
id: targetId,
|
|
203
|
+
value: item.value,
|
|
204
|
+
dataType: targetType
|
|
205
|
+
});
|
|
206
|
+
}
|
|
158
207
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
208
|
+
if (varsToWrite.length === 0) {
|
|
209
|
+
node.warn("No valid variables found in batch payload");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
163
212
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
213
|
+
} else {
|
|
214
|
+
// MODE 2: Single Mode (Configured in Node or Primitive Payload)
|
|
215
|
+
// If Single Mode is active, we IGNORE the structure of obj/array and write the WHOLE msg.payload
|
|
216
|
+
// to the configured target variable. (e.g. payload IS the value).
|
|
217
|
+
// UNLESS strict check? No, standard behavior is payload=value.
|
|
218
|
+
|
|
219
|
+
if (!isSingleMode) {
|
|
220
|
+
// No variable configured and primitive payload? Error.
|
|
221
|
+
node.error("Configuration Error: No Variable configured and payload is not a batch object.");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Resolve variable ID if needed
|
|
226
|
+
let varId = node.resolvedId;
|
|
227
|
+
let varType = node.resolvedDataType;
|
|
228
|
+
|
|
229
|
+
if (!varId && node.variableKey) {
|
|
230
|
+
node.status({ fill: 'yellow', shape: 'dot', text: 'resolving key...' });
|
|
231
|
+
const resolved = await resolveVariableKey(nc, node.providerId, node.variableKey, node, node.payloads);
|
|
232
|
+
varId = resolved.id;
|
|
233
|
+
varType = resolved.dataType;
|
|
234
|
+
node.resolvedId = varId;
|
|
235
|
+
node.resolvedDataType = varType;
|
|
236
|
+
node.status({ fill: 'green', shape: 'ring', text: 'ready' });
|
|
170
237
|
}
|
|
171
|
-
|
|
238
|
+
|
|
239
|
+
varsToWrite.push({
|
|
240
|
+
id: varId,
|
|
241
|
+
value: rawPayload,
|
|
242
|
+
dataType: varType
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Build write command (Flatbuffer)
|
|
247
|
+
// varsToWrite: [{id, value, dataType}]
|
|
248
|
+
const writeCommand = node.payloads.encodeWriteVariablesCommand(varsToWrite);
|
|
172
249
|
|
|
173
250
|
// Publish write command
|
|
174
251
|
const subject = `v1.loc.${node.providerId}.vars.cmd.write`;
|
|
175
252
|
nc.publish(subject, writeCommand);
|
|
176
253
|
|
|
177
|
-
|
|
254
|
+
const count = varsToWrite.length;
|
|
255
|
+
node.status({ fill: 'green', shape: 'dot', text: `wrote ${count} var(s)` });
|
|
178
256
|
|
|
179
257
|
// Output confirmation
|
|
180
258
|
msg.payload = {
|
|
181
259
|
success: true,
|
|
182
260
|
providerId: node.providerId,
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
value: value
|
|
261
|
+
count: count,
|
|
262
|
+
firstValue: varsToWrite[0].value
|
|
186
263
|
};
|
|
264
|
+
// If single write, maintain legacy output structure for compatibility?
|
|
265
|
+
if (isSingleMode) {
|
|
266
|
+
msg.payload.variableId = varsToWrite[0].id;
|
|
267
|
+
msg.payload.variableKey = node.variableKey || null;
|
|
268
|
+
msg.payload.value = varsToWrite[0].value;
|
|
269
|
+
}
|
|
270
|
+
|
|
187
271
|
node.send(msg);
|
|
188
272
|
|
|
189
273
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-uos-nats",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.99",
|
|
4
4
|
"description": "Node-RED nodes for Weidmüller u-OS Data Hub. Read, write, and provide variables via NATS protocol with OAuth2 authentication. Features: Variable Key resolution, custom icons, example flows, and provider definition caching.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "IoTUeli",
|