tesla-fleet-api 0.4.0 → 0.6.0
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/README.md +269 -1
- package/dist/_virtual/_rolldown/runtime.cjs +10 -0
- package/dist/bluetooth/adapters/shared.cjs +38 -0
- package/dist/bluetooth/adapters/shared.mjs +36 -0
- package/dist/bluetooth/async.cjs +121 -0
- package/dist/bluetooth/async.d.cts +10 -0
- package/dist/bluetooth/async.d.mts +10 -0
- package/dist/bluetooth/async.mjs +116 -0
- package/dist/bluetooth/broadcast.cjs +153 -0
- package/dist/bluetooth/broadcast.d.cts +59 -0
- package/dist/bluetooth/broadcast.d.mts +59 -0
- package/dist/bluetooth/broadcast.mjs +151 -0
- package/dist/bluetooth/constants.cjs +96 -0
- package/dist/bluetooth/constants.d.cts +68 -0
- package/dist/bluetooth/constants.d.mts +68 -0
- package/dist/bluetooth/constants.mjs +70 -0
- package/dist/bluetooth/framing.cjs +125 -0
- package/dist/bluetooth/framing.d.cts +45 -0
- package/dist/bluetooth/framing.d.mts +45 -0
- package/dist/bluetooth/framing.mjs +122 -0
- package/dist/bluetooth/homey.cjs +165 -0
- package/dist/bluetooth/homey.d.cts +84 -0
- package/dist/bluetooth/homey.d.mts +84 -0
- package/dist/bluetooth/homey.mjs +159 -0
- package/dist/bluetooth/names.cjs +24 -0
- package/dist/bluetooth/names.d.cts +10 -0
- package/dist/bluetooth/names.d.mts +10 -0
- package/dist/bluetooth/names.mjs +20 -0
- package/dist/bluetooth/noble.cjs +178 -0
- package/dist/bluetooth/noble.d.cts +58 -0
- package/dist/bluetooth/noble.d.mts +58 -0
- package/dist/bluetooth/noble.mjs +177 -0
- package/dist/bluetooth/streamglue.cjs +157 -0
- package/dist/bluetooth/streamglue.d.cts +37 -0
- package/dist/bluetooth/streamglue.d.mts +37 -0
- package/dist/bluetooth/streamglue.mjs +154 -0
- package/dist/bluetooth/teslabluetooth.cjs +67 -0
- package/dist/bluetooth/teslabluetooth.d.cts +41 -0
- package/dist/bluetooth/teslabluetooth.d.mts +41 -0
- package/dist/bluetooth/teslabluetooth.mjs +66 -0
- package/dist/bluetooth/transport.cjs +10 -0
- package/dist/bluetooth/transport.d.cts +97 -0
- package/dist/bluetooth/transport.d.mts +97 -0
- package/dist/bluetooth/transport.mjs +10 -0
- package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
- package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
- package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
- package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
- package/dist/bluetooth/verify.cjs +93 -0
- package/dist/bluetooth/verify.d.cts +35 -0
- package/dist/bluetooth/verify.d.mts +35 -0
- package/dist/bluetooth/verify.mjs +91 -0
- package/dist/bluetooth.cjs +52 -0
- package/dist/bluetooth.d.cts +10 -0
- package/dist/bluetooth.d.mts +10 -0
- package/dist/bluetooth.mjs +9 -0
- package/dist/commands.cjs +1080 -85
- package/dist/commands.d.cts +585 -54
- package/dist/commands.d.mts +583 -51
- package/dist/commands.mjs +1084 -93
- package/dist/const.cjs +22 -0
- package/dist/const.d.cts +21 -0
- package/dist/const.d.mts +21 -0
- package/dist/const.mjs +20 -1
- package/dist/energy.cjs +24 -19
- package/dist/energy.d.cts +21 -15
- package/dist/energy.d.mts +21 -15
- package/dist/energy.mjs +24 -19
- package/dist/energyisland.cjs +38 -0
- package/dist/energyisland.mjs +37 -0
- package/dist/energyparsers.cjs +118 -0
- package/dist/energyparsers.d.cts +39 -0
- package/dist/energyparsers.d.mts +39 -0
- package/dist/energyparsers.mjs +116 -0
- package/dist/energyspecific.cjs +375 -17
- package/dist/energyspecific.d.cts +199 -19
- package/dist/energyspecific.d.mts +197 -16
- package/dist/energyspecific.mjs +370 -18
- package/dist/exceptions.cjs +3 -2
- package/dist/exceptions.d.cts +3 -2
- package/dist/exceptions.d.mts +3 -2
- package/dist/exceptions.mjs +3 -2
- package/dist/funnel.cjs +297 -0
- package/dist/funnel.d.cts +118 -0
- package/dist/funnel.d.mts +118 -0
- package/dist/funnel.mjs +292 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +22 -8
- package/dist/index.d.mts +22 -8
- package/dist/index.mjs +19 -6
- package/dist/log.cjs +75 -0
- package/dist/log.d.cts +4 -0
- package/dist/log.d.mts +4 -0
- package/dist/log.mjs +71 -0
- package/dist/oauth.cjs +143 -0
- package/dist/oauth.d.cts +69 -0
- package/dist/oauth.d.mts +69 -0
- package/dist/oauth.mjs +143 -0
- package/dist/powerwall/client.cjs +818 -0
- package/dist/powerwall/client.d.cts +297 -0
- package/dist/powerwall/client.d.mts +297 -0
- package/dist/powerwall/client.mjs +807 -0
- package/dist/powerwall/components.cjs +188 -0
- package/dist/powerwall/components.d.cts +45 -0
- package/dist/powerwall/components.d.mts +45 -0
- package/dist/powerwall/components.mjs +182 -0
- package/dist/powerwall/energysite.cjs +216 -0
- package/dist/powerwall/energysite.d.cts +104 -0
- package/dist/powerwall/energysite.d.mts +104 -0
- package/dist/powerwall/energysite.mjs +214 -0
- package/dist/powerwall/exceptions.cjs +59 -0
- package/dist/powerwall/exceptions.d.cts +38 -0
- package/dist/powerwall/exceptions.d.mts +38 -0
- package/dist/powerwall/exceptions.mjs +51 -0
- package/dist/powerwall/gateway.cjs +102 -0
- package/dist/powerwall/gateway.d.cts +57 -0
- package/dist/powerwall/gateway.d.mts +57 -0
- package/dist/powerwall/gateway.mjs +101 -0
- package/dist/powerwall/merge.cjs +163 -0
- package/dist/powerwall/merge.d.cts +63 -0
- package/dist/powerwall/merge.d.mts +63 -0
- package/dist/powerwall/merge.mjs +159 -0
- package/dist/powerwall/models.d.cts +95 -0
- package/dist/powerwall/models.d.mts +95 -0
- package/dist/powerwall/queries.cjs +17 -0
- package/dist/powerwall/queries.mjs +10 -0
- package/dist/powerwall/transport.cjs +232 -0
- package/dist/powerwall/transport.mjs +227 -0
- package/dist/powerwall.cjs +47 -0
- package/dist/powerwall.d.cts +9 -0
- package/dist/powerwall.d.mts +9 -0
- package/dist/powerwall.mjs +8 -0
- package/dist/router/base.cjs +203 -0
- package/dist/router/base.d.cts +112 -0
- package/dist/router/base.d.mts +112 -0
- package/dist/router/base.mjs +200 -0
- package/dist/router/energysite.cjs +65 -0
- package/dist/router/energysite.d.cts +47 -0
- package/dist/router/energysite.d.mts +47 -0
- package/dist/router/energysite.mjs +60 -0
- package/dist/router.cjs +13 -0
- package/dist/router.d.cts +3 -1
- package/dist/router.d.mts +3 -1
- package/dist/router.mjs +3 -1
- package/dist/signing/crypto.cjs +28 -0
- package/dist/signing/crypto.mjs +26 -1
- package/dist/signing/energy.cjs +123 -0
- package/dist/signing/energy.d.cts +45 -0
- package/dist/signing/energy.d.mts +45 -0
- package/dist/signing/energy.mjs +120 -0
- package/dist/signing/jws.cjs +229 -0
- package/dist/signing/jws.d.cts +45 -0
- package/dist/signing/jws.d.mts +45 -0
- package/dist/signing/jws.mjs +211 -0
- package/dist/signing/keys.cjs +191 -0
- package/dist/signing/keys.d.cts +21 -0
- package/dist/signing/keys.d.mts +21 -0
- package/dist/signing/keys.mjs +182 -0
- package/dist/signing/session.cjs +104 -30
- package/dist/signing/session.d.cts +60 -23
- package/dist/signing/session.d.mts +60 -23
- package/dist/signing/session.mjs +105 -31
- package/dist/tariff.cjs +27 -0
- package/dist/tariff.d.cts +11 -0
- package/dist/tariff.d.mts +11 -0
- package/dist/tariff.mjs +27 -1
- package/dist/teslafleetapi.cjs +256 -55
- package/dist/teslafleetapi.d.cts +110 -14
- package/dist/teslafleetapi.d.mts +110 -14
- package/dist/teslafleetapi.mjs +258 -57
- package/dist/teslemetry.cjs +152 -5
- package/dist/teslemetry.d.cts +95 -5
- package/dist/teslemetry.d.mts +93 -3
- package/dist/teslemetry.mjs +151 -6
- package/dist/teslemetryenergy.cjs +287 -0
- package/dist/teslemetryenergy.d.cts +154 -0
- package/dist/teslemetryenergy.d.mts +154 -0
- package/dist/teslemetryenergy.mjs +282 -0
- package/dist/teslemetryvehicle.cjs +407 -0
- package/dist/teslemetryvehicle.d.cts +238 -0
- package/dist/teslemetryvehicle.d.mts +238 -0
- package/dist/teslemetryvehicle.mjs +405 -0
- package/dist/tessie.cjs +42 -5
- package/dist/tessie.d.cts +51 -5
- package/dist/tessie.d.mts +49 -3
- package/dist/tessie.mjs +42 -5
- package/dist/tessievehicle.cjs +514 -0
- package/dist/tessievehicle.d.cts +333 -0
- package/dist/tessievehicle.d.mts +333 -0
- package/dist/tessievehicle.mjs +513 -0
- package/dist/types/live_status.d.cts +16 -16
- package/dist/types/live_status.d.mts +16 -16
- package/dist/types/responses.d.cts +5 -1
- package/dist/types/responses.d.mts +5 -1
- package/dist/types/vehicle.d.cts +7 -1
- package/dist/types/vehicle.d.mts +7 -1
- package/dist/types.d.cts +3 -3
- package/dist/types.d.mts +3 -3
- package/dist/util.cjs +41 -0
- package/dist/util.d.cts +18 -0
- package/dist/util.d.mts +18 -0
- package/dist/util.mjs +40 -0
- package/dist/vehicle.cjs +79 -36
- package/dist/vehicle.d.cts +42 -26
- package/dist/vehicle.d.mts +42 -26
- package/dist/vehicle.mjs +80 -37
- package/dist/vehiclesigned.cjs +20 -5
- package/dist/vehiclesigned.d.cts +14 -5
- package/dist/vehiclesigned.d.mts +14 -5
- package/dist/vehiclesigned.mjs +20 -5
- package/dist/vehiclespecific.cjs +126 -15
- package/dist/vehiclespecific.d.cts +82 -18
- package/dist/vehiclespecific.d.mts +82 -18
- package/dist/vehiclespecific.mjs +126 -15
- package/package.json +56 -3
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { BleConnection, BleDeviceRef, BleLinkHandlers, BleScanOptions, BleTransport } from "./transport.mjs";
|
|
2
|
+
/**
|
|
3
|
+
* Bytes per write: `null`, a whole frame in one write. Homey hides the MTU.
|
|
4
|
+
* BLE-00 (HW-0, 2026-11-03) measured a 114-byte frame in one write at 232 ms to
|
|
5
|
+
* a reply, against 562 ms for 6 writes of 20 bytes. Only 114 bytes were tried:
|
|
6
|
+
* a signed frame (about 200 bytes) is confirmed by HW-5. Pass `maxWriteSize: 20`
|
|
7
|
+
* to `HomeyBleTransport` to chunk at the ATT minimum if that run shows a failure.
|
|
8
|
+
*/
|
|
9
|
+
export declare const HOMEY_MAX_WRITE_SIZE: null;
|
|
10
|
+
/**
|
|
11
|
+
* Bound on one scan. `discover()` takes a fixed 10.04 s when it is not cached
|
|
12
|
+
* (HW-0), so the library's 10 s default would time out about 40 ms early.
|
|
13
|
+
* `VehicleBluetooth` uses this when `scan_timeout_ms` is not set.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HOMEY_SCAN_TIMEOUT_MS = 15000;
|
|
16
|
+
/**
|
|
17
|
+
* For `connect_timeout_ms` and `max_connect_attempts`, not applied by this
|
|
18
|
+
* adapter: `discover()` caches for 30 s, so a fast retry sees old data, and
|
|
19
|
+
* these fit Homey's 9 s action window. BLE-00 step 11 sets both, against its
|
|
20
|
+
* target of a connect under 4 s in 9 of 10. A flow with no such window should
|
|
21
|
+
* keep the library's defaults until then, as a link slower than this never lands.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HOMEY_VEHICLE_OPTIONS: {
|
|
24
|
+
readonly connect_timeout_ms: 4000;
|
|
25
|
+
readonly max_connect_attempts: 1;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Bound on discovery and subscribing once a link is up. Homey has no timeout of
|
|
29
|
+
* its own, and if these hang, nothing else could release the vehicle's slot.
|
|
30
|
+
* BLE-00 steps 3 and 4 show what a slow link takes.
|
|
31
|
+
*/
|
|
32
|
+
export declare const HOMEY_SETUP_TIMEOUT_MS = 20000;
|
|
33
|
+
/** Bound on unsubscribing, which must not keep `disconnect` from running on a dead link. BLE-00 step 10. */
|
|
34
|
+
export declare const HOMEY_UNSUBSCRIBE_TIMEOUT_MS = 1000;
|
|
35
|
+
export interface HomeyCharacteristicLike {
|
|
36
|
+
uuid: string;
|
|
37
|
+
read(): Promise<Buffer>;
|
|
38
|
+
/** Always a write with response: the only kind Homey offers. */
|
|
39
|
+
write(data: Buffer): Promise<unknown>;
|
|
40
|
+
subscribeToNotifications(callback: (data: Buffer) => void): Promise<void>;
|
|
41
|
+
unsubscribeFromNotifications(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
export interface HomeyServiceLike {
|
|
44
|
+
discoverCharacteristics(): Promise<HomeyCharacteristicLike[]>;
|
|
45
|
+
}
|
|
46
|
+
export interface HomeyPeripheralLike {
|
|
47
|
+
/** Can stay true after a silent drop, so it is only ever a faster signal. BLE-00 step 9 measures it. */
|
|
48
|
+
readonly isConnected: boolean;
|
|
49
|
+
getService(uuid: string): Promise<HomeyServiceLike>;
|
|
50
|
+
disconnect(): Promise<void>;
|
|
51
|
+
/** Not guaranteed on every drop, but if it fires, the link is gone. BLE-00 step 9 measures it. */
|
|
52
|
+
once(event: "disconnect", listener: () => void): unknown;
|
|
53
|
+
removeListener(event: "disconnect", listener: () => void): unknown;
|
|
54
|
+
}
|
|
55
|
+
export interface HomeyAdvertisementLike {
|
|
56
|
+
uuid: string;
|
|
57
|
+
/** Typed as a string, but Homey's guide says it is optional. BLE-00 step 2 measures how often it is there. */
|
|
58
|
+
localName?: string;
|
|
59
|
+
rssi?: number;
|
|
60
|
+
connectable?: boolean;
|
|
61
|
+
/** If already connected, returns the existing connection. It has no timeout: the library bounds it. */
|
|
62
|
+
connect(): Promise<HomeyPeripheralLike>;
|
|
63
|
+
}
|
|
64
|
+
/** What this adapter needs of `this.homey.ble`. */
|
|
65
|
+
export interface HomeyBleManagerLike {
|
|
66
|
+
/** About 10 s, fixed, when not cached (BLE-00 HW-0). Called with no filter, because the vehicle advertises no service. */
|
|
67
|
+
discover(): Promise<HomeyAdvertisementLike[]>;
|
|
68
|
+
find(uuid: string): Promise<HomeyAdvertisementLike>;
|
|
69
|
+
}
|
|
70
|
+
export interface HomeyBleTransportOptions {
|
|
71
|
+
/** Bytes per write, or `null` for a whole frame in one write. Defaults to `HOMEY_MAX_WRITE_SIZE`. */
|
|
72
|
+
maxWriteSize?: number | null;
|
|
73
|
+
}
|
|
74
|
+
export declare class HomeyBleTransport implements BleTransport {
|
|
75
|
+
private readonly ble;
|
|
76
|
+
private readonly maxWriteSize;
|
|
77
|
+
private readonly claims;
|
|
78
|
+
readonly scanTimeoutMs = 15000;
|
|
79
|
+
/** @param ble `this.homey.ble`. The app needs the `homey:wireless:ble` permission. */
|
|
80
|
+
constructor(ble: HomeyBleManagerLike, options?: HomeyBleTransportOptions);
|
|
81
|
+
/** Takes Homey's fixed window, whatever `timeoutMs` says. Never pass `discover` a filter. */
|
|
82
|
+
scan({ matches }: BleScanOptions): Promise<BleDeviceRef[]>;
|
|
83
|
+
connect(device: BleDeviceRef, handlers: BleLinkHandlers): Promise<BleConnection>;
|
|
84
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { withTimeout } from "./async.mjs";
|
|
2
|
+
import { APPEARANCE_UUID, DEFAULT_CONNECT_TIMEOUT_MS, GENERIC_ACCESS_UUID, NAME_UUID, SERVICE_UUID, VEHICLE_NOTIFY_UUID, VEHICLE_WRITE_UUID, VERSION_UUID } from "./constants.mjs";
|
|
3
|
+
import { BleNotSubmittedError } from "./transport.mjs";
|
|
4
|
+
import { LinkClaims, compactUuid, quietly } from "./adapters/shared.mjs";
|
|
5
|
+
// src/bluetooth/adapters/homey.ts
|
|
6
|
+
/**
|
|
7
|
+
* Bytes per write: `null`, a whole frame in one write. Homey hides the MTU.
|
|
8
|
+
* BLE-00 (HW-0, 2026-11-03) measured a 114-byte frame in one write at 232 ms to
|
|
9
|
+
* a reply, against 562 ms for 6 writes of 20 bytes. Only 114 bytes were tried:
|
|
10
|
+
* a signed frame (about 200 bytes) is confirmed by HW-5. Pass `maxWriteSize: 20`
|
|
11
|
+
* to `HomeyBleTransport` to chunk at the ATT minimum if that run shows a failure.
|
|
12
|
+
*/
|
|
13
|
+
const HOMEY_MAX_WRITE_SIZE = null;
|
|
14
|
+
/**
|
|
15
|
+
* Bound on one scan. `discover()` takes a fixed 10.04 s when it is not cached
|
|
16
|
+
* (HW-0), so the library's 10 s default would time out about 40 ms early.
|
|
17
|
+
* `VehicleBluetooth` uses this when `scan_timeout_ms` is not set.
|
|
18
|
+
*/
|
|
19
|
+
const HOMEY_SCAN_TIMEOUT_MS = 15e3;
|
|
20
|
+
/**
|
|
21
|
+
* For `connect_timeout_ms` and `max_connect_attempts`, not applied by this
|
|
22
|
+
* adapter: `discover()` caches for 30 s, so a fast retry sees old data, and
|
|
23
|
+
* these fit Homey's 9 s action window. BLE-00 step 11 sets both, against its
|
|
24
|
+
* target of a connect under 4 s in 9 of 10. A flow with no such window should
|
|
25
|
+
* keep the library's defaults until then, as a link slower than this never lands.
|
|
26
|
+
*/
|
|
27
|
+
const HOMEY_VEHICLE_OPTIONS = {
|
|
28
|
+
connect_timeout_ms: 4e3,
|
|
29
|
+
max_connect_attempts: 1
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Bound on discovery and subscribing once a link is up. Homey has no timeout of
|
|
33
|
+
* its own, and if these hang, nothing else could release the vehicle's slot.
|
|
34
|
+
* BLE-00 steps 3 and 4 show what a slow link takes.
|
|
35
|
+
*/
|
|
36
|
+
const HOMEY_SETUP_TIMEOUT_MS = DEFAULT_CONNECT_TIMEOUT_MS;
|
|
37
|
+
/** Bound on unsubscribing, which must not keep `disconnect` from running on a dead link. BLE-00 step 10. */
|
|
38
|
+
const HOMEY_UNSUBSCRIBE_TIMEOUT_MS = 1e3;
|
|
39
|
+
function find(characteristics, uuid) {
|
|
40
|
+
return characteristics.find((characteristic) => compactUuid(characteristic.uuid) === compactUuid(uuid));
|
|
41
|
+
}
|
|
42
|
+
var HomeyConnection = class {
|
|
43
|
+
id;
|
|
44
|
+
handlers;
|
|
45
|
+
maxWriteSize;
|
|
46
|
+
claims;
|
|
47
|
+
lost = false;
|
|
48
|
+
closed = false;
|
|
49
|
+
closing;
|
|
50
|
+
peripheral;
|
|
51
|
+
characteristics = [];
|
|
52
|
+
genericAccess;
|
|
53
|
+
subscribed = false;
|
|
54
|
+
onDisconnect = () => {
|
|
55
|
+
this.lost = true;
|
|
56
|
+
if (!this.closed) this.handlers.onDisconnect();
|
|
57
|
+
};
|
|
58
|
+
constructor(id, handlers, maxWriteSize, claims) {
|
|
59
|
+
this.id = id;
|
|
60
|
+
this.handlers = handlers;
|
|
61
|
+
this.maxWriteSize = maxWriteSize;
|
|
62
|
+
this.claims = claims;
|
|
63
|
+
}
|
|
64
|
+
/** Stale after a silent drop. The library's timeouts are what find a dead link. */
|
|
65
|
+
get isConnected() {
|
|
66
|
+
return !this.lost && !this.closed && this.peripheral?.isConnected === true;
|
|
67
|
+
}
|
|
68
|
+
/** Discover, then subscribe: the link is live only once this resolves. */
|
|
69
|
+
async attach(peripheral) {
|
|
70
|
+
this.peripheral = peripheral;
|
|
71
|
+
this.assertCurrent();
|
|
72
|
+
peripheral.once("disconnect", this.onDisconnect);
|
|
73
|
+
const characteristics = await (await peripheral.getService(compactUuid(SERVICE_UUID))).discoverCharacteristics();
|
|
74
|
+
const notify = find(characteristics, VEHICLE_NOTIFY_UUID);
|
|
75
|
+
if (!notify || !find(characteristics, "00000212-b2d1-43f0-9b88-960cebf8b91e")) throw new Error("The vehicle's write or notify characteristic was not found.");
|
|
76
|
+
this.assertCurrent();
|
|
77
|
+
this.characteristics = characteristics;
|
|
78
|
+
await notify.subscribeToNotifications((data) => this.handlers.onNotify(data));
|
|
79
|
+
this.subscribed = true;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Homey hands every attempt the same link, and one subscription per
|
|
83
|
+
* characteristic. A superseded attempt stops here, before it changes either.
|
|
84
|
+
*/
|
|
85
|
+
assertCurrent() {
|
|
86
|
+
if (this.claims.superseded(this.id, this)) throw new Error("A newer connect attempt took over this link.");
|
|
87
|
+
}
|
|
88
|
+
async write(data) {
|
|
89
|
+
const characteristic = find(this.characteristics, VEHICLE_WRITE_UUID);
|
|
90
|
+
if (!characteristic) throw new BleNotSubmittedError("The vehicle write characteristic was not found.");
|
|
91
|
+
if (!this.isConnected) throw new BleNotSubmittedError("The link is down.");
|
|
92
|
+
await characteristic.write(Buffer.from(data));
|
|
93
|
+
}
|
|
94
|
+
async read(characteristic) {
|
|
95
|
+
const target = characteristic === "version" ? find(this.characteristics, VERSION_UUID) : find(await this.discoverGenericAccess(), characteristic === "deviceName" ? NAME_UUID : APPEARANCE_UUID);
|
|
96
|
+
if (!target) throw new BleNotSubmittedError(`The ${characteristic} characteristic was not found.`);
|
|
97
|
+
return target.read();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Homey's stack may hide this service, which BLE-00 step 8 settles. A failed
|
|
101
|
+
* lookup leaves a characteristic unresolved on a healthy link, so it must not
|
|
102
|
+
* read as a lost one.
|
|
103
|
+
*/
|
|
104
|
+
discoverGenericAccess() {
|
|
105
|
+
const { peripheral } = this;
|
|
106
|
+
if (!peripheral) return Promise.reject(new BleNotSubmittedError("The link is down."));
|
|
107
|
+
this.genericAccess ??= peripheral.getService(compactUuid(GENERIC_ACCESS_UUID)).then((service) => service.discoverCharacteristics()).catch((cause) => {
|
|
108
|
+
this.genericAccess = void 0;
|
|
109
|
+
throw new BleNotSubmittedError("Generic Access could not be resolved.", { cause });
|
|
110
|
+
});
|
|
111
|
+
return this.genericAccess;
|
|
112
|
+
}
|
|
113
|
+
disconnect() {
|
|
114
|
+
this.closing ??= (async () => {
|
|
115
|
+
this.closed = true;
|
|
116
|
+
const { peripheral, claims, id } = this;
|
|
117
|
+
peripheral?.removeListener("disconnect", this.onDisconnect);
|
|
118
|
+
const superseded = claims.superseded(id, this);
|
|
119
|
+
claims.release(id, this);
|
|
120
|
+
if (superseded || !peripheral) return;
|
|
121
|
+
if (this.subscribed) await quietly(() => withTimeout(() => find(this.characteristics, VEHICLE_NOTIFY_UUID).unsubscribeFromNotifications(), HOMEY_UNSUBSCRIBE_TIMEOUT_MS));
|
|
122
|
+
await quietly(() => peripheral.disconnect());
|
|
123
|
+
})();
|
|
124
|
+
return this.closing;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
var HomeyBleTransport = class {
|
|
128
|
+
ble;
|
|
129
|
+
maxWriteSize;
|
|
130
|
+
claims = new LinkClaims();
|
|
131
|
+
scanTimeoutMs = HOMEY_SCAN_TIMEOUT_MS;
|
|
132
|
+
/** @param ble `this.homey.ble`. The app needs the `homey:wireless:ble` permission. */
|
|
133
|
+
constructor(ble, options = {}) {
|
|
134
|
+
this.ble = ble;
|
|
135
|
+
this.maxWriteSize = options.maxWriteSize === void 0 ? null : options.maxWriteSize;
|
|
136
|
+
}
|
|
137
|
+
/** Takes Homey's fixed window, whatever `timeoutMs` says. Never pass `discover` a filter. */
|
|
138
|
+
async scan({ matches }) {
|
|
139
|
+
return (await this.ble.discover()).filter((advertisement) => !matches || advertisement.localName !== void 0 && matches(advertisement.localName)).map((advertisement) => ({
|
|
140
|
+
id: advertisement.uuid,
|
|
141
|
+
name: advertisement.localName,
|
|
142
|
+
rssi: advertisement.rssi,
|
|
143
|
+
connectable: advertisement.connectable
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
async connect(device, handlers) {
|
|
147
|
+
const connection = new HomeyConnection(device.id, handlers, this.maxWriteSize, this.claims);
|
|
148
|
+
this.claims.claim(device.id, connection);
|
|
149
|
+
try {
|
|
150
|
+
const peripheral = await (await this.ble.find(device.id)).connect();
|
|
151
|
+
await withTimeout(() => connection.attach(peripheral), HOMEY_SETUP_TIMEOUT_MS);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
await connection.disconnect();
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
return connection;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
export { HOMEY_MAX_WRITE_SIZE, HOMEY_SCAN_TIMEOUT_MS, HOMEY_SETUP_TIMEOUT_MS, HOMEY_UNSUBSCRIBE_TIMEOUT_MS, HOMEY_VEHICLE_OPTIONS, HomeyBleTransport };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
let crypto = require("crypto");
|
|
3
|
+
crypto = require_runtime.__toESM(crypto, 1);
|
|
4
|
+
// src/bluetooth/names.ts
|
|
5
|
+
const VALID_NAME = /^S[a-f0-9]{16}[CDRP]$/;
|
|
6
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
7
|
+
function valid_name(name) {
|
|
8
|
+
return VALID_NAME.test(name);
|
|
9
|
+
}
|
|
10
|
+
/** The BLE device name of a vehicle. */
|
|
11
|
+
function get_name(vin) {
|
|
12
|
+
return `S${crypto.default.createHash("sha1").update(vin, "utf8").digest("hex").slice(0, 16)}C`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Whether an advertised name belongs to this VIN. The hash matches, with any of
|
|
16
|
+
* the four suffixes `valid_name` allows, which is wider than Python's exact
|
|
17
|
+
* match on `C`.
|
|
18
|
+
*/
|
|
19
|
+
function name_matches_vin(advertised, vin) {
|
|
20
|
+
return valid_name(advertised) && advertised.slice(0, 17) === get_name(vin).slice(0, 17);
|
|
21
|
+
}
|
|
22
|
+
exports.get_name = get_name;
|
|
23
|
+
exports.name_matches_vin = name_matches_vin;
|
|
24
|
+
exports.valid_name = valid_name;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
2
|
+
export declare function valid_name(name: string): boolean;
|
|
3
|
+
/** The BLE device name of a vehicle. */
|
|
4
|
+
export declare function get_name(vin: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Whether an advertised name belongs to this VIN. The hash matches, with any of
|
|
7
|
+
* the four suffixes `valid_name` allows, which is wider than Python's exact
|
|
8
|
+
* match on `C`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function name_matches_vin(advertised: string, vin: string): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
2
|
+
export declare function valid_name(name: string): boolean;
|
|
3
|
+
/** The BLE device name of a vehicle. */
|
|
4
|
+
export declare function get_name(vin: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Whether an advertised name belongs to this VIN. The hash matches, with any of
|
|
7
|
+
* the four suffixes `valid_name` allows, which is wider than Python's exact
|
|
8
|
+
* match on `C`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function name_matches_vin(advertised: string, vin: string): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
// src/bluetooth/names.ts
|
|
3
|
+
const VALID_NAME = /^S[a-f0-9]{16}[CDRP]$/;
|
|
4
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
5
|
+
function valid_name(name) {
|
|
6
|
+
return VALID_NAME.test(name);
|
|
7
|
+
}
|
|
8
|
+
/** The BLE device name of a vehicle. */
|
|
9
|
+
function get_name(vin) {
|
|
10
|
+
return `S${crypto.createHash("sha1").update(vin, "utf8").digest("hex").slice(0, 16)}C`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Whether an advertised name belongs to this VIN. The hash matches, with any of
|
|
14
|
+
* the four suffixes `valid_name` allows, which is wider than Python's exact
|
|
15
|
+
* match on `C`.
|
|
16
|
+
*/
|
|
17
|
+
function name_matches_vin(advertised, vin) {
|
|
18
|
+
return valid_name(advertised) && advertised.slice(0, 17) === get_name(vin).slice(0, 17);
|
|
19
|
+
}
|
|
20
|
+
export { get_name, name_matches_vin, valid_name };
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_async = require("./async.cjs");
|
|
3
|
+
const require_constants = require("./constants.cjs");
|
|
4
|
+
const require_transport = require("./transport.cjs");
|
|
5
|
+
const require_shared = require("./adapters/shared.cjs");
|
|
6
|
+
// src/bluetooth/adapters/noble.ts
|
|
7
|
+
function find(characteristics, uuid) {
|
|
8
|
+
return characteristics.find((characteristic) => require_shared.compactUuid(characteristic.uuid) === require_shared.compactUuid(uuid));
|
|
9
|
+
}
|
|
10
|
+
async function loadNoble(bindings) {
|
|
11
|
+
const specifier = "@stoprocent/noble/lib/resolve-bindings.js";
|
|
12
|
+
let module;
|
|
13
|
+
try {
|
|
14
|
+
module = await import(specifier);
|
|
15
|
+
} catch (cause) {
|
|
16
|
+
throw new Error("NobleBleTransport needs @stoprocent/noble, or a `noble` option.", { cause });
|
|
17
|
+
}
|
|
18
|
+
return module.default(bindings);
|
|
19
|
+
}
|
|
20
|
+
var NobleConnection = class {
|
|
21
|
+
peripheral;
|
|
22
|
+
handlers;
|
|
23
|
+
claims;
|
|
24
|
+
lost = false;
|
|
25
|
+
closed = false;
|
|
26
|
+
closing;
|
|
27
|
+
genericAccess;
|
|
28
|
+
characteristics = [];
|
|
29
|
+
onData = (data, isNotification) => {
|
|
30
|
+
if (isNotification !== false) this.handlers.onNotify(data);
|
|
31
|
+
};
|
|
32
|
+
onDisconnect = () => {
|
|
33
|
+
this.lost = true;
|
|
34
|
+
if (!this.closed) this.handlers.onDisconnect();
|
|
35
|
+
};
|
|
36
|
+
constructor(peripheral, handlers, claims) {
|
|
37
|
+
this.peripheral = peripheral;
|
|
38
|
+
this.handlers = handlers;
|
|
39
|
+
this.claims = claims;
|
|
40
|
+
}
|
|
41
|
+
get isConnected() {
|
|
42
|
+
return !this.lost && !this.closed && this.peripheral.state === "connected";
|
|
43
|
+
}
|
|
44
|
+
/** Read each time: noble can report a larger MTU after the link is up. */
|
|
45
|
+
get maxWriteSize() {
|
|
46
|
+
const { mtu } = this.peripheral;
|
|
47
|
+
return mtu && mtu > 3 ? mtu - 3 : 20;
|
|
48
|
+
}
|
|
49
|
+
/** Resolve the characteristics and turn notifications on. */
|
|
50
|
+
async subscribe() {
|
|
51
|
+
const { peripheral } = this;
|
|
52
|
+
const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync([require_shared.compactUuid(require_constants.SERVICE_UUID)], [
|
|
53
|
+
require_constants.VEHICLE_WRITE_UUID,
|
|
54
|
+
require_constants.VEHICLE_NOTIFY_UUID,
|
|
55
|
+
require_constants.VERSION_UUID
|
|
56
|
+
].map(require_shared.compactUuid));
|
|
57
|
+
const notify = find(characteristics, require_constants.VEHICLE_NOTIFY_UUID);
|
|
58
|
+
if (!notify || !find(characteristics, "00000212-b2d1-43f0-9b88-960cebf8b91e")) throw new Error("The vehicle's write or notify characteristic was not found.");
|
|
59
|
+
this.assertCurrent();
|
|
60
|
+
this.characteristics = characteristics;
|
|
61
|
+
notify.on("data", this.onData);
|
|
62
|
+
await notify.subscribeAsync();
|
|
63
|
+
}
|
|
64
|
+
/** A superseded attempt stops here, before it changes a link another attempt owns. */
|
|
65
|
+
assertCurrent() {
|
|
66
|
+
if (this.claims.superseded(this.peripheral.id, this)) throw new Error("A newer connect attempt took over this link.");
|
|
67
|
+
}
|
|
68
|
+
async write(data) {
|
|
69
|
+
const characteristic = find(this.characteristics, require_constants.VEHICLE_WRITE_UUID);
|
|
70
|
+
if (!characteristic) throw new require_transport.BleNotSubmittedError("The vehicle write characteristic was not found.");
|
|
71
|
+
if (!this.isConnected) throw new require_transport.BleNotSubmittedError("The link is down.");
|
|
72
|
+
await characteristic.writeAsync(Buffer.from(data), false);
|
|
73
|
+
}
|
|
74
|
+
async read(characteristic) {
|
|
75
|
+
const target = characteristic === "version" ? find(this.characteristics, require_constants.VERSION_UUID) : find(await this.discoverGenericAccess(), characteristic === "deviceName" ? require_constants.NAME_UUID : require_constants.APPEARANCE_UUID);
|
|
76
|
+
if (!target) throw new require_transport.BleNotSubmittedError(`The ${characteristic} characteristic was not found.`);
|
|
77
|
+
return target.readAsync();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* BlueZ and CoreBluetooth can hide this service. A failed lookup leaves a
|
|
81
|
+
* characteristic unresolved on a healthy link, so it must not read as a lost one.
|
|
82
|
+
*/
|
|
83
|
+
discoverGenericAccess() {
|
|
84
|
+
this.genericAccess ??= this.peripheral.discoverSomeServicesAndCharacteristicsAsync([require_shared.compactUuid(require_constants.GENERIC_ACCESS_UUID)], [require_shared.compactUuid(require_constants.NAME_UUID), require_shared.compactUuid(require_constants.APPEARANCE_UUID)]).then(({ characteristics }) => characteristics).catch((cause) => {
|
|
85
|
+
this.genericAccess = void 0;
|
|
86
|
+
throw new require_transport.BleNotSubmittedError("Generic Access could not be resolved.", { cause });
|
|
87
|
+
});
|
|
88
|
+
return this.genericAccess;
|
|
89
|
+
}
|
|
90
|
+
disconnect() {
|
|
91
|
+
this.closing ??= (async () => {
|
|
92
|
+
this.closed = true;
|
|
93
|
+
const { peripheral, claims } = this;
|
|
94
|
+
peripheral.removeListener("disconnect", this.onDisconnect);
|
|
95
|
+
find(this.characteristics, require_constants.VEHICLE_NOTIFY_UUID)?.removeListener("data", this.onData);
|
|
96
|
+
const superseded = claims.superseded(peripheral.id, this);
|
|
97
|
+
claims.release(peripheral.id, this);
|
|
98
|
+
if (!superseded && peripheral.state !== "disconnected") await require_shared.quietly(() => peripheral.disconnectAsync());
|
|
99
|
+
})();
|
|
100
|
+
return this.closing;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
var NobleBleTransport = class {
|
|
104
|
+
options;
|
|
105
|
+
backend;
|
|
106
|
+
scans = new require_async.AsyncLock();
|
|
107
|
+
peripherals = /* @__PURE__ */ new Map();
|
|
108
|
+
claims = new require_shared.LinkClaims();
|
|
109
|
+
constructor(options = {}) {
|
|
110
|
+
this.options = options;
|
|
111
|
+
}
|
|
112
|
+
noble() {
|
|
113
|
+
if (this.options.noble) return Promise.resolve(this.options.noble);
|
|
114
|
+
const fallback = process.platform === "linux" ? "dbus" : "default";
|
|
115
|
+
const loading = this.backend ??= loadNoble(this.options.bindings ?? fallback);
|
|
116
|
+
loading.catch(() => {
|
|
117
|
+
this.backend = void 0;
|
|
118
|
+
});
|
|
119
|
+
return loading;
|
|
120
|
+
}
|
|
121
|
+
async scan({ timeoutMs, matches }) {
|
|
122
|
+
const noble = await this.noble();
|
|
123
|
+
return this.listen(noble, timeoutMs, (device) => device.name !== void 0 && matches?.(device.name) === true);
|
|
124
|
+
}
|
|
125
|
+
/** Scan with no service filter until `done` accepts a device or the window ends. */
|
|
126
|
+
listen(noble, timeoutMs, done) {
|
|
127
|
+
return this.scans.run(async () => {
|
|
128
|
+
await noble.waitForPoweredOnAsync(timeoutMs);
|
|
129
|
+
const seen = /* @__PURE__ */ new Map();
|
|
130
|
+
let finish;
|
|
131
|
+
const finished = new Promise((resolve) => {
|
|
132
|
+
finish = resolve;
|
|
133
|
+
});
|
|
134
|
+
const onDiscover = (peripheral) => {
|
|
135
|
+
this.peripherals.set(peripheral.id, peripheral);
|
|
136
|
+
const device = {
|
|
137
|
+
id: peripheral.id,
|
|
138
|
+
name: peripheral.advertisement.localName ?? seen.get(peripheral.id)?.name,
|
|
139
|
+
rssi: peripheral.rssi,
|
|
140
|
+
connectable: peripheral.connectable
|
|
141
|
+
};
|
|
142
|
+
seen.set(peripheral.id, device);
|
|
143
|
+
if (done(device)) finish();
|
|
144
|
+
};
|
|
145
|
+
noble.on("discover", onDiscover);
|
|
146
|
+
const timer = setTimeout(finish, Math.max(0, timeoutMs - 250));
|
|
147
|
+
try {
|
|
148
|
+
await noble.startScanningAsync([], true);
|
|
149
|
+
await finished;
|
|
150
|
+
} finally {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
noble.removeListener("discover", onDiscover);
|
|
153
|
+
await require_shared.quietly(() => noble.stopScanningAsync());
|
|
154
|
+
}
|
|
155
|
+
return [...seen.values()];
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async connect(device, handlers) {
|
|
159
|
+
const noble = await this.noble();
|
|
160
|
+
if (!this.peripherals.has(device.id)) await this.listen(noble, require_constants.DEFAULT_SCAN_TIMEOUT_MS, ({ id }) => id === device.id);
|
|
161
|
+
const peripheral = this.peripherals.get(device.id);
|
|
162
|
+
if (!peripheral) throw new Error("The device was not seen in a scan.");
|
|
163
|
+
await require_shared.quietly(() => noble.stopScanningAsync());
|
|
164
|
+
const connection = new NobleConnection(peripheral, handlers, this.claims);
|
|
165
|
+
this.claims.claim(device.id, connection);
|
|
166
|
+
peripheral.once("disconnect", connection.onDisconnect);
|
|
167
|
+
try {
|
|
168
|
+
await peripheral.connectAsync();
|
|
169
|
+
connection.assertCurrent();
|
|
170
|
+
await require_async.withTimeout(() => connection.subscribe(), require_constants.DEFAULT_CONNECT_TIMEOUT_MS);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
await connection.disconnect();
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
return connection;
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
exports.NobleBleTransport = NobleBleTransport;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BleConnection, BleDeviceRef, BleLinkHandlers, BleScanOptions, BleTransport } from "./transport.cjs";
|
|
2
|
+
export interface NobleCharacteristicLike {
|
|
3
|
+
uuid: string;
|
|
4
|
+
on(event: "data", listener: (data: Buffer, isNotification?: boolean) => void): unknown;
|
|
5
|
+
removeListener(event: "data", listener: (data: Buffer, isNotification?: boolean) => void): unknown;
|
|
6
|
+
subscribeAsync(): Promise<void>;
|
|
7
|
+
/** `withoutResponse: false` is a write with response. */
|
|
8
|
+
writeAsync(data: Buffer, withoutResponse: boolean): Promise<void>;
|
|
9
|
+
readAsync(): Promise<Buffer>;
|
|
10
|
+
}
|
|
11
|
+
export interface NoblePeripheralLike {
|
|
12
|
+
id: string;
|
|
13
|
+
state: string;
|
|
14
|
+
mtu: number | null;
|
|
15
|
+
rssi?: number;
|
|
16
|
+
connectable?: boolean;
|
|
17
|
+
advertisement: {
|
|
18
|
+
localName?: string;
|
|
19
|
+
};
|
|
20
|
+
connectAsync(): Promise<void>;
|
|
21
|
+
disconnectAsync(): Promise<void>;
|
|
22
|
+
discoverSomeServicesAndCharacteristicsAsync(serviceUUIDs: string[], characteristicUUIDs: string[]): Promise<{
|
|
23
|
+
characteristics: NobleCharacteristicLike[];
|
|
24
|
+
}>;
|
|
25
|
+
once(event: "disconnect", listener: () => void): unknown;
|
|
26
|
+
removeListener(event: "disconnect", listener: () => void): unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface NobleLike {
|
|
29
|
+
waitForPoweredOnAsync(timeout?: number): Promise<void>;
|
|
30
|
+
startScanningAsync(serviceUUIDs?: string[], allowDuplicates?: boolean): Promise<void>;
|
|
31
|
+
stopScanningAsync(): Promise<void>;
|
|
32
|
+
on(event: "discover", listener: (peripheral: NoblePeripheralLike) => void): unknown;
|
|
33
|
+
removeListener(event: "discover", listener: (peripheral: NoblePeripheralLike) => void): unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface NobleBleTransportOptions {
|
|
36
|
+
/** A noble you built, such as `withBindings("dbus")`. Without it, `@stoprocent/noble` loads on first use. */
|
|
37
|
+
noble?: NobleLike;
|
|
38
|
+
/**
|
|
39
|
+
* Which bindings to load. Defaults to `"dbus"` on Linux, which shares the
|
|
40
|
+
* adapter with BlueZ and needs no root but does need `dbus-next`, and to
|
|
41
|
+
* `"default"` elsewhere. `"hci"` needs `sudo setcap cap_net_raw+eip` on the
|
|
42
|
+
* Node binary.
|
|
43
|
+
*/
|
|
44
|
+
bindings?: "default" | "dbus" | "hci" | "mac" | "win";
|
|
45
|
+
}
|
|
46
|
+
export declare class NobleBleTransport implements BleTransport {
|
|
47
|
+
private readonly options;
|
|
48
|
+
private backend;
|
|
49
|
+
private readonly scans;
|
|
50
|
+
private readonly peripherals;
|
|
51
|
+
private readonly claims;
|
|
52
|
+
constructor(options?: NobleBleTransportOptions);
|
|
53
|
+
private noble;
|
|
54
|
+
scan({ timeoutMs, matches }: BleScanOptions): Promise<BleDeviceRef[]>;
|
|
55
|
+
/** Scan with no service filter until `done` accepts a device or the window ends. */
|
|
56
|
+
private listen;
|
|
57
|
+
connect(device: BleDeviceRef, handlers: BleLinkHandlers): Promise<BleConnection>;
|
|
58
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { BleConnection, BleDeviceRef, BleLinkHandlers, BleScanOptions, BleTransport } from "./transport.mjs";
|
|
2
|
+
export interface NobleCharacteristicLike {
|
|
3
|
+
uuid: string;
|
|
4
|
+
on(event: "data", listener: (data: Buffer, isNotification?: boolean) => void): unknown;
|
|
5
|
+
removeListener(event: "data", listener: (data: Buffer, isNotification?: boolean) => void): unknown;
|
|
6
|
+
subscribeAsync(): Promise<void>;
|
|
7
|
+
/** `withoutResponse: false` is a write with response. */
|
|
8
|
+
writeAsync(data: Buffer, withoutResponse: boolean): Promise<void>;
|
|
9
|
+
readAsync(): Promise<Buffer>;
|
|
10
|
+
}
|
|
11
|
+
export interface NoblePeripheralLike {
|
|
12
|
+
id: string;
|
|
13
|
+
state: string;
|
|
14
|
+
mtu: number | null;
|
|
15
|
+
rssi?: number;
|
|
16
|
+
connectable?: boolean;
|
|
17
|
+
advertisement: {
|
|
18
|
+
localName?: string;
|
|
19
|
+
};
|
|
20
|
+
connectAsync(): Promise<void>;
|
|
21
|
+
disconnectAsync(): Promise<void>;
|
|
22
|
+
discoverSomeServicesAndCharacteristicsAsync(serviceUUIDs: string[], characteristicUUIDs: string[]): Promise<{
|
|
23
|
+
characteristics: NobleCharacteristicLike[];
|
|
24
|
+
}>;
|
|
25
|
+
once(event: "disconnect", listener: () => void): unknown;
|
|
26
|
+
removeListener(event: "disconnect", listener: () => void): unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface NobleLike {
|
|
29
|
+
waitForPoweredOnAsync(timeout?: number): Promise<void>;
|
|
30
|
+
startScanningAsync(serviceUUIDs?: string[], allowDuplicates?: boolean): Promise<void>;
|
|
31
|
+
stopScanningAsync(): Promise<void>;
|
|
32
|
+
on(event: "discover", listener: (peripheral: NoblePeripheralLike) => void): unknown;
|
|
33
|
+
removeListener(event: "discover", listener: (peripheral: NoblePeripheralLike) => void): unknown;
|
|
34
|
+
}
|
|
35
|
+
export interface NobleBleTransportOptions {
|
|
36
|
+
/** A noble you built, such as `withBindings("dbus")`. Without it, `@stoprocent/noble` loads on first use. */
|
|
37
|
+
noble?: NobleLike;
|
|
38
|
+
/**
|
|
39
|
+
* Which bindings to load. Defaults to `"dbus"` on Linux, which shares the
|
|
40
|
+
* adapter with BlueZ and needs no root but does need `dbus-next`, and to
|
|
41
|
+
* `"default"` elsewhere. `"hci"` needs `sudo setcap cap_net_raw+eip` on the
|
|
42
|
+
* Node binary.
|
|
43
|
+
*/
|
|
44
|
+
bindings?: "default" | "dbus" | "hci" | "mac" | "win";
|
|
45
|
+
}
|
|
46
|
+
export declare class NobleBleTransport implements BleTransport {
|
|
47
|
+
private readonly options;
|
|
48
|
+
private backend;
|
|
49
|
+
private readonly scans;
|
|
50
|
+
private readonly peripherals;
|
|
51
|
+
private readonly claims;
|
|
52
|
+
constructor(options?: NobleBleTransportOptions);
|
|
53
|
+
private noble;
|
|
54
|
+
scan({ timeoutMs, matches }: BleScanOptions): Promise<BleDeviceRef[]>;
|
|
55
|
+
/** Scan with no service filter until `done` accepts a device or the window ends. */
|
|
56
|
+
private listen;
|
|
57
|
+
connect(device: BleDeviceRef, handlers: BleLinkHandlers): Promise<BleConnection>;
|
|
58
|
+
}
|