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 +3 -1
- package/nodes/datahub-input.html +6 -0
- package/nodes/datahub-input.js +46 -17
- package/package.json +1 -1
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.
|
|
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.
|
package/nodes/datahub-input.html
CHANGED
|
@@ -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">
|
package/nodes/datahub-input.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
152
|
-
|
|
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.
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|