mirror of
https://github.com/python-kasa/python-kasa.git
synced 2024-12-22 19:23:34 +00:00
Update smartcamera to support single get/set/do requests (#1187)
Not supported by H200 hub
This commit is contained in:
parent
c839aaa1dd
commit
8ee8c17bdc
@ -12,6 +12,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
import dataclasses
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
@ -23,6 +24,7 @@ from pprint import pprint
|
|||||||
|
|
||||||
import asyncclick as click
|
import asyncclick as click
|
||||||
|
|
||||||
|
from devtools.helpers.smartcamerarequests import SMARTCAMERA_REQUESTS
|
||||||
from devtools.helpers.smartrequests import SmartRequest, get_component_requests
|
from devtools.helpers.smartrequests import SmartRequest, get_component_requests
|
||||||
from kasa import (
|
from kasa import (
|
||||||
AuthenticationError,
|
AuthenticationError,
|
||||||
@ -46,10 +48,10 @@ from kasa.smart import SmartChildDevice
|
|||||||
from kasa.smartprotocol import SmartProtocol, _ChildProtocolWrapper
|
from kasa.smartprotocol import SmartProtocol, _ChildProtocolWrapper
|
||||||
|
|
||||||
Call = namedtuple("Call", "module method")
|
Call = namedtuple("Call", "module method")
|
||||||
SmartCall = namedtuple("SmartCall", "module request should_succeed child_device_id")
|
|
||||||
FixtureResult = namedtuple("FixtureResult", "filename, folder, data")
|
FixtureResult = namedtuple("FixtureResult", "filename, folder, data")
|
||||||
|
|
||||||
SMART_FOLDER = "kasa/tests/fixtures/smart/"
|
SMART_FOLDER = "kasa/tests/fixtures/smart/"
|
||||||
|
SMARTCAMERA_FOLDER = "kasa/tests/fixtures/smartcamera/"
|
||||||
SMART_CHILD_FOLDER = "kasa/tests/fixtures/smart/child/"
|
SMART_CHILD_FOLDER = "kasa/tests/fixtures/smart/child/"
|
||||||
IOT_FOLDER = "kasa/tests/fixtures/"
|
IOT_FOLDER = "kasa/tests/fixtures/"
|
||||||
|
|
||||||
@ -58,6 +60,17 @@ ENCRYPT_TYPES = [encrypt_type.value for encrypt_type in DeviceEncryptionType]
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class SmartCall:
|
||||||
|
"""Class for smart and smartcamera calls."""
|
||||||
|
|
||||||
|
module: str
|
||||||
|
request: dict
|
||||||
|
should_succeed: bool
|
||||||
|
child_device_id: str
|
||||||
|
supports_multiple: bool = True
|
||||||
|
|
||||||
|
|
||||||
def scrub(res):
|
def scrub(res):
|
||||||
"""Remove identifiers from the given dict."""
|
"""Remove identifiers from the given dict."""
|
||||||
keys_to_scrub = [
|
keys_to_scrub = [
|
||||||
@ -136,7 +149,7 @@ def scrub(res):
|
|||||||
v = base64.b64encode(b"#MASKED_SSID#").decode()
|
v = base64.b64encode(b"#MASKED_SSID#").decode()
|
||||||
elif k in ["nickname"]:
|
elif k in ["nickname"]:
|
||||||
v = base64.b64encode(b"#MASKED_NAME#").decode()
|
v = base64.b64encode(b"#MASKED_NAME#").decode()
|
||||||
elif k in ["alias", "device_alias"]:
|
elif k in ["alias", "device_alias", "device_name"]:
|
||||||
v = "#MASKED_NAME#"
|
v = "#MASKED_NAME#"
|
||||||
elif isinstance(res[k], int):
|
elif isinstance(res[k], int):
|
||||||
v = 0
|
v = 0
|
||||||
@ -477,6 +490,44 @@ def format_exception(e):
|
|||||||
return exception_str
|
return exception_str
|
||||||
|
|
||||||
|
|
||||||
|
async def _make_final_calls(
|
||||||
|
protocol: SmartProtocol,
|
||||||
|
calls: list[SmartCall],
|
||||||
|
name: str,
|
||||||
|
batch_size: int,
|
||||||
|
*,
|
||||||
|
child_device_id: str,
|
||||||
|
) -> dict[str, dict]:
|
||||||
|
"""Call all successes again.
|
||||||
|
|
||||||
|
After trying each call individually make the calls again either as a
|
||||||
|
multiple request or as single requests for those that don't support
|
||||||
|
multiple queries.
|
||||||
|
"""
|
||||||
|
multiple_requests = {
|
||||||
|
key: smartcall.request[key]
|
||||||
|
for smartcall in calls
|
||||||
|
if smartcall.supports_multiple and (key := next(iter(smartcall.request)))
|
||||||
|
}
|
||||||
|
final = await _make_requests_or_exit(
|
||||||
|
protocol,
|
||||||
|
multiple_requests,
|
||||||
|
name + " - multiple",
|
||||||
|
batch_size,
|
||||||
|
child_device_id=child_device_id,
|
||||||
|
)
|
||||||
|
single_calls = [smartcall for smartcall in calls if not smartcall.supports_multiple]
|
||||||
|
for smartcall in single_calls:
|
||||||
|
final[smartcall.module] = await _make_requests_or_exit(
|
||||||
|
protocol,
|
||||||
|
smartcall.request,
|
||||||
|
f"{name} + {smartcall.module}",
|
||||||
|
batch_size,
|
||||||
|
child_device_id=child_device_id,
|
||||||
|
)
|
||||||
|
return final
|
||||||
|
|
||||||
|
|
||||||
async def _make_requests_or_exit(
|
async def _make_requests_or_exit(
|
||||||
protocol: SmartProtocol,
|
protocol: SmartProtocol,
|
||||||
requests: dict,
|
requests: dict,
|
||||||
@ -534,69 +585,28 @@ async def get_smart_camera_test_calls(protocol: SmartProtocol):
|
|||||||
test_calls: list[SmartCall] = []
|
test_calls: list[SmartCall] = []
|
||||||
successes: list[SmartCall] = []
|
successes: list[SmartCall] = []
|
||||||
|
|
||||||
requests = {
|
|
||||||
"getAlertTypeList": {"msg_alarm": {"name": "alert_type"}},
|
|
||||||
"getNightVisionCapability": {"image_capability": {"name": ["supplement_lamp"]}},
|
|
||||||
"getDeviceInfo": {"device_info": {"name": ["basic_info"]}},
|
|
||||||
"getDetectionConfig": {"motion_detection": {"name": ["motion_det"]}},
|
|
||||||
"getPersonDetectionConfig": {"people_detection": {"name": ["detection"]}},
|
|
||||||
"getVehicleDetectionConfig": {"vehicle_detection": {"name": ["detection"]}},
|
|
||||||
"getBCDConfig": {"sound_detection": {"name": ["bcd"]}},
|
|
||||||
"getPetDetectionConfig": {"pet_detection": {"name": ["detection"]}},
|
|
||||||
"getBarkDetectionConfig": {"bark_detection": {"name": ["detection"]}},
|
|
||||||
"getMeowDetectionConfig": {"meow_detection": {"name": ["detection"]}},
|
|
||||||
"getGlassDetectionConfig": {"glass_detection": {"name": ["detection"]}},
|
|
||||||
"getTamperDetectionConfig": {"tamper_detection": {"name": "tamper_det"}},
|
|
||||||
"getLensMaskConfig": {"lens_mask": {"name": ["lens_mask_info"]}},
|
|
||||||
"getLdc": {"image": {"name": ["switch", "common"]}},
|
|
||||||
"getLastAlarmInfo": {"msg_alarm": {"name": ["chn1_msg_alarm_info"]}},
|
|
||||||
"getLedStatus": {"led": {"name": ["config"]}},
|
|
||||||
"getTargetTrackConfig": {"target_track": {"name": ["target_track_info"]}},
|
|
||||||
"getPresetConfig": {"preset": {"name": ["preset"]}},
|
|
||||||
"getFirmwareUpdateStatus": {"cloud_config": {"name": "upgrade_status"}},
|
|
||||||
"getMediaEncrypt": {"cet": {"name": ["media_encrypt"]}},
|
|
||||||
"getConnectionType": {"network": {"get_connection_type": []}},
|
|
||||||
"getAlarmConfig": {"msg_alarm": {}},
|
|
||||||
"getAlarmPlan": {"msg_alarm_plan": {}},
|
|
||||||
"getSirenTypeList": {"siren": {}},
|
|
||||||
"getSirenConfig": {"siren": {}},
|
|
||||||
"getAlertConfig": {
|
|
||||||
"msg_alarm": {
|
|
||||||
"name": ["chn1_msg_alarm_info", "capability"],
|
|
||||||
"table": ["usr_def_audio"],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"getLightTypeList": {"msg_alarm": {}},
|
|
||||||
"getSirenStatus": {"siren": {}},
|
|
||||||
"getLightFrequencyInfo": {"image": {"name": "common"}},
|
|
||||||
"getLightFrequencyCapability": {"image": {"name": "common"}},
|
|
||||||
"getRotationStatus": {"image": {"name": ["switch"]}},
|
|
||||||
"getNightVisionModeConfig": {"image": {"name": "switch"}},
|
|
||||||
"getWhitelampStatus": {"image": {"get_wtl_status": ["null"]}},
|
|
||||||
"getWhitelampConfig": {"image": {"name": "switch"}},
|
|
||||||
"getMsgPushConfig": {"msg_push": {"name": ["chn1_msg_push_info"]}},
|
|
||||||
"getSdCardStatus": {"harddisk_manage": {"table": ["hd_info"]}},
|
|
||||||
"getCircularRecordingConfig": {"harddisk_manage": {"name": "harddisk"}},
|
|
||||||
"getRecordPlan": {"record_plan": {"name": ["chn1_channel"]}},
|
|
||||||
"getAudioConfig": {"audio_config": {"name": ["speaker", "microphone"]}},
|
|
||||||
"getFirmwareAutoUpgradeConfig": {"auto_upgrade": {"name": ["common"]}},
|
|
||||||
"getVideoQualities": {"video": {"name": ["main"]}},
|
|
||||||
"getVideoCapability": {"video_capability": {"name": "main"}},
|
|
||||||
}
|
|
||||||
test_calls = []
|
test_calls = []
|
||||||
for method, params in requests.items():
|
for request in SMARTCAMERA_REQUESTS:
|
||||||
|
method = next(iter(request))
|
||||||
|
if method == "get":
|
||||||
|
module = method + "_" + next(iter(request[method]))
|
||||||
|
else:
|
||||||
|
module = method
|
||||||
test_calls.append(
|
test_calls.append(
|
||||||
SmartCall(
|
SmartCall(
|
||||||
module=method,
|
module=module,
|
||||||
request={method: params},
|
request=request,
|
||||||
should_succeed=True,
|
should_succeed=True,
|
||||||
child_device_id="",
|
child_device_id="",
|
||||||
|
supports_multiple=(method != "get"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Now get the child device requests
|
# Now get the child device requests
|
||||||
|
child_request = {
|
||||||
|
"getChildDeviceList": {"childControl": {"start_index": 0}},
|
||||||
|
}
|
||||||
try:
|
try:
|
||||||
child_request = {"getChildDeviceList": {"childControl": {"start_index": 0}}}
|
|
||||||
child_response = await protocol.query(child_request)
|
child_response = await protocol.query(child_request)
|
||||||
except Exception:
|
except Exception:
|
||||||
_LOGGER.debug("Device does not have any children.")
|
_LOGGER.debug("Device does not have any children.")
|
||||||
@ -607,6 +617,7 @@ async def get_smart_camera_test_calls(protocol: SmartProtocol):
|
|||||||
request=child_request,
|
request=child_request,
|
||||||
should_succeed=True,
|
should_succeed=True,
|
||||||
child_device_id="",
|
child_device_id="",
|
||||||
|
supports_multiple=True,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
child_list = child_response["getChildDeviceList"]["child_device_list"]
|
child_list = child_response["getChildDeviceList"]["child_device_list"]
|
||||||
@ -660,11 +671,14 @@ async def get_smart_camera_test_calls(protocol: SmartProtocol):
|
|||||||
click.echo(f"Skipping {component_id}..", nl=False)
|
click.echo(f"Skipping {component_id}..", nl=False)
|
||||||
click.echo(click.style("UNSUPPORTED", fg="yellow"))
|
click.echo(click.style("UNSUPPORTED", fg="yellow"))
|
||||||
else: # Not a smart protocol device so assume camera protocol
|
else: # Not a smart protocol device so assume camera protocol
|
||||||
for method, params in requests.items():
|
for request in SMARTCAMERA_REQUESTS:
|
||||||
|
method = next(iter(request))
|
||||||
|
if method == "get":
|
||||||
|
method = method + "_" + next(iter(request[method]))
|
||||||
test_calls.append(
|
test_calls.append(
|
||||||
SmartCall(
|
SmartCall(
|
||||||
module=method,
|
module=method,
|
||||||
request={method: params},
|
request=request,
|
||||||
should_succeed=True,
|
should_succeed=True,
|
||||||
child_device_id=child_id,
|
child_device_id=child_id,
|
||||||
)
|
)
|
||||||
@ -804,7 +818,9 @@ async def get_smart_test_calls(protocol: SmartProtocol):
|
|||||||
click.echo(click.style("UNSUPPORTED", fg="yellow"))
|
click.echo(click.style("UNSUPPORTED", fg="yellow"))
|
||||||
# Add the extra calls for each child
|
# Add the extra calls for each child
|
||||||
for extra_call in extra_test_calls:
|
for extra_call in extra_test_calls:
|
||||||
extra_child_call = extra_call._replace(child_device_id=child_device_id)
|
extra_child_call = dataclasses.replace(
|
||||||
|
extra_call, child_device_id=child_device_id
|
||||||
|
)
|
||||||
test_calls.append(extra_child_call)
|
test_calls.append(extra_child_call)
|
||||||
|
|
||||||
return test_calls, successes
|
return test_calls, successes
|
||||||
@ -879,10 +895,10 @@ async def get_smart_fixtures(
|
|||||||
finally:
|
finally:
|
||||||
await protocol.close()
|
await protocol.close()
|
||||||
|
|
||||||
device_requests: dict[str, dict] = {}
|
device_requests: dict[str, list[SmartCall]] = {}
|
||||||
for success in successes:
|
for success in successes:
|
||||||
device_request = device_requests.setdefault(success.child_device_id, {})
|
device_request = device_requests.setdefault(success.child_device_id, [])
|
||||||
device_request.update(success.request)
|
device_request.append(success)
|
||||||
|
|
||||||
scrubbed_device_ids = {
|
scrubbed_device_ids = {
|
||||||
device_id: f"SCRUBBED_CHILD_DEVICE_ID_{index}"
|
device_id: f"SCRUBBED_CHILD_DEVICE_ID_{index}"
|
||||||
@ -890,24 +906,21 @@ async def get_smart_fixtures(
|
|||||||
if device_id != ""
|
if device_id != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
final = await _make_requests_or_exit(
|
final = await _make_final_calls(
|
||||||
protocol,
|
protocol, device_requests[""], "All successes", batch_size, child_device_id=""
|
||||||
device_requests[""],
|
|
||||||
"all successes at once",
|
|
||||||
batch_size,
|
|
||||||
child_device_id="",
|
|
||||||
)
|
)
|
||||||
fixture_results = []
|
fixture_results = []
|
||||||
for child_device_id, requests in device_requests.items():
|
for child_device_id, requests in device_requests.items():
|
||||||
if child_device_id == "":
|
if child_device_id == "":
|
||||||
continue
|
continue
|
||||||
response = await _make_requests_or_exit(
|
response = await _make_final_calls(
|
||||||
protocol,
|
protocol,
|
||||||
requests,
|
requests,
|
||||||
"all child successes at once",
|
"All child successes",
|
||||||
batch_size,
|
batch_size,
|
||||||
child_device_id=child_device_id,
|
child_device_id=child_device_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
scrubbed = scrubbed_device_ids[child_device_id]
|
scrubbed = scrubbed_device_ids[child_device_id]
|
||||||
if "get_device_info" in response and "device_id" in response["get_device_info"]:
|
if "get_device_info" in response and "device_id" in response["get_device_info"]:
|
||||||
response["get_device_info"]["device_id"] = scrubbed
|
response["get_device_info"]["device_id"] = scrubbed
|
||||||
@ -963,6 +976,7 @@ async def get_smart_fixtures(
|
|||||||
click.echo(click.style("## device info file ##", bold=True))
|
click.echo(click.style("## device info file ##", bold=True))
|
||||||
|
|
||||||
if "get_device_info" in final:
|
if "get_device_info" in final:
|
||||||
|
# smart protocol
|
||||||
hw_version = final["get_device_info"]["hw_ver"]
|
hw_version = final["get_device_info"]["hw_ver"]
|
||||||
sw_version = final["get_device_info"]["fw_ver"]
|
sw_version = final["get_device_info"]["fw_ver"]
|
||||||
if discovery_info:
|
if discovery_info:
|
||||||
@ -970,16 +984,19 @@ async def get_smart_fixtures(
|
|||||||
else:
|
else:
|
||||||
model = final["get_device_info"]["model"] + "(XX)"
|
model = final["get_device_info"]["model"] + "(XX)"
|
||||||
sw_version = sw_version.split(" ", maxsplit=1)[0]
|
sw_version = sw_version.split(" ", maxsplit=1)[0]
|
||||||
|
copy_folder = SMART_FOLDER
|
||||||
else:
|
else:
|
||||||
|
# smart camera protocol
|
||||||
hw_version = final["getDeviceInfo"]["device_info"]["basic_info"]["hw_version"]
|
hw_version = final["getDeviceInfo"]["device_info"]["basic_info"]["hw_version"]
|
||||||
sw_version = final["getDeviceInfo"]["device_info"]["basic_info"]["sw_version"]
|
sw_version = final["getDeviceInfo"]["device_info"]["basic_info"]["sw_version"]
|
||||||
model = final["getDeviceInfo"]["device_info"]["basic_info"]["device_model"]
|
model = final["getDeviceInfo"]["device_info"]["basic_info"]["device_model"]
|
||||||
region = final["getDeviceInfo"]["device_info"]["basic_info"]["region"]
|
region = final["getDeviceInfo"]["device_info"]["basic_info"]["region"]
|
||||||
sw_version = sw_version.split(" ", maxsplit=1)[0]
|
sw_version = sw_version.split(" ", maxsplit=1)[0]
|
||||||
model = f"{model}({region})"
|
model = f"{model}({region})"
|
||||||
|
copy_folder = SMARTCAMERA_FOLDER
|
||||||
|
|
||||||
save_filename = f"{model}_{hw_version}_{sw_version}.json"
|
save_filename = f"{model}_{hw_version}_{sw_version}.json"
|
||||||
copy_folder = SMART_FOLDER
|
|
||||||
fixture_results.insert(
|
fixture_results.insert(
|
||||||
0, FixtureResult(filename=save_filename, folder=copy_folder, data=final)
|
0, FixtureResult(filename=save_filename, folder=copy_folder, data=final)
|
||||||
)
|
)
|
||||||
|
61
devtools/helpers/smartcamerarequests.py
Normal file
61
devtools/helpers/smartcamerarequests.py
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
"""Module for smart camera requests."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
SMARTCAMERA_REQUESTS: list[dict] = [
|
||||||
|
{"getAlertTypeList": {"msg_alarm": {"name": "alert_type"}}},
|
||||||
|
{"getNightVisionCapability": {"image_capability": {"name": ["supplement_lamp"]}}},
|
||||||
|
{"getDeviceInfo": {"device_info": {"name": ["basic_info"]}}},
|
||||||
|
{"getDetectionConfig": {"motion_detection": {"name": ["motion_det"]}}},
|
||||||
|
{"getPersonDetectionConfig": {"people_detection": {"name": ["detection"]}}},
|
||||||
|
{"getVehicleDetectionConfig": {"vehicle_detection": {"name": ["detection"]}}},
|
||||||
|
{"getBCDConfig": {"sound_detection": {"name": ["bcd"]}}},
|
||||||
|
{"getPetDetectionConfig": {"pet_detection": {"name": ["detection"]}}},
|
||||||
|
{"getBarkDetectionConfig": {"bark_detection": {"name": ["detection"]}}},
|
||||||
|
{"getMeowDetectionConfig": {"meow_detection": {"name": ["detection"]}}},
|
||||||
|
{"getGlassDetectionConfig": {"glass_detection": {"name": ["detection"]}}},
|
||||||
|
{"getTamperDetectionConfig": {"tamper_detection": {"name": "tamper_det"}}},
|
||||||
|
{"getLensMaskConfig": {"lens_mask": {"name": ["lens_mask_info"]}}},
|
||||||
|
{"getLdc": {"image": {"name": ["switch", "common"]}}},
|
||||||
|
{"getLastAlarmInfo": {"system": {"name": ["last_alarm_info"]}}},
|
||||||
|
{"getLedStatus": {"led": {"name": ["config"]}}},
|
||||||
|
{"getTargetTrackConfig": {"target_track": {"name": ["target_track_info"]}}},
|
||||||
|
{"getPresetConfig": {"preset": {"name": ["preset"]}}},
|
||||||
|
{"getFirmwareUpdateStatus": {"cloud_config": {"name": "upgrade_status"}}},
|
||||||
|
{"getMediaEncrypt": {"cet": {"name": ["media_encrypt"]}}},
|
||||||
|
{"getConnectionType": {"network": {"get_connection_type": []}}},
|
||||||
|
{
|
||||||
|
"getAlertConfig": {
|
||||||
|
"msg_alarm": {
|
||||||
|
"name": ["chn1_msg_alarm_info", "capability"],
|
||||||
|
"table": ["usr_def_audio"],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"getAlertPlan": {"msg_alarm_plan": {"name": "chn1_msg_alarm_plan"}}},
|
||||||
|
{"getSirenTypeList": {"siren": {}}},
|
||||||
|
{"getSirenConfig": {"siren": {}}},
|
||||||
|
{"getLightTypeList": {"msg_alarm": {}}},
|
||||||
|
{"getSirenStatus": {"siren": {}}},
|
||||||
|
{"getLightFrequencyInfo": {"image": {"name": "common"}}},
|
||||||
|
{"getRotationStatus": {"image": {"name": ["switch"]}}},
|
||||||
|
{"getNightVisionModeConfig": {"image": {"name": "switch"}}},
|
||||||
|
{"getWhitelampStatus": {"image": {"get_wtl_status": ["null"]}}},
|
||||||
|
{"getWhitelampConfig": {"image": {"name": "switch"}}},
|
||||||
|
{"getMsgPushConfig": {"msg_push": {"name": ["chn1_msg_push_info"]}}},
|
||||||
|
{"getSdCardStatus": {"harddisk_manage": {"table": ["hd_info"]}}},
|
||||||
|
{"getCircularRecordingConfig": {"harddisk_manage": {"name": "harddisk"}}},
|
||||||
|
{"getRecordPlan": {"record_plan": {"name": ["chn1_channel"]}}},
|
||||||
|
{"getAudioConfig": {"audio_config": {"name": ["speaker", "microphone"]}}},
|
||||||
|
{"getFirmwareAutoUpgradeConfig": {"auto_upgrade": {"name": ["common"]}}},
|
||||||
|
{"getVideoQualities": {"video": {"name": ["main"]}}},
|
||||||
|
{"getVideoCapability": {"video_capability": {"name": "main"}}},
|
||||||
|
{"getTimezone": {"system": {"name": "basic"}}},
|
||||||
|
{"getClockStatus": {"system": {"name": "clock_status"}}},
|
||||||
|
# single request only methods
|
||||||
|
{"get": {"function": {"name": ["module_spec"]}}},
|
||||||
|
{"get": {"cet": {"name": ["vhttpd"]}}},
|
||||||
|
{"get": {"motor": {"name": ["capability"]}}},
|
||||||
|
{"get": {"audio_capability": {"name": ["device_speaker", "device_microphone"]}}},
|
||||||
|
{"get": {"audio_config": {"name": ["speaker", "microphone"]}}},
|
||||||
|
]
|
@ -3,6 +3,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
from pprint import pformat as pf
|
from pprint import pformat as pf
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@ -22,6 +23,28 @@ from .sslaestransport import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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 SmartCameraProtocol(SmartProtocol):
|
||||||
"""Class for SmartCamera Protocol."""
|
"""Class for SmartCamera Protocol."""
|
||||||
@ -63,37 +86,70 @@ class SmartCameraProtocol(SmartProtocol):
|
|||||||
"""Close the underlying transport."""
|
"""Close the underlying transport."""
|
||||||
await self._transport.close()
|
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(
|
async def _execute_query(
|
||||||
self, request: str | dict, *, retry_count: int, iterate_list_pages: bool = True
|
self, request: str | dict, *, retry_count: int, iterate_list_pages: bool = True
|
||||||
) -> dict:
|
) -> dict:
|
||||||
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
|
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
|
||||||
|
|
||||||
if isinstance(request, dict):
|
if isinstance(request, dict):
|
||||||
if len(request) == 1:
|
method = next(iter(request))
|
||||||
method = next(iter(request))
|
if len(request) == 1 and method in {"get", "set", "do", "multipleRequest"}:
|
||||||
if method == "multipleRequest":
|
single_request = self._get_smart_camera_single_request(request)
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
return await self._execute_multiple_query(request, retry_count)
|
return await self._execute_multiple_query(request, retry_count)
|
||||||
else:
|
else:
|
||||||
# If method like getSomeThing then module will be some_thing
|
single_request = self._make_smart_camera_single_request(request)
|
||||||
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: {}}
|
|
||||||
|
|
||||||
smart_request = json_dumps(req)
|
smart_request = json_dumps(single_request.request)
|
||||||
if debug_enabled:
|
if debug_enabled:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"%s >> %s",
|
"%s >> %s",
|
||||||
@ -111,15 +167,29 @@ class SmartCameraProtocol(SmartProtocol):
|
|||||||
|
|
||||||
if "error_code" in response_data:
|
if "error_code" in response_data:
|
||||||
# H200 does not return an error code
|
# 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
|
# 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 {}
|
return {}
|
||||||
if method == "multipleRequest":
|
if single_request.method_type == "multi":
|
||||||
return {method: response_data["result"]}
|
return {single_request.method_name: response_data["result"]}
|
||||||
return {method: {params: response_data[params]}}
|
return {
|
||||||
|
single_request.method_name: {
|
||||||
|
single_request.param_name: response_data[single_request.param_name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class _ChildCameraProtocolWrapper(SmartProtocol):
|
class _ChildCameraProtocolWrapper(SmartProtocol):
|
||||||
|
@ -163,6 +163,10 @@ class SmartProtocol(BaseProtocol):
|
|||||||
]
|
]
|
||||||
|
|
||||||
end = len(multi_requests)
|
end = len(multi_requests)
|
||||||
|
# The SmartCameraProtocol sends requests with a length 1 as a
|
||||||
|
# multipleRequest. The SmartProtocol doesn't so will never
|
||||||
|
# raise_on_error
|
||||||
|
raise_on_error = end == 1
|
||||||
|
|
||||||
# Break the requests down as there can be a size limit
|
# Break the requests down as there can be a size limit
|
||||||
step = self._multi_request_batch_size
|
step = self._multi_request_batch_size
|
||||||
@ -172,14 +176,12 @@ class SmartProtocol(BaseProtocol):
|
|||||||
method = request["method"]
|
method = request["method"]
|
||||||
req = self.get_smart_request(method, request.get("params"))
|
req = self.get_smart_request(method, request.get("params"))
|
||||||
resp = await self._transport.send(req)
|
resp = await self._transport.send(req)
|
||||||
self._handle_response_error_code(resp, method, raise_on_error=False)
|
self._handle_response_error_code(
|
||||||
|
resp, method, raise_on_error=raise_on_error
|
||||||
|
)
|
||||||
multi_result[method] = resp["result"]
|
multi_result[method] = resp["result"]
|
||||||
return multi_result
|
return multi_result
|
||||||
|
|
||||||
# The SmartCameraProtocol sends requests with a length 1 as a
|
|
||||||
# multipleRequest. The SmartProtocol doesn't so will never
|
|
||||||
# raise_on_error
|
|
||||||
raise_on_error = end == 1
|
|
||||||
for batch_num, i in enumerate(range(0, end, step)):
|
for batch_num, i in enumerate(range(0, end, step)):
|
||||||
requests_step = multi_requests[i : i + step]
|
requests_step = multi_requests[i : i + step]
|
||||||
|
|
||||||
|
@ -173,10 +173,16 @@ class FakeSmartCameraTransport(BaseTransport):
|
|||||||
request_dict["params"]["childControl"]
|
request_dict["params"]["childControl"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if method == "set":
|
if method[:3] == "set":
|
||||||
for key, val in request_dict.items():
|
for key, val in request_dict.items():
|
||||||
if key != "method":
|
if key != "method":
|
||||||
module = key
|
# key is params for multi request and the actual params
|
||||||
|
# for single requests
|
||||||
|
if key == "params":
|
||||||
|
module = next(iter(val))
|
||||||
|
val = val[module]
|
||||||
|
else:
|
||||||
|
module = key
|
||||||
section = next(iter(val))
|
section = next(iter(val))
|
||||||
skey_val = val[section]
|
skey_val = val[section]
|
||||||
for skey, sval in skey_val.items():
|
for skey, sval in skey_val.items():
|
||||||
|
@ -5,14 +5,14 @@
|
|||||||
"connect_type": "wireless",
|
"connect_type": "wireless",
|
||||||
"device_id": "0000000000000000000000000000000000000000",
|
"device_id": "0000000000000000000000000000000000000000",
|
||||||
"http_port": 443,
|
"http_port": 443,
|
||||||
"last_alarm_time": "0",
|
"last_alarm_time": "1729264456",
|
||||||
"last_alarm_type": "",
|
"last_alarm_type": "motion",
|
||||||
"owner": "00000000000000000000000000000000",
|
"owner": "00000000000000000000000000000000",
|
||||||
"sd_status": "offline"
|
"sd_status": "offline"
|
||||||
},
|
},
|
||||||
"device_id": "00000000000000000000000000000000",
|
"device_id": "00000000000000000000000000000000",
|
||||||
"device_model": "C210",
|
"device_model": "C210",
|
||||||
"device_name": "00000 000",
|
"device_name": "#MASKED_NAME#",
|
||||||
"device_type": "SMART.IPCAMERA",
|
"device_type": "SMART.IPCAMERA",
|
||||||
"encrypt_info": {
|
"encrypt_info": {
|
||||||
"data": "",
|
"data": "",
|
||||||
@ -60,6 +60,14 @@
|
|||||||
"usr_def_audio": []
|
"usr_def_audio": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"getAlertPlan": {
|
||||||
|
"msg_alarm_plan": {
|
||||||
|
"chn1_msg_alarm_plan": {
|
||||||
|
"alarm_plan_1": "0000-0000,127",
|
||||||
|
"enabled": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"getAlertTypeList": {
|
"getAlertTypeList": {
|
||||||
"msg_alarm": {
|
"msg_alarm": {
|
||||||
"alert_type": {
|
"alert_type": {
|
||||||
@ -106,10 +114,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"getClockStatus": {
|
||||||
|
"system": {
|
||||||
|
"clock_status": {
|
||||||
|
"local_time": "2024-10-24 12:49:09",
|
||||||
|
"seconds_from_1970": 1729770549
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"getConnectionType": {
|
"getConnectionType": {
|
||||||
"link_type": "wifi",
|
"link_type": "wifi",
|
||||||
"rssi": "2",
|
"rssi": "3",
|
||||||
"rssiValue": -64,
|
"rssiValue": -62,
|
||||||
"ssid": "I01BU0tFRF9TU0lEIw=="
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
},
|
},
|
||||||
"getDetectionConfig": {
|
"getDetectionConfig": {
|
||||||
@ -133,7 +149,7 @@
|
|||||||
"device_alias": "#MASKED_NAME#",
|
"device_alias": "#MASKED_NAME#",
|
||||||
"device_info": "C210 2.0 IPC",
|
"device_info": "C210 2.0 IPC",
|
||||||
"device_model": "C210",
|
"device_model": "C210",
|
||||||
"device_name": "0000 0.0",
|
"device_name": "#MASKED_NAME#",
|
||||||
"device_type": "SMART.IPCAMERA",
|
"device_type": "SMART.IPCAMERA",
|
||||||
"features": 3,
|
"features": 3,
|
||||||
"ffs": false,
|
"ffs": false,
|
||||||
@ -171,19 +187,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"getLastAlarmInfo": {
|
"getLastAlarmInfo": {
|
||||||
"msg_alarm": {
|
"system": {
|
||||||
"chn1_msg_alarm_info": {
|
"last_alarm_info": {
|
||||||
"alarm_duration": "0",
|
"last_alarm_time": "1729264456",
|
||||||
"alarm_mode": [
|
"last_alarm_type": "motion"
|
||||||
"sound",
|
|
||||||
"light"
|
|
||||||
],
|
|
||||||
"alarm_type": "0",
|
|
||||||
"alarm_volume": "high",
|
|
||||||
"enabled": "off",
|
|
||||||
"light_alarm_enabled": "on",
|
|
||||||
"light_type": "1",
|
|
||||||
"sound_alarm_enabled": "on"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -519,6 +526,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"getTimezone": {
|
||||||
|
"system": {
|
||||||
|
"basic": {
|
||||||
|
"timezone": "UTC-00:00",
|
||||||
|
"timing_mode": "ntp",
|
||||||
|
"zone_id": "Europe/Berlin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"getVideoCapability": {
|
"getVideoCapability": {
|
||||||
"video_capability": {
|
"video_capability": {
|
||||||
"main": {
|
"main": {
|
||||||
@ -602,5 +618,199 @@
|
|||||||
"getWhitelampStatus": {
|
"getWhitelampStatus": {
|
||||||
"rest_time": 0,
|
"rest_time": 0,
|
||||||
"status": 0
|
"status": 0
|
||||||
|
},
|
||||||
|
"get_audio_capability": {
|
||||||
|
"get": {
|
||||||
|
"audio_capability": {
|
||||||
|
"device_microphone": {
|
||||||
|
"aec": "1",
|
||||||
|
"channels": "1",
|
||||||
|
"echo_cancelling": "0",
|
||||||
|
"encode_type": [
|
||||||
|
"G711alaw"
|
||||||
|
],
|
||||||
|
"half_duplex": "1",
|
||||||
|
"mute": "1",
|
||||||
|
"noise_cancelling": "1",
|
||||||
|
"sampling_rate": [
|
||||||
|
"8",
|
||||||
|
"16"
|
||||||
|
],
|
||||||
|
"volume": "1"
|
||||||
|
},
|
||||||
|
"device_speaker": {
|
||||||
|
"channels": "1",
|
||||||
|
"decode_type": [
|
||||||
|
"G711alaw",
|
||||||
|
"G711ulaw"
|
||||||
|
],
|
||||||
|
"mute": "0",
|
||||||
|
"output_device_type": "0",
|
||||||
|
"sampling_rate": [
|
||||||
|
"8",
|
||||||
|
"16"
|
||||||
|
],
|
||||||
|
"system_volume": "100",
|
||||||
|
"volume": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get_audio_config": {
|
||||||
|
"get": {
|
||||||
|
"audio_config": {
|
||||||
|
"microphone": {
|
||||||
|
"bitrate": "64",
|
||||||
|
"channels": "1",
|
||||||
|
"echo_cancelling": "off",
|
||||||
|
"encode_type": "G711alaw",
|
||||||
|
"input_device_type": "MicIn",
|
||||||
|
"mute": "off",
|
||||||
|
"noise_cancelling": "on",
|
||||||
|
"sampling_rate": "8",
|
||||||
|
"volume": "100"
|
||||||
|
},
|
||||||
|
"speaker": {
|
||||||
|
"mute": "off",
|
||||||
|
"output_device_type": "SpeakerOut",
|
||||||
|
"volume": "100"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get_cet": {
|
||||||
|
"get": {
|
||||||
|
"cet": {
|
||||||
|
"vhttpd": {
|
||||||
|
"port": "8800"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get_function": {
|
||||||
|
"get": {
|
||||||
|
"function": {
|
||||||
|
"module_spec": {
|
||||||
|
"ae_weighting_table_resolution": "5*5",
|
||||||
|
"ai_enhance_capability": "1",
|
||||||
|
"ai_enhance_range": [
|
||||||
|
"traditional_enhance"
|
||||||
|
],
|
||||||
|
"ai_firmware_upgrade": "0",
|
||||||
|
"alarm_out_num": "0",
|
||||||
|
"app_version": "1.0.0",
|
||||||
|
"audio": [
|
||||||
|
"speaker",
|
||||||
|
"microphone"
|
||||||
|
],
|
||||||
|
"auth_encrypt": "1",
|
||||||
|
"auto_ip_configurable": "1",
|
||||||
|
"backlight_coexistence": "1",
|
||||||
|
"change_password": "1",
|
||||||
|
"client_info": "1",
|
||||||
|
"cloud_storage_version": "1.0",
|
||||||
|
"config_recovery": [
|
||||||
|
"audio_config",
|
||||||
|
"OSD",
|
||||||
|
"image",
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"custom_area_compensation": "1",
|
||||||
|
"custom_auto_mode_exposure_level": "1",
|
||||||
|
"daynight_subdivision": "1",
|
||||||
|
"device_share": [
|
||||||
|
"preview",
|
||||||
|
"playback",
|
||||||
|
"voice",
|
||||||
|
"cloud_storage",
|
||||||
|
"motor"
|
||||||
|
],
|
||||||
|
"download": [
|
||||||
|
"video"
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
"motion",
|
||||||
|
"tamper"
|
||||||
|
],
|
||||||
|
"force_iframe_support": "1",
|
||||||
|
"greeter": "1.0",
|
||||||
|
"http_system_state_audio_support": "1",
|
||||||
|
"image_capability": "1",
|
||||||
|
"image_list": [
|
||||||
|
"supplement_lamp",
|
||||||
|
"expose"
|
||||||
|
],
|
||||||
|
"ir_led_pwm_control": "1",
|
||||||
|
"led": "1",
|
||||||
|
"lens_mask": "1",
|
||||||
|
"linkage_capability": "1",
|
||||||
|
"local_storage": "1",
|
||||||
|
"media_encrypt": "1",
|
||||||
|
"motor": "0",
|
||||||
|
"msg_alarm": "1",
|
||||||
|
"msg_alarm_list": [
|
||||||
|
"sound",
|
||||||
|
"light"
|
||||||
|
],
|
||||||
|
"msg_push": "1",
|
||||||
|
"multi_user": "0",
|
||||||
|
"multicast": "0",
|
||||||
|
"network": [
|
||||||
|
"wifi"
|
||||||
|
],
|
||||||
|
"osd_capability": "1",
|
||||||
|
"ota_upgrade": "1",
|
||||||
|
"p2p_support_versions": [
|
||||||
|
"1.1"
|
||||||
|
],
|
||||||
|
"personalized_audio_alarm": "0",
|
||||||
|
"playback": [
|
||||||
|
"local",
|
||||||
|
"p2p",
|
||||||
|
"relay"
|
||||||
|
],
|
||||||
|
"playback_scale": "1",
|
||||||
|
"preview": [
|
||||||
|
"local",
|
||||||
|
"p2p",
|
||||||
|
"relay"
|
||||||
|
],
|
||||||
|
"privacy_mask_api_version": "1.0",
|
||||||
|
"ptz": "1",
|
||||||
|
"record_max_slot_cnt": "10",
|
||||||
|
"record_type": [
|
||||||
|
"timing",
|
||||||
|
"motion"
|
||||||
|
],
|
||||||
|
"relay_support_versions": [
|
||||||
|
"1.3"
|
||||||
|
],
|
||||||
|
"remote_upgrade": "1",
|
||||||
|
"reonboarding": "1",
|
||||||
|
"smart_codec": "0",
|
||||||
|
"smart_detection": "1",
|
||||||
|
"smart_msg_push_capability": "1",
|
||||||
|
"ssl_cer_version": "1.0",
|
||||||
|
"storage_api_version": "2.2",
|
||||||
|
"storage_capability": "1",
|
||||||
|
"stream_max_sessions": "10",
|
||||||
|
"streaming_support_versions": [
|
||||||
|
"1.0"
|
||||||
|
],
|
||||||
|
"tapo_care_version": "1.0.0",
|
||||||
|
"target_track": "1",
|
||||||
|
"timing_reboot": "1",
|
||||||
|
"verification_change_password": "1",
|
||||||
|
"video_codec": [
|
||||||
|
"h264"
|
||||||
|
],
|
||||||
|
"video_detection_digital_sensitivity": "1",
|
||||||
|
"wide_range_inf_sensitivity": "1",
|
||||||
|
"wifi_cascade_connection": "1",
|
||||||
|
"wifi_connection_info": "1",
|
||||||
|
"wireless_hotspot": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,6 +210,22 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"getTimezone": {
|
||||||
|
"system": {
|
||||||
|
"basic": {
|
||||||
|
"zone_id": "Australia/Canberra",
|
||||||
|
"timezone": "UTC+10:00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"getClockStatus": {
|
||||||
|
"system": {
|
||||||
|
"clock_status": {
|
||||||
|
"seconds_from_1970": 1729509322,
|
||||||
|
"local_time": "2024-10-21 22:15:22"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"getFirmwareAutoUpgradeConfig": {
|
"getFirmwareAutoUpgradeConfig": {
|
||||||
"auto_upgrade": {
|
"auto_upgrade": {
|
||||||
"common": {
|
"common": {
|
||||||
|
Loading…
Reference in New Issue
Block a user