Skip to content
Open
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
44 changes: 44 additions & 0 deletions custom_components/iec/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ def __init__(
session=aiohttp_client.async_get_clientsession(hass, family=socket.AF_INET),
)
self._first_load: bool = True
self._api_rate_limiter = asyncio.Semaphore(3)

@callback
def _dummy_listener() -> None:
Expand All @@ -133,6 +134,49 @@ async def async_unload(self):
"""Unload the coordinator, cancel any pending tasks."""
_LOGGER.info("Coordinator unloaded successfully.")

async def _execute_with_rate_limit(
self,
coro,
max_retries: int = 3,
base_delay: float = 1.0,
):
"""Execute an API call with rate limiting and retry logic.

Args:
coro: The coroutine to execute
max_retries: Maximum number of retries
base_delay: Base delay in seconds for exponential backoff

Returns:
The result of the coroutine

Raises:
The last exception if all retries fail

"""
last_exception = None
for attempt in range(max_retries):
async with self._api_rate_limiter:
try:
return await coro
except IECError as e:
last_exception = e
if attempt < max_retries - 1:
delay = base_delay * (2**attempt)
_LOGGER.warning(
"API call failed (attempt %d/%d), retrying in %.1fs: %s",
attempt + 1,
max_retries,
delay,
e,
)
await asyncio.sleep(delay)
else:
_LOGGER.error(
"API call failed after %d attempts: %s", max_retries, e
)
raise last_exception

async def _get_devices_by_contract_id(self, contract_id) -> list[Device]:
devices = self._devices_by_contract_id.get(contract_id)
if not devices:
Expand Down