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 +3 -1
- package/nodes/datahub-input.js +43 -18
- 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.js
CHANGED
|
@@ -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) =>
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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(', ');
|
|
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.
|
|
157
|
-
|
|
158
|
-
|
|
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
|
|