mirror of
https://github.com/home-assistant/core.git
synced 2025-11-08 02:19:31 +00:00
248 lines
8.3 KiB
Python
248 lines
8.3 KiB
Python
"""Config flow for Volvo."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Mapping
|
|
import logging
|
|
from typing import Any
|
|
|
|
import voluptuous as vol
|
|
from volvocarsapi.api import VolvoCarsApi
|
|
from volvocarsapi.models import VolvoApiException, VolvoCarsVehicle
|
|
from volvocarsapi.scopes import ALL_SCOPES
|
|
|
|
from homeassistant.config_entries import (
|
|
SOURCE_REAUTH,
|
|
SOURCE_RECONFIGURE,
|
|
ConfigFlowResult,
|
|
)
|
|
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_API_KEY, CONF_NAME, CONF_TOKEN
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import aiohttp_client
|
|
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
|
from homeassistant.helpers.selector import (
|
|
SelectOptionDict,
|
|
SelectSelector,
|
|
SelectSelectorConfig,
|
|
TextSelector,
|
|
TextSelectorConfig,
|
|
TextSelectorType,
|
|
)
|
|
|
|
from .api import ConfigFlowVolvoAuth
|
|
from .const import CONF_VIN, DOMAIN, MANUFACTURER
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
def _create_volvo_cars_api(
|
|
hass: HomeAssistant, access_token: str, api_key: str
|
|
) -> VolvoCarsApi:
|
|
web_session = aiohttp_client.async_get_clientsession(hass)
|
|
auth = ConfigFlowVolvoAuth(web_session, access_token)
|
|
return VolvoCarsApi(web_session, auth, api_key)
|
|
|
|
|
|
class VolvoOAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
|
"""Config flow to handle Volvo OAuth2 authentication."""
|
|
|
|
DOMAIN = DOMAIN
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize Volvo config flow."""
|
|
super().__init__()
|
|
|
|
self._vehicles: list[VolvoCarsVehicle] = []
|
|
self._config_data: dict = {}
|
|
|
|
@property
|
|
def extra_authorize_data(self) -> dict:
|
|
"""Extra data that needs to be appended to the authorize url."""
|
|
return super().extra_authorize_data | {
|
|
"scope": " ".join(ALL_SCOPES),
|
|
}
|
|
|
|
@property
|
|
def logger(self) -> logging.Logger:
|
|
"""Return logger."""
|
|
return _LOGGER
|
|
|
|
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
|
|
"""Create an entry for the flow."""
|
|
self._config_data |= (self.init_data or {}) | data
|
|
return await self.async_step_api_key()
|
|
|
|
async def async_step_reauth(self, _: Mapping[str, Any]) -> ConfigFlowResult:
|
|
"""Perform reauth upon an API authentication error."""
|
|
return await self.async_step_reauth_confirm()
|
|
|
|
async def async_step_reconfigure(
|
|
self, data: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Reconfigure the entry."""
|
|
return await self.async_step_api_key()
|
|
|
|
async def async_step_reauth_confirm(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Confirm reauth dialog."""
|
|
if user_input is None:
|
|
return self.async_show_form(
|
|
step_id="reauth_confirm",
|
|
description_placeholders={CONF_NAME: self._get_reauth_entry().title},
|
|
)
|
|
return await self.async_step_user()
|
|
|
|
async def async_step_api_key(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Handle the API key step."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if user_input is not None:
|
|
api = _create_volvo_cars_api(
|
|
self.hass,
|
|
self._config_data[CONF_TOKEN][CONF_ACCESS_TOKEN],
|
|
user_input[CONF_API_KEY],
|
|
)
|
|
|
|
# Try to load all vehicles on the account. If it succeeds
|
|
# it means that the given API key is correct. The vehicle info
|
|
# is used in the VIN step.
|
|
try:
|
|
await self._async_load_vehicles(api)
|
|
except VolvoApiException:
|
|
_LOGGER.exception("Unable to retrieve vehicles")
|
|
errors["base"] = "cannot_load_vehicles"
|
|
|
|
if not errors:
|
|
self._config_data |= user_input
|
|
return await self.async_step_vin()
|
|
|
|
if user_input is None:
|
|
if self.source == SOURCE_REAUTH:
|
|
user_input = self._config_data
|
|
api = _create_volvo_cars_api(
|
|
self.hass,
|
|
self._config_data[CONF_TOKEN][CONF_ACCESS_TOKEN],
|
|
self._config_data[CONF_API_KEY],
|
|
)
|
|
|
|
# Test if the configured API key is still valid. If not, show this
|
|
# form. If it is, skip this step and go directly to the next step.
|
|
try:
|
|
await self._async_load_vehicles(api)
|
|
return await self.async_step_vin()
|
|
except VolvoApiException:
|
|
pass
|
|
|
|
elif self.source == SOURCE_RECONFIGURE:
|
|
user_input = self._config_data = dict(
|
|
self._get_reconfigure_entry().data
|
|
)
|
|
else:
|
|
user_input = {}
|
|
|
|
schema = self.add_suggested_values_to_schema(
|
|
vol.Schema(
|
|
{
|
|
vol.Required(CONF_API_KEY): TextSelector(
|
|
TextSelectorConfig(
|
|
type=TextSelectorType.TEXT, autocomplete="password"
|
|
)
|
|
),
|
|
}
|
|
),
|
|
{
|
|
CONF_API_KEY: user_input.get(CONF_API_KEY, ""),
|
|
},
|
|
)
|
|
|
|
return self.async_show_form(
|
|
step_id="api_key",
|
|
data_schema=schema,
|
|
errors=errors,
|
|
description_placeholders={
|
|
"volvo_dev_portal": "https://developer.volvocars.com/account/#your-api-applications"
|
|
},
|
|
)
|
|
|
|
async def async_step_vin(
|
|
self, user_input: dict[str, Any] | None = None
|
|
) -> ConfigFlowResult:
|
|
"""Handle the VIN step."""
|
|
errors: dict[str, str] = {}
|
|
|
|
if len(self._vehicles) == 1:
|
|
# If there is only one VIN, take that as value and
|
|
# immediately create the entry. No need to show
|
|
# the VIN step.
|
|
self._config_data[CONF_VIN] = self._vehicles[0].vin
|
|
return await self._async_create_or_update()
|
|
|
|
if self.source in (SOURCE_REAUTH, SOURCE_RECONFIGURE):
|
|
# Don't let users change the VIN. The entry should be
|
|
# recreated if they want to change the VIN.
|
|
return await self._async_create_or_update()
|
|
|
|
if user_input is not None:
|
|
self._config_data |= user_input
|
|
return await self._async_create_or_update()
|
|
|
|
if len(self._vehicles) == 0:
|
|
errors[CONF_VIN] = "no_vehicles"
|
|
|
|
schema = vol.Schema(
|
|
{
|
|
vol.Required(CONF_VIN): SelectSelector(
|
|
SelectSelectorConfig(
|
|
options=[
|
|
SelectOptionDict(
|
|
value=v.vin,
|
|
label=f"{v.description.model} ({v.vin})",
|
|
)
|
|
for v in self._vehicles
|
|
],
|
|
multiple=False,
|
|
)
|
|
),
|
|
},
|
|
)
|
|
|
|
return self.async_show_form(step_id="vin", data_schema=schema, errors=errors)
|
|
|
|
async def _async_create_or_update(self) -> ConfigFlowResult:
|
|
vin = self._config_data[CONF_VIN]
|
|
await self.async_set_unique_id(vin)
|
|
|
|
if self.source == SOURCE_REAUTH:
|
|
self._abort_if_unique_id_mismatch()
|
|
return self.async_update_reload_and_abort(
|
|
self._get_reauth_entry(),
|
|
data_updates=self._config_data,
|
|
)
|
|
|
|
if self.source == SOURCE_RECONFIGURE:
|
|
self._abort_if_unique_id_mismatch()
|
|
return self.async_update_reload_and_abort(
|
|
self._get_reconfigure_entry(),
|
|
data_updates=self._config_data,
|
|
reload_even_if_entry_is_unchanged=False,
|
|
)
|
|
|
|
self._abort_if_unique_id_configured()
|
|
return self.async_create_entry(
|
|
title=f"{MANUFACTURER} {vin}",
|
|
data=self._config_data,
|
|
)
|
|
|
|
async def _async_load_vehicles(self, api: VolvoCarsApi) -> None:
|
|
self._vehicles = []
|
|
vins = await api.async_get_vehicles()
|
|
|
|
for vin in vins:
|
|
vehicle = await api.async_get_vehicle_details(vin)
|
|
|
|
if vehicle:
|
|
self._vehicles.append(vehicle)
|