Skip to content

Commit

Permalink
Feature: Add initial ServiceHistoryResponseModel (#312)
Browse files Browse the repository at this point in the history
* Add initial ServiceHistoryResponseModel
  • Loading branch information
CM000n authored Jan 23, 2024
1 parent f688783 commit cea29b0
Show file tree
Hide file tree
Showing 8 changed files with 354 additions and 6 deletions.
21 changes: 21 additions & 0 deletions mytoyota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
VEHICLE_HEALTH_STATUS_ENDPOINT,
VEHICLE_LOCATION_ENDPOINT,
VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
VEHICLE_SERVICE_HISTORY_ENDPONT,
VEHICLE_TELEMETRY_ENDPOINT,
VEHICLE_TRIPS_ENDPOINT,
)
from mytoyota.controller import Controller
from mytoyota.models.endpoints.electric import ElectricResponseModel
from mytoyota.models.endpoints.location import LocationResponseModel
from mytoyota.models.endpoints.notifications import NotificationResponseModel
from mytoyota.models.endpoints.service_history import ServiceHistoryResponseModel
from mytoyota.models.endpoints.status import RemoteStatusResponseModel
from mytoyota.models.endpoints.telemetry import TelemetryResponseModel
from mytoyota.models.endpoints.trips import TripsResponseModel
Expand Down Expand Up @@ -242,3 +244,22 @@ async def get_trips_endpoint( # noqa: PLR0913
)
_LOGGER.debug(msg=f"Parsed 'TripsResponseModel': {parsed_response}")
return parsed_response

async def get_service_history_endpoint(self, vin: str) -> ServiceHistoryResponseModel:
"""Get the current servic history.
Response includes service category, date and dealer.
Args:
----
vin: str: The vehicles VIN
Returns:
-------
ServicHistoryResponseModel: A pydantic model for the service history response
"""
parsed_response = await self._request_and_parse(
ServiceHistoryResponseModel, "GET", VEHICLE_SERVICE_HISTORY_ENDPONT, vin=vin
)
_LOGGER.debug(msg=f"Parsed 'ServiceHistoryResponseModel': {parsed_response}")
return parsed_response
1 change: 1 addition & 0 deletions mytoyota/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
VEHICLE_TELEMETRY_ENDPOINT = "/v3/telemetry"
VEHICLE_NOTIFICATION_HISTORY_ENDPOINT = "/v2/notification/history"
VEHICLE_TRIPS_ENDPOINT = "/v1/trips?from={from_date}&to={to_date}&route={route}&summary={summary}&limit={limit}&offset={offset}" # noqa: E501
VEHICLE_SERVICE_HISTORY_ENDPONT = "/v1/servicehistory/vehicle/summary"

# Timestamps
UNLOCK_TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ"
69 changes: 69 additions & 0 deletions mytoyota/models/endpoints/service_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Toyota Connected Services API - Service History Models."""

from datetime import date
from typing import Any, List, Optional

from pydantic import BaseModel, Field

from mytoyota.models.endpoints.common import StatusModel


class ServiceHistoryModel(BaseModel):
"""Represents a service history record.
Attributes
----------
customer_created_record (bool): Indicates if the record was created by the customer.
mileage (Optional[int]): The mileage at the time of the service.
notes (Any): Additional notes about the service.
operations_performed (Any): The operations performed during the service.
ro_number (Any): The RO (Repair Order) number associated with the service.
service_category (str): The category of the service.
service_date (date): The date of the service.
service_history_id (str): The ID of the service history record.
service_provider (str): The service provider.
servicing_dealer (Any): The dealer that performed the service.
unit (Optional[str]): The unit associated with the service mileage.
"""

customer_created_record: bool = Field(alias="customerCreatedRecord")
mileage: Optional[int] = None
notes: Any
operations_performed: Any = Field(alias="operationsPerformed")
ro_number: Any = Field(alias="roNumber")
service_category: str = Field(alias="serviceCategory")
service_date: date = Field(alias="serviceDate")
service_history_id: str = Field(alias="serviceHistoryId")
service_provider: str = Field(alias="serviceProvider")
servicing_dealer: Any = Field(alias="servicingDealer")
unit: Optional[str] = None


class ServiceHistoriesModel(BaseModel):
r"""Model representing a list of service histories.
Attributes
----------
service_histories (List[Optional[ServiceHistoryModel]]): A list of all service histories.
Defaults to [].
"""

service_histories: List[Optional[ServiceHistoryModel]] = Field(
alias="serviceHistories", default=[]
)


class ServiceHistoryResponseModel(StatusModel):
"""Model representing a service history response.
Inherits from StatusModel.
Attributes
----------
payload (Optional[ServiceHistoriesModel]): The service history payload. Defaults to None.
"""

payload: Optional[ServiceHistoriesModel] = None
132 changes: 132 additions & 0 deletions mytoyota/models/service_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""models for vehicle service history."""
from datetime import date
from typing import Any, Optional

from mytoyota.models.endpoints.service_history import ServiceHistoryModel
from mytoyota.utils.conversions import convert_distance


class ServiceHistory:
"""ServiceHistory."""

def __init__(
self,
service_history: ServiceHistoryModel,
metric: bool = True,
):
"""Initialise ServiceHistory."""
self._service_history = service_history
self._distance_unit: str = "km" if metric else "mi"

def __repr__(self):
"""Representation of the model."""
return " ".join(
[
f"{k}={getattr(self, k)!s}"
for k, v in type(self).__dict__.items()
if isinstance(v, property)
],
)

@property
def service_date(self) -> date:
"""The date of the service.
Returns
-------
date: The date of the service.
"""
return self._service_history.service_date

@property
def customer_created_record(self) -> bool:
"""Indication whether it is an entry created by the user.
Returns
-------
str: Category of notification
"""
return self._service_history.customer_created_record

@property
def odometer(self) -> Optional[float]:
"""Odometer distance at the time of servicing.
Returns
-------
int: Odometer distance at the time of servicing
in the current selected units
"""
if (
self._service_history is not None
and self._service_history.unit is not None
and self._service_history.mileage is not None
):
return convert_distance(
self._distance_unit,
self._service_history.unit,
self._service_history.mileage,
)
else:
return None

@property
def notes(self) -> Any:
"""Additional notes about the service.
Returns
-------
Any: Additional notes about the service
"""
return self._service_history.notes

@property
def operations_performed(self) -> Any:
"""The operations performed during the service.
Returns
-------
Any: The operations performed during the service
"""
return self._service_history.operations_performed

@property
def ro_number(self) -> Any:
"""The RO (Repair Order) number associated with the service.
Returns
-------
Any: The RO (Repair Order) number associated with the service
"""
return self._service_history.ro_number

@property
def service_category(self) -> str:
"""The category of the service.
Returns
-------
str: The category of the service.
"""
return self._service_history.service_category

@property
def service_provider(self) -> str:
"""The service provider.
Returns
-------
str: The service provider
"""
return self._service_history.service_provider

@property
def servicing_dealer(self) -> Any:
"""Dealer that performed the service.
Returns
-------
Any: The dealer that performed the service
"""
return self._service_history.servicing_dealer
59 changes: 53 additions & 6 deletions mytoyota/models/vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from mytoyota.models.location import Location
from mytoyota.models.lock_status import LockStatus
from mytoyota.models.nofication import Notification
from mytoyota.models.service_history import ServiceHistory
from mytoyota.models.summary import Summary, SummaryType
from mytoyota.models.trips import Trip
from mytoyota.utils.helpers import add_with_none
Expand Down Expand Up @@ -75,6 +76,11 @@ def __init__(self, api: Api, vehicle_info: VehicleGuidModel, metric: bool = True
"capable": vehicle_info.extended_capabilities.vehicle_status,
"function": partial(self._api.get_remote_status_endpoint, vin=vehicle_info.vin),
},
{
"name": "service_history",
"capable": vehicle_info.features.service_history,
"function": partial(self._api.get_service_history_endpoint, vin=vehicle_info.vin),
},
]
self._endpoint_collect = [
(endpoint["name"], endpoint["function"])
Expand Down Expand Up @@ -199,15 +205,46 @@ def notifications(self) -> Optional[List[Notification]]:
"""
if "notifications" in self._endpoint_data:
ret = []
ret: List[Notification] = []
for p in self._endpoint_data["notifications"].payload:
for n in p.notifications:
ret.append(Notification(n))
ret.extend(Notification(n) for n in p.notifications)
return ret

return None

@property
def service_history(self) -> Optional[List[ServiceHistory]]:
r"""Returns a list of service history entries for the vehicle.
Returns
-------
Optional[List[ServiceHistory]]: A list of service history entries for the vehicle,
or None if not supported.
"""
if "service_history" in self._endpoint_data:
ret: List[ServiceHistory] = []
payload = self._endpoint_data["service_history"].payload
ret.extend(
ServiceHistory(service_history) for service_history in payload.service_histories
)
return ret

return None

def get_latest_service_history(self) -> Optional[ServiceHistory]:
r"""Return the latest service history entry for the vehicle.
Returns
-------
Optional[ServiceHistory]: A service history entry for the vehicle,
ordered by date and service_category. None if not supported or unknown.
"""
if self.service_history is not None:
return max(self.service_history, key=lambda x: (x.service_date, x.service_category))
return None

@property
def lock_status(self) -> Optional[LockStatus]:
"""Returns the latest lock status of Doors & Windows.
Expand Down Expand Up @@ -429,18 +466,28 @@ def _generate_weekly_summaries(self, summary) -> List[Summary]:
build_hdc = copy.copy(week_histograms[0].hdc)
build_summary = copy.copy(week_histograms[0].summary)
start_date = Arrow(
week_histograms[0].year, week_histograms[0].month, week_histograms[0].day
week_histograms[0].year,
week_histograms[0].month,
week_histograms[0].day,
)

for histogram in week_histograms[1:]:
add_with_none(build_hdc, histogram.hdc)
build_summary += histogram.summary

end_date = Arrow(
week_histograms[-1].year, week_histograms[-1].month, week_histograms[-1].day
week_histograms[-1].year,
week_histograms[-1].month,
week_histograms[-1].day,
)
ret.append(
Summary(build_summary, self._metric, start_date.date(), end_date.date(), build_hdc)
Summary(
build_summary,
self._metric,
start_date.date(),
end_date.date(),
build_hdc,
)
)

return ret
Expand Down
2 changes: 2 additions & 0 deletions simple_client_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ async def get_information():
pp.pprint(f"Lock Status: {car.lock_status}")
# Notifications
pp.pprint(f"Notifications: {[[x] for x in car.notifications]}")
# Service history
pp.pprint(f"Latest service: {car.get_latest_service_history()}")
# Summary
# pp.pprint(
# f"Summary: {[[x] for x in await car.get_summary(date.today() - timedelta(days=7), date.today(), summary_type=SummaryType.DAILY)]}" # noqa: E501 # pylint: disable=C0301
Expand Down
Loading

0 comments on commit cea29b0

Please sign in to comment.