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 fetch upstream patch
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
# Check the diff is as expected
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
# If there are any other unexpected diffs `git checkout upstream/master [thefilename]`
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
import base64
import collections.abc
import dataclasses
import json
import logging
@ -19,6 +17,7 @@ import re
import sys
import traceback
from collections import defaultdict, namedtuple
from collections.abc import Callable
from pathlib import Path
from pprint import pprint
from typing import Any
@ -39,13 +38,20 @@ from kasa import (
)
from kasa.device_factory import get_protocol
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.protocols import IotProtocol
from kasa.protocols.iotprotocol import REDACTORS as IOT_REDACTORS
from kasa.protocols.protocol import redact_data
from kasa.protocols.smartcamprotocol import (
SmartCamProtocol,
_ChildCameraProtocolWrapper,
)
from kasa.protocols.smartprotocol import REDACTORS as SMART_REDACTORS
from kasa.protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from kasa.smart import SmartChildDevice, SmartDevice
from kasa.smartcam import SmartCamDevice
@ -63,6 +69,42 @@ ENCRYPT_TYPES = [encrypt_type.value for encrypt_type in DeviceEncryptionType]
_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
class SmartCall:
"""Class for smart and smartcam calls."""
@ -74,115 +116,6 @@ class SmartCall:
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):
"""Convert nested defaultdicts to regular ones.
@ -209,7 +142,7 @@ async def handle_device(
for fixture_result in fixture_results:
save_filename = Path(basedir) / fixture_result.folder / fixture_result.filename
pprint(scrub(fixture_result.data))
pprint(fixture_result.data)
if autosave:
save = "y"
else:
@ -325,6 +258,11 @@ async def cli(
if 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)
if host is not None:
if discovery_info:
@ -377,12 +315,16 @@ async def cli(
credentials=credentials,
port=port,
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(
basedir,
autosave,
device.protocol,
discovery_info=device._discovery_info,
discovery_info=discovery_info,
batch_size=batch_size,
)
else:
@ -391,21 +333,28 @@ async def cli(
f" {target}. Use --target to override."
)
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")
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(
basedir,
autosave,
dev.protocol,
discovery_info=dev._discovery_info,
discovery_info=discovery_info,
batch_size=batch_size,
)
async def get_legacy_fixture(
protocol: IotProtocol, *, discovery_info: dict[str, Any] | None
protocol: IotProtocol, *, discovery_info: dict[str, dict[str, Any]] | None
) -> FixtureResult:
"""Get fixture for legacy IOT style protocol."""
items = [
@ -475,11 +424,21 @@ async def get_legacy_fixture(
_echo_error(f"Unable to query all successes at once: {ex}")
finally:
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"):
# Need to recreate a DiscoverResult here because we don't want the aliases
# in the fixture, we want the actual field names as returned by the device.
dr = DiscoveryResult.from_dict(discovery_info)
final["discovery_result"] = dr.to_dict()
final["discovery_result"] = redact_data(
discovery_info, _wrap_redactors(NEW_DISCOVERY_REDACTORS)
)
click.echo(f"Got {len(successes)} successes")
click.echo(click.style("## device info file ##", bold=True))
@ -867,7 +826,10 @@ def get_smart_child_fixture(response):
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]:
"""Get fixture for new TAPO style protocol."""
if isinstance(protocol, SmartCamProtocol):
@ -988,22 +950,24 @@ async def get_smart_fixtures(
continue
_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
# in the fixture, we want the actual field names as returned by the device.
final = redact_data(final, _wrap_redactors(SMART_REDACTORS))
discovery_result = None
if discovery_info:
dr = DiscoveryResult.from_dict(discovery_info) # type: ignore
final["discovery_result"] = dr.to_dict()
final["discovery_result"] = redact_data(
discovery_info, _wrap_redactors(NEW_DISCOVERY_REDACTORS)
)
discovery_result = discovery_info["result"]
click.echo(f"Got {len(successes)} successes")
click.echo(click.style("## device info file ##", bold=True))
if "get_device_info" in final:
# smart protocol
model_info = SmartDevice._get_device_info(final, discovery_info)
model_info = SmartDevice._get_device_info(final, discovery_result)
copy_folder = SMART_FOLDER
else:
# 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
hw_version = model_info.hardware_version
sw_version = model_info.firmware_version

View File

@ -205,7 +205,7 @@ def _get_supported_devices(
fixture_data = json.load(f)
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]

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
>>> for feat in dev.features.values():
>>> 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,
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.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
@ -64,7 +72,9 @@ async def detail(ctx):
await ctx.parent.invoke(state)
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"]:
return discovered
@ -77,6 +87,33 @@ async def detail(ctx):
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()
@click.pass_context
async def list(ctx):
@ -102,10 +139,17 @@ async def list(ctx):
echo(f"{host:<15} UNSUPPORTED DEVICE")
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
target = params["target"]
username = params["username"]
@ -126,6 +170,7 @@ async def _discover(ctx, print_discovered, print_unsupported, *, do_echo=True):
timeout=timeout,
discovery_timeout=discovery_timeout,
on_unsupported=print_unsupported,
on_discovered_raw=print_raw,
)
if do_echo:
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,
timeout=timeout,
credentials=credentials,
on_discovered_raw=print_raw,
)
for device in discovered_devices.values():

View File

@ -99,6 +99,7 @@ from typing import (
Annotated,
Any,
NamedTuple,
TypedDict,
cast,
)
@ -147,18 +148,43 @@ class ConnectAttempt(NamedTuple):
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]
OnDiscoveredRawCallable = Callable[[DiscoveredRaw], None]
OnUnsupportedCallable = Callable[[UnsupportedDeviceError], Coroutine]
OnConnectAttemptCallable = Callable[[ConnectAttempt, bool], None]
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] = {
"device_id": lambda x: "REDACTED_" + x[9::],
"device_name": lambda x: "#MASKED_NAME#" if x else "",
"owner": lambda x: "REDACTED_" + x[9::],
"mac": mask_mac,
"master_device_id": lambda x: "REDACTED_" + x[9::],
"group_id": lambda x: "REDACTED_" + x[9::],
"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,
*,
on_discovered: OnDiscoveredCallable | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
target: str = "255.255.255.255",
discovery_packets: int = 3,
discovery_timeout: int = 5,
@ -240,6 +267,7 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
self.unsupported_device_exceptions: dict = {}
self.invalid_device_exceptions: dict = {}
self.on_unsupported = on_unsupported
self.on_discovered_raw = on_discovered_raw
self.credentials = credentials
self.timeout = timeout
self.discovery_timeout = discovery_timeout
@ -329,12 +357,23 @@ class _DiscoverProtocol(asyncio.DatagramProtocol):
config.timeout = self.timeout
try:
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:
config.uses_http = True
device = Discover._get_device_instance(data, config)
json_func = Discover._get_discovery_json
device_func = Discover._get_device_instance
else:
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:
_LOGGER.debug("Unsupported device found at %s << %s", ip, udex)
self.unsupported_device_exceptions[ip] = udex
@ -391,6 +430,7 @@ class Discover:
*,
target: str = "255.255.255.255",
on_discovered: OnDiscoveredCallable | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
discovery_timeout: int = 5,
discovery_packets: int = 3,
interface: str | None = None,
@ -421,6 +461,8 @@ class Discover:
:param target: The target address where to send the broadcast discovery
queries if multi-homing (e.g. 192.168.xxx.255).
: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_packets: Number of discovery packets to broadcast
:param interface: Bind to specific interface
@ -443,6 +485,7 @@ class Discover:
discovery_packets=discovery_packets,
interface=interface,
on_unsupported=on_unsupported,
on_discovered_raw=on_discovered_raw,
credentials=credentials,
timeout=timeout,
discovery_timeout=discovery_timeout,
@ -476,6 +519,7 @@ class Discover:
credentials: Credentials | None = None,
username: str | None = None,
password: str | None = None,
on_discovered_raw: OnDiscoveredRawCallable | None = None,
on_unsupported: OnUnsupportedCallable | None = None,
) -> Device | None:
"""Discover a single device by the given IP address.
@ -493,6 +537,9 @@ class Discover:
username and password are ignored if provided.
:param username: Username 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
:return: Object for querying/controlling found device.
"""
@ -529,6 +576,7 @@ class Discover:
credentials=credentials,
timeout=timeout,
discovery_timeout=discovery_timeout,
on_discovered_raw=on_discovered_raw,
),
local_addr=("0.0.0.0", 0), # noqa: S104
)
@ -666,15 +714,19 @@ class Discover:
return get_device_class_from_sys_info(info)
@staticmethod
def _get_device_instance_legacy(data: bytes, config: DeviceConfig) -> IotDevice:
"""Get SmartDevice from legacy 9999 response."""
def _get_discovery_json_legacy(data: bytes, ip: str) -> dict:
"""Get discovery json from legacy 9999 response."""
try:
info = json_loads(XorEncryption.decrypt(data))
except Exception as ex:
raise KasaException(
f"Unable to read response from device: {config.host}: {ex}"
f"Unable to read response from device: {ip}: {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):
data = redact_data(info, IOT_REDACTORS) if Discover._redact_data else info
_LOGGER.debug("[DISCOVERY] %s << %s", config.host, pf(data))
@ -698,6 +750,7 @@ class Discover:
@staticmethod
def _decrypt_discovery_data(discovery_result: DiscoveryResult) -> None:
debug_enabled = _LOGGER.isEnabledFor(logging.DEBUG)
if TYPE_CHECKING:
assert discovery_result.encrypt_info
assert _AesDiscoveryQuery.keypair
@ -713,22 +766,39 @@ class Discover:
session = AesEncyptionSession(key, iv)
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
def _get_device_instance(
data: bytes,
info: dict,
config: DeviceConfig,
) -> Device:
"""Get SmartDevice from the new 20002 response."""
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:
discovery_result = DiscoveryResult.from_dict(info["result"])
@ -757,7 +827,9 @@ class Discover:
Discover._decrypt_discovery_data(discovery_result)
except 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

View File

@ -24,7 +24,6 @@ State (state): True
Signal Level (signal_level): 2
RSSI (rssi): -52
SSID (ssid): #MASKED_SSID#
Overheated (overheated): False
Reboot (reboot): <Action>
Brightness (brightness): 100
Cloud connection (cloud_connection): True
@ -39,6 +38,7 @@ Light effect (light_effect): Off
Light preset (light_preset): Not set
Smooth transition on (smooth_transition_on): 2
Smooth transition off (smooth_transition_off): 2
Overheated (overheated): False
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:

View File

@ -8,18 +8,24 @@ from typing import Any
try:
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."""
return orjson.dumps(obj).decode()
return orjson.dumps(
obj, option=orjson.OPT_INDENT_2 if indent else None
).decode()
loads = orjson.loads
except ImportError:
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."""
# 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

View File

@ -25,19 +25,35 @@ if TYPE_CHECKING:
_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] = {
"latitude": lambda x: 0,
"longitude": lambda x: 0,
"latitude_i": lambda x: 0,
"longitude_i": lambda x: 0,
"deviceId": lambda x: "REDACTED_" + x[9::],
"id": lambda x: "REDACTED_" + x[9::],
"children": _mask_children,
"alias": lambda x: "#MASKED_NAME#" if x else "",
"mac": mask_mac,
"mic_mac": mask_mac,
"ssid": lambda x: "#MASKED_SSID#" if x else "",
"oemId": lambda x: "REDACTED_" + x[9::],
"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:
"""Return mac address with last two octects blanked."""
if len(mac) == 12:
return f"{mac[:6]}000000"
delim = ":" if ":" in mac else "-"
rest = delim.join(format(s, "02x") for s in bytes.fromhex("000000"))
return f"{mac[:8]}{delim}{rest}"

View File

@ -9,6 +9,7 @@ from __future__ import annotations
import asyncio
import base64
import logging
import re
import time
import uuid
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
"nickname": lambda x: "I01BU0tFRF9OQU1FIw==" if x else "",
"mac": mask_mac,
"ssid": lambda x: "I01BU0tFRF9TU0lEIw=" if x else "",
"ssid": lambda x: "I01BU0tFRF9TU0lEIw==" if x else "",
"bssid": lambda _: "000000000000",
"channel": lambda _: 0,
"oem_id": lambda x: "REDACTED_" + x[9::],
"setup_code": None, # matter
"setup_payload": None, # matter
"mfi_setup_code": None, # mfi_ for homekit
"mfi_setup_id": None,
"mfi_token_token": None,
"mfi_token_uuid": None,
"setup_code": lambda x: re.sub(r"\w", "0", x), # matter
"setup_payload": lambda x: re.sub(r"\w", "0", x), # matter
"mfi_setup_code": lambda x: re.sub(r"\w", "0", x), # mfi_ for homekit
"mfi_setup_id": lambda x: re.sub(r"\w", "0", x),
"mfi_token_token": lambda x: re.sub(r"\w", "0", x),
"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 .lighttransition import LightTransition
from .motionsensor import MotionSensor
from .overheatprotection import OverheatProtection
from .reportmode import ReportMode
from .temperaturecontrol import TemperatureControl
from .temperaturesensor import TemperatureSensor
@ -64,4 +65,5 @@ __all__ = [
"FrostProtection",
"Thermostat",
"SmartLightEffect",
"OverheatProtection",
]

View File

@ -10,7 +10,7 @@ class ContactSensor(SmartModule):
"""Implementation of contact sensor module."""
REQUIRED_COMPONENT = None # we depend on availability of key
REQUIRED_KEY_ON_PARENT = "open"
SYSINFO_LOOKUP_KEYS = ["open"]
def _initialize_features(self) -> None:
"""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 ..deviceconfig import DeviceConfig
from ..protocols.smartprotocol import SmartProtocol, _ChildProtocolWrapper
from .smartdevice import SmartDevice
from .smartdevice import ComponentsRaw, SmartDevice
from .smartmodule import SmartModule
_LOGGER = logging.getLogger(__name__)
@ -37,7 +37,7 @@ class SmartChildDevice(SmartDevice):
self,
parent: SmartDevice,
info: dict,
component_info: dict,
component_info_raw: ComponentsRaw,
*,
config: DeviceConfig | None = None,
protocol: SmartProtocol | None = None,
@ -47,7 +47,8 @@ class SmartChildDevice(SmartDevice):
super().__init__(parent.host, config=parent.config, protocol=_protocol)
self._parent = parent
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:
"""Update child module info.
@ -84,7 +85,7 @@ class SmartChildDevice(SmartDevice):
cls,
parent: SmartDevice,
child_info: dict,
child_components: dict,
child_components_raw: ComponentsRaw,
protocol: SmartProtocol | None = None,
*,
last_update: dict | None = None,
@ -97,7 +98,7 @@ class SmartChildDevice(SmartDevice):
derived from the parent.
"""
child: SmartChildDevice = cls(
parent, child_info, child_components, protocol=protocol
parent, child_info, child_components_raw, protocol=protocol
)
if last_update:
child._last_update = last_update

View File

@ -7,7 +7,7 @@ import logging
import time
from collections.abc import Mapping, Sequence
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_type import DeviceType
@ -40,6 +40,8 @@ _LOGGER = logging.getLogger(__name__)
# same issue, homekit perhaps?
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
# and python needs a consistent method resolution order.
@ -61,7 +63,7 @@ class SmartDevice(Device):
)
super().__init__(host=host, config=config, protocol=_protocol)
self.protocol: SmartProtocol
self._components_raw: dict[str, Any] | None = None
self._components_raw: ComponentsRaw | None = None
self._components: dict[str, int] = {}
self._state_information: dict[str, Any] = {}
self._modules: dict[str | ModuleName[Module], SmartModule] = {}
@ -82,10 +84,8 @@ class SmartDevice(Device):
self.internal_state.update(resp)
children = self.internal_state["get_child_device_list"]["child_device_list"]
children_components = {
child["device_id"]: {
comp["id"]: int(comp["ver_code"]) for comp in child["component_list"]
}
children_components_raw = {
child["device_id"]: child
for child in self.internal_state["get_child_device_component_list"][
"child_component_list"
]
@ -96,7 +96,7 @@ class SmartDevice(Device):
child_info["device_id"]: await SmartChildDevice.create(
parent=self,
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
}
@ -131,6 +131,13 @@ class SmartDevice(Device):
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:
"""Perform initialization.
@ -151,12 +158,9 @@ class SmartDevice(Device):
self._info = self._try_get_response(resp, "get_device_info")
# 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 = {
comp["id"]: int(comp["ver_code"])
for comp in self._components_raw["component_list"]
}
self._components = self._parse_components(self._components_raw)
if "child_device" in self._components and not self.children:
await self._initialize_children()
@ -349,9 +353,8 @@ class SmartDevice(Device):
) or mod.__name__ in child_modules_to_skip:
continue
required_component = cast(str, mod.REQUIRED_COMPONENT)
if required_component in self._components or (
mod.REQUIRED_KEY_ON_PARENT
and self.sys_info.get(mod.REQUIRED_KEY_ON_PARENT) is not None
if required_component in self._components or any(
self.sys_info.get(key) is not None for key in mod.SYSINFO_LOOKUP_KEYS
):
_LOGGER.debug(
"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,
# as the value is falsy when the device is off.
if "on_time" in self._info:

View File

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

View File

@ -3,13 +3,14 @@
from __future__ import annotations
import logging
from typing import Any
from typing import Any, cast
from ..device import _DeviceInfo
from ..device_type import DeviceType
from ..module import Module
from ..protocols.smartcamprotocol import _ChildCameraProtocolWrapper
from ..smart import SmartChildDevice, SmartDevice
from ..smart.smartdevice import ComponentsRaw
from .modules import ChildDevice, DeviceModule
from .smartcammodule import SmartCamModule
@ -78,7 +79,7 @@ class SmartCamDevice(SmartDevice):
self._children[child_id]._update_internal_state(info)
async def _initialize_smart_child(
self, info: dict, child_components: dict
self, info: dict, child_components_raw: ComponentsRaw
) -> SmartDevice:
"""Initialize a smart child device attached to a smartcam device."""
child_id = info["device_id"]
@ -93,7 +94,7 @@ class SmartCamDevice(SmartDevice):
return await SmartChildDevice.create(
parent=self,
child_info=info,
child_components=child_components,
child_components_raw=child_components_raw,
protocol=child_protocol,
last_update=initial_response,
)
@ -108,17 +109,8 @@ class SmartCamDevice(SmartDevice):
self.internal_state.update(resp)
smart_children_components = {
child["device_id"]: {
comp["id"]: int(comp["ver_code"]) for comp in component_list
}
child["device_id"]: child
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 = {}
for info in resp["getChildDeviceList"]["child_device_list"]:
@ -172,6 +164,13 @@ class SmartCamDevice(SmartDevice):
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:
"""Perform initialization.
@ -186,12 +185,10 @@ class SmartCamDevice(SmartDevice):
self._last_update.update(resp)
self._update_internal_info(resp)
self._components = {
comp["name"]: int(comp["version"])
for comp in resp["getAppComponentList"]["app_component"][
"app_component_list"
]
}
self._components_raw = cast(
ComponentsRaw, resp["getAppComponentList"]["app_component"]
)
self._components = self._parse_components(self._components_raw)
if "childControl" in self._components and not self.children:
await self._initialize_children()

View File

@ -435,7 +435,7 @@ async def get_device_for_fixture(
discovery_data = None
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:
discovery_data = {
"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):
"""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)
@ -170,8 +171,8 @@ def create_discovery_mock(ip: str, fixture_data: dict):
)
if "discovery_result" in fixture_data:
discovery_data = {"result": fixture_data["discovery_result"].copy()}
discovery_result = fixture_data["discovery_result"]
discovery_data = fixture_data["discovery_result"].copy()
discovery_result = fixture_data["discovery_result"]["result"]
device_type = discovery_result["device_type"]
encrypt_type = discovery_result["mgt_encrypt_schm"].get(
"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.SmartProtocol.query", return_value=fixture_data)
if "discovery_result" in fixture_data:
return {"result": fixture_data["discovery_result"]}
return fixture_data["discovery_result"].copy()
else:
return {"system": {"get_sysinfo": fixture_data["system"]["get_sysinfo"]}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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