core/homeassistant/components/subaru/__init__.py

174 lines
5.2 KiB
Python
Raw Normal View History

"""The Subaru integration."""
import asyncio
from datetime import timedelta
import logging
import time
from subarulink import Controller as SubaruAPI, InvalidCredentials, SubaruException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_DEVICE_ID, CONF_PASSWORD, CONF_PIN, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import (
CONF_COUNTRY,
CONF_UPDATE_ENABLED,
COORDINATOR_NAME,
DOMAIN,
ENTRY_CONTROLLER,
ENTRY_COORDINATOR,
ENTRY_VEHICLES,
FETCH_INTERVAL,
SUPPORTED_PLATFORMS,
UPDATE_INTERVAL,
VEHICLE_API_GEN,
VEHICLE_HAS_EV,
VEHICLE_HAS_REMOTE_SERVICE,
VEHICLE_HAS_REMOTE_START,
VEHICLE_HAS_SAFETY_SERVICE,
VEHICLE_LAST_UPDATE,
VEHICLE_NAME,
VEHICLE_VIN,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass, base_config):
"""Do nothing since this integration does not support configuration.yml setup."""
hass.data.setdefault(DOMAIN, {})
return True
async def async_setup_entry(hass, entry):
"""Set up Subaru from a config entry."""
config = entry.data
websession = aiohttp_client.async_get_clientsession(hass)
try:
controller = SubaruAPI(
websession,
config[CONF_USERNAME],
config[CONF_PASSWORD],
config[CONF_DEVICE_ID],
config[CONF_PIN],
None,
config[CONF_COUNTRY],
update_interval=UPDATE_INTERVAL,
fetch_interval=FETCH_INTERVAL,
)
_LOGGER.debug("Using subarulink %s", controller.version)
await controller.connect()
except InvalidCredentials:
_LOGGER.error("Invalid account")
return False
except SubaruException as err:
raise ConfigEntryNotReady(err.message) from err
vehicle_info = {}
for vin in controller.get_vehicles():
vehicle_info[vin] = get_vehicle_info(controller, vin)
async def async_update_data():
"""Fetch data from API endpoint."""
try:
return await refresh_subaru_data(entry, vehicle_info, controller)
except SubaruException as err:
raise UpdateFailed(err.message) from err
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name=COORDINATOR_NAME,
update_method=async_update_data,
update_interval=timedelta(seconds=FETCH_INTERVAL),
)
await coordinator.async_refresh()
hass.data[DOMAIN][entry.entry_id] = {
ENTRY_CONTROLLER: controller,
ENTRY_COORDINATOR: coordinator,
ENTRY_VEHICLES: vehicle_info,
}
for component in SUPPORTED_PLATFORMS:
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, component)
)
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, component)
for component in SUPPORTED_PLATFORMS
]
)
)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
async def refresh_subaru_data(config_entry, vehicle_info, controller):
"""
Refresh local data with data fetched via Subaru API.
Subaru API calls assume a server side vehicle context
Data fetch/update must be done for each vehicle
"""
data = {}
for vehicle in vehicle_info.values():
vin = vehicle[VEHICLE_VIN]
# Active subscription required
if not vehicle[VEHICLE_HAS_SAFETY_SERVICE]:
continue
# Optionally send an "update" remote command to vehicle (throttled with update_interval)
if config_entry.options.get(CONF_UPDATE_ENABLED, False):
await update_subaru(vehicle, controller)
# Fetch data from Subaru servers
await controller.fetch(vin, force=True)
# Update our local data that will go to entity states
received_data = await controller.get_data(vin)
if received_data:
data[vin] = received_data
return data
async def update_subaru(vehicle, controller):
"""Commands remote vehicle update (polls the vehicle to update subaru API cache)."""
cur_time = time.time()
last_update = vehicle[VEHICLE_LAST_UPDATE]
if cur_time - last_update > controller.get_update_interval():
await controller.update(vehicle[VEHICLE_VIN], force=True)
vehicle[VEHICLE_LAST_UPDATE] = cur_time
def get_vehicle_info(controller, vin):
"""Obtain vehicle identifiers and capabilities."""
info = {
VEHICLE_VIN: vin,
VEHICLE_NAME: controller.vin_to_name(vin),
VEHICLE_HAS_EV: controller.get_ev_status(vin),
VEHICLE_API_GEN: controller.get_api_gen(vin),
VEHICLE_HAS_REMOTE_START: controller.get_res_status(vin),
VEHICLE_HAS_REMOTE_SERVICE: controller.get_remote_status(vin),
VEHICLE_HAS_SAFETY_SERVICE: controller.get_safety_status(vin),
VEHICLE_LAST_UPDATE: 0,
}
return info