Skip to content

Commit

Permalink
Merge branch 'main' into update_unique_id
Browse files Browse the repository at this point in the history
  • Loading branch information
WillCodeForCats committed Jan 18, 2025
2 parents 8c0e1ff + c13e5f3 commit bdcdbde
Show file tree
Hide file tree
Showing 20 changed files with 465 additions and 336 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python-quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: WillCodeForCats/[email protected].7
- uses: WillCodeForCats/[email protected].9
with:
python-root-list: "custom_components/solaredge_modbus_multi"
use-flake8: true
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ After rebooting Home Assistant, this integration can be configured through the i
[WillCodeForCats/solaredge-modbus-multi/wiki](https://github.com/WillCodeForCats/solaredge-modbus-multi/wiki)

### Required Versions
* Home Assistant 2024.4.0 or newer
* Home Assistant 2024.12.0 or newer
* Python 3.11 or newer
* pymodbus 3.6.6 or newer
* pymodbus 3.6.6 through 3.7.4

## Specifications
[WillCodeForCats/solaredge-modbus-multi/tree/main/doc](https://github.com/WillCodeForCats/solaredge-modbus-multi/tree/main/doc)
Expand Down
64 changes: 51 additions & 13 deletions custom_components/solaredge_modbus_multi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import asyncio
import logging
from datetime import timedelta
from typing import Any

import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
Expand All @@ -15,7 +14,7 @@
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed

from .const import DOMAIN, ConfDefaultInt, RetrySettings
from .const import DOMAIN, ConfDefaultInt, ConfName, RetrySettings
from .hub import DataUpdateFailed, HubInitFailed, SolarEdgeModbusMultiHub

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -67,17 +66,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up SolarEdge Modbus Muti from a config entry."""

entry_updates: dict[str, Any] = {}
if CONF_SCAN_INTERVAL in entry.data:
data = {**entry.data}
entry_updates["data"] = data
entry_updates["options"] = {
**entry.options,
CONF_SCAN_INTERVAL: data.pop(CONF_SCAN_INTERVAL),
}
if entry_updates:
hass.config_entries.async_update_entry(entry, **entry_updates)

solaredge_hub = SolarEdgeModbusMultiHub(
hass, entry.entry_id, entry.data, entry.options
)
Expand Down Expand Up @@ -166,6 +154,56 @@ async def async_remove_config_entry_device(
return True


async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug(
"Migrating from config version "
f"{config_entry.version}.{config_entry.minor_version}"
)

if config_entry.version > 1:
return False

if config_entry.version == 1:

update_data = {**config_entry.data}
update_options = {**config_entry.options}

if CONF_SCAN_INTERVAL in update_data:
update_options = {
**update_options,
CONF_SCAN_INTERVAL: update_data.pop(CONF_SCAN_INTERVAL),
}

start_device_id = update_data.pop(ConfName.DEVICE_ID)
number_of_inverters = update_data.pop(ConfName.NUMBER_INVERTERS)

inverter_list = []
for inverter_index in range(number_of_inverters):
inverter_unit_id = inverter_index + start_device_id
inverter_list.append(inverter_unit_id)

update_data = {
**update_data,
ConfName.DEVICE_LIST: inverter_list,
}

hass.config_entries.async_update_entry(
config_entry,
data=update_data,
options=update_options,
version=2,
minor_version=0,
)

_LOGGER.warning(
"Migrated to config version "
f"{config_entry.version}.{config_entry.minor_version}"
)

return True


class SolarEdgeCoordinator(DataUpdateCoordinator):
def __init__(
self, hass: HomeAssistant, hub: SolarEdgeModbusMultiHub, scan_interval: int
Expand Down
197 changes: 99 additions & 98 deletions custom_components/solaredge_modbus_multi/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from __future__ import annotations

import re
from typing import Any

import homeassistant.helpers.config_validation as cv
Expand All @@ -11,16 +12,44 @@
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.exceptions import HomeAssistantError

from .const import DEFAULT_NAME, DOMAIN, ConfDefaultFlag, ConfDefaultInt, ConfName
from .helpers import host_valid
from .const import (
DEFAULT_NAME,
DOMAIN,
ConfDefaultFlag,
ConfDefaultInt,
ConfDefaultStr,
ConfName,
)
from .helpers import device_list_from_string, host_valid


def generate_config_schema(step_id: str, user_input: dict[str, Any]) -> vol.Schema:
"""Generate config flow or repair schema."""
schema: dict[vol.Marker, Any] = {}

if step_id == "user":
schema |= {vol.Required(CONF_NAME, default=user_input[CONF_NAME]): cv.string}

if step_id in ["reconfigure", "confirm", "user"]:
schema |= {
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,
}

return vol.Schema(schema)


class SolaredgeModbusMultiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for SolarEdge Modbus Multi."""

VERSION = 1
MINOR_VERSION = 1
VERSION = 2
MINOR_VERSION = 0

@staticmethod
@callback
Expand All @@ -36,60 +65,47 @@ async def async_step_user(

if user_input is not None:
user_input[CONF_HOST] = user_input[CONF_HOST].lower()
user_input[ConfName.DEVICE_LIST] = re.sub(
r"\s+", "", user_input[ConfName.DEVICE_LIST], flags=re.UNICODE
)

try:
inverter_count = len(
device_list_from_string(user_input[ConfName.DEVICE_LIST])
)
except HomeAssistantError as e:
errors[ConfName.DEVICE_LIST] = f"{e}"

if not host_valid(user_input[CONF_HOST]):
errors[CONF_HOST] = "invalid_host"
elif user_input[CONF_PORT] < 1:
errors[CONF_PORT] = "invalid_tcp_port"
elif user_input[CONF_PORT] > 65535:
errors[CONF_PORT] = "invalid_tcp_port"
elif user_input[ConfName.DEVICE_ID] > 247:
errors[ConfName.DEVICE_ID] = "max_device_id"
elif user_input[ConfName.DEVICE_ID] < 1:
errors[ConfName.DEVICE_ID] = "min_device_id"
elif user_input[ConfName.NUMBER_INVERTERS] > 32:
errors[ConfName.NUMBER_INVERTERS] = "max_inverters"
elif user_input[ConfName.NUMBER_INVERTERS] < 1:
errors[ConfName.NUMBER_INVERTERS] = "min_inverters"
elif (
user_input[ConfName.NUMBER_INVERTERS] + user_input[ConfName.DEVICE_ID]
> 247
):
errors[ConfName.NUMBER_INVERTERS] = "too_many_inverters"
else:
await self.async_set_unique_id(user_input[CONF_HOST])
self._abort_if_unique_id_configured()
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"
elif not 1 <= inverter_count <= 32:
errors[ConfName.DEVICE_LIST] = "invalid_inverter_count"
else:
await self.async_set_unique_id(user_input[CONF_HOST])

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

user_input[ConfName.DEVICE_LIST] = device_list_from_string(
user_input[ConfName.DEVICE_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.NUMBER_INVERTERS: ConfDefaultInt.NUMBER_INVERTERS,
ConfName.DEVICE_ID: ConfDefaultInt.DEVICE_ID,
ConfName.DEVICE_LIST: ConfDefaultStr.DEVICE_LIST,
}

return self.async_show_form(
step_id="user",
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.NUMBER_INVERTERS}",
default=user_input[ConfName.NUMBER_INVERTERS],
): vol.Coerce(int),
vol.Required(
f"{ConfName.DEVICE_ID}", default=user_input[ConfName.DEVICE_ID]
): vol.Coerce(int),
},
),
data_schema=generate_config_schema("user", user_input),
errors=errors,
)

Expand All @@ -101,78 +117,63 @@ async def async_step_reconfigure(
config_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
assert config_entry
unique_id = config_entry.unique_id

if user_input is not None:
user_input[CONF_HOST] = user_input[CONF_HOST].lower()
user_input[ConfName.DEVICE_LIST] = re.sub(
r"\s+", "", user_input[ConfName.DEVICE_LIST], flags=re.UNICODE
)

if not host_valid(user_input[CONF_HOST]):
errors[CONF_HOST] = "invalid_host"
elif user_input[CONF_PORT] < 1:
errors[CONF_PORT] = "invalid_tcp_port"
elif user_input[CONF_PORT] > 65535:
errors[CONF_PORT] = "invalid_tcp_port"
elif user_input[ConfName.DEVICE_ID] > 247:
errors[ConfName.DEVICE_ID] = "max_device_id"
elif user_input[ConfName.DEVICE_ID] < 1:
errors[ConfName.DEVICE_ID] = "min_device_id"
elif user_input[ConfName.NUMBER_INVERTERS] > 32:
errors[ConfName.NUMBER_INVERTERS] = "max_inverters"
elif user_input[ConfName.NUMBER_INVERTERS] < 1:
errors[ConfName.NUMBER_INVERTERS] = "min_inverters"
elif (
user_input[ConfName.NUMBER_INVERTERS] + user_input[ConfName.DEVICE_ID]
> 247
):
errors[ConfName.NUMBER_INVERTERS] = "too_many_inverters"
else:
return self.async_update_reload_and_abort(
config_entry,
unique_id=unique_id,
data={**config_entry.data, **user_input},
reason="reconfigure_successful",
try:
inverter_count = len(
device_list_from_string(user_input[ConfName.DEVICE_LIST])
)
except HomeAssistantError as e:
errors[ConfName.DEVICE_LIST] = f"{e}"

else:
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"
elif not 1 <= inverter_count <= 32:
errors[ConfName.DEVICE_LIST] = "invalid_inverter_count"
else:

user_input[ConfName.DEVICE_LIST] = device_list_from_string(
user_input[ConfName.DEVICE_LIST]
)

return self.async_update_reload_and_abort(
config_entry,
unique_id=config_entry.unique_id,
data={**config_entry.data, **user_input},
reason="reconfigure_successful",
)
else:
reconfig_device_list = ",".join(
str(device)
for device in config_entry.data.get(
ConfName.DEVICE_LIST, ConfDefaultStr.DEVICE_LIST
)
)

user_input = {
CONF_HOST: config_entry.data.get(CONF_HOST),
CONF_PORT: config_entry.data.get(CONF_PORT, ConfDefaultInt.PORT),
ConfName.NUMBER_INVERTERS: config_entry.data.get(
ConfName.NUMBER_INVERTERS, ConfDefaultInt.NUMBER_INVERTERS
),
ConfName.DEVICE_ID: config_entry.data.get(
ConfName.DEVICE_ID, ConfDefaultInt.DEVICE_ID
),
ConfName.DEVICE_LIST: reconfig_device_list,
}

return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
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.NUMBER_INVERTERS}",
default=user_input[ConfName.NUMBER_INVERTERS],
): vol.Coerce(int),
vol.Required(
f"{ConfName.DEVICE_ID}", default=user_input[ConfName.DEVICE_ID]
): vol.Coerce(int),
},
),
data_schema=generate_config_schema("reconfigure", user_input),
errors=errors,
)


class SolaredgeModbusMultiOptionsFlowHandler(OptionsFlow):
"""Handle an options flow for SolarEdge Modbus Multi."""

def __init__(self, config_entry: ConfigEntry):
"""Initialize options flow."""
self.config_entry = config_entry

async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
Expand Down
Loading

0 comments on commit bdcdbde

Please sign in to comment.