node-red-contrib-ccu 4.5.1 → 4.5.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,21 @@ Notable changes to node-red-contrib-ccu. Format follows
5
5
  user-visible symptom and the cause, not the commit list (the release notes
6
6
  append commits automatically).
7
7
 
8
+ ## 4.5.2 (2026-10-02)
9
+
10
+ ### Fixed
11
+
12
+ - **No HmIP-RF events for up to 10 minutes after hmipserver restarted**
13
+ (B-37). hmipserver keeps its subscribers over a restart: it calls
14
+ `listDevices` and `newDevices` on them and answers their pings, but
15
+ delivers no event to such a kept subscription until it is subscribed
16
+ afresh. The liveness ping saw its PONG and was content, so the
17
+ subscription came back only with the 600 s silence timeout. A
18
+ `listDevices` or `newDevices` call that does not follow the connection's
19
+ own `init` now makes it subscribe again at once, and events are back
20
+ within seconds. A device paired while Node-RED runs costs one such `init`
21
+ as well.
22
+
8
23
  ## 4.5.1 (2026-10-02)
9
24
 
10
25
  ### Fixed
@@ -23,6 +23,11 @@ const binrpc = require('binrpc');
23
23
 
24
24
  const pkg = require(path.join(__dirname, '..', 'package.json'));
25
25
 
26
+ // B-37: how long after our init the process's listDevices/newDevices still belong to it -
27
+ // hmipserver answers the init at once and calls listDevices and newDevices afterwards. Such a
28
+ // call later than this is the process calling on its own: it restarted and kept the subscription.
29
+ const INIT_GRACE_MS = 30000;
30
+
26
31
  /**
27
32
  * check if an object is iterable
28
33
  * @link https://stackoverflow.com/a/37837872
@@ -630,6 +635,10 @@ module.exports = function (RED) {
630
635
  // and which of them are waiting for an unreachable process
631
636
  this.initRetry = {};
632
637
  this.ifaceWaiting = {};
638
+ // B-37: per interface {inFlight, at, reinit} of our own init, and how long after it the
639
+ // process's listDevices/newDevices still answer it (see keptSubscription())
640
+ this.initState = {};
641
+ this.initGrace = INIT_GRACE_MS;
633
642
  this.closing = false;
634
643
  this.serverError = {};
635
644
  this.queueTimeout = Number.parseInt(config.queueTimeout, 10) || 5000;
@@ -2345,9 +2354,12 @@ module.exports = function (RED) {
2345
2354
  this.createClient(iface, {quiet: true});
2346
2355
  }
2347
2356
 
2357
+ // B-37: listDevices/newDevices while this init runs or shortly after belong to it
2358
+ this.initState[iface] = {inFlight: true, at: 0, reinit: false};
2348
2359
  // a failed init is logged by the retry (InitRetry), not here
2349
2360
  this.methodCall(iface, 'init', [initUrl, initId], {quiet: true})
2350
2361
  .then(() => {
2362
+ this.initState[iface] = {inFlight: false, at: now(), reinit: false};
2351
2363
  // the ping/re-init liveness starts after a successful init only;
2352
2364
  // a failed one is retried by rpcInitFailed (task 11)
2353
2365
  if (this.ifaceTypes[iface].ping && !this.closing) {
@@ -2370,10 +2382,57 @@ module.exports = function (RED) {
2370
2382
  resolve(iface);
2371
2383
  }
2372
2384
  })
2373
- .catch((error) => reject(error));
2385
+ .catch((error) => {
2386
+ this.initState[iface] = {inFlight: false, at: 0, reinit: false};
2387
+ reject(error);
2388
+ });
2374
2389
  });
2375
2390
  }
2376
2391
 
2392
+ /**
2393
+ * B-37: the interface process called listDevices or newDevices. Outside our own init it
2394
+ * restarted and kept the subscription: hmipserver restores its subscribers from its
2395
+ * handlers file, calls listDevices and newDevices on them and answers their pings (the
2396
+ * PONG arrives, so neither the liveness ping nor the 600 s watchdog fires), but delivers
2397
+ * no event to such a kept subscription until a fresh init (openccu-lite B-286, measured).
2398
+ * So init again at once. A device paired while Node-RED runs costs one init the same way.
2399
+ * @param iface
2400
+ * @param {string} method
2401
+ * @returns {boolean} whether a fresh init was started
2402
+ */
2403
+ keptSubscription(iface, method) {
2404
+ const type = this.ifaceTypes[iface];
2405
+ const state = this.initState[iface];
2406
+ if (this.closing || !type || !type.enabled || !type.init) {
2407
+ return false;
2408
+ }
2409
+
2410
+ // never subscribed, an init running or already started for this restart
2411
+ if (!state || state.inFlight || state.reinit || !state.at) {
2412
+ return false;
2413
+ }
2414
+
2415
+ if (this.initRetry[iface] && this.initRetry[iface].pending) {
2416
+ return false;
2417
+ }
2418
+
2419
+ if (now() - state.at < this.initGrace) {
2420
+ return false;
2421
+ }
2422
+
2423
+ state.reinit = true;
2424
+ this.logger.info(
2425
+ iface +
2426
+ ': ' +
2427
+ method +
2428
+ ' outside our init - the interface process restarted and kept the subscription, subscribing afresh',
2429
+ );
2430
+ this.rpcInit(iface)
2431
+ .then(() => this.rpcInitSucceeded(iface))
2432
+ .catch((error) => this.rpcInitFailed(iface, error));
2433
+ return true;
2434
+ }
2435
+
2377
2436
  /**
2378
2437
  * Returns the links of a specific channel
2379
2438
  * @param {String} iface
@@ -3457,7 +3516,8 @@ module.exports = function (RED) {
3457
3516
  this.saveMetadata();
3458
3517
  }
3459
3518
 
3460
- if (iface) {
3519
+ // B-37: a restart that kept the subscription gets a fresh init, no ping
3520
+ if (iface && !this.keptSubscription(iface, 'newDevices')) {
3461
3521
  this.deviceCallback(iface);
3462
3522
  }
3463
3523
  },
@@ -3498,6 +3558,9 @@ module.exports = function (RED) {
3498
3558
 
3499
3559
  this.logger.debug(' >', iface, 'listDevices', JSON.stringify(res));
3500
3560
  callback(null, res);
3561
+ if (iface) {
3562
+ this.keptSubscription(iface, 'listDevices');
3563
+ }
3501
3564
  },
3502
3565
  event: (_, parameters, callback) => {
3503
3566
  const iface = this.rpcIface('event', parameters);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-ccu",
3
- "version": "4.5.1",
3
+ "version": "4.5.2",
4
4
  "license": "MIT",
5
5
  "main": "none",
6
6
  "keywords": [