mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-08-09 20:24:02 +00:00
Update smartcamera to support single get/set/do requests (#1187)
Not supported by H200 hub
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from pprint import pformat as pf
|
||||
from typing import Any
|
||||
|
||||
@@ -22,6 +23,28 @@ from .sslaestransport import (
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# List of getMethodNames that should be sent as {"method":"do"}
|
||||
# https://md.depau.eu/s/r1Ys_oWoP#Modules
|
||||
GET_METHODS_AS_DO = {
|
||||
"getSdCardFormatStatus",
|
||||
"getConnectionType",
|
||||
"getUserID",
|
||||
"getP2PSharePassword",
|
||||
"getAESEncryptKey",
|
||||
"getFirmwareAFResult",
|
||||
"getWhitelampStatus",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class SingleRequest:
|
||||
"""Class for returning single request details from helper functions."""
|
||||
|
||||
method_type: str
|
||||
method_name: str
|
||||
param_name: str
|
||||
request: dict[str, Any]
|
||||
|
||||
|
||||
class SmartCameraProtocol(SmartProtocol):
|
||||
"""Class for SmartCamera Protocol."""
|
||||
@@ -63,37 +86,70 @@ class SmartCameraProtocol(SmartProtocol):
|
||||
"""Close the underlying transport."""
|
||||
await self._transport.close()
|
||||
|
||||
@staticmethod
|
||||
def _get_smart_camera_single_request(
|
||||
request: dict[str, dict[str, Any]],
|
||||
) -> SingleRequest:
|
||||
method = next(iter(request))
|
||||
if method == "multipleRequest":
|
||||
method_type = "multi"
|
||||
params = request["multipleRequest"]
|
||||
req = {"method": "multipleRequest", "params": params}
|
||||
return SingleRequest("multi", "multipleRequest", "", req)
|
||||
|
||||
param = next(iter(request[method]))
|
||||
method_type = method
|
||||
req = {
|
||||
"method": method,
|
||||
param: request[method][param],
|
||||
}
|
||||
return SingleRequest(method_type, method, param, req)
|
||||
|
||||
@staticmethod
|
||||
def _make_snake_name(name: str) -> str:
|
||||
"""Convert camel or pascal case to snake name."""
|
||||
sn = "".join(["_" + i.lower() if i.isupper() else i for i in name]).lstrip("_")
|
||||
return sn
|
||||
|
||||
@staticmethod
|
||||
def _make_smart_camera_single_request(
|
||||
request: str,
|
||||
) -> SingleRequest:
|
||||
"""Make a single request given a method name and no params.
|
||||
|
||||
If method like getSomeThing then module will be some_thing.
|
||||
"""
|
||||
method = request
|
||||
method_type = request[:3]
|
||||
snake_name = SmartCameraProtocol._make_snake_name(request)
|
||||
param = snake_name[4:]
|
||||
if (
|
||||
(short_method := method[:3])
|
||||
and short_method in {"get", "set"}
|
||||
and method not in GET_METHODS_AS_DO
|
||||
):
|
||||
method_type = short_method
|
||||
param = snake_name[4:]
|
||||
else:
|
||||
method_type = "do"
|
||||
param = snake_name
|
||||
req = {"method": method_type, param: {}}
|
||||
return SingleRequest(method_type, method, param, req)
|
||||
|
||||
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:
|
||||
method = next(iter(request))
|
||||
if method == "multipleRequest":
|
||||
params = request["multipleRequest"]
|
||||
req = {"method": "multipleRequest", "params": params}
|
||||
elif method[:3] == "set":
|
||||
params = next(iter(request[method]))
|
||||
req = {
|
||||
"method": method[:3],
|
||||
params: request[method][params],
|
||||
}
|
||||
else:
|
||||
return await self._execute_multiple_query(request, retry_count)
|
||||
method = next(iter(request))
|
||||
if len(request) == 1 and method in {"get", "set", "do", "multipleRequest"}:
|
||||
single_request = self._get_smart_camera_single_request(request)
|
||||
else:
|
||||
return await self._execute_multiple_query(request, retry_count)
|
||||
else:
|
||||
# If method like getSomeThing then module will be some_thing
|
||||
method = request
|
||||
snake_name = "".join(
|
||||
["_" + i.lower() if i.isupper() else i for i in method]
|
||||
).lstrip("_")
|
||||
params = snake_name[4:]
|
||||
req = {"method": snake_name[:3], params: {}}
|
||||
single_request = self._make_smart_camera_single_request(request)
|
||||
|
||||
smart_request = json_dumps(req)
|
||||
smart_request = json_dumps(single_request.request)
|
||||
if debug_enabled:
|
||||
_LOGGER.debug(
|
||||
"%s >> %s",
|
||||
@@ -111,15 +167,29 @@ class SmartCameraProtocol(SmartProtocol):
|
||||
|
||||
if "error_code" in response_data:
|
||||
# H200 does not return an error code
|
||||
self._handle_response_error_code(response_data, method)
|
||||
self._handle_response_error_code(response_data, single_request.method_name)
|
||||
# Requests that are invalid and raise PROTOCOL_FORMAT_ERROR when sent
|
||||
# as a multipleRequest will return {} when sent as a single request.
|
||||
if single_request.method_type == "get" and (
|
||||
not (section := next(iter(response_data))) or response_data[section] == {}
|
||||
):
|
||||
raise DeviceError(
|
||||
f"No results for get request {single_request.method_name}"
|
||||
)
|
||||
|
||||
# TODO need to update handle response lists
|
||||
|
||||
if method[:3] == "set":
|
||||
if single_request.method_type == "do":
|
||||
return {single_request.method_name: response_data}
|
||||
if single_request.method_type == "set":
|
||||
return {}
|
||||
if method == "multipleRequest":
|
||||
return {method: response_data["result"]}
|
||||
return {method: {params: response_data[params]}}
|
||||
if single_request.method_type == "multi":
|
||||
return {single_request.method_name: response_data["result"]}
|
||||
return {
|
||||
single_request.method_name: {
|
||||
single_request.param_name: response_data[single_request.param_name]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class _ChildCameraProtocolWrapper(SmartProtocol):
|
||||
|
Reference in New Issue
Block a user