Files
ha_config/custom_components/omnik_inverter/config_flow.py
T
2023-11-21 12:23:38 +01:00

367 lines
12 KiB
Python

"""Config flow for Omnik Inverter integration."""
from __future__ import annotations
import socket
from typing import Any
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, OptionsFlow
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
CONF_PASSWORD,
CONF_TYPE,
CONF_USERNAME,
)
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
from omnikinverter import OmnikInverter, OmnikInverterError
from .const import (
CONF_SCAN_INTERVAL,
CONF_SERIAL,
CONF_SOURCE_TYPE,
CONF_USE_CACHE,
CONFIGFLOW_VERSION,
DEFAULT_SCAN_INTERVAL,
DOMAIN,
LOGGER,
)
async def validate_input(user_input: dict[str, Any]) -> str | None:
"""
Validate the given user input.
Args:
user_input: The user input.
Returns:
The host name of the inverter
Raises:
Exception: If the host could not be validated.
"""
host = user_input[CONF_HOST]
try:
return socket.gethostbyname(host)
except socket.gaierror as exc:
raise Exception( # pylint: disable=broad-exception-raised
"invalid_host"
) from exc
class OmnikInverterFlowHandler(ConfigFlow, domain=DOMAIN):
"""Config flow for Omnik Inverter."""
VERSION = CONFIGFLOW_VERSION
def __init__(self):
"""Initialize with empty source type."""
self.source_type = None
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OmnikInverterOptionsFlowHandler:
"""
Get the options flow for this handler.
Args:
config_entry: The ConfigEntry instance.
Returns:
The created config flow.
"""
return OmnikInverterOptionsFlowHandler(config_entry)
async def async_step_user(
self, user_input=None, errors: dict[str, str] | None = None
) -> FlowResult:
"""
Handle a flow initialized by the user.
Args:
user_input: The input received from the user or none.
errors: A dict containing errors or none.
Returns:
The created config entry or a form to re-enter the user input with errors.
"""
errors = {}
if user_input is not None:
user_selection = user_input[CONF_TYPE]
self.source_type = user_selection.lower()
if user_selection == "HTML":
return await self.async_step_setup_html()
if user_selection == "TCP":
return await self.async_step_setup_tcp()
return await self.async_step_setup()
list_of_types = ["Javascript", "JSON", "HTML", "TCP"]
schema = vol.Schema({vol.Required(CONF_TYPE): vol.In(list_of_types)})
return self.async_show_form(step_id="user", data_schema=schema, errors=errors)
async def async_step_setup(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""
Handle setup flow for the JS and JSON route.
Args:
user_input: The input received from the user or none.
Returns:
The created config entry or a form to re-enter the user input with errors.
"""
errors = {}
if user_input is not None:
try:
await validate_input(user_input)
async with OmnikInverter(
host=user_input[CONF_HOST],
source_type=self.source_type,
) as client:
await client.inverter()
except OmnikInverterError:
LOGGER.exception("Failed to connect to the Omnik")
errors["base"] = "cannot_connect"
except Exception as error: # pylint: disable=broad-exception-caught
errors["base"] = str(error)
else:
return self.async_create_entry(
title=user_input[CONF_NAME],
data={
CONF_HOST: user_input[CONF_HOST],
CONF_SOURCE_TYPE: self.source_type,
},
)
return self.async_show_form(
step_id="setup",
data_schema=vol.Schema(
{
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
vol.Required(CONF_HOST): str,
}
),
errors=errors,
)
async def async_step_setup_html(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""
Handle setup flow for the HTML`route.
Args:
user_input: The input received from the user or none.
Returns:
The created config entry or a form to re-enter the user input with errors.
"""
errors = {}
if user_input is not None:
try:
await validate_input(user_input)
async with OmnikInverter(
host=user_input[CONF_HOST],
source_type=self.source_type,
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],
) as client:
await client.inverter()
except OmnikInverterError:
LOGGER.exception("Failed to connect to the Omnik")
errors["base"] = "cannot_connect"
except Exception as error: # pylint: disable=broad-exception-caught
errors["base"] = str(error)
else:
return self.async_create_entry(
title=user_input[CONF_NAME],
data={
CONF_HOST: user_input[CONF_HOST],
CONF_SOURCE_TYPE: self.source_type,
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
},
)
return self.async_show_form(
step_id="setup_html",
data_schema=vol.Schema(
{
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
vol.Required(CONF_HOST): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
)
async def async_step_setup_tcp(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""
Handle setup flow for the TCP route.
Args:
user_input: The input received from the user or none.
Returns:
The created config entry or a form to re-enter the user input with errors.
"""
errors = {}
if user_input is not None:
try:
await validate_input(user_input)
async with OmnikInverter(
host=user_input[CONF_HOST],
source_type=self.source_type,
serial_number=user_input[CONF_SERIAL],
) as client:
await client.inverter()
except OmnikInverterError:
LOGGER.exception("Failed to connect to the Omnik")
errors["base"] = "cannot_connect"
except Exception as error: # pylint: disable=broad-except
errors["base"] = str(error)
else:
return self.async_create_entry(
title=user_input[CONF_NAME],
data={
CONF_HOST: user_input[CONF_HOST],
CONF_SOURCE_TYPE: self.source_type,
CONF_SERIAL: user_input[CONF_SERIAL],
},
)
return self.async_show_form(
step_id="setup_tcp",
data_schema=vol.Schema(
{
vol.Optional(
CONF_NAME, default=self.hass.config.location_name
): str,
vol.Required(CONF_HOST): str,
vol.Required(CONF_SERIAL): int,
}
),
errors=errors,
)
class OmnikInverterOptionsFlowHandler(OptionsFlow):
"""Handle options."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""
Initialize options flow.
Args:
config_entry: The ConfigEntry instance.
"""
self.config_entry = config_entry
self.source_type = config_entry.data[CONF_SOURCE_TYPE]
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""
Handle a flow initialized by the user.
Args:
user_input: The input received from the user or none.
Returns:
The created config entry.
"""
errors = {}
if user_input is not None:
try:
await validate_input(user_input)
except OmnikInverterError:
LOGGER.exception("Failed to connect to the Omnik")
errors["base"] = "cannot_connect"
except Exception as error: # pylint: disable=broad-except
errors["base"] = str(error)
else:
updated_config = {CONF_SOURCE_TYPE: self.source_type}
for key in (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_SERIAL):
if key in user_input:
updated_config[key] = user_input[key]
self.hass.config_entries.async_update_entry(
self.config_entry,
data=updated_config,
title=user_input.get(CONF_NAME),
)
options = {}
for key in (CONF_SCAN_INTERVAL, CONF_USE_CACHE):
options[key] = user_input[key]
return self.async_create_entry(title="", data=options)
fields = {
vol.Optional(
CONF_NAME,
default=self.config_entry.title,
): str,
vol.Required(
CONF_HOST,
default=self.config_entry.data.get(CONF_HOST),
): str,
}
if self.source_type == "html":
fields[
vol.Required(
CONF_USERNAME, default=self.config_entry.data.get(CONF_USERNAME)
)
] = str
fields[
vol.Required(
CONF_PASSWORD, default=self.config_entry.data.get(CONF_PASSWORD)
)
] = TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWORD))
elif self.source_type == "tcp":
fields[
vol.Required(
CONF_SERIAL, default=self.config_entry.data.get(CONF_SERIAL)
)
] = int
fields[
vol.Optional(
CONF_SCAN_INTERVAL,
default=self.config_entry.options.get(
CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL
),
)
] = vol.All(vol.Coerce(int), vol.Range(min=1))
fields[vol.Optional(CONF_USE_CACHE, default=False)] = bool
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(fields),
errors=errors,
)