node-red-contrib-uos-nats 0.2.34 → 0.2.36

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.
@@ -96,9 +96,11 @@ module.exports = function (RED) {
96
96
  };
97
97
 
98
98
  const sendDefinitionUpdate = async (payloads, subjects) => {
99
+ console.log(`[DataHub Output] Publishing definition with ${definitions.length} vars...`);
99
100
  const { payload, fingerprint: fp } = payloads.buildProviderDefinitionEvent(definitions);
100
101
  fingerprint = fp;
101
102
  await nc.publish(subjects.providerDefinitionChanged(this.providerId), payload);
103
+ console.log(`[DataHub Output] Definition published. FP: ${fp}`);
102
104
  };
103
105
 
104
106
  const handleRead = async (payloads, msg) => {
@@ -114,12 +116,48 @@ module.exports = function (RED) {
114
116
  await nc.publish(msg.reply, response);
115
117
  };
116
118
 
119
+ // Store loaded modules for heartbeat
120
+ let loadedPayloads = null;
121
+ let loadedSubjects = null;
122
+
123
+ const sendValuesUpdate = async () => {
124
+ if (!nc || nc.isClosed()) return;
125
+ if (!loadedPayloads || !loadedSubjects) return;
126
+
127
+ // If we have no definitions yet, nothing to send
128
+ if (definitions.length === 0) return;
129
+
130
+ const stateObj = {};
131
+ for (const s of stateMap.values()) {
132
+ stateObj[s.id] = s;
133
+ }
134
+ try {
135
+ const payload = loadedPayloads.buildVariablesChangedEvent(definitions, stateObj, fingerprint);
136
+ await nc.publish(loadedSubjects.varsChangedEvent(this.providerId), payload);
137
+ // console.log(`[DataHub Output] Heartbeat sent. State count: ${Object.keys(stateObj).length}`);
138
+ } catch (err) {
139
+ this.warn(`Heartbeat error: ${err.message}`);
140
+ }
141
+ };
142
+
143
+ const valueHeartbeat = setInterval(() => {
144
+ sendValuesUpdate();
145
+ }, 1000); // 1.0s interval matches Python SDK
146
+
117
147
  const start = async () => {
118
148
  try {
149
+ console.log('[DataHub Output] Starting...');
119
150
  this.status({ fill: 'yellow', shape: 'ring', text: 'connecting…' });
120
151
  const [payloads, subjects] = await loadModules();
152
+ console.log('[DataHub Output] Modules loaded.');
153
+ loadedPayloads = payloads;
154
+ loadedSubjects = subjects;
155
+
121
156
  nc = await connection.acquire();
157
+ console.log('[DataHub Output] NATS acquired.');
158
+
122
159
  await sendDefinitionUpdate(payloads, subjects);
160
+
123
161
  // Listen for Variable READ requests
124
162
  sub = nc.subscribe(subjects.readVariablesQuery(this.providerId), {
125
163
  callback: (err, msg) => {
@@ -130,6 +168,7 @@ module.exports = function (RED) {
130
168
  handleRead(payloads, msg).catch((error) => this.warn(error.message));
131
169
  },
132
170
  });
171
+ console.log('[DataHub Output] Subscribed to Read Query.');
133
172
 
134
173
  // Listen for Definition READ requests (Discovery)
135
174
  // SKIPPED: Permission Violation on v1.loc.<id>.def.qry.read
@@ -191,7 +230,6 @@ module.exports = function (RED) {
191
230
  return;
192
231
  }
193
232
 
194
- const [payloadsMod, subjectsMod] = await loadModules();
195
233
  let definitionsChanged = false;
196
234
  const states = [];
197
235
 
@@ -209,26 +247,19 @@ module.exports = function (RED) {
209
247
  timestampNs: Date.now() * 1_000_000,
210
248
  quality: 'GOOD',
211
249
  };
212
- states.push(state);
213
250
  // states.push(state); // No longer pushing to a temporary 'states' array
214
251
  stateMap.set(def.id, state); // Update the global stateMap
215
252
  });
253
+
216
254
  if (definitionsChanged) {
217
- await sendDefinitionUpdate(payloadsMod, subjectsMod);
218
- // Give Data Hub a moment to process the new definition before sending values
219
- await new Promise(r => setTimeout(r, 500));
220
- }
221
- // Convert stateMap to Object for payload builder
222
- const stateObj = {};
223
- for (const s of stateMap.values()) {
224
- stateObj[s.id] = s;
225
- }
226
- try {
227
- const payload = payloadsMod.buildVariablesChangedEvent(definitions, stateObj, fingerprint);
228
- await nc.publish(subjectsMod.varsChangedEvent(this.providerId), payload);
229
- } catch (err) {
230
- this.error(`[v0.2.15] Encoding Error: ${err.message}. State: ${JSON.stringify(stateObj)}`);
255
+ // If definition changed, we MUST publish definition first
256
+ await sendDefinitionUpdate(loadedPayloads, loadedSubjects);
257
+ await new Promise(r => setTimeout(r, 200));
231
258
  }
259
+
260
+ // Publish values immediately on input (don't wait for heartbeat)
261
+ await sendValuesUpdate();
262
+
232
263
  send(msg);
233
264
  done();
234
265
  }
@@ -247,6 +278,7 @@ module.exports = function (RED) {
247
278
 
248
279
  this.on('close', async (done) => {
249
280
  try {
281
+ if (valueHeartbeat) clearInterval(valueHeartbeat);
250
282
  if (sub) {
251
283
  await sub.drain();
252
284
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-uos-nats",
3
- "version": "0.2.34",
3
+ "version": "0.2.36",
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",