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
286 changes: 139 additions & 147 deletions custom_components/solaredge_modbus_multi/hub.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ class DeviceInvalid(SolarEdgeException):


class SolarEdgeModbusMultiHub:

def __init__(
self,
hass: HomeAssistant,
Expand Down Expand Up @@ -177,7 +176,6 @@ def __init__(
"retries", ModbusDefaults.Retries
)
self._id = entry_data[CONF_NAME].lower()
self._lock = asyncio.Lock()
self.inverters = []
self.meters = []
self.batteries = []
Expand Down Expand Up @@ -380,37 +378,16 @@ async def _async_init_solaredge(self) -> None:
async def async_refresh_modbus_data(self) -> bool:
"""Refresh modbus data from inverters."""

async with self._lock:
if not self.is_connected:
await self.connect()

if not self.initalized:
try:
async with asyncio.timeout(self.coordinator_timeout):
await self._async_init_solaredge()

except (ConnectionException, ModbusIOException, TimeoutError) as e:
self.disconnect()
ir.async_create_issue(
self._hass,
DOMAIN,
"check_configuration",
is_fixable=True,
severity=ir.IssueSeverity.ERROR,
translation_key="check_configuration",
data={"entry_id": self._entry_id},
)
raise HubInitFailed(f"Setup failed: {e}")

ir.async_delete_issue(self._hass, DOMAIN, "check_configuration")

if not self.keep_modbus_open:
self.disconnect()
if not self.is_connected:
await self.connect()

return True
if not self.initalized:
try:
async with asyncio.timeout(self.coordinator_timeout):
await self._async_init_solaredge()

if not self.is_connected:
self.online = False
except (ConnectionException, ModbusIOException, TimeoutError) as e:
self.disconnect()
ir.async_create_issue(
self._hass,
DOMAIN,
Expand All @@ -420,65 +397,84 @@ async def async_refresh_modbus_data(self) -> bool:
translation_key="check_configuration",
data={"entry_id": self._entry_id},
)
raise DataUpdateFailed(
f"Modbus/TCP connect to {self.hub_host}:{self.hub_port} failed."
)

if not self.online:
ir.async_delete_issue(self._hass, DOMAIN, "check_configuration")
raise HubInitFailed(f"Setup failed: {e}")

self.online = True
ir.async_delete_issue(self._hass, DOMAIN, "check_configuration")

try:
async with asyncio.timeout(self.coordinator_timeout):
for inverter in self.inverters:
await inverter.read_modbus_data()
for meter in self.meters:
await meter.read_modbus_data()
for battery in self.batteries:
await battery.read_modbus_data()

except ModbusReadError as e:
if not self.keep_modbus_open:
self.disconnect()
raise DataUpdateFailed(f"Update failed: {e}")

except DeviceInvalid as e:
self.disconnect()
raise DataUpdateFailed(f"Invalid device: {e}")
return True

except ConnectionException as e:
self.disconnect()
raise DataUpdateFailed(f"Connection failed: {e}")
if not self.is_connected:
self.online = False
ir.async_create_issue(
self._hass,
DOMAIN,
"check_configuration",
is_fixable=True,
severity=ir.IssueSeverity.ERROR,
translation_key="check_configuration",
data={"entry_id": self._entry_id},
)
raise DataUpdateFailed(
f"Modbus/TCP connect to {self.hub_host}:{self.hub_port} failed."
)

except ModbusIOException as e:
self.disconnect()
raise DataUpdateFailed(f"Modbus error: {e}")
if not self.online:
ir.async_delete_issue(self._hass, DOMAIN, "check_configuration")

except TimeoutError as e:
self.disconnect(clear_client=True)
self._timeout_counter += 1
self.online = True

_LOGGER.debug(
f"Refresh timeout {self._timeout_counter} "
f"limit {self._retry_limit}"
)
try:
async with asyncio.timeout(self.coordinator_timeout):
for inverter in self.inverters:
await inverter.read_modbus_data()
for meter in self.meters:
await meter.read_modbus_data()
for battery in self.batteries:
await battery.read_modbus_data()

if self._timeout_counter >= self._retry_limit:
self._timeout_counter = 0
raise TimeoutError
except ModbusReadError as e:
self.disconnect()
raise DataUpdateFailed(f"Update failed: {e}")

raise DataUpdateFailed(f"Timeout error: {e}")
except DeviceInvalid as e:
self.disconnect()
raise DataUpdateFailed(f"Invalid device: {e}")

if self._timeout_counter > 0:
_LOGGER.debug(
f"Timeout count {self._timeout_counter} limit {self._retry_limit}"
)
except ConnectionException as e:
self.disconnect()
raise DataUpdateFailed(f"Connection failed: {e}")

except ModbusIOException as e:
self.disconnect()
raise DataUpdateFailed(f"Modbus error: {e}")

except TimeoutError as e:
self.disconnect(clear_client=True)
self._timeout_counter += 1

_LOGGER.debug(
f"Refresh timeout {self._timeout_counter} " f"limit {self._retry_limit}"
)

if self._timeout_counter >= self._retry_limit:
self._timeout_counter = 0
raise TimeoutError

if not self.keep_modbus_open:
self.disconnect()
raise DataUpdateFailed(f"Timeout error: {e}")

return True
if self._timeout_counter > 0:
_LOGGER.debug(
f"Timeout count {self._timeout_counter} limit {self._retry_limit}"
)
self._timeout_counter = 0

if not self.keep_modbus_open:
self.disconnect()

return True

async def connect(self) -> None:
"""Connect to inverter."""
Expand Down Expand Up @@ -521,9 +517,8 @@ def disconnect(self, clear_client: bool = False) -> None:
async def shutdown(self) -> None:
"""Shut down the hub and disconnect."""

async with self._lock:
self.online = False
self.disconnect(clear_client=True)
self.online = False
self.disconnect(clear_client=True)

async def modbus_read_holding_registers(self, unit, address, rcount):
"""Read modbus registers from inverter."""
Expand Down Expand Up @@ -588,93 +583,90 @@ async def modbus_read_holding_registers(self, unit, address, rcount):
async def write_registers(self, unit: int, address: int, payload) -> None:
"""Write modbus registers to inverter."""

async with self._lock:
self._wr_unit = unit
self._wr_address = address
self._wr_payload = payload
self._wr_unit = unit
self._wr_address = address
self._wr_payload = payload

try:
if not self.is_connected:
await self.connect()
try:
if not self.is_connected:
await self.connect()

sig = inspect.signature(self._client.write_registers)
sig = inspect.signature(self._client.write_registers)

if "device_id" in sig.parameters:
result = await self._client.write_registers(
address=self._wr_address,
values=self._wr_payload,
device_id=self._wr_unit,
)
else:
result = await self._client.write_registers(
address=self._wr_address,
values=self._wr_payload,
slave=self._wr_unit,
)
if "device_id" in sig.parameters:
result = await self._client.write_registers(
address=self._wr_address,
values=self._wr_payload,
device_id=self._wr_unit,
)
else:
result = await self._client.write_registers(
address=self._wr_address,
values=self._wr_payload,
slave=self._wr_unit,
)

self.has_write = address
self.has_write = address

if self.sleep_after_write > 0:
_LOGGER.debug(
f"Sleep {self.sleep_after_write} seconds after write {address}."
)
await asyncio.sleep(self.sleep_after_write)
if self.sleep_after_write > 0:
_LOGGER.debug(
f"Sleep {self.sleep_after_write} seconds after write {address}."
)
await asyncio.sleep(self.sleep_after_write)

self.has_write = None
_LOGGER.debug(f"Finished with write {address}.")
self.has_write = None
_LOGGER.debug(f"Finished with write {address}.")

except ModbusIOException as e:
self.disconnect()
except ModbusIOException as e:
self.disconnect()

raise HomeAssistantError(
f"Error sending command to inverter ID {self._wr_unit}: {e}."
)
raise HomeAssistantError(
f"Error sending command to inverter ID {self._wr_unit}: {e}."
)

except ConnectionException as e:
self.disconnect()
except ConnectionException as e:
self.disconnect()

_LOGGER.error(f"Connection failed: {e}")
raise HomeAssistantError(
f"Connection to inverter ID {self._wr_unit} failed."
)

_LOGGER.error(f"Connection failed: {e}")
if result.isError():
if type(result) is ModbusIOException:
self.disconnect()
_LOGGER.error(
f"Write failed: No response from inverter ID {self._wr_unit}."
)
raise HomeAssistantError(
f"Connection to inverter ID {self._wr_unit} failed."
"No response from inverter ID {self._wr_unit}."
)

if result.isError():
if type(result) is ModbusIOException:
self.disconnect()
_LOGGER.error(
f"Write failed: No response from inverter ID {self._wr_unit}."
if type(result) is ExceptionResponse:
if result.exception_code == ModbusExceptions.IllegalAddress:
_LOGGER.debug(
f"Unit {self._wr_unit} Write IllegalAddress: {result}"
)
raise HomeAssistantError(
"No response from inverter ID {self._wr_unit}."
"Address not supported at device at ID {self._wr_unit}."
)

if type(result) is ExceptionResponse:
if result.exception_code == ModbusExceptions.IllegalAddress:
_LOGGER.debug(
f"Unit {self._wr_unit} Write IllegalAddress: {result}"
)
raise HomeAssistantError(
"Address not supported at device at ID {self._wr_unit}."
)

if result.exception_code == ModbusExceptions.IllegalFunction:
_LOGGER.debug(
f"Unit {self._wr_unit} Write IllegalFunction: {result}"
)
raise HomeAssistantError(
"Function not supported by device at ID {self._wr_unit}."
)
if result.exception_code == ModbusExceptions.IllegalFunction:
_LOGGER.debug(
f"Unit {self._wr_unit} Write IllegalFunction: {result}"
)
raise HomeAssistantError(
"Function not supported by device at ID {self._wr_unit}."
)

if result.exception_code == ModbusExceptions.IllegalValue:
_LOGGER.debug(
f"Unit {self._wr_unit} Write IllegalValue: {result}"
)
raise HomeAssistantError(
"Value invalid for device at ID {self._wr_unit}."
)
if result.exception_code == ModbusExceptions.IllegalValue:
_LOGGER.debug(f"Unit {self._wr_unit} Write IllegalValue: {result}")
raise HomeAssistantError(
"Value invalid for device at ID {self._wr_unit}."
)

self.disconnect()
raise ModbusWriteError(result)
self.disconnect()
raise ModbusWriteError(result)

@staticmethod
def _safe_version_tuple(version_str: str) -> tuple[int, ...]:
Expand Down
2 changes: 1 addition & 1 deletion custom_components/solaredge_modbus_multi/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"issue_tracker": "https://github.com/WillCodeForCats/solaredge-modbus-multi/issues",
"loggers": ["custom_components.solaredge_modbus_multi"],
"requirements": ["pymodbus>=3.8.3"],
"version": "3.1.7"
"version": "3.2.0-pre.1"
}