core/homeassistant/components/mazda/__init__.py

173 lines
5.3 KiB
Python

"""The Mazda Connected Services integration."""
import asyncio
from datetime import timedelta
import logging
import async_timeout
from pymazda import (
Client as MazdaAPI,
MazdaAccountLockedException,
MazdaAPIEncryptionException,
MazdaAuthenticationException,
MazdaException,
MazdaTokenExpiredException,
)
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
UpdateFailed,
)
from homeassistant.util.async_ import gather_with_concurrency
from .const import DATA_CLIENT, DATA_COORDINATOR, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["sensor"]
async def with_timeout(task, timeout_seconds=10):
"""Run an async task with a timeout."""
async with async_timeout.timeout(timeout_seconds):
return await task
async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the Mazda Connected Services component."""
hass.data[DOMAIN] = {}
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up Mazda Connected Services from a config entry."""
email = entry.data[CONF_EMAIL]
password = entry.data[CONF_PASSWORD]
region = entry.data[CONF_REGION]
websession = aiohttp_client.async_get_clientsession(hass)
mazda_client = MazdaAPI(email, password, region, websession)
try:
await mazda_client.validate_credentials()
except MazdaAuthenticationException:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
return False
except (
MazdaException,
MazdaAccountLockedException,
MazdaTokenExpiredException,
MazdaAPIEncryptionException,
) as ex:
_LOGGER.error("Error occurred during Mazda login request: %s", ex)
raise ConfigEntryNotReady from ex
async def async_update_data():
"""Fetch data from Mazda API."""
try:
vehicles = await with_timeout(mazda_client.get_vehicles())
vehicle_status_tasks = [
with_timeout(mazda_client.get_vehicle_status(vehicle["id"]))
for vehicle in vehicles
]
statuses = await gather_with_concurrency(5, *vehicle_status_tasks)
for vehicle, status in zip(vehicles, statuses):
vehicle["status"] = status
return vehicles
except MazdaAuthenticationException as ex:
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
raise UpdateFailed("Not authenticated with Mazda API") from ex
except Exception as ex:
_LOGGER.exception(
"Unknown error occurred during Mazda update request: %s", ex
)
raise UpdateFailed(ex) from ex
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=DOMAIN,
update_method=async_update_data,
update_interval=timedelta(seconds=60),
)
hass.data[DOMAIN][entry.entry_id] = {
DATA_CLIENT: mazda_client,
DATA_COORDINATOR: coordinator,
}
# Fetch initial data so we have data when entities subscribe
await coordinator.async_config_entry_first_refresh()
# Setup components
for platform in PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, platform)
)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Unload a config entry."""
unload_ok = all(
await asyncio.gather(
*[
hass.config_entries.async_forward_entry_unload(entry, platform)
for platform in PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
class MazdaEntity(CoordinatorEntity):
"""Defines a base Mazda entity."""
def __init__(self, coordinator, index):
"""Initialize the Mazda entity."""
super().__init__(coordinator)
self.index = index
self.vin = self.coordinator.data[self.index]["vin"]
@property
def device_info(self):
"""Return device info for the Mazda entity."""
data = self.coordinator.data[self.index]
return {
"identifiers": {(DOMAIN, self.vin)},
"name": self.get_vehicle_name(),
"manufacturer": "Mazda",
"model": f"{data['modelYear']} {data['carlineName']}",
}
def get_vehicle_name(self):
"""Return the vehicle name, to be used as a prefix for names of other entities."""
data = self.coordinator.data[self.index]
if "nickname" in data and len(data["nickname"]) > 0:
return data["nickname"]
return f"{data['modelYear']} {data['carlineName']}"