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 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
- if (targetType === 'BOOLEAN') {
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
- } else if (targetType === 'INT64') {
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
- intVal.value = BigInt(parseInt(val) || 0); // Ensure BigInt
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
- } else if (targetType === 'FLOAT64') {
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
- } else {
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);
@@ -157,12 +157,19 @@
157
157
  const hasId = $inputId.val();
158
158
  const hasKey = $inputKey.val();
159
159
  if (!hasId && !hasKey) {
160
- $('#var-validation-error').show();
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 from the list.</b>
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 the provider's writable variables.</p>
249
- <p>Select a variable from the list to automatically target it.</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: Toggle Boolean</h4>
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: Change String</h4>
263
- <pre><code>msg.payload = "stopped";
264
- return msg;</code></pre>
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>
@@ -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 value = msg.payload;
128
+ const rawPayload = msg.payload;
129
129
 
130
- if (value === undefined || value === null) {
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
- // Resolve variable ID if needed
150
- let varId = node.resolvedId;
151
- let varType = node.resolvedDataType;
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
- if (!varId && node.variableKey) {
154
- node.status({ fill: 'yellow', shape: 'dot', text: 'resolving key...' });
155
- const resolved = await resolveVariableKey(nc, node.providerId, node.variableKey, node, node.payloads);
156
- varId = resolved.id;
157
- varType = resolved.dataType; // Store resolved type
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
- node.resolvedId = varId;
160
- node.resolvedDataType = varType;
161
- node.status({ fill: 'green', shape: 'ring', text: 'ready' });
162
- }
208
+ if (varsToWrite.length === 0) {
209
+ node.warn("No valid variables found in batch payload");
210
+ return;
211
+ }
163
212
 
164
- // Build write command
165
- const writeCommand = node.payloads.encodeWriteVariablesCommand([
166
- {
167
- id: varId,
168
- value: value,
169
- dataType: varType // Pass dataType for strict encoding
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
- node.status({ fill: 'green', shape: 'dot', text: `wrote: ${value}` });
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
- variableId: varId,
184
- variableKey: node.variableKey || null,
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.97",
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",