Home Assistant Unofficial Reference 2024.12.1
__init__.py
Go to the documentation of this file.
1 """The Subaru integration."""
2 
3 from datetime import timedelta
4 import logging
5 import time
6 
7 from subarulink import Controller as SubaruAPI, InvalidCredentials, SubaruException
8 
9 from homeassistant.config_entries import ConfigEntry
10 from homeassistant.const import (
11  CONF_COUNTRY,
12  CONF_DEVICE_ID,
13  CONF_PASSWORD,
14  CONF_PIN,
15  CONF_USERNAME,
16 )
17 from homeassistant.core import HomeAssistant
18 from homeassistant.exceptions import ConfigEntryNotReady
19 from homeassistant.helpers import aiohttp_client
20 from homeassistant.helpers.device_registry import DeviceInfo
21 from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
22 
23 from .const import (
24  CONF_UPDATE_ENABLED,
25  COORDINATOR_NAME,
26  DOMAIN,
27  ENTRY_CONTROLLER,
28  ENTRY_COORDINATOR,
29  ENTRY_VEHICLES,
30  FETCH_INTERVAL,
31  MANUFACTURER,
32  PLATFORMS,
33  UPDATE_INTERVAL,
34  VEHICLE_API_GEN,
35  VEHICLE_HAS_EV,
36  VEHICLE_HAS_REMOTE_SERVICE,
37  VEHICLE_HAS_REMOTE_START,
38  VEHICLE_HAS_SAFETY_SERVICE,
39  VEHICLE_LAST_UPDATE,
40  VEHICLE_MODEL_NAME,
41  VEHICLE_MODEL_YEAR,
42  VEHICLE_NAME,
43  VEHICLE_VIN,
44 )
45 
46 _LOGGER = logging.getLogger(__name__)
47 
48 
49 async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
50  """Set up Subaru from a config entry."""
51  config = entry.data
52  websession = aiohttp_client.async_get_clientsession(hass)
53  try:
54  controller = SubaruAPI(
55  websession,
56  config[CONF_USERNAME],
57  config[CONF_PASSWORD],
58  config[CONF_DEVICE_ID],
59  config[CONF_PIN],
60  None,
61  config[CONF_COUNTRY],
62  update_interval=UPDATE_INTERVAL,
63  fetch_interval=FETCH_INTERVAL,
64  )
65  _LOGGER.debug("Using subarulink %s", controller.version)
66  await controller.connect()
67  except InvalidCredentials:
68  _LOGGER.error("Invalid account")
69  return False
70  except SubaruException as err:
71  raise ConfigEntryNotReady(err.message) from err
72 
73  vehicle_info = {}
74  for vin in controller.get_vehicles():
75  if controller.get_subscription_status(vin):
76  vehicle_info[vin] = get_vehicle_info(controller, vin)
77 
78  async def async_update_data():
79  """Fetch data from API endpoint."""
80  try:
81  return await refresh_subaru_data(entry, vehicle_info, controller)
82  except SubaruException as err:
83  raise UpdateFailed(err.message) from err
84 
85  coordinator = DataUpdateCoordinator(
86  hass,
87  _LOGGER,
88  config_entry=entry,
89  name=COORDINATOR_NAME,
90  update_method=async_update_data,
91  update_interval=timedelta(seconds=FETCH_INTERVAL),
92  )
93 
94  await coordinator.async_refresh()
95 
96  hass.data.setdefault(DOMAIN, {})
97  hass.data[DOMAIN][entry.entry_id] = {
98  ENTRY_CONTROLLER: controller,
99  ENTRY_COORDINATOR: coordinator,
100  ENTRY_VEHICLES: vehicle_info,
101  }
102 
103  await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
104 
105  return True
106 
107 
108 async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
109  """Unload a config entry."""
110  unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
111  if unload_ok:
112  hass.data[DOMAIN].pop(entry.entry_id)
113  return unload_ok
114 
115 
116 async def refresh_subaru_data(config_entry, vehicle_info, controller):
117  """Refresh local data with data fetched via Subaru API.
118 
119  Subaru API calls assume a server side vehicle context
120  Data fetch/update must be done for each vehicle
121  """
122  data = {}
123 
124  for vehicle in vehicle_info.values():
125  vin = vehicle[VEHICLE_VIN]
126 
127  # Optionally send an "update" remote command to vehicle (throttled with update_interval)
128  if config_entry.options.get(CONF_UPDATE_ENABLED, False):
129  await update_subaru(vehicle, controller)
130 
131  # Fetch data from Subaru servers
132  await controller.fetch(vin, force=True)
133 
134  # Update our local data that will go to entity states
135  if received_data := await controller.get_data(vin):
136  data[vin] = received_data
137 
138  return data
139 
140 
141 async def update_subaru(vehicle, controller):
142  """Commands remote vehicle update (polls the vehicle to update subaru API cache)."""
143  cur_time = time.time()
144  last_update = vehicle[VEHICLE_LAST_UPDATE]
145 
146  if cur_time - last_update > controller.get_update_interval():
147  await controller.update(vehicle[VEHICLE_VIN], force=True)
148  vehicle[VEHICLE_LAST_UPDATE] = cur_time
149 
150 
151 def get_vehicle_info(controller, vin):
152  """Obtain vehicle identifiers and capabilities."""
153  return {
154  VEHICLE_VIN: vin,
155  VEHICLE_MODEL_NAME: controller.get_model_name(vin),
156  VEHICLE_MODEL_YEAR: controller.get_model_year(vin),
157  VEHICLE_NAME: controller.vin_to_name(vin),
158  VEHICLE_HAS_EV: controller.get_ev_status(vin),
159  VEHICLE_API_GEN: controller.get_api_gen(vin),
160  VEHICLE_HAS_REMOTE_START: controller.get_res_status(vin),
161  VEHICLE_HAS_REMOTE_SERVICE: controller.get_remote_status(vin),
162  VEHICLE_HAS_SAFETY_SERVICE: controller.get_safety_status(vin),
163  VEHICLE_LAST_UPDATE: 0,
164  }
165 
166 
167 def get_device_info(vehicle_info):
168  """Return DeviceInfo object based on vehicle info."""
169  return DeviceInfo(
170  identifiers={(DOMAIN, vehicle_info[VEHICLE_VIN])},
171  manufacturer=MANUFACTURER,
172  model=f"{vehicle_info[VEHICLE_MODEL_YEAR]} {vehicle_info[VEHICLE_MODEL_NAME]}",
173  name=vehicle_info[VEHICLE_NAME],
174  )
def get_vehicle_info(controller, vin)
Definition: __init__.py:151
bool async_unload_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:108
bool async_setup_entry(HomeAssistant hass, ConfigEntry entry)
Definition: __init__.py:49
def get_device_info(vehicle_info)
Definition: __init__.py:167
def update_subaru(vehicle, controller)
Definition: __init__.py:141
def refresh_subaru_data(config_entry, vehicle_info, controller)
Definition: __init__.py:116