mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-12-20 03:30:12 +00:00
Initial TapoCamera support (#1165)
Adds experimental support for the Tapo Camera protocol also used by the H200 hub. Creates a new SslAesTransport and a derived SmartCamera and SmartCameraProtocol.
This commit is contained in:
109
kasa/experimental/smartcameraprotocol.py
Normal file
109
kasa/experimental/smartcameraprotocol.py
Normal file
@@ -0,0 +1,109 @@
|
||||
"""Module for SmartCamera Protocol."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pprint import pformat as pf
|
||||
from typing import Any
|
||||
|
||||
from ..exceptions import AuthenticationError, DeviceError, _RetryableError
|
||||
from ..json import dumps as json_dumps
|
||||
from ..smartprotocol import SmartProtocol
|
||||
from .sslaestransport import (
|
||||
SMART_AUTHENTICATION_ERRORS,
|
||||
SMART_RETRYABLE_ERRORS,
|
||||
SmartErrorCode,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SmartCameraProtocol(SmartProtocol):
|
||||
"""Class for SmartCamera Protocol."""
|
||||
|
||||
async def _handle_response_lists(
|
||||
self, response_result: dict[str, Any], method, retry_count
|
||||
):
|
||||
pass
|
||||
|
||||
def _handle_response_error_code(self, resp_dict: dict, method, raise_on_error=True):
|
||||
error_code_raw = resp_dict.get("error_code")
|
||||
try:
|
||||
error_code = SmartErrorCode.from_int(error_code_raw)
|
||||
except ValueError:
|
||||
_LOGGER.warning(
|
||||
"Device %s received unknown error code: %s", self._host, error_code_raw
|
||||
)
|
||||
error_code = SmartErrorCode.INTERNAL_UNKNOWN_ERROR
|
||||
|
||||
if error_code is SmartErrorCode.SUCCESS:
|
||||
return
|
||||
|
||||
if not raise_on_error:
|
||||
resp_dict["result"] = error_code
|
||||
return
|
||||
|
||||
msg = (
|
||||
f"Error querying device: {self._host}: "
|
||||
+ f"{error_code.name}({error_code.value})"
|
||||
+ f" for method: {method}"
|
||||
)
|
||||
if error_code in SMART_RETRYABLE_ERRORS:
|
||||
raise _RetryableError(msg, error_code=error_code)
|
||||
if error_code in SMART_AUTHENTICATION_ERRORS:
|
||||
raise AuthenticationError(msg, error_code=error_code)
|
||||
raise DeviceError(msg, error_code=error_code)
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Close the underlying transport."""
|
||||
await self._transport.close()
|
||||
|
||||
async def _execute_query(
|
||||
self, request: str | dict, *, retry_count: int, iterate_list_pages: bool = True
|
||||
) -> dict:
|
||||
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
|
||||
|
||||
if isinstance(request, dict):
|
||||
if len(request) == 1:
|
||||
multi_method = next(iter(request))
|
||||
module = next(iter(request[multi_method]))
|
||||
req = {
|
||||
"method": multi_method[:3],
|
||||
module: request[multi_method][module],
|
||||
}
|
||||
else:
|
||||
return await self._execute_multiple_query(request, retry_count)
|
||||
else:
|
||||
# If method like getSomeThing then module will be some_thing
|
||||
multi_method = request
|
||||
snake_name = "".join(
|
||||
["_" + i.lower() if i.isupper() else i for i in multi_method]
|
||||
).lstrip("_")
|
||||
module = snake_name[4:]
|
||||
req = {"method": snake_name[:3], module: {}}
|
||||
|
||||
smart_request = json_dumps(req)
|
||||
if debug_enabled:
|
||||
_LOGGER.debug(
|
||||
"%s >> %s",
|
||||
self._host,
|
||||
pf(smart_request),
|
||||
)
|
||||
response_data = await self._transport.send(smart_request)
|
||||
|
||||
if debug_enabled:
|
||||
_LOGGER.debug(
|
||||
"%s << %s",
|
||||
self._host,
|
||||
pf(response_data),
|
||||
)
|
||||
|
||||
if "error_code" in response_data:
|
||||
# H200 does not return an error code
|
||||
self._handle_response_error_code(response_data, multi_method)
|
||||
|
||||
# TODO need to update handle response lists
|
||||
|
||||
if multi_method[:3] == "set":
|
||||
return {}
|
||||
return {multi_method: {module: response_data[module]}}
|
||||
Reference in New Issue
Block a user