diff --git a/goodwe/__init__.py b/goodwe/__init__.py index b954209..12b505d 100644 --- a/goodwe/__init__.py +++ b/goodwe/__init__.py @@ -2,7 +2,6 @@ import asyncio import logging -from typing import Type from .const import GOODWE_TCP_PORT, GOODWE_UDP_PORT from .dt import DT @@ -74,22 +73,27 @@ async def discover(host: str, port: int = GOODWE_UDP_PORT, timeout: int = 1, ret model_name = response[5:15].decode("ascii").rstrip() serial_number = response[31:47].decode("ascii") - inverter_class: Type[Inverter] | None = None + i: Inverter | None = None for model_tag in ET_MODEL_TAGS: if model_tag in serial_number: logger.debug("Detected ET/EH/BT/BH/GEH inverter %s, S/N:%s.", model_name, serial_number) - inverter_class = ET - for model_tag in ES_MODEL_TAGS: - if model_tag in serial_number: - logger.debug("Detected ES/EM/BP inverter %s, S/N:%s.", model_name, serial_number) - inverter_class = ES - for model_tag in DT_MODEL_TAGS: - if model_tag in serial_number: - logger.debug("Detected DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.", model_name, serial_number) - inverter_class = DT - if inverter_class: - i = inverter_class(host, port, 0, timeout, retries) + i = ET(host, port, 0, timeout, retries) + break + if not i: + for model_tag in ES_MODEL_TAGS: + if model_tag in serial_number: + logger.debug("Detected ES/EM/BP inverter %s, S/N:%s.", model_name, serial_number) + i = ES(host, port, 0, timeout, retries) + break + if not i: + for model_tag in DT_MODEL_TAGS: + if model_tag in serial_number: + logger.debug("Detected DT/MS/D-NS/XS/GEP inverter %s, S/N:%s.", model_name, serial_number) + i = DT(host, port, 0, timeout, retries) + break + if i: await i.read_device_info() + logger.debug("Connected to inverter %s, S/N:%s.", i.model_name, i.serial_number) return i except InverterError as ex: diff --git a/goodwe/inverter.py b/goodwe/inverter.py index 5f5e8d2..acdaa1a 100644 --- a/goodwe/inverter.py +++ b/goodwe/inverter.py @@ -146,10 +146,10 @@ async def _read_from_socket(self, command: ProtocolCommand) -> ProtocolResponse: except MaxRetriesException: self._consecutive_failures_count += 1 raise RequestFailedException(f'No valid response received even after {self._protocol.retries} retries', - self._consecutive_failures_count) + self._consecutive_failures_count) from None except RequestFailedException as ex: self._consecutive_failures_count += 1 - raise RequestFailedException(ex.message, self._consecutive_failures_count) + raise RequestFailedException(ex.message, self._consecutive_failures_count) from None @abstractmethod async def read_device_info(self): diff --git a/goodwe/protocol.py b/goodwe/protocol.py index 3fef981..97e252e 100644 --- a/goodwe/protocol.py +++ b/goodwe/protocol.py @@ -19,6 +19,7 @@ class InverterProtocol: def __init__(self, host: str, port: int, timeout: int, retries: int): self._host: str = host self._port: int = port + self._timer: asyncio.TimerHandle | None = None self.timeout: int = timeout self.retries: int = retries self.protocol: asyncio.Protocol | None = None @@ -78,6 +79,8 @@ def connection_lost(self, exc: Optional[Exception]) -> None: def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None: """On datagram received""" + if self._timer: + self._timer.cancel() try: if self.command.validator(data): logger.debug("Received: %s", data.hex()) @@ -89,7 +92,8 @@ def datagram_received(self, data: bytes, addr: Tuple[str, int]) -> None: except RequestRejectedException as ex: logger.debug("Received exception response: %s", data.hex()) self.response_future.set_exception(ex) - self._close_transport() + finally: + self._close_transport() def error_received(self, exc: Exception) -> None: """On error received""" @@ -113,7 +117,7 @@ def _send_request(self, command: ProtocolCommand, response_future: Future) -> No logger.debug("Sending: %s%s", self.command, f' - retry #{self._retry}/{self.retries}' if self._retry > 0 else '') self._transport.sendto(self.command.request) - asyncio.get_running_loop().call_later(self.timeout, self._retry_mechanism) + self._timer = asyncio.get_running_loop().call_later(self.timeout, self._retry_mechanism) def _retry_mechanism(self) -> None: """Retry mechanism to prevent hanging transport""" @@ -130,7 +134,10 @@ def _retry_mechanism(self) -> None: def _close_transport(self) -> None: if self._transport: - self._transport.close() + try: + self._transport.close() + except RuntimeError: + logger.debug("Failed to close transport.") self._transport = None # Cancel Future on connection close if self.response_future and not self.response_future.done(): @@ -180,6 +187,8 @@ def connection_lost(self, exc: Optional[Exception]) -> None: def data_received(self, data: bytes) -> None: """On data received""" + if self._timer: + self._timer.cancel() try: if self.command.validator(data): logger.debug("Received: %s", data.hex()) @@ -231,7 +240,7 @@ def _send_request(self, command: ProtocolCommand, response_future: Future) -> No logger.debug("Sending: %s%s", self.command, f' - retry #{self._retry}/{self.retries}' if self._retry > 0 else '') self._transport.write(self.command.request) - asyncio.get_running_loop().call_later(self.timeout, self._timeout_mechanism) + self._timer = asyncio.get_running_loop().call_later(self.timeout, self._timeout_mechanism) def _timeout_mechanism(self) -> None: """Retry mechanism to prevent hanging transport"""