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.
Files changed (219) hide show
  1. package/README.md +284 -1
  2. package/dist/_virtual/_rolldown/runtime.cjs +10 -0
  3. package/dist/bluetooth/adapters/shared.cjs +38 -0
  4. package/dist/bluetooth/adapters/shared.mjs +36 -0
  5. package/dist/bluetooth/async.cjs +121 -0
  6. package/dist/bluetooth/async.d.cts +10 -0
  7. package/dist/bluetooth/async.d.mts +10 -0
  8. package/dist/bluetooth/async.mjs +116 -0
  9. package/dist/bluetooth/broadcast.cjs +153 -0
  10. package/dist/bluetooth/broadcast.d.cts +59 -0
  11. package/dist/bluetooth/broadcast.d.mts +59 -0
  12. package/dist/bluetooth/broadcast.mjs +151 -0
  13. package/dist/bluetooth/constants.cjs +99 -0
  14. package/dist/bluetooth/constants.d.cts +70 -0
  15. package/dist/bluetooth/constants.d.mts +70 -0
  16. package/dist/bluetooth/constants.mjs +72 -0
  17. package/dist/bluetooth/esphome.cjs +292 -0
  18. package/dist/bluetooth/esphome.d.cts +83 -0
  19. package/dist/bluetooth/esphome.d.mts +83 -0
  20. package/dist/bluetooth/esphome.mjs +291 -0
  21. package/dist/bluetooth/framing.cjs +125 -0
  22. package/dist/bluetooth/framing.d.cts +45 -0
  23. package/dist/bluetooth/framing.d.mts +45 -0
  24. package/dist/bluetooth/framing.mjs +122 -0
  25. package/dist/bluetooth/homey.cjs +155 -0
  26. package/dist/bluetooth/homey.d.cts +75 -0
  27. package/dist/bluetooth/homey.d.mts +75 -0
  28. package/dist/bluetooth/homey.mjs +150 -0
  29. package/dist/bluetooth/names.cjs +24 -0
  30. package/dist/bluetooth/names.d.cts +10 -0
  31. package/dist/bluetooth/names.d.mts +10 -0
  32. package/dist/bluetooth/names.mjs +20 -0
  33. package/dist/bluetooth/noble.cjs +178 -0
  34. package/dist/bluetooth/noble.d.cts +58 -0
  35. package/dist/bluetooth/noble.d.mts +58 -0
  36. package/dist/bluetooth/noble.mjs +177 -0
  37. package/dist/bluetooth/streamglue.cjs +157 -0
  38. package/dist/bluetooth/streamglue.d.cts +37 -0
  39. package/dist/bluetooth/streamglue.d.mts +37 -0
  40. package/dist/bluetooth/streamglue.mjs +154 -0
  41. package/dist/bluetooth/teslabluetooth.cjs +67 -0
  42. package/dist/bluetooth/teslabluetooth.d.cts +41 -0
  43. package/dist/bluetooth/teslabluetooth.d.mts +41 -0
  44. package/dist/bluetooth/teslabluetooth.mjs +66 -0
  45. package/dist/bluetooth/transport.cjs +10 -0
  46. package/dist/bluetooth/transport.d.cts +95 -0
  47. package/dist/bluetooth/transport.d.mts +95 -0
  48. package/dist/bluetooth/transport.mjs +10 -0
  49. package/dist/bluetooth/vehiclebluetooth.cjs +1272 -0
  50. package/dist/bluetooth/vehiclebluetooth.d.cts +495 -0
  51. package/dist/bluetooth/vehiclebluetooth.d.mts +495 -0
  52. package/dist/bluetooth/vehiclebluetooth.mjs +1270 -0
  53. package/dist/bluetooth/verify.cjs +93 -0
  54. package/dist/bluetooth/verify.d.cts +35 -0
  55. package/dist/bluetooth/verify.d.mts +35 -0
  56. package/dist/bluetooth/verify.mjs +91 -0
  57. package/dist/bluetooth.cjs +53 -0
  58. package/dist/bluetooth.d.cts +10 -0
  59. package/dist/bluetooth.d.mts +10 -0
  60. package/dist/bluetooth.mjs +9 -0
  61. package/dist/commands.cjs +1080 -85
  62. package/dist/commands.d.cts +585 -54
  63. package/dist/commands.d.mts +583 -51
  64. package/dist/commands.mjs +1084 -93
  65. package/dist/const.cjs +22 -0
  66. package/dist/const.d.cts +21 -0
  67. package/dist/const.d.mts +21 -0
  68. package/dist/const.mjs +20 -1
  69. package/dist/energy.cjs +24 -19
  70. package/dist/energy.d.cts +21 -15
  71. package/dist/energy.d.mts +21 -15
  72. package/dist/energy.mjs +24 -19
  73. package/dist/energyisland.cjs +38 -0
  74. package/dist/energyisland.mjs +37 -0
  75. package/dist/energyparsers.cjs +118 -0
  76. package/dist/energyparsers.d.cts +39 -0
  77. package/dist/energyparsers.d.mts +39 -0
  78. package/dist/energyparsers.mjs +116 -0
  79. package/dist/energyspecific.cjs +375 -17
  80. package/dist/energyspecific.d.cts +199 -19
  81. package/dist/energyspecific.d.mts +197 -16
  82. package/dist/energyspecific.mjs +370 -18
  83. package/dist/exceptions.cjs +3 -2
  84. package/dist/exceptions.d.cts +3 -2
  85. package/dist/exceptions.d.mts +3 -2
  86. package/dist/exceptions.mjs +3 -2
  87. package/dist/funnel.cjs +297 -0
  88. package/dist/funnel.d.cts +118 -0
  89. package/dist/funnel.d.mts +118 -0
  90. package/dist/funnel.mjs +292 -0
  91. package/dist/index.cjs +61 -0
  92. package/dist/index.d.cts +22 -8
  93. package/dist/index.d.mts +22 -8
  94. package/dist/index.mjs +19 -6
  95. package/dist/log.cjs +75 -0
  96. package/dist/log.d.cts +4 -0
  97. package/dist/log.d.mts +4 -0
  98. package/dist/log.mjs +71 -0
  99. package/dist/oauth.cjs +143 -0
  100. package/dist/oauth.d.cts +69 -0
  101. package/dist/oauth.d.mts +69 -0
  102. package/dist/oauth.mjs +143 -0
  103. package/dist/powerwall/client.cjs +818 -0
  104. package/dist/powerwall/client.d.cts +297 -0
  105. package/dist/powerwall/client.d.mts +297 -0
  106. package/dist/powerwall/client.mjs +807 -0
  107. package/dist/powerwall/components.cjs +188 -0
  108. package/dist/powerwall/components.d.cts +45 -0
  109. package/dist/powerwall/components.d.mts +45 -0
  110. package/dist/powerwall/components.mjs +182 -0
  111. package/dist/powerwall/energysite.cjs +216 -0
  112. package/dist/powerwall/energysite.d.cts +104 -0
  113. package/dist/powerwall/energysite.d.mts +104 -0
  114. package/dist/powerwall/energysite.mjs +214 -0
  115. package/dist/powerwall/exceptions.cjs +59 -0
  116. package/dist/powerwall/exceptions.d.cts +38 -0
  117. package/dist/powerwall/exceptions.d.mts +38 -0
  118. package/dist/powerwall/exceptions.mjs +51 -0
  119. package/dist/powerwall/gateway.cjs +102 -0
  120. package/dist/powerwall/gateway.d.cts +57 -0
  121. package/dist/powerwall/gateway.d.mts +57 -0
  122. package/dist/powerwall/gateway.mjs +101 -0
  123. package/dist/powerwall/merge.cjs +163 -0
  124. package/dist/powerwall/merge.d.cts +63 -0
  125. package/dist/powerwall/merge.d.mts +63 -0
  126. package/dist/powerwall/merge.mjs +159 -0
  127. package/dist/powerwall/models.d.cts +95 -0
  128. package/dist/powerwall/models.d.mts +95 -0
  129. package/dist/powerwall/queries.cjs +17 -0
  130. package/dist/powerwall/queries.mjs +10 -0
  131. package/dist/powerwall/transport.cjs +232 -0
  132. package/dist/powerwall/transport.mjs +227 -0
  133. package/dist/powerwall.cjs +47 -0
  134. package/dist/powerwall.d.cts +9 -0
  135. package/dist/powerwall.d.mts +9 -0
  136. package/dist/powerwall.mjs +8 -0
  137. package/dist/router/base.cjs +203 -0
  138. package/dist/router/base.d.cts +112 -0
  139. package/dist/router/base.d.mts +112 -0
  140. package/dist/router/base.mjs +200 -0
  141. package/dist/router/energysite.cjs +65 -0
  142. package/dist/router/energysite.d.cts +47 -0
  143. package/dist/router/energysite.d.mts +47 -0
  144. package/dist/router/energysite.mjs +60 -0
  145. package/dist/router.cjs +13 -0
  146. package/dist/router.d.cts +3 -1
  147. package/dist/router.d.mts +3 -1
  148. package/dist/router.mjs +3 -1
  149. package/dist/signing/crypto.cjs +28 -0
  150. package/dist/signing/crypto.mjs +26 -1
  151. package/dist/signing/energy.cjs +123 -0
  152. package/dist/signing/energy.d.cts +45 -0
  153. package/dist/signing/energy.d.mts +45 -0
  154. package/dist/signing/energy.mjs +120 -0
  155. package/dist/signing/jws.cjs +229 -0
  156. package/dist/signing/jws.d.cts +45 -0
  157. package/dist/signing/jws.d.mts +45 -0
  158. package/dist/signing/jws.mjs +211 -0
  159. package/dist/signing/keys.cjs +191 -0
  160. package/dist/signing/keys.d.cts +21 -0
  161. package/dist/signing/keys.d.mts +21 -0
  162. package/dist/signing/keys.mjs +182 -0
  163. package/dist/signing/session.cjs +104 -30
  164. package/dist/signing/session.d.cts +60 -23
  165. package/dist/signing/session.d.mts +60 -23
  166. package/dist/signing/session.mjs +105 -31
  167. package/dist/tariff.cjs +27 -0
  168. package/dist/tariff.d.cts +11 -0
  169. package/dist/tariff.d.mts +11 -0
  170. package/dist/tariff.mjs +27 -1
  171. package/dist/teslafleetapi.cjs +256 -55
  172. package/dist/teslafleetapi.d.cts +110 -14
  173. package/dist/teslafleetapi.d.mts +110 -14
  174. package/dist/teslafleetapi.mjs +258 -57
  175. package/dist/teslemetry.cjs +152 -5
  176. package/dist/teslemetry.d.cts +95 -5
  177. package/dist/teslemetry.d.mts +93 -3
  178. package/dist/teslemetry.mjs +151 -6
  179. package/dist/teslemetryenergy.cjs +287 -0
  180. package/dist/teslemetryenergy.d.cts +154 -0
  181. package/dist/teslemetryenergy.d.mts +154 -0
  182. package/dist/teslemetryenergy.mjs +282 -0
  183. package/dist/teslemetryvehicle.cjs +407 -0
  184. package/dist/teslemetryvehicle.d.cts +238 -0
  185. package/dist/teslemetryvehicle.d.mts +238 -0
  186. package/dist/teslemetryvehicle.mjs +405 -0
  187. package/dist/tessie.cjs +42 -5
  188. package/dist/tessie.d.cts +51 -5
  189. package/dist/tessie.d.mts +49 -3
  190. package/dist/tessie.mjs +42 -5
  191. package/dist/tessievehicle.cjs +514 -0
  192. package/dist/tessievehicle.d.cts +333 -0
  193. package/dist/tessievehicle.d.mts +333 -0
  194. package/dist/tessievehicle.mjs +513 -0
  195. package/dist/types/live_status.d.cts +16 -16
  196. package/dist/types/live_status.d.mts +16 -16
  197. package/dist/types/responses.d.cts +5 -1
  198. package/dist/types/responses.d.mts +5 -1
  199. package/dist/types/vehicle.d.cts +7 -1
  200. package/dist/types/vehicle.d.mts +7 -1
  201. package/dist/types.d.cts +3 -3
  202. package/dist/types.d.mts +3 -3
  203. package/dist/util.cjs +41 -0
  204. package/dist/util.d.cts +18 -0
  205. package/dist/util.d.mts +18 -0
  206. package/dist/util.mjs +40 -0
  207. package/dist/vehicle.cjs +79 -36
  208. package/dist/vehicle.d.cts +42 -26
  209. package/dist/vehicle.d.mts +42 -26
  210. package/dist/vehicle.mjs +80 -37
  211. package/dist/vehiclesigned.cjs +20 -5
  212. package/dist/vehiclesigned.d.cts +14 -5
  213. package/dist/vehiclesigned.d.mts +14 -5
  214. package/dist/vehiclesigned.mjs +20 -5
  215. package/dist/vehiclespecific.cjs +126 -15
  216. package/dist/vehiclespecific.d.cts +82 -18
  217. package/dist/vehiclespecific.d.mts +82 -18
  218. package/dist/vehiclespecific.mjs +126 -15
  219. package/package.json +70 -3
@@ -0,0 +1,297 @@
1
+ const require_log = require("./log.cjs");
2
+ let _teslemetry_tesla_protocol_command_vcsec = require("@teslemetry/tesla-protocol/command/vcsec");
3
+ // src/funnel.ts
4
+ /**
5
+ * A canonical routable field, valued to match its public signal name. A compound
6
+ * signal is split into one path per facet, because a broadcast and a
7
+ * `vehicle_data` result each expose individual leaves.
8
+ */
9
+ const FieldPath = Object.freeze({
10
+ LOCKED: "Locked",
11
+ CHARGE_PORT_DOOR_OPEN: "ChargePortDoorOpen",
12
+ DOOR_STATE_TRUNK_FRONT: "DoorState.TrunkFront"
13
+ });
14
+ function noop() {}
15
+ /**
16
+ * Funnels observations from every attached publisher into one listener set.
17
+ *
18
+ * Many to one: a listener registered for a field receives what any publisher
19
+ * reports for it, in arrival order. There is no source selection, ranking, or
20
+ * health model here, and detaching or silencing a publisher produces no value of
21
+ * its own. The only arbitration, dropping older observations and skipping
22
+ * unchanged values, is hard-coded.
23
+ */
24
+ var ObservationFunnel = class {
25
+ #publishers = [];
26
+ #listeners = /* @__PURE__ */ new Map();
27
+ #demandObservers = [];
28
+ #observed = /* @__PURE__ */ new Map();
29
+ #logger;
30
+ constructor(options = {}) {
31
+ this.#logger = options.logger;
32
+ }
33
+ /** Attach `publisher` and return its detach closure. */
34
+ attach(publisher) {
35
+ const attached = {
36
+ publisher,
37
+ detach: noop
38
+ };
39
+ this.#publishers.push(attached);
40
+ attached.detach = publisher.attach(this);
41
+ const active = this.#activePaths(publisher);
42
+ if (active.size > 0) publisher.request(active);
43
+ return () => {
44
+ const index = this.#publishers.indexOf(attached);
45
+ if (index === -1) return;
46
+ this.#publishers.splice(index, 1);
47
+ const stillActive = this.#activePaths(publisher);
48
+ if (stillActive.size > 0) publisher.release(stillActive);
49
+ attached.detach();
50
+ };
51
+ }
52
+ #activePaths(publisher) {
53
+ return new Set([...publisher.paths].filter((path) => this.#listeners.get(path)?.length));
54
+ }
55
+ /** Accept one observation from any publisher and fan it out. */
56
+ publish(observation) {
57
+ const { path } = observation;
58
+ const previous = this.#observed.get(path);
59
+ if (previous !== void 0 && observation.observed_at < previous.observed_at) return;
60
+ this.#observed.set(path, observation);
61
+ if (previous !== void 0 && previous.value === observation.value) return;
62
+ for (const callback of [...this.#listeners.get(path) ?? []]) {
63
+ if (this.#observed.get(path) !== observation) return;
64
+ this.#dispatch(callback, observation.value);
65
+ }
66
+ }
67
+ /**
68
+ * The last value observed for `path`. Null means there is no reading: either
69
+ * nothing has ever been observed, or a source reported the field as unavailable.
70
+ */
71
+ value(path) {
72
+ return this.#observed.get(path)?.value ?? null;
73
+ }
74
+ /**
75
+ * Register a listener for `path`, called on each changed value. The first
76
+ * listener for a path activates it on every capable publisher; the last one
77
+ * to leave releases it. Registration dispatches nothing: the value standing at
78
+ * that moment is `value()`.
79
+ */
80
+ listen(path, callback) {
81
+ let listeners = this.#listeners.get(path);
82
+ if (listeners === void 0) {
83
+ listeners = [];
84
+ this.#listeners.set(path, listeners);
85
+ }
86
+ const registered = listeners;
87
+ registered.push(callback);
88
+ if (registered.length === 1) this.#forCapable(path, (publisher, paths) => publisher.request(paths));
89
+ this.#notifyDemand();
90
+ let released = false;
91
+ return () => {
92
+ if (released) return;
93
+ released = true;
94
+ registered.splice(registered.indexOf(callback), 1);
95
+ if (registered.length === 0) this.#forCapable(path, (publisher, paths) => publisher.release(paths));
96
+ this.#notifyDemand();
97
+ };
98
+ }
99
+ #forCapable(path, action) {
100
+ const paths = /* @__PURE__ */ new Set([path]);
101
+ for (const { publisher } of [...this.#publishers]) if (publisher.paths.has(path)) action(publisher, paths);
102
+ }
103
+ /**
104
+ * Observe whether any of `paths` has at least one live listener. Derived from
105
+ * the activation counts the funnel already keeps. It reports; it never starts
106
+ * work of its own.
107
+ */
108
+ listen_demand(paths, callback) {
109
+ const observer = {
110
+ paths,
111
+ callback,
112
+ active: this.#demand(paths)
113
+ };
114
+ this.#demandObservers.push(observer);
115
+ this.#dispatch(callback, observer.active);
116
+ let released = false;
117
+ return () => {
118
+ if (released) return;
119
+ released = true;
120
+ const index = this.#demandObservers.indexOf(observer);
121
+ if (index !== -1) this.#demandObservers.splice(index, 1);
122
+ };
123
+ }
124
+ #demand(paths) {
125
+ return [...paths].some((path) => this.#listeners.get(path)?.length);
126
+ }
127
+ #notifyDemand() {
128
+ for (const observer of [...this.#demandObservers]) {
129
+ const active = this.#demand(observer.paths);
130
+ if (active !== observer.active) {
131
+ observer.active = active;
132
+ this.#dispatch(observer.callback, active);
133
+ }
134
+ }
135
+ }
136
+ /** A listener's error stays with that listener: the rest still hear the value. */
137
+ #dispatch(callback, value) {
138
+ try {
139
+ callback(value);
140
+ } catch (error) {
141
+ this.#logger?.debug(`observation funnel listener callback failed error=${require_log.errorText(error)}`);
142
+ }
143
+ }
144
+ };
145
+ const LOCK_STATES = /* @__PURE__ */ new Map([
146
+ [_teslemetry_tesla_protocol_command_vcsec.VehicleLockStateE.VEHICLELOCKSTATE_LOCKED, true],
147
+ [_teslemetry_tesla_protocol_command_vcsec.VehicleLockStateE.VEHICLELOCKSTATE_UNLOCKED, false],
148
+ [_teslemetry_tesla_protocol_command_vcsec.VehicleLockStateE.VEHICLELOCKSTATE_INTERNAL_LOCKED, true],
149
+ [_teslemetry_tesla_protocol_command_vcsec.VehicleLockStateE.VEHICLELOCKSTATE_SELECTIVE_UNLOCKED, false]
150
+ ]);
151
+ const CLOSURE_STATES = /* @__PURE__ */ new Map([
152
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_CLOSED, false],
153
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_OPEN, true],
154
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_AJAR, true],
155
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_OPENING, true],
156
+ [_teslemetry_tesla_protocol_command_vcsec.ClosureStateE.CLOSURESTATE_CLOSING, true]
157
+ ]);
158
+ /** `paths` of a passive publisher: every field. */
159
+ const ALL_PATHS = new Set(Object.values(FieldPath));
160
+ const monotonicSeconds = () => performance.now() / 1e3;
161
+ const ABSENT = Symbol("absent");
162
+ function isMapping(value) {
163
+ return typeof value === "object" && value !== null && !Array.isArray(value);
164
+ }
165
+ /** A present leaf, or `ABSENT`: a missing key is not a null reading. */
166
+ function leaf(section, key) {
167
+ return isMapping(section) && Object.hasOwn(section, key) ? section[key] : ABSENT;
168
+ }
169
+ /** A value usable as a reading: a boolean, or null for an explicit "unavailable". */
170
+ function isReading(value) {
171
+ return value === null || typeof value === "boolean";
172
+ }
173
+ /**
174
+ * A source whose readings are handed to it as arguments. It holds no client,
175
+ * session, endpoint, or callable able to obtain one, so nothing here can originate
176
+ * a request, and activation subscribes to nothing.
177
+ */
178
+ var SuppliedPublisher = class {
179
+ #clock;
180
+ #sink = null;
181
+ constructor(options = {}) {
182
+ this.#clock = options.clock ?? monotonicSeconds;
183
+ }
184
+ get paths() {
185
+ return ALL_PATHS;
186
+ }
187
+ attach(sink) {
188
+ this.#sink = sink;
189
+ return () => {
190
+ this.#sink = null;
191
+ };
192
+ }
193
+ request(_paths) {}
194
+ release(_paths) {}
195
+ _publish(input, observed_at) {
196
+ const observations = this._translate(input, observed_at ?? this.#clock());
197
+ const sink = this.#sink;
198
+ if (sink !== null) for (const observation of observations) sink.publish(observation);
199
+ return observations;
200
+ }
201
+ };
202
+ /** Translates a caller-supplied `vehicle_data` result into observations. */
203
+ var VehicleDataResultPublisher = class extends SuppliedPublisher {
204
+ /** Translate a supplied result and feed the audited leaves to the sink. */
205
+ publish_result(result, options = {}) {
206
+ return this._publish(result, options.observed_at);
207
+ }
208
+ _translate(result, observed_at) {
209
+ const response = leaf(result, "response");
210
+ const payload = isMapping(response) ? response : result;
211
+ const vehicle_state = leaf(payload, "vehicle_state");
212
+ const charge_state = leaf(payload, "charge_state");
213
+ const observations = [];
214
+ const locked = leaf(vehicle_state, "locked");
215
+ if (isReading(locked)) observations.push({
216
+ path: FieldPath.LOCKED,
217
+ value: locked,
218
+ observed_at
219
+ });
220
+ const charge_port = leaf(charge_state, "charge_port_door_open");
221
+ if (isReading(charge_port)) observations.push({
222
+ path: FieldPath.CHARGE_PORT_DOOR_OPEN,
223
+ value: charge_port,
224
+ observed_at
225
+ });
226
+ const front_trunk = leaf(vehicle_state, "ft");
227
+ if (front_trunk === null) observations.push({
228
+ path: FieldPath.DOOR_STATE_TRUNK_FRONT,
229
+ value: null,
230
+ observed_at
231
+ });
232
+ else if (front_trunk === 0 || front_trunk === 1) observations.push({
233
+ path: FieldPath.DOOR_STATE_TRUNK_FRONT,
234
+ value: front_trunk === 1,
235
+ observed_at
236
+ });
237
+ return observations;
238
+ }
239
+ };
240
+ /**
241
+ * `"true"` and `"false"` become real booleans; anything else passes through. Some
242
+ * vehicles stream a boolean signal as that literal string even when the stream
243
+ * prefers typed values.
244
+ */
245
+ function coerceStreamBool(value) {
246
+ if (value === "true") return true;
247
+ if (value === "false") return false;
248
+ return value;
249
+ }
250
+ /**
251
+ * Translates a caller-supplied Teslemetry stream signal update into observations.
252
+ * The update is keyed by signal name the way a stream push is (`Locked`,
253
+ * `ChargePortDoorOpen`, `DoorState` with a nested `TrunkFront`). `@teslemetry/api`
254
+ * owns the stream client; the two meet here.
255
+ */
256
+ var TeslemetryStreamPublisher = class extends SuppliedPublisher {
257
+ /** Translate a supplied stream signal update and feed it to the sink. */
258
+ publish_update(data, options = {}) {
259
+ return this._publish(data, options.observed_at);
260
+ }
261
+ _translate(data, observed_at) {
262
+ const observations = [];
263
+ const locked = coerceStreamBool(leaf(data, "Locked"));
264
+ if (isReading(locked)) observations.push({
265
+ path: FieldPath.LOCKED,
266
+ value: locked,
267
+ observed_at
268
+ });
269
+ const charge_port = coerceStreamBool(leaf(data, "ChargePortDoorOpen"));
270
+ if (isReading(charge_port)) observations.push({
271
+ path: FieldPath.CHARGE_PORT_DOOR_OPEN,
272
+ value: charge_port,
273
+ observed_at
274
+ });
275
+ const door_state = leaf(data, "DoorState");
276
+ if (door_state === null) observations.push({
277
+ path: FieldPath.DOOR_STATE_TRUNK_FRONT,
278
+ value: null,
279
+ observed_at
280
+ });
281
+ else if (isMapping(door_state)) {
282
+ const trunk_front = coerceStreamBool(leaf(door_state, "TrunkFront"));
283
+ if (isReading(trunk_front)) observations.push({
284
+ path: FieldPath.DOOR_STATE_TRUNK_FRONT,
285
+ value: trunk_front,
286
+ observed_at
287
+ });
288
+ }
289
+ return observations;
290
+ }
291
+ };
292
+ exports.CLOSURE_STATES = CLOSURE_STATES;
293
+ exports.FieldPath = FieldPath;
294
+ exports.LOCK_STATES = LOCK_STATES;
295
+ exports.ObservationFunnel = ObservationFunnel;
296
+ exports.TeslemetryStreamPublisher = TeslemetryStreamPublisher;
297
+ exports.VehicleDataResultPublisher = VehicleDataResultPublisher;
@@ -0,0 +1,118 @@
1
+ import { Logger } from "./log.cjs";
2
+ export type Unsubscribe = () => void;
3
+ /** Seconds off a monotonic clock. */
4
+ export type Clock = () => number;
5
+ /** A reading, or null for a source reporting the field itself as unavailable. */
6
+ export type Value = boolean | null;
7
+ /**
8
+ * A canonical routable field, valued to match its public signal name. A compound
9
+ * signal is split into one path per facet, because a broadcast and a
10
+ * `vehicle_data` result each expose individual leaves.
11
+ */
12
+ export declare const FieldPath: Readonly<{
13
+ readonly LOCKED: "Locked";
14
+ readonly CHARGE_PORT_DOOR_OPEN: "ChargePortDoorOpen";
15
+ readonly DOOR_STATE_TRUNK_FRONT: "DoorState.TrunkFront";
16
+ }>;
17
+ export type FieldPath = (typeof FieldPath)[keyof typeof FieldPath];
18
+ /** One timestamped value for one field. */
19
+ export interface Observation {
20
+ readonly path: FieldPath;
21
+ readonly value: Value;
22
+ /**
23
+ * Seconds off one monotonic clock shared by every publisher on a funnel;
24
+ * observations from different sources are ordered against each other.
25
+ */
26
+ readonly observed_at: number;
27
+ }
28
+ /** The handle a publisher is given to feed the funnel. */
29
+ export interface ObservationSink {
30
+ publish(observation: Observation): void;
31
+ }
32
+ /** A source of observations for one or more canonical fields. */
33
+ export interface Publisher {
34
+ readonly paths: ReadonlySet<FieldPath>;
35
+ attach(sink: ObservationSink): Unsubscribe;
36
+ /** Begin supplying `paths`: subscription accounting, not a data request. */
37
+ request(paths: ReadonlySet<FieldPath>): void;
38
+ /** Stop supplying `paths`. */
39
+ release(paths: ReadonlySet<FieldPath>): void;
40
+ }
41
+ /**
42
+ * Funnels observations from every attached publisher into one listener set.
43
+ *
44
+ * Many to one: a listener registered for a field receives what any publisher
45
+ * reports for it, in arrival order. There is no source selection, ranking, or
46
+ * health model here, and detaching or silencing a publisher produces no value of
47
+ * its own. The only arbitration, dropping older observations and skipping
48
+ * unchanged values, is hard-coded.
49
+ */
50
+ export declare class ObservationFunnel implements ObservationSink {
51
+ #private;
52
+ constructor(options?: {
53
+ logger?: Logger;
54
+ });
55
+ /** Attach `publisher` and return its detach closure. */
56
+ attach(publisher: Publisher): Unsubscribe;
57
+ /** Accept one observation from any publisher and fan it out. */
58
+ publish(observation: Observation): void;
59
+ /**
60
+ * The last value observed for `path`. Null means there is no reading: either
61
+ * nothing has ever been observed, or a source reported the field as unavailable.
62
+ */
63
+ value(path: FieldPath): Value;
64
+ /**
65
+ * Register a listener for `path`, called on each changed value. The first
66
+ * listener for a path activates it on every capable publisher; the last one
67
+ * to leave releases it. Registration dispatches nothing: the value standing at
68
+ * that moment is `value()`.
69
+ */
70
+ listen(path: FieldPath, callback: (value: Value) => void): Unsubscribe;
71
+ /**
72
+ * Observe whether any of `paths` has at least one live listener. Derived from
73
+ * the activation counts the funnel already keeps. It reports; it never starts
74
+ * work of its own.
75
+ */
76
+ listen_demand(paths: ReadonlySet<FieldPath>, callback: (active: boolean) => void): Unsubscribe;
77
+ }
78
+ export declare const LOCK_STATES: ReadonlyMap<number, boolean>;
79
+ export declare const CLOSURE_STATES: ReadonlyMap<number, boolean>;
80
+ /**
81
+ * A source whose readings are handed to it as arguments. It holds no client,
82
+ * session, endpoint, or callable able to obtain one, so nothing here can originate
83
+ * a request, and activation subscribes to nothing.
84
+ */
85
+ declare abstract class SuppliedPublisher<Input> implements Publisher {
86
+ #private;
87
+ constructor(options?: {
88
+ clock?: Clock;
89
+ });
90
+ get paths(): ReadonlySet<FieldPath>;
91
+ attach(sink: ObservationSink): Unsubscribe;
92
+ request(_paths: ReadonlySet<FieldPath>): void;
93
+ release(_paths: ReadonlySet<FieldPath>): void;
94
+ protected _publish(input: Input, observed_at: number | undefined): readonly Observation[];
95
+ protected abstract _translate(input: Input, observed_at: number): Observation[];
96
+ }
97
+ /** Translates a caller-supplied `vehicle_data` result into observations. */
98
+ export declare class VehicleDataResultPublisher extends SuppliedPublisher<unknown> {
99
+ /** Translate a supplied result and feed the audited leaves to the sink. */
100
+ publish_result(result: unknown, options?: {
101
+ observed_at?: number;
102
+ }): readonly Observation[];
103
+ protected _translate(result: unknown, observed_at: number): Observation[];
104
+ }
105
+ /**
106
+ * Translates a caller-supplied Teslemetry stream signal update into observations.
107
+ * The update is keyed by signal name the way a stream push is (`Locked`,
108
+ * `ChargePortDoorOpen`, `DoorState` with a nested `TrunkFront`). `@teslemetry/api`
109
+ * owns the stream client; the two meet here.
110
+ */
111
+ export declare class TeslemetryStreamPublisher extends SuppliedPublisher<unknown> {
112
+ /** Translate a supplied stream signal update and feed it to the sink. */
113
+ publish_update(data: unknown, options?: {
114
+ observed_at?: number;
115
+ }): readonly Observation[];
116
+ protected _translate(data: unknown, observed_at: number): Observation[];
117
+ }
118
+ export {};
@@ -0,0 +1,118 @@
1
+ import { Logger } from "./log.mjs";
2
+ export type Unsubscribe = () => void;
3
+ /** Seconds off a monotonic clock. */
4
+ export type Clock = () => number;
5
+ /** A reading, or null for a source reporting the field itself as unavailable. */
6
+ export type Value = boolean | null;
7
+ /**
8
+ * A canonical routable field, valued to match its public signal name. A compound
9
+ * signal is split into one path per facet, because a broadcast and a
10
+ * `vehicle_data` result each expose individual leaves.
11
+ */
12
+ export declare const FieldPath: Readonly<{
13
+ readonly LOCKED: "Locked";
14
+ readonly CHARGE_PORT_DOOR_OPEN: "ChargePortDoorOpen";
15
+ readonly DOOR_STATE_TRUNK_FRONT: "DoorState.TrunkFront";
16
+ }>;
17
+ export type FieldPath = (typeof FieldPath)[keyof typeof FieldPath];
18
+ /** One timestamped value for one field. */
19
+ export interface Observation {
20
+ readonly path: FieldPath;
21
+ readonly value: Value;
22
+ /**
23
+ * Seconds off one monotonic clock shared by every publisher on a funnel;
24
+ * observations from different sources are ordered against each other.
25
+ */
26
+ readonly observed_at: number;
27
+ }
28
+ /** The handle a publisher is given to feed the funnel. */
29
+ export interface ObservationSink {
30
+ publish(observation: Observation): void;
31
+ }
32
+ /** A source of observations for one or more canonical fields. */
33
+ export interface Publisher {
34
+ readonly paths: ReadonlySet<FieldPath>;
35
+ attach(sink: ObservationSink): Unsubscribe;
36
+ /** Begin supplying `paths`: subscription accounting, not a data request. */
37
+ request(paths: ReadonlySet<FieldPath>): void;
38
+ /** Stop supplying `paths`. */
39
+ release(paths: ReadonlySet<FieldPath>): void;
40
+ }
41
+ /**
42
+ * Funnels observations from every attached publisher into one listener set.
43
+ *
44
+ * Many to one: a listener registered for a field receives what any publisher
45
+ * reports for it, in arrival order. There is no source selection, ranking, or
46
+ * health model here, and detaching or silencing a publisher produces no value of
47
+ * its own. The only arbitration, dropping older observations and skipping
48
+ * unchanged values, is hard-coded.
49
+ */
50
+ export declare class ObservationFunnel implements ObservationSink {
51
+ #private;
52
+ constructor(options?: {
53
+ logger?: Logger;
54
+ });
55
+ /** Attach `publisher` and return its detach closure. */
56
+ attach(publisher: Publisher): Unsubscribe;
57
+ /** Accept one observation from any publisher and fan it out. */
58
+ publish(observation: Observation): void;
59
+ /**
60
+ * The last value observed for `path`. Null means there is no reading: either
61
+ * nothing has ever been observed, or a source reported the field as unavailable.
62
+ */
63
+ value(path: FieldPath): Value;
64
+ /**
65
+ * Register a listener for `path`, called on each changed value. The first
66
+ * listener for a path activates it on every capable publisher; the last one
67
+ * to leave releases it. Registration dispatches nothing: the value standing at
68
+ * that moment is `value()`.
69
+ */
70
+ listen(path: FieldPath, callback: (value: Value) => void): Unsubscribe;
71
+ /**
72
+ * Observe whether any of `paths` has at least one live listener. Derived from
73
+ * the activation counts the funnel already keeps. It reports; it never starts
74
+ * work of its own.
75
+ */
76
+ listen_demand(paths: ReadonlySet<FieldPath>, callback: (active: boolean) => void): Unsubscribe;
77
+ }
78
+ export declare const LOCK_STATES: ReadonlyMap<number, boolean>;
79
+ export declare const CLOSURE_STATES: ReadonlyMap<number, boolean>;
80
+ /**
81
+ * A source whose readings are handed to it as arguments. It holds no client,
82
+ * session, endpoint, or callable able to obtain one, so nothing here can originate
83
+ * a request, and activation subscribes to nothing.
84
+ */
85
+ declare abstract class SuppliedPublisher<Input> implements Publisher {
86
+ #private;
87
+ constructor(options?: {
88
+ clock?: Clock;
89
+ });
90
+ get paths(): ReadonlySet<FieldPath>;
91
+ attach(sink: ObservationSink): Unsubscribe;
92
+ request(_paths: ReadonlySet<FieldPath>): void;
93
+ release(_paths: ReadonlySet<FieldPath>): void;
94
+ protected _publish(input: Input, observed_at: number | undefined): readonly Observation[];
95
+ protected abstract _translate(input: Input, observed_at: number): Observation[];
96
+ }
97
+ /** Translates a caller-supplied `vehicle_data` result into observations. */
98
+ export declare class VehicleDataResultPublisher extends SuppliedPublisher<unknown> {
99
+ /** Translate a supplied result and feed the audited leaves to the sink. */
100
+ publish_result(result: unknown, options?: {
101
+ observed_at?: number;
102
+ }): readonly Observation[];
103
+ protected _translate(result: unknown, observed_at: number): Observation[];
104
+ }
105
+ /**
106
+ * Translates a caller-supplied Teslemetry stream signal update into observations.
107
+ * The update is keyed by signal name the way a stream push is (`Locked`,
108
+ * `ChargePortDoorOpen`, `DoorState` with a nested `TrunkFront`). `@teslemetry/api`
109
+ * owns the stream client; the two meet here.
110
+ */
111
+ export declare class TeslemetryStreamPublisher extends SuppliedPublisher<unknown> {
112
+ /** Translate a supplied stream signal update and feed it to the sink. */
113
+ publish_update(data: unknown, options?: {
114
+ observed_at?: number;
115
+ }): readonly Observation[];
116
+ protected _translate(data: unknown, observed_at: number): Observation[];
117
+ }
118
+ export {};