pytoyoda.api

Toyota Connected Services API.

  1"""Toyota Connected Services API."""
  2
  3from datetime import date, datetime, timezone
  4from typing import Any, TypeVar
  5from uuid import uuid4
  6
  7from loguru import logger
  8
  9from pytoyoda.const import (
 10    VEHICLE_ASSOCIATION_ENDPOINT,
 11    VEHICLE_CLIMATE_CONTROL_ENDPOINT,
 12    VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
 13    VEHICLE_CLIMATE_STATUS_ENDPOINT,
 14    VEHICLE_CLIMATE_STATUS_REFRESH_ENDPOINT,
 15    VEHICLE_COMMAND_ENDPOINT,
 16    VEHICLE_GLOBAL_REMOTE_ELECTRIC_CONTROL_ENDPOINT,
 17    VEHICLE_GLOBAL_REMOTE_ELECTRIC_REALTIME_STATUS_ENDPOINT,
 18    VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
 19    VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
 20    VEHICLE_GUID_ENDPOINT,
 21    VEHICLE_HEALTH_STATUS_ENDPOINT,
 22    VEHICLE_LOCATION_ENDPOINT,
 23    VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
 24    VEHICLE_SERVICE_HISTORY_ENDPONT,
 25    VEHICLE_TELEMETRY_ENDPOINT,
 26    VEHICLE_TRIPS_ENDPOINT,
 27)
 28from pytoyoda.controller import Controller
 29from pytoyoda.models.endpoints.climate import (
 30    ClimateControlModel,
 31    ClimateSettingsModel,
 32    ClimateSettingsResponseModel,
 33    ClimateStatusResponseModel,
 34)
 35from pytoyoda.models.endpoints.command import CommandType, RemoteCommandModel
 36from pytoyoda.models.endpoints.common import StatusModel
 37from pytoyoda.models.endpoints.electric import (
 38    ElectricCommandResponseModel,
 39    ElectricResponseModel,
 40    NextChargeSettings,
 41)
 42from pytoyoda.models.endpoints.location import LocationResponseModel
 43from pytoyoda.models.endpoints.notifications import NotificationResponseModel
 44from pytoyoda.models.endpoints.service_history import ServiceHistoryResponseModel
 45from pytoyoda.models.endpoints.status import RemoteStatusResponseModel
 46from pytoyoda.models.endpoints.telemetry import TelemetryResponseModel
 47from pytoyoda.models.endpoints.trips import TripsResponseModel
 48from pytoyoda.models.endpoints.vehicle_guid import VehiclesResponseModel
 49from pytoyoda.models.endpoints.vehicle_health import VehicleHealthResponseModel
 50
 51# Type variable for generic model handling
 52T = TypeVar(
 53    "T",
 54    bound=StatusModel
 55    | ElectricCommandResponseModel
 56    | ElectricResponseModel
 57    | LocationResponseModel
 58    | NotificationResponseModel
 59    | ServiceHistoryResponseModel
 60    | RemoteStatusResponseModel
 61    | TelemetryResponseModel
 62    | TripsResponseModel
 63    | VehiclesResponseModel
 64    | VehicleHealthResponseModel,
 65)
 66
 67
 68class Api:
 69    """API for Toyota Connected Services.
 70
 71    This class provides access to Toyota Connected Services endpoints to
 72    retrieve and manipulate vehicle data.
 73
 74    """
 75
 76    def __init__(self, controller: Controller) -> None:
 77        """Initialize the API with a controller for communication.
 78
 79        Args:
 80            controller: A controller instance to manage the API communication
 81
 82        """
 83        self.controller = controller
 84
 85    async def _request_and_parse(
 86        self,
 87        model: type[T],
 88        method: str,
 89        endpoint: str,
 90        **kwargs,  # noqa : ANN003
 91    ) -> T:
 92        """Parse requests and responses using Pydantic models.
 93
 94        Args:
 95            model: Pydantic model class to parse response into
 96            method: HTTP method (GET, POST, PUT, DELETE)
 97            endpoint: API endpoint path
 98            **kwargs: Additional arguments to pass to request method
 99
100        Returns:
101            Parsed response data as specified model instance
102
103        """
104        response = await self.controller.request_json(
105            method=method, endpoint=endpoint, **kwargs
106        )
107        parsed_response = model(**response)
108        logger.debug(f"Parsed '{model.__name__}': {parsed_response}")
109        return parsed_response
110
111    async def _create_standard_headers(self) -> dict[str, str]:
112        """Create standard headers for API requests.
113
114        Returns:
115            Dictionary with standard headers
116
117        """
118        return {
119            "datetime": str(int(datetime.now(timezone.utc).timestamp() * 1000)),
120            "x-correlationid": str(uuid4()),
121            "Content-Type": "application/json",
122        }
123
124    # Vehicle Management
125
126    async def set_vehicle_alias(self, alias: str, guid: str, vin: str) -> Any:  # noqa :ANN401
127        """Set a nickname/alias for a vehicle.
128
129        Args:
130            alias: New nickname for the vehicle
131            guid: Global unique identifier for the vehicle
132            vin: Vehicle Identification Number
133
134        Returns:
135            Raw response from the API
136
137        """
138        return await self.controller.request_raw(
139            method="PUT",
140            endpoint=VEHICLE_ASSOCIATION_ENDPOINT,
141            vin=vin,
142            headers=await self._create_standard_headers(),
143            body={"guid": guid, "vin": vin, "nickName": alias},
144        )
145
146    async def get_vehicles(self) -> VehiclesResponseModel:
147        """Get a list of vehicles registered with the Toyota account.
148
149        Returns:
150            Model containing vehicle information
151
152        """
153        return await self._request_and_parse(
154            VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
155        )
156
157    # Vehicle Status and Information
158
159    async def get_location(self, vin: str) -> LocationResponseModel:
160        """Get the last known location of a vehicle.
161
162        Only updates when car is parked.
163        Includes latitude and longitude if supported.
164
165        Args:
166            vin: Vehicle Identification Number
167
168        Returns:
169            Model containing location information
170
171        """
172        return await self._request_and_parse(
173            LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
174        )
175
176    async def get_vehicle_health_status(self, vin: str) -> VehicleHealthResponseModel:
177        """Get the latest health status of a vehicle.
178
179        Includes engine oil quantity and dashboard warning lights if supported.
180
181        Args:
182            vin: Vehicle Identification Number
183
184        Returns:
185            Model containing vehicle health information
186
187        """
188        return await self._request_and_parse(
189            VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
190        )
191
192    async def get_remote_status(self, vin: str) -> RemoteStatusResponseModel:
193        """Get general information about a vehicle.
194
195        Args:
196            vin: Vehicle Identification Number
197
198        Returns:
199            Model containing general vehicle status
200
201        """
202        return await self._request_and_parse(
203            RemoteStatusResponseModel,
204            "GET",
205            VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
206            vin=vin,
207        )
208
209    async def get_vehicle_electric_status(self, vin: str) -> ElectricResponseModel:
210        """Get the latest electric status of a vehicle.
211
212        Includes current battery level, EV range, fuel level, and charging status.
213
214        Args:
215            vin: Vehicle Identification Number
216
217        Returns:
218            Model containing electric vehicle information
219
220        """
221        return await self._request_and_parse(
222            ElectricResponseModel,
223            "GET",
224            VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
225            vin=vin,
226        )
227
228    async def refresh_electric_realtime_status(self, vin: str) -> StatusModel:
229        """Update realtime SOC.
230
231        Only requests a updated soc
232
233        Args:
234            vin: Vehicle Identification Number
235
236        Returns:
237            Model containing status of the refresh request
238
239        """
240        return await self._request_and_parse(
241            StatusModel,
242            "POST",
243            VEHICLE_GLOBAL_REMOTE_ELECTRIC_REALTIME_STATUS_ENDPOINT,
244            vin=vin,
245        )
246
247    async def get_telemetry(self, vin: str) -> TelemetryResponseModel:
248        """Get the latest telemetry data for a vehicle.
249
250        Includes current fuel level, distance to empty, and odometer.
251
252        Args:
253            vin: Vehicle Identification Number
254
255        Returns:
256            Model containing telemetry information
257
258        """
259        return await self._request_and_parse(
260            TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
261        )
262
263    # Notifications and History
264
265    async def get_notifications(self, vin: str) -> NotificationResponseModel:
266        """Get all available notifications for a vehicle.
267
268        Includes message text, notification date, read flag, and date read.
269        Note: No way to mark notifications as read or limit the response.
270
271        Args:
272            vin: Vehicle Identification Number
273
274        Returns:
275            Model containing notification information
276
277        """
278        return await self._request_and_parse(
279            NotificationResponseModel,
280            "GET",
281            VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
282            vin=vin,
283        )
284
285    async def get_service_history(self, vin: str) -> ServiceHistoryResponseModel:
286        """Get the service history for a vehicle.
287
288        Includes service category, date, and dealer information.
289
290        Args:
291            vin: Vehicle Identification Number
292
293        Returns:
294            Model containing service history information
295
296        """
297        return await self._request_and_parse(
298            ServiceHistoryResponseModel, "GET", VEHICLE_SERVICE_HISTORY_ENDPONT, vin=vin
299        )
300
301    # Climate Control
302
303    async def get_climate_status(self, vin: str) -> ClimateStatusResponseModel:
304        """Get the current climate control status.
305
306        Note: Only returns data if climate control is on. If off,
307        it returns status == 0 and all other fields are None.
308
309        Args:
310            vin: Vehicle Identification Number
311
312        Returns:
313            Model containing climate status information
314
315        """
316        return await self._request_and_parse(
317            ClimateStatusResponseModel,
318            "GET",
319            VEHICLE_CLIMATE_STATUS_ENDPOINT,
320            vin=vin,
321        )
322
323    async def refresh_climate_status(self, vin: str) -> StatusModel:
324        """Request an update to the climate status from the vehicle.
325
326        Args:
327            vin: Vehicle Identification Number
328
329        Returns:
330            Model containing status of the refresh request
331
332        """
333        return await self._request_and_parse(
334            StatusModel, "POST", VEHICLE_CLIMATE_STATUS_REFRESH_ENDPOINT, vin=vin
335        )
336
337    async def get_climate_settings(self, vin: str) -> ClimateSettingsResponseModel:
338        """Get the current climate control settings.
339
340        Args:
341            vin: Vehicle Identification Number
342
343        Returns:
344            Model containing climate settings information
345
346        """
347        return await self._request_and_parse(
348            ClimateSettingsResponseModel,
349            "GET",
350            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
351            vin=vin,
352        )
353
354    async def update_climate_settings(
355        self, vin: str, settings: ClimateSettingsModel
356    ) -> StatusModel:
357        """Update the climate control settings for a vehicle.
358
359        Args:
360            vin: Vehicle Identification Number
361            settings: New climate control settings
362
363        Returns:
364            Model containing status of the update request
365
366        """
367        return await self._request_and_parse(
368            StatusModel,
369            "PUT",
370            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
371            vin=vin,
372            body=settings.model_dump(exclude_unset=True, by_alias=True),
373        )
374
375    async def send_climate_control_command(
376        self, vin: str, command: ClimateControlModel
377    ) -> StatusModel:
378        """Send a control command to the climate system.
379
380        Args:
381            vin: Vehicle Identification Number
382            command: Climate control command to send
383
384        Returns:
385            Model containing status of the command request
386
387        """
388        return await self._request_and_parse(
389            StatusModel,
390            "POST",
391            VEHICLE_CLIMATE_CONTROL_ENDPOINT,
392            vin=vin,
393            body=command.model_dump(exclude_unset=True, by_alias=True),
394        )
395
396    # Trip Data
397
398    async def get_trips(  # noqa: PLR0913
399        self,
400        vin: str,
401        from_date: date,
402        to_date: date,
403        route: bool = False,  # noqa : FBT001, FBT002
404        summary: bool = True,  # noqa : FBT001, FBT002
405        limit: int = 5,
406        offset: int = 0,
407    ) -> TripsResponseModel:
408        """Get a list of trips for a vehicle within a date range.
409
410        Args:
411            vin: Vehicle Identification Number
412            from_date: Start date for trip data (inclusive, cannot be in future)
413            to_date: End date for trip data (inclusive, cannot be in future)
414            route: If True, returns route coordinates for each trip
415            summary: If True, returns monthly and daily trip summaries
416            limit: Maximum number of trips to return (max 50)
417            offset: Starting offset for pagination
418
419        Returns:
420            Model containing trip information
421
422        """
423        endpoint = VEHICLE_TRIPS_ENDPOINT.format(
424            from_date=from_date,
425            to_date=to_date,
426            route=route,
427            summary=summary,
428            limit=limit,
429            offset=offset,
430        )
431        return await self._request_and_parse(
432            TripsResponseModel, "GET", endpoint, vin=vin
433        )
434
435    # Remote Commands
436
437    async def send_command(
438        self, vin: str, command: CommandType, beeps: int = 0
439    ) -> StatusModel:
440        """Send a remote command to a vehicle.
441
442        Args:
443            vin: Vehicle Identification Number
444            command: Type of command to send
445            beeps: Number of beeps for commands that support it
446
447        Returns:
448            Model containing status of the command request
449
450        """
451        remote_command = RemoteCommandModel(beep_count=beeps, command=command)
452        return await self._request_and_parse(
453            StatusModel,
454            "POST",
455            VEHICLE_COMMAND_ENDPOINT,
456            vin=vin,
457            body=remote_command.model_dump(exclude_unset=True, by_alias=True),
458        )
459
460    async def send_next_charging_command(
461        self, vin: str, command: NextChargeSettings
462    ) -> ElectricCommandResponseModel:
463        """Send the next charging start/end time to the vehicle.
464
465        Args:
466            vin: Vehicle Identification Number
467            command: NextChargeSettings command to send
468
469        Returns:
470            Model containing status of the command request
471
472        """
473        return await self._request_and_parse(
474            ElectricCommandResponseModel,
475            "POST",
476            VEHICLE_GLOBAL_REMOTE_ELECTRIC_CONTROL_ENDPOINT,
477            vin=vin,
478            body=command.model_dump(exclude_none=True, by_alias=True),
479        )
class Api:
 69class Api:
 70    """API for Toyota Connected Services.
 71
 72    This class provides access to Toyota Connected Services endpoints to
 73    retrieve and manipulate vehicle data.
 74
 75    """
 76
 77    def __init__(self, controller: Controller) -> None:
 78        """Initialize the API with a controller for communication.
 79
 80        Args:
 81            controller: A controller instance to manage the API communication
 82
 83        """
 84        self.controller = controller
 85
 86    async def _request_and_parse(
 87        self,
 88        model: type[T],
 89        method: str,
 90        endpoint: str,
 91        **kwargs,  # noqa : ANN003
 92    ) -> T:
 93        """Parse requests and responses using Pydantic models.
 94
 95        Args:
 96            model: Pydantic model class to parse response into
 97            method: HTTP method (GET, POST, PUT, DELETE)
 98            endpoint: API endpoint path
 99            **kwargs: Additional arguments to pass to request method
100
101        Returns:
102            Parsed response data as specified model instance
103
104        """
105        response = await self.controller.request_json(
106            method=method, endpoint=endpoint, **kwargs
107        )
108        parsed_response = model(**response)
109        logger.debug(f"Parsed '{model.__name__}': {parsed_response}")
110        return parsed_response
111
112    async def _create_standard_headers(self) -> dict[str, str]:
113        """Create standard headers for API requests.
114
115        Returns:
116            Dictionary with standard headers
117
118        """
119        return {
120            "datetime": str(int(datetime.now(timezone.utc).timestamp() * 1000)),
121            "x-correlationid": str(uuid4()),
122            "Content-Type": "application/json",
123        }
124
125    # Vehicle Management
126
127    async def set_vehicle_alias(self, alias: str, guid: str, vin: str) -> Any:  # noqa :ANN401
128        """Set a nickname/alias for a vehicle.
129
130        Args:
131            alias: New nickname for the vehicle
132            guid: Global unique identifier for the vehicle
133            vin: Vehicle Identification Number
134
135        Returns:
136            Raw response from the API
137
138        """
139        return await self.controller.request_raw(
140            method="PUT",
141            endpoint=VEHICLE_ASSOCIATION_ENDPOINT,
142            vin=vin,
143            headers=await self._create_standard_headers(),
144            body={"guid": guid, "vin": vin, "nickName": alias},
145        )
146
147    async def get_vehicles(self) -> VehiclesResponseModel:
148        """Get a list of vehicles registered with the Toyota account.
149
150        Returns:
151            Model containing vehicle information
152
153        """
154        return await self._request_and_parse(
155            VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
156        )
157
158    # Vehicle Status and Information
159
160    async def get_location(self, vin: str) -> LocationResponseModel:
161        """Get the last known location of a vehicle.
162
163        Only updates when car is parked.
164        Includes latitude and longitude if supported.
165
166        Args:
167            vin: Vehicle Identification Number
168
169        Returns:
170            Model containing location information
171
172        """
173        return await self._request_and_parse(
174            LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
175        )
176
177    async def get_vehicle_health_status(self, vin: str) -> VehicleHealthResponseModel:
178        """Get the latest health status of a vehicle.
179
180        Includes engine oil quantity and dashboard warning lights if supported.
181
182        Args:
183            vin: Vehicle Identification Number
184
185        Returns:
186            Model containing vehicle health information
187
188        """
189        return await self._request_and_parse(
190            VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
191        )
192
193    async def get_remote_status(self, vin: str) -> RemoteStatusResponseModel:
194        """Get general information about a vehicle.
195
196        Args:
197            vin: Vehicle Identification Number
198
199        Returns:
200            Model containing general vehicle status
201
202        """
203        return await self._request_and_parse(
204            RemoteStatusResponseModel,
205            "GET",
206            VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
207            vin=vin,
208        )
209
210    async def get_vehicle_electric_status(self, vin: str) -> ElectricResponseModel:
211        """Get the latest electric status of a vehicle.
212
213        Includes current battery level, EV range, fuel level, and charging status.
214
215        Args:
216            vin: Vehicle Identification Number
217
218        Returns:
219            Model containing electric vehicle information
220
221        """
222        return await self._request_and_parse(
223            ElectricResponseModel,
224            "GET",
225            VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
226            vin=vin,
227        )
228
229    async def refresh_electric_realtime_status(self, vin: str) -> StatusModel:
230        """Update realtime SOC.
231
232        Only requests a updated soc
233
234        Args:
235            vin: Vehicle Identification Number
236
237        Returns:
238            Model containing status of the refresh request
239
240        """
241        return await self._request_and_parse(
242            StatusModel,
243            "POST",
244            VEHICLE_GLOBAL_REMOTE_ELECTRIC_REALTIME_STATUS_ENDPOINT,
245            vin=vin,
246        )
247
248    async def get_telemetry(self, vin: str) -> TelemetryResponseModel:
249        """Get the latest telemetry data for a vehicle.
250
251        Includes current fuel level, distance to empty, and odometer.
252
253        Args:
254            vin: Vehicle Identification Number
255
256        Returns:
257            Model containing telemetry information
258
259        """
260        return await self._request_and_parse(
261            TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
262        )
263
264    # Notifications and History
265
266    async def get_notifications(self, vin: str) -> NotificationResponseModel:
267        """Get all available notifications for a vehicle.
268
269        Includes message text, notification date, read flag, and date read.
270        Note: No way to mark notifications as read or limit the response.
271
272        Args:
273            vin: Vehicle Identification Number
274
275        Returns:
276            Model containing notification information
277
278        """
279        return await self._request_and_parse(
280            NotificationResponseModel,
281            "GET",
282            VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
283            vin=vin,
284        )
285
286    async def get_service_history(self, vin: str) -> ServiceHistoryResponseModel:
287        """Get the service history for a vehicle.
288
289        Includes service category, date, and dealer information.
290
291        Args:
292            vin: Vehicle Identification Number
293
294        Returns:
295            Model containing service history information
296
297        """
298        return await self._request_and_parse(
299            ServiceHistoryResponseModel, "GET", VEHICLE_SERVICE_HISTORY_ENDPONT, vin=vin
300        )
301
302    # Climate Control
303
304    async def get_climate_status(self, vin: str) -> ClimateStatusResponseModel:
305        """Get the current climate control status.
306
307        Note: Only returns data if climate control is on. If off,
308        it returns status == 0 and all other fields are None.
309
310        Args:
311            vin: Vehicle Identification Number
312
313        Returns:
314            Model containing climate status information
315
316        """
317        return await self._request_and_parse(
318            ClimateStatusResponseModel,
319            "GET",
320            VEHICLE_CLIMATE_STATUS_ENDPOINT,
321            vin=vin,
322        )
323
324    async def refresh_climate_status(self, vin: str) -> StatusModel:
325        """Request an update to the climate status from the vehicle.
326
327        Args:
328            vin: Vehicle Identification Number
329
330        Returns:
331            Model containing status of the refresh request
332
333        """
334        return await self._request_and_parse(
335            StatusModel, "POST", VEHICLE_CLIMATE_STATUS_REFRESH_ENDPOINT, vin=vin
336        )
337
338    async def get_climate_settings(self, vin: str) -> ClimateSettingsResponseModel:
339        """Get the current climate control settings.
340
341        Args:
342            vin: Vehicle Identification Number
343
344        Returns:
345            Model containing climate settings information
346
347        """
348        return await self._request_and_parse(
349            ClimateSettingsResponseModel,
350            "GET",
351            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
352            vin=vin,
353        )
354
355    async def update_climate_settings(
356        self, vin: str, settings: ClimateSettingsModel
357    ) -> StatusModel:
358        """Update the climate control settings for a vehicle.
359
360        Args:
361            vin: Vehicle Identification Number
362            settings: New climate control settings
363
364        Returns:
365            Model containing status of the update request
366
367        """
368        return await self._request_and_parse(
369            StatusModel,
370            "PUT",
371            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
372            vin=vin,
373            body=settings.model_dump(exclude_unset=True, by_alias=True),
374        )
375
376    async def send_climate_control_command(
377        self, vin: str, command: ClimateControlModel
378    ) -> StatusModel:
379        """Send a control command to the climate system.
380
381        Args:
382            vin: Vehicle Identification Number
383            command: Climate control command to send
384
385        Returns:
386            Model containing status of the command request
387
388        """
389        return await self._request_and_parse(
390            StatusModel,
391            "POST",
392            VEHICLE_CLIMATE_CONTROL_ENDPOINT,
393            vin=vin,
394            body=command.model_dump(exclude_unset=True, by_alias=True),
395        )
396
397    # Trip Data
398
399    async def get_trips(  # noqa: PLR0913
400        self,
401        vin: str,
402        from_date: date,
403        to_date: date,
404        route: bool = False,  # noqa : FBT001, FBT002
405        summary: bool = True,  # noqa : FBT001, FBT002
406        limit: int = 5,
407        offset: int = 0,
408    ) -> TripsResponseModel:
409        """Get a list of trips for a vehicle within a date range.
410
411        Args:
412            vin: Vehicle Identification Number
413            from_date: Start date for trip data (inclusive, cannot be in future)
414            to_date: End date for trip data (inclusive, cannot be in future)
415            route: If True, returns route coordinates for each trip
416            summary: If True, returns monthly and daily trip summaries
417            limit: Maximum number of trips to return (max 50)
418            offset: Starting offset for pagination
419
420        Returns:
421            Model containing trip information
422
423        """
424        endpoint = VEHICLE_TRIPS_ENDPOINT.format(
425            from_date=from_date,
426            to_date=to_date,
427            route=route,
428            summary=summary,
429            limit=limit,
430            offset=offset,
431        )
432        return await self._request_and_parse(
433            TripsResponseModel, "GET", endpoint, vin=vin
434        )
435
436    # Remote Commands
437
438    async def send_command(
439        self, vin: str, command: CommandType, beeps: int = 0
440    ) -> StatusModel:
441        """Send a remote command to a vehicle.
442
443        Args:
444            vin: Vehicle Identification Number
445            command: Type of command to send
446            beeps: Number of beeps for commands that support it
447
448        Returns:
449            Model containing status of the command request
450
451        """
452        remote_command = RemoteCommandModel(beep_count=beeps, command=command)
453        return await self._request_and_parse(
454            StatusModel,
455            "POST",
456            VEHICLE_COMMAND_ENDPOINT,
457            vin=vin,
458            body=remote_command.model_dump(exclude_unset=True, by_alias=True),
459        )
460
461    async def send_next_charging_command(
462        self, vin: str, command: NextChargeSettings
463    ) -> ElectricCommandResponseModel:
464        """Send the next charging start/end time to the vehicle.
465
466        Args:
467            vin: Vehicle Identification Number
468            command: NextChargeSettings command to send
469
470        Returns:
471            Model containing status of the command request
472
473        """
474        return await self._request_and_parse(
475            ElectricCommandResponseModel,
476            "POST",
477            VEHICLE_GLOBAL_REMOTE_ELECTRIC_CONTROL_ENDPOINT,
478            vin=vin,
479            body=command.model_dump(exclude_none=True, by_alias=True),
480        )

API for Toyota Connected Services.

This class provides access to Toyota Connected Services endpoints to retrieve and manipulate vehicle data.

Api(controller: pytoyoda.controller.Controller)
77    def __init__(self, controller: Controller) -> None:
78        """Initialize the API with a controller for communication.
79
80        Args:
81            controller: A controller instance to manage the API communication
82
83        """
84        self.controller = controller

Initialize the API with a controller for communication.

Arguments:
  • controller: A controller instance to manage the API communication
controller
async def set_vehicle_alias(self, alias: str, guid: str, vin: str) -> Any:
127    async def set_vehicle_alias(self, alias: str, guid: str, vin: str) -> Any:  # noqa :ANN401
128        """Set a nickname/alias for a vehicle.
129
130        Args:
131            alias: New nickname for the vehicle
132            guid: Global unique identifier for the vehicle
133            vin: Vehicle Identification Number
134
135        Returns:
136            Raw response from the API
137
138        """
139        return await self.controller.request_raw(
140            method="PUT",
141            endpoint=VEHICLE_ASSOCIATION_ENDPOINT,
142            vin=vin,
143            headers=await self._create_standard_headers(),
144            body={"guid": guid, "vin": vin, "nickName": alias},
145        )

Set a nickname/alias for a vehicle.

Arguments:
  • alias: New nickname for the vehicle
  • guid: Global unique identifier for the vehicle
  • vin: Vehicle Identification Number
Returns:

Raw response from the API

async def get_vehicles(self) -> pytoyoda.models.endpoints.vehicle_guid.VehiclesResponseModel:
147    async def get_vehicles(self) -> VehiclesResponseModel:
148        """Get a list of vehicles registered with the Toyota account.
149
150        Returns:
151            Model containing vehicle information
152
153        """
154        return await self._request_and_parse(
155            VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
156        )

Get a list of vehicles registered with the Toyota account.

Returns:

Model containing vehicle information

async def get_location( self, vin: str) -> pytoyoda.models.endpoints.location.LocationResponseModel:
160    async def get_location(self, vin: str) -> LocationResponseModel:
161        """Get the last known location of a vehicle.
162
163        Only updates when car is parked.
164        Includes latitude and longitude if supported.
165
166        Args:
167            vin: Vehicle Identification Number
168
169        Returns:
170            Model containing location information
171
172        """
173        return await self._request_and_parse(
174            LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
175        )

Get the last known location of a vehicle.

Only updates when car is parked. Includes latitude and longitude if supported.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing location information

async def get_vehicle_health_status( self, vin: str) -> pytoyoda.models.endpoints.vehicle_health.VehicleHealthResponseModel:
177    async def get_vehicle_health_status(self, vin: str) -> VehicleHealthResponseModel:
178        """Get the latest health status of a vehicle.
179
180        Includes engine oil quantity and dashboard warning lights if supported.
181
182        Args:
183            vin: Vehicle Identification Number
184
185        Returns:
186            Model containing vehicle health information
187
188        """
189        return await self._request_and_parse(
190            VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
191        )

Get the latest health status of a vehicle.

Includes engine oil quantity and dashboard warning lights if supported.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing vehicle health information

async def get_remote_status( self, vin: str) -> pytoyoda.models.endpoints.status.RemoteStatusResponseModel:
193    async def get_remote_status(self, vin: str) -> RemoteStatusResponseModel:
194        """Get general information about a vehicle.
195
196        Args:
197            vin: Vehicle Identification Number
198
199        Returns:
200            Model containing general vehicle status
201
202        """
203        return await self._request_and_parse(
204            RemoteStatusResponseModel,
205            "GET",
206            VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
207            vin=vin,
208        )

Get general information about a vehicle.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing general vehicle status

async def get_vehicle_electric_status( self, vin: str) -> pytoyoda.models.endpoints.electric.ElectricResponseModel:
210    async def get_vehicle_electric_status(self, vin: str) -> ElectricResponseModel:
211        """Get the latest electric status of a vehicle.
212
213        Includes current battery level, EV range, fuel level, and charging status.
214
215        Args:
216            vin: Vehicle Identification Number
217
218        Returns:
219            Model containing electric vehicle information
220
221        """
222        return await self._request_and_parse(
223            ElectricResponseModel,
224            "GET",
225            VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
226            vin=vin,
227        )

Get the latest electric status of a vehicle.

Includes current battery level, EV range, fuel level, and charging status.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing electric vehicle information

async def refresh_electric_realtime_status(self, vin: str) -> pytoyoda.models.endpoints.common.StatusModel:
229    async def refresh_electric_realtime_status(self, vin: str) -> StatusModel:
230        """Update realtime SOC.
231
232        Only requests a updated soc
233
234        Args:
235            vin: Vehicle Identification Number
236
237        Returns:
238            Model containing status of the refresh request
239
240        """
241        return await self._request_and_parse(
242            StatusModel,
243            "POST",
244            VEHICLE_GLOBAL_REMOTE_ELECTRIC_REALTIME_STATUS_ENDPOINT,
245            vin=vin,
246        )

Update realtime SOC.

Only requests a updated soc

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing status of the refresh request

async def get_telemetry( self, vin: str) -> pytoyoda.models.endpoints.telemetry.TelemetryResponseModel:
248    async def get_telemetry(self, vin: str) -> TelemetryResponseModel:
249        """Get the latest telemetry data for a vehicle.
250
251        Includes current fuel level, distance to empty, and odometer.
252
253        Args:
254            vin: Vehicle Identification Number
255
256        Returns:
257            Model containing telemetry information
258
259        """
260        return await self._request_and_parse(
261            TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
262        )

Get the latest telemetry data for a vehicle.

Includes current fuel level, distance to empty, and odometer.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing telemetry information

async def get_notifications( self, vin: str) -> pytoyoda.models.endpoints.notifications.NotificationResponseModel:
266    async def get_notifications(self, vin: str) -> NotificationResponseModel:
267        """Get all available notifications for a vehicle.
268
269        Includes message text, notification date, read flag, and date read.
270        Note: No way to mark notifications as read or limit the response.
271
272        Args:
273            vin: Vehicle Identification Number
274
275        Returns:
276            Model containing notification information
277
278        """
279        return await self._request_and_parse(
280            NotificationResponseModel,
281            "GET",
282            VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
283            vin=vin,
284        )

Get all available notifications for a vehicle.

Includes message text, notification date, read flag, and date read. Note: No way to mark notifications as read or limit the response.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing notification information

async def get_service_history( self, vin: str) -> pytoyoda.models.endpoints.service_history.ServiceHistoryResponseModel:
286    async def get_service_history(self, vin: str) -> ServiceHistoryResponseModel:
287        """Get the service history for a vehicle.
288
289        Includes service category, date, and dealer information.
290
291        Args:
292            vin: Vehicle Identification Number
293
294        Returns:
295            Model containing service history information
296
297        """
298        return await self._request_and_parse(
299            ServiceHistoryResponseModel, "GET", VEHICLE_SERVICE_HISTORY_ENDPONT, vin=vin
300        )

Get the service history for a vehicle.

Includes service category, date, and dealer information.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing service history information

async def get_climate_status( self, vin: str) -> pytoyoda.models.endpoints.climate.ClimateStatusResponseModel:
304    async def get_climate_status(self, vin: str) -> ClimateStatusResponseModel:
305        """Get the current climate control status.
306
307        Note: Only returns data if climate control is on. If off,
308        it returns status == 0 and all other fields are None.
309
310        Args:
311            vin: Vehicle Identification Number
312
313        Returns:
314            Model containing climate status information
315
316        """
317        return await self._request_and_parse(
318            ClimateStatusResponseModel,
319            "GET",
320            VEHICLE_CLIMATE_STATUS_ENDPOINT,
321            vin=vin,
322        )

Get the current climate control status.

Note: Only returns data if climate control is on. If off, it returns status == 0 and all other fields are None.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing climate status information

async def refresh_climate_status(self, vin: str) -> pytoyoda.models.endpoints.common.StatusModel:
324    async def refresh_climate_status(self, vin: str) -> StatusModel:
325        """Request an update to the climate status from the vehicle.
326
327        Args:
328            vin: Vehicle Identification Number
329
330        Returns:
331            Model containing status of the refresh request
332
333        """
334        return await self._request_and_parse(
335            StatusModel, "POST", VEHICLE_CLIMATE_STATUS_REFRESH_ENDPOINT, vin=vin
336        )

Request an update to the climate status from the vehicle.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing status of the refresh request

async def get_climate_settings( self, vin: str) -> pytoyoda.models.endpoints.climate.ClimateSettingsResponseModel:
338    async def get_climate_settings(self, vin: str) -> ClimateSettingsResponseModel:
339        """Get the current climate control settings.
340
341        Args:
342            vin: Vehicle Identification Number
343
344        Returns:
345            Model containing climate settings information
346
347        """
348        return await self._request_and_parse(
349            ClimateSettingsResponseModel,
350            "GET",
351            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
352            vin=vin,
353        )

Get the current climate control settings.

Arguments:
  • vin: Vehicle Identification Number
Returns:

Model containing climate settings information

async def update_climate_settings( self, vin: str, settings: pytoyoda.models.endpoints.climate.ClimateSettingsModel) -> pytoyoda.models.endpoints.common.StatusModel:
355    async def update_climate_settings(
356        self, vin: str, settings: ClimateSettingsModel
357    ) -> StatusModel:
358        """Update the climate control settings for a vehicle.
359
360        Args:
361            vin: Vehicle Identification Number
362            settings: New climate control settings
363
364        Returns:
365            Model containing status of the update request
366
367        """
368        return await self._request_and_parse(
369            StatusModel,
370            "PUT",
371            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
372            vin=vin,
373            body=settings.model_dump(exclude_unset=True, by_alias=True),
374        )

Update the climate control settings for a vehicle.

Arguments:
  • vin: Vehicle Identification Number
  • settings: New climate control settings
Returns:

Model containing status of the update request

async def send_climate_control_command( self, vin: str, command: pytoyoda.models.endpoints.climate.ClimateControlModel) -> pytoyoda.models.endpoints.common.StatusModel:
376    async def send_climate_control_command(
377        self, vin: str, command: ClimateControlModel
378    ) -> StatusModel:
379        """Send a control command to the climate system.
380
381        Args:
382            vin: Vehicle Identification Number
383            command: Climate control command to send
384
385        Returns:
386            Model containing status of the command request
387
388        """
389        return await self._request_and_parse(
390            StatusModel,
391            "POST",
392            VEHICLE_CLIMATE_CONTROL_ENDPOINT,
393            vin=vin,
394            body=command.model_dump(exclude_unset=True, by_alias=True),
395        )

Send a control command to the climate system.

Arguments:
  • vin: Vehicle Identification Number
  • command: Climate control command to send
Returns:

Model containing status of the command request

async def get_trips( self, vin: str, from_date: datetime.date, to_date: datetime.date, route: bool = False, summary: bool = True, limit: int = 5, offset: int = 0) -> pytoyoda.models.endpoints.trips.TripsResponseModel:
399    async def get_trips(  # noqa: PLR0913
400        self,
401        vin: str,
402        from_date: date,
403        to_date: date,
404        route: bool = False,  # noqa : FBT001, FBT002
405        summary: bool = True,  # noqa : FBT001, FBT002
406        limit: int = 5,
407        offset: int = 0,
408    ) -> TripsResponseModel:
409        """Get a list of trips for a vehicle within a date range.
410
411        Args:
412            vin: Vehicle Identification Number
413            from_date: Start date for trip data (inclusive, cannot be in future)
414            to_date: End date for trip data (inclusive, cannot be in future)
415            route: If True, returns route coordinates for each trip
416            summary: If True, returns monthly and daily trip summaries
417            limit: Maximum number of trips to return (max 50)
418            offset: Starting offset for pagination
419
420        Returns:
421            Model containing trip information
422
423        """
424        endpoint = VEHICLE_TRIPS_ENDPOINT.format(
425            from_date=from_date,
426            to_date=to_date,
427            route=route,
428            summary=summary,
429            limit=limit,
430            offset=offset,
431        )
432        return await self._request_and_parse(
433            TripsResponseModel, "GET", endpoint, vin=vin
434        )

Get a list of trips for a vehicle within a date range.

Arguments:
  • vin: Vehicle Identification Number
  • from_date: Start date for trip data (inclusive, cannot be in future)
  • to_date: End date for trip data (inclusive, cannot be in future)
  • route: If True, returns route coordinates for each trip
  • summary: If True, returns monthly and daily trip summaries
  • limit: Maximum number of trips to return (max 50)
  • offset: Starting offset for pagination
Returns:

Model containing trip information

async def send_command( self, vin: str, command: pytoyoda.models.endpoints.command.CommandType, beeps: int = 0) -> pytoyoda.models.endpoints.common.StatusModel:
438    async def send_command(
439        self, vin: str, command: CommandType, beeps: int = 0
440    ) -> StatusModel:
441        """Send a remote command to a vehicle.
442
443        Args:
444            vin: Vehicle Identification Number
445            command: Type of command to send
446            beeps: Number of beeps for commands that support it
447
448        Returns:
449            Model containing status of the command request
450
451        """
452        remote_command = RemoteCommandModel(beep_count=beeps, command=command)
453        return await self._request_and_parse(
454            StatusModel,
455            "POST",
456            VEHICLE_COMMAND_ENDPOINT,
457            vin=vin,
458            body=remote_command.model_dump(exclude_unset=True, by_alias=True),
459        )

Send a remote command to a vehicle.

Arguments:
  • vin: Vehicle Identification Number
  • command: Type of command to send
  • beeps: Number of beeps for commands that support it
Returns:

Model containing status of the command request

async def send_next_charging_command( self, vin: str, command: pytoyoda.models.endpoints.electric.NextChargeSettings) -> pytoyoda.models.endpoints.electric.ElectricCommandResponseModel:
461    async def send_next_charging_command(
462        self, vin: str, command: NextChargeSettings
463    ) -> ElectricCommandResponseModel:
464        """Send the next charging start/end time to the vehicle.
465
466        Args:
467            vin: Vehicle Identification Number
468            command: NextChargeSettings command to send
469
470        Returns:
471            Model containing status of the command request
472
473        """
474        return await self._request_and_parse(
475            ElectricCommandResponseModel,
476            "POST",
477            VEHICLE_GLOBAL_REMOTE_ELECTRIC_CONTROL_ENDPOINT,
478            vin=vin,
479            body=command.model_dump(exclude_none=True, by_alias=True),
480        )

Send the next charging start/end time to the vehicle.

Arguments:
  • vin: Vehicle Identification Number
  • command: NextChargeSettings command to send
Returns:

Model containing status of the command request