Skip to content

Commit

Permalink
Use new mytoyota api endpoints (#198)
Browse files Browse the repository at this point in the history
* poetry update and use stub files in dev dependencies for better hints

* update mytoyota dependency in manifest file

* fix implicit string concatenation

* adapt config flow to new api

* adopt statistics gathering to upcoming mytoyota changes

* adopt device tracker to new API

* update manifest file

* add statistic to base entity

* bump version

* using brand from vehicle info

* simplify genrating of device tracker

* bump required minimum mytoyota version to 1.1.0

* remove unused binary sensors for now

* simplify capability check

* adopt binary sensors to new api models

* fix import error

* use last_parked capability for generating device_tracker

* make metric value choise a requirement

* make metric value choise a requirement

* add translations

* bump minmum required mytoyota version

* invert binary sensor logic

* only create trunk status binary_sensor if bonnet_status capability is reported by car

* run pre-commit on all files

* return None if try block fails

* increase update_interval to 5 minutes

* adjust driver door lock icon

* make metric value information available for further steps

* adopt statistics sensors to new api

* update statistics utils and delete unsused constants

* add vin sensor

* display all vehicle informations in vin sensor

* display ev summary also for pure electric vehicles

* return timdelta and date as string

* add odometer and fuel sensors

* clean up sensor translations

* format vin sensor attributes

* use car_model_name

* make battery_level sensor available for all ev vehicles

* use upper case for asi_code and imei

* fix battery_level sensor

* return only true capabilities

* add battery_range

* add total_range

* add battery_range_ac

* fix unpacking

* reurn dict instead of list

* mask pii data in attributes

* round range and level values

* bump minimum required mytoyota version to 1.1.3

* reduce line-length

* no need for masking Katashiki_code

* add duration and distance to summary attributes

* remove redundant vin information in attributes

* remove redundant information

* rename fuel consumption

* bump minimum required mytoyota version to 1.2.0

* add average_fuel_consumed to vin sensor attributes

* use dictionary merging

* Make use of new helper functions for statistics

* Update readme

* generate battery sensors only for ev vehicles

* Use Mapping for brand names

* create odometer only when telemetry capable

* Update French translations (#203)

Signed-off-by: Pierre Belloy <[email protected]>

* Use same condition for ev vehicles like in mytoyota lib

* use same condition for last_parked like in mytoyota lib

* create statistics sensors by default for now

---------

Signed-off-by: Pierre Belloy <[email protected]>
Co-authored-by: Pierre Belloy <[email protected]>
  • Loading branch information
CM000n and pierrebelloy authored Jan 14, 2024
1 parent 142b5fe commit 8f7909a
Show file tree
Hide file tree
Showing 20 changed files with 1,102 additions and 754 deletions.
107 changes: 47 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
## About

This is a custom integration the retrieves' data from the
Toyota EU MyT API and makes them available in Home Assistant as different types of sensors.
Toyota EU MyToyota ctpa-oneapi API and makes them available in Home Assistant as different types of sensors.
As there is no official API from Toyota, I will try my best to keep
it working, but there are no promises.

Expand All @@ -30,47 +30,42 @@ See [here](https://github.com/widewing/ha-toyota-na) for North America.

### Overview

- Numberplate and starter battery sensors
- VIN (Vehicle Identification Number) sensor
- Fuel, battery and odometer information
- Current week, month and year statistics.
- HVAC, Window and lights sensors
- Current day, week, month and year statistics.
- Door and door lock sensors, including hood and trunk sensor.
- Is key in car and over all status sensor.

### Binary sensor(s)

| <div style="width:250px">Name</div> | Description |
| --------------------------------------- | ----------------------------------------------------------------------------------- |
| `binary_sensor.corolla_hood` | If the hood is open of not. |
| `binary_sensor.corolla_*_defogger` | Defogger is on sensor, one is created for front and rear if available |
| `binary_sensor.corolla_*_door` | Door sensor, one is created for each door and trunk. |
| `binary_sensor.corolla_*_lock` | Lock sensor, one is created for each door and trunk. |
| `binary_sensor.corolla_*_lights` | Light sensor, one is created for front, back and hazard lights. |
| `binary_sensor.corolla_over_all_status` | Over all status of the vehicle, if warning is true for a sensor, this will show it. |
| `binary_sensor.corolla_key_in_car` | If key is in the car. |
| `binary_sensor.corolla_*_window` | Window sensor, one is created for window. |
| <div style="width:250px">Name</div> | Description |
| ---------------------------------------- | ----------------------------------------------------- |
| `binary_sensor.<you_car_alias>_hood` | If the hood is open of not. |
| `binary_sensor.<you_car_alias>_*_door` | Door sensors, one is created for each door and trunk. |
| `binary_sensor.<you_car_alias>_*_lock` | Lock sensors, one is created for each door and trunk. |
| `binary_sensor.<you_car_alias>_*_window` | Window sensors, one is created for window. |

### Device tracker(s)

| <div style="width:250px">Name</div> | Description |
| ----------------------------------- | ---------------------------------- |
| `device_tracker.corolla` | Shows you last parked information. |
| <div style="width:250px">Name</div> | Description |
| ----------------------------------- | ----------------------------------- |
| `device_tracker.<you_car_alias>` | Shows you last parking information. |

### Sensor(s)

| <div style="width:250px">Name</div> | Description |
| ------------------------------------ | ------------------------------------------------------------------------ |
| `sensor.corolla` | Static data about your car. |
| `sensor.corolla_ev_battery_status` | EV battery information |
| `sensor.corolla_ev_remaining_charge` | EV battery remaining charge (in per cent of full capacity) |
| `sensor.corolla_fuel_tank` | Fuel tank information. |
| `sensor.corolla_hvac` | HVAC sensor showing current and target temperature, including other data |
| `sensor.corolla_odometer` | Odometer information. |
| `sensor.corolla_range` | Remaining range sensor |
| `sensor.aygo_starter_battery` | Starter battery health. |
| `sensor.corolla_current_week_stats` | Statistics for current week. |
| `sensor.corolla_current_month_stats` | Statistics for current month. |
| `sensor.corolla_current_year_stats` | Statistics for current year. |
| <div style="width:250px">Name</div> | Description |
| -------------------------------------------- | -------------------------------------------------- |
| `sensor.<you_car_alias>_vin` | Static data about your car. |
| `sensor.<you_car_alias>_odometer` | Odometer information. |
| `sensor.<you_car_alias>_fuel_level` | Fuel level information. |
| `sensor.<you_car_alias>_fuel_range` | Fuel range information. |
| `sensor.<you_car_alias>_battery_level` | Battery level information. |
| `sensor.<you_car_alias>_battery_range` | Battery range information. |
| `sensor.<you_car_alias>_battery_range_ac` | Battery range information when AC is on. |
| `sensor.<you_car_alias>_total_range` | Information about combined fuel and battery range. |
| `sensor.<you_car_alias>_current_day_stats` | Statistics for current day. |
| `sensor.<you_car_alias>_current_week_stats` | Statistics for current week. |
| `sensor.<you_car_alias>_current_month_stats` | Statistics for current month. |
| `sensor.<you_car_alias>_current_year_stats` | Statistics for current year. |

### Statistics sensors

Expand All @@ -83,34 +78,26 @@ Due to this, this integration will list sensors as unavailable when no data is a

**Disclaimer: Attributes available depends on your car model and year.**

All values will show zero if no data is available for the periode.

| Attribute | Description |
| ------------------------------------ | --------------------------------------------------------------------------------------------------- |
| `Highway_distance` | Distance driven on Highway/Motorway. |
| `Highway_percentage` | Percentage driven on Highway/Motorway. |
| `Number_of_trips` | Number of trips performed. A trip is started when you start the engine. |
| `Number_of_night_trips` | Number of trips performed at night. |
| `Total_driving_time` | Total time driven. |
| `Average_speed` | Average speed. |
| `Max_speed` | Max speed achieved. |
| `Hard_acceleration_count` | Hard accelerations counter. Can be very sensitive. |
| `Hard_braking_count` | Hard braking counter. Can be very sensitive. |
| `Average_fuel_consumed` | Average fuel consumed. If car is in km then this will show L/100km. If in mi then it will show Mpg. |
| `Coaching_advice_most_occurrence` | Coaching advice most occurrence. |
| `Average_driver_score` | Average driver score. |
| `Average_driver_score_accelerations` | Average driver score for accelerations. |
| `Average_driver_score_braking` | Average driver score for braking. |
| `EV_distance` | Distance which have been driven on electric. |
| `EV_distance_percentage` | Percentage of the distance driven on electric. |
| `EV_driving_time` | Driving time on electric. |
| `EV_duration_percentage` | Percentage of the driving time on electric. |
All values will show `None` if no data is available for the periode.

| Attribute | Description |
| ----------------------- | ------------------------------------------------------------------------------- |
| `Distance` | Distance driven (Displayed as sensor value). |
| `Average_speed` | The average speed in the respective period (can be km/h or mph). |
| `Countries` | The countries travelled through in the respective period. |
| `Duration` | The total driving time in the respective period. |
| `Total_fuel_consumed` | The total fuel consumption in the respective period (can be litres or gallons). |
| `Average_fuel_consumed` | The average fuel consumption in the respective period (can be l/100km or mpg). |
| `EV_distance` | The driving distiance in EV mode in the respective period . |
| `EV_duration` | The driving time in EV mode in the respective period . |
| `From_date` | Start date of the calculation period. |
| `To_date` | End date of the calculation period. |

## Getting started

### Prerequisites

Use Home Assistant build 2021.4 or above.
Use Home Assistant build 2023.12 or above.

If you can confirm that it is working as advertised on older version please open a PR.

Expand Down Expand Up @@ -138,16 +125,16 @@ Now you can clone the repository somewhere else and symlink it to Home Assistant

1. Clone the repo.

```shell
git clone https://github.com/DurgNomis-drol/ha_toyota.git
```
```shell
git clone https://github.com/DurgNomis-drol/ha_toyota.git
```

2. Create the symlink to `toyota` in the configuration directory.
If you have non-standard directory for configuration, use it instead.

```shell
ln -s ha_toyota/custom_components/toyota ~/.homeassistant/custom_components/toyota
```
```shell
ln -s ha_toyota/custom_components/toyota ~/.homeassistant/custom_components/toyota
```

#### Copy method

Expand Down
96 changes: 38 additions & 58 deletions custom_components/toyota/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,40 @@
import asyncio.exceptions as asyncioexceptions
import logging
from datetime import timedelta
from typing import Any, Optional, TypedDict
from typing import Optional, TypedDict

import httpcore
import httpx
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_EMAIL,
CONF_PASSWORD,
CONF_UNIT_SYSTEM_IMPERIAL,
CONF_UNIT_SYSTEM_METRIC,
)
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from mytoyota import MyT
from mytoyota.exceptions import ToyotaApiError, ToyotaInternalError, ToyotaLoginError
from mytoyota.models.summary import Summary
from mytoyota.models.vehicle import Vehicle

from .const import (
CONF_UNIT_SYSTEM_IMPERIAL_LITERS,
CONF_USE_LITERS_PER_100_MILES,
DOMAIN,
PLATFORMS,
STARTUP_MESSAGE,
)
from .const import CONF_METRIC_VALUES, DOMAIN, PLATFORMS, STARTUP_MESSAGE

_LOGGER = logging.getLogger(__name__)


class StatisticsData(TypedDict):
"""Representing Statistics data."""

day: list[dict[str, Any]]
week: list[dict[str, Any]]
month: list[dict[str, Any]]
year: list[dict[str, Any]]
day: Optional[Summary]
week: Optional[Summary]
month: Optional[Summary]
year: Optional[Summary]


class VehicleData(TypedDict):
"""Representing Vehicle data."""

data: Vehicle
statistics: Optional[StatisticsData]
metric_values: bool


async def async_setup_entry( # pylint: disable=too-many-statements
Expand All @@ -60,12 +51,11 @@ async def async_setup_entry( # pylint: disable=too-many-statements

email = entry.data[CONF_EMAIL]
password = entry.data[CONF_PASSWORD]
use_liters = entry.options.get(CONF_USE_LITERS_PER_100_MILES, False)
use_metric_values = entry.data[CONF_METRIC_VALUES]

client = MyT(
username=email,
password=password,
disable_locale_check=True,
)

try:
Expand All @@ -75,49 +65,38 @@ async def async_setup_entry( # pylint: disable=too-many-statements
except (httpx.ConnectTimeout, httpcore.ConnectTimeout) as ex:
raise ConfigEntryNotReady("Unable to connect to Toyota Connected Services") from ex

async def async_get_vehicle_data() -> list[VehicleData]:
async def async_get_vehicle_data() -> Optional[list[VehicleData]]:
"""Fetch vehicle data from Toyota API."""
try:
vehicles = await asyncio.wait_for(client.get_vehicles(), 15)
vehicles = await asyncio.wait_for(client.get_vehicles(metric=use_metric_values), 15)
vehicle_informations: list[VehicleData] = []
for vehicle in vehicles:
vehicle_status = await client.get_vehicle_status(vehicle)
_LOGGER.debug(vars(vehicle_status))

vehicle_data = VehicleData(data=vehicle_status, statistics=None)

unit_system_map = {
False: CONF_UNIT_SYSTEM_IMPERIAL,
True: CONF_UNIT_SYSTEM_IMPERIAL_LITERS,
}
unit = CONF_UNIT_SYSTEM_METRIC if vehicle_status.dashboard.is_metric else unit_system_map[use_liters]

_LOGGER.debug(f"The car is reporting data in {unit}")
if use_liters and not vehicle_status.dashboard.is_metric:
_LOGGER.debug("Getting statistics in imperial and L/100 miles")
elif not vehicle_status.dashboard.is_metric:
_LOGGER.debug("Getting statistics in imperial and MPG")

if vehicle_status.is_connected_services_enabled and vehicle_status.vin is not None:
# Use parallel request to get car statistics.
driving_statistics = await asyncio.gather(
client.get_driving_statistics(vehicle_status.vin, interval="day", unit=unit),
client.get_driving_statistics(vehicle_status.vin, interval="isoweek", unit=unit),
client.get_driving_statistics(vehicle_status.vin, unit=unit),
client.get_driving_statistics(vehicle_status.vin, interval="year", unit=unit),
if vehicles is not None:
for vehicle in vehicles:
await vehicle.update()
vehicle_data = VehicleData(
data=vehicle, statistics=None, metric_values=use_metric_values
)

vehicle_data["statistics"] = StatisticsData(
day=driving_statistics[0],
week=driving_statistics[1],
month=driving_statistics[2],
year=driving_statistics[3],
)
if vehicle.vin is not None:
# Use parallel request to get car statistics.
driving_statistics = await asyncio.gather(
vehicle.get_current_day_summary(),
vehicle.get_current_week_summary(),
vehicle.get_current_month_summary(),
vehicle.get_current_year_summary(),
)

vehicle_data["statistics"] = StatisticsData(
day=driving_statistics[0],
week=driving_statistics[1],
month=driving_statistics[2],
year=driving_statistics[3],
)

vehicle_informations.append(vehicle_data)
vehicle_informations.append(vehicle_data)

_LOGGER.debug(vehicle_informations)
return vehicle_informations
_LOGGER.debug(vehicle_informations)
return vehicle_informations

except ToyotaLoginError as ex:
_LOGGER.error(ex)
Expand All @@ -133,15 +112,16 @@ async def async_get_vehicle_data() -> list[VehicleData]:
httpx.ReadTimeout,
) as ex:
raise UpdateFailed(
"Update canceled! Toyota's API was too slow to respond." " Will try again later..."
"Update canceled! Toyota's API was too slow to respond. Will try again later..."
) from ex
return None

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
update_method=async_get_vehicle_data,
update_interval=timedelta(seconds=120),
update_interval=timedelta(seconds=360),
)

await coordinator.async_config_entry_first_refresh()
Expand Down
Loading

0 comments on commit 8f7909a

Please sign in to comment.