tesla-fleet-api 0.4.0 → 0.5.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 +284 -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 +99 -0
- package/dist/bluetooth/constants.d.cts +70 -0
- package/dist/bluetooth/constants.d.mts +70 -0
- package/dist/bluetooth/constants.mjs +72 -0
- package/dist/bluetooth/esphome.cjs +292 -0
- package/dist/bluetooth/esphome.d.cts +83 -0
- package/dist/bluetooth/esphome.d.mts +83 -0
- package/dist/bluetooth/esphome.mjs +291 -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 +155 -0
- package/dist/bluetooth/homey.d.cts +75 -0
- package/dist/bluetooth/homey.d.mts +75 -0
- package/dist/bluetooth/homey.mjs +150 -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 +95 -0
- package/dist/bluetooth/transport.d.mts +95 -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 +53 -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 +70 -3
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Clock, FieldPath, ObservationSink, Publisher, Unsubscribe } from "../funnel.mjs";
|
|
2
|
+
import { BroadcastListeners } from "./broadcast.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* What the glue feeds. Structurally a Teslemetry stream's `ingest`: no stream
|
|
5
|
+
* package is imported here, so a sink can be anything with this method.
|
|
6
|
+
*/
|
|
7
|
+
export interface StreamSink {
|
|
8
|
+
ingest(data: Record<string, unknown>, metadata?: Record<string, unknown>): unknown;
|
|
9
|
+
}
|
|
10
|
+
export declare const GEAR_STATES: ReadonlyMap<number, string>;
|
|
11
|
+
export declare const TONNEAU_POSITION_STATES: ReadonlyMap<number, string>;
|
|
12
|
+
/**
|
|
13
|
+
* Feeds a vehicle's broadcasts into a stream-shaped sink, so a Bluetooth reading
|
|
14
|
+
* reaches the listeners a native stream event does. There is no source ranking:
|
|
15
|
+
* every call reaches them in arrival order.
|
|
16
|
+
*/
|
|
17
|
+
export declare class BleBroadcastStreamGlue {
|
|
18
|
+
#private;
|
|
19
|
+
constructor(vehicle: BroadcastListeners, sink: StreamSink);
|
|
20
|
+
/** Stop listening. Safe to call twice. */
|
|
21
|
+
stop(): void;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Publishes VCSEC status broadcasts to an `ObservationFunnel`. Registration only:
|
|
25
|
+
* a dropped session ends the broadcasts and says nothing about the fields, so it
|
|
26
|
+
* publishes nothing.
|
|
27
|
+
*/
|
|
28
|
+
export declare class BleBroadcastPublisher implements Publisher {
|
|
29
|
+
#private;
|
|
30
|
+
readonly paths: ReadonlySet<FieldPath>;
|
|
31
|
+
constructor(vehicle: BroadcastListeners, options?: {
|
|
32
|
+
clock?: Clock;
|
|
33
|
+
});
|
|
34
|
+
attach(sink: ObservationSink): Unsubscribe;
|
|
35
|
+
request(paths: ReadonlySet<FieldPath>): void;
|
|
36
|
+
release(paths: ReadonlySet<FieldPath>): void;
|
|
37
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { CLOSURE_STATES, FieldPath, LOCK_STATES } from "../funnel.mjs";
|
|
2
|
+
import { ClosureStateE, GearE, closureStateEToJSON, gearEToJSON, vehicleLockStateEToJSON } from "@teslemetry/tesla-protocol/command/vcsec";
|
|
3
|
+
// src/bluetooth/streamglue.ts
|
|
4
|
+
const GEAR_STATES = /* @__PURE__ */ new Map([
|
|
5
|
+
[GearE.GEAR_PARK, "ShiftStateP"],
|
|
6
|
+
[GearE.GEAR_DRIVE, "ShiftStateD"],
|
|
7
|
+
[GearE.GEAR_REVERSE, "ShiftStateR"],
|
|
8
|
+
[GearE.GEAR_NEUTRAL, "ShiftStateN"]
|
|
9
|
+
]);
|
|
10
|
+
const TONNEAU_POSITION_STATES = /* @__PURE__ */ new Map([
|
|
11
|
+
[ClosureStateE.CLOSURESTATE_CLOSED, "TonneauPositionStateClosed"],
|
|
12
|
+
[ClosureStateE.CLOSURESTATE_OPEN, "TonneauPositionStateFullyOpen"],
|
|
13
|
+
[ClosureStateE.CLOSURESTATE_AJAR, "TonneauPositionStatePartiallyOpen"]
|
|
14
|
+
]);
|
|
15
|
+
/** A closure state's name, or the bare number for one this build has no name for. */
|
|
16
|
+
function closureName(state) {
|
|
17
|
+
const name = closureStateEToJSON(state);
|
|
18
|
+
return name === "UNRECOGNIZED" ? String(state) : name;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Feeds a vehicle's broadcasts into a stream-shaped sink, so a Bluetooth reading
|
|
22
|
+
* reaches the listeners a native stream event does. There is no source ranking:
|
|
23
|
+
* every call reaches them in arrival order.
|
|
24
|
+
*/
|
|
25
|
+
var BleBroadcastStreamGlue = class {
|
|
26
|
+
#sink;
|
|
27
|
+
#unsubscribes;
|
|
28
|
+
constructor(vehicle, sink) {
|
|
29
|
+
this.#sink = sink;
|
|
30
|
+
this.#unsubscribes = [
|
|
31
|
+
vehicle.listen_vehicle_lock_state((state) => this.#onLockState(state)),
|
|
32
|
+
vehicle.listen_charge_port((state) => this.#onChargePort(state)),
|
|
33
|
+
vehicle.listen_closure_statuses((closures) => this.#onClosures(closures)),
|
|
34
|
+
vehicle.listen_gear((gear) => this.#onGear(gear)),
|
|
35
|
+
vehicle.listen_tonneau((state) => this.#onTonneau(state)),
|
|
36
|
+
vehicle.listen_tonneau_percent_open((percent) => this.#sink.ingest({ TonneauOpenPercent: percent }, { source: "bluetooth" }))
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
/** Stop listening. Safe to call twice. */
|
|
40
|
+
stop() {
|
|
41
|
+
for (const unsubscribe of this.#unsubscribes) unsubscribe();
|
|
42
|
+
this.#unsubscribes = [];
|
|
43
|
+
}
|
|
44
|
+
#onLockState(state) {
|
|
45
|
+
const locked = LOCK_STATES.get(state);
|
|
46
|
+
if (locked !== void 0) this.#sink.ingest({ Locked: locked }, {
|
|
47
|
+
source: "bluetooth",
|
|
48
|
+
raw: vehicleLockStateEToJSON(state)
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
#onChargePort(state) {
|
|
52
|
+
const open = CLOSURE_STATES.get(state);
|
|
53
|
+
if (open !== void 0) this.#sink.ingest({ ChargePortDoorOpen: open }, {
|
|
54
|
+
source: "bluetooth",
|
|
55
|
+
raw: closureStateEToJSON(state)
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Every door listener fires on any `DoorState` event and reads its own key, so
|
|
60
|
+
* a leaf sent alone would reach the other five as missing. All six go in one
|
|
61
|
+
* event, as a native event's do, with a leaf in an unmapped state left out.
|
|
62
|
+
*/
|
|
63
|
+
#onClosures(closures) {
|
|
64
|
+
const raw = {
|
|
65
|
+
DriverFront: closures.frontDriverDoor,
|
|
66
|
+
PassengerFront: closures.frontPassengerDoor,
|
|
67
|
+
DriverRear: closures.rearDriverDoor,
|
|
68
|
+
PassengerRear: closures.rearPassengerDoor,
|
|
69
|
+
TrunkFront: closures.frontTrunk,
|
|
70
|
+
TrunkRear: closures.rearTrunk
|
|
71
|
+
};
|
|
72
|
+
const leaves = Object.entries(raw);
|
|
73
|
+
const doorState = leaves.flatMap(([key, state]) => {
|
|
74
|
+
const open = CLOSURE_STATES.get(state);
|
|
75
|
+
return open === void 0 ? [] : [[key, open]];
|
|
76
|
+
});
|
|
77
|
+
if (doorState.length > 0) this.#sink.ingest({ DoorState: Object.fromEntries(doorState) }, {
|
|
78
|
+
source: "bluetooth",
|
|
79
|
+
raw: Object.fromEntries(leaves.map(([key, state]) => [key, closureName(state)]))
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
#onGear(gear) {
|
|
83
|
+
const state = GEAR_STATES.get(gear);
|
|
84
|
+
if (state !== void 0) this.#sink.ingest({ Gear: state }, {
|
|
85
|
+
source: "bluetooth",
|
|
86
|
+
raw: gearEToJSON(gear)
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
#onTonneau(state) {
|
|
90
|
+
const position = TONNEAU_POSITION_STATES.get(state);
|
|
91
|
+
if (position !== void 0) this.#sink.ingest({ TonneauPosition: position }, {
|
|
92
|
+
source: "bluetooth",
|
|
93
|
+
raw: closureStateEToJSON(state)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
/** The map and listener behind each field a broadcast carries. */
|
|
98
|
+
const BROADCAST_FIELDS = /* @__PURE__ */ new Map([
|
|
99
|
+
[FieldPath.LOCKED, {
|
|
100
|
+
states: LOCK_STATES,
|
|
101
|
+
listen: (vehicle, callback) => vehicle.listen_vehicle_lock_state(callback)
|
|
102
|
+
}],
|
|
103
|
+
[FieldPath.CHARGE_PORT_DOOR_OPEN, {
|
|
104
|
+
states: CLOSURE_STATES,
|
|
105
|
+
listen: (vehicle, callback) => vehicle.listen_charge_port(callback)
|
|
106
|
+
}],
|
|
107
|
+
[FieldPath.DOOR_STATE_TRUNK_FRONT, {
|
|
108
|
+
states: CLOSURE_STATES,
|
|
109
|
+
listen: (vehicle, callback) => vehicle.listen_front_trunk(callback)
|
|
110
|
+
}]
|
|
111
|
+
]);
|
|
112
|
+
/**
|
|
113
|
+
* Publishes VCSEC status broadcasts to an `ObservationFunnel`. Registration only:
|
|
114
|
+
* a dropped session ends the broadcasts and says nothing about the fields, so it
|
|
115
|
+
* publishes nothing.
|
|
116
|
+
*/
|
|
117
|
+
var BleBroadcastPublisher = class {
|
|
118
|
+
paths = new Set(BROADCAST_FIELDS.keys());
|
|
119
|
+
#vehicle;
|
|
120
|
+
#clock;
|
|
121
|
+
#subscriptions = /* @__PURE__ */ new Map();
|
|
122
|
+
#sink = null;
|
|
123
|
+
constructor(vehicle, options = {}) {
|
|
124
|
+
this.#vehicle = vehicle;
|
|
125
|
+
this.#clock = options.clock ?? (() => performance.now() / 1e3);
|
|
126
|
+
}
|
|
127
|
+
attach(sink) {
|
|
128
|
+
this.#sink = sink;
|
|
129
|
+
return () => {
|
|
130
|
+
this.release(new Set(this.#subscriptions.keys()));
|
|
131
|
+
this.#sink = null;
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
request(paths) {
|
|
135
|
+
for (const path of paths) {
|
|
136
|
+
const field = BROADCAST_FIELDS.get(path);
|
|
137
|
+
if (field && !this.#subscriptions.has(path)) this.#subscriptions.set(path, field.listen(this.#vehicle, (state) => {
|
|
138
|
+
const value = field.states.get(state);
|
|
139
|
+
if (value !== void 0) this.#sink?.publish({
|
|
140
|
+
path,
|
|
141
|
+
value,
|
|
142
|
+
observed_at: this.#clock()
|
|
143
|
+
});
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
release(paths) {
|
|
148
|
+
for (const path of paths) {
|
|
149
|
+
this.#subscriptions.get(path)?.();
|
|
150
|
+
this.#subscriptions.delete(path);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
export { BleBroadcastPublisher, BleBroadcastStreamGlue, GEAR_STATES, TONNEAU_POSITION_STATES };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const require_exceptions = require("../exceptions.cjs");
|
|
2
|
+
const require_teslafleetapi = require("../teslafleetapi.cjs");
|
|
3
|
+
const require_vehicle = require("../vehicle.cjs");
|
|
4
|
+
const require_names = require("./names.cjs");
|
|
5
|
+
const require_vehiclebluetooth = require("./vehiclebluetooth.cjs");
|
|
6
|
+
// src/bluetooth/teslabluetooth.ts
|
|
7
|
+
/**
|
|
8
|
+
* The vehicles of a `TeslaBluetooth`. `createBluetooth` lives here, not on the
|
|
9
|
+
* root `Vehicle`, so the root entry never loads Bluetooth code.
|
|
10
|
+
*/
|
|
11
|
+
var VehiclesBluetooth = class extends require_vehicle.default {
|
|
12
|
+
/** Class `createBluetooth` instantiates; a subclass overrides it to supply its own vehicle type. */
|
|
13
|
+
static Bluetooth = require_vehiclebluetooth.default;
|
|
14
|
+
/** Same as `createBluetooth`, under Python's other name. */
|
|
15
|
+
create(vin, options) {
|
|
16
|
+
return this.createBluetooth(vin, options);
|
|
17
|
+
}
|
|
18
|
+
/** Creates a vehicle reached over Bluetooth through `options.transport`. */
|
|
19
|
+
createBluetooth(vin, options) {
|
|
20
|
+
const vehicle = new this.constructor.Bluetooth(this, vin, options);
|
|
21
|
+
this.byVin.set(vin, vehicle);
|
|
22
|
+
return vehicle;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A client with no cloud behind it, for vehicles reached only over Bluetooth.
|
|
27
|
+
* Every REST path raises `BluetoothCloudUnavailableError` before any I/O, so a
|
|
28
|
+
* REST method a vehicle inherits fails clearly, sends nothing, and is safe for
|
|
29
|
+
* a router to fail over on. A cloud client that wants the same key on both
|
|
30
|
+
* paths uses `new VehicleBluetooth(client.vehicles, vin, options)` instead.
|
|
31
|
+
*/
|
|
32
|
+
var TeslaBluetooth = class extends require_teslafleetapi.default {
|
|
33
|
+
static Vehicles = VehiclesBluetooth;
|
|
34
|
+
_transport_name = "bluetooth";
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
super({
|
|
37
|
+
server: "bluetooth://local",
|
|
38
|
+
chargingScope: false,
|
|
39
|
+
energyScope: false,
|
|
40
|
+
partnerScope: false,
|
|
41
|
+
userScope: false,
|
|
42
|
+
debug: options.debug,
|
|
43
|
+
logger: options.logger
|
|
44
|
+
});
|
|
45
|
+
this.private_key = options.privateKey ?? null;
|
|
46
|
+
}
|
|
47
|
+
async _request() {
|
|
48
|
+
throw new require_exceptions.BluetoothCloudUnavailableError();
|
|
49
|
+
}
|
|
50
|
+
async _fetch() {
|
|
51
|
+
throw new require_exceptions.BluetoothCloudUnavailableError();
|
|
52
|
+
}
|
|
53
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
54
|
+
valid_name(name) {
|
|
55
|
+
return require_names.valid_name(name);
|
|
56
|
+
}
|
|
57
|
+
/** The BLE device name of a vehicle. */
|
|
58
|
+
get_name(vin) {
|
|
59
|
+
return require_names.get_name(vin);
|
|
60
|
+
}
|
|
61
|
+
/** Whether an advertised name belongs to this VIN, with any valid suffix. */
|
|
62
|
+
name_matches_vin(advertised, vin) {
|
|
63
|
+
return require_names.name_matches_vin(advertised, vin);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
exports.VehiclesBluetooth = VehiclesBluetooth;
|
|
67
|
+
exports.default = TeslaBluetooth;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Logger } from "../log.cjs";
|
|
2
|
+
import TeslaFleetApi from "../teslafleetapi.cjs";
|
|
3
|
+
import Vehicle from "../vehicle.cjs";
|
|
4
|
+
import VehicleBluetooth, { VehicleBluetoothOptions } from "./vehiclebluetooth.cjs";
|
|
5
|
+
/**
|
|
6
|
+
* The vehicles of a `TeslaBluetooth`. `createBluetooth` lives here, not on the
|
|
7
|
+
* root `Vehicle`, so the root entry never loads Bluetooth code.
|
|
8
|
+
*/
|
|
9
|
+
export declare class VehiclesBluetooth extends Vehicle {
|
|
10
|
+
/** Class `createBluetooth` instantiates; a subclass overrides it to supply its own vehicle type. */
|
|
11
|
+
static Bluetooth: new (parent: Vehicle, vin: string, options: VehicleBluetoothOptions) => VehicleBluetooth;
|
|
12
|
+
/** Same as `createBluetooth`, under Python's other name. */
|
|
13
|
+
create(vin: string, options: VehicleBluetoothOptions): VehicleBluetooth;
|
|
14
|
+
/** Creates a vehicle reached over Bluetooth through `options.transport`. */
|
|
15
|
+
createBluetooth(vin: string, options: VehicleBluetoothOptions): VehicleBluetooth;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A client with no cloud behind it, for vehicles reached only over Bluetooth.
|
|
19
|
+
* Every REST path raises `BluetoothCloudUnavailableError` before any I/O, so a
|
|
20
|
+
* REST method a vehicle inherits fails clearly, sends nothing, and is safe for
|
|
21
|
+
* a router to fail over on. A cloud client that wants the same key on both
|
|
22
|
+
* paths uses `new VehicleBluetooth(client.vehicles, vin, options)` instead.
|
|
23
|
+
*/
|
|
24
|
+
export default class TeslaBluetooth extends TeslaFleetApi {
|
|
25
|
+
static Vehicles: new (parent: TeslaFleetApi) => Vehicle;
|
|
26
|
+
vehicles: VehiclesBluetooth;
|
|
27
|
+
readonly _transport_name: string;
|
|
28
|
+
constructor(options?: {
|
|
29
|
+
privateKey?: Buffer;
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
logger?: Logger;
|
|
32
|
+
});
|
|
33
|
+
_request(): Promise<never>;
|
|
34
|
+
protected _fetch(): Promise<never>;
|
|
35
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
36
|
+
valid_name(name: string): boolean;
|
|
37
|
+
/** The BLE device name of a vehicle. */
|
|
38
|
+
get_name(vin: string): string;
|
|
39
|
+
/** Whether an advertised name belongs to this VIN, with any valid suffix. */
|
|
40
|
+
name_matches_vin(advertised: string, vin: string): boolean;
|
|
41
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Logger } from "../log.mjs";
|
|
2
|
+
import TeslaFleetApi from "../teslafleetapi.mjs";
|
|
3
|
+
import Vehicle from "../vehicle.mjs";
|
|
4
|
+
import VehicleBluetooth, { VehicleBluetoothOptions } from "./vehiclebluetooth.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* The vehicles of a `TeslaBluetooth`. `createBluetooth` lives here, not on the
|
|
7
|
+
* root `Vehicle`, so the root entry never loads Bluetooth code.
|
|
8
|
+
*/
|
|
9
|
+
export declare class VehiclesBluetooth extends Vehicle {
|
|
10
|
+
/** Class `createBluetooth` instantiates; a subclass overrides it to supply its own vehicle type. */
|
|
11
|
+
static Bluetooth: new (parent: Vehicle, vin: string, options: VehicleBluetoothOptions) => VehicleBluetooth;
|
|
12
|
+
/** Same as `createBluetooth`, under Python's other name. */
|
|
13
|
+
create(vin: string, options: VehicleBluetoothOptions): VehicleBluetooth;
|
|
14
|
+
/** Creates a vehicle reached over Bluetooth through `options.transport`. */
|
|
15
|
+
createBluetooth(vin: string, options: VehicleBluetoothOptions): VehicleBluetooth;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A client with no cloud behind it, for vehicles reached only over Bluetooth.
|
|
19
|
+
* Every REST path raises `BluetoothCloudUnavailableError` before any I/O, so a
|
|
20
|
+
* REST method a vehicle inherits fails clearly, sends nothing, and is safe for
|
|
21
|
+
* a router to fail over on. A cloud client that wants the same key on both
|
|
22
|
+
* paths uses `new VehicleBluetooth(client.vehicles, vin, options)` instead.
|
|
23
|
+
*/
|
|
24
|
+
export default class TeslaBluetooth extends TeslaFleetApi {
|
|
25
|
+
static Vehicles: new (parent: TeslaFleetApi) => Vehicle;
|
|
26
|
+
vehicles: VehiclesBluetooth;
|
|
27
|
+
readonly _transport_name: string;
|
|
28
|
+
constructor(options?: {
|
|
29
|
+
privateKey?: Buffer;
|
|
30
|
+
debug?: boolean;
|
|
31
|
+
logger?: Logger;
|
|
32
|
+
});
|
|
33
|
+
_request(): Promise<never>;
|
|
34
|
+
protected _fetch(): Promise<never>;
|
|
35
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
36
|
+
valid_name(name: string): boolean;
|
|
37
|
+
/** The BLE device name of a vehicle. */
|
|
38
|
+
get_name(vin: string): string;
|
|
39
|
+
/** Whether an advertised name belongs to this VIN, with any valid suffix. */
|
|
40
|
+
name_matches_vin(advertised: string, vin: string): boolean;
|
|
41
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { BluetoothCloudUnavailableError } from "../exceptions.mjs";
|
|
2
|
+
import TeslaFleetApi from "../teslafleetapi.mjs";
|
|
3
|
+
import Vehicle from "../vehicle.mjs";
|
|
4
|
+
import { get_name, name_matches_vin, valid_name } from "./names.mjs";
|
|
5
|
+
import VehicleBluetooth from "./vehiclebluetooth.mjs";
|
|
6
|
+
// src/bluetooth/teslabluetooth.ts
|
|
7
|
+
/**
|
|
8
|
+
* The vehicles of a `TeslaBluetooth`. `createBluetooth` lives here, not on the
|
|
9
|
+
* root `Vehicle`, so the root entry never loads Bluetooth code.
|
|
10
|
+
*/
|
|
11
|
+
var VehiclesBluetooth = class extends Vehicle {
|
|
12
|
+
/** Class `createBluetooth` instantiates; a subclass overrides it to supply its own vehicle type. */
|
|
13
|
+
static Bluetooth = VehicleBluetooth;
|
|
14
|
+
/** Same as `createBluetooth`, under Python's other name. */
|
|
15
|
+
create(vin, options) {
|
|
16
|
+
return this.createBluetooth(vin, options);
|
|
17
|
+
}
|
|
18
|
+
/** Creates a vehicle reached over Bluetooth through `options.transport`. */
|
|
19
|
+
createBluetooth(vin, options) {
|
|
20
|
+
const vehicle = new this.constructor.Bluetooth(this, vin, options);
|
|
21
|
+
this.byVin.set(vin, vehicle);
|
|
22
|
+
return vehicle;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* A client with no cloud behind it, for vehicles reached only over Bluetooth.
|
|
27
|
+
* Every REST path raises `BluetoothCloudUnavailableError` before any I/O, so a
|
|
28
|
+
* REST method a vehicle inherits fails clearly, sends nothing, and is safe for
|
|
29
|
+
* a router to fail over on. A cloud client that wants the same key on both
|
|
30
|
+
* paths uses `new VehicleBluetooth(client.vehicles, vin, options)` instead.
|
|
31
|
+
*/
|
|
32
|
+
var TeslaBluetooth = class extends TeslaFleetApi {
|
|
33
|
+
static Vehicles = VehiclesBluetooth;
|
|
34
|
+
_transport_name = "bluetooth";
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
super({
|
|
37
|
+
server: "bluetooth://local",
|
|
38
|
+
chargingScope: false,
|
|
39
|
+
energyScope: false,
|
|
40
|
+
partnerScope: false,
|
|
41
|
+
userScope: false,
|
|
42
|
+
debug: options.debug,
|
|
43
|
+
logger: options.logger
|
|
44
|
+
});
|
|
45
|
+
this.private_key = options.privateKey ?? null;
|
|
46
|
+
}
|
|
47
|
+
async _request() {
|
|
48
|
+
throw new BluetoothCloudUnavailableError();
|
|
49
|
+
}
|
|
50
|
+
async _fetch() {
|
|
51
|
+
throw new BluetoothCloudUnavailableError();
|
|
52
|
+
}
|
|
53
|
+
/** Whether a BLE device name is a valid Tesla vehicle's. */
|
|
54
|
+
valid_name(name) {
|
|
55
|
+
return valid_name(name);
|
|
56
|
+
}
|
|
57
|
+
/** The BLE device name of a vehicle. */
|
|
58
|
+
get_name(vin) {
|
|
59
|
+
return get_name(vin);
|
|
60
|
+
}
|
|
61
|
+
/** Whether an advertised name belongs to this VIN, with any valid suffix. */
|
|
62
|
+
name_matches_vin(advertised, vin) {
|
|
63
|
+
return name_matches_vin(advertised, vin);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
export { VehiclesBluetooth, TeslaBluetooth as default };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/bluetooth/transport.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by an adapter only when bytes provably never entered the BLE stack,
|
|
4
|
+
* such as an unresolved characteristic, or a link the backend reports down
|
|
5
|
+
* before the call. A router can then safely replay the command elsewhere.
|
|
6
|
+
*/
|
|
7
|
+
var BleNotSubmittedError = class extends Error {
|
|
8
|
+
name = "BleNotSubmittedError";
|
|
9
|
+
};
|
|
10
|
+
exports.BleNotSubmittedError = BleNotSubmittedError;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the library and a Bluetooth backend.
|
|
3
|
+
*
|
|
4
|
+
* The library owns framing, reassembly, signing, sessions, retries, keepalive
|
|
5
|
+
* and confirmation. An adapter owns raw GATT and holds no Tesla logic but the
|
|
6
|
+
* UUIDs, which it imports. Around every adapter the library adds one call at a
|
|
7
|
+
* time per connection, a timeout on every call, frame chunking and link-loss
|
|
8
|
+
* handling, so an adapter needs none of them. This file imports no backend,
|
|
9
|
+
* and neither does anything else in `./bluetooth`.
|
|
10
|
+
*
|
|
11
|
+
* The contract asks only for what the weakest backend, Homey, can do:
|
|
12
|
+
*
|
|
13
|
+
* - `scan` may take a fixed window and ignore `timeoutMs`. Homey's `discover()`
|
|
14
|
+
* takes no filter and scans for about 5 s, whatever it is asked. The library
|
|
15
|
+
* filters what comes back, so an adapter may return all it saw.
|
|
16
|
+
* - `connect` needs no timeout of its own, and Homey has none. The library
|
|
17
|
+
* abandons a hung attempt, and closes the connection if it arrives later.
|
|
18
|
+
* - `write` is always with response, the only mode Homey offers and the only
|
|
19
|
+
* one the vehicle takes.
|
|
20
|
+
* - `maxWriteSize` may be left out when the backend hides its MTU. The library
|
|
21
|
+
* then writes `DEFAULT_MAX_WRITE_SIZE` bytes at a time.
|
|
22
|
+
* - `isConnected` and `onDisconnect` may both lie: Homey's state goes stale on
|
|
23
|
+
* a silent drop, and its event is not guaranteed. Neither has to be right,
|
|
24
|
+
* since a failed or timed-out operation is what the library treats as a dead
|
|
25
|
+
* link. A rejection with `BleNotSubmittedError` is the exception.
|
|
26
|
+
*
|
|
27
|
+
* Adapter rules, each worth a test in the adapter's own suite:
|
|
28
|
+
*
|
|
29
|
+
* 1. Never scan with a service filter, because the vehicle advertises none.
|
|
30
|
+
* Scan actively where the backend has a choice: the name is in the scan
|
|
31
|
+
* response.
|
|
32
|
+
* 2. `connect` resolves only once the notify subscription is confirmed, as a
|
|
33
|
+
* reply sent before that is lost.
|
|
34
|
+
* 3. `connect` closes a half-open link before it rejects. The vehicle has
|
|
35
|
+
* three connection slots.
|
|
36
|
+
* 4. Subscribe again on every connect. Subscriptions end with the link.
|
|
37
|
+
* 5. `write` is always with response.
|
|
38
|
+
* 6. Throw `BleNotSubmittedError` only when the failure is provably before any
|
|
39
|
+
* radio I/O, as a read of a characteristic that was never resolved is on a
|
|
40
|
+
* stack that hides Generic Access. It leaves the link up. All other failures
|
|
41
|
+
* stay plain errors, and the library treats them as a lost link.
|
|
42
|
+
* 7. Pass notifications unchanged and in order. The library copies them, so a
|
|
43
|
+
* reused backend buffer is safe once the handler returns.
|
|
44
|
+
* 8. Do not retry writes. A retry can run a command twice.
|
|
45
|
+
* 9. `disconnect` is safe to call twice, and on a dead link.
|
|
46
|
+
*/
|
|
47
|
+
/** Named characteristics, so an adapter resolves service and UUID its own way. */
|
|
48
|
+
export type BleCharacteristic = "version" | "deviceName" | "appearance";
|
|
49
|
+
/** A found vehicle. `id` is opaque: a Homey peripheral uuid, a MAC, a browser device id. */
|
|
50
|
+
export interface BleDeviceRef {
|
|
51
|
+
id: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
rssi?: number;
|
|
54
|
+
/** `false` means the vehicle already holds its maximum BLE connections. */
|
|
55
|
+
connectable?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface BleLinkHandlers {
|
|
58
|
+
/** One notification, unchanged and in arrival order. */
|
|
59
|
+
onNotify(chunk: Uint8Array): void;
|
|
60
|
+
/** Best effort, and a faster signal only. The library also treats a failed operation as a lost link. */
|
|
61
|
+
onDisconnect(): void;
|
|
62
|
+
}
|
|
63
|
+
export interface BleConnection {
|
|
64
|
+
/** The adapter's best knowledge. It may be stale. */
|
|
65
|
+
readonly isConnected: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Largest bytes per write. `null` means the backend accepts a full frame.
|
|
68
|
+
* Left out, the library uses `DEFAULT_MAX_WRITE_SIZE`.
|
|
69
|
+
*/
|
|
70
|
+
readonly maxWriteSize?: number | null;
|
|
71
|
+
/** One write-with-response to the vehicle write characteristic. Resolves on the ATT response. */
|
|
72
|
+
write(data: Uint8Array): Promise<void>;
|
|
73
|
+
read(characteristic: BleCharacteristic): Promise<Uint8Array>;
|
|
74
|
+
disconnect(): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export interface BleScanOptions {
|
|
77
|
+
/** How long the library will wait. A backend with a fixed scan window may ignore it. */
|
|
78
|
+
timeoutMs: number;
|
|
79
|
+
/** Tells an adapter which name it can stop scanning for. */
|
|
80
|
+
matches?: (name: string) => boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface BleTransport {
|
|
83
|
+
/** Active, unfiltered scan. Resolves once a match is found, or when the scan ends with what was seen. */
|
|
84
|
+
scan(options: BleScanOptions): Promise<BleDeviceRef[]>;
|
|
85
|
+
/** Connect, discover, subscribe to notify. Resolves only when notifications are live. */
|
|
86
|
+
connect(device: BleDeviceRef, handlers: BleLinkHandlers): Promise<BleConnection>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thrown by an adapter only when bytes provably never entered the BLE stack,
|
|
90
|
+
* such as an unresolved characteristic, or a link the backend reports down
|
|
91
|
+
* before the call. A router can then safely replay the command elsewhere.
|
|
92
|
+
*/
|
|
93
|
+
export declare class BleNotSubmittedError extends Error {
|
|
94
|
+
readonly name = "BleNotSubmittedError";
|
|
95
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The seam between the library and a Bluetooth backend.
|
|
3
|
+
*
|
|
4
|
+
* The library owns framing, reassembly, signing, sessions, retries, keepalive
|
|
5
|
+
* and confirmation. An adapter owns raw GATT and holds no Tesla logic but the
|
|
6
|
+
* UUIDs, which it imports. Around every adapter the library adds one call at a
|
|
7
|
+
* time per connection, a timeout on every call, frame chunking and link-loss
|
|
8
|
+
* handling, so an adapter needs none of them. This file imports no backend,
|
|
9
|
+
* and neither does anything else in `./bluetooth`.
|
|
10
|
+
*
|
|
11
|
+
* The contract asks only for what the weakest backend, Homey, can do:
|
|
12
|
+
*
|
|
13
|
+
* - `scan` may take a fixed window and ignore `timeoutMs`. Homey's `discover()`
|
|
14
|
+
* takes no filter and scans for about 5 s, whatever it is asked. The library
|
|
15
|
+
* filters what comes back, so an adapter may return all it saw.
|
|
16
|
+
* - `connect` needs no timeout of its own, and Homey has none. The library
|
|
17
|
+
* abandons a hung attempt, and closes the connection if it arrives later.
|
|
18
|
+
* - `write` is always with response, the only mode Homey offers and the only
|
|
19
|
+
* one the vehicle takes.
|
|
20
|
+
* - `maxWriteSize` may be left out when the backend hides its MTU. The library
|
|
21
|
+
* then writes `DEFAULT_MAX_WRITE_SIZE` bytes at a time.
|
|
22
|
+
* - `isConnected` and `onDisconnect` may both lie: Homey's state goes stale on
|
|
23
|
+
* a silent drop, and its event is not guaranteed. Neither has to be right,
|
|
24
|
+
* since a failed or timed-out operation is what the library treats as a dead
|
|
25
|
+
* link. A rejection with `BleNotSubmittedError` is the exception.
|
|
26
|
+
*
|
|
27
|
+
* Adapter rules, each worth a test in the adapter's own suite:
|
|
28
|
+
*
|
|
29
|
+
* 1. Never scan with a service filter, because the vehicle advertises none.
|
|
30
|
+
* Scan actively where the backend has a choice: the name is in the scan
|
|
31
|
+
* response.
|
|
32
|
+
* 2. `connect` resolves only once the notify subscription is confirmed, as a
|
|
33
|
+
* reply sent before that is lost.
|
|
34
|
+
* 3. `connect` closes a half-open link before it rejects. The vehicle has
|
|
35
|
+
* three connection slots.
|
|
36
|
+
* 4. Subscribe again on every connect. Subscriptions end with the link.
|
|
37
|
+
* 5. `write` is always with response.
|
|
38
|
+
* 6. Throw `BleNotSubmittedError` only when the failure is provably before any
|
|
39
|
+
* radio I/O, as a read of a characteristic that was never resolved is on a
|
|
40
|
+
* stack that hides Generic Access. It leaves the link up. All other failures
|
|
41
|
+
* stay plain errors, and the library treats them as a lost link.
|
|
42
|
+
* 7. Pass notifications unchanged and in order. The library copies them, so a
|
|
43
|
+
* reused backend buffer is safe once the handler returns.
|
|
44
|
+
* 8. Do not retry writes. A retry can run a command twice.
|
|
45
|
+
* 9. `disconnect` is safe to call twice, and on a dead link.
|
|
46
|
+
*/
|
|
47
|
+
/** Named characteristics, so an adapter resolves service and UUID its own way. */
|
|
48
|
+
export type BleCharacteristic = "version" | "deviceName" | "appearance";
|
|
49
|
+
/** A found vehicle. `id` is opaque: a Homey peripheral uuid, a MAC, a browser device id. */
|
|
50
|
+
export interface BleDeviceRef {
|
|
51
|
+
id: string;
|
|
52
|
+
name?: string;
|
|
53
|
+
rssi?: number;
|
|
54
|
+
/** `false` means the vehicle already holds its maximum BLE connections. */
|
|
55
|
+
connectable?: boolean;
|
|
56
|
+
}
|
|
57
|
+
export interface BleLinkHandlers {
|
|
58
|
+
/** One notification, unchanged and in arrival order. */
|
|
59
|
+
onNotify(chunk: Uint8Array): void;
|
|
60
|
+
/** Best effort, and a faster signal only. The library also treats a failed operation as a lost link. */
|
|
61
|
+
onDisconnect(): void;
|
|
62
|
+
}
|
|
63
|
+
export interface BleConnection {
|
|
64
|
+
/** The adapter's best knowledge. It may be stale. */
|
|
65
|
+
readonly isConnected: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Largest bytes per write. `null` means the backend accepts a full frame.
|
|
68
|
+
* Left out, the library uses `DEFAULT_MAX_WRITE_SIZE`.
|
|
69
|
+
*/
|
|
70
|
+
readonly maxWriteSize?: number | null;
|
|
71
|
+
/** One write-with-response to the vehicle write characteristic. Resolves on the ATT response. */
|
|
72
|
+
write(data: Uint8Array): Promise<void>;
|
|
73
|
+
read(characteristic: BleCharacteristic): Promise<Uint8Array>;
|
|
74
|
+
disconnect(): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
export interface BleScanOptions {
|
|
77
|
+
/** How long the library will wait. A backend with a fixed scan window may ignore it. */
|
|
78
|
+
timeoutMs: number;
|
|
79
|
+
/** Tells an adapter which name it can stop scanning for. */
|
|
80
|
+
matches?: (name: string) => boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface BleTransport {
|
|
83
|
+
/** Active, unfiltered scan. Resolves once a match is found, or when the scan ends with what was seen. */
|
|
84
|
+
scan(options: BleScanOptions): Promise<BleDeviceRef[]>;
|
|
85
|
+
/** Connect, discover, subscribe to notify. Resolves only when notifications are live. */
|
|
86
|
+
connect(device: BleDeviceRef, handlers: BleLinkHandlers): Promise<BleConnection>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thrown by an adapter only when bytes provably never entered the BLE stack,
|
|
90
|
+
* such as an unresolved characteristic, or a link the backend reports down
|
|
91
|
+
* before the call. A router can then safely replay the command elsewhere.
|
|
92
|
+
*/
|
|
93
|
+
export declare class BleNotSubmittedError extends Error {
|
|
94
|
+
readonly name = "BleNotSubmittedError";
|
|
95
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/bluetooth/transport.ts
|
|
2
|
+
/**
|
|
3
|
+
* Thrown by an adapter only when bytes provably never entered the BLE stack,
|
|
4
|
+
* such as an unresolved characteristic, or a link the backend reports down
|
|
5
|
+
* before the call. A router can then safely replay the command elsewhere.
|
|
6
|
+
*/
|
|
7
|
+
var BleNotSubmittedError = class extends Error {
|
|
8
|
+
name = "BleNotSubmittedError";
|
|
9
|
+
};
|
|
10
|
+
export { BleNotSubmittedError };
|