tesla-fleet-api 0.0.3__py3-none-any.whl → 0.0.5__py3-none-any.whl

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.
@@ -1,4 +1,3 @@
1
- from .TeslaFleetApi import TeslaFleetApi
2
- from .TeslaFleetOAuth import TeslaFleetOAuth
3
- from .Teslemetry import Teslemetry
4
- from .exceptions import TeslaFleetError
1
+ from .teslafleetapi import TeslaFleetApi
2
+ from .teslafleetoauth import TeslaFleetOAuth
3
+ from .teslemetry import Teslemetry
@@ -0,0 +1,57 @@
1
+ from typing import Any
2
+ from .const import (
3
+ Methods,
4
+ )
5
+
6
+
7
+ class Charging:
8
+ """Class describing the Tesla Fleet API charging endpoints."""
9
+
10
+ def __init__(self, parent):
11
+ self._request = parent._request
12
+
13
+ async def history(
14
+ self,
15
+ vin: str | None = None,
16
+ startTime: str | None = None,
17
+ endTime: str | None = None,
18
+ pageNo: int | None = None,
19
+ pageSize: int | None = None,
20
+ sortBy: str | None = None,
21
+ sortOrder: str | None = None,
22
+ ) -> dict[str, Any]:
23
+ """Returns the paginated charging history."""
24
+ return await self._request(
25
+ Methods.GET,
26
+ "api/1/dx/charging/history",
27
+ {
28
+ vin: vin,
29
+ startTime: startTime,
30
+ endTime: endTime,
31
+ pageNo: pageNo,
32
+ pageSize: pageSize,
33
+ sortBy: sortBy,
34
+ sortOrder: sortOrder,
35
+ },
36
+ )
37
+
38
+ async def sessions(
39
+ self,
40
+ vin: str | None = None,
41
+ date_from: str | None = None,
42
+ date_to: str | None = None,
43
+ limit: int | None = None,
44
+ offset: int | None = None,
45
+ ) -> dict[str, Any]:
46
+ """Returns the charging session information including pricing and energy data. This endpoint is only available for business accounts that own a fleet of vehicles."""
47
+ return await self._request(
48
+ Methods.GET,
49
+ "api/1/dx/charging/sessions",
50
+ {
51
+ vin: vin,
52
+ date_from: date_from,
53
+ date_to: date_to,
54
+ limit: limit,
55
+ offset: offset,
56
+ },
57
+ )
tesla_fleet_api/const.py CHANGED
@@ -1,5 +1,13 @@
1
1
  """Tesla Fleet API constants."""
2
- from enum import StrEnum
2
+ from enum import StrEnum, IntEnum
3
+
4
+
5
+ class Methods(StrEnum):
6
+ """HTTP methods."""
7
+
8
+ GET = "GET"
9
+ POST = "POST"
10
+ DELETE = "DELETE"
3
11
 
4
12
 
5
13
  class Errors(StrEnum):
@@ -20,3 +28,229 @@ SERVERS = {
20
28
  "eu": "https://fleet-api.prd.eu.vn.cloud.tesla.com",
21
29
  "cn": "https://fleet-api.prd.cn.vn.cloud.tesla.cn",
22
30
  }
31
+
32
+
33
+ class Trunks(StrEnum):
34
+ """Trunk options"""
35
+
36
+ FRONT: "front"
37
+ REAR: "rear"
38
+
39
+
40
+ class ClimateKeeperMode(IntEnum):
41
+ """Climate Keeper Mode options"""
42
+
43
+ OFF = 0
44
+ KEEP_MODE = 1
45
+ DOG_MODE = 2
46
+ CAMP_MODE = 3
47
+
48
+
49
+ class CabinOverheatProtectionTemps(IntEnum):
50
+ """COP Temp options"""
51
+
52
+ LOW = 0 # 30C 90F
53
+ MEDIUM = 1 # 35C 95F
54
+ HIGH = 2 # 40C 100F
55
+
56
+
57
+ class VehicleDataEndpoints(StrEnum):
58
+ """Endpoints options"""
59
+
60
+ CHARGE_STATE = "charge_state"
61
+ CLIMATE_STATE = "climate_state"
62
+ CLOSURES_STATE = "closures_state"
63
+ DRIVE_STATE = "drive_state"
64
+ GUI_SETTINGS = "gui_settings"
65
+ LOCATION_DATA = "location_data"
66
+ VEHICLE_CONFIG = "vehicle_config"
67
+ VEHICLE_STATE = "vehicle_state"
68
+ VEHICLE_DATA_COMBO = "vehicle_data_combo"
69
+
70
+
71
+ class SunRoofCommands(StrEnum):
72
+ """Sunroof options"""
73
+
74
+ STOP = "stop"
75
+ CLOSE = "close"
76
+ VENT = "vent"
77
+
78
+
79
+ class WindowCommands(StrEnum):
80
+ """Window Control options"""
81
+
82
+ VENT = "vent"
83
+ CLOSE = "close"
84
+
85
+
86
+ class DeviceTypes(StrEnum):
87
+ """Device Type options"""
88
+
89
+ ANDROID = "android"
90
+ IOS_DEVELOPMENT = "ios-development"
91
+ IOS_ENTERPRISE = "ios-enterprise"
92
+ IOS_BETA = "ios-beta"
93
+ IOS_PRODUCTION = "ios-production"
94
+
95
+
96
+ class TelemetryFields(StrEnum):
97
+ """Fields available in telemetry streams"""
98
+
99
+ AC_CHARGING_ENERGY_IN = "ACChargingEnergyIn"
100
+ AC_CHARGING_POWER = "ACChargingPower"
101
+ AUTO_SEAT_CLIMATE_LEFT = "AutoSeatClimateLeft"
102
+ AUTO_SEAT_CLIMATE_RIGHT = "AutoSeatClimateRight"
103
+ AUTOMATIC_BLIND_SPOT_CAMERA = "AutomaticBlindSpotCamera"
104
+ AUTOMATIC_EMERGENCY_BRAKING_OFF = "AutomaticEmergencyBrakingOff"
105
+ BMS_STATE = "BMSState"
106
+ BATTERY_HEATER_ON = "BatteryHeaterOn"
107
+ BATTERY_LEVEL = "BatteryLevel"
108
+ BLIND_SPOT_COLLISION_WARNING_CHIME = "BlindSpotCollisionWarningChime"
109
+ BMS_FULL_CHARGE_COMPLETE = "BmsFullchargecomplete"
110
+ BRAKE_PEDAL = "BrakePedal"
111
+ BRAKE_PEDAL_POS = "BrakePedalPos"
112
+ BRICK_VOLTAGE_MAX = "BrickVoltageMax"
113
+ BRICK_VOLTAGE_MIN = "BrickVoltageMin"
114
+ CAR_TYPE = "CarType"
115
+ CHARGE_AMPS = "ChargeAmps"
116
+ CHARGE_CURRENT_REQUEST = "ChargeCurrentRequest"
117
+ CHARGE_CURRENT_REQUEST_MAX = "ChargeCurrentRequestMax"
118
+ CHARGE_ENABLE_REQUEST = "ChargeEnableRequest"
119
+ CHARGE_LIMIT_SOC = "ChargeLimitSoc"
120
+ CHARGE_PORT = "ChargePort"
121
+ CHARGE_PORT_COLD_WEATHER_MODE = "ChargePortColdWeatherMode"
122
+ CHARGE_PORT_LATCH = "ChargePortLatch"
123
+ CHARGE_STATE = "ChargeState"
124
+ CHARGER_PHASES = "ChargerPhases"
125
+ CRUISE_FOLLOW_DISTANCE = "CruiseFollowDistance"
126
+ CRUISE_SET_SPEED = "CruiseSetSpeed"
127
+ CRUISE_STATE = "CruiseState"
128
+ CURRENT_LIMIT_MPH = "CurrentLimitMph"
129
+ DC_CHARGING_ENERGY_IN = "DCChargingEnergyIn"
130
+ DC_CHARGING_POWER = "DCChargingPower"
131
+ DC_DC_ENABLE = "DCDCEnable"
132
+ DESTINATION_LOCATION = "DestinationLocation"
133
+ DI_AXLE_SPEED_F = "DiAxleSpeedF"
134
+ DI_AXLE_SPEED_R = "DiAxleSpeedR"
135
+ DI_AXLE_SPEED_REL = "DiAxleSpeedREL"
136
+ DI_AXLE_SPEED_RER = "DiAxleSpeedRER"
137
+ DI_HEATSINK_TF = "DiHeatsinkTF"
138
+ DI_HEATSINK_TR = "DiHeatsinkTR"
139
+ DI_HEATSINK_TREL = "DiHeatsinkTREL"
140
+ DI_HEATSINK_TRER = "DiHeatsinkTRER"
141
+ DI_MOTOR_CURRENT_F = "DiMotorCurrentF"
142
+ DI_MOTOR_CURRENT_R = "DiMotorCurrentR"
143
+ DI_MOTOR_CURRENT_REL = "DiMotorCurrentREL"
144
+ DI_MOTOR_CURRENT_RER = "DiMotorCurrentRER"
145
+ DI_SLAVE_TORQUE_CMD = "DiSlaveTorqueCmd"
146
+ DI_STATE_F = "DiStateF"
147
+ DI_STATE_R = "DiStateR"
148
+ DI_STATE_REL = "DiStateREL"
149
+ DI_STATE_RER = "DiStateRER"
150
+ DI_STATOR_TEMP_F = "DiStatorTempF"
151
+ DI_STATOR_TEMP_R = "DiStatorTempR"
152
+ DI_STATOR_TEMP_REL = "DiStatorTempREL"
153
+ DI_STATOR_TEMP_RER = "DiStatorTempRER"
154
+ DI_TORQUE_ACTUAL_F = "DiTorqueActualF"
155
+ DI_TORQUE_ACTUAL_R = "DiTorqueActualR"
156
+ DI_TORQUE_ACTUAL_REL = "DiTorqueActualREL"
157
+ DI_TORQUE_ACTUAL_RER = "DiTorqueActualRER"
158
+ DI_TORQUEMOTOR = "DiTorquemotor"
159
+ DI_V_BAT_F = "DiVBatF"
160
+ DI_V_BAT_R = "DiVBatR"
161
+ DI_V_BAT_REL = "DiVBatREL"
162
+ DI_V_BAT_RER = "DiVBatRER"
163
+ DOOR_STATE = "DoorState"
164
+ DRIVE_RAIL = "DriveRail"
165
+ DRIVER_SEAT_BELT = "DriverSeatBelt"
166
+ DRIVER_SEAT_OCCUPIED = "DriverSeatOccupied"
167
+ EMERGENCY_LANE_DEPARTURE_AVOIDANCE = "EmergencyLaneDepartureAvoidance"
168
+ ENERGY_REMAINING = "EnergyRemaining"
169
+ EST_BATTERY_RANGE = "EstBatteryRange"
170
+ EXPERIMENTAL_1 = "Experimental_1"
171
+ EXPERIMENTAL_2 = "Experimental_2"
172
+ EXPERIMENTAL_3 = "Experimental_3"
173
+ EXPERIMENTAL_4 = "Experimental_4"
174
+ EXTERIOR_COLOR = "ExteriorColor"
175
+ FAST_CHARGER_PRESENT = "FastChargerPresent"
176
+ FD_WINDOW = "FdWindow"
177
+ FORWARD_COLLISION_WARNING = "ForwardCollisionWarning"
178
+ FP_WINDOW = "FpWindow"
179
+ GEAR = "Gear"
180
+ GPS_HEADING = "GpsHeading"
181
+ GPS_STATE = "GpsState"
182
+ GUEST_MODE_ENABLED = "GuestModeEnabled"
183
+ GUEST_MODE_MOBILE_ACCESS_STATE = "GuestModeMobileAccessState"
184
+ HVIL = "Hvil"
185
+ IDEAL_BATTERY_RANGE = "IdealBatteryRange"
186
+ INSIDE_TEMP = "InsideTemp"
187
+ ISOLATION_RESISTANCE = "IsolationResistance"
188
+ LANE_DEPARTURE_AVOIDANCE = "LaneDepartureAvoidance"
189
+ LATERAL_ACCELERATION = "LateralAcceleration"
190
+ LIFETIME_ENERGY_GAINED_REGEN = "LifetimeEnergyGainedRegen"
191
+ LIFETIME_ENERGY_USED = "LifetimeEnergyUsed"
192
+ LIFETIME_ENERGY_USED_DRIVE = "LifetimeEnergyUsedDrive"
193
+ LOCATION = "Location"
194
+ LOCKED = "Locked"
195
+ LONGITUDINAL_ACCELERATION = "LongitudinalAcceleration"
196
+ MILES_TO_ARRIVAL = "MilesToArrival"
197
+ MINUTES_TO_ARRIVAL = "MinutesToArrival"
198
+ MODULE_TEMP_MAX = "ModuleTempMax"
199
+ MODULE_TEMP_MIN = "ModuleTempMin"
200
+ NOT_ENOUGH_POWER_TO_HEAT = "NotEnoughPowerToHeat"
201
+ NUM_BRICK_VOLTAGE_MAX = "NumBrickVoltageMax"
202
+ NUM_BRICK_VOLTAGE_MIN = "NumBrickVoltageMin"
203
+ NUM_MODULE_TEMP_MAX = "NumModuleTempMax"
204
+ NUM_MODULE_TEMP_MIN = "NumModuleTempMin"
205
+ ODOMETER = "Odometer"
206
+ ORIGIN_LOCATION = "OriginLocation"
207
+ OUTSIDE_TEMP = "OutsideTemp"
208
+ PACK_CURRENT = "PackCurrent"
209
+ PACK_VOLTAGE = "PackVoltage"
210
+ PAIRED_PHONE_KEY_AND_KEY_FOB_QTY = "PairedPhoneKeyAndKeyFobQty"
211
+ PASSENGER_SEAT_BELT = "PassengerSeatBelt"
212
+ PEDAL_POSITION = "PedalPosition"
213
+ PIN_TO_DRIVE_ENABLED = "PinToDriveEnabled"
214
+ PRECONDITIONING_ENABLED = "PreconditioningEnabled"
215
+ RATED_RANGE = "RatedRange"
216
+ RD_WINDOW = "RdWindow"
217
+ ROOF_COLOR = "RoofColor"
218
+ ROUTE_LAST_UPDATED = "RouteLastUpdated"
219
+ ROUTE_LINE = "RouteLine"
220
+ RP_WINDOW = "RpWindow"
221
+ SCHEDULED_CHARGING_MODE = "ScheduledChargingMode"
222
+ SCHEDULED_CHARGING_PENDING = "ScheduledChargingPending"
223
+ SCHEDULED_CHARGING_START_TIME = "ScheduledChargingStartTime"
224
+ SCHEDULED_DEPARTURE_TIME = "ScheduledDepartureTime"
225
+ SEAT_HEATER_LEFT = "SeatHeaterLeft"
226
+ SEAT_HEATER_REAR_CENTER = "SeatHeaterRearCenter"
227
+ SEAT_HEATER_REAR_LEFT = "SeatHeaterRearLeft"
228
+ SEAT_HEATER_REAR_RIGHT = "SeatHeaterRearRight"
229
+ SEAT_HEATER_RIGHT = "SeatHeaterRight"
230
+ SENTRY_MODE = "SentryMode"
231
+ SERVICE_MODE = "ServiceMode"
232
+ SOC = "Soc"
233
+ SPEED_LIMIT_MODE = "SpeedLimitMode"
234
+ SPEED_LIMIT_WARNING = "SpeedLimitWarning"
235
+ SUPERCHARGER_SESSION_TRIP_PLANNER = "SuperchargerSessionTripPlanner"
236
+ TIME_TO_FULL_CHARGE = "TimeToFullCharge"
237
+ TPMS_LAST_SEEN_PRESSURE_TIME_FL = "TpmsLastSeenPressureTimeFl"
238
+ TPMS_LAST_SEEN_PRESSURE_TIME_FR = "TpmsLastSeenPressureTimeFr"
239
+ TPMS_LAST_SEEN_PRESSURE_TIME_RL = "TpmsLastSeenPressureTimeRl"
240
+ TPMS_LAST_SEEN_PRESSURE_TIME_RR = "TpmsLastSeenPressureTimeRr"
241
+ TPMS_PRESSURE_FL = "TpmsPressureFl"
242
+ TPMS_PRESSURE_FR = "TpmsPressureFr"
243
+ TPMS_PRESSURE_RL = "TpmsPressureRl"
244
+ TPMS_PRESSURE_RR = "TpmsPressureRr"
245
+ TRIM = "Trim"
246
+ VEHICLE_NAME = "VehicleName"
247
+ VEHICLE_SPEED = "VehicleSpeed"
248
+ VERSION = "Version"
249
+
250
+
251
+ class TelemetryAlerts(StrEnum):
252
+ """Alerts available in telemetry streams"""
253
+
254
+ CUSTOMER = "Customer"
255
+ SERVICE = "Service"
256
+ SERVICE_FIX = "ServiceFix"
@@ -0,0 +1,130 @@
1
+ from typing import Any
2
+ from .const import Methods
3
+
4
+
5
+ class Energy:
6
+ """Class describing the Tesla Fleet API partner endpoints"""
7
+
8
+ def __init__(self, parent):
9
+ self._request = parent._request
10
+
11
+ async def backup(
12
+ self, energy_site_id: int, backup_reserve_percent: int
13
+ ) -> dict[str, Any]:
14
+ """Adjust the site's backup reserve."""
15
+ return await self._request(
16
+ Methods.POST,
17
+ f"api/1/energy_sites/{energy_site_id}/backup",
18
+ data={backup_reserve_percent: backup_reserve_percent},
19
+ )
20
+
21
+ async def backup_history(
22
+ self,
23
+ energy_site_id: int,
24
+ kind: str,
25
+ start_date: str,
26
+ end_date: str,
27
+ period: str,
28
+ time_zone: str,
29
+ ) -> dict[str, Any]:
30
+ """Returns the backup (off-grid) event history of the site in duration of seconds."""
31
+ return await self._request(
32
+ Methods.GET,
33
+ f"api/1/energy_sites/{energy_site_id}/calendar_history?kind={kind}&start_date={start_date}&end_date={end_date}&period={period}&time_zone={time_zone}",
34
+ )
35
+
36
+ async def charge_history(
37
+ self,
38
+ energy_site_id: int,
39
+ kind: str,
40
+ start_date: str,
41
+ end_date: str,
42
+ time_zone: str,
43
+ ) -> dict[str, Any]:
44
+ """Returns the charging history of a wall connector."""
45
+ return await self._request(
46
+ Methods.GET,
47
+ f"api/1/energy_sites/{energy_site_id}/telemetry_history?kind={kind}&start_date={start_date}&end_date={end_date}&time_zone={time_zone}",
48
+ )
49
+
50
+ async def energy_history(
51
+ self,
52
+ energy_site_id: int,
53
+ kind: str,
54
+ start_date: str,
55
+ end_date: str,
56
+ period: str,
57
+ time_zone: str,
58
+ ) -> dict[str, Any]:
59
+ """Returns the energy measurements of the site, aggregated to the requested period."""
60
+ return await self._request(
61
+ Methods.GET,
62
+ f"api/1/energy_sites/{energy_site_id}/calendar_history?kind={kind}&start_date={start_date}&end_date={end_date}&period={period}&time_zone={time_zone}",
63
+ )
64
+
65
+ async def grid_import_export(
66
+ self,
67
+ energy_site_id: int,
68
+ disallow_charge_from_grid_with_solar_installed: bool,
69
+ customer_preferred_export_rule: str,
70
+ ) -> dict[str, Any]:
71
+ """Allow/disallow charging from the grid and exporting energy to the grid."""
72
+ return await self._request(
73
+ Methods.POST,
74
+ f"api/1/energy_sites/{energy_site_id}/grid_import_export",
75
+ data={
76
+ disallow_charge_from_grid_with_solar_installed: disallow_charge_from_grid_with_solar_installed,
77
+ customer_preferred_export_rule: customer_preferred_export_rule,
78
+ },
79
+ )
80
+
81
+ async def live_status(self, energy_site_id: int) -> dict[str, Any]:
82
+ """Returns the live status of the site (power, state of energy, grid status, storm mode)."""
83
+ return await self._request(
84
+ Methods.GET,
85
+ f"api/1/energy_sites/{energy_site_id}/live_status",
86
+ )
87
+
88
+ async def off_grid_vehicle_charging_reserve(
89
+ self, energy_site_id: int, off_grid_vehicle_charging_reserve_percent: int
90
+ ) -> dict[str, Any]:
91
+ """Adjust the site's off-grid vehicle charging backup reserve."""
92
+ return await self._request(
93
+ Methods.POST,
94
+ f"api/1/energy_sites/{energy_site_id}/off_grid_vehicle_charging_reserve",
95
+ data={
96
+ off_grid_vehicle_charging_reserve_percent: off_grid_vehicle_charging_reserve_percent
97
+ },
98
+ )
99
+
100
+ async def operation(
101
+ self, energy_site_id: int, default_real_mode: str
102
+ ) -> dict[str, Any]:
103
+ """Set the site's mode."""
104
+ return await self._request(
105
+ Methods.POST,
106
+ f"api/1/energy_sites/{energy_site_id}/operation",
107
+ data={default_real_mode: default_real_mode},
108
+ )
109
+
110
+ async def products(self) -> dict[str, Any]:
111
+ """Returns products mapped to user."""
112
+ return await self._request(
113
+ Methods.GET,
114
+ "api/1/products",
115
+ )
116
+
117
+ async def site_info(self, energy_site_id: int) -> dict[str, Any]:
118
+ """Returns information about the site. Things like assets (has solar, etc), settings (backup reserve, etc), and features (storm_mode_capable, etc)."""
119
+ return await self._request(
120
+ Methods.GET,
121
+ f"api/1/energy_sites/{energy_site_id}/site_info",
122
+ )
123
+
124
+ async def storm_mode(self, energy_site_id: int, enabled: bool) -> dict[str, Any]:
125
+ """Update storm watch participation."""
126
+ return await self._request(
127
+ Methods.POST,
128
+ f"api/1/energy_sites/{energy_site_id}/storm_mode",
129
+ data={enabled: enabled},
130
+ )
@@ -211,16 +211,16 @@ async def raise_for_status(resp: aiohttp.ClientResponse) -> None:
211
211
  """Raise an exception if the response status code is >=400."""
212
212
  # https://developer.tesla.com/docs/fleet-api#response-codes
213
213
 
214
- if e.status == 401 and resp.content_length == 0:
214
+ if resp.status == 401 and resp.content_length == 0:
215
215
  # This error does not return a body
216
- raise OAuthExpired() from e
216
+ raise OAuthExpired()
217
217
 
218
218
  data = await resp.json()
219
219
 
220
220
  try:
221
221
  resp.raise_for_status()
222
222
  except aiohttp.ClientResponseError as e:
223
- if e.status == 400:
223
+ if resp.status == 400:
224
224
  if data.error == Errors.INVALID_COMMAND:
225
225
  raise InvalidCommand(data) from e
226
226
  elif data.error == Errors.INVALID_FIELD:
@@ -233,44 +233,44 @@ async def raise_for_status(resp: aiohttp.ClientResponse) -> None:
233
233
  raise InvalidRedirectUrl(data) from e
234
234
  elif data.error == Errors.UNAUTHORIZED_CLIENT:
235
235
  raise UnauthorizedClient(data) from e
236
- elif e.status == 401:
236
+ elif resp.status == 401:
237
237
  if data.error == Errors.MOBILE_ACCESS_DISABLED:
238
238
  raise MobileAccessDisabled(data) from e
239
239
  elif data.error == Errors.INVALID_TOKEN:
240
240
  raise InvalidToken(data) from e
241
- elif e.status == 402:
241
+ elif resp.status == 402:
242
242
  raise PaymentRequired(data) from e
243
- elif e.status == 403:
243
+ elif resp.status == 403:
244
244
  raise Forbidden(data) from e
245
- elif e.status == 404:
245
+ elif resp.status == 404:
246
246
  raise NotFound(data) from e
247
- elif e.status == 405:
247
+ elif resp.status == 405:
248
248
  raise NotAllowed(data) from e
249
- elif e.status == 406:
249
+ elif resp.status == 406:
250
250
  raise NotAcceptable(data) from e
251
- elif e.status == 408:
251
+ elif resp.status == 408:
252
252
  raise VehicleOffline(data) from e
253
- elif e.status == 412:
253
+ elif resp.status == 412:
254
254
  raise PreconditionFailed(data) from e
255
- elif e.status == 421:
255
+ elif resp.status == 421:
256
256
  raise InvalidRegion(data) from e
257
- elif e.status == 422:
257
+ elif resp.status == 422:
258
258
  raise InvalidResource(data) from e
259
- elif e.status == 423:
259
+ elif resp.status == 423:
260
260
  raise Locked(data) from e
261
- elif e.status == 429:
261
+ elif resp.status == 429:
262
262
  raise RateLimited(data) from e
263
- elif e.status == 451:
263
+ elif resp.status == 451:
264
264
  raise ResourceUnavailableForLegalReasons(data) from e
265
- elif e.status == 499:
265
+ elif resp.status == 499:
266
266
  raise ClientClosedRequest(data) from e
267
- elif e.status == 500:
267
+ elif resp.status == 500:
268
268
  raise InternalServerError(data) from e
269
- elif e.status == 503:
269
+ elif resp.status == 503:
270
270
  raise ServiceUnavailable(data) from e
271
- elif e.status == 504:
271
+ elif resp.status == 504:
272
272
  raise GatewayTimeout(data) from e
273
- elif e.status == 540:
273
+ elif resp.status == 540:
274
274
  raise DeviceUnexpectedResponse(data) from e
275
275
  else:
276
276
  raise e
@@ -0,0 +1,21 @@
1
+ from typing import Any
2
+ from .const import Methods
3
+
4
+
5
+ class Partner:
6
+ """Class describing the Tesla Fleet API partner endpoints"""
7
+
8
+ def __init__(self, parent):
9
+ self._request = parent._request
10
+
11
+ async def public_key(self, domain: str | None = None) -> dict[str, Any]:
12
+ """Returns the public key associated with a domain. It can be used to ensure the registration was successful."""
13
+ return await self._request(
14
+ Methods.GET, "api/1/partner_accounts/public_key", data={domain: domain}
15
+ )
16
+
17
+ async def register(self, domain: str) -> dict[str, Any]:
18
+ """Registers an existing account before it can be used for general API access. Each application from developer.tesla.com must complete this step."""
19
+ return await self._request(
20
+ Methods.POST, "api/1/partner_accounts", data={domain: domain}
21
+ )
@@ -0,0 +1,116 @@
1
+ import aiohttp
2
+ from .exceptions import raise_for_status, InvalidRegion, LibraryError
3
+ from typing import Any
4
+ from .const import (
5
+ SERVERS,
6
+ Methods,
7
+ )
8
+ from .charging import Charging
9
+ from .energy import Energy
10
+ from .partner import Partner
11
+ from .user import User
12
+ from .vehicle import Vehicle
13
+ from .vehiclespecific import VehicleSpecific
14
+
15
+
16
+ # Based on https://developer.tesla.com/docs/fleet-api
17
+ class TeslaFleetApi:
18
+ """Class describing the Tesla Fleet API."""
19
+
20
+ server: str
21
+ session: aiohttp.ClientSession
22
+ headers: dict[str, str]
23
+ raise_for_status: bool
24
+ vehicles: list[VehicleSpecific]
25
+
26
+ def __init__(
27
+ self,
28
+ session: aiohttp.ClientSession,
29
+ access_token: str,
30
+ use_command_protocol: bool = False,
31
+ region: str | None = None,
32
+ server: str | None = None,
33
+ raise_for_status: bool = True,
34
+ charging_scope: bool = True,
35
+ energy_scope: bool = True,
36
+ partner_scope: bool = True,
37
+ user_scope: bool = True,
38
+ vehicle_scope: bool = True,
39
+ ):
40
+ """Initialize the Tesla Fleet API."""
41
+
42
+ self.session = session
43
+ self.access_token = access_token
44
+ self.use_command_protocol = use_command_protocol
45
+
46
+ if region and not server and region not in SERVERS:
47
+ raise ValueError(f"Region must be one of {", ".join(SERVERS.keys())}")
48
+ self.server = server or SERVERS.get(region)
49
+ self.raise_for_status = raise_for_status
50
+
51
+ if charging_scope:
52
+ self.charging = Charging(self)
53
+ if energy_scope:
54
+ self.energy = Energy(self)
55
+ if user_scope:
56
+ self.user = User(self)
57
+ if partner_scope:
58
+ self.partner = Partner(self)
59
+ if vehicle_scope:
60
+ self.vehicle = Vehicle(self)
61
+
62
+ async def find_server(self) -> None:
63
+ """Find the server URL for the Tesla Fleet API."""
64
+ for server in SERVERS.values():
65
+ self.server = server
66
+ try:
67
+ await self.user.region()
68
+ return
69
+ except InvalidRegion:
70
+ continue
71
+ raise LibraryError("Could not find a valid Tesla API server.")
72
+
73
+ async def _request(
74
+ self,
75
+ method: Methods,
76
+ path: str,
77
+ params: dict[str:Any] | None = None,
78
+ data: dict[str:Any] | None = None,
79
+ json: dict[str:Any] | None = None,
80
+ ):
81
+ """Send a request to the Tesla Fleet API."""
82
+
83
+ if not self.server:
84
+ raise ValueError("Server was not set at init. Call find_server() first.")
85
+
86
+ if method == Methods.GET and (data is not None or json is not None):
87
+ raise ValueError("GET requests cannot have data or json parameters.")
88
+
89
+ if params:
90
+ params = {k: v for k, v in params.items() if v is not None}
91
+ if data:
92
+ data = {k: v for k, v in data.items() if v is not None}
93
+ if json:
94
+ json = {k: v for k, v in json.items() if v is not None}
95
+
96
+ async with self.session.request(
97
+ method,
98
+ f"{self.server}/{path}",
99
+ headers={
100
+ "Authorization": f"Bearer {self.access_token}",
101
+ "Content-Type": "application/json",
102
+ },
103
+ data=data,
104
+ json=json,
105
+ params=params,
106
+ ) as resp:
107
+ if self.raise_for_status:
108
+ await raise_for_status(resp)
109
+ return await resp.json()
110
+
111
+ async def status(self):
112
+ """This endpoint returns the string "ok" if the API is operating normally. No HTTP headers are required."""
113
+ if not self.server:
114
+ raise ValueError("Server was not set at init. Call find_server() first.")
115
+ async with self.session.get(f"{self.server}/status") as resp:
116
+ return await resp.text()
@@ -1,6 +1,6 @@
1
1
  import aiohttp
2
2
  import datetime
3
- from .TeslaFleetApi import TeslaFleetApi
3
+ from .teslafleetapi import TeslaFleetApi
4
4
 
5
5
 
6
6
  class TeslaFleetOAuth(TeslaFleetApi):