mirror of
https://github.com/python-kasa/python-kasa.git
synced 2025-01-24 21:57:07 +00:00
Allow https for klaptransport (#1415)
Some checks failed
CI / Perform linting checks (3.13) (push) Waiting to run
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.13) (push) Blocked by required conditions
CodeQL checks / Analyze (python) (push) Has been cancelled
Some checks failed
CI / Perform linting checks (3.13) (push) Waiting to run
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, macos-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, ubuntu-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (false, windows-latest, 3.13) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Python ${{ matrix.python-version}} on ${{ matrix.os }}${{ fromJSON('[" (extras)", ""]')[matrix.extras == ''] }} (true, ubuntu-latest, 3.13) (push) Blocked by required conditions
CodeQL checks / Analyze (python) (push) Has been cancelled
Later firmware versions on robovacs use `KLAP` over https instead of ssltransport (reported as AES)
This commit is contained in:
parent
fa0f7157c6
commit
7b1b14d1e6
@ -204,7 +204,7 @@ The following devices have been tested and confirmed as working. If your device
|
|||||||
- **Cameras**: C100, C210, C220, C225, C325WB, C520WS, C720, D230, TC65, TC70
|
- **Cameras**: C100, C210, C220, C225, C325WB, C520WS, C720, D230, TC65, TC70
|
||||||
- **Hubs**: H100, H200
|
- **Hubs**: H100, H200
|
||||||
- **Hub-Connected Devices[^3]**: S200B, S200D, T100, T110, T300, T310, T315
|
- **Hub-Connected Devices[^3]**: S200B, S200D, T100, T110, T300, T310, T315
|
||||||
- **Vacuums**: RV20 Max Plus
|
- **Vacuums**: RV20 Max Plus, RV30 Max
|
||||||
|
|
||||||
<!--SUPPORTED_END-->
|
<!--SUPPORTED_END-->
|
||||||
[^1]: Model requires authentication
|
[^1]: Model requires authentication
|
||||||
|
@ -330,6 +330,8 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
|
|||||||
|
|
||||||
- **RV20 Max Plus**
|
- **RV20 Max Plus**
|
||||||
- Hardware: 1.0 (EU) / Firmware: 1.0.7
|
- Hardware: 1.0 (EU) / Firmware: 1.0.7
|
||||||
|
- **RV30 Max**
|
||||||
|
- Hardware: 1.0 (US) / Firmware: 1.2.0
|
||||||
|
|
||||||
|
|
||||||
<!--SUPPORTED_END-->
|
<!--SUPPORTED_END-->
|
||||||
|
@ -300,7 +300,9 @@ async def cli(
|
|||||||
connection_type = DeviceConnectionParameters.from_values(
|
connection_type = DeviceConnectionParameters.from_values(
|
||||||
dr.device_type,
|
dr.device_type,
|
||||||
dr.mgt_encrypt_schm.encrypt_type,
|
dr.mgt_encrypt_schm.encrypt_type,
|
||||||
dr.mgt_encrypt_schm.lv,
|
login_version=dr.mgt_encrypt_schm.lv,
|
||||||
|
https=dr.mgt_encrypt_schm.is_support_https,
|
||||||
|
http_port=dr.mgt_encrypt_schm.http_port,
|
||||||
)
|
)
|
||||||
dc = DeviceConfig(
|
dc = DeviceConfig(
|
||||||
host=host,
|
host=host,
|
||||||
|
@ -261,8 +261,11 @@ async def config(ctx: click.Context) -> DeviceDict:
|
|||||||
host_port = host + (f":{port}" if port else "")
|
host_port = host + (f":{port}" if port else "")
|
||||||
|
|
||||||
def on_attempt(connect_attempt: ConnectAttempt, success: bool) -> None:
|
def on_attempt(connect_attempt: ConnectAttempt, success: bool) -> None:
|
||||||
prot, tran, dev = connect_attempt
|
prot, tran, dev, https = connect_attempt
|
||||||
key_str = f"{prot.__name__} + {tran.__name__} + {dev.__name__}"
|
key_str = (
|
||||||
|
f"{prot.__name__} + {tran.__name__} + {dev.__name__}"
|
||||||
|
f" + {'https' if https else 'http'}"
|
||||||
|
)
|
||||||
result = "succeeded" if success else "failed"
|
result = "succeeded" if success else "failed"
|
||||||
msg = f"Attempt to connect to {host_port} with {key_str} {result}"
|
msg = f"Attempt to connect to {host_port} with {key_str} {result}"
|
||||||
echo(msg)
|
echo(msg)
|
||||||
|
@ -189,6 +189,7 @@ def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol
|
|||||||
:param config: Device config to derive protocol
|
:param config: Device config to derive protocol
|
||||||
:param strict: Require exact match on encrypt type
|
:param strict: Require exact match on encrypt type
|
||||||
"""
|
"""
|
||||||
|
_LOGGER.debug("Finding protocol for %s", config.host)
|
||||||
ctype = config.connection_type
|
ctype = config.connection_type
|
||||||
protocol_name = ctype.device_family.value.split(".")[0]
|
protocol_name = ctype.device_family.value.split(".")[0]
|
||||||
_LOGGER.debug("Finding protocol for %s", ctype.device_family)
|
_LOGGER.debug("Finding protocol for %s", ctype.device_family)
|
||||||
@ -203,9 +204,11 @@ def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol
|
|||||||
return None
|
return None
|
||||||
return IotProtocol(transport=LinkieTransportV2(config=config))
|
return IotProtocol(transport=LinkieTransportV2(config=config))
|
||||||
|
|
||||||
if ctype.device_family is DeviceFamily.SmartTapoRobovac:
|
# Older FW used a different transport
|
||||||
if strict and ctype.encryption_type is not DeviceEncryptionType.Aes:
|
if (
|
||||||
return None
|
ctype.device_family is DeviceFamily.SmartTapoRobovac
|
||||||
|
and ctype.encryption_type is DeviceEncryptionType.Aes
|
||||||
|
):
|
||||||
return SmartProtocol(transport=SslTransport(config=config))
|
return SmartProtocol(transport=SslTransport(config=config))
|
||||||
|
|
||||||
protocol_transport_key = (
|
protocol_transport_key = (
|
||||||
@ -223,6 +226,7 @@ def get_protocol(config: DeviceConfig, *, strict: bool = False) -> BaseProtocol
|
|||||||
"IOT.KLAP": (IotProtocol, KlapTransport),
|
"IOT.KLAP": (IotProtocol, KlapTransport),
|
||||||
"SMART.AES": (SmartProtocol, AesTransport),
|
"SMART.AES": (SmartProtocol, AesTransport),
|
||||||
"SMART.KLAP": (SmartProtocol, KlapTransportV2),
|
"SMART.KLAP": (SmartProtocol, KlapTransportV2),
|
||||||
|
"SMART.KLAP.HTTPS": (SmartProtocol, KlapTransportV2),
|
||||||
# H200 is device family SMART.TAPOHUB and uses SmartCamProtocol so use
|
# H200 is device family SMART.TAPOHUB and uses SmartCamProtocol so use
|
||||||
# https to distuingish from SmartProtocol devices
|
# https to distuingish from SmartProtocol devices
|
||||||
"SMART.AES.HTTPS": (SmartCamProtocol, SslAesTransport),
|
"SMART.AES.HTTPS": (SmartCamProtocol, SslAesTransport),
|
||||||
|
@ -20,7 +20,7 @@ None
|
|||||||
{'host': '127.0.0.3', 'timeout': 5, 'credentials': {'username': 'user@example.com', \
|
{'host': '127.0.0.3', 'timeout': 5, 'credentials': {'username': 'user@example.com', \
|
||||||
'password': 'great_password'}, 'connection_type'\
|
'password': 'great_password'}, 'connection_type'\
|
||||||
: {'device_family': 'SMART.TAPOBULB', 'encryption_type': 'KLAP', 'login_version': 2, \
|
: {'device_family': 'SMART.TAPOBULB', 'encryption_type': 'KLAP', 'login_version': 2, \
|
||||||
'https': False}}
|
'https': False, 'http_port': 80}}
|
||||||
|
|
||||||
>>> later_device = await Device.connect(config=Device.Config.from_dict(config_dict))
|
>>> later_device = await Device.connect(config=Device.Config.from_dict(config_dict))
|
||||||
>>> print(later_device.alias) # Alias is available as connect() calls update()
|
>>> print(later_device.alias) # Alias is available as connect() calls update()
|
||||||
@ -98,13 +98,16 @@ class DeviceConnectionParameters(_DeviceConfigBaseMixin):
|
|||||||
encryption_type: DeviceEncryptionType
|
encryption_type: DeviceEncryptionType
|
||||||
login_version: int | None = None
|
login_version: int | None = None
|
||||||
https: bool = False
|
https: bool = False
|
||||||
|
http_port: int | None = None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_values(
|
def from_values(
|
||||||
device_family: str,
|
device_family: str,
|
||||||
encryption_type: str,
|
encryption_type: str,
|
||||||
|
*,
|
||||||
login_version: int | None = None,
|
login_version: int | None = None,
|
||||||
https: bool | None = None,
|
https: bool | None = None,
|
||||||
|
http_port: int | None = None,
|
||||||
) -> DeviceConnectionParameters:
|
) -> DeviceConnectionParameters:
|
||||||
"""Return connection parameters from string values."""
|
"""Return connection parameters from string values."""
|
||||||
try:
|
try:
|
||||||
@ -115,6 +118,7 @@ class DeviceConnectionParameters(_DeviceConfigBaseMixin):
|
|||||||
DeviceEncryptionType(encryption_type),
|
DeviceEncryptionType(encryption_type),
|
||||||
login_version,
|
login_version,
|
||||||
https,
|
https,
|
||||||
|
http_port=http_port,
|
||||||
)
|
)
|
||||||
except (ValueError, TypeError) as ex:
|
except (ValueError, TypeError) as ex:
|
||||||
raise KasaException(
|
raise KasaException(
|
||||||
|
@ -146,6 +146,7 @@ class ConnectAttempt(NamedTuple):
|
|||||||
protocol: type
|
protocol: type
|
||||||
transport: type
|
transport: type
|
||||||
device: type
|
device: type
|
||||||
|
https: bool
|
||||||
|
|
||||||
|
|
||||||
class DiscoveredMeta(TypedDict):
|
class DiscoveredMeta(TypedDict):
|
||||||
@ -637,10 +638,10 @@ class Discover:
|
|||||||
Device.Family.IotIpCamera,
|
Device.Family.IotIpCamera,
|
||||||
}
|
}
|
||||||
candidates: dict[
|
candidates: dict[
|
||||||
tuple[type[BaseProtocol], type[BaseTransport], type[Device]],
|
tuple[type[BaseProtocol], type[BaseTransport], type[Device], bool],
|
||||||
tuple[BaseProtocol, DeviceConfig],
|
tuple[BaseProtocol, DeviceConfig],
|
||||||
] = {
|
] = {
|
||||||
(type(protocol), type(protocol._transport), device_class): (
|
(type(protocol), type(protocol._transport), device_class, https): (
|
||||||
protocol,
|
protocol,
|
||||||
config,
|
config,
|
||||||
)
|
)
|
||||||
@ -870,8 +871,9 @@ class Discover:
|
|||||||
config.connection_type = DeviceConnectionParameters.from_values(
|
config.connection_type = DeviceConnectionParameters.from_values(
|
||||||
type_,
|
type_,
|
||||||
encrypt_type,
|
encrypt_type,
|
||||||
login_version,
|
login_version=login_version,
|
||||||
encrypt_schm.is_support_https,
|
https=encrypt_schm.is_support_https,
|
||||||
|
http_port=encrypt_schm.http_port,
|
||||||
)
|
)
|
||||||
except KasaException as ex:
|
except KasaException as ex:
|
||||||
raise UnsupportedDeviceError(
|
raise UnsupportedDeviceError(
|
||||||
|
@ -36,6 +36,18 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _mask_area_list(area_list: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||||
|
def mask_area(area: dict[str, Any]) -> dict[str, Any]:
|
||||||
|
result = {**area}
|
||||||
|
# Will leave empty names as blank
|
||||||
|
if area.get("name"):
|
||||||
|
result["name"] = "I01BU0tFRF9OQU1FIw==" # #MASKED_NAME#
|
||||||
|
return result
|
||||||
|
|
||||||
|
return [mask_area(area) for area in area_list]
|
||||||
|
|
||||||
|
|
||||||
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,
|
||||||
@ -71,6 +83,10 @@ REDACTORS: dict[str, Callable[[Any], Any] | None] = {
|
|||||||
"custom_sn": lambda _: "000000000000",
|
"custom_sn": lambda _: "000000000000",
|
||||||
"location": lambda x: "#MASKED_NAME#" if x else "",
|
"location": lambda x: "#MASKED_NAME#" if x else "",
|
||||||
"map_data": lambda x: "#SCRUBBED_MAPDATA#" if x else "",
|
"map_data": lambda x: "#SCRUBBED_MAPDATA#" if x else "",
|
||||||
|
"map_name": lambda x: "I01BU0tFRF9OQU1FIw==", # #MASKED_NAME#
|
||||||
|
"area_list": _mask_area_list,
|
||||||
|
# unknown robovac binary blob in get_device_info
|
||||||
|
"cd": lambda x: "I01BU0tFRF9CSU5BUlkj", # #MASKED_BINARY#
|
||||||
}
|
}
|
||||||
|
|
||||||
# Queries that are known not to work properly when sent as a
|
# Queries that are known not to work properly when sent as a
|
||||||
|
@ -120,6 +120,8 @@ class AesTransport(BaseTransport):
|
|||||||
@property
|
@property
|
||||||
def default_port(self) -> int:
|
def default_port(self) -> int:
|
||||||
"""Default port for the transport."""
|
"""Default port for the transport."""
|
||||||
|
if port := self._config.connection_type.http_port:
|
||||||
|
return port
|
||||||
return self.DEFAULT_PORT
|
return self.DEFAULT_PORT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -48,6 +48,7 @@ import datetime
|
|||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
import secrets
|
import secrets
|
||||||
|
import ssl
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
from asyncio import Future
|
from asyncio import Future
|
||||||
@ -92,8 +93,21 @@ class KlapTransport(BaseTransport):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_PORT: int = 80
|
DEFAULT_PORT: int = 80
|
||||||
|
DEFAULT_HTTPS_PORT: int = 4433
|
||||||
|
|
||||||
SESSION_COOKIE_NAME = "TP_SESSIONID"
|
SESSION_COOKIE_NAME = "TP_SESSIONID"
|
||||||
TIMEOUT_COOKIE_NAME = "TIMEOUT"
|
TIMEOUT_COOKIE_NAME = "TIMEOUT"
|
||||||
|
# Copy & paste from sslaestransport
|
||||||
|
CIPHERS = ":".join(
|
||||||
|
[
|
||||||
|
"AES256-GCM-SHA384",
|
||||||
|
"AES256-SHA256",
|
||||||
|
"AES128-GCM-SHA256",
|
||||||
|
"AES128-SHA256",
|
||||||
|
"AES256-SHA",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
_ssl_context: ssl.SSLContext | None = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -125,12 +139,20 @@ class KlapTransport(BaseTransport):
|
|||||||
self._session_cookie: dict[str, Any] | None = None
|
self._session_cookie: dict[str, Any] | None = None
|
||||||
|
|
||||||
_LOGGER.debug("Created KLAP transport for %s", self._host)
|
_LOGGER.debug("Created KLAP transport for %s", self._host)
|
||||||
self._app_url = URL(f"http://{self._host}:{self._port}/app")
|
protocol = "https" if config.connection_type.https else "http"
|
||||||
|
self._app_url = URL(f"{protocol}://{self._host}:{self._port}/app")
|
||||||
self._request_url = self._app_url / "request"
|
self._request_url = self._app_url / "request"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_port(self) -> int:
|
def default_port(self) -> int:
|
||||||
"""Default port for the transport."""
|
"""Default port for the transport."""
|
||||||
|
config = self._config
|
||||||
|
if port := config.connection_type.http_port:
|
||||||
|
return port
|
||||||
|
|
||||||
|
if config.connection_type.https:
|
||||||
|
return self.DEFAULT_HTTPS_PORT
|
||||||
|
|
||||||
return self.DEFAULT_PORT
|
return self.DEFAULT_PORT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -152,7 +174,9 @@ class KlapTransport(BaseTransport):
|
|||||||
|
|
||||||
url = self._app_url / "handshake1"
|
url = self._app_url / "handshake1"
|
||||||
|
|
||||||
response_status, response_data = await self._http_client.post(url, data=payload)
|
response_status, response_data = await self._http_client.post(
|
||||||
|
url, data=payload, ssl=await self._get_ssl_context()
|
||||||
|
)
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
@ -263,6 +287,7 @@ class KlapTransport(BaseTransport):
|
|||||||
url,
|
url,
|
||||||
data=payload,
|
data=payload,
|
||||||
cookies_dict=self._session_cookie,
|
cookies_dict=self._session_cookie,
|
||||||
|
ssl=await self._get_ssl_context(),
|
||||||
)
|
)
|
||||||
|
|
||||||
if _LOGGER.isEnabledFor(logging.DEBUG):
|
if _LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
@ -337,6 +362,7 @@ class KlapTransport(BaseTransport):
|
|||||||
params={"seq": seq},
|
params={"seq": seq},
|
||||||
data=payload,
|
data=payload,
|
||||||
cookies_dict=self._session_cookie,
|
cookies_dict=self._session_cookie,
|
||||||
|
ssl=await self._get_ssl_context(),
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = (
|
msg = (
|
||||||
@ -413,6 +439,23 @@ class KlapTransport(BaseTransport):
|
|||||||
un = creds.username
|
un = creds.username
|
||||||
return md5(un.encode())
|
return md5(un.encode())
|
||||||
|
|
||||||
|
# Copy & paste from sslaestransport.
|
||||||
|
def _create_ssl_context(self) -> ssl.SSLContext:
|
||||||
|
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
|
context.set_ciphers(self.CIPHERS)
|
||||||
|
context.check_hostname = False
|
||||||
|
context.verify_mode = ssl.CERT_NONE
|
||||||
|
return context
|
||||||
|
|
||||||
|
# Copy & paste from sslaestransport.
|
||||||
|
async def _get_ssl_context(self) -> ssl.SSLContext:
|
||||||
|
if not self._ssl_context:
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
self._ssl_context = await loop.run_in_executor(
|
||||||
|
None, self._create_ssl_context
|
||||||
|
)
|
||||||
|
return self._ssl_context
|
||||||
|
|
||||||
|
|
||||||
class KlapTransportV2(KlapTransport):
|
class KlapTransportV2(KlapTransport):
|
||||||
"""Implementation of the KLAP encryption protocol with v2 hanshake hashes."""
|
"""Implementation of the KLAP encryption protocol with v2 hanshake hashes."""
|
||||||
|
@ -55,6 +55,8 @@ class LinkieTransportV2(BaseTransport):
|
|||||||
@property
|
@property
|
||||||
def default_port(self) -> int:
|
def default_port(self) -> int:
|
||||||
"""Default port for the transport."""
|
"""Default port for the transport."""
|
||||||
|
if port := self._config.connection_type.http_port:
|
||||||
|
return port
|
||||||
return self.DEFAULT_PORT
|
return self.DEFAULT_PORT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -133,6 +133,8 @@ class SslAesTransport(BaseTransport):
|
|||||||
@property
|
@property
|
||||||
def default_port(self) -> int:
|
def default_port(self) -> int:
|
||||||
"""Default port for the transport."""
|
"""Default port for the transport."""
|
||||||
|
if port := self._config.connection_type.http_port:
|
||||||
|
return port
|
||||||
return self.DEFAULT_PORT
|
return self.DEFAULT_PORT
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -94,6 +94,8 @@ class SslTransport(BaseTransport):
|
|||||||
@property
|
@property
|
||||||
def default_port(self) -> int:
|
def default_port(self) -> int:
|
||||||
"""Default port for the transport."""
|
"""Default port for the transport."""
|
||||||
|
if port := self._config.connection_type.http_port:
|
||||||
|
return port
|
||||||
return self.DEFAULT_PORT
|
return self.DEFAULT_PORT
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -159,6 +159,7 @@ def create_discovery_mock(ip: str, fixture_data: dict):
|
|||||||
https: bool
|
https: bool
|
||||||
login_version: int | None = None
|
login_version: int | None = None
|
||||||
port_override: int | None = None
|
port_override: int | None = None
|
||||||
|
http_port: int | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
@ -194,9 +195,15 @@ def create_discovery_mock(ip: str, fixture_data: dict):
|
|||||||
):
|
):
|
||||||
login_version = max([int(i) for i in et])
|
login_version = max([int(i) for i in et])
|
||||||
https = discovery_result["mgt_encrypt_schm"]["is_support_https"]
|
https = discovery_result["mgt_encrypt_schm"]["is_support_https"]
|
||||||
|
http_port = discovery_result["mgt_encrypt_schm"].get("http_port")
|
||||||
|
if not http_port: # noqa: SIM108
|
||||||
|
# Not all discovery responses set the http port, i.e. smartcam.
|
||||||
|
default_port = 443 if https else 80
|
||||||
|
else:
|
||||||
|
default_port = http_port
|
||||||
dm = _DiscoveryMock(
|
dm = _DiscoveryMock(
|
||||||
ip,
|
ip,
|
||||||
80,
|
default_port,
|
||||||
20002,
|
20002,
|
||||||
discovery_data,
|
discovery_data,
|
||||||
fixture_data,
|
fixture_data,
|
||||||
@ -204,6 +211,7 @@ def create_discovery_mock(ip: str, fixture_data: dict):
|
|||||||
encrypt_type,
|
encrypt_type,
|
||||||
https,
|
https,
|
||||||
login_version,
|
login_version,
|
||||||
|
http_port=http_port,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
sys_info = fixture_data["system"]["get_sysinfo"]
|
sys_info = fixture_data["system"]["get_sysinfo"]
|
||||||
|
888
tests/fixtures/smart/RV30 Max(US)_1.0_1.2.0.json
vendored
Normal file
888
tests/fixtures/smart/RV30 Max(US)_1.0_1.2.0.json
vendored
Normal file
@ -0,0 +1,888 @@
|
|||||||
|
{
|
||||||
|
"component_nego": {
|
||||||
|
"component_list": [
|
||||||
|
{
|
||||||
|
"id": "device",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "iot_cloud",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "time",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "firmware",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "quick_setup",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clean",
|
||||||
|
"ver_code": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "battery",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "consumables",
|
||||||
|
"ver_code": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "direction_control",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "button_and_led",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "speaker",
|
||||||
|
"ver_code": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "schedule",
|
||||||
|
"ver_code": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "wireless",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "map",
|
||||||
|
"ver_code": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "auto_change_map",
|
||||||
|
"ver_code": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mop",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ble_whole_setup",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "do_not_disturb",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inherit",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "device_local_time",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "charge_pose_clean",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "continue_breakpoint_sweep",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "goto_point",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "furniture",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "map_cloud_backup",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dev_log",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "map_lock",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "carpet_area",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clean_angle",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "clean_percent",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "no_pose_config",
|
||||||
|
"ver_code": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"discovery_result": {
|
||||||
|
"error_code": 0,
|
||||||
|
"result": {
|
||||||
|
"device_id": "00000000000000000000000000000000",
|
||||||
|
"device_model": "RV30 Max(US)",
|
||||||
|
"device_type": "SMART.TAPOROBOVAC",
|
||||||
|
"factory_default": false,
|
||||||
|
"ip": "127.0.0.123",
|
||||||
|
"is_support_iot_cloud": true,
|
||||||
|
"mac": "7C-F1-7E-00-00-00",
|
||||||
|
"mgt_encrypt_schm": {
|
||||||
|
"encrypt_type": "KLAP",
|
||||||
|
"http_port": 4433,
|
||||||
|
"is_support_https": true
|
||||||
|
},
|
||||||
|
"obd_src": "tplink",
|
||||||
|
"owner": "00000000000000000000000000000000",
|
||||||
|
"protocol_version": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"getAreaUnit": {
|
||||||
|
"area_unit": 1
|
||||||
|
},
|
||||||
|
"getAutoChangeMap": {
|
||||||
|
"auto_change_map": true
|
||||||
|
},
|
||||||
|
"getBatteryInfo": {
|
||||||
|
"battery_percentage": 100
|
||||||
|
},
|
||||||
|
"getCarpetClean": {
|
||||||
|
"carpet_clean_prefer": "boost"
|
||||||
|
},
|
||||||
|
"getChildLockInfo": {
|
||||||
|
"child_lock_status": false
|
||||||
|
},
|
||||||
|
"getCleanAttr": {
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"suction": 2
|
||||||
|
},
|
||||||
|
"getCleanInfo": {
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_percent": 100,
|
||||||
|
"clean_time": 56
|
||||||
|
},
|
||||||
|
"getCleanRecords": {
|
||||||
|
"lastest_day_record": [
|
||||||
|
1737387294,
|
||||||
|
56,
|
||||||
|
59,
|
||||||
|
1
|
||||||
|
],
|
||||||
|
"record_list": [
|
||||||
|
{
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_time": 57,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 0,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1737041654
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 39,
|
||||||
|
"clean_time": 58,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 1,
|
||||||
|
"map_id": 1736541042,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 1,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1737055944
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 1,
|
||||||
|
"clean_time": 3,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 2,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 4,
|
||||||
|
"timestamp": 1737074472
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_time": 58,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 3,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1737128195
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 68,
|
||||||
|
"clean_time": 78,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 2,
|
||||||
|
"map_id": 1736541042,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 4,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 1,
|
||||||
|
"timestamp": 1737216716
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 3,
|
||||||
|
"clean_time": 3,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734742958,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 5,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 3,
|
||||||
|
"timestamp": 1737300731
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 20,
|
||||||
|
"clean_time": 16,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734742958,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 6,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 3,
|
||||||
|
"timestamp": 1737304391
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_time": 56,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 7,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1737387294
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 17,
|
||||||
|
"clean_time": 16,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 8,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 3,
|
||||||
|
"timestamp": 1736707487
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 8,
|
||||||
|
"clean_time": 10,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 9,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 4,
|
||||||
|
"timestamp": 1736708425
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_time": 54,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 10,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1736782261
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 60,
|
||||||
|
"clean_time": 56,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 11,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1736868752
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 58,
|
||||||
|
"clean_time": 68,
|
||||||
|
"dust_collection": true,
|
||||||
|
"error": 1,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1736541042,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 12,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 1,
|
||||||
|
"timestamp": 1736881428
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 59,
|
||||||
|
"clean_time": 59,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 13,
|
||||||
|
"start_type": 4,
|
||||||
|
"task_type": 0,
|
||||||
|
"timestamp": 1736955682
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"clean_area": 36,
|
||||||
|
"clean_time": 33,
|
||||||
|
"dust_collection": false,
|
||||||
|
"error": 0,
|
||||||
|
"info_num": 0,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"message": 0,
|
||||||
|
"record_index": 14,
|
||||||
|
"start_type": 1,
|
||||||
|
"task_type": 4,
|
||||||
|
"timestamp": 1736960713
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"record_list_num": 15,
|
||||||
|
"total_area": 2304,
|
||||||
|
"total_number": 85,
|
||||||
|
"total_time": 2510
|
||||||
|
},
|
||||||
|
"getCleanStatus": {
|
||||||
|
"clean_status": 0,
|
||||||
|
"is_mapping": false,
|
||||||
|
"is_relocating": false,
|
||||||
|
"is_working": false
|
||||||
|
},
|
||||||
|
"getConsumablesInfo": {
|
||||||
|
"charge_contact_time": 660,
|
||||||
|
"edge_brush_time": 2743,
|
||||||
|
"filter_time": 287,
|
||||||
|
"main_brush_lid_time": 2462,
|
||||||
|
"rag_time": 0,
|
||||||
|
"roll_brush_time": 2719,
|
||||||
|
"sensor_time": 935
|
||||||
|
},
|
||||||
|
"getCurrentVoiceLanguage": {
|
||||||
|
"name": "bb053ca2c5605a55090fcdb952f3902b",
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"getDoNotDisturb": {
|
||||||
|
"do_not_disturb": true,
|
||||||
|
"e_min": 480,
|
||||||
|
"s_min": 1320
|
||||||
|
},
|
||||||
|
"getMapData": {
|
||||||
|
"area_list": [
|
||||||
|
{
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"color": 3,
|
||||||
|
"floor_texture": -1,
|
||||||
|
"id": 5,
|
||||||
|
"name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"suction": 2,
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"color": 4,
|
||||||
|
"floor_texture": -1,
|
||||||
|
"id": 6,
|
||||||
|
"name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"suction": 2,
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"color": 1,
|
||||||
|
"floor_texture": 0,
|
||||||
|
"id": 2,
|
||||||
|
"name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"suction": 2,
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"color": 5,
|
||||||
|
"floor_texture": 90,
|
||||||
|
"id": 3,
|
||||||
|
"name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"suction": 2,
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cistern": 1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"color": 2,
|
||||||
|
"floor_texture": -1,
|
||||||
|
"id": 4,
|
||||||
|
"name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"suction": 2,
|
||||||
|
"type": "room"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 401,
|
||||||
|
"type": "virtual_wall",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
4711,
|
||||||
|
985
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4717,
|
||||||
|
-404
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 301,
|
||||||
|
"type": "forbid",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
3061,
|
||||||
|
-3027
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3580,
|
||||||
|
-3027
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3580,
|
||||||
|
-3692
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3061,
|
||||||
|
-3692
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 402,
|
||||||
|
"type": "virtual_wall",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
5302,
|
||||||
|
6816
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5304,
|
||||||
|
4924
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cistern": -1,
|
||||||
|
"clean_number": 1,
|
||||||
|
"id": 501,
|
||||||
|
"suction": -1,
|
||||||
|
"type": "area",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
2889,
|
||||||
|
6241
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3721,
|
||||||
|
6241
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3721,
|
||||||
|
4919
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2889,
|
||||||
|
4919
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"carpet_strategy": 11,
|
||||||
|
"id": 101,
|
||||||
|
"type": "carpet_rectangle",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
-2012
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2857,
|
||||||
|
-2012
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2857,
|
||||||
|
-4122
|
||||||
|
],
|
||||||
|
[
|
||||||
|
20,
|
||||||
|
-4122
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"carpet_strategy": 11,
|
||||||
|
"id": 102,
|
||||||
|
"type": "carpet_rectangle",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
1327,
|
||||||
|
3064
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2428,
|
||||||
|
3064
|
||||||
|
],
|
||||||
|
[
|
||||||
|
2428,
|
||||||
|
2258
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1327,
|
||||||
|
2258
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"carpet_strategy": 11,
|
||||||
|
"id": 103,
|
||||||
|
"type": "carpet_rectangle",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
4458,
|
||||||
|
5974
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5336,
|
||||||
|
5974
|
||||||
|
],
|
||||||
|
[
|
||||||
|
5336,
|
||||||
|
4903
|
||||||
|
],
|
||||||
|
[
|
||||||
|
4458,
|
||||||
|
4903
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"carpet_strategy": 11,
|
||||||
|
"id": 104,
|
||||||
|
"type": "carpet_rectangle",
|
||||||
|
"vertexs": [
|
||||||
|
[
|
||||||
|
-1383,
|
||||||
|
2730
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-761,
|
||||||
|
2730
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-761,
|
||||||
|
1587
|
||||||
|
],
|
||||||
|
[
|
||||||
|
-1383,
|
||||||
|
1587
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"auto_area_flag": true,
|
||||||
|
"bit_list": {
|
||||||
|
"auto_area": [
|
||||||
|
0,
|
||||||
|
100
|
||||||
|
],
|
||||||
|
"barrier": 0,
|
||||||
|
"clean": 255,
|
||||||
|
"none": 127
|
||||||
|
},
|
||||||
|
"bitnum": 8,
|
||||||
|
"charge_coor": [
|
||||||
|
65,
|
||||||
|
134,
|
||||||
|
272
|
||||||
|
],
|
||||||
|
"furniture_list": [],
|
||||||
|
"height": 303,
|
||||||
|
"map_data": "#SCRUBBED_MAPDATA#",
|
||||||
|
"map_hash": "A5D8FA4487CC40312EF58D8123F0A4CC",
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"map_locked": 0,
|
||||||
|
"map_name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"origin_coor": [
|
||||||
|
-33,
|
||||||
|
-108,
|
||||||
|
270
|
||||||
|
],
|
||||||
|
"path_id": 122,
|
||||||
|
"pix_len": 66660,
|
||||||
|
"pix_lz4len": 6826,
|
||||||
|
"real_charge_coor": [
|
||||||
|
1599,
|
||||||
|
1295,
|
||||||
|
272
|
||||||
|
],
|
||||||
|
"real_origin_coor": [
|
||||||
|
-1674,
|
||||||
|
-5424,
|
||||||
|
270
|
||||||
|
],
|
||||||
|
"real_vac_coor": [
|
||||||
|
1599,
|
||||||
|
1076,
|
||||||
|
272
|
||||||
|
],
|
||||||
|
"resolution": 50,
|
||||||
|
"resolution_unit": "mm",
|
||||||
|
"vac_coor": [
|
||||||
|
65,
|
||||||
|
130,
|
||||||
|
272
|
||||||
|
],
|
||||||
|
"version": "LDS",
|
||||||
|
"width": 220
|
||||||
|
},
|
||||||
|
"getMapInfo": {
|
||||||
|
"auto_change_map": true,
|
||||||
|
"current_map_id": 1734727686,
|
||||||
|
"map_list": [
|
||||||
|
{
|
||||||
|
"auto_area_flag": true,
|
||||||
|
"global_cleaned": -1,
|
||||||
|
"is_saved": true,
|
||||||
|
"map_id": 1734727686,
|
||||||
|
"map_locked": 0,
|
||||||
|
"map_name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"rotate_angle": 270,
|
||||||
|
"update_time": 1737387285
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"auto_area_flag": true,
|
||||||
|
"global_cleaned": -1,
|
||||||
|
"is_saved": true,
|
||||||
|
"map_id": 1734742958,
|
||||||
|
"map_locked": 0,
|
||||||
|
"map_name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"rotate_angle": 0,
|
||||||
|
"update_time": 1737304392
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"auto_area_flag": true,
|
||||||
|
"global_cleaned": -1,
|
||||||
|
"is_saved": true,
|
||||||
|
"map_id": 1736541042,
|
||||||
|
"map_locked": 0,
|
||||||
|
"map_name": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"rotate_angle": 270,
|
||||||
|
"update_time": 1737216718
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"map_num": 3,
|
||||||
|
"version": "LDS"
|
||||||
|
},
|
||||||
|
"getMopState": {
|
||||||
|
"mop_state": false
|
||||||
|
},
|
||||||
|
"getVacStatus": {
|
||||||
|
"err_status": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"errorCode_id": [
|
||||||
|
1144500830
|
||||||
|
],
|
||||||
|
"prompt": [],
|
||||||
|
"promptCode_id": [],
|
||||||
|
"status": 6
|
||||||
|
},
|
||||||
|
"getVolume": {
|
||||||
|
"volume": 60
|
||||||
|
},
|
||||||
|
"get_device_info": {
|
||||||
|
"auto_pack_ver": "0.0.131.1852",
|
||||||
|
"avatar": "",
|
||||||
|
"board_sn": "000000000000",
|
||||||
|
"cd": "I01BU0tFRF9CSU5BUlkj",
|
||||||
|
"custom_sn": "000000000000",
|
||||||
|
"device_id": "0000000000000000000000000000000000000000",
|
||||||
|
"fw_id": "00000000000000000000000000000000",
|
||||||
|
"fw_ver": "1.2.0 Build 241219 Rel.163928",
|
||||||
|
"has_set_location_info": true,
|
||||||
|
"hw_id": "00000000000000000000000000000000",
|
||||||
|
"hw_ver": "1.0",
|
||||||
|
"ip": "127.0.0.123",
|
||||||
|
"lang": "",
|
||||||
|
"latitude": 0,
|
||||||
|
"linux_ver": "V21.198.1708420747",
|
||||||
|
"location": "",
|
||||||
|
"longitude": 0,
|
||||||
|
"mac": "7C-F1-7E-00-00-00",
|
||||||
|
"mcu_ver": "1.1.2724.442",
|
||||||
|
"model": "RV30 Max",
|
||||||
|
"nickname": "I01BU0tFRF9OQU1FIw==",
|
||||||
|
"oem_id": "00000000000000000000000000000000",
|
||||||
|
"overheated": false,
|
||||||
|
"product_id": "1794",
|
||||||
|
"region": "America/Chicago",
|
||||||
|
"rssi": -38,
|
||||||
|
"signal_level": 3,
|
||||||
|
"specs": "",
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw==",
|
||||||
|
"sub_ver": "0.0.131.1852-1.4.40",
|
||||||
|
"time_diff": -360,
|
||||||
|
"total_ver": "1.4.40",
|
||||||
|
"type": "SMART.TAPOROBOVAC"
|
||||||
|
},
|
||||||
|
"get_device_time": {
|
||||||
|
"region": "America/Chicago",
|
||||||
|
"time_diff": -360,
|
||||||
|
"timestamp": 1737399953
|
||||||
|
},
|
||||||
|
"get_fw_download_state": {
|
||||||
|
"auto_upgrade": false,
|
||||||
|
"download_progress": 0,
|
||||||
|
"reboot_time": 5,
|
||||||
|
"status": 0,
|
||||||
|
"upgrade_time": 5
|
||||||
|
},
|
||||||
|
"get_inherit_info": {
|
||||||
|
"inherit_status": true
|
||||||
|
},
|
||||||
|
"get_latest_fw": {
|
||||||
|
"fw_size": 0,
|
||||||
|
"fw_ver": "1.2.0 Build 241219 Rel.163928",
|
||||||
|
"hw_id": "",
|
||||||
|
"need_to_upgrade": false,
|
||||||
|
"oem_id": "",
|
||||||
|
"release_date": "",
|
||||||
|
"release_note": "",
|
||||||
|
"type": 0
|
||||||
|
},
|
||||||
|
"get_schedule_rules": {
|
||||||
|
"enable": true,
|
||||||
|
"rule_list": [
|
||||||
|
{
|
||||||
|
"alarm_min": 0,
|
||||||
|
"cancel": false,
|
||||||
|
"clean_attr": {
|
||||||
|
"cistern": 2,
|
||||||
|
"clean_mode": 0,
|
||||||
|
"clean_number": 1,
|
||||||
|
"clean_order": false,
|
||||||
|
"suction": 2
|
||||||
|
},
|
||||||
|
"day": 21,
|
||||||
|
"enable": true,
|
||||||
|
"id": "S1",
|
||||||
|
"invalid": 0,
|
||||||
|
"mode": "repeat",
|
||||||
|
"month": 1,
|
||||||
|
"s_min": 515,
|
||||||
|
"start_remind": true,
|
||||||
|
"week_day": 62,
|
||||||
|
"year": 2025
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"schedule_rule_max_count": 32,
|
||||||
|
"start_index": 0,
|
||||||
|
"sum": 1
|
||||||
|
},
|
||||||
|
"get_wireless_scan_info": {
|
||||||
|
"ap_list": [
|
||||||
|
{
|
||||||
|
"key_type": "wpa2_psk",
|
||||||
|
"signal_level": 3,
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_type": "wpa2_psk",
|
||||||
|
"signal_level": 1,
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_type": "wpa2_psk",
|
||||||
|
"signal_level": 1,
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_type": "wpa2_psk",
|
||||||
|
"signal_level": 1,
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key_type": "wpa2_psk",
|
||||||
|
"signal_level": 1,
|
||||||
|
"ssid": "I01BU0tFRF9TU0lEIw=="
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_index": 0,
|
||||||
|
"sum": 5,
|
||||||
|
"wep_supported": true
|
||||||
|
},
|
||||||
|
"qs_component_nego": {
|
||||||
|
"component_list": [
|
||||||
|
{
|
||||||
|
"id": "quick_setup",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "iot_cloud",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "firmware",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ble_whole_setup",
|
||||||
|
"ver_code": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "inherit",
|
||||||
|
"ver_code": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"extra_info": {
|
||||||
|
"device_model": "RV30 Max",
|
||||||
|
"device_type": "SMART.TAPOROBOVAC"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,6 +117,10 @@ async def test_actions(
|
|||||||
async def test_post_update_hook(dev: SmartDevice, err_status: list, error: ErrorCode):
|
async def test_post_update_hook(dev: SmartDevice, err_status: list, error: ErrorCode):
|
||||||
"""Test that post update hook sets error states correctly."""
|
"""Test that post update hook sets error states correctly."""
|
||||||
clean = next(get_parent_and_child_modules(dev, Module.Clean))
|
clean = next(get_parent_and_child_modules(dev, Module.Clean))
|
||||||
|
assert clean
|
||||||
|
|
||||||
|
# _post_update_hook will pop an item off the status list so create a copy.
|
||||||
|
err_status = [e for e in err_status]
|
||||||
clean.data["getVacStatus"]["err_status"] = err_status
|
clean.data["getVacStatus"]["err_status"] = err_status
|
||||||
|
|
||||||
await clean._post_update_hook()
|
await clean._post_update_hook()
|
||||||
|
@ -1308,11 +1308,11 @@ async def test_discover_config(dev: Device, mocker, runner):
|
|||||||
expected = f"--device-family {cparam.device_family.value} --encrypt-type {cparam.encryption_type.value} {'--https' if cparam.https else '--no-https'}"
|
expected = f"--device-family {cparam.device_family.value} --encrypt-type {cparam.encryption_type.value} {'--https' if cparam.https else '--no-https'}"
|
||||||
assert expected in res.output
|
assert expected in res.output
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r"Attempt to connect to 127\.0\.0\.1 with \w+ \+ \w+ \+ \w+ failed",
|
r"Attempt to connect to 127\.0\.0\.1 with \w+ \+ \w+ \+ \w+ \+ \w+ failed",
|
||||||
res.output.replace("\n", ""),
|
res.output.replace("\n", ""),
|
||||||
)
|
)
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r"Attempt to connect to 127\.0\.0\.1 with \w+ \+ \w+ \+ \w+ succeeded",
|
r"Attempt to connect to 127\.0\.0\.1 with \w+ \+ \w+ \+ \w+ \+ \w+ succeeded",
|
||||||
res.output.replace("\n", ""),
|
res.output.replace("\n", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,8 +63,9 @@ def _get_connection_type_device_class(discovery_info):
|
|||||||
connection_type = DeviceConnectionParameters.from_values(
|
connection_type = DeviceConnectionParameters.from_values(
|
||||||
dr.device_type,
|
dr.device_type,
|
||||||
dr.mgt_encrypt_schm.encrypt_type,
|
dr.mgt_encrypt_schm.encrypt_type,
|
||||||
dr.mgt_encrypt_schm.lv,
|
login_version=dr.mgt_encrypt_schm.lv,
|
||||||
dr.mgt_encrypt_schm.is_support_https,
|
https=dr.mgt_encrypt_schm.is_support_https,
|
||||||
|
http_port=dr.mgt_encrypt_schm.http_port,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
connection_type = DeviceConnectionParameters.from_values(
|
connection_type = DeviceConnectionParameters.from_values(
|
||||||
|
@ -157,14 +157,15 @@ async def test_discover_single(discovery_mock, custom_port, mocker):
|
|||||||
)
|
)
|
||||||
# Make sure discovery does not call update()
|
# Make sure discovery does not call update()
|
||||||
assert update_mock.call_count == 0
|
assert update_mock.call_count == 0
|
||||||
if discovery_mock.default_port == 80:
|
if discovery_mock.default_port != 9999:
|
||||||
assert x.alias is None
|
assert x.alias is None
|
||||||
|
|
||||||
ct = DeviceConnectionParameters.from_values(
|
ct = DeviceConnectionParameters.from_values(
|
||||||
discovery_mock.device_type,
|
discovery_mock.device_type,
|
||||||
discovery_mock.encrypt_type,
|
discovery_mock.encrypt_type,
|
||||||
discovery_mock.login_version,
|
login_version=discovery_mock.login_version,
|
||||||
discovery_mock.https,
|
https=discovery_mock.https,
|
||||||
|
http_port=discovery_mock.http_port,
|
||||||
)
|
)
|
||||||
config = DeviceConfig(
|
config = DeviceConfig(
|
||||||
host=host,
|
host=host,
|
||||||
@ -425,9 +426,9 @@ async def test_discover_single_http_client(discovery_mock, mocker):
|
|||||||
|
|
||||||
x: Device = await Discover.discover_single(host)
|
x: Device = await Discover.discover_single(host)
|
||||||
|
|
||||||
assert x.config.uses_http == (discovery_mock.default_port == 80)
|
assert x.config.uses_http == (discovery_mock.default_port != 9999)
|
||||||
|
|
||||||
if discovery_mock.default_port == 80:
|
if discovery_mock.default_port != 9999:
|
||||||
assert x.protocol._transport._http_client.client != http_client
|
assert x.protocol._transport._http_client.client != http_client
|
||||||
x.config.http_client = http_client
|
x.config.http_client = http_client
|
||||||
assert x.protocol._transport._http_client.client == http_client
|
assert x.protocol._transport._http_client.client == http_client
|
||||||
@ -442,9 +443,9 @@ async def test_discover_http_client(discovery_mock, mocker):
|
|||||||
|
|
||||||
devices = await Discover.discover(discovery_timeout=0)
|
devices = await Discover.discover(discovery_timeout=0)
|
||||||
x: Device = devices[host]
|
x: Device = devices[host]
|
||||||
assert x.config.uses_http == (discovery_mock.default_port == 80)
|
assert x.config.uses_http == (discovery_mock.default_port != 9999)
|
||||||
|
|
||||||
if discovery_mock.default_port == 80:
|
if discovery_mock.default_port != 9999:
|
||||||
assert x.protocol._transport._http_client.client != http_client
|
assert x.protocol._transport._http_client.client != http_client
|
||||||
x.config.http_client = http_client
|
x.config.http_client = http_client
|
||||||
assert x.protocol._transport._http_client.client == http_client
|
assert x.protocol._transport._http_client.client == http_client
|
||||||
@ -674,8 +675,9 @@ async def test_discover_try_connect_all(discovery_mock, mocker):
|
|||||||
cparams = DeviceConnectionParameters.from_values(
|
cparams = DeviceConnectionParameters.from_values(
|
||||||
discovery_mock.device_type,
|
discovery_mock.device_type,
|
||||||
discovery_mock.encrypt_type,
|
discovery_mock.encrypt_type,
|
||||||
discovery_mock.login_version,
|
login_version=discovery_mock.login_version,
|
||||||
discovery_mock.https,
|
https=discovery_mock.https,
|
||||||
|
http_port=discovery_mock.http_port,
|
||||||
)
|
)
|
||||||
protocol = get_protocol(
|
protocol = get_protocol(
|
||||||
DeviceConfig(discovery_mock.ip, connection_type=cparams)
|
DeviceConfig(discovery_mock.ip, connection_type=cparams)
|
||||||
@ -687,10 +689,13 @@ async def test_discover_try_connect_all(discovery_mock, mocker):
|
|||||||
protocol_class = IotProtocol
|
protocol_class = IotProtocol
|
||||||
transport_class = XorTransport
|
transport_class = XorTransport
|
||||||
|
|
||||||
|
default_port = discovery_mock.default_port
|
||||||
|
|
||||||
async def _query(self, *args, **kwargs):
|
async def _query(self, *args, **kwargs):
|
||||||
if (
|
if (
|
||||||
self.__class__ is protocol_class
|
self.__class__ is protocol_class
|
||||||
and self._transport.__class__ is transport_class
|
and self._transport.__class__ is transport_class
|
||||||
|
and self._transport._port == default_port
|
||||||
):
|
):
|
||||||
return discovery_mock.query_data
|
return discovery_mock.query_data
|
||||||
raise KasaException("Unable to execute query")
|
raise KasaException("Unable to execute query")
|
||||||
@ -699,6 +704,7 @@ async def test_discover_try_connect_all(discovery_mock, mocker):
|
|||||||
if (
|
if (
|
||||||
self.protocol.__class__ is protocol_class
|
self.protocol.__class__ is protocol_class
|
||||||
and self.protocol._transport.__class__ is transport_class
|
and self.protocol._transport.__class__ is transport_class
|
||||||
|
and self.protocol._transport._port == default_port
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user