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

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.
@@ -29,9 +29,24 @@ 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
+
33
+ // Parse variables, supporting "Name:ID" format for manual mapping
34
+ this.manualDefs = [];
32
35
  this.variables = text
33
36
  .split(',')
34
- .map((entry) => (entry ? String(entry).trim() : ''))
37
+ .map((entry) => {
38
+ let trimmed = entry ? String(entry).trim() : '';
39
+ if (trimmed.includes(':')) {
40
+ const parts = trimmed.split(':');
41
+ const name = parts[0].trim();
42
+ const id = parseInt(parts[1].trim(), 10);
43
+ if (name && !isNaN(id)) {
44
+ this.manualDefs.push({ id, key: name });
45
+ return name;
46
+ }
47
+ }
48
+ return trimmed;
49
+ })
35
50
  .filter((entry) => entry.length > 0);
36
51
 
37
52
  let nc;
@@ -39,6 +54,9 @@ module.exports = function (RED) {
39
54
  let closed = false;
40
55
  const defMap = new Map();
41
56
 
57
+ // Pre-populate raw map with manual definitions
58
+ this.manualDefs.forEach(d => defMap.set(d.id, { ...d, type: 'MANUAL', dataType: 'UNKNOWN', access: 'READ_ONLY' }));
59
+
42
60
  const shouldInclude = (key) => {
43
61
  if (!this.variables.length) {
44
62
  return true;
@@ -61,8 +79,9 @@ module.exports = function (RED) {
61
79
  }));
62
80
 
63
81
  // Warn if we have filters but no definitions (all keys will be raw IDs)
82
+ // Skip warning if we have manual definitions or successfully loaded map
64
83
  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).');
84
+ 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
85
  }
67
86
 
68
87
  return mapped.filter((state) => shouldInclude(state.key));
@@ -85,29 +104,37 @@ module.exports = function (RED) {
85
104
  const { ReadVariablesQueryResponse } = readRespMod;
86
105
  const { VariablesChangedEvent } = changeEventMod;
87
106
 
107
+ // Try to fetch definitions (Discovery), but respect manual defs
88
108
  try {
109
+ // Verify connection first or use connection helper?
110
+ // fetchProviderVariables uses HTTP, so it's independent of 'nc'
89
111
  const definitions = await connection.fetchProviderVariables(this.providerId);
90
112
  definitions.forEach((def) => defMap.set(def.id, def));
91
113
  } catch (e) {
92
- this.warn(`REST API failed (${e.message}). Attempting NATS fallback...`);
114
+ // Only warn if we don't have manual defs to fall back on
115
+ if (this.manualDefs.length === 0) {
116
+ this.warn(`REST API failed (${e.message}). Attempting NATS fallback...`);
117
+ } else {
118
+ this.warn(`REST API Discovery failed, but using ${this.manualDefs.length} manual definitions.`);
119
+ }
120
+
93
121
  try {
94
122
  // Fallback: Fetch definitions via NATS
95
- // We need to load the response type dynamically as well if not already loaded
123
+ // ... import and logic remains ...
124
+ // We can skip NATS fetch if manual defs are sufficient?
125
+ // Better to try anyway to get Metadata (DataType etc).
96
126
  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
127
  } catch (natsErr) {
102
- this.warn(`NATS definition fetch also failed: ${natsErr.message}`);
128
+ if (this.manualDefs.length === 0) this.warn(`NATS definition fetch also failed: ${natsErr.message}`);
103
129
  }
104
130
  }
105
131
 
132
+
106
133
  nc = await connection.acquire();
107
134
  this.status({ fill: 'green', shape: 'dot', text: 'connected' });
108
135
 
109
- // Retry Definition Fetch via NATS if Map is empty
110
- if (defMap.size === 0) {
136
+ // Retry Definition Fetch via NATS if Map is empty AND no manual defs
137
+ if (defMap.size === 0 && this.manualDefs.length === 0) {
111
138
  try {
112
139
  this.warn(`Attempting to fetch definitions via NATS for ${this.providerId}...`);
113
140
  const defMsg = await nc.request(`v1.loc.${this.providerId}.def.qry.read`, new Uint8Array(0), { timeout: 2000 });
@@ -148,14 +175,12 @@ module.exports = function (RED) {
148
175
  }
149
176
  }
150
177
  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(', ');
178
+ // Warning logic ...
179
+ const sampleKeys = Array.from(defMap.values()).filter(d => d.type !== 'MANUAL').map(d => `'${d.key}'`).slice(0, 5).join(', ');
155
180
  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.`);
181
+ if (!sampleKeys && this.manualDefs.length > 0) {
182
+ this.warn(' -> Manual Definitions are configured but did not match the requested variable names? Check capitalization.');
183
+ }
159
184
  }
160
185
  }
161
186
 
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.44",
4
4
  "description": "Node-RED nodes for u-OS Data Hub via NATS",
5
5
  "repository": {
6
6
  "type": "git",