node-red-contrib-uos-nats 1.2.2 → 1.2.4

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.
@@ -150,26 +150,108 @@ module.exports = function (RED) {
150
150
  nc = await connection.acquire();
151
151
  this.status({ fill: 'green', shape: 'dot', text: 'connected' });
152
152
 
153
- // Retry Definition Fetch via NATS if Map is empty AND no manual defs
154
- if (defMap.size === 0 && this.manualDefs.length === 0) {
153
+ // Retry Definition Fetch via NATS if Map is empty OR if we have Heuristic IDs (missingId)
154
+ // Heuristic IDs (ID=Index) are dangerous because they might not match the real NATS IDs (e.g. 291 vs 5)
155
+ const hasMissingIds = Array.from(defMap.values()).some(d => d.missingId);
156
+
157
+ if ((defMap.size === 0 && this.manualDefs.length === 0) || hasMissingIds) {
155
158
  try {
159
+ this.warn(hasMissingIds
160
+ ? `Loaded variables have unresolved IDs (Heuristic). Attempting NATS Discovery to resolve real IDs for ${this.providerId}...`
161
+ : `Attempting NATS Discovery (Direct) for ${this.providerId}...`
162
+ );
163
+
156
164
  // Strategy 1: Direct Provider Query (Standard for many providers)
157
- this.warn(`Attempting NATS Discovery (Direct) for ${this.providerId}...`);
158
- const defMsg = await nc.request(subjects.readProviderDefinitionQuery(this.providerId), payloads.buildReadProviderDefinitionQuery(), { timeout: 1000 });
165
+ const requestOptions = { timeout: 2000 };
166
+ // Reuse serialRequest if available to avoid blocking connection?
167
+ // Discovery is one-off, nc.request is fine, but safer to use serial if we updated input.js fully.
168
+ // Start uses 'nc' directly currently. That's fine for now as it's sequential in 'start'.
169
+
170
+ const defMsg = await nc.request(subjects.readProviderDefinitionQuery(this.providerId), payloads.buildReadProviderDefinitionQuery(), requestOptions);
159
171
  const defs = payloads.decodeProviderDefinition(defMsg.data);
160
- this.warn(`NATS Direct Discovery: Loaded ${defs.length} variables.`);
161
- defs.forEach((def) => defMap.set(def.id, def));
172
+
173
+ if (defs && defs.variables.length > 0) {
174
+ this.warn(`NATS Discovery Successful: Received ${defs.variables.length} definitions with real IDs.`);
175
+ // Overwrite/Update defMap
176
+ // Logic: Match by KEY. DataHub providers should have unique keys.
177
+ // If we have existing "fake" ID 5 for "temp", and NATS says "temp" is ID 291.
178
+ // We need to update defMap to use 291.
179
+
180
+ // Clear Heuristic entries if we trust NATS fully?
181
+ // Or just merge?
182
+ // Safer: Create a lookup from NATS.
183
+ const realMap = new Map();
184
+ defs.variables.forEach(d => realMap.set(d.key, d));
185
+
186
+ // Update existing defMap
187
+ // If we had a heuristic entry, replace it.
188
+ // We rebuild defMap based on NATS mostly, but keep manual fallback?
189
+
190
+ // Let's iterate NATS defs and Populating defMap.
191
+ // Note: NATS Defs don't have 'missingId'.
192
+ defs.variables.forEach(d => {
193
+ // If we overwrite, we lose manual metadata (if any)?
194
+ // REST might have had better metadata? Usually NATS is source of truth for IDs.
195
+ defMap.set(d.id, d);
196
+ });
197
+
198
+ // Use Key Matching to remove old Heuristic entries?
199
+ // Heuristic entries are stored by Key (via fetchProviderVariables logic? No, by ID).
200
+ // We need to clean up the Fake IDs (0..N) if they don't map to real IDs.
201
+ // Actually, if we just add real IDs, we have duplicates?
202
+ // Map is Key=ID.
203
+ // Fake ID 5: { key: 'voltage' }
204
+ // Real ID 291: { key: 'voltage' }
205
+ // If user selected 'voltage', filtering uses KEYS (processStates line 104).
206
+ // Resolution (line 195) iterates values and matches Key.
207
+ // It will find BOTH 5 and 291.
208
+ // targetIds will get [5, 291].
209
+ // DataHub gets request [5, 291].
210
+ // 5 is invalid -> ignored.
211
+ // 291 is valid -> returns value.
212
+ // Result: It works! (Partially, effectively).
213
+
214
+ // But cleaner to remove heuristic ones.
215
+ for (const [id, def] of defMap.entries()) {
216
+ if (def.missingId) {
217
+ const real = realMap.get(def.key);
218
+ if (real && real.id !== id) {
219
+ defMap.delete(id); // Remove fake ID
220
+ }
221
+ }
222
+ }
223
+ this.warn(`IDs resolved via NATS. Mapped ${defs.variables.length} real IDs.`);
224
+ }
225
+
162
226
  } catch (firstErr) {
163
- // Strategy 2: Registry Query (Central lookup, often requires different perms or used by Hub)
227
+ // Strategy 2: Registry Query
164
228
  try {
165
- this.warn(`NATS Direct failed (${firstErr.message}), trying Registry Discovery...`);
166
- // Note: 'registryProviderQuery' accesses the central registry which might proxy the definition
229
+ if (!hasMissingIds) { // Only log if we were truly empty
230
+ this.warn(`NATS Direct failed (${firstErr.message}), trying Registry Discovery...`);
231
+ }
167
232
  const regMsg = await nc.request(subjects.registryProviderQuery(this.providerId), payloads.buildReadProviderDefinitionQuery(), { timeout: 2000 });
168
233
  const defs = payloads.decodeProviderDefinition(regMsg.data);
169
- this.warn(`NATS Registry Discovery: Loaded ${defs.length} variables.`);
170
- defs.forEach((def) => defMap.set(def.id, def));
234
+ if (defs && defs.variables.length > 0) {
235
+ this.warn(`NATS Registry Discovery: Loaded ${defs.variables.length} variables.`);
236
+ // Same merge logic
237
+ const realMap = new Map();
238
+ defs.variables.forEach(d => realMap.set(d.key, d));
239
+ defs.variables.forEach(d => defMap.set(d.id, d));
240
+ for (const [id, def] of defMap.entries()) {
241
+ if (def.missingId) {
242
+ const real = realMap.get(def.key);
243
+ if (real && real.id !== id) {
244
+ defMap.delete(id);
245
+ }
246
+ }
247
+ }
248
+ }
171
249
  } catch (secondErr) {
172
- this.warn(`All Discovery methods failed (REST, NATS Direct, NATS Registry). Please use Manual Definitions (Name:ID). Error: ${secondErr.message}`);
250
+ if (!hasMissingIds) {
251
+ this.warn(`All Discovery methods failed (REST, NATS Direct, NATS Registry). Please use Manual Definitions (Name:ID). Error: ${secondErr.message}`);
252
+ } else {
253
+ this.warn(`NATS ID Resolution failed. Continuing with Heuristic (Index-based) IDs. This generally fails for advanced providers.`);
254
+ }
173
255
  }
174
256
  }
175
257
  }
@@ -230,12 +312,41 @@ module.exports = function (RED) {
230
312
  // Re-process states (lookup names, formatting)
231
313
  const filteredSnapshot = processStates(states);
232
314
 
233
- if (filteredSnapshot.length) {
315
+ if (filteredSnapshot.length > 0) {
234
316
  this.send({ payload: { type: 'snapshot', variables: filteredSnapshot } });
235
317
  } else {
236
318
  if (states.length > 0) {
237
319
  this.warn(`Snapshot received data but everything was filtered out. Check Variable selection. Debug: First raw ID: ${states[0].id}, DefMap has it? ${defMap.has(states[0].id)}`);
238
320
  } else {
321
+ // EMPTY RESPONSE
322
+ // Check if we requested multiple IDs. Some providers fail on bulk read.
323
+ if (targetIds.length > 1) {
324
+ this.warn(`Snapshot Bulk Read failed (Empty List). Retrying ${targetIds.length} variables individually...`);
325
+ const accumulatedStates = [];
326
+
327
+ for (const id of targetIds) {
328
+ try {
329
+ let msg;
330
+ if (typeof connection.serialRequest === 'function') {
331
+ msg = await connection.serialRequest(subjects.readVariablesQuery(this.providerId), payloads.buildReadVariablesQuery([id]), { timeout: 2000 });
332
+ } else {
333
+ msg = await nc.request(subjects.readVariablesQuery(this.providerId), payloads.buildReadVariablesQuery([id]), { timeout: 2000 });
334
+ }
335
+ const singleResponse = payloads.decodeVariableList(ReadVariablesQueryResponse.getRootAsReadVariablesQueryResponse(new flatbuffers.ByteBuffer(msg.data)).variables());
336
+ if (singleResponse.length > 0) {
337
+ accumulatedStates.push(...singleResponse);
338
+ }
339
+ } catch (e) { /* ignore single failures */ }
340
+ }
341
+
342
+ const accumulatedFiltered = processStates(accumulatedStates);
343
+ if (accumulatedFiltered.length > 0) {
344
+ this.send({ payload: { type: 'snapshot', variables: accumulatedFiltered } });
345
+ this.warn(`Snapshot Recovery successful! Retrieved ${accumulatedFiltered.length} items via single requests.`);
346
+ return; // Success
347
+ }
348
+ }
349
+
239
350
  this.warn(`Snapshot received empty list from Data Hub. (Requested ${targetIds.length > 0 ? targetIds.length + ' specific IDs' : 'ALL variables'}).`);
240
351
  }
241
352
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
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",