tesla-fleet-api 0.2.2 → 0.4.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 (192) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +22 -0
  2. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  3. package/dist/charging.cjs +53 -0
  4. package/dist/charging.d.cts +31 -0
  5. package/dist/charging.d.mts +30 -0
  6. package/dist/charging.mjs +53 -0
  7. package/dist/commands.cjs +802 -0
  8. package/dist/commands.d.cts +350 -0
  9. package/dist/commands.d.mts +349 -0
  10. package/dist/commands.mjs +800 -0
  11. package/dist/const.cjs +300 -0
  12. package/dist/const.d.cts +287 -0
  13. package/dist/const.d.mts +287 -0
  14. package/dist/const.mjs +263 -0
  15. package/dist/energy.cjs +137 -0
  16. package/dist/energy.d.cts +95 -0
  17. package/{src/energy.ts → dist/energy.d.mts} +17 -140
  18. package/dist/energy.mjs +137 -0
  19. package/dist/energyspecific.cjs +99 -0
  20. package/dist/energyspecific.d.cts +78 -0
  21. package/dist/energyspecific.d.mts +77 -0
  22. package/dist/energyspecific.mjs +99 -0
  23. package/dist/exceptions.cjs +1487 -0
  24. package/dist/exceptions.d.cts +1142 -0
  25. package/dist/exceptions.d.mts +1142 -0
  26. package/dist/exceptions.mjs +1324 -0
  27. package/dist/index.cjs +226 -0
  28. package/dist/index.d.cts +14 -0
  29. package/dist/index.d.mts +14 -0
  30. package/dist/index.mjs +14 -0
  31. package/dist/partner.cjs +32 -0
  32. package/dist/partner.d.cts +24 -0
  33. package/dist/partner.d.mts +23 -0
  34. package/dist/partner.mjs +32 -0
  35. package/dist/router.cjs +0 -0
  36. package/dist/router.d.cts +1 -0
  37. package/dist/router.d.mts +1 -0
  38. package/dist/router.mjs +1 -0
  39. package/dist/signing/crypto.cjs +31 -0
  40. package/dist/signing/crypto.mjs +27 -0
  41. package/dist/signing/session.cjs +112 -0
  42. package/dist/signing/session.d.cts +69 -0
  43. package/dist/signing/session.d.mts +69 -0
  44. package/dist/signing/session.mjs +110 -0
  45. package/dist/tariff.cjs +292 -0
  46. package/dist/{tariff.d.ts → tariff.d.cts} +17 -17
  47. package/dist/tariff.d.mts +29 -0
  48. package/dist/tariff.mjs +291 -0
  49. package/dist/teslafleetapi.cjs +125 -0
  50. package/dist/teslafleetapi.d.cts +64 -0
  51. package/dist/teslafleetapi.d.mts +64 -0
  52. package/dist/teslafleetapi.mjs +125 -0
  53. package/dist/teslemetry.cjs +36 -0
  54. package/dist/teslemetry.d.cts +23 -0
  55. package/dist/teslemetry.d.mts +23 -0
  56. package/dist/teslemetry.mjs +36 -0
  57. package/dist/tessie.cjs +34 -0
  58. package/dist/tessie.d.cts +23 -0
  59. package/dist/tessie.d.mts +23 -0
  60. package/dist/tessie.mjs +34 -0
  61. package/dist/types/commands.d.cts +28 -0
  62. package/dist/types/commands.d.mts +28 -0
  63. package/dist/types/live_status.d.cts +30 -0
  64. package/dist/types/live_status.d.mts +30 -0
  65. package/dist/types/products.d.cts +58 -0
  66. package/dist/types/products.d.mts +58 -0
  67. package/dist/types/responses.d.cts +9 -0
  68. package/dist/types/responses.d.mts +9 -0
  69. package/dist/types/site_info.d.cts +154 -0
  70. package/dist/types/site_info.d.mts +154 -0
  71. package/dist/types/vehicle.d.cts +27 -0
  72. package/dist/types/vehicle.d.mts +27 -0
  73. package/dist/types/vehicle_data.d.cts +278 -0
  74. package/dist/types/vehicle_data.d.mts +278 -0
  75. package/dist/types.cjs +0 -0
  76. package/dist/types.d.cts +9 -0
  77. package/dist/types.d.mts +9 -0
  78. package/dist/types.mjs +1 -0
  79. package/dist/user.cjs +28 -0
  80. package/dist/user.d.cts +17 -0
  81. package/dist/user.d.mts +16 -0
  82. package/dist/user.mjs +28 -0
  83. package/dist/vehicle.cjs +804 -0
  84. package/dist/vehicle.d.cts +550 -0
  85. package/dist/vehicle.d.mts +549 -0
  86. package/dist/vehicle.mjs +804 -0
  87. package/dist/vehiclesigned.cjs +20 -0
  88. package/dist/vehiclesigned.d.cts +14 -0
  89. package/dist/{vehiclesigned.d.ts → vehiclesigned.d.mts} +3 -3
  90. package/dist/vehiclesigned.mjs +20 -0
  91. package/dist/vehiclespecific.cjs +604 -0
  92. package/dist/vehiclespecific.d.cts +433 -0
  93. package/dist/vehiclespecific.d.mts +432 -0
  94. package/dist/vehiclespecific.mjs +604 -0
  95. package/package.json +108 -35
  96. package/dist/charging.d.ts +0 -30
  97. package/dist/charging.js +0 -51
  98. package/dist/charging.js.map +0 -1
  99. package/dist/commands.d.ts +0 -352
  100. package/dist/commands.js +0 -875
  101. package/dist/commands.js.map +0 -1
  102. package/dist/energy.d.ts +0 -94
  103. package/dist/energy.js +0 -125
  104. package/dist/energy.js.map +0 -1
  105. package/dist/energyspecific.d.ts +0 -77
  106. package/dist/energyspecific.js +0 -97
  107. package/dist/energyspecific.js.map +0 -1
  108. package/dist/helpers.d.ts +0 -2
  109. package/dist/helpers.js +0 -10
  110. package/dist/helpers.js.map +0 -1
  111. package/dist/index.d.ts +0 -14
  112. package/dist/index.js +0 -14
  113. package/dist/index.js.map +0 -1
  114. package/dist/partner.d.ts +0 -23
  115. package/dist/partner.js +0 -31
  116. package/dist/partner.js.map +0 -1
  117. package/dist/signing/crypto.d.ts +0 -10
  118. package/dist/signing/crypto.js +0 -26
  119. package/dist/signing/crypto.js.map +0 -1
  120. package/dist/signing/errors.d.ts +0 -29
  121. package/dist/signing/errors.js +0 -40
  122. package/dist/signing/errors.js.map +0 -1
  123. package/dist/signing/session.d.ts +0 -69
  124. package/dist/signing/session.js +0 -102
  125. package/dist/signing/session.js.map +0 -1
  126. package/dist/tariff.js +0 -302
  127. package/dist/tariff.js.map +0 -1
  128. package/dist/teslafleetapi.d.ts +0 -58
  129. package/dist/teslafleetapi.js +0 -120
  130. package/dist/teslafleetapi.js.map +0 -1
  131. package/dist/teslemetry.d.ts +0 -29
  132. package/dist/teslemetry.js +0 -39
  133. package/dist/teslemetry.js.map +0 -1
  134. package/dist/tessie.d.ts +0 -23
  135. package/dist/tessie.js +0 -28
  136. package/dist/tessie.js.map +0 -1
  137. package/dist/types/commands.d.ts +0 -44
  138. package/dist/types/commands.js +0 -11
  139. package/dist/types/commands.js.map +0 -1
  140. package/dist/types/live_status.d.ts +0 -26
  141. package/dist/types/live_status.js +0 -2
  142. package/dist/types/live_status.js.map +0 -1
  143. package/dist/types/products.d.ts +0 -54
  144. package/dist/types/products.js +0 -2
  145. package/dist/types/products.js.map +0 -1
  146. package/dist/types/responses.d.ts +0 -5
  147. package/dist/types/responses.js +0 -2
  148. package/dist/types/responses.js.map +0 -1
  149. package/dist/types/site_info.d.ts +0 -150
  150. package/dist/types/site_info.js +0 -2
  151. package/dist/types/site_info.js.map +0 -1
  152. package/dist/types/vehicle.d.ts +0 -22
  153. package/dist/types/vehicle.js +0 -2
  154. package/dist/types/vehicle.js.map +0 -1
  155. package/dist/types/vehicle_data.d.ts +0 -274
  156. package/dist/types/vehicle_data.js +0 -2
  157. package/dist/types/vehicle_data.js.map +0 -1
  158. package/dist/user.d.ts +0 -16
  159. package/dist/user.js +0 -26
  160. package/dist/user.js.map +0 -1
  161. package/dist/vehicle.d.ts +0 -548
  162. package/dist/vehicle.js +0 -781
  163. package/dist/vehicle.js.map +0 -1
  164. package/dist/vehiclesigned.js +0 -19
  165. package/dist/vehiclesigned.js.map +0 -1
  166. package/dist/vehiclespecific.d.ts +0 -432
  167. package/dist/vehiclespecific.js +0 -601
  168. package/dist/vehiclespecific.js.map +0 -1
  169. package/src/charging.ts +0 -66
  170. package/src/commands.ts +0 -1134
  171. package/src/energyspecific.ts +0 -116
  172. package/src/helpers.ts +0 -11
  173. package/src/index.ts +0 -20
  174. package/src/partner.ts +0 -37
  175. package/src/signing/crypto.ts +0 -29
  176. package/src/signing/errors.ts +0 -50
  177. package/src/signing/session.ts +0 -132
  178. package/src/tariff.ts +0 -375
  179. package/src/teslafleetapi.ts +0 -182
  180. package/src/teslemetry.ts +0 -54
  181. package/src/tessie.ts +0 -42
  182. package/src/types/commands.ts +0 -62
  183. package/src/types/live_status.ts +0 -27
  184. package/src/types/products.ts +0 -61
  185. package/src/types/responses.ts +0 -1
  186. package/src/types/site_info.ts +0 -159
  187. package/src/types/vehicle.ts +0 -23
  188. package/src/types/vehicle_data.ts +0 -286
  189. package/src/user.ts +0 -35
  190. package/src/vehicle.ts +0 -895
  191. package/src/vehiclesigned.ts +0 -20
  192. package/src/vehiclespecific.ts +0 -818
@@ -1,22 +1,22 @@
1
- import { TariffContentV2 } from "./types/site_info.js";
1
+ import { TariffContentV2 } from "./types/site_info.cjs";
2
2
  export interface TariffRate {
3
- price: number | null;
4
- periodName: string | null;
5
- seasonName: string | null;
3
+ price: number | null;
4
+ periodName: string | null;
5
+ seasonName: string | null;
6
6
  }
7
7
  export interface TariffPeriod {
8
- start: Date;
9
- end: Date;
10
- buy: TariffRate;
11
- sell: TariffRate;
8
+ start: Date;
9
+ end: Date;
10
+ buy: TariffRate;
11
+ sell: TariffRate;
12
12
  }
13
13
  export interface TariffResolution {
14
- buy: TariffRate;
15
- sell: TariffRate;
16
- currentStart: Date;
17
- nextChange: Date;
18
- currency: string | null;
19
- upcoming: TariffPeriod[] | null;
14
+ buy: TariffRate;
15
+ sell: TariffRate;
16
+ currentStart: Date;
17
+ nextChange: Date;
18
+ currency: string | null;
19
+ upcoming: TariffPeriod[] | null;
20
20
  }
21
21
  /**
22
22
  * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
@@ -24,6 +24,6 @@ export interface TariffResolution {
24
24
  * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
25
25
  */
26
26
  export declare function getTariffPeriods(tariff: TariffContentV2, now: Date, opts?: {
27
- timeZone: string;
28
- horizonHours?: number;
29
- }): TariffResolution | null;
27
+ timeZone: string;
28
+ horizonHours?: number;
29
+ }): TariffResolution | null;
@@ -0,0 +1,29 @@
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
+ * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
23
+ * `tariff` carries no timezone; the caller must supply the site's IANA zone (e.g. from
24
+ * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
25
+ */
26
+ export declare function getTariffPeriods(tariff: TariffContentV2, now: Date, opts?: {
27
+ timeZone: string;
28
+ horizonHours?: number;
29
+ }): TariffResolution | null;
@@ -0,0 +1,291 @@
1
+ // src/tariff.ts
2
+ const MINUTES_PER_DAY = 1440;
3
+ function formatParts(date, timeZone) {
4
+ const parts = new Intl.DateTimeFormat("en-US", {
5
+ timeZone,
6
+ year: "numeric",
7
+ month: "2-digit",
8
+ day: "2-digit",
9
+ hour: "2-digit",
10
+ minute: "2-digit",
11
+ second: "2-digit",
12
+ hourCycle: "h23"
13
+ }).formatToParts(date);
14
+ const out = {};
15
+ for (const p of parts) out[p.type] = Number(p.value);
16
+ return out;
17
+ }
18
+ /** Tesla weekday (Mon=0..Sun=6) for a calendar date, via JS Date.getUTCDay() (Sun=0..Sat=6). */
19
+ function dowFromDate(year, month, day) {
20
+ return (new Date(Date.UTC(year, month - 1, day)).getUTCDay() + 6) % 7;
21
+ }
22
+ function getWallClock(now, timeZone) {
23
+ const { year, month, day, hour, minute } = formatParts(now, timeZone);
24
+ return {
25
+ year,
26
+ month,
27
+ day,
28
+ dow: dowFromDate(year, month, day),
29
+ minuteOfDay: hour * 60 + minute
30
+ };
31
+ }
32
+ /**
33
+ * Converts a target site-local wall-clock (year/month/day/hour/minute in `timeZone`) to the UTC
34
+ * instant it represents. Iterates because the zone's UTC offset at the target instant is itself
35
+ * what we're solving for (DST) - 3 rounds converges for any real-world offset change.
36
+ */
37
+ function wallClockToUtcMillis(year, month, day, hour, minute, timeZone) {
38
+ const target = Date.UTC(year, month - 1, day, hour, minute);
39
+ let guess = target;
40
+ for (let i = 0; i < 3; i++) {
41
+ const p = formatParts(new Date(guess), timeZone);
42
+ const next = target - (Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second) - guess);
43
+ if (next === guess) break;
44
+ guess = next;
45
+ }
46
+ return guess;
47
+ }
48
+ /**
49
+ * Adds `deltaMinutes` of wall-clock time (not elapsed real time) to a wall-clock reading, staying
50
+ * in pure calendar arithmetic throughout - no `timeZone`/`Intl` round trip, so this is safe to use
51
+ * for "what day/weekday is N minutes from now" even across a DST transition, where a real-time-ms
52
+ * shift and a wall-clock-minutes shift land on different calendar days.
53
+ */
54
+ function wallClockAt(wallClock, deltaMinutes) {
55
+ const naive = new Date(Date.UTC(wallClock.year, wallClock.month - 1, wallClock.day, 0, wallClock.minuteOfDay + deltaMinutes));
56
+ const year = naive.getUTCFullYear();
57
+ const month = naive.getUTCMonth() + 1;
58
+ const day = naive.getUTCDate();
59
+ const minuteOfDay = naive.getUTCHours() * 60 + naive.getUTCMinutes();
60
+ return {
61
+ year,
62
+ month,
63
+ day,
64
+ dow: dowFromDate(year, month, day),
65
+ minuteOfDay
66
+ };
67
+ }
68
+ function dowInRange(d, fromDow, toDow) {
69
+ return fromDow <= toDow ? d >= fromDow && d <= toDow : d >= fromDow || d <= toDow;
70
+ }
71
+ function seasonContains(season, month, day) {
72
+ if (!season?.tou_periods) return false;
73
+ const fromMonth = season.fromMonth ?? 1;
74
+ const fromDay = season.fromDay ?? 1;
75
+ const toMonth = season.toMonth ?? 12;
76
+ const toDay = season.toDay ?? 31;
77
+ const from = fromMonth * 100 + fromDay;
78
+ const to = toMonth * 100 + toDay;
79
+ const cur = month * 100 + day;
80
+ return from <= to ? cur >= from && cur <= to : cur >= from || cur <= to;
81
+ }
82
+ function findSeason(seasons, month, day) {
83
+ if (!seasons) return null;
84
+ for (const [name, season] of Object.entries(seasons)) if (seasonContains(season, month, day)) return {
85
+ name,
86
+ season
87
+ };
88
+ return null;
89
+ }
90
+ /**
91
+ * Expands a season's `tou_periods` into the windows active on one specific calendar day (given
92
+ * as a Tesla weekday, Mon=0..Sun=6). A period whose `fromDayOfWeek..toDayOfWeek` includes `dow`
93
+ * contributes its own window (clamped to this day, `endMin` capped at 1440 if it crosses
94
+ * midnight); a period that crosses midnight and includes *yesterday* also contributes its
95
+ * early-morning continuation here, `trueStartMin` carrying its real (pre-midnight) start so a
96
+ * currently-active continuation can still report an accurate `currentStart`.
97
+ */
98
+ function dayPeriods(touPeriods, dow) {
99
+ const segments = [];
100
+ const prevDow = (dow + 6) % 7;
101
+ for (const [periodName, def] of Object.entries(touPeriods)) for (const window of def.periods ?? []) {
102
+ const fromDow = window.fromDayOfWeek ?? 0;
103
+ const toDow = window.toDayOfWeek ?? 6;
104
+ const fromMin = (window.fromHour ?? 0) * 60 + (window.fromMinute ?? 0);
105
+ const toMin = (window.toHour ?? 0) * 60 + (window.toMinute ?? 0);
106
+ const crossesMidnight = toMin <= fromMin;
107
+ if (dowInRange(dow, fromDow, toDow)) {
108
+ const endMin = crossesMidnight ? MINUTES_PER_DAY : toMin;
109
+ if (endMin > fromMin) segments.push({
110
+ periodName,
111
+ startMin: fromMin,
112
+ endMin,
113
+ trueStartMin: fromMin
114
+ });
115
+ }
116
+ if (crossesMidnight && toMin > 0 && dowInRange(prevDow, fromDow, toDow)) segments.push({
117
+ periodName,
118
+ startMin: 0,
119
+ endMin: toMin,
120
+ trueStartMin: fromMin - MINUTES_PER_DAY
121
+ });
122
+ }
123
+ return segments;
124
+ }
125
+ function lookupPrice(charges, seasonName, periodName) {
126
+ if (!charges) return null;
127
+ const seasonRates = charges[seasonName]?.rates ?? charges["ALL"]?.rates;
128
+ if (!seasonRates) return null;
129
+ const value = Object.prototype.hasOwnProperty.call(seasonRates, periodName) ? seasonRates[periodName] : seasonRates["ALL"];
130
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
131
+ }
132
+ const EMPTY_RATE = {
133
+ price: null,
134
+ periodName: null,
135
+ seasonName: null
136
+ };
137
+ /**
138
+ * Resolves one grid's (buy's, or sell's) state at `wallClock`: the active period if any, and the
139
+ * next boundary regardless of whether a period is currently active - a sparse tariff's gap still
140
+ * has a "next change" (the gap's own end) and a "since" (the gap's own start), just like an active
141
+ * period does (its own end and start).
142
+ */
143
+ function scheduleAt(seasons, wallClock) {
144
+ const matched = findSeason(seasons, wallClock.month, wallClock.day);
145
+ if (!matched) return null;
146
+ const todaySegs = dayPeriods(matched.season.tou_periods, wallClock.dow);
147
+ const current = todaySegs.find((s) => s.startMin <= wallClock.minuteOfDay && wallClock.minuteOfDay < s.endMin) ?? null;
148
+ const laterTodayStarts = todaySegs.filter((s) => s.startMin > wallClock.minuteOfDay).map((s) => s.startMin);
149
+ let nextMin = current ? current.endMin : null;
150
+ if (laterTodayStarts.length > 0) nextMin = nextMin === null ? Math.min(...laterTodayStarts) : Math.min(nextMin, Math.min(...laterTodayStarts));
151
+ else if (nextMin === null || nextMin >= MINUTES_PER_DAY) {
152
+ const tomorrowWallClock = wallClockAt(wallClock, MINUTES_PER_DAY - wallClock.minuteOfDay);
153
+ const tomorrowMatched = findSeason(seasons, tomorrowWallClock.month, tomorrowWallClock.day);
154
+ const tomorrowSegs = tomorrowMatched ? dayPeriods(tomorrowMatched.season.tou_periods, tomorrowWallClock.dow) : [];
155
+ const continuation = current && tomorrowMatched?.name === matched.name ? tomorrowSegs.find((s) => s.startMin === 0 && s.periodName === current.periodName) : void 0;
156
+ if (continuation) nextMin = MINUTES_PER_DAY + continuation.endMin;
157
+ else if (tomorrowSegs.length > 0) {
158
+ const tomorrowFirst = MINUTES_PER_DAY + Math.min(...tomorrowSegs.map((s) => s.startMin));
159
+ nextMin = nextMin === null ? tomorrowFirst : Math.min(nextMin, tomorrowFirst);
160
+ }
161
+ }
162
+ let sinceMin;
163
+ if (current) sinceMin = current.trueStartMin;
164
+ else {
165
+ const earlierTodayEnds = todaySegs.filter((s) => s.endMin <= wallClock.minuteOfDay).map((s) => s.endMin);
166
+ if (earlierTodayEnds.length > 0) sinceMin = Math.max(...earlierTodayEnds);
167
+ else {
168
+ const yesterdayWallClock = wallClockAt(wallClock, -1440);
169
+ const yesterdayMatched = findSeason(seasons, yesterdayWallClock.month, yesterdayWallClock.day);
170
+ const yesterdaySegs = yesterdayMatched ? dayPeriods(yesterdayMatched.season.tou_periods, yesterdayWallClock.dow) : [];
171
+ sinceMin = yesterdaySegs.length > 0 ? Math.max(...yesterdaySegs.map((s) => s.endMin)) - MINUTES_PER_DAY : null;
172
+ }
173
+ }
174
+ return {
175
+ seasonName: matched.name,
176
+ current,
177
+ nextChangeGM: nextMin === null ? null : nextMin - wallClock.minuteOfDay,
178
+ sinceGM: sinceMin === null ? null : sinceMin - wallClock.minuteOfDay
179
+ };
180
+ }
181
+ /** 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. */
182
+ function collectSegments(seasons, wallClock, horizonMinutes) {
183
+ const segments = [];
184
+ let dayStartGM = -wallClock.minuteOfDay;
185
+ while (dayStartGM < horizonMinutes) {
186
+ const dayWallClock = wallClockAt(wallClock, dayStartGM);
187
+ const matched = findSeason(seasons, dayWallClock.month, dayWallClock.day);
188
+ if (matched) for (const seg of dayPeriods(matched.season.tou_periods, dayWallClock.dow)) {
189
+ const startGM = dayStartGM + seg.startMin;
190
+ const endGM = dayStartGM + seg.endMin;
191
+ if (endGM > 0 && startGM < horizonMinutes) segments.push({
192
+ startGM,
193
+ endGM,
194
+ periodName: seg.periodName,
195
+ seasonName: matched.name
196
+ });
197
+ }
198
+ dayStartGM += MINUTES_PER_DAY;
199
+ }
200
+ segments.sort((a, b) => a.startGM - b.startGM);
201
+ const merged = [];
202
+ for (const seg of segments) {
203
+ const last = merged[merged.length - 1];
204
+ if (last && last.endGM === seg.startGM && last.periodName === seg.periodName && last.seasonName === seg.seasonName) last.endGM = seg.endGM;
205
+ else merged.push({ ...seg });
206
+ }
207
+ return merged;
208
+ }
209
+ function buildUpcoming(buySeasons, buyCharges, sellSeasons, sellCharges, hasSell, wallClock, horizonMinutes, toDate) {
210
+ const buySegs = collectSegments(buySeasons, wallClock, horizonMinutes);
211
+ const sellSegs = hasSell ? collectSegments(sellSeasons, wallClock, horizonMinutes) : [];
212
+ const boundaries = /* @__PURE__ */ new Set([0, horizonMinutes]);
213
+ const addBoundary = (gm) => {
214
+ if (gm > 0 && gm < horizonMinutes) boundaries.add(gm);
215
+ };
216
+ for (const s of buySegs) {
217
+ addBoundary(s.startGM);
218
+ addBoundary(s.endGM);
219
+ }
220
+ for (const s of sellSegs) {
221
+ addBoundary(s.startGM);
222
+ addBoundary(s.endGM);
223
+ }
224
+ const sorted = [...boundaries].sort((a, b) => a - b);
225
+ const periods = [];
226
+ for (let i = 0; i < sorted.length - 1; i++) {
227
+ const segStart = sorted[i];
228
+ const segEnd = sorted[i + 1];
229
+ if (segStart === segEnd) continue;
230
+ const buyAt = buySegs.find((s) => s.startGM <= segStart && segStart < s.endGM);
231
+ const sellAt = sellSegs.find((s) => s.startGM <= segStart && segStart < s.endGM);
232
+ periods.push({
233
+ start: toDate(segStart),
234
+ end: toDate(segEnd),
235
+ buy: buyAt ? {
236
+ price: lookupPrice(buyCharges, buyAt.seasonName, buyAt.periodName),
237
+ periodName: buyAt.periodName,
238
+ seasonName: buyAt.seasonName
239
+ } : EMPTY_RATE,
240
+ sell: sellAt ? {
241
+ price: lookupPrice(sellCharges, sellAt.seasonName, sellAt.periodName),
242
+ periodName: sellAt.periodName,
243
+ seasonName: sellAt.seasonName
244
+ } : EMPTY_RATE
245
+ });
246
+ }
247
+ return periods;
248
+ }
249
+ /**
250
+ * Resolves the current buy/sell time-of-use rate for a Tariff V2 object at a given instant.
251
+ * `tariff` carries no timezone; the caller must supply the site's IANA zone (e.g. from
252
+ * `site_info.installation_time_zone`) so wall-clock period boundaries match correctly.
253
+ */
254
+ function getTariffPeriods(tariff, now, opts) {
255
+ if (!tariff?.seasons || !tariff?.energy_charges) return null;
256
+ if (!opts?.timeZone) throw new Error("getTariffPeriods requires opts.timeZone");
257
+ const timeZone = opts.timeZone;
258
+ const wallClock = getWallClock(now, timeZone);
259
+ const buySchedule = scheduleAt(tariff.seasons, wallClock);
260
+ if (!buySchedule?.current) return null;
261
+ const sellSeasons = tariff.sell_tariff?.seasons ?? tariff.seasons;
262
+ const sellSchedule = tariff.sell_tariff ? scheduleAt(sellSeasons, wallClock) : null;
263
+ const buyRate = {
264
+ price: lookupPrice(tariff.energy_charges, buySchedule.seasonName, buySchedule.current.periodName),
265
+ periodName: buySchedule.current.periodName,
266
+ seasonName: buySchedule.seasonName
267
+ };
268
+ const sellRate = sellSchedule?.current ? {
269
+ price: lookupPrice(tariff.sell_tariff.energy_charges, sellSchedule.seasonName, sellSchedule.current.periodName),
270
+ periodName: sellSchedule.current.periodName,
271
+ seasonName: sellSchedule.seasonName
272
+ } : EMPTY_RATE;
273
+ const toDate = (gm) => {
274
+ const target = wallClockAt(wallClock, gm);
275
+ const hour = Math.floor(target.minuteOfDay / 60);
276
+ const minute = target.minuteOfDay % 60;
277
+ return new Date(wallClockToUtcMillis(target.year, target.month, target.day, hour, minute, timeZone));
278
+ };
279
+ const nextChangeGM = sellSchedule?.nextChangeGM != null ? Math.min(buySchedule.nextChangeGM, sellSchedule.nextChangeGM) : buySchedule.nextChangeGM;
280
+ const resolution = {
281
+ buy: buyRate,
282
+ sell: sellRate,
283
+ currentStart: toDate(sellSchedule?.sinceGM != null ? Math.max(buySchedule.sinceGM, sellSchedule.sinceGM) : buySchedule.sinceGM),
284
+ nextChange: toDate(nextChangeGM),
285
+ currency: typeof tariff.currency === "string" ? tariff.currency : null,
286
+ upcoming: null
287
+ };
288
+ 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);
289
+ return resolution;
290
+ }
291
+ export { getTariffPeriods };
@@ -0,0 +1,125 @@
1
+ const require_charging = require("./charging.cjs");
2
+ const require_energy = require("./energy.cjs");
3
+ const require_partner = require("./partner.cjs");
4
+ const require_user = require("./user.cjs");
5
+ const require_vehicle = require("./vehicle.cjs");
6
+ const require_const = require("./const.cjs");
7
+ const require_exceptions = require("./exceptions.cjs");
8
+ // src/teslafleetapi.ts
9
+ var TeslaFleetApi = class {
10
+ server = null;
11
+ accessToken;
12
+ charging;
13
+ energy;
14
+ partner;
15
+ user;
16
+ vehicle;
17
+ debug = false;
18
+ /** Abort a request (including reading its body) after this many milliseconds; `null` waits indefinitely. */
19
+ timeout;
20
+ constructor(options) {
21
+ this.debug = !!options.debug;
22
+ this.timeout = options.timeout ?? null;
23
+ this.accessToken = options.accessToken ?? null;
24
+ if (options.server) this.server = options.server;
25
+ else if (options.region && require_const.is_valid_region(options.region)) this.server = require_const.SERVERS[options.region];
26
+ else throw new Error("Either server or region must be provided.");
27
+ this.debug && console.debug(`Using server ${this.server}`);
28
+ if (options.chargingScope !== false) this.charging = new require_charging.default(this);
29
+ if (options.energyScope !== false) this.energy = new require_energy.default(this);
30
+ if (options.userScope !== false) this.user = new require_user.default(this);
31
+ if (options.partnerScope !== false) this.partner = new require_partner.default(this);
32
+ if (options.vehicleScope !== false) this.vehicle = new require_vehicle.default(this);
33
+ }
34
+ /**
35
+ * Turn a response into its decoded JSON body, throwing the matching
36
+ * `TeslaFleetError` for an error status and `ResponseError` for a
37
+ * successful response whose body is not JSON.
38
+ */
39
+ async _response(resp) {
40
+ await require_exceptions.raise_for_status(resp);
41
+ const text = await resp.text();
42
+ try {
43
+ return JSON.parse(text);
44
+ } catch {
45
+ throw new require_exceptions.ResponseError(text, resp.status);
46
+ }
47
+ }
48
+ /**
49
+ * Make a request to the Tesla Fleet API.
50
+ * @param method
51
+ * @param path
52
+ * @param params
53
+ * @param json
54
+ * @returns The decoded JSON body.
55
+ * @throws A `TeslaFleetError` subclass for an error status, or
56
+ * `TeslaFleetConnectionError` (with the original error as `cause`) when the
57
+ * request could not complete or timed out.
58
+ */
59
+ async _request(method, path, params = null, json = null) {
60
+ if (!this.server) throw new Error("Server was not set at init. Call findServer() first.");
61
+ if (method === "GET" && json !== null) throw new Error("GET requests cannot have a body.");
62
+ this.debug && console.debug(`Sending request to ${path}`);
63
+ if (params) {
64
+ params = Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null));
65
+ this.debug && console.debug(`Parameters: ${JSON.stringify(params)}`);
66
+ }
67
+ if (json) {
68
+ json = Object.fromEntries(Object.entries(json).filter(([_, v]) => v != null));
69
+ this.debug && console.debug(`Body: ${JSON.stringify(json)}`);
70
+ }
71
+ const headers = {
72
+ Authorization: `Bearer ${this.accessToken}`,
73
+ "Content-Type": "application/json",
74
+ "X-Library": `node tesla_fleet_api`
75
+ };
76
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
77
+ try {
78
+ const resp = await fetch(`${this.server}/${path}${query}`, {
79
+ method,
80
+ headers,
81
+ body: json ? JSON.stringify(json) : null,
82
+ signal: this.timeout != null ? AbortSignal.timeout(this.timeout) : null
83
+ });
84
+ return await this._response(resp);
85
+ } catch (error) {
86
+ if (error instanceof require_exceptions.TeslaFleetError) throw error;
87
+ throw new require_exceptions.TeslaFleetConnectionError(null, null, { cause: error });
88
+ }
89
+ }
90
+ /**
91
+ * Returns products mapped to user.
92
+ * @returns An array of products including both vehicles and energy sites.
93
+ */
94
+ async products() {
95
+ return this._request("GET", "api/1/products").then(({ response }) => response);
96
+ }
97
+ /**
98
+ * Return Tesla products separated into vehicles and energy_sites
99
+ * @returns A dictionary of vehicles and energy sites, each with an array of those products
100
+ */
101
+ async products_by_type() {
102
+ return this.products().then((products) => ({
103
+ vehicles: products.filter((product) => "vin" in product),
104
+ energy_sites: products.filter((product) => "energy_site_id" in product)
105
+ }));
106
+ }
107
+ async loadPrivateKey(path) {
108
+ return (await import("fs")).promises.readFile(path, "utf8").then((key) => {
109
+ this.accessToken = key;
110
+ }).catch(async () => {
111
+ const { privateKey: _privateKey } = (await import("crypto")).generateKeyPairSync("ec", {
112
+ namedCurve: "secp256r1",
113
+ publicKeyEncoding: {
114
+ type: "spki",
115
+ format: "pem"
116
+ },
117
+ privateKeyEncoding: {
118
+ type: "pkcs8",
119
+ format: "pem"
120
+ }
121
+ });
122
+ });
123
+ }
124
+ };
125
+ exports.default = TeslaFleetApi;
@@ -0,0 +1,64 @@
1
+ import Charging from "./charging.cjs";
2
+ import Energy from "./energy.cjs";
3
+ import Partner from "./partner.cjs";
4
+ import User from "./user.cjs";
5
+ import Vehicle from "./vehicle.cjs";
6
+ import { Method, Region } from "./const.cjs";
7
+ import { EnergyProduct, ProductsResponse, VehicleProduct } from "./types/products.cjs";
8
+ export type ProductsByType = {
9
+ vehicles: VehicleProduct[];
10
+ energy_sites: EnergyProduct[];
11
+ };
12
+ export default class TeslaFleetApi {
13
+ server: string | null;
14
+ accessToken: string | null;
15
+ charging?: Charging;
16
+ energy?: Energy;
17
+ partner?: Partner;
18
+ user?: User;
19
+ vehicle?: Vehicle;
20
+ debug: boolean;
21
+ /** Abort a request (including reading its body) after this many milliseconds; `null` waits indefinitely. */
22
+ timeout: number | null;
23
+ constructor(options: {
24
+ accessToken?: string;
25
+ region?: Region;
26
+ server?: string;
27
+ chargingScope?: boolean;
28
+ energyScope?: boolean;
29
+ partnerScope?: boolean;
30
+ userScope?: boolean;
31
+ vehicleScope?: boolean;
32
+ debug?: boolean;
33
+ timeout?: number;
34
+ });
35
+ /**
36
+ * Turn a response into its decoded JSON body, throwing the matching
37
+ * `TeslaFleetError` for an error status and `ResponseError` for a
38
+ * successful response whose body is not JSON.
39
+ */
40
+ _response(resp: Response): Promise<any>;
41
+ /**
42
+ * Make a request to the Tesla Fleet API.
43
+ * @param method
44
+ * @param path
45
+ * @param params
46
+ * @param json
47
+ * @returns The decoded JSON body.
48
+ * @throws A `TeslaFleetError` subclass for an error status, or
49
+ * `TeslaFleetConnectionError` (with the original error as `cause`) when the
50
+ * request could not complete or timed out.
51
+ */
52
+ _request(method: Method, path: string, params?: Record<string, any> | null, json?: Record<string, any> | null): Promise<any>;
53
+ /**
54
+ * Returns products mapped to user.
55
+ * @returns An array of products including both vehicles and energy sites.
56
+ */
57
+ products(): Promise<ProductsResponse>;
58
+ /**
59
+ * Return Tesla products separated into vehicles and energy_sites
60
+ * @returns A dictionary of vehicles and energy sites, each with an array of those products
61
+ */
62
+ products_by_type(): Promise<ProductsByType>;
63
+ loadPrivateKey(path: string): Promise<void>;
64
+ }
@@ -0,0 +1,64 @@
1
+ import Charging from "./charging.mjs";
2
+ import Energy from "./energy.mjs";
3
+ import Partner from "./partner.mjs";
4
+ import User from "./user.mjs";
5
+ import Vehicle from "./vehicle.mjs";
6
+ import { Method, Region } from "./const.mjs";
7
+ import { EnergyProduct, ProductsResponse, VehicleProduct } from "./types/products.mjs";
8
+ export type ProductsByType = {
9
+ vehicles: VehicleProduct[];
10
+ energy_sites: EnergyProduct[];
11
+ };
12
+ export default class TeslaFleetApi {
13
+ server: string | null;
14
+ accessToken: string | null;
15
+ charging?: Charging;
16
+ energy?: Energy;
17
+ partner?: Partner;
18
+ user?: User;
19
+ vehicle?: Vehicle;
20
+ debug: boolean;
21
+ /** Abort a request (including reading its body) after this many milliseconds; `null` waits indefinitely. */
22
+ timeout: number | null;
23
+ constructor(options: {
24
+ accessToken?: string;
25
+ region?: Region;
26
+ server?: string;
27
+ chargingScope?: boolean;
28
+ energyScope?: boolean;
29
+ partnerScope?: boolean;
30
+ userScope?: boolean;
31
+ vehicleScope?: boolean;
32
+ debug?: boolean;
33
+ timeout?: number;
34
+ });
35
+ /**
36
+ * Turn a response into its decoded JSON body, throwing the matching
37
+ * `TeslaFleetError` for an error status and `ResponseError` for a
38
+ * successful response whose body is not JSON.
39
+ */
40
+ _response(resp: Response): Promise<any>;
41
+ /**
42
+ * Make a request to the Tesla Fleet API.
43
+ * @param method
44
+ * @param path
45
+ * @param params
46
+ * @param json
47
+ * @returns The decoded JSON body.
48
+ * @throws A `TeslaFleetError` subclass for an error status, or
49
+ * `TeslaFleetConnectionError` (with the original error as `cause`) when the
50
+ * request could not complete or timed out.
51
+ */
52
+ _request(method: Method, path: string, params?: Record<string, any> | null, json?: Record<string, any> | null): Promise<any>;
53
+ /**
54
+ * Returns products mapped to user.
55
+ * @returns An array of products including both vehicles and energy sites.
56
+ */
57
+ products(): Promise<ProductsResponse>;
58
+ /**
59
+ * Return Tesla products separated into vehicles and energy_sites
60
+ * @returns A dictionary of vehicles and energy sites, each with an array of those products
61
+ */
62
+ products_by_type(): Promise<ProductsByType>;
63
+ loadPrivateKey(path: string): Promise<void>;
64
+ }