Skip to content

Commit

Permalink
Allow selecting different behavior for merged circuits, as needed by …
Browse files Browse the repository at this point in the history
…your setup.
  • Loading branch information
magico13 committed Feb 1, 2025
1 parent 6b05182 commit 5ba3493
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 70 deletions.
28 changes: 22 additions & 6 deletions custom_components/emporia_vue/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
ENABLE_1D,
ENABLE_1M,
ENABLE_1MON,
MERGED_ABS,
MERGED_BEHAVIOR,
MERGED_INVERT,
VUE_DATA,
)

Expand All @@ -51,6 +54,7 @@
LAST_MINUTE_DATA: dict[str, Any] = {}
LAST_DAY_DATA: dict[str, Any] = {}
LAST_DAY_UPDATE: datetime | None = None
CONF_MERGED_BEHAVIOR: str = MERGED_INVERT

async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Emporia Vue component."""
Expand All @@ -59,6 +63,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if not conf:
return True

global CONF_MERGED_BEHAVIOR
if MERGED_BEHAVIOR in conf:
CONF_MERGED_BEHAVIOR = conf[MERGED_BEHAVIOR]

hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
Expand All @@ -69,6 +77,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
ENABLE_1M: conf[ENABLE_1M],
ENABLE_1D: conf[ENABLE_1D],
ENABLE_1MON: conf[ENABLE_1MON],
MERGED_BEHAVIOR: conf[MERGED_BEHAVIOR],
CUSTOMER_GID: conf[CUSTOMER_GID],
CONFIG_TITLE: conf[CONFIG_TITLE],
},
Expand Down Expand Up @@ -481,11 +490,9 @@ 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()
merged = "merged" in info_channel.type.lower()
fixed_usage = fix_usage_sign(channel_num, fixed_usage, bidirectional, merged, CONF_MERGED_BEHAVIOR)

data[identifier] = {
"device_gid": gid,
Expand Down Expand Up @@ -565,11 +572,20 @@ def make_channel_id(channel: VueDeviceChannel, scale: str) -> str:
return f"{channel.device_gid}-{channel.channel_num}-{scale}"


def fix_usage_sign(channel_num: str, usage: float, bidirectional: bool) -> float:
def fix_usage_sign(channel_num: str, usage: float, bidirectional: bool, merged:bool, merged_behavior: str) -> float:
"""If the channel is not '1,2,3' or 'Balance' we need it to be positive.
Merged circuits are a special case, as they can be inverted, abs'd, or left alone to support different solar setups.
(see https://github.com/magico13/ha-emporia-vue/issues/57)
"""
if merged:
# for merged channels, we need to either invert or abs the value as requested
if merged_behavior == MERGED_ABS:
return abs(usage)
if merged_behavior == MERGED_INVERT:
return -usage
return usage

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,
Expand Down
86 changes: 59 additions & 27 deletions custom_components/emporia_vue/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

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

from .const import (
CONFIG_TITLE,
Expand All @@ -18,7 +20,10 @@
ENABLE_1D,
ENABLE_1M,
ENABLE_1MON,
USER_CONFIG_SCHEMA,
MERGED_ABS,
MERGED_BEHAVIOR,
MERGED_INVERT,
MERGED_NONE,
)

_LOGGER: logging.Logger = logging.getLogger(__name__)
Expand All @@ -42,7 +47,7 @@ async def authenticate(self, username, password) -> bool:
return await loop.run_in_executor(None, self.vue.login, username, password)


async def validate_input(data: dict):
async def validate_input(data: dict | Mapping[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect.
Data has the keys from DATA_SCHEMA with values provided by the user.
Expand All @@ -59,15 +64,21 @@ async def validate_input(data: dict):
if not hub.vue.customer:
raise InvalidAuth

new_data = dict(data)

if MERGED_BEHAVIOR not in new_data:
new_data[MERGED_BEHAVIOR] = MERGED_INVERT

# Return info that you want to store in the config entry.
return {
CONFIG_TITLE: f"{hub.vue.customer.email} ({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],
ENABLE_1M: new_data[ENABLE_1M],
ENABLE_1D: new_data[ENABLE_1D],
ENABLE_1MON: new_data[ENABLE_1MON],
MERGED_BEHAVIOR: new_data[MERGED_BEHAVIOR],
CONF_EMAIL: new_data[CONF_EMAIL],
CONF_PASSWORD: new_data[CONF_PASSWORD],
}


Expand Down Expand Up @@ -98,8 +109,22 @@ async def async_step_user(self, user_input=None) -> config_entries.ConfigFlowRes
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"

config_schema = {
vol.Required(CONF_EMAIL): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(ENABLE_1M, default=True): cv.boolean,
vol.Optional(ENABLE_1D, default=True): cv.boolean,
vol.Optional(ENABLE_1MON, default=True): cv.boolean,
}

config_schema[vol.Required(MERGED_BEHAVIOR)] = selector({
"select": {
"options": [MERGED_INVERT, MERGED_ABS, MERGED_NONE],
}
})

return self.async_show_form(
step_id="user", data_schema=USER_CONFIG_SCHEMA, errors=errors
step_id="user", data_schema=vol.Schema(config_schema), errors=errors
)

async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None) -> config_entries.ConfigFlowResult:
Expand All @@ -111,14 +136,15 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None)
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
info = await validate_input(current_config.data)

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],
MERGED_BEHAVIOR: user_input[MERGED_BEHAVIOR],
CUSTOMER_GID: info[CUSTOMER_GID],
CONFIG_TITLE: info[CONFIG_TITLE],
}
Expand All @@ -127,24 +153,30 @@ async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None)
data_updates=data,
)

data_schema : dict[vol.Optional | vol.Required, Any] = {
vol.Optional(
ENABLE_1M,
default=current_config.data.get(ENABLE_1M, True),
): cv.boolean,
vol.Optional(
ENABLE_1D,
default=current_config.data.get(ENABLE_1D, True),
): cv.boolean,
vol.Optional(
ENABLE_1MON,
default=current_config.data.get(ENABLE_1MON, True),
): cv.boolean,
}

data_schema[vol.Required(MERGED_BEHAVIOR)] = selector({
"select": {
"options": [MERGED_INVERT, MERGED_ABS, MERGED_NONE],
}
})

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,
}
),
data_schema=vol.Schema(data_schema),
)

async def async_step_reauth(
Expand Down Expand Up @@ -184,8 +216,8 @@ async def async_step_reauth_confirm(
step_id="reauth_confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_EMAIL, default=existing_entry.data[CONF_EMAIL]): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_EMAIL, default=existing_entry.data[CONF_EMAIL]): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
}
),
errors=errors,
Expand Down
34 changes: 4 additions & 30 deletions custom_components/emporia_vue/const.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,16 @@
"""Constants for the Emporia Vue integration."""

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"
MERGED_BEHAVIOR = "merged_behavior"
CUSTOMER_GID = "customer_gid"
CONFIG_TITLE = "title"

USER_CONFIG_SCHEMA = 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
}
)
MERGED_INVERT = "Invert"
MERGED_ABS = "Absolute Value"
MERGED_NONE = "None"

CONFIG_SCHEMA = vol.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,
)
6 changes: 4 additions & 2 deletions custom_components/emporia_vue/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@
"password": "[%key:common::config_flow::data::password%]",
"enable_1m": "Power Minute Average Sensor",
"enable_1d": "Energy Today Sensor",
"enable_1mon": "Energy This Month Sensor"
"enable_1mon": "Energy This Month Sensor",
"merged_behavior": "Merged Circuit Operation"
}
},
"reconfigure": {
"data": {
"enable_1m": "[%key:component::emporia_vue::config::step::user::data::enable_1m%]",
"enable_1d": "[%key:component::emporia_vue::config::step::user::data::enable_1d%]",
"enable_1mon": "[%key:component::emporia_vue::config::step::user::data::enable_1mon%]"
"enable_1mon": "[%key:component::emporia_vue::config::step::user::data::enable_1mon%]",
"merged_behavior": "[%key:component::emporia_vue::config::step::user::data::merged_behavior%]"
}
},
"reauth_confirm": {
Expand Down
12 changes: 7 additions & 5 deletions custom_components/emporia_vue/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,19 @@
},
"step": {
"reauth_confirm": {
"description": "The Emporia Vue integration needs to re-authenticate your account",
"title": "Authentication expired for {name}",
"data":{
"data": {
"email": "Email",
"password": "Password"
}
},
"description": "The Emporia Vue integration needs to re-authenticate your account",
"title": "Authentication expired for {name}"
},
"reconfigure": {
"data": {
"enable_1d": "Energy Today Sensor",
"enable_1m": "Power Minute Average Sensor",
"enable_1mon": "Energy This Month Sensor"
"enable_1mon": "Energy This Month Sensor",
"merged_behavior": "Merged Circuit Operation"
}
},
"user": {
Expand All @@ -32,6 +33,7 @@
"enable_1d": "Energy Today Sensor",
"enable_1m": "Power Minute Average Sensor",
"enable_1mon": "Energy This Month Sensor",
"merged_behavior": "Merged Circuit Operation",
"password": "Password"
}
}
Expand Down

0 comments on commit 5ba3493

Please sign in to comment.