pytoyoda.api

Toyota Connected Services API.

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

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)
73    def __init__(self, controller: Controller) -> None:
74        """Initialize the API with a controller for communication.
75
76        Args:
77            controller: A controller instance to manage the API communication
78
79        """
80        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:
123    async def set_vehicle_alias(self, alias: str, guid: str, vin: str) -> Any:  # noqa :ANN401
124        """Set a nickname/alias for a vehicle.
125
126        Args:
127            alias: New nickname for the vehicle
128            guid: Global unique identifier for the vehicle
129            vin: Vehicle Identification Number
130
131        Returns:
132            Raw response from the API
133
134        """
135        return await self.controller.request_raw(
136            method="PUT",
137            endpoint=VEHICLE_ASSOCIATION_ENDPOINT,
138            vin=vin,
139            headers=await self._create_standard_headers(),
140            body={"guid": guid, "vin": vin, "nickName": alias},
141        )

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:
143    async def get_vehicles(self) -> VehiclesResponseModel:
144        """Get a list of vehicles registered with the Toyota account.
145
146        Returns:
147            Model containing vehicle information
148
149        """
150        return await self._request_and_parse(
151            VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
152        )

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:
156    async def get_location(self, vin: str) -> LocationResponseModel:
157        """Get the last known location of a vehicle.
158
159        Only updates when car is parked.
160        Includes latitude and longitude if supported.
161
162        Args:
163            vin: Vehicle Identification Number
164
165        Returns:
166            Model containing location information
167
168        """
169        return await self._request_and_parse(
170            LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
171        )

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:
173    async def get_vehicle_health_status(self, vin: str) -> VehicleHealthResponseModel:
174        """Get the latest health status of a vehicle.
175
176        Includes engine oil quantity and dashboard warning lights if supported.
177
178        Args:
179            vin: Vehicle Identification Number
180
181        Returns:
182            Model containing vehicle health information
183
184        """
185        return await self._request_and_parse(
186            VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
187        )

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:
189    async def get_remote_status(self, vin: str) -> RemoteStatusResponseModel:
190        """Get general information about a vehicle.
191
192        Args:
193            vin: Vehicle Identification Number
194
195        Returns:
196            Model containing general vehicle status
197
198        """
199        return await self._request_and_parse(
200            RemoteStatusResponseModel,
201            "GET",
202            VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
203            vin=vin,
204        )

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:
206    async def get_vehicle_electric_status(self, vin: str) -> ElectricResponseModel:
207        """Get the latest electric status of a vehicle.
208
209        Includes current battery level, EV range, fuel level, and charging status.
210
211        Args:
212            vin: Vehicle Identification Number
213
214        Returns:
215            Model containing electric vehicle information
216
217        """
218        return await self._request_and_parse(
219            ElectricResponseModel,
220            "GET",
221            VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
222            vin=vin,
223        )

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:
225    async def refresh_electric_realtime_status(self, vin: str) -> StatusModel:
226        """Update realtime SOC.
227
228        Only requests a updated soc
229
230        Args:
231            vin: Vehicle Identification Number
232
233        Returns:
234            Model containing status of the refresh request
235
236        """
237        return await self._request_and_parse(
238            StatusModel,
239            "POST",
240            VEHICLE_GLOBAL_REMOTE_ELECTRIC_REALTIME_STATUS_ENDPOINT,
241            vin=vin,
242        )

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:
244    async def get_telemetry(self, vin: str) -> TelemetryResponseModel:
245        """Get the latest telemetry data for a vehicle.
246
247        Includes current fuel level, distance to empty, and odometer.
248
249        Args:
250            vin: Vehicle Identification Number
251
252        Returns:
253            Model containing telemetry information
254
255        """
256        return await self._request_and_parse(
257            TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
258        )

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

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:
282    async def get_service_history(self, vin: str) -> ServiceHistoryResponseModel:
283        """Get the service history for a vehicle.
284
285        Includes service category, date, and dealer information.
286
287        Args:
288            vin: Vehicle Identification Number
289
290        Returns:
291            Model containing service history information
292
293        """
294        return await self._request_and_parse(
295            ServiceHistoryResponseModel, "GET", VEHICLE_SERVICE_HISTORY_ENDPONT, vin=vin
296        )

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:
300    async def get_climate_status(self, vin: str) -> ClimateStatusResponseModel:
301        """Get the current climate control status.
302
303        Note: Only returns data if climate control is on. If off,
304        it returns status == 0 and all other fields are None.
305
306        Args:
307            vin: Vehicle Identification Number
308
309        Returns:
310            Model containing climate status information
311
312        """
313        return await self._request_and_parse(
314            ClimateStatusResponseModel,
315            "GET",
316            VEHICLE_CLIMATE_STATUS_ENDPOINT,
317            vin=vin,
318        )

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:
320    async def refresh_climate_status(self, vin: str) -> StatusModel:
321        """Request an update to the climate status from the vehicle.
322
323        Args:
324            vin: Vehicle Identification Number
325
326        Returns:
327            Model containing status of the refresh request
328
329        """
330        return await self._request_and_parse(
331            StatusModel, "POST", VEHICLE_CLIMATE_STATUS_REFRESH_ENDPOINT, vin=vin
332        )

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:
334    async def get_climate_settings(self, vin: str) -> ClimateSettingsResponseModel:
335        """Get the current climate control settings.
336
337        Args:
338            vin: Vehicle Identification Number
339
340        Returns:
341            Model containing climate settings information
342
343        """
344        return await self._request_and_parse(
345            ClimateSettingsResponseModel,
346            "GET",
347            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
348            vin=vin,
349        )

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:
351    async def update_climate_settings(
352        self, vin: str, settings: ClimateSettingsModel
353    ) -> StatusModel:
354        """Update the climate control settings for a vehicle.
355
356        Args:
357            vin: Vehicle Identification Number
358            settings: New climate control settings
359
360        Returns:
361            Model containing status of the update request
362
363        """
364        return await self._request_and_parse(
365            StatusModel,
366            "PUT",
367            VEHICLE_CLIMATE_SETTINGS_ENDPOINT,
368            vin=vin,
369            body=settings.model_dump(exclude_unset=True, by_alias=True),
370        )

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:
372    async def send_climate_control_command(
373        self, vin: str, command: ClimateControlModel
374    ) -> StatusModel:
375        """Send a control command to the climate system.
376
377        Args:
378            vin: Vehicle Identification Number
379            command: Climate control command to send
380
381        Returns:
382            Model containing status of the command request
383
384        """
385        return await self._request_and_parse(
386            StatusModel,
387            "POST",
388            VEHICLE_CLIMATE_CONTROL_ENDPOINT,
389            vin=vin,
390            body=command.model_dump(exclude_unset=True, by_alias=True),
391        )

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

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

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