158 lines
4.8 KiB
Python
158 lines
4.8 KiB
Python
"""The Subaru integration."""
|
|
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,
|
|
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_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.setdefault(DOMAIN, {})
|
|
hass.data[DOMAIN][entry.entry_id] = {
|
|
ENTRY_CONTROLLER: controller,
|
|
ENTRY_COORDINATOR: coordinator,
|
|
ENTRY_VEHICLES: vehicle_info,
|
|
}
|
|
|
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
|
|
|
return True
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|
"""Unload a config entry."""
|
|
unload_ok = await hass.config_entries.async_unload_platforms(entry, 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
|