tesla-fleet-api 0.3.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.
Files changed (342) hide show
  1. package/README.md +284 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +32 -0
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/bluetooth/adapters/shared.cjs +38 -0
  5. package/dist/bluetooth/adapters/shared.mjs +36 -0
  6. package/dist/bluetooth/async.cjs +121 -0
  7. package/dist/bluetooth/async.d.cts +10 -0
  8. package/dist/bluetooth/async.d.mts +10 -0
  9. package/dist/bluetooth/async.mjs +116 -0
  10. package/dist/bluetooth/broadcast.cjs +153 -0
  11. package/dist/bluetooth/broadcast.d.cts +59 -0
  12. package/dist/bluetooth/broadcast.d.mts +59 -0
  13. package/dist/bluetooth/broadcast.mjs +151 -0
  14. package/dist/bluetooth/constants.cjs +99 -0
  15. package/dist/bluetooth/constants.d.cts +70 -0
  16. package/dist/bluetooth/constants.d.mts +70 -0
  17. package/dist/bluetooth/constants.mjs +72 -0
  18. package/dist/bluetooth/esphome.cjs +292 -0
  19. package/dist/bluetooth/esphome.d.cts +83 -0
  20. package/dist/bluetooth/esphome.d.mts +83 -0
  21. package/dist/bluetooth/esphome.mjs +291 -0
  22. package/dist/bluetooth/framing.cjs +125 -0
  23. package/dist/bluetooth/framing.d.cts +45 -0
  24. package/dist/bluetooth/framing.d.mts +45 -0
  25. package/dist/bluetooth/framing.mjs +122 -0
  26. package/dist/bluetooth/homey.cjs +155 -0
  27. package/dist/bluetooth/homey.d.cts +75 -0
  28. package/dist/bluetooth/homey.d.mts +75 -0
  29. package/dist/bluetooth/homey.mjs +150 -0
  30. package/dist/bluetooth/names.cjs +24 -0
  31. package/dist/bluetooth/names.d.cts +10 -0
  32. package/dist/bluetooth/names.d.mts +10 -0
  33. package/dist/bluetooth/names.mjs +20 -0
  34. package/dist/bluetooth/noble.cjs +178 -0
  35. package/dist/bluetooth/noble.d.cts +58 -0
  36. package/dist/bluetooth/noble.d.mts +58 -0
  37. package/dist/bluetooth/noble.mjs +177 -0
  38. package/dist/bluetooth/streamglue.cjs +157 -0
  39. package/dist/bluetooth/streamglue.d.cts +37 -0
  40. package/dist/bluetooth/streamglue.d.mts +37 -0
  41. package/dist/bluetooth/streamglue.mjs +154 -0
  42. package/dist/bluetooth/teslabluetooth.cjs +67 -0
  43. package/dist/bluetooth/teslabluetooth.d.cts +41 -0
  44. package/dist/bluetooth/teslabluetooth.d.mts +41 -0
  45. package/dist/bluetooth/teslabluetooth.mjs +66 -0
  46. package/dist/bluetooth/transport.cjs +10 -0
  47. package/dist/bluetooth/transport.d.cts +95 -0
  48. package/dist/bluetooth/transport.d.mts +95 -0
  49. package/dist/bluetooth/transport.mjs +10 -0
  50. package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
  51. package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
  52. package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
  53. package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
  54. package/dist/bluetooth/verify.cjs +93 -0
  55. package/dist/bluetooth/verify.d.cts +35 -0
  56. package/dist/bluetooth/verify.d.mts +35 -0
  57. package/dist/bluetooth/verify.mjs +91 -0
  58. package/dist/bluetooth.cjs +53 -0
  59. package/dist/bluetooth.d.cts +10 -0
  60. package/dist/bluetooth.d.mts +10 -0
  61. package/dist/bluetooth.mjs +9 -0
  62. package/dist/charging.cjs +53 -0
  63. package/dist/charging.d.cts +31 -0
  64. package/dist/charging.d.mts +30 -0
  65. package/dist/charging.mjs +53 -0
  66. package/dist/commands.cjs +1797 -0
  67. package/dist/commands.d.cts +881 -0
  68. package/dist/commands.d.mts +881 -0
  69. package/dist/commands.mjs +1791 -0
  70. package/dist/const.cjs +322 -0
  71. package/dist/const.d.cts +308 -0
  72. package/dist/const.d.mts +308 -0
  73. package/dist/const.mjs +282 -0
  74. package/dist/energy.cjs +142 -0
  75. package/dist/energy.d.cts +101 -0
  76. package/dist/energy.d.mts +100 -0
  77. package/dist/energy.mjs +142 -0
  78. package/dist/energyisland.cjs +38 -0
  79. package/dist/energyisland.mjs +37 -0
  80. package/dist/energyparsers.cjs +118 -0
  81. package/dist/energyparsers.d.cts +39 -0
  82. package/dist/energyparsers.d.mts +39 -0
  83. package/dist/energyparsers.mjs +116 -0
  84. package/dist/energyspecific.cjs +457 -0
  85. package/dist/energyspecific.d.cts +258 -0
  86. package/dist/energyspecific.d.mts +258 -0
  87. package/dist/energyspecific.mjs +451 -0
  88. package/dist/exceptions.cjs +1488 -0
  89. package/dist/exceptions.d.cts +1143 -0
  90. package/dist/exceptions.d.mts +1143 -0
  91. package/dist/exceptions.mjs +1325 -0
  92. package/dist/funnel.cjs +297 -0
  93. package/dist/funnel.d.cts +118 -0
  94. package/dist/funnel.d.mts +118 -0
  95. package/dist/funnel.mjs +292 -0
  96. package/dist/index.cjs +287 -0
  97. package/dist/index.d.cts +28 -0
  98. package/dist/index.d.mts +28 -0
  99. package/dist/index.mjs +27 -0
  100. package/dist/log.cjs +75 -0
  101. package/dist/log.d.cts +4 -0
  102. package/dist/log.d.mts +4 -0
  103. package/dist/log.mjs +71 -0
  104. package/dist/oauth.cjs +143 -0
  105. package/dist/oauth.d.cts +69 -0
  106. package/dist/oauth.d.mts +69 -0
  107. package/dist/oauth.mjs +143 -0
  108. package/dist/partner.cjs +32 -0
  109. package/dist/partner.d.cts +24 -0
  110. package/dist/partner.d.mts +23 -0
  111. package/dist/partner.mjs +32 -0
  112. package/dist/powerwall/client.cjs +818 -0
  113. package/dist/powerwall/client.d.cts +297 -0
  114. package/dist/powerwall/client.d.mts +297 -0
  115. package/dist/powerwall/client.mjs +807 -0
  116. package/dist/powerwall/components.cjs +188 -0
  117. package/dist/powerwall/components.d.cts +45 -0
  118. package/dist/powerwall/components.d.mts +45 -0
  119. package/dist/powerwall/components.mjs +182 -0
  120. package/dist/powerwall/energysite.cjs +216 -0
  121. package/dist/powerwall/energysite.d.cts +104 -0
  122. package/dist/powerwall/energysite.d.mts +104 -0
  123. package/dist/powerwall/energysite.mjs +214 -0
  124. package/dist/powerwall/exceptions.cjs +59 -0
  125. package/dist/powerwall/exceptions.d.cts +38 -0
  126. package/dist/powerwall/exceptions.d.mts +38 -0
  127. package/dist/powerwall/exceptions.mjs +51 -0
  128. package/dist/powerwall/gateway.cjs +102 -0
  129. package/dist/powerwall/gateway.d.cts +57 -0
  130. package/dist/powerwall/gateway.d.mts +57 -0
  131. package/dist/powerwall/gateway.mjs +101 -0
  132. package/dist/powerwall/merge.cjs +163 -0
  133. package/dist/powerwall/merge.d.cts +63 -0
  134. package/dist/powerwall/merge.d.mts +63 -0
  135. package/dist/powerwall/merge.mjs +159 -0
  136. package/dist/powerwall/models.d.cts +95 -0
  137. package/dist/powerwall/models.d.mts +95 -0
  138. package/dist/powerwall/queries.cjs +17 -0
  139. package/dist/powerwall/queries.mjs +10 -0
  140. package/dist/powerwall/transport.cjs +232 -0
  141. package/dist/powerwall/transport.mjs +227 -0
  142. package/dist/powerwall.cjs +47 -0
  143. package/dist/powerwall.d.cts +9 -0
  144. package/dist/powerwall.d.mts +9 -0
  145. package/dist/powerwall.mjs +8 -0
  146. package/dist/router/base.cjs +203 -0
  147. package/dist/router/base.d.cts +112 -0
  148. package/dist/router/base.d.mts +112 -0
  149. package/dist/router/base.mjs +200 -0
  150. package/dist/router/energysite.cjs +65 -0
  151. package/dist/router/energysite.d.cts +47 -0
  152. package/dist/router/energysite.d.mts +47 -0
  153. package/dist/router/energysite.mjs +60 -0
  154. package/dist/router.cjs +13 -0
  155. package/dist/router.d.cts +3 -0
  156. package/dist/router.d.mts +3 -0
  157. package/dist/router.mjs +3 -0
  158. package/dist/signing/crypto.cjs +59 -0
  159. package/dist/signing/crypto.mjs +52 -0
  160. package/dist/signing/energy.cjs +123 -0
  161. package/dist/signing/energy.d.cts +45 -0
  162. package/dist/signing/energy.d.mts +45 -0
  163. package/dist/signing/energy.mjs +120 -0
  164. package/dist/signing/jws.cjs +229 -0
  165. package/dist/signing/jws.d.cts +45 -0
  166. package/dist/signing/jws.d.mts +45 -0
  167. package/dist/signing/jws.mjs +211 -0
  168. package/dist/signing/keys.cjs +191 -0
  169. package/dist/signing/keys.d.cts +21 -0
  170. package/dist/signing/keys.d.mts +21 -0
  171. package/dist/signing/keys.mjs +182 -0
  172. package/dist/signing/session.cjs +186 -0
  173. package/dist/signing/session.d.cts +106 -0
  174. package/dist/signing/session.d.mts +106 -0
  175. package/dist/signing/session.mjs +184 -0
  176. package/dist/tariff.cjs +319 -0
  177. package/dist/tariff.d.cts +40 -0
  178. package/dist/tariff.d.mts +40 -0
  179. package/dist/tariff.mjs +317 -0
  180. package/dist/teslafleetapi.cjs +326 -0
  181. package/dist/teslafleetapi.d.cts +160 -0
  182. package/dist/teslafleetapi.d.mts +160 -0
  183. package/dist/teslafleetapi.mjs +326 -0
  184. package/dist/teslemetry.cjs +183 -0
  185. package/dist/teslemetry.d.cts +113 -0
  186. package/dist/teslemetry.d.mts +113 -0
  187. package/dist/teslemetry.mjs +181 -0
  188. package/dist/teslemetryenergy.cjs +287 -0
  189. package/dist/teslemetryenergy.d.cts +154 -0
  190. package/dist/teslemetryenergy.d.mts +154 -0
  191. package/dist/teslemetryenergy.mjs +282 -0
  192. package/dist/teslemetryvehicle.cjs +407 -0
  193. package/dist/teslemetryvehicle.d.cts +238 -0
  194. package/dist/teslemetryvehicle.d.mts +238 -0
  195. package/dist/teslemetryvehicle.mjs +405 -0
  196. package/dist/tessie.cjs +71 -0
  197. package/dist/tessie.d.cts +69 -0
  198. package/dist/tessie.d.mts +69 -0
  199. package/dist/tessie.mjs +71 -0
  200. package/dist/tessievehicle.cjs +514 -0
  201. package/dist/tessievehicle.d.cts +333 -0
  202. package/dist/tessievehicle.d.mts +333 -0
  203. package/dist/tessievehicle.mjs +513 -0
  204. package/dist/types/commands.d.cts +28 -0
  205. package/dist/types/commands.d.mts +28 -0
  206. package/dist/types/live_status.d.cts +30 -0
  207. package/dist/types/live_status.d.mts +30 -0
  208. package/dist/types/products.d.cts +58 -0
  209. package/dist/types/products.d.mts +58 -0
  210. package/dist/types/responses.d.cts +13 -0
  211. package/dist/types/responses.d.mts +13 -0
  212. package/dist/types/site_info.d.cts +154 -0
  213. package/dist/types/site_info.d.mts +154 -0
  214. package/dist/types/vehicle.d.cts +33 -0
  215. package/dist/types/vehicle.d.mts +33 -0
  216. package/dist/types/vehicle_data.d.cts +278 -0
  217. package/dist/types/vehicle_data.d.mts +278 -0
  218. package/dist/types.cjs +0 -0
  219. package/dist/types.d.cts +9 -0
  220. package/dist/types.d.mts +9 -0
  221. package/dist/types.mjs +1 -0
  222. package/dist/user.cjs +28 -0
  223. package/dist/user.d.cts +17 -0
  224. package/dist/user.d.mts +16 -0
  225. package/dist/user.mjs +28 -0
  226. package/dist/util.cjs +41 -0
  227. package/dist/util.d.cts +18 -0
  228. package/dist/util.d.mts +18 -0
  229. package/dist/util.mjs +40 -0
  230. package/dist/vehicle.cjs +847 -0
  231. package/dist/vehicle.d.cts +566 -0
  232. package/dist/vehicle.d.mts +565 -0
  233. package/dist/vehicle.mjs +847 -0
  234. package/dist/vehiclesigned.cjs +35 -0
  235. package/dist/vehiclesigned.d.cts +23 -0
  236. package/dist/vehiclesigned.d.mts +22 -0
  237. package/dist/vehiclesigned.mjs +35 -0
  238. package/dist/vehiclespecific.cjs +715 -0
  239. package/dist/vehiclespecific.d.cts +497 -0
  240. package/dist/vehiclespecific.d.mts +496 -0
  241. package/dist/vehiclespecific.mjs +715 -0
  242. package/package.json +178 -38
  243. package/dist/charging.d.ts +0 -30
  244. package/dist/charging.js +0 -51
  245. package/dist/charging.js.map +0 -1
  246. package/dist/commands.d.ts +0 -352
  247. package/dist/commands.js +0 -875
  248. package/dist/commands.js.map +0 -1
  249. package/dist/energy.d.ts +0 -94
  250. package/dist/energy.js +0 -125
  251. package/dist/energy.js.map +0 -1
  252. package/dist/energyspecific.d.ts +0 -77
  253. package/dist/energyspecific.js +0 -97
  254. package/dist/energyspecific.js.map +0 -1
  255. package/dist/helpers.d.ts +0 -2
  256. package/dist/helpers.js +0 -10
  257. package/dist/helpers.js.map +0 -1
  258. package/dist/index.d.ts +0 -15
  259. package/dist/index.js +0 -15
  260. package/dist/index.js.map +0 -1
  261. package/dist/partner.d.ts +0 -23
  262. package/dist/partner.js +0 -31
  263. package/dist/partner.js.map +0 -1
  264. package/dist/signing/crypto.d.ts +0 -10
  265. package/dist/signing/crypto.js +0 -26
  266. package/dist/signing/crypto.js.map +0 -1
  267. package/dist/signing/errors.d.ts +0 -29
  268. package/dist/signing/errors.js +0 -40
  269. package/dist/signing/errors.js.map +0 -1
  270. package/dist/signing/session.d.ts +0 -69
  271. package/dist/signing/session.js +0 -102
  272. package/dist/signing/session.js.map +0 -1
  273. package/dist/tariff.d.ts +0 -29
  274. package/dist/tariff.js +0 -302
  275. package/dist/tariff.js.map +0 -1
  276. package/dist/teslafleetapi.d.ts +0 -58
  277. package/dist/teslafleetapi.js +0 -120
  278. package/dist/teslafleetapi.js.map +0 -1
  279. package/dist/teslemetry.d.ts +0 -29
  280. package/dist/teslemetry.js +0 -39
  281. package/dist/teslemetry.js.map +0 -1
  282. package/dist/tessie.d.ts +0 -23
  283. package/dist/tessie.js +0 -28
  284. package/dist/tessie.js.map +0 -1
  285. package/dist/types/commands.d.ts +0 -44
  286. package/dist/types/commands.js +0 -11
  287. package/dist/types/commands.js.map +0 -1
  288. package/dist/types/live_status.d.ts +0 -26
  289. package/dist/types/live_status.js +0 -2
  290. package/dist/types/live_status.js.map +0 -1
  291. package/dist/types/products.d.ts +0 -54
  292. package/dist/types/products.js +0 -2
  293. package/dist/types/products.js.map +0 -1
  294. package/dist/types/responses.d.ts +0 -5
  295. package/dist/types/responses.js +0 -2
  296. package/dist/types/responses.js.map +0 -1
  297. package/dist/types/site_info.d.ts +0 -150
  298. package/dist/types/site_info.js +0 -2
  299. package/dist/types/site_info.js.map +0 -1
  300. package/dist/types/vehicle.d.ts +0 -23
  301. package/dist/types/vehicle.js +0 -10
  302. package/dist/types/vehicle.js.map +0 -1
  303. package/dist/types/vehicle_data.d.ts +0 -274
  304. package/dist/types/vehicle_data.js +0 -2
  305. package/dist/types/vehicle_data.js.map +0 -1
  306. package/dist/user.d.ts +0 -16
  307. package/dist/user.js +0 -26
  308. package/dist/user.js.map +0 -1
  309. package/dist/vehicle.d.ts +0 -548
  310. package/dist/vehicle.js +0 -774
  311. package/dist/vehicle.js.map +0 -1
  312. package/dist/vehiclesigned.d.ts +0 -13
  313. package/dist/vehiclesigned.js +0 -19
  314. package/dist/vehiclesigned.js.map +0 -1
  315. package/dist/vehiclespecific.d.ts +0 -432
  316. package/dist/vehiclespecific.js +0 -601
  317. package/dist/vehiclespecific.js.map +0 -1
  318. package/src/charging.ts +0 -66
  319. package/src/commands.ts +0 -1134
  320. package/src/energy.ts +0 -217
  321. package/src/energyspecific.ts +0 -116
  322. package/src/helpers.ts +0 -11
  323. package/src/index.ts +0 -21
  324. package/src/partner.ts +0 -37
  325. package/src/signing/crypto.ts +0 -29
  326. package/src/signing/errors.ts +0 -50
  327. package/src/signing/session.ts +0 -132
  328. package/src/tariff.ts +0 -375
  329. package/src/teslafleetapi.ts +0 -182
  330. package/src/teslemetry.ts +0 -54
  331. package/src/tessie.ts +0 -42
  332. package/src/types/commands.ts +0 -62
  333. package/src/types/live_status.ts +0 -27
  334. package/src/types/products.ts +0 -61
  335. package/src/types/responses.ts +0 -1
  336. package/src/types/site_info.ts +0 -159
  337. package/src/types/vehicle.ts +0 -33
  338. package/src/types/vehicle_data.ts +0 -286
  339. package/src/user.ts +0 -35
  340. package/src/vehicle.ts +0 -886
  341. package/src/vehiclesigned.ts +0 -20
  342. package/src/vehiclespecific.ts +0 -818
@@ -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
+ }
@@ -0,0 +1,177 @@
1
+ import { AsyncLock, withTimeout } from "./async.mjs";
2
+ import { APPEARANCE_UUID, DEFAULT_CONNECT_TIMEOUT_MS, DEFAULT_SCAN_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/noble.ts
6
+ function find(characteristics, uuid) {
7
+ return characteristics.find((characteristic) => compactUuid(characteristic.uuid) === compactUuid(uuid));
8
+ }
9
+ async function loadNoble(bindings) {
10
+ const specifier = "@stoprocent/noble/lib/resolve-bindings.js";
11
+ let module;
12
+ try {
13
+ module = await import(specifier);
14
+ } catch (cause) {
15
+ throw new Error("NobleBleTransport needs @stoprocent/noble, or a `noble` option.", { cause });
16
+ }
17
+ return module.default(bindings);
18
+ }
19
+ var NobleConnection = class {
20
+ peripheral;
21
+ handlers;
22
+ claims;
23
+ lost = false;
24
+ closed = false;
25
+ closing;
26
+ genericAccess;
27
+ characteristics = [];
28
+ onData = (data, isNotification) => {
29
+ if (isNotification !== false) this.handlers.onNotify(data);
30
+ };
31
+ onDisconnect = () => {
32
+ this.lost = true;
33
+ if (!this.closed) this.handlers.onDisconnect();
34
+ };
35
+ constructor(peripheral, handlers, claims) {
36
+ this.peripheral = peripheral;
37
+ this.handlers = handlers;
38
+ this.claims = claims;
39
+ }
40
+ get isConnected() {
41
+ return !this.lost && !this.closed && this.peripheral.state === "connected";
42
+ }
43
+ /** Read each time: noble can report a larger MTU after the link is up. */
44
+ get maxWriteSize() {
45
+ const { mtu } = this.peripheral;
46
+ return mtu && mtu > 3 ? mtu - 3 : 20;
47
+ }
48
+ /** Resolve the characteristics and turn notifications on. */
49
+ async subscribe() {
50
+ const { peripheral } = this;
51
+ const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync([compactUuid(SERVICE_UUID)], [
52
+ VEHICLE_WRITE_UUID,
53
+ VEHICLE_NOTIFY_UUID,
54
+ VERSION_UUID
55
+ ].map(compactUuid));
56
+ const notify = find(characteristics, VEHICLE_NOTIFY_UUID);
57
+ if (!notify || !find(characteristics, "00000212-b2d1-43f0-9b88-960cebf8b91e")) throw new Error("The vehicle's write or notify characteristic was not found.");
58
+ this.assertCurrent();
59
+ this.characteristics = characteristics;
60
+ notify.on("data", this.onData);
61
+ await notify.subscribeAsync();
62
+ }
63
+ /** A superseded attempt stops here, before it changes a link another attempt owns. */
64
+ assertCurrent() {
65
+ if (this.claims.superseded(this.peripheral.id, this)) throw new Error("A newer connect attempt took over this link.");
66
+ }
67
+ async write(data) {
68
+ const characteristic = find(this.characteristics, VEHICLE_WRITE_UUID);
69
+ if (!characteristic) throw new BleNotSubmittedError("The vehicle write characteristic was not found.");
70
+ if (!this.isConnected) throw new BleNotSubmittedError("The link is down.");
71
+ await characteristic.writeAsync(Buffer.from(data), false);
72
+ }
73
+ async read(characteristic) {
74
+ const target = characteristic === "version" ? find(this.characteristics, VERSION_UUID) : find(await this.discoverGenericAccess(), characteristic === "deviceName" ? NAME_UUID : APPEARANCE_UUID);
75
+ if (!target) throw new BleNotSubmittedError(`The ${characteristic} characteristic was not found.`);
76
+ return target.readAsync();
77
+ }
78
+ /**
79
+ * BlueZ and CoreBluetooth can hide this service. A failed lookup leaves a
80
+ * characteristic unresolved on a healthy link, so it must not read as a lost one.
81
+ */
82
+ discoverGenericAccess() {
83
+ this.genericAccess ??= this.peripheral.discoverSomeServicesAndCharacteristicsAsync([compactUuid(GENERIC_ACCESS_UUID)], [compactUuid(NAME_UUID), compactUuid(APPEARANCE_UUID)]).then(({ characteristics }) => characteristics).catch((cause) => {
84
+ this.genericAccess = void 0;
85
+ throw new BleNotSubmittedError("Generic Access could not be resolved.", { cause });
86
+ });
87
+ return this.genericAccess;
88
+ }
89
+ disconnect() {
90
+ this.closing ??= (async () => {
91
+ this.closed = true;
92
+ const { peripheral, claims } = this;
93
+ peripheral.removeListener("disconnect", this.onDisconnect);
94
+ find(this.characteristics, VEHICLE_NOTIFY_UUID)?.removeListener("data", this.onData);
95
+ const superseded = claims.superseded(peripheral.id, this);
96
+ claims.release(peripheral.id, this);
97
+ if (!superseded && peripheral.state !== "disconnected") await quietly(() => peripheral.disconnectAsync());
98
+ })();
99
+ return this.closing;
100
+ }
101
+ };
102
+ var NobleBleTransport = class {
103
+ options;
104
+ backend;
105
+ scans = new AsyncLock();
106
+ peripherals = /* @__PURE__ */ new Map();
107
+ claims = new LinkClaims();
108
+ constructor(options = {}) {
109
+ this.options = options;
110
+ }
111
+ noble() {
112
+ if (this.options.noble) return Promise.resolve(this.options.noble);
113
+ const fallback = process.platform === "linux" ? "dbus" : "default";
114
+ const loading = this.backend ??= loadNoble(this.options.bindings ?? fallback);
115
+ loading.catch(() => {
116
+ this.backend = void 0;
117
+ });
118
+ return loading;
119
+ }
120
+ async scan({ timeoutMs, matches }) {
121
+ const noble = await this.noble();
122
+ return this.listen(noble, timeoutMs, (device) => device.name !== void 0 && matches?.(device.name) === true);
123
+ }
124
+ /** Scan with no service filter until `done` accepts a device or the window ends. */
125
+ listen(noble, timeoutMs, done) {
126
+ return this.scans.run(async () => {
127
+ await noble.waitForPoweredOnAsync(timeoutMs);
128
+ const seen = /* @__PURE__ */ new Map();
129
+ let finish;
130
+ const finished = new Promise((resolve) => {
131
+ finish = resolve;
132
+ });
133
+ const onDiscover = (peripheral) => {
134
+ this.peripherals.set(peripheral.id, peripheral);
135
+ const device = {
136
+ id: peripheral.id,
137
+ name: peripheral.advertisement.localName ?? seen.get(peripheral.id)?.name,
138
+ rssi: peripheral.rssi,
139
+ connectable: peripheral.connectable
140
+ };
141
+ seen.set(peripheral.id, device);
142
+ if (done(device)) finish();
143
+ };
144
+ noble.on("discover", onDiscover);
145
+ const timer = setTimeout(finish, Math.max(0, timeoutMs - 250));
146
+ try {
147
+ await noble.startScanningAsync([], true);
148
+ await finished;
149
+ } finally {
150
+ clearTimeout(timer);
151
+ noble.removeListener("discover", onDiscover);
152
+ await quietly(() => noble.stopScanningAsync());
153
+ }
154
+ return [...seen.values()];
155
+ });
156
+ }
157
+ async connect(device, handlers) {
158
+ const noble = await this.noble();
159
+ if (!this.peripherals.has(device.id)) await this.listen(noble, DEFAULT_SCAN_TIMEOUT_MS, ({ id }) => id === device.id);
160
+ const peripheral = this.peripherals.get(device.id);
161
+ if (!peripheral) throw new Error("The device was not seen in a scan.");
162
+ await quietly(() => noble.stopScanningAsync());
163
+ const connection = new NobleConnection(peripheral, handlers, this.claims);
164
+ this.claims.claim(device.id, connection);
165
+ peripheral.once("disconnect", connection.onDisconnect);
166
+ try {
167
+ await peripheral.connectAsync();
168
+ connection.assertCurrent();
169
+ await withTimeout(() => connection.subscribe(), DEFAULT_CONNECT_TIMEOUT_MS);
170
+ } catch (error) {
171
+ await connection.disconnect();
172
+ throw error;
173
+ }
174
+ return connection;
175
+ }
176
+ };
177
+ export { NobleBleTransport };
@@ -0,0 +1,157 @@
1
+ const require_funnel = require("../funnel.cjs");
2
+ let _teslemetry_tesla_protocol_command_vcsec = require("@teslemetry/tesla-protocol/command/vcsec");
3
+ // src/bluetooth/streamglue.ts
4
+ const GEAR_STATES = /* @__PURE__ */ new Map([
5
+ [_teslemetry_tesla_protocol_command_vcsec.GearE.GEAR_PARK, "ShiftStateP"],
6
+ [_teslemetry_tesla_protocol_command_vcsec.GearE.GEAR_DRIVE, "ShiftStateD"],
7
+ [_teslemetry_tesla_protocol_command_vcsec.GearE.GEAR_REVERSE, "ShiftStateR"],
8
+ [_teslemetry_tesla_protocol_command_vcsec.GearE.GEAR_NEUTRAL, "ShiftStateN"]
9
+ ]);
10
+ const TONNEAU_POSITION_STATES = /* @__PURE__ */ new Map([
11
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_CLOSED, "TonneauPositionStateClosed"],
12
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_OPEN, "TonneauPositionStateFullyOpen"],
13
+ [_teslemetry_tesla_protocol_command_vcsec.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 = (0, _teslemetry_tesla_protocol_command_vcsec.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 = require_funnel.LOCK_STATES.get(state);
46
+ if (locked !== void 0) this.#sink.ingest({ Locked: locked }, {
47
+ source: "bluetooth",
48
+ raw: (0, _teslemetry_tesla_protocol_command_vcsec.vehicleLockStateEToJSON)(state)
49
+ });
50
+ }
51
+ #onChargePort(state) {
52
+ const open = require_funnel.CLOSURE_STATES.get(state);
53
+ if (open !== void 0) this.#sink.ingest({ ChargePortDoorOpen: open }, {
54
+ source: "bluetooth",
55
+ raw: (0, _teslemetry_tesla_protocol_command_vcsec.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 = require_funnel.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: (0, _teslemetry_tesla_protocol_command_vcsec.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: (0, _teslemetry_tesla_protocol_command_vcsec.closureStateEToJSON)(state)
94
+ });
95
+ }
96
+ };
97
+ /** The map and listener behind each field a broadcast carries. */
98
+ const BROADCAST_FIELDS = /* @__PURE__ */ new Map([
99
+ [require_funnel.FieldPath.LOCKED, {
100
+ states: require_funnel.LOCK_STATES,
101
+ listen: (vehicle, callback) => vehicle.listen_vehicle_lock_state(callback)
102
+ }],
103
+ [require_funnel.FieldPath.CHARGE_PORT_DOOR_OPEN, {
104
+ states: require_funnel.CLOSURE_STATES,
105
+ listen: (vehicle, callback) => vehicle.listen_charge_port(callback)
106
+ }],
107
+ [require_funnel.FieldPath.DOOR_STATE_TRUNK_FRONT, {
108
+ states: require_funnel.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
+ exports.BleBroadcastPublisher = BleBroadcastPublisher;
155
+ exports.BleBroadcastStreamGlue = BleBroadcastStreamGlue;
156
+ exports.GEAR_STATES = GEAR_STATES;
157
+ exports.TONNEAU_POSITION_STATES = TONNEAU_POSITION_STATES;
@@ -0,0 +1,37 @@
1
+ import { Clock, FieldPath, ObservationSink, Publisher, Unsubscribe } from "../funnel.cjs";
2
+ import { BroadcastListeners } from "./broadcast.cjs";
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
+ }