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,188 @@
1
+ // src/powerwall/components.ts
2
+ /** Idle PCH string currents read as float noise around 1e-16 A. */
3
+ const PV_CURRENT_NOISE_FLOOR_A = .001;
4
+ const SERIAL_KINDS = [
5
+ "hvp",
6
+ "bms",
7
+ "pch",
8
+ "baggr"
9
+ ];
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ /** Walk nested objects and lists; `undefined` if a step is missing. */
14
+ function path(data, ...keys) {
15
+ let value = data;
16
+ for (const key of keys) if (typeof key === "number") value = Array.isArray(value) ? value[key] : void 0;
17
+ else value = isRecord(value) ? value[key] : void 0;
18
+ return value;
19
+ }
20
+ /** The `kind` component at `slot`, or `null`. */
21
+ function component(components, kind, slot = 0) {
22
+ const item = path(components, "components", kind, slot);
23
+ return isRecord(item) ? item : null;
24
+ }
25
+ /** One signal of a component: its `value`, else `textValue`, else `boolValue`, or `null` when none is set. */
26
+ function component_signal(components, kind, name, slot = 0) {
27
+ const signals = component(components, kind, slot)?.signals;
28
+ const signal = (Array.isArray(signals) ? signals : []).find((item) => isRecord(item) && item.name === name);
29
+ return signal?.value ?? signal?.textValue ?? signal?.boolValue ?? null;
30
+ }
31
+ /** A numeric signal, or `null` when it is missing or not a number. */
32
+ function component_float(components, kind, name, slot = 0) {
33
+ const value = component_signal(components, kind, name, slot);
34
+ return typeof value === "number" ? value : null;
35
+ }
36
+ /** PCH PV string current in amps, for a `string` from `A` to `F`. An idle string's noise reads as 0. */
37
+ function pv_string_current(components, string, slot = 0) {
38
+ const value = component_float(components, "pch", `PCH_PvCurrent${string}`, slot);
39
+ return value !== null && Math.abs(value) < PV_CURRENT_NOISE_FLOOR_A ? 0 : value;
40
+ }
41
+ function firstAcrossKinds(components, slot, field) {
42
+ for (const kind of SERIAL_KINDS) {
43
+ const value = path(components, "components", kind, slot, field);
44
+ if (typeof value === "string" && value) return value;
45
+ }
46
+ return null;
47
+ }
48
+ /** The first non-empty serial number reported for `slot`. */
49
+ function component_serial(components, slot) {
50
+ return firstAcrossKinds(components, slot, "serialNumber");
51
+ }
52
+ /** The first non-empty part number reported for `slot`. */
53
+ function component_part_number(components, slot) {
54
+ return firstAcrossKinds(components, slot, "partNumber");
55
+ }
56
+ function unitDin(item) {
57
+ for (const key of ["din", "vin"]) {
58
+ const value = item[key];
59
+ if (typeof value === "string" && value) return value;
60
+ }
61
+ return null;
62
+ }
63
+ function serialFromDin(din) {
64
+ return din.split("--").at(-1);
65
+ }
66
+ function bmsFull(components, slot) {
67
+ return component_float(components, "bms", "BMS_nominalFullPackEnergy", slot);
68
+ }
69
+ /** Component slots that look like real BMS modules. */
70
+ function bmsSlots(components) {
71
+ const items = path(components, "components", "bms");
72
+ if (!Array.isArray(items)) return [];
73
+ return items.flatMap((_, slot) => (bmsFull(components, slot) ?? 0) > 0 ? [slot] : []);
74
+ }
75
+ /**
76
+ * BMS slots after dropping phantom expansion rows. A gateway can keep a
77
+ * registered but uninstalled expansion: a plausible capacity, no serial and
78
+ * near-zero energy. Such rows are dropped only when the system aggregate already
79
+ * matches the kept rows, so a real pack with a stale reading stays.
80
+ */
81
+ function ghostFilteredBmsSlots(components, status) {
82
+ const slots = bmsSlots(components);
83
+ const aggregateWh = path(status, "control", "systemStatus", "nominalFullPackEnergyWh");
84
+ if (slots.length === 0 || typeof aggregateWh !== "number" || aggregateWh <= 0) return slots;
85
+ const aggregateKwh = aggregateWh / 1e3;
86
+ const ghosts = slots.filter((slot) => {
87
+ if (component_serial(components, slot)) return false;
88
+ const full = bmsFull(components, slot);
89
+ const remaining = component_float(components, "bms", "BMS_nominalEnergyRemaining", slot);
90
+ if (full === null || full <= 0 || remaining === null) return false;
91
+ return remaining < .5 || remaining / full < .05;
92
+ });
93
+ if (ghosts.length === 0) return slots;
94
+ const kept = slots.filter((slot) => !ghosts.includes(slot));
95
+ const keptKwh = kept.reduce((sum, slot) => sum + (bmsFull(components, slot) ?? 0), 0);
96
+ return keptKwh > 0 && Math.abs(aggregateKwh - keptKwh) / aggregateKwh < .1 ? kept : slots;
97
+ }
98
+ /**
99
+ * Choose the component slots of follower Powerwalls. A slot matched to a
100
+ * configured expansion is never chosen. Candidates with no serial win when
101
+ * there are enough of them; otherwise the last candidates are taken.
102
+ */
103
+ function followerSlots(components, powerwallCount, expansionSlots, slots) {
104
+ const followerCount = Math.max(0, powerwallCount - 1);
105
+ if (followerCount === 0) return [];
106
+ if (slots.length === 0) return Array.from({ length: followerCount }, (_, index) => index + 1);
107
+ const candidates = slots.filter((slot) => slot !== 0 && !expansionSlots.has(slot));
108
+ const noSerial = candidates.filter((slot) => !component_serial(components, slot));
109
+ const chosen = (noSerial.length >= followerCount ? noSerial : candidates).slice(-followerCount);
110
+ for (let fallback = 1; chosen.length < followerCount; fallback++) if (!chosen.includes(fallback) && !expansionSlots.has(fallback)) chosen.push(fallback);
111
+ return chosen.sort((a, b) => a - b);
112
+ }
113
+ function statusBatteryDins(status) {
114
+ const blocks = path(status, "control", "batteryBlocks");
115
+ if (!Array.isArray(blocks)) return [];
116
+ return blocks.filter(isRecord).flatMap((block) => unitDin(block) ?? []);
117
+ }
118
+ /**
119
+ * Every battery unit of the site with its component slot, by block: each
120
+ * Powerwall, then its expansions.
121
+ *
122
+ * Powerwalls come from `battery_blocks` plus any extra DIN in the status; the
123
+ * first is the leader at slot 0. Expansions come from each block's
124
+ * `battery_expansions` and match a slot by serial number. A configured expansion
125
+ * with no real BMS row is dropped, and a real row no configured unit claims
126
+ * becomes an expansion of the leader with `din: null`.
127
+ */
128
+ function battery_units(config, status, components) {
129
+ const configured = config.battery_blocks;
130
+ const blocks = Array.isArray(configured) ? configured.filter(isRecord) : [];
131
+ const known = new Set(blocks.map(unitDin));
132
+ for (const din of statusBatteryDins(status)) if (!known.has(din)) {
133
+ blocks.push({ din });
134
+ known.add(din);
135
+ }
136
+ if (blocks.length === 0) blocks.push({});
137
+ const powerwallCount = blocks.length;
138
+ const expansionDinsByBlock = blocks.map((block) => {
139
+ const expansions = block.battery_expansions;
140
+ return Array.isArray(expansions) ? expansions.filter(isRecord).flatMap((item) => unitDin(item) ?? []) : [];
141
+ });
142
+ const slots = ghostFilteredBmsSlots(components, status);
143
+ const serialToDin = /* @__PURE__ */ new Map();
144
+ for (const din of expansionDinsByBlock.flat()) {
145
+ const serial = serialFromDin(din);
146
+ if (serial) serialToDin.set(serial, din);
147
+ }
148
+ const matched = /* @__PURE__ */ new Map();
149
+ for (const slot of slots) {
150
+ const serial = component_serial(components, slot);
151
+ const din = serial ? serialToDin.get(serial) : void 0;
152
+ if (din !== void 0 && !matched.has(din)) matched.set(din, slot);
153
+ }
154
+ const followers = followerSlots(components, powerwallCount, new Set(matched.values()), slots);
155
+ const assigned = /* @__PURE__ */ new Set([
156
+ 0,
157
+ ...followers,
158
+ ...matched.values()
159
+ ]);
160
+ const unclaimed = slots.filter((slot) => !assigned.has(slot));
161
+ let nextUnobserved = Math.max(powerwallCount, slots.length);
162
+ const expansionsByBlock = expansionDinsByBlock.map((dins) => {
163
+ const expansions = [];
164
+ for (const din of dins) {
165
+ const slot = matched.get(din) ?? unclaimed.shift();
166
+ if (slot !== void 0) expansions.push([din, slot]);
167
+ else if (slots.length === 0) expansions.push([din, nextUnobserved++]);
168
+ }
169
+ return expansions;
170
+ });
171
+ expansionsByBlock[0].push(...unclaimed.map((slot) => [null, slot]));
172
+ const unit = (role, block_index, din, slot) => ({
173
+ role,
174
+ block_index,
175
+ component_slot: slot,
176
+ din,
177
+ serial_number: component_serial(components, slot),
178
+ part_number: component_part_number(components, slot)
179
+ });
180
+ return blocks.flatMap((block, index) => [unit(index === 0 ? "leader" : "follower", index, unitDin(block), index === 0 ? 0 : followers[index - 1] ?? index), ...expansionsByBlock[index].map(([din, slot]) => unit("expansion", index, din, slot))]);
181
+ }
182
+ exports.battery_units = battery_units;
183
+ exports.component = component;
184
+ exports.component_float = component_float;
185
+ exports.component_part_number = component_part_number;
186
+ exports.component_serial = component_serial;
187
+ exports.component_signal = component_signal;
188
+ exports.pv_string_current = pv_string_current;
@@ -0,0 +1,45 @@
1
+ import { ComponentsPayload, ConfigPayload, StatusPayload } from "./models.cjs";
2
+ export type SignalValue = number | string | boolean;
3
+ export type BatteryRole = "leader" | "follower" | "expansion";
4
+ /**
5
+ * One battery unit: a Powerwall or an expansion.
6
+ *
7
+ * `din` comes from `config.json` or the status, and is `null` for an expansion
8
+ * found only in the components payload. `component_slot` is the index in each
9
+ * `components[<kind>]` list, which can be past the end of them while the gateway
10
+ * returns no BMS rows. `block_index` is the owning Powerwall's position in
11
+ * `battery_blocks`.
12
+ */
13
+ export type BatteryUnit = {
14
+ role: BatteryRole;
15
+ block_index: number;
16
+ component_slot: number;
17
+ din: string | null;
18
+ serial_number: string | null;
19
+ part_number: string | null;
20
+ };
21
+ type Json = Record<string, any>;
22
+ /** The `kind` component at `slot`, or `null`. */
23
+ export declare function component(components: ComponentsPayload, kind: string, slot?: number): Json | null;
24
+ /** One signal of a component: its `value`, else `textValue`, else `boolValue`, or `null` when none is set. */
25
+ export declare function component_signal(components: ComponentsPayload, kind: string, name: string, slot?: number): SignalValue | null;
26
+ /** A numeric signal, or `null` when it is missing or not a number. */
27
+ export declare function component_float(components: ComponentsPayload, kind: string, name: string, slot?: number): number | null;
28
+ /** PCH PV string current in amps, for a `string` from `A` to `F`. An idle string's noise reads as 0. */
29
+ export declare function pv_string_current(components: ComponentsPayload, string: string, slot?: number): number | null;
30
+ /** The first non-empty serial number reported for `slot`. */
31
+ export declare function component_serial(components: ComponentsPayload, slot: number): string | null;
32
+ /** The first non-empty part number reported for `slot`. */
33
+ export declare function component_part_number(components: ComponentsPayload, slot: number): string | null;
34
+ /**
35
+ * Every battery unit of the site with its component slot, by block: each
36
+ * Powerwall, then its expansions.
37
+ *
38
+ * Powerwalls come from `battery_blocks` plus any extra DIN in the status; the
39
+ * first is the leader at slot 0. Expansions come from each block's
40
+ * `battery_expansions` and match a slot by serial number. A configured expansion
41
+ * with no real BMS row is dropped, and a real row no configured unit claims
42
+ * becomes an expansion of the leader with `din: null`.
43
+ */
44
+ export declare function battery_units(config: ConfigPayload, status: StatusPayload, components: ComponentsPayload): BatteryUnit[];
45
+ export {};
@@ -0,0 +1,45 @@
1
+ import { ComponentsPayload, ConfigPayload, StatusPayload } from "./models.mjs";
2
+ export type SignalValue = number | string | boolean;
3
+ export type BatteryRole = "leader" | "follower" | "expansion";
4
+ /**
5
+ * One battery unit: a Powerwall or an expansion.
6
+ *
7
+ * `din` comes from `config.json` or the status, and is `null` for an expansion
8
+ * found only in the components payload. `component_slot` is the index in each
9
+ * `components[<kind>]` list, which can be past the end of them while the gateway
10
+ * returns no BMS rows. `block_index` is the owning Powerwall's position in
11
+ * `battery_blocks`.
12
+ */
13
+ export type BatteryUnit = {
14
+ role: BatteryRole;
15
+ block_index: number;
16
+ component_slot: number;
17
+ din: string | null;
18
+ serial_number: string | null;
19
+ part_number: string | null;
20
+ };
21
+ type Json = Record<string, any>;
22
+ /** The `kind` component at `slot`, or `null`. */
23
+ export declare function component(components: ComponentsPayload, kind: string, slot?: number): Json | null;
24
+ /** One signal of a component: its `value`, else `textValue`, else `boolValue`, or `null` when none is set. */
25
+ export declare function component_signal(components: ComponentsPayload, kind: string, name: string, slot?: number): SignalValue | null;
26
+ /** A numeric signal, or `null` when it is missing or not a number. */
27
+ export declare function component_float(components: ComponentsPayload, kind: string, name: string, slot?: number): number | null;
28
+ /** PCH PV string current in amps, for a `string` from `A` to `F`. An idle string's noise reads as 0. */
29
+ export declare function pv_string_current(components: ComponentsPayload, string: string, slot?: number): number | null;
30
+ /** The first non-empty serial number reported for `slot`. */
31
+ export declare function component_serial(components: ComponentsPayload, slot: number): string | null;
32
+ /** The first non-empty part number reported for `slot`. */
33
+ export declare function component_part_number(components: ComponentsPayload, slot: number): string | null;
34
+ /**
35
+ * Every battery unit of the site with its component slot, by block: each
36
+ * Powerwall, then its expansions.
37
+ *
38
+ * Powerwalls come from `battery_blocks` plus any extra DIN in the status; the
39
+ * first is the leader at slot 0. Expansions come from each block's
40
+ * `battery_expansions` and match a slot by serial number. A configured expansion
41
+ * with no real BMS row is dropped, and a real row no configured unit claims
42
+ * becomes an expansion of the leader with `din: null`.
43
+ */
44
+ export declare function battery_units(config: ConfigPayload, status: StatusPayload, components: ComponentsPayload): BatteryUnit[];
45
+ export {};
@@ -0,0 +1,182 @@
1
+ // src/powerwall/components.ts
2
+ /** Idle PCH string currents read as float noise around 1e-16 A. */
3
+ const PV_CURRENT_NOISE_FLOOR_A = .001;
4
+ const SERIAL_KINDS = [
5
+ "hvp",
6
+ "bms",
7
+ "pch",
8
+ "baggr"
9
+ ];
10
+ function isRecord(value) {
11
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12
+ }
13
+ /** Walk nested objects and lists; `undefined` if a step is missing. */
14
+ function path(data, ...keys) {
15
+ let value = data;
16
+ for (const key of keys) if (typeof key === "number") value = Array.isArray(value) ? value[key] : void 0;
17
+ else value = isRecord(value) ? value[key] : void 0;
18
+ return value;
19
+ }
20
+ /** The `kind` component at `slot`, or `null`. */
21
+ function component(components, kind, slot = 0) {
22
+ const item = path(components, "components", kind, slot);
23
+ return isRecord(item) ? item : null;
24
+ }
25
+ /** One signal of a component: its `value`, else `textValue`, else `boolValue`, or `null` when none is set. */
26
+ function component_signal(components, kind, name, slot = 0) {
27
+ const signals = component(components, kind, slot)?.signals;
28
+ const signal = (Array.isArray(signals) ? signals : []).find((item) => isRecord(item) && item.name === name);
29
+ return signal?.value ?? signal?.textValue ?? signal?.boolValue ?? null;
30
+ }
31
+ /** A numeric signal, or `null` when it is missing or not a number. */
32
+ function component_float(components, kind, name, slot = 0) {
33
+ const value = component_signal(components, kind, name, slot);
34
+ return typeof value === "number" ? value : null;
35
+ }
36
+ /** PCH PV string current in amps, for a `string` from `A` to `F`. An idle string's noise reads as 0. */
37
+ function pv_string_current(components, string, slot = 0) {
38
+ const value = component_float(components, "pch", `PCH_PvCurrent${string}`, slot);
39
+ return value !== null && Math.abs(value) < PV_CURRENT_NOISE_FLOOR_A ? 0 : value;
40
+ }
41
+ function firstAcrossKinds(components, slot, field) {
42
+ for (const kind of SERIAL_KINDS) {
43
+ const value = path(components, "components", kind, slot, field);
44
+ if (typeof value === "string" && value) return value;
45
+ }
46
+ return null;
47
+ }
48
+ /** The first non-empty serial number reported for `slot`. */
49
+ function component_serial(components, slot) {
50
+ return firstAcrossKinds(components, slot, "serialNumber");
51
+ }
52
+ /** The first non-empty part number reported for `slot`. */
53
+ function component_part_number(components, slot) {
54
+ return firstAcrossKinds(components, slot, "partNumber");
55
+ }
56
+ function unitDin(item) {
57
+ for (const key of ["din", "vin"]) {
58
+ const value = item[key];
59
+ if (typeof value === "string" && value) return value;
60
+ }
61
+ return null;
62
+ }
63
+ function serialFromDin(din) {
64
+ return din.split("--").at(-1);
65
+ }
66
+ function bmsFull(components, slot) {
67
+ return component_float(components, "bms", "BMS_nominalFullPackEnergy", slot);
68
+ }
69
+ /** Component slots that look like real BMS modules. */
70
+ function bmsSlots(components) {
71
+ const items = path(components, "components", "bms");
72
+ if (!Array.isArray(items)) return [];
73
+ return items.flatMap((_, slot) => (bmsFull(components, slot) ?? 0) > 0 ? [slot] : []);
74
+ }
75
+ /**
76
+ * BMS slots after dropping phantom expansion rows. A gateway can keep a
77
+ * registered but uninstalled expansion: a plausible capacity, no serial and
78
+ * near-zero energy. Such rows are dropped only when the system aggregate already
79
+ * matches the kept rows, so a real pack with a stale reading stays.
80
+ */
81
+ function ghostFilteredBmsSlots(components, status) {
82
+ const slots = bmsSlots(components);
83
+ const aggregateWh = path(status, "control", "systemStatus", "nominalFullPackEnergyWh");
84
+ if (slots.length === 0 || typeof aggregateWh !== "number" || aggregateWh <= 0) return slots;
85
+ const aggregateKwh = aggregateWh / 1e3;
86
+ const ghosts = slots.filter((slot) => {
87
+ if (component_serial(components, slot)) return false;
88
+ const full = bmsFull(components, slot);
89
+ const remaining = component_float(components, "bms", "BMS_nominalEnergyRemaining", slot);
90
+ if (full === null || full <= 0 || remaining === null) return false;
91
+ return remaining < .5 || remaining / full < .05;
92
+ });
93
+ if (ghosts.length === 0) return slots;
94
+ const kept = slots.filter((slot) => !ghosts.includes(slot));
95
+ const keptKwh = kept.reduce((sum, slot) => sum + (bmsFull(components, slot) ?? 0), 0);
96
+ return keptKwh > 0 && Math.abs(aggregateKwh - keptKwh) / aggregateKwh < .1 ? kept : slots;
97
+ }
98
+ /**
99
+ * Choose the component slots of follower Powerwalls. A slot matched to a
100
+ * configured expansion is never chosen. Candidates with no serial win when
101
+ * there are enough of them; otherwise the last candidates are taken.
102
+ */
103
+ function followerSlots(components, powerwallCount, expansionSlots, slots) {
104
+ const followerCount = Math.max(0, powerwallCount - 1);
105
+ if (followerCount === 0) return [];
106
+ if (slots.length === 0) return Array.from({ length: followerCount }, (_, index) => index + 1);
107
+ const candidates = slots.filter((slot) => slot !== 0 && !expansionSlots.has(slot));
108
+ const noSerial = candidates.filter((slot) => !component_serial(components, slot));
109
+ const chosen = (noSerial.length >= followerCount ? noSerial : candidates).slice(-followerCount);
110
+ for (let fallback = 1; chosen.length < followerCount; fallback++) if (!chosen.includes(fallback) && !expansionSlots.has(fallback)) chosen.push(fallback);
111
+ return chosen.sort((a, b) => a - b);
112
+ }
113
+ function statusBatteryDins(status) {
114
+ const blocks = path(status, "control", "batteryBlocks");
115
+ if (!Array.isArray(blocks)) return [];
116
+ return blocks.filter(isRecord).flatMap((block) => unitDin(block) ?? []);
117
+ }
118
+ /**
119
+ * Every battery unit of the site with its component slot, by block: each
120
+ * Powerwall, then its expansions.
121
+ *
122
+ * Powerwalls come from `battery_blocks` plus any extra DIN in the status; the
123
+ * first is the leader at slot 0. Expansions come from each block's
124
+ * `battery_expansions` and match a slot by serial number. A configured expansion
125
+ * with no real BMS row is dropped, and a real row no configured unit claims
126
+ * becomes an expansion of the leader with `din: null`.
127
+ */
128
+ function battery_units(config, status, components) {
129
+ const configured = config.battery_blocks;
130
+ const blocks = Array.isArray(configured) ? configured.filter(isRecord) : [];
131
+ const known = new Set(blocks.map(unitDin));
132
+ for (const din of statusBatteryDins(status)) if (!known.has(din)) {
133
+ blocks.push({ din });
134
+ known.add(din);
135
+ }
136
+ if (blocks.length === 0) blocks.push({});
137
+ const powerwallCount = blocks.length;
138
+ const expansionDinsByBlock = blocks.map((block) => {
139
+ const expansions = block.battery_expansions;
140
+ return Array.isArray(expansions) ? expansions.filter(isRecord).flatMap((item) => unitDin(item) ?? []) : [];
141
+ });
142
+ const slots = ghostFilteredBmsSlots(components, status);
143
+ const serialToDin = /* @__PURE__ */ new Map();
144
+ for (const din of expansionDinsByBlock.flat()) {
145
+ const serial = serialFromDin(din);
146
+ if (serial) serialToDin.set(serial, din);
147
+ }
148
+ const matched = /* @__PURE__ */ new Map();
149
+ for (const slot of slots) {
150
+ const serial = component_serial(components, slot);
151
+ const din = serial ? serialToDin.get(serial) : void 0;
152
+ if (din !== void 0 && !matched.has(din)) matched.set(din, slot);
153
+ }
154
+ const followers = followerSlots(components, powerwallCount, new Set(matched.values()), slots);
155
+ const assigned = /* @__PURE__ */ new Set([
156
+ 0,
157
+ ...followers,
158
+ ...matched.values()
159
+ ]);
160
+ const unclaimed = slots.filter((slot) => !assigned.has(slot));
161
+ let nextUnobserved = Math.max(powerwallCount, slots.length);
162
+ const expansionsByBlock = expansionDinsByBlock.map((dins) => {
163
+ const expansions = [];
164
+ for (const din of dins) {
165
+ const slot = matched.get(din) ?? unclaimed.shift();
166
+ if (slot !== void 0) expansions.push([din, slot]);
167
+ else if (slots.length === 0) expansions.push([din, nextUnobserved++]);
168
+ }
169
+ return expansions;
170
+ });
171
+ expansionsByBlock[0].push(...unclaimed.map((slot) => [null, slot]));
172
+ const unit = (role, block_index, din, slot) => ({
173
+ role,
174
+ block_index,
175
+ component_slot: slot,
176
+ din,
177
+ serial_number: component_serial(components, slot),
178
+ part_number: component_part_number(components, slot)
179
+ });
180
+ return blocks.flatMap((block, index) => [unit(index === 0 ? "leader" : "follower", index, unitDin(block), index === 0 ? 0 : followers[index - 1] ?? index), ...expansionsByBlock[index].map(([din, slot]) => unit("expansion", index, din, slot))]);
181
+ }
182
+ export { battery_units, component, component_float, component_part_number, component_serial, component_signal, pv_string_current };
@@ -0,0 +1,216 @@
1
+ const require_const = require("../const.cjs");
2
+ const require_energyisland = require("../energyisland.cjs");
3
+ const require_energyparsers = require("../energyparsers.cjs");
4
+ const require_client = require("./client.cjs");
5
+ // src/powerwall/energysite.ts
6
+ /** Best-effort cloud `grid_status` and `island_status` for each gateway grid string. */
7
+ const GRID_STATUS = {
8
+ SystemGridConnected: {
9
+ grid_status: "Active",
10
+ island_status: "on_grid"
11
+ },
12
+ SystemIslandedActive: {
13
+ grid_status: "Inactive",
14
+ island_status: "off_grid"
15
+ },
16
+ SystemIslandedReady: {
17
+ grid_status: "Inactive",
18
+ island_status: "off_grid"
19
+ },
20
+ SystemTransitionToGrid: {
21
+ grid_status: "Active",
22
+ island_status: "on_grid"
23
+ },
24
+ SystemTransitionToIsland: {
25
+ grid_status: "Inactive",
26
+ island_status: "off_grid"
27
+ },
28
+ SystemMicroGridFaulted: {
29
+ grid_status: "Inactive",
30
+ island_status: "off_grid"
31
+ },
32
+ SystemWaitForUser: {
33
+ grid_status: "Inactive",
34
+ island_status: "island_status_unknown"
35
+ }
36
+ };
37
+ const GRID_STATUS_UNKNOWN = {
38
+ grid_status: "Unknown",
39
+ island_status: "island_status_unknown"
40
+ };
41
+ /** Grid-connected, the `mode` for `set_island_mode` that reconnects. */
42
+ const ISLAND_MODE_ON_GRID = require_const.EnergyIslandMode.ON_GRID;
43
+ /** Islanded, the `mode` for `set_island_mode` that goes off grid. */
44
+ const ISLAND_MODE_OFF_GRID = require_const.EnergyIslandMode.OFF_GRID;
45
+ function ok() {
46
+ return { response: {
47
+ code: 201,
48
+ message: "",
49
+ result: true
50
+ } };
51
+ }
52
+ function numberOrNull(value) {
53
+ return typeof value === "number" ? value : null;
54
+ }
55
+ /**
56
+ * The cloud energy site's surface over a local `PowerwallClient`: the same method
57
+ * names, arguments and reply shapes, so a router can prefer the LAN and fall back
58
+ * to the cloud. It imports nothing from the router.
59
+ *
60
+ * What has no local mapping is absent rather than stubbed, so a router routes it
61
+ * to the cloud: `site_info`, `storm_mode`, the histories and the rest of Python's
62
+ * `NotImplementedError` group. `add_authorized_client` is one of those, because
63
+ * registration goes through the cloud.
64
+ */
65
+ var PowerwallEnergySite = class {
66
+ #client;
67
+ /** @param client An existing client. This never opens its own transport. */
68
+ constructor(client) {
69
+ this.#client = client;
70
+ }
71
+ /** The wrapped client, for what has no cloud counterpart. */
72
+ get powerwall() {
73
+ return this.#client;
74
+ }
75
+ /**
76
+ * Set the operation mode.
77
+ * @param default_real_mode `self_consumption`, `autonomous` or `backup`.
78
+ */
79
+ async operation(default_real_mode) {
80
+ await this.#client.set_operation_mode(default_real_mode);
81
+ return ok();
82
+ }
83
+ /**
84
+ * Set the backup reserve.
85
+ * @param backup_reserve_percent On the app's 0-100 scale, as the cloud takes it.
86
+ */
87
+ async backup(backup_reserve_percent) {
88
+ await this.#client.set_backup_reserve(backup_reserve_percent);
89
+ return ok();
90
+ }
91
+ /** Set the grid-charging flag and export rule. Both in one call means one `config.json` write. */
92
+ async grid_import_export(disallow_charge_from_grid_with_solar_installed = null, customer_preferred_export_rule = null) {
93
+ await this.#client.set_grid_import_export({
94
+ customer_preferred_export_rule,
95
+ disallow_charge_from_grid_with_solar_installed
96
+ });
97
+ return ok();
98
+ }
99
+ /**
100
+ * Open or close the grid contactor. See `PowerwallClient.set_island_mode`:
101
+ * read `islanding.contactorClosed` afterwards.
102
+ * @param mode 6 islands and 1 reconnects. Anything else raises `TypeError`.
103
+ * @param force Defaults to true off grid and false otherwise, like the cloud.
104
+ */
105
+ async set_island_mode(mode, force) {
106
+ require_energyisland.validate_island_mode(mode, force);
107
+ const off_grid = mode === require_const.EnergyIslandMode.OFF_GRID;
108
+ await this.#client.set_island_mode({
109
+ off_grid,
110
+ force: force ?? off_grid,
111
+ mode_override: mode
112
+ });
113
+ return ok();
114
+ }
115
+ /** Disconnect from the grid. */
116
+ async go_off_grid() {
117
+ await this.#client.go_off_grid();
118
+ return ok();
119
+ }
120
+ /** Reconnect to the grid. */
121
+ async reconnect_grid() {
122
+ await this.#client.reconnect_grid();
123
+ return ok();
124
+ }
125
+ /**
126
+ * Schedule a manual backup event. `start_time` and `priority` exist for
127
+ * signature parity and are not honoured: the event always starts now at
128
+ * maximum priority.
129
+ * @param duration_seconds Defaults to 7200.
130
+ */
131
+ async schedule_backup_event(_start_time, duration_seconds, _priority) {
132
+ await this.#client.schedule_max_backup(duration_seconds ?? void 0);
133
+ return ok();
134
+ }
135
+ /** Cancel the active manual backup event. */
136
+ async cancel_backup_event() {
137
+ await this.#client.cancel_max_backup();
138
+ return ok();
139
+ }
140
+ /**
141
+ * Request a timed battery power setpoint.
142
+ * @param power_watts Positive discharges, negative charges.
143
+ * @param duration_seconds Whole seconds from 1 to 86400.
144
+ */
145
+ async dispatch_battery_power(power_watts, duration_seconds) {
146
+ await this.#client.dispatch_battery_power(power_watts, duration_seconds);
147
+ return ok();
148
+ }
149
+ /** Release a `dispatch_battery_power` setpoint. */
150
+ async resume_battery_operation() {
151
+ await this.#client.resume_battery_operation();
152
+ return ok();
153
+ }
154
+ /** The active manual backup and scheduled events, in the local shape: the cloud's gRPC reply cannot be reproduced. */
155
+ async get_backup_events() {
156
+ return { response: await this.#client.get_backup_events() };
157
+ }
158
+ /**
159
+ * A cloud-shaped `live_status`, from three sequential reads. Keys with no local
160
+ * source are `null`, never guessed. A merge with the cloud fills them in.
161
+ */
162
+ async live_status() {
163
+ const meters = await this.#client.get_meters_aggregates();
164
+ const status = await this.#client.get_status();
165
+ const gridStatus = await this.#client.get_grid_status();
166
+ const power = (location) => numberOrNull(meters[location]?.instant_power);
167
+ const systemStatus = status.control?.systemStatus;
168
+ return { response: {
169
+ solar_power: power("solar"),
170
+ energy_left: numberOrNull(systemStatus?.nominalEnergyRemainingWh),
171
+ total_pack_energy: numberOrNull(systemStatus?.nominalFullPackEnergyWh),
172
+ percentage_charged: require_client.battery_level(status),
173
+ backup_capable: null,
174
+ battery_power: power("battery"),
175
+ load_power: power("load"),
176
+ grid_power: power("site"),
177
+ grid_services_power: null,
178
+ generator_power: power("generator"),
179
+ ...Object.hasOwn(GRID_STATUS, gridStatus) ? GRID_STATUS[gridStatus] : GRID_STATUS_UNKNOWN,
180
+ grid_services_active: null,
181
+ storm_mode_active: null,
182
+ timestamp: null,
183
+ wall_connectors: null
184
+ } };
185
+ }
186
+ /** The reserve (on the app's scale) and mode from `config.json`. A bare object, unlike the cloud's reads. */
187
+ async local_config() {
188
+ const config = await this.#client.get_config();
189
+ const result = {};
190
+ const reserve = config.site_info?.backup_reserve_percent;
191
+ if (typeof reserve === "number") result.backup_reserve_percent = require_client.raw_to_scaled_reserve(reserve);
192
+ if (config.default_real_mode !== void 0) result.default_real_mode = config.default_real_mode;
193
+ return result;
194
+ }
195
+ /** The paired keys in the gateway's raw form. Prefer `find_authorized_clients`. */
196
+ async list_authorized_clients() {
197
+ return { response: await this.#client.list_authorized_clients() };
198
+ }
199
+ /** The paired keys, in the same typed form as `TeslemetryEnergySite.find_authorized_clients`. */
200
+ async find_authorized_clients() {
201
+ const { response } = await this.list_authorized_clients();
202
+ return require_energyparsers.parse_authorized_clients(response);
203
+ }
204
+ /**
205
+ * Revoke a paired key. Any paired key can remove any other, and removing the
206
+ * signing key locks this client out.
207
+ * @param public_key Raw DER bytes, or base64 as `list_authorized_clients` reports it.
208
+ */
209
+ async remove_authorized_client(public_key) {
210
+ await this.#client.remove_authorized_client(public_key);
211
+ return ok();
212
+ }
213
+ };
214
+ exports.ISLAND_MODE_OFF_GRID = ISLAND_MODE_OFF_GRID;
215
+ exports.ISLAND_MODE_ON_GRID = ISLAND_MODE_ON_GRID;
216
+ exports.PowerwallEnergySite = PowerwallEnergySite;