Merge branch 'master' into feat/smartcam_passthrough

This commit is contained in:
Steven B. 2024-12-11 14:48:04 +00:00 committed by GitHub
commit 9f10cd10cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
155 changed files with 2092 additions and 1491 deletions

View File

@ -283,9 +283,12 @@ git rebase upstream/master
git checkout -b janitor/merge_patch git checkout -b janitor/merge_patch
git fetch upstream patch git fetch upstream patch
git merge upstream/patch --no-commit git merge upstream/patch --no-commit
# If there are any merge conflicts run the following command which will simply make master win
# Do not run it if there are no conflicts as it will end up checking out upstream/master
git diff --name-only --diff-filter=U | xargs git checkout upstream/master git diff --name-only --diff-filter=U | xargs git checkout upstream/master
# Check the diff is as expected
git diff --staged git diff --staged
# The only diff should be the version in pyproject.toml and CHANGELOG.md # The only diff should be the version in pyproject.toml and uv.lock, and CHANGELOG.md
# unless a change made on patch that was not part of a cherry-pick commit # unless a change made on patch that was not part of a cherry-pick commit
# If there are any other unexpected diffs `git checkout upstream/master [thefilename]` # If there are any other unexpected diffs `git checkout upstream/master [thefilename]`
git commit -m "Merge patch into local master" -S git commit -m "Merge patch into local master" -S

View File

@ -10,8 +10,6 @@ and finally execute a query to query all of them at once.
from __future__ import annotations from __future__ import annotations
import base64
import collections.abc
import dataclasses import dataclasses
import json import json
import logging import logging
@ -19,6 +17,7 @@ import re
import sys import sys
import traceback import traceback
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from collections.abc import Callable
from pathlib import Path from pathlib import Path
from pprint import pprint from pprint import pprint
from typing import Any from typing import Any
@ -39,13 +38,20 @@ from kasa import (
) )
from kasa.device_factory import get_protocol from kasa.device_factory import get_protocol
from kasa.deviceconfig import DeviceEncryptionType, DeviceFamily from kasa.deviceconfig import DeviceEncryptionType, DeviceFamily
from kasa.discover import DiscoveryResult from kasa.discover import (
NEW_DISCOVERY_REDACTORS,
DiscoveredRaw,
DiscoveryResult,
)
from kasa.exceptions import SmartErrorCode from kasa.exceptions import SmartErrorCode
from kasa.protocols import IotProtocol from kasa.protocols import IotProtocol
from kasa.protocols.iotprotocol import REDACTORS as IOT_REDACTORS
from kasa.protocols.protocol import redact_data
from kasa.protocols.smartcamprotocol import ( from kasa.protocols.smartcamprotocol import (
SmartCamProtocol, SmartCamProtocol,
_ChildCameraProtocolWrapper, _ChildCameraProtocolWrapper,
) )
from kasa.protocols.smartprotocol import REDACTORS as SMART_REDACTORS
from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from kasa.smart import SmartChildDevice, SmartDevice from kasa.smart import SmartChildDevice, SmartDevice
from kasa.smartcam import SmartCamDevice from kasa.smartcam import SmartCamDevice
@ -63,6 +69,42 @@ ENCRYPT_TYPES = [encrypt_type.value for encrypt_type in DeviceEncryptionType]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _wrap_redactors(redactors: dict[str, Callable[[Any], Any] | None]):
"""Wrap the redactors for dump_devinfo.
Will replace all partial REDACT_ values with zeros.
If the data item is already scrubbed by dump_devinfo will leave as-is.
"""
def _wrap(key: str) -> Any:
def _wrapped(redactor: Callable[[Any], Any] | None) -> Any | None:
if redactor is None:
return lambda x: "**SCRUBBED**"
def _redact_to_zeros(x: Any) -> Any:
if isinstance(x, str) and "REDACT" in x:
return re.sub(r"\w", "0", x)
if isinstance(x, dict):
for k, v in x.items():
x[k] = _redact_to_zeros(v)
return x
def _scrub(x: Any) -> Any:
if key in {"ip", "local_ip"}:
return "127.0.0.123"
# Already scrubbed by dump_devinfo
if isinstance(x, str) and "SCRUBBED" in x:
return x
default = redactor(x)
return _redact_to_zeros(default)
return _scrub
return _wrapped(redactors[key])
return {key: _wrap(key) for key in redactors}
@dataclasses.dataclass @dataclasses.dataclass
class SmartCall: class SmartCall:
"""Class for smart and smartcam calls.""" """Class for smart and smartcam calls."""
@ -74,115 +116,6 @@ class SmartCall:
supports_multiple: bool = True supports_multiple: bool = True
def scrub(res):
"""Remove identifiers from the given dict."""
keys_to_scrub = [
"deviceId",
"fwId",
"hwId",
"oemId",
"mac",
"mic_mac",
"latitude_i",
"longitude_i",
"latitude",
"longitude",
"la", # lat on ks240
"lo", # lon on ks240
"owner",
"device_id",
"ip",
"ssid",
"hw_id",
"fw_id",
"oem_id",
"nickname",
"alias",
"bssid",
"channel",
"original_device_id", # for child devices on strips
"parent_device_id", # for hub children
"setup_code", # matter
"setup_payload", # matter
"mfi_setup_code", # mfi_ for homekit
"mfi_setup_id",
"mfi_token_token",
"mfi_token_uuid",
"dev_id",
"device_name",
"device_alias",
"connect_ssid",
"encrypt_info",
"local_ip",
"username",
# vacuum
"board_sn",
"custom_sn",
"location",
]
for k, v in res.items():
if isinstance(v, collections.abc.Mapping):
if k == "encrypt_info":
if "data" in v:
v["data"] = ""
if "key" in v:
v["key"] = ""
else:
res[k] = scrub(res.get(k))
elif (
isinstance(v, list)
and len(v) > 0
and isinstance(v[0], collections.abc.Mapping)
):
res[k] = [scrub(vi) for vi in v]
else:
if k in keys_to_scrub:
if k in ["mac", "mic_mac"]:
# Some macs have : or - as a separator and others do not
if len(v) == 12:
v = f"{v[:6]}000000"
else:
delim = ":" if ":" in v else "-"
rest = delim.join(
format(s, "02x") for s in bytes.fromhex("000000")
)
v = f"{v[:8]}{delim}{rest}"
elif k in ["latitude", "latitude_i", "longitude", "longitude_i"]:
v = 0
elif k in ["ip", "local_ip"]:
v = "127.0.0.123"
elif k in ["ssid"]:
# Need a valid base64 value here
v = base64.b64encode(b"#MASKED_SSID#").decode()
elif k in ["nickname"]:
v = base64.b64encode(b"#MASKED_NAME#").decode()
elif k in [
"alias",
"device_alias",
"device_name",
"username",
"location",
]:
v = "#MASKED_NAME#"
elif isinstance(res[k], int):
v = 0
elif k in ["map_data"]: #
v = "#SCRUBBED_MAPDATA#"
elif k in ["device_id", "dev_id"] and "SCRUBBED" in v:
pass # already scrubbed
elif k == ["device_id", "dev_id"] and len(v) > 40:
# retain the last two chars when scrubbing child ids
end = v[-2:]
v = re.sub(r"\w", "0", v)
v = v[:40] + end
else:
v = re.sub(r"\w", "0", v)
res[k] = v
return res
def default_to_regular(d): def default_to_regular(d):
"""Convert nested defaultdicts to regular ones. """Convert nested defaultdicts to regular ones.
@ -209,7 +142,7 @@ async def handle_device(
for fixture_result in fixture_results: for fixture_result in fixture_results:
save_filename = Path(basedir) / fixture_result.folder / fixture_result.filename save_filename = Path(basedir) / fixture_result.folder / fixture_result.filename
pprint(scrub(fixture_result.data)) pprint(fixture_result.data)
if autosave: if autosave:
save = "y" save = "y"
else: else:
@ -325,6 +258,11 @@ async def cli(
if debug: if debug:
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
raw_discovery = {}
def capture_raw(discovered: DiscoveredRaw):
raw_discovery[discovered["meta"]["ip"]] = discovered["discovery_response"]
credentials = Credentials(username=username, password=password) credentials = Credentials(username=username, password=password)
if host is not None: if host is not None:
if discovery_info: if discovery_info:
@ -377,12 +315,16 @@ async def cli(
credentials=credentials, credentials=credentials,
port=port, port=port,
discovery_timeout=discovery_timeout, discovery_timeout=discovery_timeout,
on_discovered_raw=capture_raw,
) )
discovery_info = raw_discovery[device.host]
if decrypted_data := device._discovery_info.get("decrypted_data"):
discovery_info["decrypted_data"] = decrypted_data
await handle_device( await handle_device(
basedir, basedir,
autosave, autosave,
device.protocol, device.protocol,
discovery_info=device._discovery_info, discovery_info=discovery_info,
batch_size=batch_size, batch_size=batch_size,
) )
else: else:
@ -391,21 +333,28 @@ async def cli(
f" {target}. Use --target to override." f" {target}. Use --target to override."
) )
devices = await Discover.discover( devices = await Discover.discover(
target=target, credentials=credentials, discovery_timeout=discovery_timeout target=target,
credentials=credentials,
discovery_timeout=discovery_timeout,
on_discovered_raw=capture_raw,
) )
click.echo(f"Detected {len(devices)} devices") click.echo(f"Detected {len(devices)} devices")
for dev in devices.values(): for dev in devices.values():
discovery_info = raw_discovery[dev.host]
if decrypted_data := dev._discovery_info.get("decrypted_data"):
discovery_info["decrypted_data"] = decrypted_data
await handle_device( await handle_device(
basedir, basedir,
autosave, autosave,
dev.protocol, dev.protocol,
discovery_info=dev._discovery_info, discovery_info=discovery_info,
batch_size=batch_size, batch_size=batch_size,
) )
async def get_legacy_fixture( async def get_legacy_fixture(
protocol: IotProtocol, *, discovery_info: dict[str, Any] | None protocol: IotProtocol, *, discovery_info: dict[str, dict[str, Any]] | None
) -> FixtureResult: ) -> FixtureResult:
"""Get fixture for legacy IOT style protocol.""" """Get fixture for legacy IOT style protocol."""
items = [ items = [
@ -475,11 +424,21 @@ async def get_legacy_fixture(
_echo_error(f"Unable to query all successes at once: {ex}") _echo_error(f"Unable to query all successes at once: {ex}")
finally: finally:
await protocol.close() await protocol.close()
final = redact_data(final, _wrap_redactors(IOT_REDACTORS))
# Scrub the child device ids
if children := final.get("system", {}).get("get_sysinfo", {}).get("children"):
for index, child in enumerate(children):
if "id" not in child:
_LOGGER.error("Could not find a device for the child device: %s", child)
else:
child["id"] = f"SCRUBBED_CHILD_DEVICE_ID_{index + 1}"
if discovery_info and not discovery_info.get("system"): if discovery_info and not discovery_info.get("system"):
# Need to recreate a DiscoverResult here because we don't want the aliases final["discovery_result"] = redact_data(
# in the fixture, we want the actual field names as returned by the device. discovery_info, _wrap_redactors(NEW_DISCOVERY_REDACTORS)
dr = DiscoveryResult.from_dict(discovery_info) )
final["discovery_result"] = dr.to_dict()
click.echo(f"Got {len(successes)} successes") click.echo(f"Got {len(successes)} successes")
click.echo(click.style("## device info file ##", bold=True)) click.echo(click.style("## device info file ##", bold=True))
@ -867,7 +826,10 @@ def get_smart_child_fixture(response):
async def get_smart_fixtures( async def get_smart_fixtures(
protocol: SmartProtocol, *, discovery_info: dict[str, Any] | None, batch_size: int protocol: SmartProtocol,
*,
discovery_info: dict[str, dict[str, Any]] | None,
batch_size: int,
) -> list[FixtureResult]: ) -> list[FixtureResult]:
"""Get fixture for new TAPO style protocol.""" """Get fixture for new TAPO style protocol."""
if isinstance(protocol, SmartCamProtocol): if isinstance(protocol, SmartCamProtocol):
@ -988,22 +950,24 @@ async def get_smart_fixtures(
continue continue
_LOGGER.error("Could not find a device for the child device: %s", child) _LOGGER.error("Could not find a device for the child device: %s", child)
# Need to recreate a DiscoverResult here because we don't want the aliases final = redact_data(final, _wrap_redactors(SMART_REDACTORS))
# in the fixture, we want the actual field names as returned by the device. discovery_result = None
if discovery_info: if discovery_info:
dr = DiscoveryResult.from_dict(discovery_info) # type: ignore final["discovery_result"] = redact_data(
final["discovery_result"] = dr.to_dict() discovery_info, _wrap_redactors(NEW_DISCOVERY_REDACTORS)
)
discovery_result = discovery_info["result"]
click.echo(f"Got {len(successes)} successes") click.echo(f"Got {len(successes)} successes")
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 # smart protocol
model_info = SmartDevice._get_device_info(final, discovery_info) model_info = SmartDevice._get_device_info(final, discovery_result)
copy_folder = SMART_FOLDER copy_folder = SMART_FOLDER
else: else:
# smart camera protocol # smart camera protocol
model_info = SmartCamDevice._get_device_info(final, discovery_info) model_info = SmartCamDevice._get_device_info(final, discovery_result)
copy_folder = SMARTCAM_FOLDER copy_folder = SMARTCAM_FOLDER
hw_version = model_info.hardware_version hw_version = model_info.hardware_version
sw_version = model_info.firmware_version sw_version = model_info.firmware_version

View File

@ -205,7 +205,7 @@ def _get_supported_devices(
fixture_data = json.load(f) fixture_data = json.load(f)
model_info = device_cls._get_device_info( model_info = device_cls._get_device_info(
fixture_data, fixture_data.get("discovery_result") fixture_data, fixture_data.get("discovery_result", {}).get("result")
) )
supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[model_info.device_type] supported_type = DEVICE_TYPE_TO_PRODUCT_GROUP[model_info.device_type]

128
devtools/update_fixtures.py Normal file
View File

@ -0,0 +1,128 @@
"""Module to mass update fixture files."""
import json
import logging
from collections.abc import Callable
from pathlib import Path
import asyncclick as click
from devtools.dump_devinfo import _wrap_redactors
from kasa.discover import NEW_DISCOVERY_REDACTORS, redact_data
from kasa.protocols.iotprotocol import REDACTORS as IOT_REDACTORS
from kasa.protocols.smartprotocol import REDACTORS as SMART_REDACTORS
FIXTURE_FOLDER = "tests/fixtures/"
_LOGGER = logging.getLogger(__name__)
def update_fixtures(update_func: Callable[[dict], bool], *, dry_run: bool) -> None:
"""Run the update function against the fixtures."""
for file in Path(FIXTURE_FOLDER).glob("**/*.json"):
with file.open("r") as f:
fixture_data = json.load(f)
if file.parent.name == "serialization":
continue
changed = update_func(fixture_data)
if changed:
click.echo(f"Will update {file.name}\n")
if changed and not dry_run:
with file.open("w") as f:
json.dump(fixture_data, f, sort_keys=True, indent=4)
f.write("\n")
def _discovery_result_update(info) -> bool:
"""Update discovery_result to be the raw result and error_code."""
if (disco_result := info.get("discovery_result")) and "result" not in disco_result:
info["discovery_result"] = {
"result": disco_result,
"error_code": 0,
}
return True
return False
def _child_device_id_update(info) -> bool:
"""Update child device ids to be the scrubbed ids from dump_devinfo."""
changed = False
if get_child_device_list := info.get("get_child_device_list"):
child_device_list = get_child_device_list["child_device_list"]
child_component_list = info["get_child_device_component_list"][
"child_component_list"
]
for index, child_device in enumerate(child_device_list):
child_component = child_component_list[index]
if "SCRUBBED" not in child_device["device_id"]:
dev_id = f"SCRUBBED_CHILD_DEVICE_ID_{index + 1}"
click.echo(
f"child_device_id{index}: {child_device['device_id']} -> {dev_id}"
)
child_device["device_id"] = dev_id
child_component["device_id"] = dev_id
changed = True
if children := info.get("system", {}).get("get_sysinfo", {}).get("children"):
for index, child_device in enumerate(children):
if "SCRUBBED" not in child_device["id"]:
dev_id = f"SCRUBBED_CHILD_DEVICE_ID_{index + 1}"
click.echo(f"child_device_id{index}: {child_device['id']} -> {dev_id}")
child_device["id"] = dev_id
changed = True
return changed
def _diff_data(fullkey, data1, data2, diffs):
if isinstance(data1, dict):
for k, v in data1.items():
_diff_data(fullkey + "/" + k, v, data2[k], diffs)
elif isinstance(data1, list):
for index, item in enumerate(data1):
_diff_data(fullkey + "/" + str(index), item, data2[index], diffs)
elif data1 != data2:
diffs[fullkey] = (data1, data2)
def _redactor_result_update(info) -> bool:
"""Update fixtures with the output using the common redactors."""
changed = False
redactors = IOT_REDACTORS if "system" in info else SMART_REDACTORS
for key, val in info.items():
if not isinstance(val, dict):
continue
if key == "discovery_result":
info[key] = redact_data(val, _wrap_redactors(NEW_DISCOVERY_REDACTORS))
else:
info[key] = redact_data(val, _wrap_redactors(redactors))
diffs: dict[str, tuple[str, str]] = {}
_diff_data(key, val, info[key], diffs)
if diffs:
for k, v in diffs.items():
click.echo(f"{k}: {v[0]} -> {v[1]}")
changed = True
return changed
@click.option(
"--dry-run/--no-dry-run",
default=False,
is_flag=True,
type=bool,
help="Perform a dry run without saving.",
)
@click.command()
async def cli(dry_run: bool) -> None:
"""Cli method fo rupdating fixtures."""
update_fixtures(_discovery_result_update, dry_run=dry_run)
update_fixtures(_child_device_id_update, dry_run=dry_run)
update_fixtures(_redactor_result_update, dry_run=dry_run)
if __name__ == "__main__":
cli()

View File

@ -91,5 +91,5 @@ False
True True
>>> for feat in dev.features.values(): >>> for feat in dev.features.values():
>>> print(f"{feat.name}: {feat.value}") >>> print(f"{feat.name}: {feat.value}")
Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nOverheated: False\nReboot: <Action>\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: None\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: None\nCheck latest firmware: <Action>\nLight effect: Party\nLight preset: Light preset 1\nSmooth transition on: 2\nSmooth transition off: 2\nDevice time: 2024-02-23 02:40:15+01:00 Device ID: 0000000000000000000000000000000000000000\nState: True\nSignal Level: 2\nRSSI: -52\nSSID: #MASKED_SSID#\nReboot: <Action>\nBrightness: 50\nCloud connection: True\nHSV: HSV(hue=0, saturation=100, value=50)\nColor temperature: 2700\nAuto update enabled: True\nUpdate available: None\nCurrent firmware version: 1.1.6 Build 240130 Rel.173828\nAvailable firmware version: None\nCheck latest firmware: <Action>\nLight effect: Party\nLight preset: Light preset 1\nSmooth transition on: 2\nSmooth transition off: 2\nOverheated: False\nDevice time: 2024-02-23 02:40:15+01:00
""" """

View File

@ -14,9 +14,17 @@ from kasa import (
Discover, Discover,
UnsupportedDeviceError, UnsupportedDeviceError,
) )
from kasa.discover import ConnectAttempt, DiscoveryResult from kasa.discover import (
NEW_DISCOVERY_REDACTORS,
ConnectAttempt,
DiscoveredRaw,
DiscoveryResult,
)
from kasa.iot.iotdevice import _extract_sys_info from kasa.iot.iotdevice import _extract_sys_info
from kasa.protocols.iotprotocol import REDACTORS as IOT_REDACTORS
from kasa.protocols.protocol import redact_data
from ..json import dumps as json_dumps
from .common import echo, error from .common import echo, error
@ -64,7 +72,9 @@ async def detail(ctx):
await ctx.parent.invoke(state) await ctx.parent.invoke(state)
echo() echo()
discovered = await _discover(ctx, print_discovered, print_unsupported) discovered = await _discover(
ctx, print_discovered=print_discovered, print_unsupported=print_unsupported
)
if ctx.parent.parent.params["host"]: if ctx.parent.parent.params["host"]:
return discovered return discovered
@ -77,6 +87,33 @@ async def detail(ctx):
return discovered return discovered
@discover.command()
@click.option(
"--redact/--no-redact",
default=False,
is_flag=True,
type=bool,
help="Set flag to redact sensitive data from raw output.",
)
@click.pass_context
async def raw(ctx, redact: bool):
"""Return raw discovery data returned from devices."""
def print_raw(discovered: DiscoveredRaw):
if redact:
redactors = (
NEW_DISCOVERY_REDACTORS
if discovered["meta"]["port"] == Discover.DISCOVERY_PORT_2
else IOT_REDACTORS
)
discovered["discovery_response"] = redact_data(
discovered["discovery_response"], redactors
)
echo(json_dumps(discovered, indent=True))
return await _discover(ctx, print_raw=print_raw, do_echo=False)
@discover.command() @discover.command()
@click.pass_context @click.pass_context
async def list(ctx): async def list(ctx):
@ -102,10 +139,17 @@ async def list(ctx):
echo(f"{host:<15} UNSUPPORTED DEVICE") echo(f"{host:<15} UNSUPPORTED DEVICE")
echo(f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}") echo(f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}")
return await _discover(ctx, print_discovered, print_unsupported, do_echo=False) return await _discover(
ctx,
print_discovered=print_discovered,
print_unsupported=print_unsupported,
do_echo=False,
)
async def _discover(ctx, print_discovered, print_unsupported, *, do_echo=True): async def _discover(
ctx, *, print_discovered=None, print_unsupported=None, print_raw=None, do_echo=True
):
params = ctx.parent.parent.params params = ctx.parent.parent.params
target = params["target"] target = params["target"]
username = params["username"] username = params["username"]
@ -126,6 +170,7 @@ async def _discover(ctx, print_discovered, print_unsupported, *, do_echo=True):
timeout=timeout, timeout=timeout,
discovery_timeout=discovery_timeout, discovery_timeout=discovery_timeout,
on_unsupported=print_unsupported, on_unsupported=print_unsupported,
on_discovered_raw=print_raw,
) )
if do_echo: if do_echo:
echo(f"Discovering devices on {target} for {discovery_timeout} seconds") echo(f"Discovering devices on {target} for {discovery_timeout} seconds")
@ -137,6 +182,7 @@ async def _discover(ctx, print_discovered, print_unsupported, *, do_echo=True):
port=port, port=port,
timeout=timeout, timeout=timeout,
credentials=credentials, credentials=credentials,
on_discovered_raw=print_raw,
) )
for device in discovered_devices.values(): for device in discovered_devices.values():

View File

@ -99,6 +99,7 @@ from typing import (
Annotated, Annotated,
Any, Any,
NamedTuple, NamedTuple,
TypedDict,
cast, cast,
) )
@ -147,18 +148,43 @@ class ConnectAttempt(NamedTuple):
device: type device: type
class DiscoveredMeta(TypedDict):
"""Meta info about discovery response."""
ip: str
port: int
class DiscoveredRaw(TypedDict):
"""Try to connect attempt."""
meta: DiscoveredMeta
discovery_response: dict
OnDiscoveredCallable = Callable[[Device], Coroutine] OnDiscoveredCallable = Callable[[Device], Coroutine]
OnDiscoveredRawCallable = Callable[[DiscoveredRaw], None]
OnUnsupportedCallable = Callable[[UnsupportedDeviceError], Coroutine] OnUnsupportedCallable = Callable[[UnsupportedDeviceError], Coroutine]
OnConnectAttemptCallable = Callable[[ConnectAttempt, bool], None] OnConnectAttemptCallable = Callable[[ConnectAttempt, bool], None]
DeviceDict = dict[str, Device] DeviceDict = dict[str, Device]
DECRYPTED_REDACTORS: dict[str, Callable[[Any], Any] | None] = {
"connect_ssid": lambda x: "#MASKED_SSID#" if x else "",
"device_id": lambda x: "REDACTED_" + x[9::],
"owner": lambda x: "REDACTED_" + x[9::],
}
NEW_DISCOVERY_REDACTORS: dict[str, Callable[[Any], Any] | None] = { NEW_DISCOVERY_REDACTORS: dict[str, Callable[[Any], Any] | None] = {
"device_id": lambda x: "REDACTED_" + x[9::], "device_id": lambda x: "REDACTED_" + x[9::],
"device_name": lambda x: "#MASKED_NAME#" if x else "",
"owner": lambda x: "REDACTED_" + x[9::], "owner": lambda x: "REDACTED_" + x[9::],
"mac": mask_mac, "mac": mask_mac,
"master_device_id": lambda x: "REDACTED_" + x[9::], "master_device_id": lambda x: "REDACTED_" + x[9::],
"group_id": lambda x: "REDACTED_" + x[9::], "group_id": lambda x: "REDACTED_" + x[9::],
"group_name": lambda x: "I01BU0tFRF9TU0lEIw==", "group_name": lambda x: "I01BU0tFRF9TU0lEIw==",
"encrypt_info": lambda x: {**x, "key": "", "data": ""},
"ip": lambda x: x, # don't redact but keep listed here for dump_devinfo
"decrypted_data": lambda x: redact_data(x, DECRYPTED_REDACTORS),
} }
@ -216,6 +242,7 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
self, self,
*, *,
on_discovered: OnDiscoveredCallable | None = None, on_discovered: OnDiscoveredCallable | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
target: str = "255.255.255.255", target: str = "255.255.255.255",
discovery_packets: int = 3, discovery_packets: int = 3,
discovery_timeout: int = 5, discovery_timeout: int = 5,
@ -240,6 +267,7 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
self.unsupported_device_exceptions: dict = {} self.unsupported_device_exceptions: dict = {}
self.invalid_device_exceptions: dict = {} self.invalid_device_exceptions: dict = {}
self.on_unsupported = on_unsupported self.on_unsupported = on_unsupported
self.on_discovered_raw = on_discovered_raw
self.credentials = credentials self.credentials = credentials
self.timeout = timeout self.timeout = timeout
self.discovery_timeout = discovery_timeout self.discovery_timeout = discovery_timeout
@ -329,12 +357,23 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
config.timeout = self.timeout config.timeout = self.timeout
try: try:
if port == self.discovery_port: if port == self.discovery_port:
device = Discover._get_device_instance_legacy(data, config) json_func = Discover._get_discovery_json_legacy
device_func = Discover._get_device_instance_legacy
elif port == Discover.DISCOVERY_PORT_2: elif port == Discover.DISCOVERY_PORT_2:
config.uses_http = True config.uses_http = True
device = Discover._get_device_instance(data, config) json_func = Discover._get_discovery_json
device_func = Discover._get_device_instance
else: else:
return return
info = json_func(data, ip)
if self.on_discovered_raw is not None:
self.on_discovered_raw(
{
"discovery_response": info,
"meta": {"ip": ip, "port": port},
}
)
device = device_func(info, config)
except UnsupportedDeviceError as udex: except UnsupportedDeviceError as udex:
_LOGGER.debug("Unsupported device found at %s << %s", ip, udex) _LOGGER.debug("Unsupported device found at %s << %s", ip, udex)
self.unsupported_device_exceptions[ip] = udex self.unsupported_device_exceptions[ip] = udex
@ -391,6 +430,7 @@ class Discover:
*, *,
target: str = "255.255.255.255", target: str = "255.255.255.255",
on_discovered: OnDiscoveredCallable | None = None, on_discovered: OnDiscoveredCallable | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
discovery_timeout: int = 5, discovery_timeout: int = 5,
discovery_packets: int = 3, discovery_packets: int = 3,
interface: str | None = None, interface: str | None = None,
@ -421,6 +461,8 @@ class Discover:
:param target: The target address where to send the broadcast discovery :param target: The target address where to send the broadcast discovery
queries if multi-homing (e.g. 192.168.xxx.255). queries if multi-homing (e.g. 192.168.xxx.255).
:param on_discovered: coroutine to execute on discovery :param on_discovered: coroutine to execute on discovery
:param on_discovered_raw: Optional callback once discovered json is loaded
before any attempt to deserialize it and create devices
:param discovery_timeout: Seconds to wait for responses, defaults to 5 :param discovery_timeout: Seconds to wait for responses, defaults to 5
:param discovery_packets: Number of discovery packets to broadcast :param discovery_packets: Number of discovery packets to broadcast
:param interface: Bind to specific interface :param interface: Bind to specific interface
@ -443,6 +485,7 @@ class Discover:
discovery_packets=discovery_packets, discovery_packets=discovery_packets,
interface=interface, interface=interface,
on_unsupported=on_unsupported, on_unsupported=on_unsupported,
on_discovered_raw=on_discovered_raw,
credentials=credentials, credentials=credentials,
timeout=timeout, timeout=timeout,
discovery_timeout=discovery_timeout, discovery_timeout=discovery_timeout,
@ -476,6 +519,7 @@ class Discover:
credentials: Credentials | None = None, credentials: Credentials | None = None,
username: str | None = None, username: str | None = None,
password: str | None = None, password: str | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
on_unsupported: OnUnsupportedCallable | None = None, on_unsupported: OnUnsupportedCallable | None = None,
) -> Device | None: ) -> Device | None:
"""Discover a single device by the given IP address. """Discover a single device by the given IP address.
@ -493,6 +537,9 @@ class Discover:
username and password are ignored if provided. username and password are ignored if provided.
:param username: Username for devices that require authentication :param username: Username for devices that require authentication
:param password: Password for devices that require authentication :param password: Password for devices that require authentication
:param on_discovered_raw: Optional callback once discovered json is loaded
before any attempt to deserialize it and create devices
:param on_unsupported: Optional callback when unsupported devices are discovered
:rtype: SmartDevice :rtype: SmartDevice
:return: Object for querying/controlling found device. :return: Object for querying/controlling found device.
""" """
@ -529,6 +576,7 @@ class Discover:
credentials=credentials, credentials=credentials,
timeout=timeout, timeout=timeout,
discovery_timeout=discovery_timeout, discovery_timeout=discovery_timeout,
on_discovered_raw=on_discovered_raw,
), ),
local_addr=("0.0.0.0", 0), # noqa: S104 local_addr=("0.0.0.0", 0), # noqa: S104
) )
@ -666,15 +714,19 @@ class Discover:
return get_device_class_from_sys_info(info) return get_device_class_from_sys_info(info)
@staticmethod @staticmethod
def _get_device_instance_legacy(data: bytes, config: DeviceConfig) -> IotDevice: def _get_discovery_json_legacy(data: bytes, ip: str) -> dict:
"""Get SmartDevice from legacy 9999 response.""" """Get discovery json from legacy 9999 response."""
try: try:
info = json_loads(XorEncryption.decrypt(data)) info = json_loads(XorEncryption.decrypt(data))
except Exception as ex: except Exception as ex:
raise KasaException( raise KasaException(
f"Unable to read response from device: {config.host}: {ex}" f"Unable to read response from device: {ip}: {ex}"
) from ex ) from ex
return info
@staticmethod
def _get_device_instance_legacy(info: dict, config: DeviceConfig) -> Device:
"""Get IotDevice from legacy 9999 response."""
if _LOGGER.isEnabledFor(logging.DEBUG): if _LOGGER.isEnabledFor(logging.DEBUG):
data = redact_data(info, IOT_REDACTORS) if Discover._redact_data else info data = redact_data(info, IOT_REDACTORS) if Discover._redact_data else info
_LOGGER.debug("[DISCOVERY] %s << %s", config.host, pf(data)) _LOGGER.debug("[DISCOVERY] %s << %s", config.host, pf(data))
@ -698,6 +750,7 @@ class Discover:
@staticmethod @staticmethod
def _decrypt_discovery_data(discovery_result: DiscoveryResult) -> None: def _decrypt_discovery_data(discovery_result: DiscoveryResult) -> None:
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
if TYPE_CHECKING: if TYPE_CHECKING:
assert discovery_result.encrypt_info assert discovery_result.encrypt_info
assert _AesDiscoveryQuery.keypair assert _AesDiscoveryQuery.keypair
@ -713,22 +766,39 @@ class Discover:
session = AesEncyptionSession(key, iv) session = AesEncyptionSession(key, iv)
decrypted_data = session.decrypt(encrypted_data) decrypted_data = session.decrypt(encrypted_data)
discovery_result.decrypted_data = json_loads(decrypted_data) result = json_loads(decrypted_data)
if debug_enabled:
data = (
redact_data(result, DECRYPTED_REDACTORS)
if Discover._redact_data
else result
)
_LOGGER.debug(
"Decrypted encrypt_info for %s: %s",
discovery_result.ip,
pf(data),
)
discovery_result.decrypted_data = result
@staticmethod
def _get_discovery_json(data: bytes, ip: str) -> dict:
"""Get discovery json from the new 20002 response."""
try:
info = json_loads(data[16:])
except Exception as ex:
_LOGGER.debug("Got invalid response from device %s: %s", ip, data)
raise KasaException(
f"Unable to read response from device: {ip}: {ex}"
) from ex
return info
@staticmethod @staticmethod
def _get_device_instance( def _get_device_instance(
data: bytes, info: dict,
config: DeviceConfig, config: DeviceConfig,
) -> Device: ) -> Device:
"""Get SmartDevice from the new 20002 response.""" """Get SmartDevice from the new 20002 response."""
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG) debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
try:
info = json_loads(data[16:])
except Exception as ex:
_LOGGER.debug("Got invalid response from device %s: %s", config.host, data)
raise KasaException(
f"Unable to read response from device: {config.host}: {ex}"
) from ex
try: try:
discovery_result = DiscoveryResult.from_dict(info["result"]) discovery_result = DiscoveryResult.from_dict(info["result"])
@ -757,7 +827,9 @@ class Discover:
Discover._decrypt_discovery_data(discovery_result) Discover._decrypt_discovery_data(discovery_result)
except Exception: except Exception:
_LOGGER.exception( _LOGGER.exception(
"Unable to decrypt discovery data %s: %s", config.host, data "Unable to decrypt discovery data %s: %s",
config.host,
redact_data(info, NEW_DISCOVERY_REDACTORS),
) )
type_ = discovery_result.device_type type_ = discovery_result.device_type

View File

@ -24,7 +24,6 @@ State (state): True
Signal Level (signal_level): 2 Signal Level (signal_level): 2
RSSI (rssi): -52 RSSI (rssi): -52
SSID (ssid): #MASKED_SSID# SSID (ssid): #MASKED_SSID#
Overheated (overheated): False
Reboot (reboot): <Action> Reboot (reboot): <Action>
Brightness (brightness): 100 Brightness (brightness): 100
Cloud connection (cloud_connection): True Cloud connection (cloud_connection): True
@ -39,6 +38,7 @@ Light effect (light_effect): Off
Light preset (light_preset): Not set Light preset (light_preset): Not set
Smooth transition on (smooth_transition_on): 2 Smooth transition on (smooth_transition_on): 2
Smooth transition off (smooth_transition_off): 2 Smooth transition off (smooth_transition_off): 2
Overheated (overheated): False
Device time (device_time): 2024-02-23 02:40:15+01:00 Device time (device_time): 2024-02-23 02:40:15+01:00
To see whether a device supports a feature, check for the existence of it: To see whether a device supports a feature, check for the existence of it:

View File

@ -8,18 +8,24 @@ from typing import Any
try: try:
import orjson import orjson
def dumps(obj: Any, *, default: Callable | None = None) -> str: def dumps(
obj: Any, *, default: Callable | None = None, indent: bool = False
) -> str:
"""Dump JSON.""" """Dump JSON."""
return orjson.dumps(obj).decode() return orjson.dumps(
obj, option=orjson.OPT_INDENT_2 if indent else None
).decode()
loads = orjson.loads loads = orjson.loads
except ImportError: except ImportError:
import json import json
def dumps(obj: Any, *, default: Callable | None = None) -> str: def dumps(
obj: Any, *, default: Callable | None = None, indent: bool = False
) -> str:
"""Dump JSON.""" """Dump JSON."""
# Separators specified for consistency with orjson # Separators specified for consistency with orjson
return json.dumps(obj, separators=(",", ":")) return json.dumps(obj, separators=(",", ":"), indent=2 if indent else None)
loads = json.loads loads = json.loads

View File

@ -25,19 +25,35 @@ if TYPE_CHECKING:
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def _mask_children(children: list[dict[str, Any]]) -> list[dict[str, Any]]:
def mask_child(child: dict[str, Any], index: int) -> dict[str, Any]:
result = {
**child,
"id": f"SCRUBBED_CHILD_DEVICE_ID_{index+1}",
}
# Will leave empty aliases as blank
if child.get("alias"):
result["alias"] = f"#MASKED_NAME# {index + 1}"
return result
return [mask_child(child, index) for index, child in enumerate(children)]
REDACTORS: dict[str, Callable[[Any], Any] | None] = { REDACTORS: dict[str, Callable[[Any], Any] | None] = {
"latitude": lambda x: 0, "latitude": lambda x: 0,
"longitude": lambda x: 0, "longitude": lambda x: 0,
"latitude_i": lambda x: 0, "latitude_i": lambda x: 0,
"longitude_i": lambda x: 0, "longitude_i": lambda x: 0,
"deviceId": lambda x: "REDACTED_" + x[9::], "deviceId": lambda x: "REDACTED_" + x[9::],
"id": lambda x: "REDACTED_" + x[9::], "children": _mask_children,
"alias": lambda x: "#MASKED_NAME#" if x else "", "alias": lambda x: "#MASKED_NAME#" if x else "",
"mac": mask_mac, "mac": mask_mac,
"mic_mac": mask_mac, "mic_mac": mask_mac,
"ssid": lambda x: "#MASKED_SSID#" if x else "", "ssid": lambda x: "#MASKED_SSID#" if x else "",
"oemId": lambda x: "REDACTED_" + x[9::], "oemId": lambda x: "REDACTED_" + x[9::],
"username": lambda _: "user@example.com", # cnCloud "username": lambda _: "user@example.com", # cnCloud
"hwId": lambda x: "REDACTED_" + x[9::],
} }

View File

@ -66,6 +66,8 @@ def redact_data(data: _T, redactors: dict[str, Callable[[Any], Any] | None]) ->
def mask_mac(mac: str) -> str: def mask_mac(mac: str) -> str:
"""Return mac address with last two octects blanked.""" """Return mac address with last two octects blanked."""
if len(mac) == 12:
return f"{mac[:6]}000000"
delim = ":" if ":" in mac else "-" delim = ":" if ":" in mac else "-"
rest = delim.join(format(s, "02x") for s in bytes.fromhex("000000")) rest = delim.join(format(s, "02x") for s in bytes.fromhex("000000"))
return f"{mac[:8]}{delim}{rest}" return f"{mac[:8]}{delim}{rest}"

View File

@ -9,6 +9,7 @@ from __future__ import annotations
import asyncio import asyncio
import base64 import base64
import logging import logging
import re
import time import time
import uuid import uuid
from collections.abc import Callable from collections.abc import Callable
@ -45,15 +46,27 @@ REDACTORS: dict[str, Callable[[Any], Any] | None] = {
"original_device_id": lambda x: "REDACTED_" + x[9::], # Strip children "original_device_id": lambda x: "REDACTED_" + x[9::], # Strip children
"nickname": lambda x: "I01BU0tFRF9OQU1FIw==" if x else "", "nickname": lambda x: "I01BU0tFRF9OQU1FIw==" if x else "",
"mac": mask_mac, "mac": mask_mac,
"ssid": lambda x: "I01BU0tFRF9TU0lEIw=" if x else "", "ssid": lambda x: "I01BU0tFRF9TU0lEIw==" if x else "",
"bssid": lambda _: "000000000000", "bssid": lambda _: "000000000000",
"channel": lambda _: 0,
"oem_id": lambda x: "REDACTED_" + x[9::], "oem_id": lambda x: "REDACTED_" + x[9::],
"setup_code": None, # matter "setup_code": lambda x: re.sub(r"\w", "0", x), # matter
"setup_payload": None, # matter "setup_payload": lambda x: re.sub(r"\w", "0", x), # matter
"mfi_setup_code": None, # mfi_ for homekit "mfi_setup_code": lambda x: re.sub(r"\w", "0", x), # mfi_ for homekit
"mfi_setup_id": None, "mfi_setup_id": lambda x: re.sub(r"\w", "0", x),
"mfi_token_token": None, "mfi_token_token": lambda x: re.sub(r"\w", "0", x),
"mfi_token_uuid": None, "mfi_token_uuid": lambda x: re.sub(r"\w", "0", x),
"ip": lambda x: x, # don't redact but keep listed here for dump_devinfo
# smartcam
"dev_id": lambda x: "REDACTED_" + x[9::],
"device_name": lambda x: "#MASKED_NAME#" if x else "",
"device_alias": lambda x: "#MASKED_NAME#" if x else "",
"local_ip": lambda x: x, # don't redact but keep listed here for dump_devinfo
# robovac
"board_sn": lambda _: "000000000000",
"custom_sn": lambda _: "000000000000",
"location": lambda x: "#MASKED_NAME#" if x else "",
"map_data": lambda x: "#SCRUBBED_MAPDATA#" if x else "",
} }

View File

@ -24,6 +24,7 @@ from .lightpreset import LightPreset
from .lightstripeffect import LightStripEffect from .lightstripeffect import LightStripEffect
from .lighttransition import LightTransition from .lighttransition import LightTransition
from .motionsensor import MotionSensor from .motionsensor import MotionSensor
from .overheatprotection import OverheatProtection
from .reportmode import ReportMode from .reportmode import ReportMode
from .temperaturecontrol import TemperatureControl from .temperaturecontrol import TemperatureControl
from .temperaturesensor import TemperatureSensor from .temperaturesensor import TemperatureSensor
@ -64,4 +65,5 @@ __all__ = [
"FrostProtection", "FrostProtection",
"Thermostat", "Thermostat",
"SmartLightEffect", "SmartLightEffect",
"OverheatProtection",
] ]

View File

@ -10,7 +10,7 @@ class ContactSensor(SmartModule):
"""Implementation of contact sensor module.""" """Implementation of contact sensor module."""
REQUIRED_COMPONENT = None # we depend on availability of key REQUIRED_COMPONENT = None # we depend on availability of key
REQUIRED_KEY_ON_PARENT = "open" SYSINFO_LOOKUP_KEYS = ["open"]
def _initialize_features(self) -> None: def _initialize_features(self) -> None:
"""Initialize features after the initial update.""" """Initialize features after the initial update."""

View File

@ -0,0 +1,41 @@
"""Overheat module."""
from __future__ import annotations
from ...feature import Feature
from ..smartmodule import SmartModule
class OverheatProtection(SmartModule):
"""Implementation for overheat_protection."""
SYSINFO_LOOKUP_KEYS = ["overheated", "overheat_status"]
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
self._add_feature(
Feature(
self._device,
container=self,
id="overheated",
name="Overheated",
attribute_getter="overheated",
icon="mdi:heat-wave",
type=Feature.Type.BinarySensor,
category=Feature.Category.Info,
)
)
@property
def overheated(self) -> bool:
"""Return True if device reports overheating."""
if (value := self._device.sys_info.get("overheat_status")) is not None:
# Value can be normal, cooldown, or overheated.
# We report all but normal as overheated.
return value != "normal"
return self._device.sys_info["overheated"]
def query(self) -> dict:
"""Query to execute during the update cycle."""
return {}

View File

@ -9,7 +9,7 @@ from typing import Any
from ..device_type import DeviceType from ..device_type import DeviceType
from ..deviceconfig import DeviceConfig from ..deviceconfig import DeviceConfig
from ..protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper from ..protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from .smartdevice import SmartDevice from .smartdevice import ComponentsRaw, SmartDevice
from .smartmodule import SmartModule from .smartmodule import SmartModule
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -37,7 +37,7 @@ class SmartChildDevice(SmartDevice):
self, self,
parent: SmartDevice, parent: SmartDevice,
info: dict, info: dict,
component_info: dict, component_info_raw: ComponentsRaw,
*, *,
config: DeviceConfig | None = None, config: DeviceConfig | None = None,
protocol: SmartProtocol | None = None, protocol: SmartProtocol | None = None,
@ -47,7 +47,8 @@ class SmartChildDevice(SmartDevice):
super().__init__(parent.host, config=parent.config, protocol=_protocol) super().__init__(parent.host, config=parent.config, protocol=_protocol)
self._parent = parent self._parent = parent
self._update_internal_state(info) self._update_internal_state(info)
self._components = component_info self._components_raw = component_info_raw
self._components = self._parse_components(self._components_raw)
async def update(self, update_children: bool = True) -> None: async def update(self, update_children: bool = True) -> None:
"""Update child module info. """Update child module info.
@ -84,7 +85,7 @@ class SmartChildDevice(SmartDevice):
cls, cls,
parent: SmartDevice, parent: SmartDevice,
child_info: dict, child_info: dict,
child_components: dict, child_components_raw: ComponentsRaw,
protocol: SmartProtocol | None = None, protocol: SmartProtocol | None = None,
*, *,
last_update: dict | None = None, last_update: dict | None = None,
@ -97,7 +98,7 @@ class SmartChildDevice(SmartDevice):
derived from the parent. derived from the parent.
""" """
child: SmartChildDevice = cls( child: SmartChildDevice = cls(
parent, child_info, child_components, protocol=protocol parent, child_info, child_components_raw, protocol=protocol
) )
if last_update: if last_update:
child._last_update = last_update child._last_update = last_update

View File

@ -7,7 +7,7 @@ import logging
import time import time
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from datetime import UTC, datetime, timedelta, tzinfo from datetime import UTC, datetime, timedelta, tzinfo
from typing import TYPE_CHECKING, Any, cast from typing import TYPE_CHECKING, Any, TypeAlias, cast
from ..device import Device, WifiNetwork, _DeviceInfo from ..device import Device, WifiNetwork, _DeviceInfo
from ..device_type import DeviceType from ..device_type import DeviceType
@ -40,6 +40,8 @@ _LOGGER = logging.getLogger(__name__)
# same issue, homekit perhaps? # same issue, homekit perhaps?
NON_HUB_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud] NON_HUB_PARENT_ONLY_MODULES = [DeviceModule, Time, Firmware, Cloud]
ComponentsRaw: TypeAlias = dict[str, list[dict[str, int | str]]]
# Device must go last as the other interfaces also inherit Device # Device must go last as the other interfaces also inherit Device
# and python needs a consistent method resolution order. # and python needs a consistent method resolution order.
@ -61,7 +63,7 @@ class SmartDevice(Device):
) )
super().__init__(host=host, config=config, protocol=_protocol) super().__init__(host=host, config=config, protocol=_protocol)
self.protocol: SmartProtocol self.protocol: SmartProtocol
self._components_raw: dict[str, Any] | None = None self._components_raw: ComponentsRaw | None = None
self._components: dict[str, int] = {} self._components: dict[str, int] = {}
self._state_information: dict[str, Any] = {} self._state_information: dict[str, Any] = {}
self._modules: dict[str | ModuleName[Module], SmartModule] = {} self._modules: dict[str | ModuleName[Module], SmartModule] = {}
@ -82,10 +84,8 @@ class SmartDevice(Device):
self.internal_state.update(resp) self.internal_state.update(resp)
children = self.internal_state["get_child_device_list"]["child_device_list"] children = self.internal_state["get_child_device_list"]["child_device_list"]
children_components = { children_components_raw = {
child["device_id"]: { child["device_id"]: child
comp["id"]: int(comp["ver_code"]) for comp in child["component_list"]
}
for child in self.internal_state["get_child_device_component_list"][ for child in self.internal_state["get_child_device_component_list"][
"child_component_list" "child_component_list"
] ]
@ -96,7 +96,7 @@ class SmartDevice(Device):
child_info["device_id"]: await SmartChildDevice.create( child_info["device_id"]: await SmartChildDevice.create(
parent=self, parent=self,
child_info=child_info, child_info=child_info,
child_components=children_components[child_info["device_id"]], child_components_raw=children_components_raw[child_info["device_id"]],
) )
for child_info in children for child_info in children
} }
@ -131,6 +131,13 @@ class SmartDevice(Device):
f"{request} not found in {responses} for device {self.host}" f"{request} not found in {responses} for device {self.host}"
) )
@staticmethod
def _parse_components(components_raw: ComponentsRaw) -> dict[str, int]:
return {
str(comp["id"]): int(comp["ver_code"])
for comp in components_raw["component_list"]
}
async def _negotiate(self) -> None: async def _negotiate(self) -> None:
"""Perform initialization. """Perform initialization.
@ -151,12 +158,9 @@ class SmartDevice(Device):
self._info = self._try_get_response(resp, "get_device_info") self._info = self._try_get_response(resp, "get_device_info")
# Create our internal presentation of available components # Create our internal presentation of available components
self._components_raw = cast(dict, resp["component_nego"]) self._components_raw = cast(ComponentsRaw, resp["component_nego"])
self._components = { self._components = self._parse_components(self._components_raw)
comp["id"]: int(comp["ver_code"])
for comp in self._components_raw["component_list"]
}
if "child_device" in self._components and not self.children: if "child_device" in self._components and not self.children:
await self._initialize_children() await self._initialize_children()
@ -349,9 +353,8 @@ class SmartDevice(Device):
) or mod.__name__ in child_modules_to_skip: ) or mod.__name__ in child_modules_to_skip:
continue continue
required_component = cast(str, mod.REQUIRED_COMPONENT) required_component = cast(str, mod.REQUIRED_COMPONENT)
if required_component in self._components or ( if required_component in self._components or any(
mod.REQUIRED_KEY_ON_PARENT self.sys_info.get(key) is not None for key in mod.SYSINFO_LOOKUP_KEYS
and self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
): ):
_LOGGER.debug( _LOGGER.debug(
"Device %s, found required %s, adding %s to modules.", "Device %s, found required %s, adding %s to modules.",
@ -440,19 +443,6 @@ class SmartDevice(Device):
) )
) )
if "overheated" in self._info:
self._add_feature(
Feature(
self,
id="overheated",
name="Overheated",
attribute_getter=lambda x: x._info["overheated"],
icon="mdi:heat-wave",
type=Feature.Type.BinarySensor,
category=Feature.Category.Info,
)
)
# We check for the key available, and not for the property truthiness, # We check for the key available, and not for the property truthiness,
# as the value is falsy when the device is off. # as the value is falsy when the device is off.
if "on_time" in self._info: if "on_time" in self._info:

View File

@ -54,8 +54,8 @@ class SmartModule(Module):
NAME: str NAME: str
#: Module is initialized, if the given component is available #: Module is initialized, if the given component is available
REQUIRED_COMPONENT: str | None = None REQUIRED_COMPONENT: str | None = None
#: Module is initialized, if the given key available in the main sysinfo #: Module is initialized, if any of the given keys exists in the sysinfo
REQUIRED_KEY_ON_PARENT: str | None = None SYSINFO_LOOKUP_KEYS: list[str] = []
#: Query to execute during the main update cycle #: Query to execute during the main update cycle
QUERY_GETTER_NAME: str QUERY_GETTER_NAME: str

View File

@ -3,13 +3,14 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import Any from typing import Any, cast
from ..device import _DeviceInfo from ..device import _DeviceInfo
from ..device_type import DeviceType from ..device_type import DeviceType
from ..module import Module from ..module import Module
from ..protocols.smartcamprotocol import _ChildCameraProtocolWrapper from ..protocols.smartcamprotocol import _ChildCameraProtocolWrapper
from ..smart import SmartChildDevice, SmartDevice from ..smart import SmartChildDevice, SmartDevice
from ..smart.smartdevice import ComponentsRaw
from .modules import ChildDevice, DeviceModule from .modules import ChildDevice, DeviceModule
from .smartcammodule import SmartCamModule from .smartcammodule import SmartCamModule
@ -78,7 +79,7 @@ class SmartCamDevice(SmartDevice):
self._children[child_id]._update_internal_state(info) self._children[child_id]._update_internal_state(info)
async def _initialize_smart_child( async def _initialize_smart_child(
self, info: dict, child_components: dict self, info: dict, child_components_raw: ComponentsRaw
) -> SmartDevice: ) -> SmartDevice:
"""Initialize a smart child device attached to a smartcam device.""" """Initialize a smart child device attached to a smartcam device."""
child_id = info["device_id"] child_id = info["device_id"]
@ -93,7 +94,7 @@ class SmartCamDevice(SmartDevice):
return await SmartChildDevice.create( return await SmartChildDevice.create(
parent=self, parent=self,
child_info=info, child_info=info,
child_components=child_components, child_components_raw=child_components_raw,
protocol=child_protocol, protocol=child_protocol,
last_update=initial_response, last_update=initial_response,
) )
@ -108,17 +109,8 @@ class SmartCamDevice(SmartDevice):
self.internal_state.update(resp) self.internal_state.update(resp)
smart_children_components = { smart_children_components = {
child["device_id"]: { child["device_id"]: child
comp["id"]: int(comp["ver_code"]) for comp in component_list
}
for child in resp["getChildDeviceComponentList"]["child_component_list"] for child in resp["getChildDeviceComponentList"]["child_component_list"]
if (component_list := child.get("component_list"))
# Child camera devices will have a different component schema so only
# extract smart values.
and (first_comp := next(iter(component_list), None))
and isinstance(first_comp, dict)
and "id" in first_comp
and "ver_code" in first_comp
} }
children = {} children = {}
for info in resp["getChildDeviceList"]["child_device_list"]: for info in resp["getChildDeviceList"]["child_device_list"]:
@ -172,6 +164,13 @@ class SmartCamDevice(SmartDevice):
return res return res
@staticmethod
def _parse_components(components_raw: ComponentsRaw) -> dict[str, int]:
return {
str(comp["name"]): int(comp["version"])
for comp in components_raw["app_component_list"]
}
async def _negotiate(self) -> None: async def _negotiate(self) -> None:
"""Perform initialization. """Perform initialization.
@ -186,12 +185,10 @@ class SmartCamDevice(SmartDevice):
self._last_update.update(resp) self._last_update.update(resp)
self._update_internal_info(resp) self._update_internal_info(resp)
self._components = { self._components_raw = cast(
comp["name"]: int(comp["version"]) ComponentsRaw, resp["getAppComponentList"]["app_component"]
for comp in resp["getAppComponentList"]["app_component"][ )
"app_component_list" self._components = self._parse_components(self._components_raw)
]
}
if "childControl" in self._components and not self.children: if "childControl" in self._components and not self.children:
await self._initialize_children() await self._initialize_children()

View File

@ -435,7 +435,7 @@ async def get_device_for_fixture(
discovery_data = None discovery_data = None
if "discovery_result" in fixture_data.data: if "discovery_result" in fixture_data.data:
discovery_data = fixture_data.data["discovery_result"] discovery_data = fixture_data.data["discovery_result"]["result"]
elif "system" in fixture_data.data: elif "system" in fixture_data.data:
discovery_data = { discovery_data = {
"system": {"get_sysinfo": fixture_data.data["system"]["get_sysinfo"]} "system": {"get_sysinfo": fixture_data.data["system"]["get_sysinfo"]}

View File

@ -139,7 +139,8 @@ smart_discovery = parametrize_discovery("smart discovery", protocol_filter={"SMA
) )
async def discovery_mock(request, mocker): async def discovery_mock(request, mocker):
"""Mock discovery and patch protocol queries to use Fake protocols.""" """Mock discovery and patch protocol queries to use Fake protocols."""
fixture_info: FixtureInfo = request.param fi: FixtureInfo = request.param
fixture_info = FixtureInfo(fi.name, fi.protocol, copy.deepcopy(fi.data))
return patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker) return patch_discovery({DISCOVERY_MOCK_IP: fixture_info}, mocker)
@ -170,8 +171,8 @@ def create_discovery_mock(ip: str, fixture_data: dict):
) )
if "discovery_result" in fixture_data: if "discovery_result" in fixture_data:
discovery_data = {"result": fixture_data["discovery_result"].copy()} discovery_data = fixture_data["discovery_result"].copy()
discovery_result = fixture_data["discovery_result"] discovery_result = fixture_data["discovery_result"]["result"]
device_type = discovery_result["device_type"] device_type = discovery_result["device_type"]
encrypt_type = discovery_result["mgt_encrypt_schm"].get( encrypt_type = discovery_result["mgt_encrypt_schm"].get(
"encrypt_type", discovery_result.get("encrypt_info", {}).get("sym_schm") "encrypt_type", discovery_result.get("encrypt_info", {}).get("sym_schm")
@ -305,7 +306,7 @@ def discovery_data(request, mocker):
mocker.patch("kasa.IotProtocol.query", return_value=fixture_data) mocker.patch("kasa.IotProtocol.query", return_value=fixture_data)
mocker.patch("kasa.SmartProtocol.query", return_value=fixture_data) mocker.patch("kasa.SmartProtocol.query", return_value=fixture_data)
if "discovery_result" in fixture_data: if "discovery_result" in fixture_data:
return {"result": fixture_data["discovery_result"]} return fixture_data["discovery_result"].copy()
else: else:
return {"system": {"get_sysinfo": fixture_data["system"]["get_sysinfo"]}} return {"system": {"get_sysinfo": fixture_data["system"]["get_sysinfo"]}}

View File

@ -2,7 +2,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "167 lamp", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini", "dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -1,12 +1,12 @@
{ {
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Smart Plug_004F", "alias": "#MASKED_NAME#",
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "Zombie", "alias": "#MASKED_NAME# 1",
"id": "8006231E1499BAC4D4BC7EFCD4B075181E6393F200", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Magic", "alias": "#MASKED_NAME# 2",
"id": "8006231E1499BAC4D4BC7EFCD4B075181E6393F201", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -78,7 +78,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test ES20M", "alias": "#MASKED_NAME#",
"brightness": 35, "brightness": 35,
"dev_name": "Wi-Fi Smart Dimmer with sensor", "dev_name": "Wi-Fi Smart Dimmer with sensor",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",

View File

@ -1,18 +1,21 @@
{ {
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "HS100(UK)", "result": {
"device_type": "IOT.SMARTPLUGSWITCH", "device_id": "00000000000000000000000000000000",
"factory_default": true, "device_model": "HS100(UK)",
"hw_ver": "4.1", "device_type": "IOT.SMARTPLUGSWITCH",
"ip": "127.0.0.123", "factory_default": true,
"mac": "CC-32-E5-00-00-00", "hw_ver": "4.1",
"mgt_encrypt_schm": { "ip": "127.0.0.123",
"encrypt_type": "KLAP", "mac": "CC-32-E5-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false "encrypt_type": "KLAP",
}, "http_port": 80,
"owner": "00000000000000000000000000000000" "is_support_https": false
},
"owner": "00000000000000000000000000000000"
}
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {

View File

@ -18,7 +18,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "schedule", "active_mode": "schedule",
"alias": "Unused 3", "alias": "#MASKED_NAME#",
"dev_name": "Wi-Fi Smart Plug", "dev_name": "Wi-Fi Smart Plug",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "3D Printer", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug", "dev_name": "Smart Wi-Fi Plug",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "schedule", "active_mode": "schedule",
"alias": "Night lite", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Lite", "dev_name": "Smart Wi-Fi Plug Lite",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -18,7 +18,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Corner", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Lite", "dev_name": "Smart Wi-Fi Plug Lite",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -2,7 +2,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Plug", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Lite", "dev_name": "Smart Wi-Fi Plug Lite",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Unused 1", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini", "dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -17,12 +17,12 @@
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Smart Plug_D310", "alias": "#MASKED_NAME#",
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "Garage Charger 1", "alias": "#MASKED_NAME# 1",
"id": "00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -30,8 +30,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Garage Charger 2", "alias": "#MASKED_NAME# 2",
"id": "01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -46,7 +46,7 @@
"hw_ver": "1.0", "hw_ver": "1.0",
"latitude_i": 0, "latitude_i": 0,
"led_off": 0, "led_off": 0,
"longitude_i": -0, "longitude_i": 0,
"mac": "00:00:00:00:00:00", "mac": "00:00:00:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH", "mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS107(US)", "model": "HS107(US)",

View File

@ -11,7 +11,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "schedule", "active_mode": "schedule",
"alias": "Bedroom Lamp Plug", "alias": "#MASKED_NAME#",
"dev_name": "Wi-Fi Smart Plug With Energy Monitoring", "dev_name": "Wi-Fi Smart Plug With Energy Monitoring",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -11,7 +11,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "schedule", "active_mode": "schedule",
"alias": "Home Google WiFi HS110", "alias": "#MASKED_NAME#",
"dev_name": "Wi-Fi Smart Plug With Energy Monitoring", "dev_name": "Wi-Fi Smart Plug With Energy Monitoring",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Master Bedroom Fan", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Light Switch", "dev_name": "Smart Wi-Fi Light Switch",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -2,7 +2,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "House Fan", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Light Switch", "dev_name": "Smart Wi-Fi Light Switch",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -21,7 +21,7 @@
"get_sysinfo": { "get_sysinfo": {
"abnormal_detect": 1, "abnormal_detect": 1,
"active_mode": "none", "active_mode": "none",
"alias": "Garage Light", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi 3-Way Light Switch", "dev_name": "Smart Wi-Fi 3-Way Light Switch",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -28,7 +28,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Living Room Dimmer Switch", "alias": "#MASKED_NAME#",
"brightness": 25, "brightness": 25,
"dev_name": "Smart Wi-Fi Dimmer", "dev_name": "Smart Wi-Fi Dimmer",
"deviceId": "000000000000000000000000000000000000000", "deviceId": "000000000000000000000000000000000000000",
@ -38,9 +38,9 @@
"hwId": "00000000000000000000000000000000", "hwId": "00000000000000000000000000000000",
"hw_ver": "1.0", "hw_ver": "1.0",
"icon_hash": "", "icon_hash": "",
"latitude_i": 11.6210, "latitude_i": 0,
"led_off": 0, "led_off": 0,
"longitude_i": 42.2074, "longitude_i": 0,
"mac": "00:00:00:00:00:00", "mac": "00:00:00:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH", "mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS220(US)", "model": "HS220(US)",

View File

@ -17,7 +17,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Living Room Dimmer Switch", "alias": "#MASKED_NAME#",
"brightness": 100, "brightness": 100,
"dev_name": "Wi-Fi Smart Dimmer", "dev_name": "Wi-Fi Smart Dimmer",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",

View File

@ -22,12 +22,12 @@
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Power Strip_DAE1", "alias": "#MASKED_NAME#",
"child_num": 6, "child_num": 6,
"children": [ "children": [
{ {
"alias": "Office Monitor 1", "alias": "#MASKED_NAME# 1",
"id": "00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -35,8 +35,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Office Monitor 2", "alias": "#MASKED_NAME# 2",
"id": "01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -44,8 +44,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Office Monitor 3", "alias": "#MASKED_NAME# 3",
"id": "02", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -53,8 +53,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Office Laptop Dock", "alias": "#MASKED_NAME# 4",
"id": "03", "id": "SCRUBBED_CHILD_DEVICE_ID_4",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -62,8 +62,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Office Desk Light", "alias": "#MASKED_NAME# 5",
"id": "04", "id": "SCRUBBED_CHILD_DEVICE_ID_5",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -71,8 +71,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Laptop", "alias": "#MASKED_NAME# 6",
"id": "05", "id": "SCRUBBED_CHILD_DEVICE_ID_6",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -87,7 +87,7 @@
"hw_ver": "1.0", "hw_ver": "1.0",
"latitude_i": 0, "latitude_i": 0,
"led_off": 0, "led_off": 0,
"longitude_i": -0, "longitude_i": 0,
"mac": "00:00:00:00:00:00", "mac": "00:00:00:00:00:00",
"mic_type": "IOT.SMARTPLUGSWITCH", "mic_type": "IOT.SMARTPLUGSWITCH",
"model": "HS300(US)", "model": "HS300(US)",

View File

@ -10,12 +10,12 @@
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Power Strip_2CA9", "alias": "#MASKED_NAME#",
"child_num": 6, "child_num": 6,
"children": [ "children": [
{ {
"alias": "Home CameraPC", "alias": "#MASKED_NAME# 1",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -23,8 +23,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Home Firewalla", "alias": "#MASKED_NAME# 2",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -32,8 +32,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Home Cox modem", "alias": "#MASKED_NAME# 3",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED02", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -41,8 +41,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Home rpi3-2", "alias": "#MASKED_NAME# 4",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED03", "id": "SCRUBBED_CHILD_DEVICE_ID_4",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -50,8 +50,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Home Camera Switch", "alias": "#MASKED_NAME# 5",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED05", "id": "SCRUBBED_CHILD_DEVICE_ID_5",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -59,8 +59,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Home Network Switch", "alias": "#MASKED_NAME# 6",
"id": "800623145DFF1AA096363EFD161C2E661A9D8DED04", "id": "SCRUBBED_CHILD_DEVICE_ID_6",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -15,8 +15,8 @@
"child_num": 6, "child_num": 6,
"children": [ "children": [
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 1",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -24,8 +24,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 2",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -33,8 +33,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 3",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D02", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -42,8 +42,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 4",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D03", "id": "SCRUBBED_CHILD_DEVICE_ID_4",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -51,8 +51,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 5",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D04", "id": "SCRUBBED_CHILD_DEVICE_ID_5",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -60,8 +60,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 6",
"id": "8006A0F1D01120C3F93794F7AACACDBE1EAD246D05", "id": "SCRUBBED_CHILD_DEVICE_ID_6",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -11,12 +11,12 @@
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Power Strip_5C33", "alias": "#MASKED_NAME#",
"child_num": 6, "child_num": 6,
"children": [ "children": [
{ {
"alias": "Plug 1", "alias": "#MASKED_NAME# 1",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031900", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -24,8 +24,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 2", "alias": "#MASKED_NAME# 2",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031901", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -33,8 +33,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 3", "alias": "#MASKED_NAME# 3",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031902", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -42,8 +42,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 4", "alias": "#MASKED_NAME# 4",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031903", "id": "SCRUBBED_CHILD_DEVICE_ID_4",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -51,8 +51,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 5", "alias": "#MASKED_NAME# 5",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031904", "id": "SCRUBBED_CHILD_DEVICE_ID_5",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -60,8 +60,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 6", "alias": "#MASKED_NAME# 6",
"id": "8006AF35494E7DB13DDE9B8F40BF2E001E77031905", "id": "SCRUBBED_CHILD_DEVICE_ID_6",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -21,7 +21,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Bulb3", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -19,7 +19,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Home Family Room Table", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -34,11 +34,11 @@
}, },
"description": "Smart Wi-Fi LED Bulb with Tunable White Light", "description": "Smart Wi-Fi LED Bulb with Tunable White Light",
"dev_state": "normal", "dev_state": "normal",
"deviceId": "801200814AD69370AC59DE5501319C051AF409C3", "deviceId": "0000000000000000000000000000000000000000",
"disco_ver": "1.0", "disco_ver": "1.0",
"err_code": 0, "err_code": 0,
"heapsize": 290784, "heapsize": 290784,
"hwId": "111E35908497A05512E259BB76801E10", "hwId": "00000000000000000000000000000000",
"hw_ver": "1.0", "hw_ver": "1.0",
"is_color": 0, "is_color": 0,
"is_dimmable": 1, "is_dimmable": 1,
@ -52,10 +52,10 @@
"on_off": 1, "on_off": 1,
"saturation": 0 "saturation": 0
}, },
"mic_mac": "D80D17150474", "mic_mac": "D80D17000000",
"mic_type": "IOT.SMARTBULB", "mic_type": "IOT.SMARTBULB",
"model": "KL120(US)", "model": "KL120(US)",
"oemId": "1210657CD7FBDC72895644388EEFAE8B", "oemId": "00000000000000000000000000000000",
"preferred_state": [ "preferred_state": [
{ {
"brightness": 100, "brightness": 100,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "kasa-bc01", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -22,7 +22,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test bulb 6", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -21,7 +21,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Bulb2", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -20,7 +20,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "KL135 Bulb", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 0, "LEF": 0,
"active_mode": "none", "active_mode": "none",
"alias": "Kl400", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 0, "LEF": 0,
"active_mode": "none", "active_mode": "none",
"alias": "Kl400", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 1, "LEF": 1,
"active_mode": "none", "active_mode": "none",
"alias": "Kl420 test", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 1, "LEF": 1,
"active_mode": "none", "active_mode": "none",
"alias": "Bedroom light strip", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -23,7 +23,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "schedule", "active_mode": "schedule",
"alias": "Bedroom Lightstrip", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 1, "LEF": 1,
"active_mode": "none", "active_mode": "none",
"alias": "89 strip", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -10,7 +10,7 @@
"get_sysinfo": { "get_sysinfo": {
"LEF": 1, "LEF": 1,
"active_mode": "none", "active_mode": "none",
"alias": "kl430 updated", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -22,7 +22,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Kl50", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -32,7 +32,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "TP-LINK_Smart Bulb_9179", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"
@ -60,7 +60,7 @@
"on_off": 0 "on_off": 0
}, },
"longitude_i": 0, "longitude_i": 0,
"mic_mac": "74DA88C89179", "mic_mac": "74DA88000000",
"mic_type": "IOT.SMARTBULB", "mic_type": "IOT.SMARTBULB",
"model": "KL60(UN)", "model": "KL60(UN)",
"oemId": "00000000000000000000000000000000", "oemId": "00000000000000000000000000000000",

View File

@ -22,7 +22,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Gold fil", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -2,7 +2,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Kasa", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini", "dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": -7, "err_code": -7,

View File

@ -11,7 +11,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test plug", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini", "dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -11,7 +11,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test plug", "alias": "#MASKED_NAME#",
"dev_name": "Smart Wi-Fi Plug Mini", "dev_name": "Smart Wi-Fi Plug Mini",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -1,12 +1,12 @@
{ {
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Smart Plug_C2D6", "alias": "#MASKED_NAME#",
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "One ", "alias": "#MASKED_NAME# 1",
"id": "80066788DFFFD572D9F2E4A5A6847669213E039F00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Two ", "alias": "#MASKED_NAME# 2",
"id": "80066788DFFFD572D9F2E4A5A6847669213E039F01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -1,12 +1,12 @@
{ {
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "Bedroom Power Strip", "alias": "#MASKED_NAME#",
"child_num": 3, "child_num": 3,
"children": [ "children": [
{ {
"alias": "Plug 1", "alias": "#MASKED_NAME# 1",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7700", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Plug 2", "alias": "#MASKED_NAME# 2",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7701", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -23,8 +23,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 3", "alias": "#MASKED_NAME# 3",
"id": "8006E9854025B67C3F9D99BA1E66223D1C9A8A7702", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -1,12 +1,12 @@
{ {
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Power Strip_BDF6", "alias": "#MASKED_NAME#",
"child_num": 3, "child_num": 3,
"children": [ "children": [
{ {
"alias": "Plug 1", "alias": "#MASKED_NAME# 1",
"id": "800681855E0E9AEF096F4891B3DC88C71E59F42E00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 2", "alias": "#MASKED_NAME# 2",
"id": "800681855E0E9AEF096F4891B3DC88C71E59F42E01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -23,8 +23,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 3", "alias": "#MASKED_NAME# 3",
"id": "800681855E0E9AEF096F4891B3DC88C71E59F42E02", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -5,8 +5,8 @@
"child_num": 3, "child_num": 3,
"children": [ "children": [
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 1",
"id": "800639AA097730E58235162FCDA301CE1F038F9101", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 2",
"id": "800639AA097730E58235162FCDA301CE1F038F9102", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -23,8 +23,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 3",
"id": "800639AA097730E58235162FCDA301CE1F038F9100", "id": "SCRUBBED_CHILD_DEVICE_ID_3",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -17,12 +17,12 @@
}, },
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Smart Plug_2ECE", "alias": "#MASKED_NAME#",
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "Rope", "alias": "#MASKED_NAME# 1",
"id": "00", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"action": 1, "action": 1,
"schd_sec": 69240, "schd_sec": 69240,
@ -32,8 +32,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "Plug 2", "alias": "#MASKED_NAME# 2",
"id": "01", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -1,12 +1,12 @@
{ {
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"alias": "TP-LINK_Smart Plug_DC2A", "alias": "#MASKED_NAME#",
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "Anc ", "alias": "#MASKED_NAME# 1",
"id": "8006B8E953CC4149E2B13AA27E0D18EF1DCFBF3400", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "Plug 2", "alias": "#MASKED_NAME# 2",
"id": "8006B8E953CC4149E2B13AA27E0D18EF1DCFBF3401", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -5,8 +5,8 @@
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 1",
"id": "8006521377E30159055A751347B5A5E321A8D0A100", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 1 "state": 1
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 2",
"id": "8006521377E30159055A751347B5A5E321A8D0A101", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -5,8 +5,8 @@
"child_num": 2, "child_num": 2,
"children": [ "children": [
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 1",
"id": "8006521377E30159055A751347B5A5E321A8D0A100", "id": "SCRUBBED_CHILD_DEVICE_ID_1",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },
@ -14,8 +14,8 @@
"state": 0 "state": 0
}, },
{ {
"alias": "#MASKED_NAME#", "alias": "#MASKED_NAME# 2",
"id": "8006521377E30159055A751347B5A5E321A8D0A101", "id": "SCRUBBED_CHILD_DEVICE_ID_2",
"next_action": { "next_action": {
"type": -1 "type": -1
}, },

View File

@ -2,7 +2,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Kp401", "alias": "#MASKED_NAME#",
"dev_name": "Smart Outdoor Plug", "dev_name": "Smart Outdoor Plug",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -15,7 +15,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Porch Lights", "alias": "#MASKED_NAME#",
"brightness": 50, "brightness": 50,
"dev_name": "Kasa Smart Wi-Fi Outdoor Plug-In Dimmer", "dev_name": "Kasa Smart Wi-Fi Outdoor Plug-In Dimmer",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -66,7 +66,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test KS200M", "alias": "#MASKED_NAME#",
"dev_name": "Smart Light Switch with PIR", "dev_name": "Smart Light Switch with PIR",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",
"err_code": 0, "err_code": 0,

View File

@ -11,7 +11,7 @@
"stopConnect": 0, "stopConnect": 0,
"tcspInfo": "", "tcspInfo": "",
"tcspStatus": 1, "tcspStatus": 1,
"username": "#MASKED_NAME#" "username": "user@example.com"
}, },
"get_intl_fw_list": { "get_intl_fw_list": {
"err_code": 0, "err_code": 0,

View File

@ -78,7 +78,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Garage Entryway Lights", "alias": "#MASKED_NAME#",
"brightness": 100, "brightness": 100,
"dev_name": "Wi-Fi Smart Dimmer with sensor", "dev_name": "Wi-Fi Smart Dimmer with sensor",
"deviceId": "0000000000000000000000000000000000000000", "deviceId": "0000000000000000000000000000000000000000",

View File

@ -14,7 +14,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "Test KS230", "alias": "#MASKED_NAME#",
"brightness": 60, "brightness": 60,
"dc_state": 0, "dc_state": 0,
"dev_name": "Wi-Fi Smart 3-Way Dimmer", "dev_name": "Wi-Fi Smart 3-Way Dimmer",

View File

@ -21,7 +21,7 @@
"system": { "system": {
"get_sysinfo": { "get_sysinfo": {
"active_mode": "none", "active_mode": "none",
"alias": "TP-LINK_Smart Bulb_43EC", "alias": "#MASKED_NAME#",
"ctrl_protocols": { "ctrl_protocols": {
"name": "Linkie", "name": "Linkie",
"version": "1.0" "version": "1.0"

View File

@ -84,21 +84,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "EP25(US)", "result": {
"device_type": "SMART.KASAPLUG", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "EP25(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASAPLUG",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "00-00-00-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "00-00-00-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

View File

@ -88,21 +88,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "EP25(US)", "result": {
"device_type": "SMART.KASAPLUG", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "EP25(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASAPLUG",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "3C-52-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "3C-52-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

View File

@ -379,21 +379,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "EP40M(US)", "result": {
"device_type": "SMART.KASAPLUG", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "EP40M(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASAPLUG",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "F0-09-0D-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "KLAP", "mac": "F0-09-0D-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "KLAP",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "matter", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "matter",
"owner": "00000000000000000000000000000000"
}
}, },
"get_auto_update_info": { "get_auto_update_info": {
"enable": true, "enable": true,

View File

@ -84,21 +84,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "H100(EU)", "result": {
"device_type": "SMART.TAPOHUB", "device_id": "00000000000000000000000000000000",
"factory_default": true, "device_model": "H100(EU)",
"ip": "127.0.0.123", "device_type": "SMART.TAPOHUB",
"is_support_iot_cloud": true, "factory_default": true,
"mac": "3C-52-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "3C-52-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "" },
"obd_src": "tplink",
"owner": ""
}
}, },
"get_auto_update_info": { "get_auto_update_info": {
"enable": true, "enable": true,

View File

@ -96,21 +96,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "H100(EU)", "result": {
"device_type": "SMART.TAPOHUB", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "H100(EU)",
"ip": "127.0.0.123", "device_type": "SMART.TAPOHUB",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "3C-52-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "3C-52-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_alarm_configure": { "get_alarm_configure": {
"duration": 10, "duration": 10,

View File

@ -92,21 +92,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "H100(EU)", "result": {
"device_type": "SMART.TAPOHUB", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "H100(EU)",
"ip": "127.0.0.123", "device_type": "SMART.TAPOHUB",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "3C-52-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "3C-52-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_alarm_configure": { "get_alarm_configure": {
"duration": 10, "duration": 10,
@ -195,7 +198,7 @@
"ver_code": 1 "ver_code": 1
} }
], ],
"device_id": "0000000000000000000000000000000000000000" "device_id": "SCRUBBED_CHILD_DEVICE_ID_1"
} }
], ],
"start_index": 0, "start_index": 0,
@ -213,7 +216,7 @@
"current_humidity_exception": -34, "current_humidity_exception": -34,
"current_temp": 22.2, "current_temp": 22.2,
"current_temp_exception": 0, "current_temp_exception": 0,
"device_id": "0000000000000000000000000000000000000000", "device_id": "SCRUBBED_CHILD_DEVICE_ID_1",
"fw_ver": "1.7.0 Build 230424 Rel.170332", "fw_ver": "1.7.0 Build 230424 Rel.170332",
"hw_id": "00000000000000000000000000000000", "hw_id": "00000000000000000000000000000000",
"hw_ver": "1.0", "hw_ver": "1.0",

View File

@ -80,21 +80,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "HS200(US)", "result": {
"device_type": "SMART.KASASWITCH", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "HS200(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASASWITCH",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "74-FE-CE-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "KLAP", "mac": "74-FE-CE-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "KLAP",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

View File

@ -100,20 +100,23 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"owner": "00000000000000000000000000000000", "result": {
"device_type": "SMART.KASASWITCH", "device_id": "00000000000000000000000000000000",
"device_model": "HS220(US)", "device_model": "HS220(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASASWITCH",
"mac": "24-2F-D0-00-00-00", "factory_default": false,
"is_support_iot_cloud": true, "ip": "127.0.0.123",
"obd_src": "tplink", "is_support_iot_cloud": true,
"factory_default": false, "mac": "24-2F-D0-00-00-00",
"mgt_encrypt_schm": { "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"encrypt_type": "AES", "http_port": 80,
"http_port": 80, "is_support_https": false,
"lv": 2 "lv": 2
},
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
} }
}, },
"get_antitheft_rules": { "get_antitheft_rules": {

View File

@ -84,21 +84,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KH100(EU)", "result": {
"device_type": "SMART.KASAHUB", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KH100(EU)",
"ip": "127.0.0.123", "device_type": "SMART.KASAHUB",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "A8-42-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "A8-42-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_alarm_configure": { "get_alarm_configure": {
"duration": 300, "duration": 300,

View File

@ -88,21 +88,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KH100(EU)", "result": {
"device_type": "SMART.KASAHUB", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KH100(EU)",
"ip": "127.0.0.123", "device_type": "SMART.KASAHUB",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "A8-42-A1-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "KLAP", "mac": "A8-42-A1-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "KLAP",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_alarm_configure": { "get_alarm_configure": {
"duration": 300, "duration": 300,

View File

@ -1,4 +1,4 @@
{ {
"component_nego": { "component_nego": {
"component_list": [ "component_list": [
{ {
@ -88,21 +88,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KH100(UK)", "result": {
"device_type": "SMART.KASAHUB", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KH100(UK)",
"ip": "127.0.0.123", "device_type": "SMART.KASAHUB",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "F0-A7-31-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "F0-A7-31-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_alarm_configure": { "get_alarm_configure": {
"duration": 300, "duration": 300,

View File

@ -84,21 +84,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KP125M(US)", "result": {
"device_type": "SMART.KASAPLUG", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KP125M(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASAPLUG",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "00-00-00-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "00-00-00-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_current_power": { "get_current_power": {
"current_power": 17 "current_power": 17
@ -124,7 +127,7 @@
"longitude": 0, "longitude": 0,
"mac": "00-00-00-00-00-00", "mac": "00-00-00-00-00-00",
"model": "KP125M", "model": "KP125M",
"nickname": "IyNNQVNLRUROQU1FIyM=", "nickname": "I01BU0tFRF9OQU1FIw==",
"oem_id": "00000000000000000000000000000000", "oem_id": "00000000000000000000000000000000",
"on_time": 5332, "on_time": 5332,
"overheated": false, "overheated": false,
@ -133,7 +136,7 @@
"rssi": -62, "rssi": -62,
"signal_level": 2, "signal_level": 2,
"specs": "", "specs": "",
"ssid": "IyNNQVNLRUROQU1FIyM=", "ssid": "I01BU0tFRF9TU0lEIw==",
"time_diff": -360, "time_diff": -360,
"type": "SMART.KASAPLUG" "type": "SMART.KASAPLUG"
}, },

View File

@ -88,21 +88,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KP125M(US)", "result": {
"device_type": "SMART.KASAPLUG", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KP125M(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASAPLUG",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "78-8C-B5-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "KLAP", "mac": "78-8C-B5-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "KLAP",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

View File

@ -80,21 +80,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KS205(US)", "result": {
"device_type": "SMART.KASASWITCH", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KS205(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASASWITCH",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "00-00-00-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "AES", "mac": "00-00-00-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "AES",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "00000000000000000000000000000000" },
"obd_src": "tplink",
"owner": "00000000000000000000000000000000"
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

View File

@ -76,21 +76,24 @@
] ]
}, },
"discovery_result": { "discovery_result": {
"device_id": "00000000000000000000000000000000", "error_code": 0,
"device_model": "KS205(US)", "result": {
"device_type": "SMART.KASASWITCH", "device_id": "00000000000000000000000000000000",
"factory_default": false, "device_model": "KS205(US)",
"ip": "127.0.0.123", "device_type": "SMART.KASASWITCH",
"is_support_iot_cloud": true, "factory_default": false,
"mac": "40-ED-00-00-00-00", "ip": "127.0.0.123",
"mgt_encrypt_schm": { "is_support_iot_cloud": true,
"encrypt_type": "KLAP", "mac": "40-ED-00-00-00-00",
"http_port": 80, "mgt_encrypt_schm": {
"is_support_https": false, "encrypt_type": "KLAP",
"lv": 2 "http_port": 80,
}, "is_support_https": false,
"obd_src": "tplink", "lv": 2
"owner": "" },
"obd_src": "tplink",
"owner": ""
}
}, },
"get_antitheft_rules": { "get_antitheft_rules": {
"antitheft_rule_max_count": 1, "antitheft_rule_max_count": 1,

Some files were not shown because too many files have changed in this diff Show More