node-red-contrib-uos-nats 0.1.43 → 0.1.45

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
@@ -41,7 +41,9 @@ The config node automatically fetches tokens via Client Credentials flow.
41
41
  - Select the u-OS config node.
42
42
  - **Providers**: Click the **Refresh** button to list available providers. This list can be cached from the "Test Connection" button in the Config Node.
43
43
  - **Variables**: Select a provider, then click *Refresh* to load its variables. **Note:** To load variables, the Config Node **must be deployed** first!
44
- - **Variables**: Select a provider, then click *Refresh* to load its variables. **Note:** To load variables, the Config Node **must be deployed** first!
44
+ - **Variables**: Select a provider, then click *Refresh* to load its variables.
45
+ - **Note:** To load variables, the Config Node **must be deployed** first!
46
+ - **Manual Mapping**: If Discovery fails (e.g. permission errors), you can enter variables manually in the format `Name:ID` (e.g. `pressure:5, temp:6`). This bypasses the API/NATS lookup.
45
47
  - **Input Port Triggers**: The Input Node now accepts messages on its input port. Sending any message (e.g. from an **Inject** or **Timestamp** node) triggers an immediate snapshot of all values. This replaces the internal "Polling Interval" setting, giving you full control via standard Node-RED flows.
46
48
  - **Troubleshooting**:
47
49
  - If lists remain empty, check the Node-RED debug tab. Ensure your OAuth client has `hub.variables.readonly` permission.
@@ -7,6 +7,7 @@
7
7
  connection: { type: 'uos-config', required: true },
8
8
  providerId: { value: '', required: false },
9
9
  variablesText: { value: '', required: false },
10
+ manualVariables: { value: '', required: false },
10
11
  pollingInterval: { value: 0, required: false, validate: RED.validators.number() },
11
12
  },
12
13
  inputs: 1,
@@ -346,6 +347,11 @@
346
347
  <label></label>
347
348
  <div id="datahub-variables-status" class="form-tips">Leave empty to listen to every variable.</div>
348
349
  </div>
350
+ <div class="form-row">
351
+ <label for="node-input-manualVariables" style="width: auto;"><i class="fa fa-wrench"></i> Manual Definitions</label>
352
+ <input type="text" id="node-input-manualVariables" placeholder="e.g. pressure:5, temp:6">
353
+ </div>
354
+ <div class="form-tips">Use <code>Name:ID</code> format to provide definitions if API Discovery fails (fixes "Empty List" issues).</div>
349
355
  </script>
350
356
 
351
357
  <script type="text/html" data-help-name="datahub-input">
@@ -29,16 +29,38 @@ module.exports = function (RED) {
29
29
  this.providerId = config.providerId || 'sampleprovider';
30
30
  this.pollingInterval = parseInt(config.pollingInterval, 10) || 0; // ms, 0 = disabled (default)
31
31
  const text = config.variablesText || '';
32
+ const manualText = config.manualVariables || '';
33
+
34
+ // Parse variables: Standard list of names to filter
32
35
  this.variables = text
33
36
  .split(',')
34
37
  .map((entry) => (entry ? String(entry).trim() : ''))
35
38
  .filter((entry) => entry.length > 0);
36
39
 
40
+ // Parse Manual Definitions "Name:ID"
41
+ this.manualDefs = [];
42
+ if (manualText) {
43
+ manualText.split(',').forEach(entry => {
44
+ let trimmed = entry ? String(entry).trim() : '';
45
+ if (trimmed.includes(':')) {
46
+ const parts = trimmed.split(':');
47
+ const name = parts[0].trim();
48
+ const id = parseInt(parts[1].trim(), 10);
49
+ if (name && !isNaN(id)) {
50
+ this.manualDefs.push({ id, key: name });
51
+ }
52
+ }
53
+ });
54
+ }
55
+
37
56
  let nc;
38
57
  let sub;
39
58
  let closed = false;
40
59
  const defMap = new Map();
41
60
 
61
+ // Pre-populate raw map with manual definitions
62
+ this.manualDefs.forEach(d => defMap.set(d.id, { ...d, type: 'MANUAL', dataType: 'UNKNOWN', access: 'READ_ONLY' }));
63
+
42
64
  const shouldInclude = (key) => {
43
65
  if (!this.variables.length) {
44
66
  return true;
@@ -61,8 +83,9 @@ module.exports = function (RED) {
61
83
  }));
62
84
 
63
85
  // Warn if we have filters but no definitions (all keys will be raw IDs)
86
+ // Skip warning if we have manual definitions or successfully loaded map
64
87
  if (this.variables.length > 0 && defMap.size === 0 && mapped.length > 0) {
65
- this.warnOnce('Filtering active but Variable Definitions failed to load (API Error). Names cannot be resolved, so filters will likely block all data. Please fix the API error (check Provider ID/Permissions).');
88
+ this.warnOnce('Filtering active but Variable Definitions failed to load (API Error). Names cannot be resolved. Try using "Name:ID" format to manually map variables.');
66
89
  }
67
90
 
68
91
  return mapped.filter((state) => shouldInclude(state.key));
@@ -85,29 +108,37 @@ module.exports = function (RED) {
85
108
  const { ReadVariablesQueryResponse } = readRespMod;
86
109
  const { VariablesChangedEvent } = changeEventMod;
87
110
 
111
+ // Try to fetch definitions (Discovery), but respect manual defs
88
112
  try {
113
+ // Verify connection first or use connection helper?
114
+ // fetchProviderVariables uses HTTP, so it's independent of 'nc'
89
115
  const definitions = await connection.fetchProviderVariables(this.providerId);
90
116
  definitions.forEach((def) => defMap.set(def.id, def));
91
117
  } catch (e) {
92
- this.warn(`REST API failed (${e.message}). Attempting NATS fallback...`);
118
+ // Only warn if we don't have manual defs to fall back on
119
+ if (this.manualDefs.length === 0) {
120
+ this.warn(`REST API failed (${e.message}). Attempting NATS fallback...`);
121
+ } else {
122
+ this.warn(`REST API Discovery failed, but using ${this.manualDefs.length} manual definitions.`);
123
+ }
124
+
93
125
  try {
94
126
  // Fallback: Fetch definitions via NATS
95
- // We need to load the response type dynamically as well if not already loaded
127
+ // ... import and logic remains ...
128
+ // We can skip NATS fetch if manual defs are sufficient?
129
+ // Better to try anyway to get Metadata (DataType etc).
96
130
  const { ReadProviderDefinitionQueryResponse } = await import(pathToFileURL(path.join(__dirname, '..', 'lib', 'fbs', 'weidmueller', 'ucontrol', 'hub', 'read-provider-definition-query-response.js')).href);
97
-
98
- // Ensure we have a connection even if start() isn't fully done (we might need to move this)
99
- // But 'nc' is acquired below. Let's acquire it first if possible, or do this AFTER acquired.
100
- // Refactoring: We will move this logic 'down' after nc is acquired.
101
131
  } catch (natsErr) {
102
- this.warn(`NATS definition fetch also failed: ${natsErr.message}`);
132
+ if (this.manualDefs.length === 0) this.warn(`NATS definition fetch also failed: ${natsErr.message}`);
103
133
  }
104
134
  }
105
135
 
136
+
106
137
  nc = await connection.acquire();
107
138
  this.status({ fill: 'green', shape: 'dot', text: 'connected' });
108
139
 
109
- // Retry Definition Fetch via NATS if Map is empty
110
- if (defMap.size === 0) {
140
+ // Retry Definition Fetch via NATS if Map is empty AND no manual defs
141
+ if (defMap.size === 0 && this.manualDefs.length === 0) {
111
142
  try {
112
143
  this.warn(`Attempting to fetch definitions via NATS for ${this.providerId}...`);
113
144
  const defMsg = await nc.request(`v1.loc.${this.providerId}.def.qry.read`, new Uint8Array(0), { timeout: 2000 });
@@ -148,14 +179,12 @@ module.exports = function (RED) {
148
179
  }
149
180
  }
150
181
  if (targetIds.length === 0 && defMap.size > 0) {
151
- // We have definitions but found no matching keys for our config.
152
- // This implies misconfiguration or name changes.
153
- const sampleKeys = Array.from(defMap.values()).map(d => `'${d.key}'`).slice(0, 5).join(', ');
154
- const reqSample = this.variables.map(v => `'${v}'`).slice(0, 5).join(', ');
182
+ // Warning logic ...
183
+ const sampleKeys = Array.from(defMap.values()).filter(d => d.type !== 'MANUAL').map(d => `'${d.key}'`).slice(0, 5).join(', ');
155
184
  this.warn(`Snapshot Warning: None of the ${this.variables.length} configured variables were found in the Provider Definition.`);
156
- this.warn(` -> Requested: [${reqSample}...]`);
157
- this.warn(` -> Available in DefMap (Size: ${defMap.size}): [${sampleKeys}...]`);
158
- this.warn(` -> Please check for typos or prefix mismatches.`);
185
+ if (!sampleKeys && this.manualDefs.length > 0) {
186
+ this.warn(' -> Manual Definitions are configured but did not match the requested variable names? Check capitalization.');
187
+ }
159
188
  }
160
189
  }
161
190
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "description": "Node-RED nodes for u-OS Data Hub via NATS",
5
5
  "repository": {
6
6
  "type": "git",