homebridge-lovesac-stealthtech 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/dist/ble/BleClient.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export declare class BleClient implements IBleClient {
|
|
|
19
19
|
constructor(log: Logger);
|
|
20
20
|
get resolvedAddress(): string;
|
|
21
21
|
connect(address?: string): Promise<void>;
|
|
22
|
+
private connectPeripheral;
|
|
23
|
+
private scanWithRetry;
|
|
22
24
|
disconnect(): Promise<void>;
|
|
23
25
|
isConnected(): boolean;
|
|
24
26
|
write(characteristicUuid: string, data: Buffer): Promise<void>;
|
package/dist/ble/BleClient.js
CHANGED
|
@@ -25,26 +25,27 @@ class BleClient {
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
const gen = ++this.connectGeneration;
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
this.log.debug('BLE:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
// Fast path: reuse cached peripheral from a prior connection (skip scan)
|
|
29
|
+
if (this.peripheral) {
|
|
30
|
+
this.log.debug('BLE: Reconnecting to cached peripheral %s...', this._resolvedAddress);
|
|
31
|
+
try {
|
|
32
|
+
await this.connectPeripheral(this.peripheral, gen);
|
|
33
|
+
return;
|
|
34
34
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
peripheral = await this.scanForAnyDevice();
|
|
39
|
-
if (!peripheral) {
|
|
40
|
-
throw new Error('No Lovesac StealthTech device found');
|
|
35
|
+
catch (err) {
|
|
36
|
+
this.log.debug('BLE: Cached peripheral connect failed: %s — falling back to scan', err.message);
|
|
37
|
+
this.peripheral = null;
|
|
41
38
|
}
|
|
42
39
|
}
|
|
40
|
+
// Slow path: scan for the device (with one retry)
|
|
41
|
+
const peripheral = await this.scanWithRetry(address, gen);
|
|
43
42
|
const resolvedId = peripheral.address !== '' && peripheral.address !== 'unknown'
|
|
44
43
|
? peripheral.address
|
|
45
44
|
: peripheral.id ?? peripheral.uuid ?? 'unknown';
|
|
46
|
-
this.log.debug('BLE: Connecting to %s...', resolvedId);
|
|
47
45
|
this._resolvedAddress = resolvedId;
|
|
46
|
+
await this.connectPeripheral(peripheral, gen);
|
|
47
|
+
}
|
|
48
|
+
async connectPeripheral(peripheral, gen) {
|
|
48
49
|
// Register disconnect handler BEFORE connecting to avoid race (P0-2).
|
|
49
50
|
// Capture the generation so a stale handler from a timed-out attempt does
|
|
50
51
|
// not clear state that belongs to a newer connection.
|
|
@@ -52,7 +53,6 @@ class BleClient {
|
|
|
52
53
|
this.log.debug('BLE: Disconnected');
|
|
53
54
|
if (gen === this.connectGeneration) {
|
|
54
55
|
this._connected = false;
|
|
55
|
-
this.peripheral = null;
|
|
56
56
|
this.characteristics = {};
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -79,6 +79,33 @@ class BleClient {
|
|
|
79
79
|
}
|
|
80
80
|
this.log.debug('BLE: Found %d characteristics', Object.keys(this.characteristics).length);
|
|
81
81
|
}
|
|
82
|
+
async scanWithRetry(address, gen) {
|
|
83
|
+
const label = address ?? 'auto-discovery';
|
|
84
|
+
const scanFn = address
|
|
85
|
+
? () => this.scanForDevice(address)
|
|
86
|
+
: () => this.scanForAnyDevice();
|
|
87
|
+
this.log.debug('BLE: Starting scan for %s...', label);
|
|
88
|
+
let peripheral = await scanFn();
|
|
89
|
+
if (peripheral) {
|
|
90
|
+
return peripheral;
|
|
91
|
+
}
|
|
92
|
+
// First scan missed — retry once after a short delay
|
|
93
|
+
if (gen !== this.connectGeneration) {
|
|
94
|
+
throw new Error('Connection attempt superseded');
|
|
95
|
+
}
|
|
96
|
+
this.log.info('BLE: Scan found nothing, retrying in %ds...', settings_1.BLE_SCAN_RETRY_DELAY / 1000);
|
|
97
|
+
await new Promise(r => setTimeout(r, settings_1.BLE_SCAN_RETRY_DELAY));
|
|
98
|
+
if (gen !== this.connectGeneration) {
|
|
99
|
+
throw new Error('Connection attempt superseded');
|
|
100
|
+
}
|
|
101
|
+
peripheral = await scanFn();
|
|
102
|
+
if (peripheral) {
|
|
103
|
+
return peripheral;
|
|
104
|
+
}
|
|
105
|
+
throw new Error(address
|
|
106
|
+
? `Device ${address} not found`
|
|
107
|
+
: 'No Lovesac StealthTech device found');
|
|
108
|
+
}
|
|
82
109
|
async disconnect() {
|
|
83
110
|
if (this.peripheral && this._connected) {
|
|
84
111
|
try {
|
|
@@ -89,8 +116,8 @@ class BleClient {
|
|
|
89
116
|
}
|
|
90
117
|
}
|
|
91
118
|
this._connected = false;
|
|
92
|
-
this.peripheral = null;
|
|
93
119
|
this.characteristics = {};
|
|
120
|
+
// Keep this.peripheral cached for fast reconnect
|
|
94
121
|
}
|
|
95
122
|
isConnected() {
|
|
96
123
|
return this._connected;
|
|
@@ -147,18 +147,25 @@ class LovesacDevice {
|
|
|
147
147
|
onPollFailure(message) {
|
|
148
148
|
this.consecutiveFailures++;
|
|
149
149
|
this.log.warn('Background poll failed (%d/%d): %s', this.consecutiveFailures, settings_1.UNREACHABLE_THRESHOLD, message);
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
// Apply backoff at each threshold multiple (6, 12, 18, …) while unreachable.
|
|
151
|
+
// First crossing also marks the device unreachable and resets cached state.
|
|
152
|
+
if (this.consecutiveFailures >= settings_1.UNREACHABLE_THRESHOLD
|
|
153
|
+
&& this.consecutiveFailures % settings_1.UNREACHABLE_THRESHOLD === 0) {
|
|
154
|
+
this.backoffPollInterval();
|
|
155
|
+
if (this.reachable) {
|
|
156
|
+
this.markUnreachable();
|
|
157
|
+
}
|
|
152
158
|
}
|
|
153
159
|
}
|
|
154
|
-
|
|
155
|
-
this.reachable = false;
|
|
156
|
-
// Exponential backoff: double the poll interval, capped at 10 minutes
|
|
160
|
+
backoffPollInterval() {
|
|
157
161
|
const newInterval = Math.min(this.pollIntervalMs * 2, LovesacDevice.MAX_POLL_INTERVAL_MS);
|
|
158
162
|
if (newInterval !== this.pollIntervalMs) {
|
|
159
163
|
this.pollIntervalMs = newInterval;
|
|
160
164
|
this.log.info('Poll interval backed off to %ds', this.pollIntervalMs / 1000);
|
|
161
165
|
}
|
|
166
|
+
}
|
|
167
|
+
markUnreachable() {
|
|
168
|
+
this.reachable = false;
|
|
162
169
|
this.log.warn('Device unreachable after %d consecutive poll failures — resetting cached state', this.consecutiveFailures);
|
|
163
170
|
// Reset state to sentinels so next successful connection triggers full re-sync
|
|
164
171
|
(0, responses_1.resetState)(this.state);
|
package/dist/settings.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export declare const BLE_CONNECT_TIMEOUT = 30000;
|
|
|
21
21
|
export declare const BLE_WRITE_TIMEOUT = 10000;
|
|
22
22
|
export declare const BLE_DISCONNECT_TIMEOUT = 10000;
|
|
23
23
|
export declare const BLE_DISCOVER_TIMEOUT = 15000;
|
|
24
|
-
export declare const
|
|
24
|
+
export declare const BLE_SCAN_RETRY_DELAY = 2000;
|
|
25
|
+
export declare const UNREACHABLE_THRESHOLD = 6;
|
|
25
26
|
/**
|
|
26
27
|
* Race a promise against a timeout. Rejects with a descriptive error if
|
|
27
28
|
* the timeout fires first. The underlying promise is NOT cancelled — the
|
package/dist/settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.UNREACHABLE_THRESHOLD = exports.BLE_DISCOVER_TIMEOUT = exports.BLE_DISCONNECT_TIMEOUT = exports.BLE_WRITE_TIMEOUT = exports.BLE_CONNECT_TIMEOUT = exports.BLE_SCAN_TIMEOUT = exports.MAX_VOLUME = exports.CharUUID = exports.SOFA_SERVICE_UUID_SHORT = exports.SOFA_SERVICE_UUID = exports.PLATFORM_NAME = exports.PLUGIN_NAME = void 0;
|
|
3
|
+
exports.UNREACHABLE_THRESHOLD = exports.BLE_SCAN_RETRY_DELAY = exports.BLE_DISCOVER_TIMEOUT = exports.BLE_DISCONNECT_TIMEOUT = exports.BLE_WRITE_TIMEOUT = exports.BLE_CONNECT_TIMEOUT = exports.BLE_SCAN_TIMEOUT = exports.MAX_VOLUME = exports.CharUUID = exports.SOFA_SERVICE_UUID_SHORT = exports.SOFA_SERVICE_UUID = exports.PLATFORM_NAME = exports.PLUGIN_NAME = void 0;
|
|
4
4
|
exports.withTimeout = withTimeout;
|
|
5
5
|
exports.errorMessage = errorMessage;
|
|
6
6
|
exports.resolveDeviceConfig = resolveDeviceConfig;
|
|
@@ -29,8 +29,10 @@ exports.BLE_CONNECT_TIMEOUT = 30_000;
|
|
|
29
29
|
exports.BLE_WRITE_TIMEOUT = 10_000;
|
|
30
30
|
exports.BLE_DISCONNECT_TIMEOUT = 10_000;
|
|
31
31
|
exports.BLE_DISCOVER_TIMEOUT = 15_000;
|
|
32
|
+
// Delay between scan retry attempts (milliseconds)
|
|
33
|
+
exports.BLE_SCAN_RETRY_DELAY = 2000;
|
|
32
34
|
// Consecutive poll failures before marking device unreachable
|
|
33
|
-
exports.UNREACHABLE_THRESHOLD =
|
|
35
|
+
exports.UNREACHABLE_THRESHOLD = 6;
|
|
34
36
|
/**
|
|
35
37
|
* Race a promise against a timeout. Rejects with a descriptive error if
|
|
36
38
|
* the timeout fires first. The underlying promise is NOT cancelled — the
|