node-red-contrib-uos-nats 0.2.46 → 0.2.48
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/lib/payloads.js +31 -2
- package/nodes/datahub-output.js +6 -6
- package/nodes/datahub-write.js +34 -10
- package/package.json +1 -1
package/lib/payloads.js
CHANGED
|
@@ -115,14 +115,43 @@ export function encodeWriteVariablesCommand(variables) {
|
|
|
115
115
|
|
|
116
116
|
// Encode value based on type
|
|
117
117
|
// Explicitly handle null/undefined by defaulting to safe empty values based on type
|
|
118
|
-
// This prevents "Field 6 must be set" error
|
|
119
118
|
const val = v.value;
|
|
120
|
-
|
|
119
|
+
let targetType = v.dataType; // Optional known type from definition
|
|
120
|
+
|
|
121
|
+
// If targetType is provided, force specific flatbuffer encoding
|
|
122
|
+
if (targetType) {
|
|
123
|
+
targetType = String(targetType).toUpperCase();
|
|
124
|
+
if (targetType === 'BOOLEAN') {
|
|
125
|
+
const boolVal = new VariableValueBooleanT();
|
|
126
|
+
boolVal.value = Boolean(val);
|
|
127
|
+
varT.value = boolVal;
|
|
128
|
+
varT.valueType = VariableValue.Boolean;
|
|
129
|
+
} else if (targetType === 'INT64') {
|
|
130
|
+
const intVal = new VariableValueInt64T();
|
|
131
|
+
intVal.value = BigInt(parseInt(val) || 0); // Ensure BigInt
|
|
132
|
+
varT.value = intVal;
|
|
133
|
+
varT.valueType = VariableValue.Int64;
|
|
134
|
+
} else if (targetType === 'FLOAT64') {
|
|
135
|
+
const floatVal = new VariableValueFloat64T();
|
|
136
|
+
floatVal.value = Number(val);
|
|
137
|
+
varT.value = floatVal;
|
|
138
|
+
varT.valueType = VariableValue.Float64;
|
|
139
|
+
} else {
|
|
140
|
+
// STRING or default
|
|
141
|
+
const strVal = new VariableValueStringT();
|
|
142
|
+
strVal.value = (typeof val === 'object') ? JSON.stringify(val) : String(val);
|
|
143
|
+
varT.value = strVal;
|
|
144
|
+
varT.valueType = VariableValue.String;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Fallback: Infer from value (Risk: Int sent as Float or vice versa)
|
|
148
|
+
else if (typeof val === 'boolean') {
|
|
121
149
|
const boolVal = new VariableValueBooleanT();
|
|
122
150
|
boolVal.value = val;
|
|
123
151
|
varT.value = boolVal;
|
|
124
152
|
varT.valueType = VariableValue.Boolean;
|
|
125
153
|
} else if (Number.isInteger(val)) {
|
|
154
|
+
|
|
126
155
|
const intVal = new VariableValueInt64T();
|
|
127
156
|
intVal.value = BigInt(val);
|
|
128
157
|
varT.value = intVal;
|
package/nodes/datahub-output.js
CHANGED
|
@@ -65,11 +65,10 @@ module.exports = function (RED) {
|
|
|
65
65
|
}
|
|
66
66
|
// Retrieve configuration node
|
|
67
67
|
this.providerId = (config.providerId || 'nodered').trim();
|
|
68
|
-
|
|
69
|
-
this.definitions = config.definitions || [];
|
|
68
|
+
// this.definitions = config.definitions || [];
|
|
70
69
|
|
|
71
70
|
const defMap = new Map();
|
|
72
|
-
|
|
71
|
+
const definitions = [];
|
|
73
72
|
const stateMap = new Map();
|
|
74
73
|
let nextId = 100;
|
|
75
74
|
let fingerprint = 0;
|
|
@@ -92,7 +91,7 @@ module.exports = function (RED) {
|
|
|
92
91
|
stateMap.set(def.id, {
|
|
93
92
|
id: def.id,
|
|
94
93
|
value: defaultValue(dataType),
|
|
95
|
-
|
|
94
|
+
timestamp: BigInt(Date.now()) * 1_000_000n,
|
|
96
95
|
quality: 'GOOD',
|
|
97
96
|
});
|
|
98
97
|
return { def, created: true };
|
|
@@ -139,7 +138,7 @@ module.exports = function (RED) {
|
|
|
139
138
|
const stateObj = {};
|
|
140
139
|
const nowNs = Date.now() * 1_000_000;
|
|
141
140
|
for (const s of stateMap.values()) {
|
|
142
|
-
s.
|
|
141
|
+
s.timestamp = BigInt(Date.now()) * 1_000_000n; // Force refresh timestamp
|
|
143
142
|
stateObj[s.id] = s;
|
|
144
143
|
}
|
|
145
144
|
try {
|
|
@@ -153,6 +152,7 @@ module.exports = function (RED) {
|
|
|
153
152
|
console.log(`[DataHub Output] Packet HEX (${payload.length} bytes): ${hex}`);
|
|
154
153
|
|
|
155
154
|
await nc.publish(subject, payload);
|
|
155
|
+
await nc.flush(); // Ensure NATS accepts the packet (catches Permission Errors)
|
|
156
156
|
console.log(`[DataHub Output] Heartbeat sent. State count: ${Object.keys(stateObj).length}`);
|
|
157
157
|
} catch (err) {
|
|
158
158
|
this.warn(`Heartbeat error: ${err.message}`);
|
|
@@ -263,7 +263,7 @@ module.exports = function (RED) {
|
|
|
263
263
|
const state = {
|
|
264
264
|
id: def.id,
|
|
265
265
|
value,
|
|
266
|
-
|
|
266
|
+
timestamp: BigInt(Date.now()) * 1_000_000n,
|
|
267
267
|
quality: 'GOOD',
|
|
268
268
|
};
|
|
269
269
|
// states.push(state); // No longer pushing to a temporary 'states' array
|
package/nodes/datahub-write.js
CHANGED
|
@@ -17,20 +17,36 @@ module.exports = function (RED) {
|
|
|
17
17
|
const variable = cached.definition.variables.find(v => v.key === key);
|
|
18
18
|
if (variable) {
|
|
19
19
|
node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id} (cached)`);
|
|
20
|
-
|
|
20
|
+
// Return full object including type
|
|
21
|
+
return { id: variable.id, dataType: variable.dataType };
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
// Query provider definition
|
|
26
|
+
let definition = null;
|
|
25
27
|
try {
|
|
26
28
|
const query = payloads.buildReadProviderDefinitionQuery();
|
|
27
|
-
const subject = `v1.loc.registry.providers.${providerId}.def.qry.read`;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Strategy 1: Direct Query (v1.loc.<provider>.def.qry.read)
|
|
31
|
+
// This is required for providers like Node-RED itself or simple Python scripts
|
|
32
|
+
const directSubject = `v1.loc.${providerId}.def.qry.read`;
|
|
33
|
+
try {
|
|
34
|
+
const response = await nc.request(directSubject, query, { timeout: 1000 });
|
|
35
|
+
definition = payloads.decodeProviderDefinition(response.data);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
node.debug && node.debug(`Direct Query failed: ${err.message}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Strategy 2: Registry Query (v1.loc.registry.providers.<provider>.def.qry.read)
|
|
41
|
+
// Fallback for managed providers
|
|
42
|
+
if (!definition) {
|
|
43
|
+
const registrySubject = `v1.loc.registry.providers.${providerId}.def.qry.read`;
|
|
44
|
+
const response = await nc.request(registrySubject, query, { timeout: 2000 });
|
|
45
|
+
definition = payloads.decodeProviderDefinition(response.data);
|
|
46
|
+
}
|
|
31
47
|
|
|
32
48
|
if (!definition) {
|
|
33
|
-
throw new Error(`Provider ${providerId} not found
|
|
49
|
+
throw new Error(`Provider ${providerId} not found (tried Direct & Registry)`);
|
|
34
50
|
}
|
|
35
51
|
|
|
36
52
|
// Cache the definition
|
|
@@ -45,8 +61,8 @@ module.exports = function (RED) {
|
|
|
45
61
|
throw new Error(`Variable key '${key}' not found in provider ${providerId}`);
|
|
46
62
|
}
|
|
47
63
|
|
|
48
|
-
node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id}`);
|
|
49
|
-
return variable.id;
|
|
64
|
+
node.debug && node.debug(`Key '${key}' resolved to ID ${variable.id} (Type: ${variable.dataType})`);
|
|
65
|
+
return { id: variable.id, dataType: variable.dataType };
|
|
50
66
|
|
|
51
67
|
} catch (err) {
|
|
52
68
|
throw new Error(`Failed to resolve key '${key}': ${err.message}`);
|
|
@@ -71,6 +87,7 @@ module.exports = function (RED) {
|
|
|
71
87
|
this.variableId = config.variableId ? parseInt(config.variableId, 10) : null;
|
|
72
88
|
this.variableKey = config.variableKey?.trim();
|
|
73
89
|
this.resolvedId = null; // Cached resolved ID
|
|
90
|
+
this.resolvedDataType = null; // Cached Data Type for strict writing
|
|
74
91
|
this.payloads = null; // Will be loaded dynamically
|
|
75
92
|
|
|
76
93
|
if (!this.providerId) {
|
|
@@ -131,10 +148,16 @@ module.exports = function (RED) {
|
|
|
131
148
|
|
|
132
149
|
// Resolve variable ID if needed
|
|
133
150
|
let varId = node.resolvedId;
|
|
151
|
+
let varType = node.resolvedDataType;
|
|
152
|
+
|
|
134
153
|
if (!varId && node.variableKey) {
|
|
135
154
|
node.status({ fill: 'yellow', shape: 'dot', text: 'resolving key...' });
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
const resolved = await resolveVariableKey(nc, node.providerId, node.variableKey, node, node.payloads);
|
|
156
|
+
varId = resolved.id;
|
|
157
|
+
varType = resolved.dataType; // Store resolved type
|
|
158
|
+
|
|
159
|
+
node.resolvedId = varId;
|
|
160
|
+
node.resolvedDataType = varType;
|
|
138
161
|
node.status({ fill: 'green', shape: 'ring', text: 'ready' });
|
|
139
162
|
}
|
|
140
163
|
|
|
@@ -142,7 +165,8 @@ module.exports = function (RED) {
|
|
|
142
165
|
const writeCommand = node.payloads.encodeWriteVariablesCommand([
|
|
143
166
|
{
|
|
144
167
|
id: varId,
|
|
145
|
-
value: value
|
|
168
|
+
value: value,
|
|
169
|
+
dataType: varType // Pass dataType for strict encoding
|
|
146
170
|
}
|
|
147
171
|
]);
|
|
148
172
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-uos-nats",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.48",
|
|
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",
|