Skip to content

Commit

Permalink
Merge branch 'main' into service-refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
WillCodeForCats committed Jul 29, 2023
2 parents 3998aac + f70ca8d commit 4e33e04
Show file tree
Hide file tree
Showing 17 changed files with 384 additions and 282 deletions.
15 changes: 7 additions & 8 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,18 @@ If applicable, add screenshots of the problem and/or configuration.
**Logs**
Copy any logs in Home Assistant from "custom_components.solaredge_modbus".

**Diagnostic File**
Attach your diagnostic file that shows the issue. The diagnostic file is a snapshot: make sure it's taken at the exact time the issue is happening.

**Debug Logs**
Debug logs are not always necessary, but may be required for more complex issues. You can enable debug logs by adding the following to your `configuration.yaml` file and restarting Home Assistant.
```
logger:
default: warning
logs:
custom_components.solaredge_modbus_multi: debug
```
Debug logging will generate a large amount of data: it is recommended to configure it, collect the data needed, then remove debug logging during normal operation.
Debug logs are not always necessary, but may be required for more complex issues. You can enable debug logs in Home Assistant. Debug logging will generate a large amount of data: it is recommended to configure it, collect the data needed, then remove debug logging during normal operation.

**Home Assistant (please complete the following information):**
- Home Assistant Core Version:
- solaredge-modbus-multi Version:
- Installation Type: (HAOS, Container, Supervised, Core)

Note: Your issue must be duplicated in an HAOS VM. If your issue is related to running Core you will need to do your own debugging.

**Additional context**
Add any other context about the problem here.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
[![hacs_badge](https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=for-the-badge)](https://github.com/hacs/integration)

# SolarEdge Modbus Multi Device
# SolarEdge Modbus Multi

Home Assistant integration `solaredge-modbus-multi` was designed for SolarEdge inverters using Modbus/TCP. It supports single inverters, multiple inverters, meters, batteries, and many other improvements over other integrations that didn't work well with a multi-device setup.
Home Assistant integration `solaredge-modbus-multi` supports SolarEdge inverters with Modbus/TCP local polling. It works with single inverters, multiple inverters, meters, batteries, and many other improvements over other integrations that didn't work well with a multi-device setup.

It is designed to communicate locally using Modbus/TCP where you have a single Leader inverter connected with one or more Follower inverters chained using the RS485 bus. Inverters support up to three meters and two batteries over modbus.
It is designed to communicate locally using Modbus/TCP where you have a single Leader inverter connected with one or more Follower inverters chained using the RS485 bus. Each inverter can connect to three meters and two batteries.

Simple single inverter setups are fully supported - multiple devices is a feature, not a requirement.

### Features
* Inverter support for 1 to 32 SolarEdge inverters.
* Meter support for 1 to 3 meters per inverter.
* Battery support for 1 or 2 batteries per inverter.
* Supports site limit and storage controls.
* Automatically detects meters and batteries.
* Supports Three Phase Inverters with Synergy Technology.
* Polling frequency configuration option (1 to 86400 seconds).
* Configurable starting inverter device ID.
* Connects using Modbus/TCP - no cloud dependencies.
* Connects locally using Modbus/TCP - no cloud dependencies.
* Informational sensor for device and its attributes
* Supports status and error reporting sensors.
* User friendly configuration through Config Flow.
Expand All @@ -41,9 +42,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 2023.4.0 or newer
* Home Assistant 2023.7.0 or newer
* Python 3.10 or newer
* pymodbus 3.1.1 or newer
* pymodbus 3.3.1 or newer

## Specifications
[WillCodeForCats/solaredge-modbus-multi/tree/main/doc](https://github.com/WillCodeForCats/solaredge-modbus-multi/tree/main/doc)
Expand Down
22 changes: 9 additions & 13 deletions custom_components/solaredge_modbus_multi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
entry.options.get(
ConfName.KEEP_MODBUS_OPEN, bool(ConfDefaultFlag.KEEP_MODBUS_OPEN)
),
entry.options.get(
ConfName.ADV_PWR_CONTROL, bool(ConfDefaultFlag.ADV_PWR_CONTROL)
),
entry.options.get(
ConfName.ADV_STORAGE_CONTROL, bool(ConfDefaultFlag.ADV_STORAGE_CONTROL)
),
Expand All @@ -90,7 +87,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
ConfName.ALLOW_BATTERY_ENERGY_RESET,
bool(ConfDefaultFlag.ALLOW_BATTERY_ENERGY_RESET),
),
entry.options.get(ConfName.SLEEP_AFTER_WRITE, ConfDefaultInt.SLEEP_AFTER_WRITE),
entry.options.get(
ConfName.BATTERY_RATING_ADJUST, ConfDefaultInt.BATTERY_RATING_ADJUST
),
Expand Down Expand Up @@ -203,13 +199,12 @@ def __init__(

async def _async_update_data(self):
try:
async with async_timeout.timeout(self._hub.coordinator_timeout):
return await self._refresh_modbus_data_with_retry(
ex_type=DataUpdateFailed,
limit=RetrySettings.Limit,
wait_ms=RetrySettings.Time,
wait_ratio=RetrySettings.Ratio,
)
return await self._refresh_modbus_data_with_retry(
ex_type=DataUpdateFailed,
limit=RetrySettings.Limit,
wait_ms=RetrySettings.Time,
wait_ratio=RetrySettings.Ratio,
)

except HubInitFailed as e:
raise UpdateFailed(f"{e}")
Expand Down Expand Up @@ -238,15 +233,16 @@ async def _refresh_modbus_data_with_retry(
attempt = 1
while True:
try:
return await self._hub.async_refresh_modbus_data()
async with async_timeout.timeout(self._hub.coordinator_timeout):
return await self._hub.async_refresh_modbus_data()
except Exception as ex:
if not isinstance(ex, ex_type):
raise ex
if 0 < limit <= attempt:
_LOGGER.debug(f"No more data refresh attempts (maximum {limit})")
raise ex

_LOGGER.debug(f"Failed data refresh attempt #{attempt}", exc_info=ex)
_LOGGER.debug(f"Failed data refresh attempt #{attempt}")

attempt += 1
_LOGGER.debug(
Expand Down
2 changes: 1 addition & 1 deletion custom_components/solaredge_modbus_multi/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def config_entry_name(self):

@property
def available(self) -> bool:
return self._platform.online
return super().available and self._platform.online

@callback
def _handle_coordinator_update(self) -> None:
Expand Down
25 changes: 7 additions & 18 deletions custom_components/solaredge_modbus_multi/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, OptionsFlow
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult
Expand All @@ -29,7 +29,8 @@ class SolaredgeModbusMultiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry):
def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow:
"""Create the options flow for SolarEdge Modbus Multi."""
return SolaredgeModbusMultiOptionsFlowHandler(config_entry)

async def async_step_user(self, user_input=None) -> FlowResult:
Expand Down Expand Up @@ -97,7 +98,7 @@ async def async_step_user(self, user_input=None) -> FlowResult:
)


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

def __init__(self, config_entry: ConfigEntry):
Expand Down Expand Up @@ -233,14 +234,9 @@ async def async_step_adv_pwr_ctl(self, user_input=None) -> FlowResult:
errors = {}

if user_input is not None:
if user_input[ConfName.SLEEP_AFTER_WRITE] < 0:
errors[ConfName.SLEEP_AFTER_WRITE] = "invalid_sleep_interval"
elif user_input[ConfName.SLEEP_AFTER_WRITE] > 60:
errors[ConfName.SLEEP_AFTER_WRITE] = "invalid_sleep_interval"
else:
return self.async_create_entry(
title="", data={**self.init_info, **user_input}
)
return self.async_create_entry(
title="", data={**self.init_info, **user_input}
)

else:
user_input = {
Expand All @@ -252,9 +248,6 @@ async def async_step_adv_pwr_ctl(self, user_input=None) -> FlowResult:
ConfName.ADV_SITE_LIMIT_CONTROL,
bool(ConfDefaultFlag.ADV_SITE_LIMIT_CONTROL),
),
ConfName.SLEEP_AFTER_WRITE: self.config_entry.options.get(
ConfName.SLEEP_AFTER_WRITE, ConfDefaultInt.SLEEP_AFTER_WRITE
),
}

return self.async_show_form(
Expand All @@ -269,10 +262,6 @@ async def async_step_adv_pwr_ctl(self, user_input=None) -> FlowResult:
f"{ConfName.ADV_SITE_LIMIT_CONTROL}",
default=user_input[ConfName.ADV_SITE_LIMIT_CONTROL],
): cv.boolean,
vol.Optional(
f"{ConfName.SLEEP_AFTER_WRITE}",
default=user_input[ConfName.SLEEP_AFTER_WRITE],
): vol.Coerce(int),
}
),
errors=errors,
Expand Down
18 changes: 14 additions & 4 deletions custom_components/solaredge_modbus_multi/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,27 @@ class StrEnum(str, Enum):
)


class SolarEdgeTimeouts(IntEnum):
"""Timeouts in milliseconds."""

Inverter = 8400
Device = 1200
Init = 1200


class RetrySettings(IntEnum):
"""Retry settings when opening a connection to the inverter fails."""

Time = 800 # first attempt in milliseconds
Ratio = 2 # time multiplier between each attempt
Ratio = 3 # time multiplier between each attempt
Limit = 4 # number of attempts before failing


class BatteryLimit(IntEnum):
"""Configure battery limits for input and display validation."""

Vmin = 0 # volts
Vmax = 600 # volts
Vmax = 1000 # volts
Amin = -200 # amps
Amax = 200 # amps
Tmax = 100 # degrees C
Expand All @@ -62,16 +70,19 @@ class BatteryLimit(IntEnum):


class ConfDefaultInt(IntEnum):
"""Defaults for options that are integers."""

SCAN_INTERVAL = 300
PORT = 1502
NUMBER_INVERTERS = 1
DEVICE_ID = 1
SLEEP_AFTER_WRITE = 3
BATTERY_RATING_ADJUST = 0
BATTERY_ENERGY_RESET_CYCLES = 0


class ConfDefaultFlag(IntEnum):
"""Defaults for options that are booleans."""

DETECT_METERS = 1
DETECT_BATTERIES = 0
KEEP_MODBUS_OPEN = 0
Expand All @@ -91,7 +102,6 @@ class ConfName(StrEnum):
ADV_STORAGE_CONTROL = "adv_storage_control"
ADV_SITE_LIMIT_CONTROL = "adv_site_limit_control"
ALLOW_BATTERY_ENERGY_RESET = "allow_battery_energy_reset"
SLEEP_AFTER_WRITE = "sleep_after_write"
BATTERY_RATING_ADJUST = "battery_rating_adjust"
BATTERY_ENERGY_RESET_CYCLES = "battery_energy_reset_cycles"

Expand Down
Loading

0 comments on commit 4e33e04

Please sign in to comment.