2023-12-04 18:50:05 +00:00
|
|
|
"""Module for the IOT legacy IOT KASA protocol."""
|
|
|
|
import asyncio
|
|
|
|
import logging
|
2023-12-19 14:11:59 +00:00
|
|
|
from typing import Dict, Union
|
2023-12-04 18:50:05 +00:00
|
|
|
|
2024-01-18 09:57:33 +00:00
|
|
|
from .exceptions import (
|
|
|
|
AuthenticationException,
|
|
|
|
ConnectionException,
|
|
|
|
RetryableException,
|
|
|
|
SmartDeviceException,
|
|
|
|
TimeoutException,
|
|
|
|
)
|
2023-12-04 18:50:05 +00:00
|
|
|
from .json import dumps as json_dumps
|
2024-01-22 15:28:30 +00:00
|
|
|
from .protocol import BaseProtocol, BaseTransport
|
2023-12-04 18:50:05 +00:00
|
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2024-01-22 15:28:30 +00:00
|
|
|
class IotProtocol(BaseProtocol):
|
2023-12-04 18:50:05 +00:00
|
|
|
"""Class for the legacy TPLink IOT KASA Protocol."""
|
|
|
|
|
2024-01-18 09:57:33 +00:00
|
|
|
BACKOFF_SECONDS_AFTER_TIMEOUT = 1
|
|
|
|
|
2023-12-04 18:50:05 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
*,
|
2023-12-19 14:11:59 +00:00
|
|
|
transport: BaseTransport,
|
2023-12-04 18:50:05 +00:00
|
|
|
) -> None:
|
2023-12-19 14:11:59 +00:00
|
|
|
"""Create a protocol object."""
|
2023-12-29 19:17:15 +00:00
|
|
|
super().__init__(transport=transport)
|
2023-12-04 18:50:05 +00:00
|
|
|
|
|
|
|
self._query_lock = asyncio.Lock()
|
|
|
|
|
|
|
|
async def query(self, request: Union[str, Dict], retry_count: int = 3) -> Dict:
|
|
|
|
"""Query the device retrying for retry_count on failure."""
|
|
|
|
if isinstance(request, dict):
|
|
|
|
request = json_dumps(request)
|
|
|
|
assert isinstance(request, str) # noqa: S101
|
|
|
|
|
|
|
|
async with self._query_lock:
|
|
|
|
return await self._query(request, retry_count)
|
|
|
|
|
|
|
|
async def _query(self, request: str, retry_count: int = 3) -> Dict:
|
|
|
|
for retry in range(retry_count + 1):
|
|
|
|
try:
|
|
|
|
return await self._execute_query(request, retry)
|
2024-01-18 09:57:33 +00:00
|
|
|
except ConnectionException as sdex:
|
2023-12-04 18:50:05 +00:00
|
|
|
if retry >= retry_count:
|
2023-12-19 14:11:59 +00:00
|
|
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
2024-01-18 09:57:33 +00:00
|
|
|
raise sdex
|
2023-12-04 18:50:05 +00:00
|
|
|
continue
|
|
|
|
except AuthenticationException as auex:
|
2024-01-23 22:15:18 +00:00
|
|
|
await self._transport.reset()
|
2023-12-19 14:11:59 +00:00
|
|
|
_LOGGER.debug(
|
|
|
|
"Unable to authenticate with %s, not retrying", self._host
|
|
|
|
)
|
2023-12-04 18:50:05 +00:00
|
|
|
raise auex
|
2024-01-18 09:57:33 +00:00
|
|
|
except RetryableException as ex:
|
2024-01-23 22:15:18 +00:00
|
|
|
await self._transport.reset()
|
2024-01-18 09:57:33 +00:00
|
|
|
if retry >= retry_count:
|
|
|
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
|
|
|
raise ex
|
|
|
|
continue
|
|
|
|
except TimeoutException as ex:
|
2024-01-23 22:15:18 +00:00
|
|
|
await self._transport.reset()
|
2024-01-18 09:57:33 +00:00
|
|
|
if retry >= retry_count:
|
|
|
|
_LOGGER.debug("Giving up on %s after %s retries", self._host, retry)
|
|
|
|
raise ex
|
|
|
|
await asyncio.sleep(self.BACKOFF_SECONDS_AFTER_TIMEOUT)
|
|
|
|
continue
|
2023-12-20 17:08:04 +00:00
|
|
|
except SmartDeviceException as ex:
|
2024-01-23 22:15:18 +00:00
|
|
|
await self._transport.reset()
|
2023-12-20 17:08:04 +00:00
|
|
|
_LOGGER.debug(
|
2024-01-18 09:57:33 +00:00
|
|
|
"Unable to query the device: %s, not retrying: %s",
|
2023-12-20 17:08:04 +00:00
|
|
|
self._host,
|
|
|
|
ex,
|
|
|
|
)
|
|
|
|
raise ex
|
2023-12-04 18:50:05 +00:00
|
|
|
|
|
|
|
# make mypy happy, this should never be reached..
|
|
|
|
raise SmartDeviceException("Query reached somehow to unreachable")
|
|
|
|
|
|
|
|
async def _execute_query(self, request: str, retry_count: int) -> Dict:
|
|
|
|
return await self._transport.send(request)
|
|
|
|
|
|
|
|
async def close(self) -> None:
|
2024-01-23 22:15:18 +00:00
|
|
|
"""Close the underlying transport."""
|
2023-12-04 18:50:05 +00:00
|
|
|
await self._transport.close()
|