diff --git a/custom_components/emporia_vue/__init__.py b/custom_components/emporia_vue/__init__.py index a4dc28b..4d74de8 100644 --- a/custom_components/emporia_vue/__init__.py +++ b/custom_components/emporia_vue/__init__.py @@ -1,21 +1,13 @@ """The Emporia Vue integration.""" import asyncio +from datetime import UTC, datetime, timedelta, tzinfo import logging import re -from datetime import UTC, datetime, timedelta, tzinfo from typing import Any import dateutil.relativedelta import dateutil.tz -import requests -from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant, State -from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError -from homeassistant.helpers import entity_registry as er -from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from pyemvue import PyEmVue from pyemvue.device import ( ChargerDevice, @@ -25,6 +17,19 @@ VueUsageDevice, ) from pyemvue.enums import Scale +import requests + +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry +from homeassistant.const import CONF_EMAIL, CONF_PASSWORD +from homeassistant.core import HomeAssistant, State +from homeassistant.exceptions import ( + ConfigEntryAuthFailed, + ConfigEntryNotReady, + HomeAssistantError, +) +from homeassistant.helpers import entity_registry as er +from homeassistant.helpers.typing import ConfigType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, ENABLE_1D, ENABLE_1M, ENABLE_1MON, VUE_DATA @@ -79,10 +84,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: result: bool = await loop.run_in_executor(None, vue.login, email, password) if not result: _LOGGER.error("Failed to login to Emporia Vue") - return False + raise ConfigEntryAuthFailed("Failed to login to Emporia Vue") + except ConfigEntryAuthFailed: + raise except Exception as err: # pylint: disable=broad-exception-caught _LOGGER.error("Failed to login to Emporia Vue: %s", err) - return False + raise ConfigEntryAuthFailed("Failed to login to Emporia Vue") from err try: devices: list[VueDevice] = await loop.run_in_executor(None, vue.get_devices) @@ -459,10 +466,11 @@ async def parse_flattened_usage_data( fixed_usage, ) - bidirectional = "bidirectional" in info_channel.type.lower() or "merged" in info_channel.type.lower() - fixed_usage = fix_usage_sign( - channel_num, fixed_usage, bidirectional + bidirectional = ( + "bidirectional" in info_channel.type.lower() + or "merged" in info_channel.type.lower() ) + fixed_usage = fix_usage_sign(channel_num, fixed_usage, bidirectional) data[identifier] = { "device_gid": gid, diff --git a/custom_components/emporia_vue/config_flow.py b/custom_components/emporia_vue/config_flow.py index 0abc147..21bb33e 100644 --- a/custom_components/emporia_vue/config_flow.py +++ b/custom_components/emporia_vue/config_flow.py @@ -2,11 +2,13 @@ import asyncio import logging +from typing import Any +from pyemvue import PyEmVue import voluptuous as vol + from homeassistant import config_entries, core, exceptions from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from pyemvue import PyEmVue from .const import DOMAIN, DOMAIN_SCHEMA, ENABLE_1D, ENABLE_1M, ENABLE_1MON @@ -33,7 +35,7 @@ async def validate_input(data: dict): """ hub = VueHub() if not await hub.authenticate(data[CONF_EMAIL], data[CONF_PASSWORD]): - raise InvalidAuth() + raise InvalidAuth # If you cannot connect: # throw CannotConnect @@ -41,7 +43,7 @@ async def validate_input(data: dict): # InvalidAuth if not hub.vue.customer: - raise InvalidAuth() + raise InvalidAuth # Return info that you want to store in the config entry. return { @@ -84,6 +86,49 @@ async def async_step_user(self, user_input=None) -> config_entries.FlowResult: step_id="user", data_schema=DOMAIN_SCHEMA, errors=errors ) + async def async_step_reauth( + self, entry_data: dict[str, Any] + ) -> config_entries.FlowResult: + """Perform reauthentication upon an API authentication error.""" + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: dict[str, Any] | None = None + ) -> config_entries.FlowResult: + """Confirm reauthentication dialog.""" + errors: dict[str, str] = {} + if user_input: + gid = 0 + try: + hub = VueHub() + if not await hub.authenticate( + user_input[CONF_EMAIL], user_input[CONF_PASSWORD] + ): + raise InvalidAuth + gid = hub.vue.customer.customer_gid + except InvalidAuth: + errors["base"] = "invalid_auth" + else: + await self.async_set_unique_id(str(gid)) + self._abort_if_unique_id_mismatch(reason="wrong_account") + return self.async_update_reload_and_abort( + self._get_reauth_entry(), + data_updates={ + CONF_EMAIL: user_input[CONF_EMAIL], + CONF_PASSWORD: user_input[CONF_PASSWORD], + }, + ) + return self.async_show_form( + step_id="reauth_confirm", + data_schema=vol.Schema( + { + vol.Required(CONF_EMAIL): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + @staticmethod @core.callback def async_get_options_flow( @@ -98,11 +143,7 @@ class CannotConnect(exceptions.HomeAssistantError): class OptionsFlowHandler(config_entries.OptionsFlow): - """Handle a options flow for Emporia Vue.""" - - def __init__(self, config_entry: config_entries.ConfigEntry) -> None: - """Initialize options flow.""" - self.config_entry: config_entries.ConfigEntry = config_entry + """Handle an options flow for Emporia Vue.""" async def async_step_init(self, user_input=None) -> config_entries.FlowResult: """Manage the options.""" diff --git a/custom_components/emporia_vue/sensor.py b/custom_components/emporia_vue/sensor.py index 3b7214c..91e990a 100644 --- a/custom_components/emporia_vue/sensor.py +++ b/custom_components/emporia_vue/sensor.py @@ -138,7 +138,7 @@ def unique_id(self) -> str: f"{self._channel.device_gid}-{self._channel.channel_num}" ) return ( - "sensor.emporia_vue.{self._scale}." + f"sensor.emporia_vue.{self._scale}." f"{self._channel.device_gid}-{self._channel.channel_num}" ) diff --git a/custom_components/emporia_vue/strings.json b/custom_components/emporia_vue/strings.json index de9ef3c..044b498 100644 --- a/custom_components/emporia_vue/strings.json +++ b/custom_components/emporia_vue/strings.json @@ -5,9 +5,9 @@ "data": { "email": "[%key:common::config_flow::data::email%]", "password": "[%key:common::config_flow::data::password%]", - "enable_1m": "One Minute Sensor", - "enable_1d": "One Day Sensor", - "enable_1mon": "One Month Sensor" + "enable_1m": "Power Minute Average Sensor", + "enable_1d": "Energy Today Sensor", + "enable_1mon": "Energy This Month Sensor" } } }, @@ -26,9 +26,9 @@ "data": { "email": "[%key:common::config_flow::data::email%]", "password": "[%key:common::config_flow::data::password%]", - "enable_1m": "One Minute Sensor", - "enable_1d": "One Day Sensor", - "enable_1mon": "One Month Sensor" + "enable_1m": "Power Minute Average Sensor", + "enable_1d": "Energy Today Sensor", + "enable_1mon": "Energy This Month Sensor" } } }