homebridge-bluos 1.1.1 → 1.1.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
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.1.2](https://github.com/tbaur/homebridge-bluos/compare/v1.1.1...v1.1.2) (2026-08-30)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * do not end the reboot quiet window on a last-gasp poll ([#26](https://github.com/tbaur/homebridge-bluos/issues/26)) ([91651a4](https://github.com/tbaur/homebridge-bluos/commit/91651a4c4b114c8a8e7bfba0cc7c4d6148375b7d))
9
+
3
10
  ## [1.1.1](https://github.com/tbaur/homebridge-bluos/compare/v1.1.0...v1.1.1) (2026-08-30)
4
11
 
5
12
 
package/dist/platform.js CHANGED
@@ -529,9 +529,10 @@ class BluOSPlatform {
529
529
  }
530
530
  }
531
531
  publish(deviceId, observation, reason) {
532
- // The player answered, so the reboot is over even if the window has time
533
- // left. Anything that fails after this is a real outage and is said so.
534
- this.rebootGrace.clear(this.hostOf(deviceId));
532
+ // Ends the window only after the box has already gone quiet. A last-gasp
533
+ // reading while the ports are still up must not cancel it, or the real
534
+ // outage that follows is logged as a surprise.
535
+ this.rebootGrace.clearIfRecovered(this.hostOf(deviceId));
535
536
  for (const handler of this.handlers.get(deviceId) ?? []) {
536
537
  try {
537
538
  handler.applyObservation(observation, reason);
@@ -542,7 +543,9 @@ class BluOSPlatform {
542
543
  }
543
544
  }
544
545
  reportUnavailable(deviceId, error) {
545
- if (this.rebootGrace.isExpected(this.hostOf(deviceId))) {
546
+ const host = this.hostOf(deviceId);
547
+ if (this.rebootGrace.isExpected(host)) {
548
+ this.rebootGrace.noteSilence(host);
546
549
  return;
547
550
  }
548
551
  for (const handler of this.handlers.get(deviceId) ?? []) {
@@ -10,20 +10,40 @@
10
10
  * address would otherwise warn and later claim to have recovered. That is noise:
11
11
  * we asked it to go down. This clock is what lets the platform stay quiet until
12
12
  * the player answers again, or until the window ends and a real outage remains.
13
+ *
14
+ * A reading that arrives before the box actually drops is not recovery. The
15
+ * control ports often answer one more time after POST /reboot. That last gasp
16
+ * must not end the window, or the real outage that follows is logged as usual.
17
+ * Recovery is a successful poll after we have already seen the expected silence.
13
18
  */
14
19
  /** Hosts we have just asked to reboot, and when that expectation expires. */
15
20
  export declare class RebootGrace {
16
21
  private readonly graceMs;
17
22
  private readonly now;
18
23
  private readonly expiresAt;
24
+ /** Addresses that have gone quiet at least once inside their window. */
25
+ private readonly sawSilence;
19
26
  constructor(graceMs: number, now?: () => number);
20
27
  /** Start (or refresh) the quiet window for this address. */
21
28
  expect(host: string): void;
22
29
  /**
23
- * End the window early, because the address answered.
30
+ * Record that this address failed while a reboot was still expected.
31
+ *
32
+ * The next successful poll is then treated as the player coming back, not as
33
+ * a last gasp from a box that has not gone down yet.
34
+ */
35
+ noteSilence(host: string | undefined): void;
36
+ /**
37
+ * End the window if this address already went quiet and has now answered.
38
+ *
39
+ * A success with no prior silence is ignored: the box has not dropped yet.
40
+ */
41
+ clearIfRecovered(host: string | undefined): void;
42
+ /**
43
+ * End the window now.
24
44
  *
25
- * A box is usually back well inside the window. Without this, a genuine
26
- * failure in the time that remains would be swallowed as part of the reboot.
45
+ * Used when the clock runs out, and by tests. Recovery goes through
46
+ * {@link clearIfRecovered} so a last-gasp reading cannot cancel the window.
27
47
  */
28
48
  clear(host: string | undefined): void;
29
49
  /** True while a reboot of this address is still expected to look like an outage. */
@@ -11,6 +11,11 @@
11
11
  * address would otherwise warn and later claim to have recovered. That is noise:
12
12
  * we asked it to go down. This clock is what lets the platform stay quiet until
13
13
  * the player answers again, or until the window ends and a real outage remains.
14
+ *
15
+ * A reading that arrives before the box actually drops is not recovery. The
16
+ * control ports often answer one more time after POST /reboot. That last gasp
17
+ * must not end the window, or the real outage that follows is logged as usual.
18
+ * Recovery is a successful poll after we have already seen the expected silence.
14
19
  */
15
20
  Object.defineProperty(exports, "__esModule", { value: true });
16
21
  exports.RebootGrace = void 0;
@@ -19,6 +24,8 @@ class RebootGrace {
19
24
  graceMs;
20
25
  now;
21
26
  expiresAt = new Map();
27
+ /** Addresses that have gone quiet at least once inside their window. */
28
+ sawSilence = new Set();
22
29
  constructor(graceMs, now = Date.now) {
23
30
  this.graceMs = graceMs;
24
31
  this.now = now;
@@ -26,16 +33,39 @@ class RebootGrace {
26
33
  /** Start (or refresh) the quiet window for this address. */
27
34
  expect(host) {
28
35
  this.expiresAt.set(host, this.now() + this.graceMs);
36
+ this.sawSilence.delete(host);
37
+ }
38
+ /**
39
+ * Record that this address failed while a reboot was still expected.
40
+ *
41
+ * The next successful poll is then treated as the player coming back, not as
42
+ * a last gasp from a box that has not gone down yet.
43
+ */
44
+ noteSilence(host) {
45
+ if (host !== undefined && this.isExpected(host)) {
46
+ this.sawSilence.add(host);
47
+ }
29
48
  }
30
49
  /**
31
- * End the window early, because the address answered.
50
+ * End the window if this address already went quiet and has now answered.
32
51
  *
33
- * A box is usually back well inside the window. Without this, a genuine
34
- * failure in the time that remains would be swallowed as part of the reboot.
52
+ * A success with no prior silence is ignored: the box has not dropped yet.
53
+ */
54
+ clearIfRecovered(host) {
55
+ if (host !== undefined && this.sawSilence.has(host)) {
56
+ this.clear(host);
57
+ }
58
+ }
59
+ /**
60
+ * End the window now.
61
+ *
62
+ * Used when the clock runs out, and by tests. Recovery goes through
63
+ * {@link clearIfRecovered} so a last-gasp reading cannot cancel the window.
35
64
  */
36
65
  clear(host) {
37
66
  if (host !== undefined) {
38
67
  this.expiresAt.delete(host);
68
+ this.sawSilence.delete(host);
39
69
  }
40
70
  }
41
71
  /** True while a reboot of this address is still expected to look like an outage. */
@@ -48,7 +78,7 @@ class RebootGrace {
48
78
  return false;
49
79
  }
50
80
  if (this.now() >= expires) {
51
- this.expiresAt.delete(host);
81
+ this.clear(host);
52
82
  return false;
53
83
  }
54
84
  return true;
@@ -209,7 +209,7 @@ A per-player reboot switch on a shared chassis, at startup:
209
209
  [BluOS] Zone One Reboot: will also reboot Zone Two: they are zones of one chassis, and BluOS reboots the whole box
210
210
  ```
211
211
 
212
- After a reboot the other accessories stay quiet. The player is expected to stop answering, so they do not log `is not responding` or `is responding again`. The next successful poll writes whatever the player is actually doing into HomeKit. A player that is still silent after that window is logged as not responding, the usual way.
212
+ After a reboot the other accessories stay quiet. The player is expected to stop answering, so they do not log `is not responding` or `is responding again`. A reading that arrives before the box actually drops is ignored for that purpose: the control ports often answer once more after the reboot is sent. The first successful poll after the silence writes whatever the player is actually doing into HomeKit. A player that is still silent after that window is logged as not responding, the usual way.
213
213
 
214
214
  A configuration the plugin will not act on:
215
215
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-bluos",
3
3
  "displayName": "Homebridge BluOS",
4
- "version": "1.1.1",
4
+ "version": "1.1.2",
5
5
  "description": "Homebridge plugin for BluOS players — per-zone volume, mute, volume-preset and battery accessories over the LAN Custom Integration API. Verified on NAD and Bluesound hardware",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",