Update powerwall for tesla_powerwall 0.5.0 which is async (#107164)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
bdba6f41c9
commit
c74bef265a
20 changed files with 401 additions and 209 deletions
|
@ -1,16 +1,18 @@
|
|||
"""Config flow for Tesla Powerwall integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import CookieJar
|
||||
from tesla_powerwall import (
|
||||
AccessDeniedError,
|
||||
MissingAttributeError,
|
||||
Powerwall,
|
||||
PowerwallUnreachableError,
|
||||
SiteInfo,
|
||||
SiteInfoResponse,
|
||||
)
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -18,6 +20,7 @@ from homeassistant import config_entries, core, exceptions
|
|||
from homeassistant.components import dhcp
|
||||
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
|
||||
from homeassistant.data_entry_flow import FlowResult
|
||||
from homeassistant.helpers.aiohttp_client import async_create_clientsession
|
||||
from homeassistant.util.network import is_ip_address
|
||||
|
||||
from . import async_last_update_was_successful
|
||||
|
@ -32,19 +35,23 @@ ENTRY_FAILURE_STATES = {
|
|||
}
|
||||
|
||||
|
||||
def _login_and_fetch_site_info(
|
||||
async def _login_and_fetch_site_info(
|
||||
power_wall: Powerwall, password: str
|
||||
) -> tuple[SiteInfo, str]:
|
||||
) -> tuple[SiteInfoResponse, str]:
|
||||
"""Login to the powerwall and fetch the base info."""
|
||||
if password is not None:
|
||||
power_wall.login(password)
|
||||
return power_wall.get_site_info(), power_wall.get_gateway_din()
|
||||
await power_wall.login(password)
|
||||
|
||||
return await asyncio.gather(
|
||||
power_wall.get_site_info(), power_wall.get_gateway_din()
|
||||
)
|
||||
|
||||
|
||||
def _powerwall_is_reachable(ip_address: str, password: str) -> bool:
|
||||
async def _powerwall_is_reachable(ip_address: str, password: str) -> bool:
|
||||
"""Check if the powerwall is reachable."""
|
||||
try:
|
||||
Powerwall(ip_address).login(password)
|
||||
async with Powerwall(ip_address) as power_wall:
|
||||
await power_wall.login(password)
|
||||
except AccessDeniedError:
|
||||
return True
|
||||
except PowerwallUnreachableError:
|
||||
|
@ -59,21 +66,23 @@ async def validate_input(
|
|||
|
||||
Data has the keys from schema with values provided by the user.
|
||||
"""
|
||||
session = async_create_clientsession(
|
||||
hass, verify_ssl=False, cookie_jar=CookieJar(unsafe=True)
|
||||
)
|
||||
async with Powerwall(data[CONF_IP_ADDRESS], http_session=session) as power_wall:
|
||||
password = data[CONF_PASSWORD]
|
||||
|
||||
power_wall = Powerwall(data[CONF_IP_ADDRESS])
|
||||
password = data[CONF_PASSWORD]
|
||||
try:
|
||||
site_info, gateway_din = await _login_and_fetch_site_info(
|
||||
power_wall, password
|
||||
)
|
||||
except MissingAttributeError as err:
|
||||
# Only log the exception without the traceback
|
||||
_LOGGER.error(str(err))
|
||||
raise WrongVersion from err
|
||||
|
||||
try:
|
||||
site_info, gateway_din = await hass.async_add_executor_job(
|
||||
_login_and_fetch_site_info, power_wall, password
|
||||
)
|
||||
except MissingAttributeError as err:
|
||||
# Only log the exception without the traceback
|
||||
_LOGGER.error(str(err))
|
||||
raise WrongVersion from err
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": site_info.site_name, "unique_id": gateway_din.upper()}
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": site_info.site_name, "unique_id": gateway_din.upper()}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
|
@ -102,9 +111,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
return bool(
|
||||
entry.state in ENTRY_FAILURE_STATES
|
||||
or not async_last_update_was_successful(self.hass, entry)
|
||||
) and not await self.hass.async_add_executor_job(
|
||||
_powerwall_is_reachable, ip_address, password
|
||||
)
|
||||
) and not await _powerwall_is_reachable(ip_address, password)
|
||||
|
||||
async def async_step_dhcp(self, discovery_info: dhcp.DhcpServiceInfo) -> FlowResult:
|
||||
"""Handle dhcp discovery."""
|
||||
|
@ -137,7 +144,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"name": gateway_din,
|
||||
"ip_address": self.ip_address,
|
||||
}
|
||||
errors, info = await self._async_try_connect(
|
||||
errors, info, _ = await self._async_try_connect(
|
||||
{CONF_IP_ADDRESS: self.ip_address, CONF_PASSWORD: gateway_din[-5:]}
|
||||
)
|
||||
if errors:
|
||||
|
@ -152,23 +159,28 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
|
||||
async def _async_try_connect(
|
||||
self, user_input: dict[str, Any]
|
||||
) -> tuple[dict[str, Any] | None, dict[str, str] | None]:
|
||||
) -> tuple[dict[str, Any] | None, dict[str, str] | None, dict[str, str]]:
|
||||
"""Try to connect to the powerwall."""
|
||||
info = None
|
||||
errors: dict[str, str] = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
except PowerwallUnreachableError:
|
||||
except PowerwallUnreachableError as ex:
|
||||
errors[CONF_IP_ADDRESS] = "cannot_connect"
|
||||
except WrongVersion:
|
||||
description_placeholders = {"error": str(ex)}
|
||||
except WrongVersion as ex:
|
||||
errors["base"] = "wrong_version"
|
||||
except AccessDeniedError:
|
||||
description_placeholders = {"error": str(ex)}
|
||||
except AccessDeniedError as ex:
|
||||
errors[CONF_PASSWORD] = "invalid_auth"
|
||||
except Exception: # pylint: disable=broad-except
|
||||
description_placeholders = {"error": str(ex)}
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
_LOGGER.exception("Unexpected exception")
|
||||
errors["base"] = "unknown"
|
||||
description_placeholders = {"error": str(ex)}
|
||||
|
||||
return errors, info
|
||||
return errors, info, description_placeholders
|
||||
|
||||
async def async_step_confirm_discovery(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
@ -204,8 +216,11 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
) -> FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] | None = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
errors, info = await self._async_try_connect(user_input)
|
||||
errors, info, description_placeholders = await self._async_try_connect(
|
||||
user_input
|
||||
)
|
||||
if not errors:
|
||||
assert info is not None
|
||||
if info["unique_id"]:
|
||||
|
@ -227,6 +242,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
}
|
||||
),
|
||||
errors=errors,
|
||||
description_placeholders=description_placeholders,
|
||||
)
|
||||
|
||||
async def async_step_reauth_confirm(
|
||||
|
@ -235,9 +251,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Handle reauth confirmation."""
|
||||
assert self.reauth_entry is not None
|
||||
errors: dict[str, str] | None = {}
|
||||
description_placeholders: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
entry_data = self.reauth_entry.data
|
||||
errors, _ = await self._async_try_connect(
|
||||
errors, _, description_placeholders = await self._async_try_connect(
|
||||
{CONF_IP_ADDRESS: entry_data[CONF_IP_ADDRESS], **user_input}
|
||||
)
|
||||
if not errors:
|
||||
|
@ -251,6 +268,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
step_id="reauth_confirm",
|
||||
data_schema=vol.Schema({vol.Optional(CONF_PASSWORD): str}),
|
||||
errors=errors,
|
||||
description_placeholders=description_placeholders,
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue