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,102 @@
1
+ const require_energyparsers = require("../energyparsers.cjs");
2
+ const require_client = require("./client.cjs");
3
+ const require_exceptions = require("./exceptions.cjs");
4
+ // src/powerwall/gateway.ts
5
+ function createClient(options, legacy_gateway) {
6
+ return legacy_gateway ? require_client.PowerwallClient.legacy_gateway(options) : new require_client.PowerwallClient(options);
7
+ }
8
+ /**
9
+ * Connect at the stored host, and if that fails ask the cloud where the gateway
10
+ * is. Any error is a lead, because another device may have taken the address.
11
+ *
12
+ * Certificate checks are off and a login sends the password, so the reported
13
+ * address is not trusted: anything outside a private or link-local range gets no
14
+ * request at all. A gateway whose DIN is not the expected one is refused after
15
+ * login, because the DIN read needs a token.
16
+ *
17
+ * Every outcome returns a client, so the caller still builds the router. A stale
18
+ * client's calls fail with `PowerwallConnectionError` and go to the cloud, so a
19
+ * site degrades to cloud control and does not break.
20
+ *
21
+ * @throws PowerwallAuthenticationError for a bad PEM, which no address can repair.
22
+ */
23
+ async function connect_gateway(options) {
24
+ const { cloud, legacy_gateway, allow_address = require_energyparsers.is_lan_address } = options;
25
+ const clientOptions = {
26
+ ...options.client,
27
+ expectedDin: options.client.expectedDin ?? cloud.din
28
+ };
29
+ const stored = clientOptions.host ?? "192.168.91.1";
30
+ const client = createClient(clientOptions, legacy_gateway);
31
+ let error;
32
+ try {
33
+ await client.connect();
34
+ return {
35
+ client,
36
+ host: stored,
37
+ outcome: "connected"
38
+ };
39
+ } catch (caught) {
40
+ if (!(caught instanceof require_exceptions.PowerwallError)) throw caught;
41
+ if (caught instanceof require_exceptions.PowerwallRateLimitError) return {
42
+ client,
43
+ host: stored,
44
+ outcome: "rate_limited",
45
+ error: caught
46
+ };
47
+ error = caught;
48
+ }
49
+ let address = null;
50
+ try {
51
+ address = await cloud.find_gateway_address();
52
+ } catch {}
53
+ if (address === stored && !(error instanceof require_exceptions.PowerwallConnectionError)) return {
54
+ client,
55
+ host: stored,
56
+ outcome: "refused",
57
+ error
58
+ };
59
+ if (address != null && address !== stored && allow_address(address)) {
60
+ const moved = createClient({
61
+ ...clientOptions,
62
+ host: address
63
+ }, legacy_gateway);
64
+ try {
65
+ await moved.connect();
66
+ client.close();
67
+ return {
68
+ client: moved,
69
+ host: address,
70
+ outcome: "moved"
71
+ };
72
+ } catch (caught) {
73
+ moved.close();
74
+ if (!(caught instanceof require_exceptions.PowerwallError)) throw caught;
75
+ }
76
+ }
77
+ return {
78
+ client,
79
+ host: stored,
80
+ outcome: "not_found",
81
+ error
82
+ };
83
+ }
84
+ /**
85
+ * Prove a stored password and key against the gateway. A login proves the
86
+ * password and a signed read proves the key: they are independent.
87
+ *
88
+ * @throws PowerwallAuthenticationError when the password is wrong.
89
+ * @throws PowerwallKeyRejectedError when the password worked and the key did not.
90
+ * @throws Any other `PowerwallError` when the gateway cannot be reached.
91
+ */
92
+ async function verify_local_gateway(client) {
93
+ await client.connect();
94
+ try {
95
+ await client.get_status();
96
+ } catch (error) {
97
+ if (error instanceof require_exceptions.PowerwallAuthenticationError) throw new require_exceptions.PowerwallKeyRejectedError("The gateway accepted the password but not the signing key", { cause: error });
98
+ throw error;
99
+ }
100
+ }
101
+ exports.connect_gateway = connect_gateway;
102
+ exports.verify_local_gateway = verify_local_gateway;
@@ -0,0 +1,57 @@
1
+ import { PowerwallClient, PowerwallClientOptions } from "./client.cjs";
2
+ import { PowerwallError } from "./exceptions.cjs";
3
+ export type GatewayOutcome =
4
+ /** Connected at the stored host. */
5
+ "connected" |
6
+ /** The gateway answered 429 or 503: it is there, so there is nothing to repair. */
7
+ "rate_limited" |
8
+ /** Connected at a new host. The caller must persist `host`: nothing here does. */
9
+ "moved" |
10
+ /** The cloud confirms this address and the gateway refused us. A wrong password lands here. */
11
+ "refused" |
12
+ /** No usable address. The caller should offer a repair. */
13
+ "not_found";
14
+ export type ConnectGatewayOptions = {
15
+ client: PowerwallClientOptions;
16
+ /** A cloud site, or anything that can report the gateway's address. Its `din`, when set, is the DIN to expect. */
17
+ cloud: {
18
+ find_gateway_address(): Promise<string | null>;
19
+ din?: string;
20
+ };
21
+ /** Use `PowerwallClient.legacy_gateway` for an early Gateway 1 unit. */
22
+ legacy_gateway?: boolean;
23
+ /** Which reported addresses may receive the password. Defaults to private and link-local IPv4. */
24
+ allow_address?: (address: string) => boolean;
25
+ };
26
+ export type ConnectGatewayResult = {
27
+ client: PowerwallClient;
28
+ host: string;
29
+ outcome: GatewayOutcome;
30
+ /** Why the stored host did not connect. Absent for `connected` and `moved`. */
31
+ error?: PowerwallError;
32
+ };
33
+ /**
34
+ * Connect at the stored host, and if that fails ask the cloud where the gateway
35
+ * is. Any error is a lead, because another device may have taken the address.
36
+ *
37
+ * Certificate checks are off and a login sends the password, so the reported
38
+ * address is not trusted: anything outside a private or link-local range gets no
39
+ * request at all. A gateway whose DIN is not the expected one is refused after
40
+ * login, because the DIN read needs a token.
41
+ *
42
+ * Every outcome returns a client, so the caller still builds the router. A stale
43
+ * client's calls fail with `PowerwallConnectionError` and go to the cloud, so a
44
+ * site degrades to cloud control and does not break.
45
+ *
46
+ * @throws PowerwallAuthenticationError for a bad PEM, which no address can repair.
47
+ */
48
+ export declare function connect_gateway(options: ConnectGatewayOptions): Promise<ConnectGatewayResult>;
49
+ /**
50
+ * Prove a stored password and key against the gateway. A login proves the
51
+ * password and a signed read proves the key: they are independent.
52
+ *
53
+ * @throws PowerwallAuthenticationError when the password is wrong.
54
+ * @throws PowerwallKeyRejectedError when the password worked and the key did not.
55
+ * @throws Any other `PowerwallError` when the gateway cannot be reached.
56
+ */
57
+ export declare function verify_local_gateway(client: PowerwallClient): Promise<void>;
@@ -0,0 +1,57 @@
1
+ import { PowerwallClient, PowerwallClientOptions } from "./client.mjs";
2
+ import { PowerwallError } from "./exceptions.mjs";
3
+ export type GatewayOutcome =
4
+ /** Connected at the stored host. */
5
+ "connected" |
6
+ /** The gateway answered 429 or 503: it is there, so there is nothing to repair. */
7
+ "rate_limited" |
8
+ /** Connected at a new host. The caller must persist `host`: nothing here does. */
9
+ "moved" |
10
+ /** The cloud confirms this address and the gateway refused us. A wrong password lands here. */
11
+ "refused" |
12
+ /** No usable address. The caller should offer a repair. */
13
+ "not_found";
14
+ export type ConnectGatewayOptions = {
15
+ client: PowerwallClientOptions;
16
+ /** A cloud site, or anything that can report the gateway's address. Its `din`, when set, is the DIN to expect. */
17
+ cloud: {
18
+ find_gateway_address(): Promise<string | null>;
19
+ din?: string;
20
+ };
21
+ /** Use `PowerwallClient.legacy_gateway` for an early Gateway 1 unit. */
22
+ legacy_gateway?: boolean;
23
+ /** Which reported addresses may receive the password. Defaults to private and link-local IPv4. */
24
+ allow_address?: (address: string) => boolean;
25
+ };
26
+ export type ConnectGatewayResult = {
27
+ client: PowerwallClient;
28
+ host: string;
29
+ outcome: GatewayOutcome;
30
+ /** Why the stored host did not connect. Absent for `connected` and `moved`. */
31
+ error?: PowerwallError;
32
+ };
33
+ /**
34
+ * Connect at the stored host, and if that fails ask the cloud where the gateway
35
+ * is. Any error is a lead, because another device may have taken the address.
36
+ *
37
+ * Certificate checks are off and a login sends the password, so the reported
38
+ * address is not trusted: anything outside a private or link-local range gets no
39
+ * request at all. A gateway whose DIN is not the expected one is refused after
40
+ * login, because the DIN read needs a token.
41
+ *
42
+ * Every outcome returns a client, so the caller still builds the router. A stale
43
+ * client's calls fail with `PowerwallConnectionError` and go to the cloud, so a
44
+ * site degrades to cloud control and does not break.
45
+ *
46
+ * @throws PowerwallAuthenticationError for a bad PEM, which no address can repair.
47
+ */
48
+ export declare function connect_gateway(options: ConnectGatewayOptions): Promise<ConnectGatewayResult>;
49
+ /**
50
+ * Prove a stored password and key against the gateway. A login proves the
51
+ * password and a signed read proves the key: they are independent.
52
+ *
53
+ * @throws PowerwallAuthenticationError when the password is wrong.
54
+ * @throws PowerwallKeyRejectedError when the password worked and the key did not.
55
+ * @throws Any other `PowerwallError` when the gateway cannot be reached.
56
+ */
57
+ export declare function verify_local_gateway(client: PowerwallClient): Promise<void>;
@@ -0,0 +1,101 @@
1
+ import { is_lan_address } from "../energyparsers.mjs";
2
+ import { PowerwallClient } from "./client.mjs";
3
+ import { PowerwallAuthenticationError, PowerwallConnectionError, PowerwallError, PowerwallKeyRejectedError, PowerwallRateLimitError } from "./exceptions.mjs";
4
+ // src/powerwall/gateway.ts
5
+ function createClient(options, legacy_gateway) {
6
+ return legacy_gateway ? PowerwallClient.legacy_gateway(options) : new PowerwallClient(options);
7
+ }
8
+ /**
9
+ * Connect at the stored host, and if that fails ask the cloud where the gateway
10
+ * is. Any error is a lead, because another device may have taken the address.
11
+ *
12
+ * Certificate checks are off and a login sends the password, so the reported
13
+ * address is not trusted: anything outside a private or link-local range gets no
14
+ * request at all. A gateway whose DIN is not the expected one is refused after
15
+ * login, because the DIN read needs a token.
16
+ *
17
+ * Every outcome returns a client, so the caller still builds the router. A stale
18
+ * client's calls fail with `PowerwallConnectionError` and go to the cloud, so a
19
+ * site degrades to cloud control and does not break.
20
+ *
21
+ * @throws PowerwallAuthenticationError for a bad PEM, which no address can repair.
22
+ */
23
+ async function connect_gateway(options) {
24
+ const { cloud, legacy_gateway, allow_address = is_lan_address } = options;
25
+ const clientOptions = {
26
+ ...options.client,
27
+ expectedDin: options.client.expectedDin ?? cloud.din
28
+ };
29
+ const stored = clientOptions.host ?? "192.168.91.1";
30
+ const client = createClient(clientOptions, legacy_gateway);
31
+ let error;
32
+ try {
33
+ await client.connect();
34
+ return {
35
+ client,
36
+ host: stored,
37
+ outcome: "connected"
38
+ };
39
+ } catch (caught) {
40
+ if (!(caught instanceof PowerwallError)) throw caught;
41
+ if (caught instanceof PowerwallRateLimitError) return {
42
+ client,
43
+ host: stored,
44
+ outcome: "rate_limited",
45
+ error: caught
46
+ };
47
+ error = caught;
48
+ }
49
+ let address = null;
50
+ try {
51
+ address = await cloud.find_gateway_address();
52
+ } catch {}
53
+ if (address === stored && !(error instanceof PowerwallConnectionError)) return {
54
+ client,
55
+ host: stored,
56
+ outcome: "refused",
57
+ error
58
+ };
59
+ if (address != null && address !== stored && allow_address(address)) {
60
+ const moved = createClient({
61
+ ...clientOptions,
62
+ host: address
63
+ }, legacy_gateway);
64
+ try {
65
+ await moved.connect();
66
+ client.close();
67
+ return {
68
+ client: moved,
69
+ host: address,
70
+ outcome: "moved"
71
+ };
72
+ } catch (caught) {
73
+ moved.close();
74
+ if (!(caught instanceof PowerwallError)) throw caught;
75
+ }
76
+ }
77
+ return {
78
+ client,
79
+ host: stored,
80
+ outcome: "not_found",
81
+ error
82
+ };
83
+ }
84
+ /**
85
+ * Prove a stored password and key against the gateway. A login proves the
86
+ * password and a signed read proves the key: they are independent.
87
+ *
88
+ * @throws PowerwallAuthenticationError when the password is wrong.
89
+ * @throws PowerwallKeyRejectedError when the password worked and the key did not.
90
+ * @throws Any other `PowerwallError` when the gateway cannot be reached.
91
+ */
92
+ async function verify_local_gateway(client) {
93
+ await client.connect();
94
+ try {
95
+ await client.get_status();
96
+ } catch (error) {
97
+ if (error instanceof PowerwallAuthenticationError) throw new PowerwallKeyRejectedError("The gateway accepted the password but not the signing key", { cause: error });
98
+ throw error;
99
+ }
100
+ }
101
+ export { connect_gateway, verify_local_gateway };
@@ -0,0 +1,163 @@
1
+ const require_energysite = require("../router/energysite.cjs");
2
+ const require_exceptions = require("./exceptions.cjs");
3
+ // src/powerwall/merge.ts
4
+ /** How often to call `LiveStatusMerge.poll()`. */
5
+ const ENERGY_LIVE_INTERVAL_MS = 5e3;
6
+ /** The longest `LiveStatusMerge` waits between local attempts after failures. */
7
+ const ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS = 6e4;
8
+ /** How often to call `SiteInfoMerge.poll()`. */
9
+ const ENERGY_CONFIG_INTERVAL_MS = 3e4;
10
+ const MAX_BACKOFF_TICKS = ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS / ENERGY_LIVE_INTERVAL_MS;
11
+ /**
12
+ * Keeps the latest cloud `live_status` and local reading, and re-merges on either
13
+ * update. The cloud never clobbers a healthy local key, and a failed poll clears
14
+ * the local reading, so everything falls back to cloud values rather than to
15
+ * unavailable. All documents are unwrapped: the value under `response`.
16
+ *
17
+ * A failed poll never changes availability by itself. `poll()` resolves on a
18
+ * `PowerwallError` and rejects on anything else, which is a bug, so attach a
19
+ * rejection handler.
20
+ */
21
+ var LiveStatusMerge = class {
22
+ #site;
23
+ #owned_keys;
24
+ #cloud;
25
+ #local = null;
26
+ #healthy = null;
27
+ #last_error = null;
28
+ #polling = false;
29
+ #backoff_ticks = 1;
30
+ #ticks_to_skip = 0;
31
+ constructor(local, cloud, options = {}) {
32
+ this.#site = local;
33
+ this.#cloud = cloud;
34
+ this.#owned_keys = options.owned_keys ?? require_energysite.LOCAL_LIVE_STATUS_KEYS;
35
+ }
36
+ /** The cloud document with the local reading overlaid. */
37
+ get merged() {
38
+ return require_energysite.merge_local_into_cloud(this.#cloud, this.#local, this.#owned_keys);
39
+ }
40
+ /** True after a successful poll, false after a failed one, null before the first. */
41
+ get healthy() {
42
+ return this.#healthy;
43
+ }
44
+ /** The error from the last poll, or null after a success. */
45
+ get last_error() {
46
+ return this.#last_error;
47
+ }
48
+ /** Store a cloud document and return the merged view. */
49
+ set_cloud(cloud) {
50
+ this.#cloud = cloud;
51
+ return this.merged;
52
+ }
53
+ /** One 5 s tick. Returns the merged view, or null when the tick was skipped. */
54
+ async poll() {
55
+ if (this.#polling) return null;
56
+ if (this.#ticks_to_skip > 0) {
57
+ this.#ticks_to_skip--;
58
+ return null;
59
+ }
60
+ this.#polling = true;
61
+ try {
62
+ this.#local = (await this.#site.live_status()).response;
63
+ this.#backoff_ticks = 1;
64
+ this.#healthy = true;
65
+ this.#last_error = null;
66
+ } catch (error) {
67
+ if (!(error instanceof require_exceptions.PowerwallError)) throw error;
68
+ this.#local = null;
69
+ this.#backoff_ticks = Math.min(this.#backoff_ticks * 2, MAX_BACKOFF_TICKS);
70
+ this.#ticks_to_skip = this.#backoff_ticks - 1;
71
+ this.#healthy = false;
72
+ this.#last_error = error;
73
+ } finally {
74
+ this.#polling = false;
75
+ }
76
+ return this.merged;
77
+ }
78
+ };
79
+ /**
80
+ * Keeps the latest cloud `site_info` and local `config.json` view, and holds a
81
+ * command's value until its source next reports it, so neither a poll that
82
+ * straddles the command nor a failed poll reverts what the user just set. All
83
+ * documents are unwrapped: the value under `response`.
84
+ *
85
+ * `poll()` resolves on a `PowerwallError` and rejects on anything else, which is a
86
+ * bug, so attach a rejection handler.
87
+ */
88
+ var SiteInfoMerge = class {
89
+ #site;
90
+ #owned_keys;
91
+ #cloud;
92
+ #local = null;
93
+ #held = {};
94
+ #generation = 0;
95
+ #healthy = null;
96
+ #last_error = null;
97
+ #polling = false;
98
+ constructor(local, cloud, options = {}) {
99
+ this.#site = local;
100
+ this.#cloud = cloud;
101
+ this.#owned_keys = options.owned_keys ?? require_energysite.LOCAL_SITE_INFO_KEYS;
102
+ }
103
+ /** The cloud document with held values, then the local view, overlaid. */
104
+ get merged() {
105
+ return require_energysite.merge_local_into_cloud({
106
+ ...this.#cloud,
107
+ ...this.#held
108
+ }, this.#local, this.#owned_keys);
109
+ }
110
+ /** True after a successful read, false after a failed one, null before the first. */
111
+ get healthy() {
112
+ return this.#healthy;
113
+ }
114
+ /** The error from the last read, or null after a success. */
115
+ get last_error() {
116
+ return this.#last_error;
117
+ }
118
+ /** Store a cloud document, which reports every held value, and return the merged view. */
119
+ set_cloud(cloud) {
120
+ this.#cloud = cloud;
121
+ this.#held = {};
122
+ return this.merged;
123
+ }
124
+ /** One 30 s tick. Returns the merged view, or null when the tick was skipped or its read discarded. */
125
+ async poll() {
126
+ if (this.#polling) return null;
127
+ const generation = this.#generation;
128
+ this.#polling = true;
129
+ let local = null;
130
+ try {
131
+ local = await this.#site.local_config();
132
+ this.#healthy = true;
133
+ this.#last_error = null;
134
+ } catch (error) {
135
+ if (!(error instanceof require_exceptions.PowerwallError)) throw error;
136
+ this.#healthy = false;
137
+ this.#last_error = error;
138
+ } finally {
139
+ this.#polling = false;
140
+ }
141
+ if (generation !== this.#generation) return null;
142
+ this.#local = local;
143
+ for (const key of this.#owned_keys) if (local?.[key] !== void 0 && local[key] !== null) delete this.#held[key];
144
+ return this.merged;
145
+ }
146
+ /** Hold a successful command's value until its source next reports it. */
147
+ hold_command_value(key, value) {
148
+ if (this.#owned_keys.includes(key)) {
149
+ this.#local = {
150
+ ...this.#local,
151
+ [key]: value
152
+ };
153
+ this.#generation++;
154
+ }
155
+ this.#held[key] = value;
156
+ return this.merged;
157
+ }
158
+ };
159
+ exports.ENERGY_CONFIG_INTERVAL_MS = ENERGY_CONFIG_INTERVAL_MS;
160
+ exports.ENERGY_LIVE_INTERVAL_MS = ENERGY_LIVE_INTERVAL_MS;
161
+ exports.ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS = ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS;
162
+ exports.LiveStatusMerge = LiveStatusMerge;
163
+ exports.SiteInfoMerge = SiteInfoMerge;
@@ -0,0 +1,63 @@
1
+ import { LiveStatusResponse } from "../types/live_status.cjs";
2
+ import { SiteInfoResponse } from "../types/site_info.cjs";
3
+ import { PowerwallEnergySite } from "./energysite.cjs";
4
+ import { PowerwallError } from "./exceptions.cjs";
5
+ /** How often to call `LiveStatusMerge.poll()`. */
6
+ export declare const ENERGY_LIVE_INTERVAL_MS = 5000;
7
+ /** The longest `LiveStatusMerge` waits between local attempts after failures. */
8
+ export declare const ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS = 60000;
9
+ /** How often to call `SiteInfoMerge.poll()`. */
10
+ export declare const ENERGY_CONFIG_INTERVAL_MS = 30000;
11
+ export type MergeOptions = {
12
+ /** The keys local overlays. Homey passes the live list without `island_status`. */
13
+ owned_keys?: readonly string[];
14
+ };
15
+ /**
16
+ * Keeps the latest cloud `live_status` and local reading, and re-merges on either
17
+ * update. The cloud never clobbers a healthy local key, and a failed poll clears
18
+ * the local reading, so everything falls back to cloud values rather than to
19
+ * unavailable. All documents are unwrapped: the value under `response`.
20
+ *
21
+ * A failed poll never changes availability by itself. `poll()` resolves on a
22
+ * `PowerwallError` and rejects on anything else, which is a bug, so attach a
23
+ * rejection handler.
24
+ */
25
+ export declare class LiveStatusMerge {
26
+ #private;
27
+ constructor(local: Pick<PowerwallEnergySite, "live_status">, cloud: LiveStatusResponse, options?: MergeOptions);
28
+ /** The cloud document with the local reading overlaid. */
29
+ get merged(): LiveStatusResponse;
30
+ /** True after a successful poll, false after a failed one, null before the first. */
31
+ get healthy(): boolean | null;
32
+ /** The error from the last poll, or null after a success. */
33
+ get last_error(): PowerwallError | null;
34
+ /** Store a cloud document and return the merged view. */
35
+ set_cloud(cloud: LiveStatusResponse): LiveStatusResponse;
36
+ /** One 5 s tick. Returns the merged view, or null when the tick was skipped. */
37
+ poll(): Promise<LiveStatusResponse | null>;
38
+ }
39
+ /**
40
+ * Keeps the latest cloud `site_info` and local `config.json` view, and holds a
41
+ * command's value until its source next reports it, so neither a poll that
42
+ * straddles the command nor a failed poll reverts what the user just set. All
43
+ * documents are unwrapped: the value under `response`.
44
+ *
45
+ * `poll()` resolves on a `PowerwallError` and rejects on anything else, which is a
46
+ * bug, so attach a rejection handler.
47
+ */
48
+ export declare class SiteInfoMerge {
49
+ #private;
50
+ constructor(local: Pick<PowerwallEnergySite, "local_config">, cloud: SiteInfoResponse, options?: MergeOptions);
51
+ /** The cloud document with held values, then the local view, overlaid. */
52
+ get merged(): SiteInfoResponse;
53
+ /** True after a successful read, false after a failed one, null before the first. */
54
+ get healthy(): boolean | null;
55
+ /** The error from the last read, or null after a success. */
56
+ get last_error(): PowerwallError | null;
57
+ /** Store a cloud document, which reports every held value, and return the merged view. */
58
+ set_cloud(cloud: SiteInfoResponse): SiteInfoResponse;
59
+ /** One 30 s tick. Returns the merged view, or null when the tick was skipped or its read discarded. */
60
+ poll(): Promise<SiteInfoResponse | null>;
61
+ /** Hold a successful command's value until its source next reports it. */
62
+ hold_command_value(key: string, value: unknown): SiteInfoResponse;
63
+ }
@@ -0,0 +1,63 @@
1
+ import { LiveStatusResponse } from "../types/live_status.mjs";
2
+ import { SiteInfoResponse } from "../types/site_info.mjs";
3
+ import { PowerwallEnergySite } from "./energysite.mjs";
4
+ import { PowerwallError } from "./exceptions.mjs";
5
+ /** How often to call `LiveStatusMerge.poll()`. */
6
+ export declare const ENERGY_LIVE_INTERVAL_MS = 5000;
7
+ /** The longest `LiveStatusMerge` waits between local attempts after failures. */
8
+ export declare const ENERGY_LIVE_LOCAL_MAX_BACKOFF_MS = 60000;
9
+ /** How often to call `SiteInfoMerge.poll()`. */
10
+ export declare const ENERGY_CONFIG_INTERVAL_MS = 30000;
11
+ export type MergeOptions = {
12
+ /** The keys local overlays. Homey passes the live list without `island_status`. */
13
+ owned_keys?: readonly string[];
14
+ };
15
+ /**
16
+ * Keeps the latest cloud `live_status` and local reading, and re-merges on either
17
+ * update. The cloud never clobbers a healthy local key, and a failed poll clears
18
+ * the local reading, so everything falls back to cloud values rather than to
19
+ * unavailable. All documents are unwrapped: the value under `response`.
20
+ *
21
+ * A failed poll never changes availability by itself. `poll()` resolves on a
22
+ * `PowerwallError` and rejects on anything else, which is a bug, so attach a
23
+ * rejection handler.
24
+ */
25
+ export declare class LiveStatusMerge {
26
+ #private;
27
+ constructor(local: Pick<PowerwallEnergySite, "live_status">, cloud: LiveStatusResponse, options?: MergeOptions);
28
+ /** The cloud document with the local reading overlaid. */
29
+ get merged(): LiveStatusResponse;
30
+ /** True after a successful poll, false after a failed one, null before the first. */
31
+ get healthy(): boolean | null;
32
+ /** The error from the last poll, or null after a success. */
33
+ get last_error(): PowerwallError | null;
34
+ /** Store a cloud document and return the merged view. */
35
+ set_cloud(cloud: LiveStatusResponse): LiveStatusResponse;
36
+ /** One 5 s tick. Returns the merged view, or null when the tick was skipped. */
37
+ poll(): Promise<LiveStatusResponse | null>;
38
+ }
39
+ /**
40
+ * Keeps the latest cloud `site_info` and local `config.json` view, and holds a
41
+ * command's value until its source next reports it, so neither a poll that
42
+ * straddles the command nor a failed poll reverts what the user just set. All
43
+ * documents are unwrapped: the value under `response`.
44
+ *
45
+ * `poll()` resolves on a `PowerwallError` and rejects on anything else, which is a
46
+ * bug, so attach a rejection handler.
47
+ */
48
+ export declare class SiteInfoMerge {
49
+ #private;
50
+ constructor(local: Pick<PowerwallEnergySite, "local_config">, cloud: SiteInfoResponse, options?: MergeOptions);
51
+ /** The cloud document with held values, then the local view, overlaid. */
52
+ get merged(): SiteInfoResponse;
53
+ /** True after a successful read, false after a failed one, null before the first. */
54
+ get healthy(): boolean | null;
55
+ /** The error from the last read, or null after a success. */
56
+ get last_error(): PowerwallError | null;
57
+ /** Store a cloud document, which reports every held value, and return the merged view. */
58
+ set_cloud(cloud: SiteInfoResponse): SiteInfoResponse;
59
+ /** One 30 s tick. Returns the merged view, or null when the tick was skipped or its read discarded. */
60
+ poll(): Promise<SiteInfoResponse | null>;
61
+ /** Hold a successful command's value until its source next reports it. */
62
+ hold_command_value(key: string, value: unknown): SiteInfoResponse;
63
+ }