node-red-contrib-uos-nats 0.2.99 → 0.2.101

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
@@ -110,12 +110,11 @@ export function parseRegistryStateEvent(buffer) {
110
110
  }
111
111
 
112
112
  // Encode write variables command
113
- export function encodeWriteVariablesCommand(variables) {
114
- // variables: [{id: number, value: any}, ...]
113
+ // Encode write variables command
114
+ export function encodeWriteVariablesCommand(variables, fingerprint = BigInt(0)) {
115
115
  // variables: [{id: number, value: any}, ...]
116
116
  // VariableListT constructor: fingerprint, baseTimestamp, items
117
- // VariableListT constructor: fingerprint, baseTimestamp, items
118
- const varList = new VariableListT(BigInt(0), nowNs(), []);
117
+ const varList = new VariableListT(fingerprint, nowNs(), []);
119
118
  varList.items = variables.map(v => {
120
119
  const varT = new VariableT();
121
120
  varT.id = v.id;
@@ -8,18 +8,14 @@ const providerCache = new Map();
8
8
  const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
9
9
 
10
10
  module.exports = function (RED) {
11
- async function resolveVariableKey(nc, providerId, key, node, payloads) {
11
+ // Helper to get or fetch provider definition
12
+ async function getProviderDefinition(nc, providerId, node, payloads) {
12
13
  const cacheKey = `${providerId}`;
13
14
  const cached = providerCache.get(cacheKey);
14
15
 
15
16
  // Check cache
16
17
  if (cached && (Date.now() - cached.timestamp < CACHE_TTL)) {
17
- const variable = cached.definition.variables.find(v => v.key === key);
18
- if (variable) {
19
- node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id} (cached)`);
20
- // Return full object including type
21
- return { id: variable.id, dataType: variable.dataType };
22
- }
18
+ return cached.definition;
23
19
  }
24
20
 
25
21
  // Query provider definition
@@ -28,7 +24,6 @@ module.exports = function (RED) {
28
24
  const query = payloads.buildReadProviderDefinitionQuery();
29
25
 
30
26
  // Strategy 1: Direct Query (v1.loc.<provider>.def.qry.read)
31
- // This is required for providers like Node-RED itself or simple Python scripts
32
27
  const directSubject = `v1.loc.${providerId}.def.qry.read`;
33
28
  try {
34
29
  const response = await nc.request(directSubject, query, { timeout: 1000 });
@@ -38,7 +33,6 @@ module.exports = function (RED) {
38
33
  }
39
34
 
40
35
  // Strategy 2: Registry Query (v1.loc.registry.providers.<provider>.def.qry.read)
41
- // Fallback for managed providers
42
36
  if (!definition) {
43
37
  const registrySubject = `v1.loc.registry.providers.${providerId}.def.qry.read`;
44
38
  const response = await nc.request(registrySubject, query, { timeout: 2000 });
@@ -55,6 +49,17 @@ module.exports = function (RED) {
55
49
  timestamp: Date.now()
56
50
  });
57
51
 
52
+ return definition;
53
+
54
+ } catch (err) {
55
+ throw new Error(`Failed to fetch definition for '${providerId}': ${err.message}`);
56
+ }
57
+ }
58
+
59
+ async function resolveVariableKey(nc, providerId, key, node, payloads) {
60
+ try {
61
+ const definition = await getProviderDefinition(nc, providerId, node, payloads);
62
+
58
63
  // Find variable by key
59
64
  const variable = definition.variables.find(v => v.key === key);
60
65
  if (!variable) {
@@ -62,13 +67,14 @@ module.exports = function (RED) {
62
67
  }
63
68
 
64
69
  node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id} (Type: ${variable.dataType})`);
65
- return { id: variable.id, dataType: variable.dataType };
70
+ return { id: variable.id, dataType: variable.dataType, fingerprint: definition.fingerprint };
66
71
 
67
72
  } catch (err) {
68
73
  throw new Error(`Failed to resolve key '${key}': ${err.message}`);
69
74
  }
70
75
  }
71
76
 
77
+
72
78
  function DataHubWriteNode(config) {
73
79
  RED.nodes.createNode(this, config);
74
80
  const node = this;
@@ -88,6 +94,7 @@ module.exports = function (RED) {
88
94
  this.variableKey = config.variableKey?.trim();
89
95
  this.resolvedId = null; // Cached resolved ID
90
96
  this.resolvedDataType = null; // Cached Data Type for strict writing
97
+ this.resolvedFingerprint = BigInt(0); // Cached Fingerprint
91
98
  this.payloads = null; // Will be loaded dynamically
92
99
 
93
100
  if (!this.providerId) {
@@ -97,11 +104,7 @@ module.exports = function (RED) {
97
104
  }
98
105
 
99
106
  // Validate: either ID or Key required
100
- if (!this.variableId && !this.variableKey) {
101
- node.error('Either Variable ID or Variable Key is required');
102
- node.status({ fill: 'red', shape: 'dot', text: 'missing variable' });
103
- return;
104
- }
107
+ // Relaxed validation for Batch Mode (handled in Input)
105
108
 
106
109
  // If ID provided and valid, use it
107
110
  if (this.variableId && !isNaN(this.variableId)) {
@@ -110,10 +113,6 @@ module.exports = function (RED) {
110
113
  } else if (this.variableKey) {
111
114
  // Key provided - will resolve on first message
112
115
  node.status({ fill: 'yellow', shape: 'ring', text: 'key needs resolution' });
113
- } else {
114
- node.error('Invalid Variable ID');
115
- node.status({ fill: 'red', shape: 'dot', text: 'invalid ID' });
116
- return;
117
116
  }
118
117
 
119
118
  // Load payloads module dynamically
@@ -148,6 +147,7 @@ module.exports = function (RED) {
148
147
 
149
148
  // Prepare list of variables to write
150
149
  let varsToWrite = [];
150
+ let currentFingerprint = node.resolvedFingerprint || BigInt(0);
151
151
 
152
152
  // MODE 1: Batch Write (Object or Array) AND no configured single variable
153
153
  // If the user configured a Provider but NOT a Variable ID/Key, we assume Dynamic Mode.
@@ -165,8 +165,6 @@ module.exports = function (RED) {
165
165
  items = Object.entries(rawPayload).map(([k, v]) => {
166
166
  // Detect if key is actually an ID (e.g. "5" or "id:5")?
167
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
168
  const asInt = parseInt(k, 10);
171
169
  if (!isNaN(asInt) && String(asInt) === k) {
172
170
  return { id: asInt, value: v };
@@ -184,10 +182,14 @@ module.exports = function (RED) {
184
182
  if (item.key) {
185
183
  // Resolve Key
186
184
  try {
185
+ // Resolving individual keys for batch... this might be slow if uncached.
186
+ // Optimization: resolveVariableKey uses cache.
187
187
  const resolved = await resolveVariableKey(nc, node.providerId, item.key, node, node.payloads);
188
188
  targetId = resolved.id;
189
189
  // Use resolved type if not explicit
190
190
  if (!targetType) targetType = resolved.dataType;
191
+ // Use the fingerprint from the LAST resolution (assuming they come from same provider version)
192
+ if (resolved.fingerprint) currentFingerprint = resolved.fingerprint;
191
193
  } catch (e) {
192
194
  node.warn(`Skipping key '${item.key}': ${e.message}`);
193
195
  continue;
@@ -211,11 +213,7 @@ module.exports = function (RED) {
211
213
  }
212
214
 
213
215
  } 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
-
216
+ // MODE 2: Single Mode
219
217
  if (!isSingleMode) {
220
218
  // No variable configured and primitive payload? Error.
221
219
  node.error("Configuration Error: No Variable configured and payload is not a batch object.");
@@ -226,13 +224,49 @@ module.exports = function (RED) {
226
224
  let varId = node.resolvedId;
227
225
  let varType = node.resolvedDataType;
228
226
 
227
+ // Logic to ensure we have a fingerprint (critical for strict providers)
228
+ // If we have an ID but no fingerprint, we must fetch the definition.
229
+ if (varId && (node.resolvedFingerprint === BigInt(0) || !node.resolvedFingerprint)) {
230
+ try {
231
+ node.status({ fill: 'yellow', shape: 'dot', text: 'fetching definition...' });
232
+ // Reuse resolveVariableKey logic but just to get definition?
233
+ // We can't use resolveVariableKey easily if we don't have a key.
234
+ // But we can peek into the cache directly or force a lookup.
235
+ // Let's call a simplified lookup.
236
+
237
+ // We don't have a dedicated function for "getDefinition", so we construct it or assume key is available?
238
+ // If user selected from list, we might NOT have the key in config (if legacy)?
239
+ // But usually we do.
240
+ // If we don't have key, we can matches by ID.
241
+
242
+ const definitions = await getProviderDefinition(nc, node.providerId, node, node.payloads);
243
+ node.resolvedFingerprint = definitions.fingerprint;
244
+ currentFingerprint = definitions.fingerprint;
245
+
246
+ // Optional: Verify ID matches and get Type
247
+ const foundVar = definitions.variables.find(v => v.id === varId);
248
+ if (foundVar) {
249
+ varType = foundVar.dataType;
250
+ node.resolvedDataType = varType; // update cache
251
+ }
252
+
253
+ } catch (e) {
254
+ node.warn(`Could not fetch fingerprint for provider ${node.providerId}: ${e.message}`);
255
+ // Non-fatal? If strict, write will fail. If not, 0 might work.
256
+ }
257
+ }
258
+
229
259
  if (!varId && node.variableKey) {
230
260
  node.status({ fill: 'yellow', shape: 'dot', text: 'resolving key...' });
231
261
  const resolved = await resolveVariableKey(nc, node.providerId, node.variableKey, node, node.payloads);
232
262
  varId = resolved.id;
233
263
  varType = resolved.dataType;
264
+
234
265
  node.resolvedId = varId;
235
266
  node.resolvedDataType = varType;
267
+ node.resolvedFingerprint = resolved.fingerprint;
268
+ currentFingerprint = resolved.fingerprint;
269
+
236
270
  node.status({ fill: 'green', shape: 'ring', text: 'ready' });
237
271
  }
238
272
 
@@ -244,8 +278,8 @@ module.exports = function (RED) {
244
278
  }
245
279
 
246
280
  // Build write command (Flatbuffer)
247
- // varsToWrite: [{id, value, dataType}]
248
- const writeCommand = node.payloads.encodeWriteVariablesCommand(varsToWrite);
281
+ // Pass the fingerprint (defaults to 0 if not found)
282
+ const writeCommand = node.payloads.encodeWriteVariablesCommand(varsToWrite, currentFingerprint);
249
283
 
250
284
  // Publish write command
251
285
  const subject = `v1.loc.${node.providerId}.vars.cmd.write`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.2.99",
3
+ "version": "0.2.101",
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",