Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Lexus Work #316

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 47 additions & 23 deletions mytoyota/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,20 @@ def __init__(self, controller: Controller) -> None:
"""
self.controller = controller

async def _request_and_parse(self, model, method: str, endpoint: str, **kwargs):
async def _request_and_parse(self, model, method: str, endpoint: str, brand: str, **kwargs):
"""Parse requests and responses."""
response = await self.controller.request_json(method=method, endpoint=endpoint, **kwargs)
response = await self.controller.request_json(
method=method, endpoint=endpoint, brand=brand, **kwargs
)
return model(**response)

async def set_vehicle_alias_endpoint(self, alias: str, guid: str, vin: str):
async def set_vehicle_alias_endpoint(self, alias: str, guid: str, vin: str, brand: str):
"""Set the alias for a vehicle."""
return await self.controller.request_raw(
method="PUT",
endpoint=VEHICLE_ASSOCIATION_ENDPOINT,
vin=vin,
brand=brand,
headers={
"datetime": str(int(datetime.now(timezone.utc).timestamp() * 1000)),
"x-correlationid": str(uuid4()),
Expand All @@ -76,73 +79,81 @@ async def set_vehicle_alias_endpoint(self, alias: str, guid: str, vin: str):
# method="POST", endpoint="/v2/global/remote/wake"
# )

async def get_vehicles_endpoint(self) -> VehiclesResponseModel:
async def get_vehicles_endpoint(self, brand: str = "T") -> VehiclesResponseModel:
"""Return list of vehicles registered with provider."""
parsed_response = await self._request_and_parse(
VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT
VehiclesResponseModel, "GET", VEHICLE_GUID_ENDPOINT, brand
)
_LOGGER.debug(msg=f"Parsed 'VehiclesResponseModel': {parsed_response}")
return parsed_response

async def get_location_endpoint(self, vin: str) -> LocationResponseModel:
async def get_location_endpoint(self, vin: str, brand: str) -> LocationResponseModel:
"""Get the last known location of your car. Only updates when car is parked.

Response includes Lat, Lon position. * If supported.

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

Returns:
-------
LocationResponseModel: A pydantic model for the location response
"""
parsed_response = await self._request_and_parse(
LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin
LocationResponseModel, "GET", VEHICLE_LOCATION_ENDPOINT, vin=vin, brand=brand
)
_LOGGER.debug(msg=f"Parsed 'LocationResponseModel': {parsed_response}")
return parsed_response

async def get_vehicle_health_status_endpoint(self, vin: str) -> VehicleHealthResponseModel:
async def get_vehicle_health_status_endpoint(
self, vin: str, brand: str
) -> VehicleHealthResponseModel:
r"""Get the latest health status.

Response includes the quantity of engine oil and any dashboard warning lights. \n
* If supported.

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

Returns:
-------
VehicleHealthResponseModel: A pydantic model for the vehicle health response
"""
parsed_response = await self._request_and_parse(
VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin
VehicleHealthResponseModel, "GET", VEHICLE_HEALTH_STATUS_ENDPOINT, vin=vin, brand=brand
)
_LOGGER.debug(msg=f"Parsed 'VehicleHealthResponseModel': {parsed_response}")
return parsed_response

async def get_remote_status_endpoint(self, vin: str) -> RemoteStatusResponseModel:
async def get_remote_status_endpoint(self, vin: str, brand: str) -> RemoteStatusResponseModel:
"""Get information about the vehicle."""
parsed_response = await self._request_and_parse(
RemoteStatusResponseModel,
"GET",
VEHICLE_GLOBAL_REMOTE_STATUS_ENDPOINT,
vin=vin,
brand=brand,
)
_LOGGER.debug(msg=f"Parsed 'RemoteStatusResponseModel': {parsed_response}")
return parsed_response

async def get_vehicle_electric_status_endpoint(self, vin: str) -> ElectricResponseModel:
async def get_vehicle_electric_status_endpoint(
self, vin: str, brand: str
) -> ElectricResponseModel:
r"""Get the latest electric status.

Response includes current battery level, EV Range, EV Range with AC, \n
fuel level, fuel range and current charging status

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

Returns:
-------
Expand All @@ -153,30 +164,32 @@ async def get_vehicle_electric_status_endpoint(self, vin: str) -> ElectricRespon
"GET",
VEHICLE_GLOBAL_REMOTE_ELECTRIC_STATUS_ENDPOINT,
vin=vin,
brand=brand,
)
_LOGGER.debug(msg=f"Parsed 'ElectricResponseModel': {parsed_response}")
return parsed_response

async def get_telemetry_endpoint(self, vin: str) -> TelemetryResponseModel:
async def get_telemetry_endpoint(self, vin: str, brand: str) -> TelemetryResponseModel:
"""Get the latest telemetry status.

Response includes current fuel level, distance to empty and odometer

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

Returns:
-------
TelemetryResponseModel: A pydantic model for the telemetry response
"""
parsed_response = await self._request_and_parse(
TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin
TelemetryResponseModel, "GET", VEHICLE_TELEMETRY_ENDPOINT, vin=vin, brand=brand
)
_LOGGER.debug(msg=f"Parsed 'TelemetryResponseModel': {parsed_response}")
return parsed_response

async def get_notification_endpoint(self, vin: str) -> NotificationResponseModel:
async def get_notification_endpoint(self, vin: str, brand: str) -> NotificationResponseModel:
"""Get all available notifications for the vehicle.

A notification includes a message, notification date, read flag, date read.
Expand All @@ -185,7 +198,8 @@ async def get_notification_endpoint(self, vin: str) -> NotificationResponseModel

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

Returns:
-------
Expand All @@ -196,13 +210,15 @@ async def get_notification_endpoint(self, vin: str) -> NotificationResponseModel
"GET",
VEHICLE_NOTIFICATION_HISTORY_ENDPOINT,
vin=vin,
brand=brand,
)
_LOGGER.debug(msg=f"Parsed 'NotificationResponseModel': {parsed_response}")
return parsed_response

async def get_trips_endpoint( # noqa: PLR0913
self,
vin: str,
brand: str,
from_date: date,
to_date: date,
route: bool = False,
Expand All @@ -219,6 +235,7 @@ async def get_trips_endpoint( # noqa: PLR0913
Args:
----
vin: str: The vehicles VIN
brand: str: The car brand used for the request.
from_date: date: From date to include trips, inclusive. Cant be in the future.
to_date: date: To date to include trips, inclusive. Cant be in the future.
route: bool: If true returns the route of each trip as a list of coordinates.
Expand All @@ -240,26 +257,33 @@ async def get_trips_endpoint( # noqa: PLR0913
offset=offset,
)
parsed_response = await self._request_and_parse(
TripsResponseModel, "GET", endpoint, vin=vin
TripsResponseModel, "GET", endpoint, vin=vin, brand=brand
)
_LOGGER.debug(msg=f"Parsed 'TripsResponseModel': {parsed_response}")
return parsed_response

async def get_service_history_endpoint(self, vin: str) -> ServiceHistoryResponseModel:
async def get_service_history_endpoint(
self, vin: str, brand: str
) -> ServiceHistoryResponseModel:
"""Get the current servic history.

Response includes service category, date and dealer.

Args:
----
vin: str: The vehicles VIN
vin: (str): The vehicles VIN
brand (str): The car brand used for the request.

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
ServiceHistoryResponseModel,
"GET",
VEHICLE_SERVICE_HISTORY_ENDPONT,
vin=vin,
brand=brand,
)
_LOGGER.debug(msg=f"Parsed 'ServiceHistoryResponseModel': {parsed_response}")
return parsed_response
4 changes: 2 additions & 2 deletions mytoyota/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,10 @@
_LOGGER.debug("Performing first login")
await self._api.controller.login()

async def get_vehicles(self, metric: bool = True) -> Optional[List[Vehicle]]:
async def get_vehicles(self, metric: bool = True, brand: str = "T") -> Optional[List[Vehicle]]:
"""Return a list of vehicles."""
_LOGGER.debug("Getting list of vehicles associated with the account")
vehicles = await self._api.get_vehicles_endpoint()
vehicles = await self._api.get_vehicles_endpoint(brand)

Check warning on line 64 in mytoyota/client.py

View check run for this annotation

Codecov / codecov/patch

mytoyota/client.py#L64

Added line #L64 was not covered by tests
if vehicles.payload is not None:
return [Vehicle(self._api, v, metric) for v in vehicles.payload]

Expand Down
21 changes: 19 additions & 2 deletions mytoyota/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@
# This seems to work sometimes but no others. Needs investigation.


def get_brand_headers(headers: Dict[str, Any], brand: str) -> Dict[str, Any]:
"""Update the headers, depending on the given car brand."""
if brand == "L":
headers |= {

Check warning on line 41 in mytoyota/controller.py

View check run for this annotation

Codecov / codecov/patch

mytoyota/controller.py#L40-L41

Added lines #L40 - L41 were not covered by tests
"x-appbrand": "L",
"brand": "L",
"x-brand": "L",
}
elif brand == "T":
headers["x-brand"] = "T"
return headers

Check warning on line 48 in mytoyota/controller.py

View check run for this annotation

Codecov / codecov/patch

mytoyota/controller.py#L46-L48

Added lines #L46 - L48 were not covered by tests


class Controller:
"""Controller class."""

Expand Down Expand Up @@ -215,6 +228,7 @@
self,
method: str,
endpoint: str,
brand: str = "T",
vin: Optional[str] = None,
body: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
Expand All @@ -236,10 +250,10 @@
"guid": self._uuid,
"authorization": f"Bearer {self._token}",
"x-channel": "ONEAPP",
"x-brand": "T",
"user-agent": "okhttp/4.10.0",
},
)
headers = get_brand_headers(headers, brand)

Check warning on line 256 in mytoyota/controller.py

View check run for this annotation

Codecov / codecov/patch

mytoyota/controller.py#L256

Added line #L256 was not covered by tests
# Add vin if passed
if vin is not None:
headers.update({"vin": vin})
Expand All @@ -266,6 +280,7 @@
self,
method: str,
endpoint: str,
brand: str = "T",
vin: Optional[str] = None,
body: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
Expand All @@ -279,6 +294,8 @@
endpoint (str): The endpoint to send the request to.
vin (Optional[str], optional): The VIN (Vehicle Identification Number) to include
in the request. Defaults to None.
brand (str): The car brand used for the request.
Defaults to Toyota.
body (Optional[Dict[str, Any]], optional): The JSON body to include in the request.
Defaults to None.
params (Optional[Dict[str, Any]], optional): The query parameters to
Expand All @@ -294,6 +311,6 @@
--------
response = await request_json("GET", "/cars", vin="1234567890")
"""
response = await self.request_raw(method, endpoint, vin, body, params, headers)
response = await self.request_raw(method, endpoint, brand, vin, body, params, headers)

Check warning on line 314 in mytoyota/controller.py

View check run for this annotation

Codecov / codecov/patch

mytoyota/controller.py#L314

Added line #L314 was not covered by tests

return response.json()
2 changes: 1 addition & 1 deletion mytoyota/models/endpoints/vehicle_guid.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ class VehicleGuidModel(BaseModel):
head_unit: _HeadUnitModel = Field(alias="headUnit")
hw_type: Optional[Any] = Field(alias="hwType") # TODO unsure what this returns
image: str
imei: str
imei: Optional[str] = None
katashiki_code: str = Field(alias="katashikiCode")
manufactured_date: date = Field(alias="manufacturedDate")
manufactured_code: str = Field(alias="manufacturerCode")
Expand Down
30 changes: 25 additions & 5 deletions mytoyota/models/vehicle.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,17 @@ def __init__(self, api: Api, vehicle_info: VehicleGuidModel, metric: bool = True
"name": "location",
"capable": vehicle_info.extended_capabilities.last_parked_capable
or vehicle_info.features.last_parked,
"function": partial(self._api.get_location_endpoint, vin=vehicle_info.vin),
"function": partial(
self._api.get_location_endpoint, vin=vehicle_info.vin, brand=vehicle_info.brand
),
},
{
"name": "health_status",
"capable": True, # TODO Unsure of the required capability
"function": partial(
self._api.get_vehicle_health_status_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
{
Expand All @@ -59,27 +62,44 @@ def __init__(self, api: Api, vehicle_info: VehicleGuidModel, metric: bool = True
"function": partial(
self._api.get_vehicle_electric_status_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
{
"name": "telemetry",
"capable": vehicle_info.extended_capabilities.telemetry_capable,
"function": partial(self._api.get_telemetry_endpoint, vin=vehicle_info.vin),
"function": partial(
self._api.get_telemetry_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
{
"name": "notifications",
"capable": True, # TODO Unsure of the required capability
"function": partial(self._api.get_notification_endpoint, vin=vehicle_info.vin),
"function": partial(
self._api.get_notification_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
{
"name": "status",
"capable": vehicle_info.extended_capabilities.vehicle_status,
"function": partial(self._api.get_remote_status_endpoint, vin=vehicle_info.vin),
"function": partial(
self._api.get_remote_status_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
{
"name": "service_history",
"capable": vehicle_info.features.service_history,
"function": partial(self._api.get_service_history_endpoint, vin=vehicle_info.vin),
"function": partial(
self._api.get_service_history_endpoint,
vin=vehicle_info.vin,
brand=vehicle_info.brand,
),
},
]
self._endpoint_collect = [
Expand Down
6 changes: 2 additions & 4 deletions simple_client_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async def get_information():
await client.login()

print("Retrieving cars...")
cars = await client.get_vehicles(metric=True)
cars = await client.get_vehicles(metric=True, brand="L")

for car in cars:
await car.update()
Expand Down Expand Up @@ -85,6 +85,4 @@ async def get_information():
# pp.pprint(car._dump_all())


loop = asyncio.get_event_loop()
loop.run_until_complete(get_information())
loop.close()
asyncio.run(get_information())
Loading
Loading