homebridge-pitpat-treadmill 0.1.2 → 0.1.4

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
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. The project follows Semantic Versioning.
4
4
 
5
+ ## [0.1.4] - 2026-10-09
6
+
7
+ ### Fixed
8
+
9
+ - Allow an isolated Homebridge child bridge to use a host BlueZ D-Bus socket via `PITPAT_DBUS_SYSTEM_BUS_ADDRESS` without redirecting Avahi or other container services.
10
+
11
+ ## [0.1.3] - 2026-10-09
12
+
13
+ ### Fixed
14
+
15
+ - Attempt one controlled HCI controller reset when Noble remains outside the `poweredOn` state after a BLE link timeout.
16
+ - Preserve normal reconnect backoff when the adapter cannot recover.
17
+
5
18
  ## [0.1.2] - 2026-10-09
6
19
 
7
20
  ### Fixed
package/README.md CHANGED
@@ -130,6 +130,8 @@ HomeKit does not reliably tell a plugin whether a write came from a person, Siri
130
130
 
131
131
  The container needs access to the host Bluetooth stack or a dedicated HCI adapter. Device mapping and D-Bus configuration vary by host. Docker operation has not yet been verified and is not currently claimed as supported.
132
132
 
133
+ When the plugin runs as an isolated child bridge with Noble's D-Bus binding, set `NOBLE_BINDINGS=dbus` and use `PITPAT_DBUS_SYSTEM_BUS_ADDRESS` for a host socket mounted at a non-default path. The plugin copies this value to `DBUS_SYSTEM_BUS_ADDRESS` inside its child process only, so container services such as Avahi continue using the container's own system bus. The D-Bus binding also requires `dbus-next` to be installed in the Homebridge environment.
134
+
133
135
  ### Common messages
134
136
 
135
137
  - `treadmill ... was not discovered`: verify the exact identifier, power, range, and that the mobile app is closed.
@@ -1,7 +1,12 @@
1
+ import type { Logger } from 'homebridge';
1
2
  import type { BleConnection, BleTransport } from './transport.js';
2
3
  export declare class NobleTransport implements BleTransport {
4
+ private readonly log;
3
5
  private noble?;
6
+ constructor(log: Pick<Logger, 'warn'>);
4
7
  private loadNoble;
5
8
  connect(deviceIdentifier: string, timeoutMs: number): Promise<BleConnection>;
9
+ private ensurePoweredOn;
6
10
  private findPeripheral;
7
11
  }
12
+ export declare function configureNobleDbusAddress(env?: NodeJS.ProcessEnv): void;
@@ -39,9 +39,14 @@ class NobleConnection {
39
39
  }
40
40
  }
41
41
  export class NobleTransport {
42
+ log;
42
43
  noble;
44
+ constructor(log) {
45
+ this.log = log;
46
+ }
43
47
  async loadNoble() {
44
48
  if (!this.noble) {
49
+ configureNobleDbusAddress();
45
50
  const module = await import('@stoprocent/noble');
46
51
  this.noble = module.default;
47
52
  }
@@ -49,7 +54,7 @@ export class NobleTransport {
49
54
  }
50
55
  async connect(deviceIdentifier, timeoutMs) {
51
56
  const noble = await this.loadNoble();
52
- await noble.waitForPoweredOnAsync(timeoutMs);
57
+ await this.ensurePoweredOn(noble, timeoutMs);
53
58
  const peripheral = await this.findPeripheral(noble, deviceIdentifier, timeoutMs);
54
59
  await peripheral.connectAsync();
55
60
  try {
@@ -71,6 +76,25 @@ export class NobleTransport {
71
76
  throw error;
72
77
  }
73
78
  }
79
+ async ensurePoweredOn(noble, timeoutMs) {
80
+ try {
81
+ await noble.waitForPoweredOnAsync(timeoutMs);
82
+ return;
83
+ }
84
+ catch (error) {
85
+ if (noble.state === 'unauthorized' || noble.state === 'unsupported') {
86
+ throw error;
87
+ }
88
+ this.log.warn(`[PiTPAT] BLE adapter state is ${noble.state}; attempting one controller reset`);
89
+ noble.reset();
90
+ try {
91
+ await noble.waitForPoweredOnAsync(Math.min(timeoutMs, 10_000));
92
+ }
93
+ catch (recoveryError) {
94
+ throw new Error(`BLE adapter did not recover after reset (state ${noble.state}): ${errorMessage(recoveryError)}`, { cause: recoveryError });
95
+ }
96
+ }
97
+ }
74
98
  async findPeripheral(noble, configuredIdentifier, timeoutMs) {
75
99
  const expected = normalizeIdentifier(configuredIdentifier);
76
100
  return await new Promise((resolve, reject) => {
@@ -111,3 +135,12 @@ export class NobleTransport {
111
135
  });
112
136
  }
113
137
  }
138
+ export function configureNobleDbusAddress(env = process.env) {
139
+ const address = env.PITPAT_DBUS_SYSTEM_BUS_ADDRESS?.trim();
140
+ if (address) {
141
+ env.DBUS_SYSTEM_BUS_ADDRESS = address;
142
+ }
143
+ }
144
+ function errorMessage(error) {
145
+ return error instanceof Error ? error.message : String(error);
146
+ }
package/dist/platform.js CHANGED
@@ -40,7 +40,7 @@ export class PitPatPlatform {
40
40
  accessory = new this.api.platformAccessory(config.name, uuid, 3 /* this.api.hap.Categories.FAN */);
41
41
  this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [accessory]);
42
42
  }
43
- this.bleManager = new BleManager(config, new NobleTransport(), this.log);
43
+ this.bleManager = new BleManager(config, new NobleTransport(this.log), this.log);
44
44
  new TreadmillAccessory(accessory, this.bleManager, config, this.api, this.log);
45
45
  void this.bleManager.start().catch(() => undefined);
46
46
  const staleAccessories = [...this.cachedAccessories.values()].filter((cached) => cached.UUID !== uuid);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "homebridge-pitpat-treadmill",
3
3
  "displayName": "PitPat Treadmill",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "description": "Local Homebridge integration for PitPat treadmills over Bluetooth Low Energy.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",