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,319 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_exceptions = require("./exceptions.cjs");
3
+ // src/tariff.ts
4
+ function isObject(value) {
5
+ return typeof value === "object" && value !== null && !Array.isArray(value);
6
+ }
7
+ /**
8
+ * Extract the `tariff_content_v2` object from a raw API response.
9
+ *
10
+ * Accepts the `site_info` read envelope (`{ response: { tariff_content_v2 } }`), the
11
+ * `time_of_use_settings` write envelope (`{ tou_settings: { tariff_content_v2 } }`), or the bare
12
+ * `tariff_content_v2` object itself. Throws {@link InvalidResponse} for a null body, an
13
+ * unrecognized envelope, or an extracted object without the minimal Tariff V2 shape (`seasons`
14
+ * and `energy_charges`) - checked identically on every path, since a malformed body (including an
15
+ * empty `{}` nested under a recognized envelope) is not the same as "no tariff configured".
16
+ */
17
+ function unwrapTariffV2(response) {
18
+ if (response == null) throw new require_exceptions.InvalidResponse("tariff response body was null");
19
+ if (!isObject(response)) throw new require_exceptions.InvalidResponse(String(response));
20
+ let candidate;
21
+ if ("tariff_content_v2" in response) candidate = response.tariff_content_v2;
22
+ else if ("response" in response || "tou_settings" in response) {
23
+ const inner = "response" in response ? response.response : response.tou_settings;
24
+ candidate = isObject(inner) ? inner.tariff_content_v2 : void 0;
25
+ } else candidate = response;
26
+ if (isObject(candidate) && "seasons" in candidate && "energy_charges" in candidate) return candidate;
27
+ throw new require_exceptions.InvalidResponse(response);
28
+ }
29
+ const MINUTES_PER_DAY = 1440;
30
+ function formatParts(date, timeZone) {
31
+ const parts = new Intl.DateTimeFormat("en-US", {
32
+ timeZone,
33
+ year: "numeric",
34
+ month: "2-digit",
35
+ day: "2-digit",
36
+ hour: "2-digit",
37
+ minute: "2-digit",
38
+ second: "2-digit",
39
+ hourCycle: "h23"
40
+ }).formatToParts(date);
41
+ const out = {};
42
+ for (const p of parts) out[p.type] = Number(p.value);
43
+ return out;
44
+ }
45
+ /** Tesla weekday (Mon=0..Sun=6) for a calendar date, via JS Date.getUTCDay() (Sun=0..Sat=6). */
46
+ function dowFromDate(year, month, day) {
47
+ return (new Date(Date.UTC(year, month - 1, day)).getUTCDay() + 6) % 7;
48
+ }
49
+ function getWallClock(now, timeZone) {
50
+ const { year, month, day, hour, minute } = formatParts(now, timeZone);
51
+ return {
52
+ year,
53
+ month,
54
+ day,
55
+ dow: dowFromDate(year, month, day),
56
+ minuteOfDay: hour * 60 + minute
57
+ };
58
+ }
59
+ /**
60
+ * Converts a target site-local wall-clock (year/month/day/hour/minute in `timeZone`) to the UTC
61
+ * instant it represents. Iterates because the zone's UTC offset at the target instant is itself
62
+ * what we're solving for (DST) - 3 rounds converges for any real-world offset change.
63
+ */
64
+ function wallClockToUtcMillis(year, month, day, hour, minute, timeZone) {
65
+ const target = Date.UTC(year, month - 1, day, hour, minute);
66
+ let guess = target;
67
+ for (let i = 0; i < 3; i++) {
68
+ const p = formatParts(new Date(guess), timeZone);
69
+ const next = target - (Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - guess);
70
+ if (next === guess) break;
71
+ guess = next;
72
+ }
73
+ return guess;
74
+ }
75
+ /**
76
+ * Adds `deltaMinutes` of wall-clock time (not elapsed real time) to a wall-clock reading, staying
77
+ * in pure calendar arithmetic throughout - no `timeZone`/`Intl` round trip, so this is safe to use
78
+ * for "what day/weekday is N minutes from now" even across a DST transition, where a real-time-ms
79
+ * shift and a wall-clock-minutes shift land on different calendar days.
80
+ */
81
+ function wallClockAt(wallClock, deltaMinutes) {
82
+ const naive = new Date(Date.UTC(wallClock.year, wallClock.month - 1, wallClock.day, 0, wallClock.minuteOfDay + deltaMinutes));
83
+ const year = naive.getUTCFullYear();
84
+ const month = naive.getUTCMonth() + 1;
85
+ const day = naive.getUTCDate();
86
+ const minuteOfDay = naive.getUTCHours() * 60 + naive.getUTCMinutes();
87
+ return {
88
+ year,
89
+ month,
90
+ day,
91
+ dow: dowFromDate(year, month, day),
92
+ minuteOfDay
93
+ };
94
+ }
95
+ function dowInRange(d, fromDow, toDow) {
96
+ return fromDow <= toDow ? d >= fromDow && d <= toDow : d >= fromDow || d <= toDow;
97
+ }
98
+ function seasonContains(season, month, day) {
99
+ if (!season?.tou_periods) return false;
100
+ const fromMonth = season.fromMonth ?? 1;
101
+ const fromDay = season.fromDay ?? 1;
102
+ const toMonth = season.toMonth ?? 12;
103
+ const toDay = season.toDay ?? 31;
104
+ const from = fromMonth * 100 + fromDay;
105
+ const to = toMonth * 100 + toDay;
106
+ const cur = month * 100 + day;
107
+ return from <= to ? cur >= from && cur <= to : cur >= from || cur <= to;
108
+ }
109
+ function findSeason(seasons, month, day) {
110
+ if (!seasons) return null;
111
+ for (const [name, season] of Object.entries(seasons)) if (seasonContains(season, month, day)) return {
112
+ name,
113
+ season
114
+ };
115
+ return null;
116
+ }
117
+ /**
118
+ * Expands a season's `tou_periods` into the windows active on one specific calendar day (given
119
+ * as a Tesla weekday, Mon=0..Sun=6). A period whose `fromDayOfWeek..toDayOfWeek` includes `dow`
120
+ * contributes its own window (clamped to this day, `endMin` capped at 1440 if it crosses
121
+ * midnight); a period that crosses midnight and includes *yesterday* also contributes its
122
+ * early-morning continuation here, `trueStartMin` carrying its real (pre-midnight) start so a
123
+ * currently-active continuation can still report an accurate `currentStart`.
124
+ */
125
+ function dayPeriods(touPeriods, dow) {
126
+ const segments = [];
127
+ const prevDow = (dow + 6) % 7;
128
+ for (const [periodName, def] of Object.entries(touPeriods)) for (const window of def.periods ?? []) {
129
+ const fromDow = window.fromDayOfWeek ?? 0;
130
+ const toDow = window.toDayOfWeek ?? 6;
131
+ const fromMin = (window.fromHour ?? 0) * 60 + (window.fromMinute ?? 0);
132
+ const toMin = (window.toHour ?? 0) * 60 + (window.toMinute ?? 0);
133
+ const crossesMidnight = toMin <= fromMin;
134
+ if (dowInRange(dow, fromDow, toDow)) {
135
+ const endMin = crossesMidnight ? MINUTES_PER_DAY : toMin;
136
+ if (endMin > fromMin) segments.push({
137
+ periodName,
138
+ startMin: fromMin,
139
+ endMin,
140
+ trueStartMin: fromMin
141
+ });
142
+ }
143
+ if (crossesMidnight && toMin > 0 && dowInRange(prevDow, fromDow, toDow)) segments.push({
144
+ periodName,
145
+ startMin: 0,
146
+ endMin: toMin,
147
+ trueStartMin: fromMin - MINUTES_PER_DAY
148
+ });
149
+ }
150
+ return segments;
151
+ }
152
+ function lookupPrice(charges, seasonName, periodName) {
153
+ if (!charges) return null;
154
+ const seasonRates = charges[seasonName]?.rates ?? charges["ALL"]?.rates;
155
+ if (!seasonRates) return null;
156
+ const value = Object.prototype.hasOwnProperty.call(seasonRates, periodName) ? seasonRates[periodName] : seasonRates["ALL"];
157
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
158
+ }
159
+ const EMPTY_RATE = {
160
+ price: null,
161
+ periodName: null,
162
+ seasonName: null
163
+ };
164
+ /**
165
+ * Resolves one grid's (buy's, or sell's) state at `wallClock`: the active period if any, and the
166
+ * next boundary regardless of whether a period is currently active - a sparse tariff's gap still
167
+ * has a "next change" (the gap's own end) and a "since" (the gap's own start), just like an active
168
+ * period does (its own end and start).
169
+ */
170
+ function scheduleAt(seasons, wallClock) {
171
+ const matched = findSeason(seasons, wallClock.month, wallClock.day);
172
+ if (!matched) return null;
173
+ const todaySegs = dayPeriods(matched.season.tou_periods, wallClock.dow);
174
+ const current = todaySegs.find((s) => s.startMin <= wallClock.minuteOfDay && wallClock.minuteOfDay < s.endMin) ?? null;
175
+ const laterTodayStarts = todaySegs.filter((s) => s.startMin > wallClock.minuteOfDay).map((s) => s.startMin);
176
+ let nextMin = current ? current.endMin : null;
177
+ if (laterTodayStarts.length > 0) nextMin = nextMin === null ? Math.min(...laterTodayStarts) : Math.min(nextMin, Math.min(...laterTodayStarts));
178
+ else if (nextMin === null || nextMin >= MINUTES_PER_DAY) {
179
+ const tomorrowWallClock = wallClockAt(wallClock, MINUTES_PER_DAY - wallClock.minuteOfDay);
180
+ const tomorrowMatched = findSeason(seasons, tomorrowWallClock.month, tomorrowWallClock.day);
181
+ const tomorrowSegs = tomorrowMatched ? dayPeriods(tomorrowMatched.season.tou_periods, tomorrowWallClock.dow) : [];
182
+ const continuation = current && tomorrowMatched?.name === matched.name ? tomorrowSegs.find((s) => s.startMin === 0 && s.periodName === current.periodName) : void 0;
183
+ if (continuation) nextMin = MINUTES_PER_DAY + continuation.endMin;
184
+ else if (tomorrowSegs.length > 0) {
185
+ const tomorrowFirst = MINUTES_PER_DAY + Math.min(...tomorrowSegs.map((s) => s.startMin));
186
+ nextMin = nextMin === null ? tomorrowFirst : Math.min(nextMin, tomorrowFirst);
187
+ }
188
+ }
189
+ let sinceMin;
190
+ if (current) sinceMin = current.trueStartMin;
191
+ else {
192
+ const earlierTodayEnds = todaySegs.filter((s) => s.endMin <= wallClock.minuteOfDay).map((s) => s.endMin);
193
+ if (earlierTodayEnds.length > 0) sinceMin = Math.max(...earlierTodayEnds);
194
+ else {
195
+ const yesterdayWallClock = wallClockAt(wallClock, -1440);
196
+ const yesterdayMatched = findSeason(seasons, yesterdayWallClock.month, yesterdayWallClock.day);
197
+ const yesterdaySegs = yesterdayMatched ? dayPeriods(yesterdayMatched.season.tou_periods, yesterdayWallClock.dow) : [];
198
+ sinceMin = yesterdaySegs.length > 0 ? Math.max(...yesterdaySegs.map((s) => s.endMin)) - MINUTES_PER_DAY : null;
199
+ }
200
+ }
201
+ return {
202
+ seasonName: matched.name,
203
+ current,
204
+ nextChangeGM: nextMin === null ? null : nextMin - wallClock.minuteOfDay,
205
+ sinceGM: sinceMin === null ? null : sinceMin - wallClock.minuteOfDay
206
+ };
207
+ }
208
+ /** Walks one calendar day at a time across the horizon, re-resolving the season fresh on each day (via pure wall-clock arithmetic, not elapsed-ms/DST-sensitive arithmetic) so a horizon crossing a season boundary re-prices and re-labels correctly. */
209
+ function collectSegments(seasons, wallClock, horizonMinutes) {
210
+ const segments = [];
211
+ let dayStartGM = -wallClock.minuteOfDay;
212
+ while (dayStartGM < horizonMinutes) {
213
+ const dayWallClock = wallClockAt(wallClock, dayStartGM);
214
+ const matched = findSeason(seasons, dayWallClock.month, dayWallClock.day);
215
+ if (matched) for (const seg of dayPeriods(matched.season.tou_periods, dayWallClock.dow)) {
216
+ const startGM = dayStartGM + seg.startMin;
217
+ const endGM = dayStartGM + seg.endMin;
218
+ if (endGM > 0 && startGM < horizonMinutes) segments.push({
219
+ startGM,
220
+ endGM,
221
+ periodName: seg.periodName,
222
+ seasonName: matched.name
223
+ });
224
+ }
225
+ dayStartGM += MINUTES_PER_DAY;
226
+ }
227
+ segments.sort((a, b) => a.startGM - b.startGM);
228
+ const merged = [];
229
+ for (const seg of segments) {
230
+ const last = merged[merged.length - 1];
231
+ if (last && last.endGM === seg.startGM && last.periodName === seg.periodName && last.seasonName === seg.seasonName) last.endGM = seg.endGM;
232
+ else merged.push({ ...seg });
233
+ }
234
+ return merged;
235
+ }
236
+ function buildUpcoming(buySeasons, buyCharges, sellSeasons, sellCharges, hasSell, wallClock, horizonMinutes, toDate) {
237
+ const buySegs = collectSegments(buySeasons, wallClock, horizonMinutes);
238
+ const sellSegs = hasSell ? collectSegments(sellSeasons, wallClock, horizonMinutes) : [];
239
+ const boundaries = /* @__PURE__ */ new Set([0, horizonMinutes]);
240
+ const addBoundary = (gm) => {
241
+ if (gm > 0 && gm < horizonMinutes) boundaries.add(gm);
242
+ };
243
+ for (const s of buySegs) {
244
+ addBoundary(s.startGM);
245
+ addBoundary(s.endGM);
246
+ }
247
+ for (const s of sellSegs) {
248
+ addBoundary(s.startGM);
249
+ addBoundary(s.endGM);
250
+ }
251
+ const sorted = [...boundaries].sort((a, b) => a - b);
252
+ const periods = [];
253
+ for (let i = 0; i < sorted.length - 1; i++) {
254
+ const segStart = sorted[i];
255
+ const segEnd = sorted[i + 1];
256
+ if (segStart === segEnd) continue;
257
+ const buyAt = buySegs.find((s) => s.startGM <= segStart && segStart < s.endGM);
258
+ const sellAt = sellSegs.find((s) => s.startGM <= segStart && segStart < s.endGM);
259
+ periods.push({
260
+ start: toDate(segStart),
261
+ end: toDate(segEnd),
262
+ buy: buyAt ? {
263
+ price: lookupPrice(buyCharges, buyAt.seasonName, buyAt.periodName),
264
+ periodName: buyAt.periodName,
265
+ seasonName: buyAt.seasonName
266
+ } : EMPTY_RATE,
267
+ sell: sellAt ? {
268
+ price: lookupPrice(sellCharges, sellAt.seasonName, sellAt.periodName),
269
+ periodName: sellAt.periodName,
270
+ seasonName: sellAt.seasonName
271
+ } : EMPTY_RATE
272
+ });
273
+ }
274
+ return periods;
275
+ }
276
+ /**
277
+ * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
278
+ * `tariff` carries no timezone; the caller must supply the site's IANA zone (e.g. from
279
+ * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
280
+ */
281
+ function getTariffPeriods(tariff, now, opts) {
282
+ if (!tariff?.seasons || !tariff?.energy_charges) return null;
283
+ if (!opts?.timeZone) throw new Error("getTariffPeriods requires opts.timeZone");
284
+ const timeZone = opts.timeZone;
285
+ const wallClock = getWallClock(now, timeZone);
286
+ const buySchedule = scheduleAt(tariff.seasons, wallClock);
287
+ if (!buySchedule?.current) return null;
288
+ const sellSeasons = tariff.sell_tariff?.seasons ?? tariff.seasons;
289
+ const sellSchedule = tariff.sell_tariff ? scheduleAt(sellSeasons, wallClock) : null;
290
+ const buyRate = {
291
+ price: lookupPrice(tariff.energy_charges, buySchedule.seasonName, buySchedule.current.periodName),
292
+ periodName: buySchedule.current.periodName,
293
+ seasonName: buySchedule.seasonName
294
+ };
295
+ const sellRate = sellSchedule?.current ? {
296
+ price: lookupPrice(tariff.sell_tariff.energy_charges, sellSchedule.seasonName, sellSchedule.current.periodName),
297
+ periodName: sellSchedule.current.periodName,
298
+ seasonName: sellSchedule.seasonName
299
+ } : EMPTY_RATE;
300
+ const toDate = (gm) => {
301
+ const target = wallClockAt(wallClock, gm);
302
+ const hour = Math.floor(target.minuteOfDay / 60);
303
+ const minute = target.minuteOfDay % 60;
304
+ return new Date(wallClockToUtcMillis(target.year, target.month, target.day, hour, minute, timeZone));
305
+ };
306
+ const nextChangeGM = sellSchedule?.nextChangeGM != null ? Math.min(buySchedule.nextChangeGM, sellSchedule.nextChangeGM) : buySchedule.nextChangeGM;
307
+ const resolution = {
308
+ buy: buyRate,
309
+ sell: sellRate,
310
+ currentStart: toDate(sellSchedule?.sinceGM != null ? Math.max(buySchedule.sinceGM, sellSchedule.sinceGM) : buySchedule.sinceGM),
311
+ nextChange: toDate(nextChangeGM),
312
+ currency: typeof tariff.currency === "string" ? tariff.currency : null,
313
+ upcoming: null
314
+ };
315
+ if (opts.horizonHours) resolution.upcoming = buildUpcoming(tariff.seasons, tariff.energy_charges, sellSeasons, tariff.sell_tariff?.energy_charges, !!tariff.sell_tariff, wallClock, opts.horizonHours * 60, toDate);
316
+ return resolution;
317
+ }
318
+ exports.getTariffPeriods = getTariffPeriods;
319
+ exports.unwrapTariffV2 = unwrapTariffV2;
@@ -0,0 +1,40 @@
1
+ import { TariffContentV2 } from "./types/site_info.cjs";
2
+ export interface TariffRate {
3
+ price: number | null;
4
+ periodName: string | null;
5
+ seasonName: string | null;
6
+ }
7
+ export interface TariffPeriod {
8
+ start: Date;
9
+ end: Date;
10
+ buy: TariffRate;
11
+ sell: TariffRate;
12
+ }
13
+ export interface TariffResolution {
14
+ buy: TariffRate;
15
+ sell: TariffRate;
16
+ currentStart: Date;
17
+ nextChange: Date;
18
+ currency: string | null;
19
+ upcoming: TariffPeriod[] | null;
20
+ }
21
+ /**
22
+ * Extract the `tariff_content_v2` object from a raw API response.
23
+ *
24
+ * Accepts the `site_info` read envelope (`{ response: { tariff_content_v2 } }`), the
25
+ * `time_of_use_settings` write envelope (`{ tou_settings: { tariff_content_v2 } }`), or the bare
26
+ * `tariff_content_v2` object itself. Throws {@link InvalidResponse} for a null body, an
27
+ * unrecognized envelope, or an extracted object without the minimal Tariff V2 shape (`seasons`
28
+ * and `energy_charges`) - checked identically on every path, since a malformed body (including an
29
+ * empty `{}` nested under a recognized envelope) is not the same as "no tariff configured".
30
+ */
31
+ export declare function unwrapTariffV2(response: unknown): TariffContentV2;
32
+ /**
33
+ * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
34
+ * `tariff` carries no timezone; the caller must supply the site's IANA zone (e.g. from
35
+ * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
36
+ */
37
+ export declare function getTariffPeriods(tariff: TariffContentV2, now: Date, opts?: {
38
+ timeZone: string;
39
+ horizonHours?: number;
40
+ }): TariffResolution | null;
@@ -0,0 +1,40 @@
1
+ import { TariffContentV2 } from "./types/site_info.mjs";
2
+ export interface TariffRate {
3
+ price: number | null;
4
+ periodName: string | null;
5
+ seasonName: string | null;
6
+ }
7
+ export interface TariffPeriod {
8
+ start: Date;
9
+ end: Date;
10
+ buy: TariffRate;
11
+ sell: TariffRate;
12
+ }
13
+ export interface TariffResolution {
14
+ buy: TariffRate;
15
+ sell: TariffRate;
16
+ currentStart: Date;
17
+ nextChange: Date;
18
+ currency: string | null;
19
+ upcoming: TariffPeriod[] | null;
20
+ }
21
+ /**
22
+ * Extract the `tariff_content_v2` object from a raw API response.
23
+ *
24
+ * Accepts the `site_info` read envelope (`{ response: { tariff_content_v2 } }`), the
25
+ * `time_of_use_settings` write envelope (`{ tou_settings: { tariff_content_v2 } }`), or the bare
26
+ * `tariff_content_v2` object itself. Throws {@link InvalidResponse} for a null body, an
27
+ * unrecognized envelope, or an extracted object without the minimal Tariff V2 shape (`seasons`
28
+ * and `energy_charges`) - checked identically on every path, since a malformed body (including an
29
+ * empty `{}` nested under a recognized envelope) is not the same as "no tariff configured".
30
+ */
31
+ export declare function unwrapTariffV2(response: unknown): TariffContentV2;
32
+ /**
33
+ * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
34
+ * `tariff` carries no timezone; the caller must supply the site's IANA zone (e.g. from
35
+ * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
36
+ */
37
+ export declare function getTariffPeriods(tariff: TariffContentV2, now: Date, opts?: {
38
+ timeZone: string;
39
+ horizonHours?: number;
40
+ }): TariffResolution | null;