Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 131 additions & 10 deletions custom_components/solaredge_modbus_multi/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

from __future__ import annotations

import asyncio
import re
from typing import Any

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry, OptionsFlow
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult, OptionsFlow
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SCAN_INTERVAL
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.exceptions import HomeAssistantError

from .const import (
DEFAULT_NAME,
DOMAIN,
SETUP_MANUAL,
SETUP_SCAN_FAST,
SETUP_SCAN_FULL,
SETUP_TYPE,
ConfDefaultFlag,
ConfDefaultInt,
ConfDefaultStr,
ConfName,
)
from .helpers import device_list_from_string, host_valid
from .scanner import SolarEdgeDeviceScanner


def generate_config_schema(step_id: str, user_input: dict[str, Any]) -> vol.Schema:
Expand Down Expand Up @@ -59,8 +65,111 @@ def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:

async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Handle the initial config flow step."""
) -> ConfigFlowResult:
"""Handle the initial step."""
data_schema = vol.Schema(
{
vol.Required(SETUP_TYPE, default=SETUP_SCAN_FAST): vol.In(
(
SETUP_SCAN_FAST,
SETUP_SCAN_FULL,
SETUP_MANUAL,
)
)
}
)

if user_input is None:
return self.async_show_form(
step_id="user",
data_schema=data_schema,
)

if user_input[SETUP_TYPE] == SETUP_SCAN_FAST:
self.init_info = user_input
return await self.async_step_scan_ask_host()
if user_input[SETUP_TYPE] == SETUP_SCAN_FULL:
self.init_info = user_input
return await self.async_step_scan_ask_host()
if user_input[SETUP_TYPE] == SETUP_MANUAL:
return await self.async_step_manual()

raise AbortFlow(f"Unknown setup type: {user_input[SETUP_TYPE]}")

async def async_step_scan_ask_host(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
errors = {}
scan_list = []

if user_input is not None:
user_input[CONF_HOST] = user_input[CONF_HOST].lower()

if not host_valid(user_input[CONF_HOST]):
errors[CONF_HOST] = "invalid_host"
elif not 1 <= user_input[CONF_PORT] <= 65535:
errors[CONF_PORT] = "invalid_tcp_port"
else:
new_unique_id = f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
await self.async_set_unique_id(new_unique_id)

self._abort_if_unique_id_configured()

scanner = SolarEdgeDeviceScanner(
user_input[CONF_HOST], user_input[CONF_PORT]
)

await scanner.connect()

try:
if self.init_info[SETUP_TYPE] == SETUP_SCAN_FAST:
scan_list = await scanner.scan_list(list(range(1, 33)))
if self.init_info[SETUP_TYPE] == SETUP_SCAN_FULL:
scan_list = await scanner.scan_list(list(range(1, 248)))
except HomeAssistantError as e:
raise AbortFlow(f"Scan failed: {e}")

await scanner.disconnect()
await asyncio.sleep(1.0)

if not scan_list:
raise AbortFlow(
f"No SolarEdge devices were detected at {user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
)

user_input[ConfName.DEVICE_LIST] = scan_list

return self.async_create_entry(
title=user_input[CONF_NAME],
data=user_input,
)

else:
user_input = {
CONF_NAME: DEFAULT_NAME,
CONF_HOST: "",
CONF_PORT: ConfDefaultInt.PORT,
ConfName.DEVICE_LIST: ConfDefaultStr.DEVICE_LIST,
}

return self.async_show_form(
step_id="scan_ask_host",
data_schema=vol.Schema(
{
vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string,
vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string,
vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce(
int
),
},
),
errors=errors,
)

async def async_step_manual(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the manual config flow step."""
errors = {}

if user_input is not None:
Expand Down Expand Up @@ -105,14 +214,26 @@ async def async_step_user(
}

return self.async_show_form(
step_id="user",
data_schema=generate_config_schema("user", user_input),
step_id="manual",
data_schema=vol.Schema(
{
vol.Optional(CONF_NAME, default=user_input[CONF_NAME]): cv.string,
vol.Required(CONF_HOST, default=user_input[CONF_HOST]): cv.string,
vol.Required(CONF_PORT, default=user_input[CONF_PORT]): vol.Coerce(
int
),
vol.Required(
f"{ConfName.DEVICE_LIST}",
default=user_input[ConfName.DEVICE_LIST],
): cv.string,
},
),
errors=errors,
)

async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Handle the reconfigure flow step."""
errors = {}
config_entry = self.hass.config_entries.async_get_entry(
Expand Down Expand Up @@ -185,7 +306,7 @@ class SolaredgeModbusMultiOptionsFlowHandler(OptionsFlow):

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Handle the initial options flow step."""
errors = {}

Expand Down Expand Up @@ -274,7 +395,7 @@ async def async_step_init(

async def async_step_battery_options(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Battery Options"""
errors = {}

Expand Down Expand Up @@ -331,7 +452,7 @@ async def async_step_battery_options(

async def async_step_adv_pwr_ctl(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
) -> ConfigFlowResult:
"""Power Control Options"""
errors = {}

Expand Down
7 changes: 7 additions & 0 deletions custom_components/solaredge_modbus_multi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
DOMAIN = "solaredge_modbus_multi"
DEFAULT_NAME = "SolarEdge"

SETUP_TYPE = "setup_type"
SETUP_SCAN_FAST = "Scan for Inverters (Fast: IDs 1-32)" # Scan IDs 1-32
SETUP_SCAN_FULL = "Scan for Inverters (Full: IDs 1-247)" # Scan IDs 1-247
SETUP_MANUAL = "Manual Setup"

SCAN_RETRIES = 3

# raise a startup exception if pymodbus version is less than this
PYMODBUS_REQUIRED_VERSION = "3.8.3"

Expand Down
Loading