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,65 @@
1
+ const require_base = require("./base.cjs");
2
+ // src/router/energysite.ts
3
+ /** The `live_status` keys a local Powerwall owns. */
4
+ const LOCAL_LIVE_STATUS_KEYS = [
5
+ "solar_power",
6
+ "energy_left",
7
+ "total_pack_energy",
8
+ "percentage_charged",
9
+ "battery_power",
10
+ "load_power",
11
+ "grid_power",
12
+ "generator_power",
13
+ "grid_status",
14
+ "island_status"
15
+ ];
16
+ /** The `site_info` keys a local Powerwall owns. */
17
+ const LOCAL_SITE_INFO_KEYS = ["backup_reserve_percent", "default_real_mode"];
18
+ /**
19
+ * Overlay `owned_keys` present in `local` onto a copy of `cloud`. Every other key
20
+ * keeps its cloud value.
21
+ *
22
+ * A local `live_status` returns every cloud key, with `null` for the ones it
23
+ * cannot serve, so presence cannot be the ownership test. A fixed owned-key set
24
+ * overlays local readings without clobbering cloud values, and lets a local outage
25
+ * fall back to the cloud value rather than to an unavailable one.
26
+ *
27
+ * A key overlays only when it is owned, present, and not null: null means "not
28
+ * served this tick" and the cloud value is kept. Any other falsy value (`0`,
29
+ * `false`, `""`) overlays, because a zero reading must win. `local` of null, a
30
+ * failed poll, yields a copy of `cloud`.
31
+ */
32
+ function merge_local_into_cloud(cloud, local, owned_keys) {
33
+ const merged = { ...cloud };
34
+ if (local == null) return merged;
35
+ for (const key of owned_keys) {
36
+ const value = local[key];
37
+ if (value !== void 0 && value !== null) merged[key] = value;
38
+ }
39
+ return merged;
40
+ }
41
+ /** Overlay a local `live_status` onto the cloud document. See {@link merge_local_into_cloud}. */
42
+ function merge_live_status(cloud, local) {
43
+ return merge_local_into_cloud(cloud, local, LOCAL_LIVE_STATUS_KEYS);
44
+ }
45
+ /** Overlay a local `site_info` onto the cloud document. See {@link merge_local_into_cloud}. */
46
+ function merge_site_info(cloud, local) {
47
+ return merge_local_into_cloud(cloud, local, LOCAL_SITE_INFO_KEYS);
48
+ }
49
+ var EnergySiteRouterBase = class extends require_base.Router {
50
+ /** The site id of the first backend that has one. */
51
+ get energy_site_id() {
52
+ return this._first_attribute("energy_site_id");
53
+ }
54
+ /** Logs `transport=lan` for the local primary. Cloud fallbacks keep `backend=`. */
55
+ _backend_field(backend) {
56
+ return backend === this.primary ? "transport=lan" : super._backend_field(backend);
57
+ }
58
+ };
59
+ const EnergySiteRouter = EnergySiteRouterBase;
60
+ exports.EnergySiteRouter = EnergySiteRouter;
61
+ exports.LOCAL_LIVE_STATUS_KEYS = LOCAL_LIVE_STATUS_KEYS;
62
+ exports.LOCAL_SITE_INFO_KEYS = LOCAL_SITE_INFO_KEYS;
63
+ exports.merge_live_status = merge_live_status;
64
+ exports.merge_local_into_cloud = merge_local_into_cloud;
65
+ exports.merge_site_info = merge_site_info;
@@ -0,0 +1,47 @@
1
+ import { Backends, MemberOf, Routed, Router, RouterOptions } from "./base.cjs";
2
+ /** The `live_status` keys a local Powerwall owns. */
3
+ export declare const LOCAL_LIVE_STATUS_KEYS: readonly ["solar_power", "energy_left", "total_pack_energy", "percentage_charged", "battery_power", "load_power", "grid_power", "generator_power", "grid_status", "island_status"];
4
+ /** The `site_info` keys a local Powerwall owns. */
5
+ export declare const LOCAL_SITE_INFO_KEYS: readonly ["backup_reserve_percent", "default_real_mode"];
6
+ /**
7
+ * Overlay `owned_keys` present in `local` onto a copy of `cloud`. Every other key
8
+ * keeps its cloud value.
9
+ *
10
+ * A local `live_status` returns every cloud key, with `null` for the ones it
11
+ * cannot serve, so presence cannot be the ownership test. A fixed owned-key set
12
+ * overlays local readings without clobbering cloud values, and lets a local outage
13
+ * fall back to the cloud value rather than to an unavailable one.
14
+ *
15
+ * A key overlays only when it is owned, present, and not null: null means "not
16
+ * served this tick" and the cloud value is kept. Any other falsy value (`0`,
17
+ * `false`, `""`) overlays, because a zero reading must win. `local` of null, a
18
+ * failed poll, yields a copy of `cloud`.
19
+ */
20
+ export declare function merge_local_into_cloud<T extends object>(cloud: T, local: object | null, owned_keys: readonly string[]): T;
21
+ /** Overlay a local `live_status` onto the cloud document. See {@link merge_local_into_cloud}. */
22
+ export declare function merge_live_status<T extends object>(cloud: T, local: object | null): T;
23
+ /** Overlay a local `site_info` onto the cloud document. See {@link merge_local_into_cloud}. */
24
+ export declare function merge_site_info<T extends object>(cloud: T, local: object | null): T;
25
+ declare class EnergySiteRouterBase<Bs extends Backends> extends Router<Bs> {
26
+ /** The site id of the first backend that has one. */
27
+ get energy_site_id(): MemberOf<Bs[number], "energy_site_id">;
28
+ /** Logs `transport=lan` for the local primary. Cloud fallbacks keep `backend=`. */
29
+ protected _backend_field(backend: object): string;
30
+ }
31
+ /**
32
+ * A router over energy sites: a local primary, such as `PowerwallEnergySite`,
33
+ * and one or more cloud fallbacks. See `Router` for dispatch, failover and the
34
+ * replay warning.
35
+ *
36
+ * There is no per-method list. A method the local backend omits goes straight to
37
+ * the cloud, and registration and discovery (`add_authorized_client`,
38
+ * `find_gateway_address`) belong on `router.secondary` directly. A local-only
39
+ * caller that must not fail over uses `router.primary`.
40
+ *
41
+ * A failed-over write can run twice. `operation`, `backup` and
42
+ * `grid_import_export` all write `config.json`, so do not split them across
43
+ * backends: one write would stomp the other.
44
+ */
45
+ export type EnergySiteRouter<Bs extends Backends> = EnergySiteRouterBase<Bs> & Routed<Bs[number]>;
46
+ export declare const EnergySiteRouter: new <const Bs extends Backends>(backends: Bs, options?: RouterOptions) => EnergySiteRouter<Bs>;
47
+ export {};
@@ -0,0 +1,47 @@
1
+ import { Backends, MemberOf, Routed, Router, RouterOptions } from "./base.mjs";
2
+ /** The `live_status` keys a local Powerwall owns. */
3
+ export declare const LOCAL_LIVE_STATUS_KEYS: readonly ["solar_power", "energy_left", "total_pack_energy", "percentage_charged", "battery_power", "load_power", "grid_power", "generator_power", "grid_status", "island_status"];
4
+ /** The `site_info` keys a local Powerwall owns. */
5
+ export declare const LOCAL_SITE_INFO_KEYS: readonly ["backup_reserve_percent", "default_real_mode"];
6
+ /**
7
+ * Overlay `owned_keys` present in `local` onto a copy of `cloud`. Every other key
8
+ * keeps its cloud value.
9
+ *
10
+ * A local `live_status` returns every cloud key, with `null` for the ones it
11
+ * cannot serve, so presence cannot be the ownership test. A fixed owned-key set
12
+ * overlays local readings without clobbering cloud values, and lets a local outage
13
+ * fall back to the cloud value rather than to an unavailable one.
14
+ *
15
+ * A key overlays only when it is owned, present, and not null: null means "not
16
+ * served this tick" and the cloud value is kept. Any other falsy value (`0`,
17
+ * `false`, `""`) overlays, because a zero reading must win. `local` of null, a
18
+ * failed poll, yields a copy of `cloud`.
19
+ */
20
+ export declare function merge_local_into_cloud<T extends object>(cloud: T, local: object | null, owned_keys: readonly string[]): T;
21
+ /** Overlay a local `live_status` onto the cloud document. See {@link merge_local_into_cloud}. */
22
+ export declare function merge_live_status<T extends object>(cloud: T, local: object | null): T;
23
+ /** Overlay a local `site_info` onto the cloud document. See {@link merge_local_into_cloud}. */
24
+ export declare function merge_site_info<T extends object>(cloud: T, local: object | null): T;
25
+ declare class EnergySiteRouterBase<Bs extends Backends> extends Router<Bs> {
26
+ /** The site id of the first backend that has one. */
27
+ get energy_site_id(): MemberOf<Bs[number], "energy_site_id">;
28
+ /** Logs `transport=lan` for the local primary. Cloud fallbacks keep `backend=`. */
29
+ protected _backend_field(backend: object): string;
30
+ }
31
+ /**
32
+ * A router over energy sites: a local primary, such as `PowerwallEnergySite`,
33
+ * and one or more cloud fallbacks. See `Router` for dispatch, failover and the
34
+ * replay warning.
35
+ *
36
+ * There is no per-method list. A method the local backend omits goes straight to
37
+ * the cloud, and registration and discovery (`add_authorized_client`,
38
+ * `find_gateway_address`) belong on `router.secondary` directly. A local-only
39
+ * caller that must not fail over uses `router.primary`.
40
+ *
41
+ * A failed-over write can run twice. `operation`, `backup` and
42
+ * `grid_import_export` all write `config.json`, so do not split them across
43
+ * backends: one write would stomp the other.
44
+ */
45
+ export type EnergySiteRouter<Bs extends Backends> = EnergySiteRouterBase<Bs> & Routed<Bs[number]>;
46
+ export declare const EnergySiteRouter: new <const Bs extends Backends>(backends: Bs, options?: RouterOptions) => EnergySiteRouter<Bs>;
47
+ export {};
@@ -0,0 +1,60 @@
1
+ import { Router } from "./base.mjs";
2
+ // src/router/energysite.ts
3
+ /** The `live_status` keys a local Powerwall owns. */
4
+ const LOCAL_LIVE_STATUS_KEYS = [
5
+ "solar_power",
6
+ "energy_left",
7
+ "total_pack_energy",
8
+ "percentage_charged",
9
+ "battery_power",
10
+ "load_power",
11
+ "grid_power",
12
+ "generator_power",
13
+ "grid_status",
14
+ "island_status"
15
+ ];
16
+ /** The `site_info` keys a local Powerwall owns. */
17
+ const LOCAL_SITE_INFO_KEYS = ["backup_reserve_percent", "default_real_mode"];
18
+ /**
19
+ * Overlay `owned_keys` present in `local` onto a copy of `cloud`. Every other key
20
+ * keeps its cloud value.
21
+ *
22
+ * A local `live_status` returns every cloud key, with `null` for the ones it
23
+ * cannot serve, so presence cannot be the ownership test. A fixed owned-key set
24
+ * overlays local readings without clobbering cloud values, and lets a local outage
25
+ * fall back to the cloud value rather than to an unavailable one.
26
+ *
27
+ * A key overlays only when it is owned, present, and not null: null means "not
28
+ * served this tick" and the cloud value is kept. Any other falsy value (`0`,
29
+ * `false`, `""`) overlays, because a zero reading must win. `local` of null, a
30
+ * failed poll, yields a copy of `cloud`.
31
+ */
32
+ function merge_local_into_cloud(cloud, local, owned_keys) {
33
+ const merged = { ...cloud };
34
+ if (local == null) return merged;
35
+ for (const key of owned_keys) {
36
+ const value = local[key];
37
+ if (value !== void 0 && value !== null) merged[key] = value;
38
+ }
39
+ return merged;
40
+ }
41
+ /** Overlay a local `live_status` onto the cloud document. See {@link merge_local_into_cloud}. */
42
+ function merge_live_status(cloud, local) {
43
+ return merge_local_into_cloud(cloud, local, LOCAL_LIVE_STATUS_KEYS);
44
+ }
45
+ /** Overlay a local `site_info` onto the cloud document. See {@link merge_local_into_cloud}. */
46
+ function merge_site_info(cloud, local) {
47
+ return merge_local_into_cloud(cloud, local, LOCAL_SITE_INFO_KEYS);
48
+ }
49
+ var EnergySiteRouterBase = class extends Router {
50
+ /** The site id of the first backend that has one. */
51
+ get energy_site_id() {
52
+ return this._first_attribute("energy_site_id");
53
+ }
54
+ /** Logs `transport=lan` for the local primary. Cloud fallbacks keep `backend=`. */
55
+ _backend_field(backend) {
56
+ return backend === this.primary ? "transport=lan" : super._backend_field(backend);
57
+ }
58
+ };
59
+ const EnergySiteRouter = EnergySiteRouterBase;
60
+ export { EnergySiteRouter, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, merge_live_status, merge_local_into_cloud, merge_site_info };
package/dist/router.cjs CHANGED
@@ -0,0 +1,13 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_base = require("./router/base.cjs");
3
+ const require_energysite = require("./router/energysite.cjs");
4
+ exports.EnergySiteRouter = require_energysite.EnergySiteRouter;
5
+ exports.FAILOVER_SAFE_ERRORS = require_base.FAILOVER_SAFE_ERRORS;
6
+ exports.LOCAL_LIVE_STATUS_KEYS = require_energysite.LOCAL_LIVE_STATUS_KEYS;
7
+ exports.LOCAL_SITE_INFO_KEYS = require_energysite.LOCAL_SITE_INFO_KEYS;
8
+ exports.Router = require_base.Router;
9
+ exports.VehicleRouter = require_base.VehicleRouter;
10
+ exports.is_failover_safe = require_base.is_failover_safe;
11
+ exports.merge_live_status = require_energysite.merge_live_status;
12
+ exports.merge_local_into_cloud = require_energysite.merge_local_into_cloud;
13
+ exports.merge_site_info = require_energysite.merge_site_info;
package/dist/router.d.cts CHANGED
@@ -1 +1,3 @@
1
- export {}
1
+ import { Backends, FAILOVER_SAFE_ERRORS, HealthCheck, ResultHandler, Routed, Router, RouterOptions, VehicleRouter, is_failover_safe } from "./router/base.cjs";
2
+ import { EnergySiteRouter, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, merge_live_status, merge_local_into_cloud, merge_site_info } from "./router/energysite.cjs";
3
+ export { type Backends, EnergySiteRouter, FAILOVER_SAFE_ERRORS, type HealthCheck, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, type ResultHandler, type Routed, Router, type RouterOptions, VehicleRouter, is_failover_safe, merge_live_status, merge_local_into_cloud, merge_site_info };
package/dist/router.d.mts CHANGED
@@ -1 +1,3 @@
1
- export {}
1
+ import { Backends, FAILOVER_SAFE_ERRORS, HealthCheck, ResultHandler, Routed, Router, RouterOptions, VehicleRouter, is_failover_safe } from "./router/base.mjs";
2
+ import { EnergySiteRouter, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, merge_live_status, merge_local_into_cloud, merge_site_info } from "./router/energysite.mjs";
3
+ export { type Backends, EnergySiteRouter, FAILOVER_SAFE_ERRORS, type HealthCheck, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, type ResultHandler, type Routed, Router, type RouterOptions, VehicleRouter, is_failover_safe, merge_live_status, merge_local_into_cloud, merge_site_info };
package/dist/router.mjs CHANGED
@@ -1 +1,3 @@
1
- export {};
1
+ import { FAILOVER_SAFE_ERRORS, Router, VehicleRouter, is_failover_safe } from "./router/base.mjs";
2
+ import { EnergySiteRouter, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, merge_live_status, merge_local_into_cloud, merge_site_info } from "./router/energysite.mjs";
3
+ export { EnergySiteRouter, FAILOVER_SAFE_ERRORS, LOCAL_LIVE_STATUS_KEYS, LOCAL_SITE_INFO_KEYS, Router, VehicleRouter, is_failover_safe, merge_live_status, merge_local_into_cloud, merge_site_info };
@@ -3,6 +3,7 @@ let crypto = require("crypto");
3
3
  crypto = require_runtime.__toESM(crypto, 1);
4
4
  // src/signing/crypto.ts
5
5
  const CURVE = "prime256v1";
6
+ const AES_GCM_TAG_LENGTH = 16;
6
7
  /** Derive the uncompressed public key point for a raw P-256 private scalar. */
7
8
  function publicKeyFor(privateKey) {
8
9
  const ecdh = crypto.default.createECDH(CURVE);
@@ -20,12 +21,39 @@ function deriveSharedKey(privateKey, vehiclePublicKey) {
20
21
  const secret = ecdh.computeSecret(vehiclePublicKey);
21
22
  return crypto.default.createHash("sha1").update(secret).digest().subarray(0, 16);
22
23
  }
24
+ /** SHA-256 of a metadata string: the AAD of the protocol's AES-GCM operations. */
25
+ function sha256(data) {
26
+ return crypto.default.createHash("sha256").update(data).digest();
27
+ }
28
+ /** AES-128-GCM with a detached 16-byte tag, as the vehicle-command protocol carries it. */
29
+ function aesGcmEncrypt(key, nonce, plaintext, aad) {
30
+ const cipher = crypto.default.createCipheriv("aes-128-gcm", key, nonce, { authTagLength: AES_GCM_TAG_LENGTH });
31
+ cipher.setAAD(aad);
32
+ return {
33
+ ciphertext: Buffer.concat([cipher.update(plaintext), cipher.final()]),
34
+ tag: cipher.getAuthTag()
35
+ };
36
+ }
37
+ /**
38
+ * Open an AES-128-GCM message. `authTagLength` makes a tag of any other length
39
+ * fail, where the default would accept one truncated by an attacker.
40
+ * @throws Error when the tag does not verify.
41
+ */
42
+ function aesGcmDecrypt(key, nonce, ciphertext, tag, aad) {
43
+ const decipher = crypto.default.createDecipheriv("aes-128-gcm", key, nonce, { authTagLength: AES_GCM_TAG_LENGTH });
44
+ decipher.setAAD(aad);
45
+ decipher.setAuthTag(tag);
46
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
47
+ }
23
48
  /** Big-endian uint32, as used throughout the signed-command metadata tags. */
24
49
  function uint32BE(value) {
25
50
  const buffer = Buffer.alloc(4);
26
51
  buffer.writeUInt32BE(value >>> 0, 0);
27
52
  return buffer;
28
53
  }
54
+ exports.aesGcmDecrypt = aesGcmDecrypt;
55
+ exports.aesGcmEncrypt = aesGcmEncrypt;
29
56
  exports.deriveSharedKey = deriveSharedKey;
30
57
  exports.publicKeyFor = publicKeyFor;
58
+ exports.sha256 = sha256;
31
59
  exports.uint32BE = uint32BE;
@@ -1,6 +1,7 @@
1
1
  import crypto from "crypto";
2
2
  // src/signing/crypto.ts
3
3
  const CURVE = "prime256v1";
4
+ const AES_GCM_TAG_LENGTH = 16;
4
5
  /** Derive the uncompressed public key point for a raw P-256 private scalar. */
5
6
  function publicKeyFor(privateKey) {
6
7
  const ecdh = crypto.createECDH(CURVE);
@@ -18,10 +19,34 @@ function deriveSharedKey(privateKey, vehiclePublicKey) {
18
19
  const secret = ecdh.computeSecret(vehiclePublicKey);
19
20
  return crypto.createHash("sha1").update(secret).digest().subarray(0, 16);
20
21
  }
22
+ /** SHA-256 of a metadata string: the AAD of the protocol's AES-GCM operations. */
23
+ function sha256(data) {
24
+ return crypto.createHash("sha256").update(data).digest();
25
+ }
26
+ /** AES-128-GCM with a detached 16-byte tag, as the vehicle-command protocol carries it. */
27
+ function aesGcmEncrypt(key, nonce, plaintext, aad) {
28
+ const cipher = crypto.createCipheriv("aes-128-gcm", key, nonce, { authTagLength: AES_GCM_TAG_LENGTH });
29
+ cipher.setAAD(aad);
30
+ return {
31
+ ciphertext: Buffer.concat([cipher.update(plaintext), cipher.final()]),
32
+ tag: cipher.getAuthTag()
33
+ };
34
+ }
35
+ /**
36
+ * Open an AES-128-GCM message. `authTagLength` makes a tag of any other length
37
+ * fail, where the default would accept one truncated by an attacker.
38
+ * @throws Error when the tag does not verify.
39
+ */
40
+ function aesGcmDecrypt(key, nonce, ciphertext, tag, aad) {
41
+ const decipher = crypto.createDecipheriv("aes-128-gcm", key, nonce, { authTagLength: AES_GCM_TAG_LENGTH });
42
+ decipher.setAAD(aad);
43
+ decipher.setAuthTag(tag);
44
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
45
+ }
21
46
  /** Big-endian uint32, as used throughout the signed-command metadata tags. */
22
47
  function uint32BE(value) {
23
48
  const buffer = Buffer.alloc(4);
24
49
  buffer.writeUInt32BE(value >>> 0, 0);
25
50
  return buffer;
26
51
  }
27
- export { deriveSharedKey, publicKeyFor, uint32BE };
52
+ export { aesGcmDecrypt, aesGcmEncrypt, deriveSharedKey, publicKeyFor, sha256, uint32BE };
@@ -0,0 +1,123 @@
1
+ const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_energyisland = require("../energyisland.cjs");
3
+ const require_crypto = require("./crypto.cjs");
4
+ const require_keys = require("./keys.cjs");
5
+ let _teslemetry_tesla_protocol_energy_device_transport = require("@teslemetry/tesla-protocol/energy_device/transport");
6
+ let node_crypto = require("node:crypto");
7
+ node_crypto = require_runtime.__toESM(node_crypto, 1);
8
+ let _teslemetry_tesla_protocol_energy_device_authorization_types = require("@teslemetry/tesla-protocol/energy_device/authorization_types");
9
+ let _teslemetry_tesla_protocol_energy_device_signed_message = require("@teslemetry/tesla-protocol/energy_device/signed_message");
10
+ // src/signing/energy.ts
11
+ /** The cloud round trip needs more than the 12 s LAN TTL; 30 s worked live through `device_command`. */
12
+ const ENERGY_SIGNATURE_TTL_SECONDS = 30;
13
+ /** ts-proto's value for an enum input it does not recognise. */
14
+ const UNRECOGNIZED_ENUM = -1;
15
+ /** Encode one tag-length-value entry. */
16
+ function tlv(tag, value) {
17
+ return Buffer.concat([Buffer.from([tag, value.length]), value]);
18
+ }
19
+ function isPlainObject(value) {
20
+ return typeof value === "object" && value !== null && !Array.isArray(value);
21
+ }
22
+ /** A proto field name in the camelCase form ts-proto generates. */
23
+ function camelCase(name) {
24
+ return name.replace(/_([a-z0-9])/g, (_, character) => character.toUpperCase());
25
+ }
26
+ /** Convert every key from Python's proto field names; ts-proto's `fromJSON` reads camelCase only. */
27
+ function camelCaseKeys(value) {
28
+ if (Array.isArray(value)) return value.map(camelCaseKeys);
29
+ if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [camelCase(key), camelCaseKeys(item)]));
30
+ return value;
31
+ }
32
+ /**
33
+ * Reject input `fromJSON` dropped or could not read, as Python's `ParseDict`
34
+ * does: otherwise a mistyped field would be signed and sent without it.
35
+ */
36
+ function assertParsed(input, parsed, path) {
37
+ if (Array.isArray(input) && Array.isArray(parsed)) input.forEach((item, index) => assertParsed(item, parsed[index], `${path}[${index}]`));
38
+ else if (isPlainObject(input) && isPlainObject(parsed) && !(parsed instanceof Date) && !ArrayBuffer.isView(parsed)) for (const [key, item] of Object.entries(input)) {
39
+ if (!(key in parsed)) throw new TypeError(`Unknown energy device command field ${path}.${key}`);
40
+ assertParsed(item, parsed[key], `${path}.${key}`);
41
+ }
42
+ else if (parsed instanceof Date && Number.isNaN(parsed.getTime()) || parsed === UNRECOGNIZED_ENUM && input !== UNRECOGNIZED_ENUM) throw new TypeError(`Invalid value for energy device command field ${path}`);
43
+ }
44
+ /**
45
+ * Builds RSA-signed `RoutableMessage` bytes for one energy device key.
46
+ *
47
+ * The key must be an RSA key registered on the gateway with
48
+ * `add_authorized_client` and in state `VERIFIED`. An ECC key cannot sign this
49
+ * scheme: the `SignatureData` oneof has only `rsa_data`.
50
+ */
51
+ var EnergyDeviceSigner = class {
52
+ /** The signer's public key, PKCS#1 DER. */
53
+ publicKeyDer;
54
+ /** Seconds a signed message stays valid. */
55
+ ttl;
56
+ #privateKey;
57
+ /**
58
+ * @param privateKey The RSA private key.
59
+ * @param ttl Seconds a signed message stays valid. `V1rTransport` passes 12.
60
+ * @throws TypeError for anything but an RSA private key.
61
+ */
62
+ constructor(privateKey, ttl = 30) {
63
+ if (privateKey?.type !== "private" || privateKey.asymmetricKeyType !== "rsa") throw new TypeError("Energy device signing requires an RSA private key");
64
+ this.#privateKey = privateKey;
65
+ this.publicKeyDer = require_keys.rsaPublicDerPkcs1(privateKey);
66
+ this.ttl = ttl;
67
+ }
68
+ /**
69
+ * Serialize a cloud `MessageEnvelope` addressed to `din`.
70
+ *
71
+ * `category`, `command` and `params` are the snake_case proto field names
72
+ * the unsigned `grpc_command` sends, so every command can be signed without
73
+ * per-command code. `delivery_channel` stays unset, as in the envelope
74
+ * proven on the cloud path.
75
+ * @throws TypeError for a field the message does not have, or an island mode that is not 1 or 6.
76
+ */
77
+ static envelope(din, category, command, params) {
78
+ if (category === "teg" && command === "set_island_mode_request") require_energyisland.validate_island_mode(params?.mode, params?.force);
79
+ const input = camelCaseKeys({ [category]: { [command]: params ?? {} } });
80
+ const envelope = _teslemetry_tesla_protocol_energy_device_transport.MessageEnvelope.fromJSON(input);
81
+ assertParsed(input, envelope, "message");
82
+ envelope.sender = { authorizedClient: _teslemetry_tesla_protocol_energy_device_authorization_types.AuthorizedClientType.AUTHORIZED_CLIENT_TYPE_CUSTOMER_MOBILE_APP };
83
+ envelope.recipient = { din };
84
+ return _teslemetry_tesla_protocol_energy_device_transport.MessageEnvelope.encode(envelope).finish();
85
+ }
86
+ /**
87
+ * Wrap `envelope` in a signed `RoutableMessage` and serialize it.
88
+ *
89
+ * RSA-4096 blocks for milliseconds, so this signs on the thread pool.
90
+ * @param envelope The serialized `MessageEnvelope`.
91
+ * @param din The gateway DIN the envelope and the request target.
92
+ * @param options `now` (in seconds) and `messageUuid` exist for test vectors.
93
+ */
94
+ async sign(envelope, din, options = {}) {
95
+ if (din.length > 255 || /\P{ASCII}/u.test(din)) throw new TypeError("A gateway DIN must be at most 255 ASCII characters");
96
+ const expiresAt = Math.ceil(options.now ?? Date.now() / 1e3) + this.ttl;
97
+ const signedData = Buffer.concat([
98
+ tlv(_teslemetry_tesla_protocol_energy_device_signed_message.Tag.TAG_SIGNATURE_TYPE, Buffer.from([_teslemetry_tesla_protocol_energy_device_signed_message.SignatureType.SIGNATURE_TYPE_RSA])),
99
+ tlv(_teslemetry_tesla_protocol_energy_device_signed_message.Tag.TAG_DOMAIN, Buffer.from([_teslemetry_tesla_protocol_energy_device_signed_message.Domain.DOMAIN_ENERGY_DEVICE])),
100
+ tlv(_teslemetry_tesla_protocol_energy_device_signed_message.Tag.TAG_PERSONALIZATION, Buffer.from(din, "ascii")),
101
+ tlv(_teslemetry_tesla_protocol_energy_device_signed_message.Tag.TAG_EXPIRES_AT, require_crypto.uint32BE(expiresAt)),
102
+ Buffer.from([_teslemetry_tesla_protocol_energy_device_signed_message.Tag.TAG_END]),
103
+ envelope
104
+ ]);
105
+ const signature = await new Promise((resolve, reject) => {
106
+ node_crypto.default.sign("sha512", signedData, this.#privateKey, (error, result) => error ? reject(error) : resolve(result));
107
+ });
108
+ return _teslemetry_tesla_protocol_energy_device_signed_message.RoutableMessage.encode(_teslemetry_tesla_protocol_energy_device_signed_message.RoutableMessage.create({
109
+ toDestination: { domain: _teslemetry_tesla_protocol_energy_device_signed_message.Domain.DOMAIN_ENERGY_DEVICE },
110
+ protobufMessageAsBytes: envelope,
111
+ signatureData: {
112
+ signerIdentity: { publicKey: this.publicKeyDer },
113
+ rsaData: {
114
+ expiresAt,
115
+ signature
116
+ }
117
+ },
118
+ uuid: options.messageUuid ?? node_crypto.default.randomBytes(16)
119
+ })).finish();
120
+ }
121
+ };
122
+ exports.ENERGY_SIGNATURE_TTL_SECONDS = ENERGY_SIGNATURE_TTL_SECONDS;
123
+ exports.EnergyDeviceSigner = EnergyDeviceSigner;
@@ -0,0 +1,45 @@
1
+ import crypto from "node:crypto";
2
+ /** The cloud round trip needs more than the 12 s LAN TTL; 30 s worked live through `device_command`. */
3
+ export declare const ENERGY_SIGNATURE_TTL_SECONDS = 30;
4
+ /**
5
+ * Builds RSA-signed `RoutableMessage` bytes for one energy device key.
6
+ *
7
+ * The key must be an RSA key registered on the gateway with
8
+ * `add_authorized_client` and in state `VERIFIED`. An ECC key cannot sign this
9
+ * scheme: the `SignatureData` oneof has only `rsa_data`.
10
+ */
11
+ export declare class EnergyDeviceSigner {
12
+ #private;
13
+ /** The signer's public key, PKCS#1 DER. */
14
+ readonly publicKeyDer: Buffer;
15
+ /** Seconds a signed message stays valid. */
16
+ readonly ttl: number;
17
+ /**
18
+ * @param privateKey The RSA private key.
19
+ * @param ttl Seconds a signed message stays valid. `V1rTransport` passes 12.
20
+ * @throws TypeError for anything but an RSA private key.
21
+ */
22
+ constructor(privateKey: crypto.KeyObject, ttl?: number);
23
+ /**
24
+ * Serialize a cloud `MessageEnvelope` addressed to `din`.
25
+ *
26
+ * `category`, `command` and `params` are the snake_case proto field names
27
+ * the unsigned `grpc_command` sends, so every command can be signed without
28
+ * per-command code. `delivery_channel` stays unset, as in the envelope
29
+ * proven on the cloud path.
30
+ * @throws TypeError for a field the message does not have, or an island mode that is not 1 or 6.
31
+ */
32
+ static envelope(din: string, category: string, command: string, params?: Record<string, unknown> | null): Uint8Array;
33
+ /**
34
+ * Wrap `envelope` in a signed `RoutableMessage` and serialize it.
35
+ *
36
+ * RSA-4096 blocks for milliseconds, so this signs on the thread pool.
37
+ * @param envelope The serialized `MessageEnvelope`.
38
+ * @param din The gateway DIN the envelope and the request target.
39
+ * @param options `now` (in seconds) and `messageUuid` exist for test vectors.
40
+ */
41
+ sign(envelope: Uint8Array, din: string, options?: {
42
+ now?: number;
43
+ messageUuid?: Uint8Array;
44
+ }): Promise<Uint8Array>;
45
+ }
@@ -0,0 +1,45 @@
1
+ import crypto from "node:crypto";
2
+ /** The cloud round trip needs more than the 12 s LAN TTL; 30 s worked live through `device_command`. */
3
+ export declare const ENERGY_SIGNATURE_TTL_SECONDS = 30;
4
+ /**
5
+ * Builds RSA-signed `RoutableMessage` bytes for one energy device key.
6
+ *
7
+ * The key must be an RSA key registered on the gateway with
8
+ * `add_authorized_client` and in state `VERIFIED`. An ECC key cannot sign this
9
+ * scheme: the `SignatureData` oneof has only `rsa_data`.
10
+ */
11
+ export declare class EnergyDeviceSigner {
12
+ #private;
13
+ /** The signer's public key, PKCS#1 DER. */
14
+ readonly publicKeyDer: Buffer;
15
+ /** Seconds a signed message stays valid. */
16
+ readonly ttl: number;
17
+ /**
18
+ * @param privateKey The RSA private key.
19
+ * @param ttl Seconds a signed message stays valid. `V1rTransport` passes 12.
20
+ * @throws TypeError for anything but an RSA private key.
21
+ */
22
+ constructor(privateKey: crypto.KeyObject, ttl?: number);
23
+ /**
24
+ * Serialize a cloud `MessageEnvelope` addressed to `din`.
25
+ *
26
+ * `category`, `command` and `params` are the snake_case proto field names
27
+ * the unsigned `grpc_command` sends, so every command can be signed without
28
+ * per-command code. `delivery_channel` stays unset, as in the envelope
29
+ * proven on the cloud path.
30
+ * @throws TypeError for a field the message does not have, or an island mode that is not 1 or 6.
31
+ */
32
+ static envelope(din: string, category: string, command: string, params?: Record<string, unknown> | null): Uint8Array;
33
+ /**
34
+ * Wrap `envelope` in a signed `RoutableMessage` and serialize it.
35
+ *
36
+ * RSA-4096 blocks for milliseconds, so this signs on the thread pool.
37
+ * @param envelope The serialized `MessageEnvelope`.
38
+ * @param din The gateway DIN the envelope and the request target.
39
+ * @param options `now` (in seconds) and `messageUuid` exist for test vectors.
40
+ */
41
+ sign(envelope: Uint8Array, din: string, options?: {
42
+ now?: number;
43
+ messageUuid?: Uint8Array;
44
+ }): Promise<Uint8Array>;
45
+ }