Skip to content

Commit

Permalink
Use Reconfiguration instead of Options for this data. Store customer …
Browse files Browse the repository at this point in the history
…gid and title. No longer updating info after reload though.
  • Loading branch information
magico13 committed Jan 25, 2025
1 parent be40e96 commit 33ef788
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 115 deletions.
21 changes: 16 additions & 5 deletions custom_components/emporia_vue/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@
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
from .const import (
CONFIG_TITLE,
CUSTOMER_GID,
DOMAIN,
ENABLE_1D,
ENABLE_1M,
ENABLE_1MON,
VUE_DATA,
)

_LOGGER: logging.Logger = logging.getLogger(__name__)

Expand All @@ -44,7 +52,6 @@
LAST_DAY_DATA: dict[str, Any] = {}
LAST_DAY_UPDATE: datetime | None = None


async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Emporia Vue component."""
hass.data.setdefault(DOMAIN, {})
Expand All @@ -62,6 +69,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
ENABLE_1M: conf[ENABLE_1M],
ENABLE_1D: conf[ENABLE_1D],
ENABLE_1MON: conf[ENABLE_1MON],
CUSTOMER_GID: conf[CUSTOMER_GID],
CONFIG_TITLE: conf[CONFIG_TITLE],
},
)
)
Expand All @@ -76,6 +85,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
DEVICE_INFORMATION = {}

entry_data = entry.data
_LOGGER.warning("Setting up Emporia Vue with entry data: %s", entry_data)
email: str = entry_data[CONF_EMAIL]
password: str = entry_data[CONF_PASSWORD]
vue = PyEmVue()
Expand Down Expand Up @@ -551,10 +561,11 @@ def make_channel_id(channel: VueDeviceChannel, scale: str) -> str:


def fix_usage_sign(channel_num: str, usage: float, bidirectional: bool) -> float:
"""If the channel is not '1,2,3' or 'Balance' we need it to be positive.
(see https://github.com/magico13/ha-emporia-vue/issues/57)
"""
If the channel is not '1,2,3' or 'Balance' we need it to be positive
(see https://github.com/magico13/ha-emporia-vue/issues/57).
"""

if usage and not bidirectional and channel_num not in ["1,2,3", "Balance"]:
# With bidirectionality, we need to also check if bidirectional. If yes,
# we either don't abs, or we flip the sign.
Expand Down
119 changes: 68 additions & 51 deletions custom_components/emporia_vue/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@
from pyemvue import PyEmVue
import voluptuous as vol

from homeassistant import config_entries, core, exceptions
from homeassistant import config_entries, exceptions
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD

from .const import DOMAIN, DOMAIN_SCHEMA, ENABLE_1D, ENABLE_1M, ENABLE_1MON
from .const import (
CONFIG_TITLE,
CUSTOMER_GID,
DOMAIN,
ENABLE_1D,
ENABLE_1M,
ENABLE_1MON,
USER_CONFIG_SCHEMA,
)

_LOGGER: logging.Logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -47,11 +55,13 @@ async def validate_input(data: dict):

# Return info that you want to store in the config entry.
return {
"title": f"Customer {hub.vue.customer.customer_gid}",
"gid": f"{hub.vue.customer.customer_gid}",
CONFIG_TITLE: f"Customer {hub.vue.customer.customer_gid}",
CUSTOMER_GID: f"{hub.vue.customer.customer_gid}",
ENABLE_1M: data[ENABLE_1M],
ENABLE_1D: data[ENABLE_1D],
ENABLE_1MON: data[ENABLE_1MON],
CONF_EMAIL: data[CONF_EMAIL],
CONF_PASSWORD: data[CONF_PASSWORD],
}


Expand All @@ -61,7 +71,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL

async def async_step_user(self, user_input=None) -> config_entries.FlowResult:
async def async_step_user(self, user_input=None) -> config_entries.ConfigFlowResult:
"""Handle the initial step."""
errors = {}
if user_input is not None:
Expand All @@ -72,7 +82,7 @@ async def async_step_user(self, user_input=None) -> config_entries.FlowResult:
self._abort_if_unique_id_configured()

return self.async_create_entry(
title=info["title"], data=user_input, options=user_input
title=info["title"], data=user_input
)
except CannotConnect:
errors["base"] = "cannot_connect"
Expand All @@ -83,20 +93,66 @@ async def async_step_user(self, user_input=None) -> config_entries.FlowResult:
errors["base"] = "unknown"

return self.async_show_form(
step_id="user", data_schema=DOMAIN_SCHEMA, errors=errors
step_id="user", data_schema=USER_CONFIG_SCHEMA, errors=errors
)

async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
"""Handle the reconfiguration step."""
current_config = self._get_reconfigure_entry()
if user_input is not None:
_LOGGER.warning("User input on reconfigure was the following: %s", user_input)
_LOGGER.warning("Current config is: %s", current_config.data)
info = current_config.data
# if gid is not in current config, reauth and get gid again
if CUSTOMER_GID not in current_config.data or not current_config.data[CUSTOMER_GID]:
info = await validate_input(current_config.data) # type: ignore

await self.async_set_unique_id(info[CUSTOMER_GID])
self._abort_if_unique_id_mismatch(reason="wrong_account")
data = {
ENABLE_1M: user_input[ENABLE_1M],
ENABLE_1D: user_input[ENABLE_1D],
ENABLE_1MON: user_input[ENABLE_1MON],
CUSTOMER_GID: info[CUSTOMER_GID],
CONFIG_TITLE: info[CONFIG_TITLE],
}
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=data,
)

return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
vol.Optional(
ENABLE_1M,
default=current_config.data.get(ENABLE_1M, True),
): bool,
vol.Optional(
ENABLE_1D,
default=current_config.data.get(ENABLE_1D, True),
): bool,
vol.Optional(
ENABLE_1MON,
default=current_config.data.get(ENABLE_1MON, True),
): bool,
}
),
)

async def async_step_reauth(
self, entry_data: dict[str, Any]
) -> config_entries.FlowResult:
) -> config_entries.ConfigFlowResult:
"""Perform reauthentication upon an API authentication error."""
return await self.async_step_reauth_confirm()
return await self.async_step_reauth_confirm(entry_data)

async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> config_entries.FlowResult:
) -> config_entries.ConfigFlowResult:
"""Confirm reauthentication dialog."""
errors: dict[str, str] = {}
existing_entry = self._get_reauth_entry()
if user_input:
gid = 0
try:
Expand All @@ -112,7 +168,7 @@ async def async_step_reauth_confirm(
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(),
existing_entry,
data_updates={
CONF_EMAIL: user_input[CONF_EMAIL],
CONF_PASSWORD: user_input[CONF_PASSWORD],
Expand All @@ -122,54 +178,15 @@ async def async_step_reauth_confirm(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_EMAIL): str,
vol.Required(CONF_EMAIL, default=existing_entry.data[CONF_EMAIL]): str,
vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
)

@staticmethod
@core.callback
def async_get_options_flow(
config_entry: config_entries.ConfigEntry,
) -> config_entries.OptionsFlow:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)


class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""


class OptionsFlowHandler(config_entries.OptionsFlow):
"""Handle an options flow for Emporia Vue."""

async def async_step_init(self, user_input=None) -> config_entries.FlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(title="", data=user_input)

return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
{
vol.Optional(
ENABLE_1M,
default=self.config_entry.options.get(ENABLE_1M, True),
): bool,
vol.Optional(
ENABLE_1D,
default=self.config_entry.options.get(ENABLE_1D, True),
): bool,
vol.Optional(
ENABLE_1MON,
default=self.config_entry.options.get(ENABLE_1MON, True),
): bool,
}
),
)


class InvalidAuth(exceptions.HomeAssistantError):
"""Error to indicate there is invalid auth."""
19 changes: 16 additions & 3 deletions custom_components/emporia_vue/const.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
"""Constants for the Emporia Vue integration."""

import homeassistant.helpers.config_validation as cv
import voluptuous as vol

from homeassistant.const import CONF_EMAIL, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv

DOMAIN = "emporia_vue"
VUE_DATA = "vue_data"
ENABLE_1S = "enable_1s"
ENABLE_1M = "enable_1m"
ENABLE_1D = "enable_1d"
ENABLE_1MON = "enable_1mon"
CUSTOMER_GID = "customer_gid"
CONFIG_TITLE = "title"

DOMAIN_SCHEMA = vol.Schema(
USER_CONFIG_SCHEMA = vol.Schema(
{
vol.Required(CONF_EMAIL): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
Expand All @@ -23,7 +26,17 @@

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: DOMAIN_SCHEMA,
DOMAIN: vol.Schema(
{
vol.Required(CONF_EMAIL): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(ENABLE_1M, default=True): cv.boolean, # type: ignore
vol.Optional(ENABLE_1D, default=True): cv.boolean, # type: ignore
vol.Optional(ENABLE_1MON, default=True): cv.boolean, # type: ignore
vol.Required(CUSTOMER_GID): cv.positive_int,
vol.Required(CONFIG_TITLE): cv.string,
}
),
},
extra=vol.ALLOW_EXTRA,
)
16 changes: 6 additions & 10 deletions custom_components/emporia_vue/sensor.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""Platform for sensor integration."""

import logging
from datetime import datetime
from functools import cached_property
import logging

from pyemvue.device import VueDevice, VueDeviceChannel
from pyemvue.enums import Scale

from homeassistant.components.sensor import (
SensorDeviceClass,
Expand All @@ -15,8 +17,6 @@
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from pyemvue.device import VueDevice, VueDeviceChannel
from pyemvue.enums import Scale

from .const import DOMAIN

Expand Down Expand Up @@ -100,7 +100,6 @@ def __init__(self, coordinator, identifier) -> None:
self._attr_suggested_display_precision = 1
self._attr_name = f"Power {self.scale_readable()}"

@cached_property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
device_name = self._channel.name or self._device.device_name
Expand All @@ -114,24 +113,21 @@ def device_info(self) -> DeviceInfo:
manufacturer="Emporia",
)

@cached_property
def last_reset(self) -> datetime | None:
"""The time when the daily/monthly sensor was reset. Midnight local time."""
"""Reset time of the daily/monthly sensor. Midnight local time."""
if self._id in self.coordinator.data:
return self.coordinator.data[self._id]["reset"]
return None

@cached_property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
if self._id in self.coordinator.data:
usage = self.coordinator.data[self._id]["usage"]
return self.scale_usage(usage) if usage is not None else None
return None

@cached_property
def unique_id(self) -> str:
"""Unique ID for the sensor."""
"""Return the Unique ID for the sensor."""
if self._scale == Scale.MINUTE.value:
return (
"sensor.emporia_vue.instant."
Expand Down
28 changes: 13 additions & 15 deletions custom_components/emporia_vue/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@
"enable_1d": "Energy Today Sensor",
"enable_1mon": "Energy This Month Sensor"
}
},
"reconfigure": {
"enable_1m": "Power Minute Average Sensor",
"enable_1d": "Energy Today Sensor",
"enable_1mon": "Energy This Month Sensor"
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The Emporia Vue integration needs to re-authenticate your account"
}
},
"error": {
Expand All @@ -17,20 +26,9 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
},
"options": {
"step": {
"init": {
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]",
"enable_1m": "Power Minute Average Sensor",
"enable_1d": "Energy Today Sensor",
"enable_1mon": "Energy This Month Sensor"
}
}
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}
}
}
}
Loading

0 comments on commit 33ef788

Please sign in to comment.