Move TAPO smartcamera out of experimental package (#1255)

Co-authored-by: Teemu R. <tpr@iki.fi>
This commit is contained in:
Steven B. 2024-11-13 19:59:42 +00:00 committed by GitHub
parent e55731c110
commit 6213b90f62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 59 additions and 36 deletions

View File

@ -40,7 +40,7 @@ from kasa.device_factory import get_protocol
from kasa.deviceconfig import DeviceEncryptionType, DeviceFamily from kasa.deviceconfig import DeviceEncryptionType, DeviceFamily
from kasa.discover import DiscoveryResult from kasa.discover import DiscoveryResult
from kasa.exceptions import SmartErrorCode from kasa.exceptions import SmartErrorCode
from kasa.experimental.smartcameraprotocol import ( from kasa.protocols.smartcameraprotocol import (
SmartCameraProtocol, SmartCameraProtocol,
_ChildCameraProtocolWrapper, _ChildCameraProtocolWrapper,
) )

View File

@ -10,9 +10,6 @@ from .device import Device
from .device_type import DeviceType from .device_type import DeviceType
from .deviceconfig import DeviceConfig from .deviceconfig import DeviceConfig
from .exceptions import KasaException, UnsupportedDeviceError from .exceptions import KasaException, UnsupportedDeviceError
from .experimental.smartcamera import SmartCamera
from .experimental.smartcameraprotocol import SmartCameraProtocol
from .experimental.sslaestransport import SslAesTransport
from .iot import ( from .iot import (
IotBulb, IotBulb,
IotDevice, IotDevice,
@ -27,7 +24,9 @@ from .protocols import (
IotProtocol, IotProtocol,
SmartProtocol, SmartProtocol,
) )
from .protocols.smartcameraprotocol import SmartCameraProtocol
from .smart import SmartDevice from .smart import SmartDevice
from .smartcamera.smartcamera import SmartCamera
from .transports import ( from .transports import (
AesTransport, AesTransport,
BaseTransport, BaseTransport,
@ -35,6 +34,7 @@ from .transports import (
KlapTransportV2, KlapTransportV2,
XorTransport, XorTransport,
) )
from .transports.sslaestransport import SslAesTransport
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -217,12 +217,9 @@ def get_protocol(
"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.AES.HTTPS": (SmartCameraProtocol, SslAesTransport),
} }
if not (prot_tran_cls := supported_device_protocols.get(protocol_transport_key)): if not (prot_tran_cls := supported_device_protocols.get(protocol_transport_key)):
from .experimental import Experimental return None
protocol_cls, transport_cls = prot_tran_cls
if Experimental.enabled() and protocol_transport_key == "SMART.AES.HTTPS": return protocol_cls(transport=transport_cls(config=config))
prot_tran_cls = (SmartCameraProtocol, SslAesTransport)
else:
return None
return prot_tran_cls[0](transport=prot_tran_cls[1](config=config))

View File

@ -55,9 +55,9 @@ from .modulemapping import ModuleName
if TYPE_CHECKING: if TYPE_CHECKING:
from . import interfaces from . import interfaces
from .device import Device from .device import Device
from .experimental import modules as experimental
from .iot import modules as iot from .iot import modules as iot
from .smart import modules as smart from .smart import modules as smart
from .smartcamera import modules as smartcamera
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -133,7 +133,7 @@ class Module(ABC):
TriggerLogs: Final[ModuleName[smart.TriggerLogs]] = ModuleName("TriggerLogs") TriggerLogs: Final[ModuleName[smart.TriggerLogs]] = ModuleName("TriggerLogs")
# SMARTCAMERA only modules # SMARTCAMERA only modules
Camera: Final[ModuleName[experimental.Camera]] = ModuleName("Camera") Camera: Final[ModuleName[smartcamera.Camera]] = ModuleName("Camera")
def __init__(self, device: Device, module: str) -> None: def __init__(self, device: Device, module: str) -> None:
self._device = device self._device = device

View File

@ -14,12 +14,12 @@ from ..exceptions import (
_RetryableError, _RetryableError,
) )
from ..json import dumps as json_dumps from ..json import dumps as json_dumps
from ..protocols import SmartProtocol from ..transports.sslaestransport import (
from .sslaestransport import (
SMART_AUTHENTICATION_ERRORS, SMART_AUTHENTICATION_ERRORS,
SMART_RETRYABLE_ERRORS, SMART_RETRYABLE_ERRORS,
SmartErrorCode, SmartErrorCode,
) )
from . import SmartProtocol
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -0,0 +1,5 @@
"""Package for supporting tapo-branded cameras."""
from .smartcamera import SmartCamera
__all__ = ["SmartCamera"]

View File

@ -7,11 +7,10 @@ from typing import Any
from ..device_type import DeviceType from ..device_type import DeviceType
from ..module import Module from ..module import Module
from ..protocols.smartcameraprotocol import _ChildCameraProtocolWrapper
from ..smart import SmartChildDevice, SmartDevice from ..smart import SmartChildDevice, SmartDevice
from .modules.childdevice import ChildDevice from .modules import ChildDevice, DeviceModule
from .modules.device import DeviceModule
from .smartcameramodule import SmartCameraModule from .smartcameramodule import SmartCameraModule
from .smartcameraprotocol import _ChildCameraProtocolWrapper
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -27,7 +27,7 @@ from ..exceptions import (
from ..httpclient import HttpClient from ..httpclient import HttpClient
from ..json import dumps as json_dumps from ..json import dumps as json_dumps
from ..json import loads as json_loads from ..json import loads as json_loads
from ..transports import AesEncyptionSession, BaseTransport from . import AesEncyptionSession, BaseTransport
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -11,9 +11,9 @@ from kasa import (
DeviceType, DeviceType,
Discover, Discover,
) )
from kasa.experimental.smartcamera import SmartCamera
from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotWallSwitch from kasa.iot import IotBulb, IotDimmer, IotLightStrip, IotPlug, IotStrip, IotWallSwitch
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
from kasa.smartcamera.smartcamera import SmartCamera
from .fakeprotocol_iot import FakeIotProtocol from .fakeprotocol_iot import FakeIotProtocol
from .fakeprotocol_smart import FakeSmartProtocol from .fakeprotocol_smart import FakeSmartProtocol

View File

@ -10,6 +10,7 @@ from kasa.transports.xortransport import XorEncryption
from .fakeprotocol_iot import FakeIotProtocol from .fakeprotocol_iot import FakeIotProtocol
from .fakeprotocol_smart import FakeSmartProtocol, FakeSmartTransport from .fakeprotocol_smart import FakeSmartProtocol, FakeSmartTransport
from .fakeprotocol_smartcamera import FakeSmartCameraProtocol
from .fixtureinfo import FixtureInfo, filter_fixtures, idgenerator from .fixtureinfo import FixtureInfo, filter_fixtures, idgenerator
DISCOVERY_MOCK_IP = "127.0.0.123" DISCOVERY_MOCK_IP = "127.0.0.123"
@ -126,12 +127,14 @@ def create_discovery_mock(ip: str, fixture_data: dict):
if "discovery_result" in fixture_data: if "discovery_result" in fixture_data:
discovery_data = {"result": fixture_data["discovery_result"].copy()} discovery_data = {"result": fixture_data["discovery_result"].copy()}
device_type = fixture_data["discovery_result"]["device_type"] discovery_result = fixture_data["discovery_result"]
encrypt_type = fixture_data["discovery_result"]["mgt_encrypt_schm"][ device_type = discovery_result["device_type"]
"encrypt_type" encrypt_type = discovery_result["mgt_encrypt_schm"].get(
] "encrypt_type", discovery_result.get("encrypt_info", {}).get("sym_schm")
login_version = fixture_data["discovery_result"]["mgt_encrypt_schm"].get("lv") )
https = fixture_data["discovery_result"]["mgt_encrypt_schm"]["is_support_https"]
login_version = discovery_result["mgt_encrypt_schm"].get("lv")
https = discovery_result["mgt_encrypt_schm"]["is_support_https"]
dm = _DiscoveryMock( dm = _DiscoveryMock(
ip, ip,
80, 80,
@ -172,7 +175,9 @@ def patch_discovery(fixture_infos: dict[str, FixtureInfo], mocker):
} }
protos = { protos = {
ip: FakeSmartProtocol(fixture_info.data, fixture_info.name) ip: FakeSmartProtocol(fixture_info.data, fixture_info.name)
if "SMART" in fixture_info.protocol if fixture_info.protocol in {"SMART", "SMART.CHILD"}
else FakeSmartCameraProtocol(fixture_info.data, fixture_info.name)
if fixture_info.protocol in {"SMARTCAMERA", "SMARTCAMERA.CHILD"}
else FakeIotProtocol(fixture_info.data, fixture_info.name) else FakeIotProtocol(fixture_info.data, fixture_info.name)
for ip, fixture_info in fixture_infos.items() for ip, fixture_info in fixture_infos.items()
} }
@ -197,7 +202,9 @@ def patch_discovery(fixture_infos: dict[str, FixtureInfo], mocker):
# update the protos for any host testing or the test overriding the first ip # update the protos for any host testing or the test overriding the first ip
protos[host] = ( protos[host] = (
FakeSmartProtocol(fixture_info.data, fixture_info.name) FakeSmartProtocol(fixture_info.data, fixture_info.name)
if "SMART" in fixture_info.protocol if fixture_info.protocol in {"SMART", "SMART.CHILD"}
else FakeSmartCameraProtocol(fixture_info.data, fixture_info.name)
if fixture_info.protocol in {"SMARTCAMERA", "SMARTCAMERA.CHILD"}
else FakeIotProtocol(fixture_info.data, fixture_info.name) else FakeIotProtocol(fixture_info.data, fixture_info.name)
) )
port = ( port = (

View File

@ -4,7 +4,7 @@ import copy
from json import loads as json_loads from json import loads as json_loads
from kasa import Credentials, DeviceConfig, SmartProtocol from kasa import Credentials, DeviceConfig, SmartProtocol
from kasa.experimental.smartcameraprotocol import SmartCameraProtocol from kasa.protocols.smartcameraprotocol import SmartCameraProtocol
from kasa.transports.basetransport import BaseTransport from kasa.transports.basetransport import BaseTransport
from .fakeprotocol_smart import FakeSmartTransport from .fakeprotocol_smart import FakeSmartTransport
@ -136,6 +136,12 @@ class FakeSmartCameraTransport(BaseTransport):
"basic", "basic",
"zone_id", "zone_id",
], ],
("led", "config", "enabled"): [
"getLedStatus",
"led",
"config",
"enabled",
],
} }
async def _send_request(self, request_dict: dict): async def _send_request(self, request_dict: dict):

View File

@ -8,8 +8,8 @@ from typing import Iterable, NamedTuple
from kasa.device_factory import _get_device_type_from_sys_info from kasa.device_factory import _get_device_type_from_sys_info
from kasa.device_type import DeviceType from kasa.device_type import DeviceType
from kasa.experimental.smartcamera import SmartCamera
from kasa.smart.smartdevice import SmartDevice from kasa.smart.smartdevice import SmartDevice
from kasa.smartcamera.smartcamera import SmartCamera
class FixtureInfo(NamedTuple): class FixtureInfo(NamedTuple):
@ -179,7 +179,7 @@ def filter_fixtures(
filtered = [] filtered = []
if protocol_filter is None: if protocol_filter is None:
protocol_filter = {"IOT", "SMART"} protocol_filter = {"IOT", "SMART", "SMARTCAMERA"}
for fixture_data in fixture_list: for fixture_data in fixture_list:
if data_root_filter and data_root_filter not in fixture_data.data: if data_root_filter and data_root_filter not in fixture_data.data:
continue continue

View File

@ -43,6 +43,7 @@ from kasa.cli.wifi import wifi
from kasa.discover import Discover, DiscoveryResult from kasa.discover import Discover, DiscoveryResult
from kasa.iot import IotDevice from kasa.iot import IotDevice
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
from kasa.smartcamera import SmartCamera
from .conftest import ( from .conftest import (
device_smart, device_smart,
@ -178,6 +179,9 @@ async def test_state(dev, turn_on, runner):
@turn_on @turn_on
async def test_toggle(dev, turn_on, runner): async def test_toggle(dev, turn_on, runner):
if isinstance(dev, SmartCamera) and dev.device_type == DeviceType.Hub:
pytest.skip(reason="Hub cannot toggle state")
await handle_turn_on(dev, turn_on) await handle_turn_on(dev, turn_on)
await dev.update() await dev.update()
assert dev.is_on == turn_on assert dev.is_on == turn_on
@ -208,7 +212,9 @@ async def test_raw_command(dev, mocker, runner):
update = mocker.patch.object(dev, "update") update = mocker.patch.object(dev, "update")
from kasa.smart import SmartDevice from kasa.smart import SmartDevice
if isinstance(dev, SmartDevice): if isinstance(dev, SmartCamera):
params = ["na", "getDeviceInfo"]
elif isinstance(dev, SmartDevice):
params = ["na", "get_device_info"] params = ["na", "get_device_info"]
else: else:
params = ["system", "get_sysinfo"] params = ["system", "get_sysinfo"]

View File

@ -19,6 +19,7 @@ from kasa import (
) )
from kasa.device_factory import ( from kasa.device_factory import (
Device, Device,
SmartCamera,
SmartDevice, SmartDevice,
_get_device_type_from_sys_info, _get_device_type_from_sys_info,
connect, connect,
@ -177,7 +178,9 @@ async def test_connect_http_client(discovery_mock, mocker):
async def test_device_types(dev: Device): async def test_device_types(dev: Device):
await dev.update() await dev.update()
if isinstance(dev, SmartDevice): if isinstance(dev, SmartCamera):
res = SmartCamera._get_device_type_from_sysinfo(dev.sys_info)
elif isinstance(dev, SmartDevice):
assert dev._discovery_info assert dev._discovery_info
device_type = cast(str, dev._discovery_info["result"]["device_type"]) device_type = cast(str, dev._discovery_info["result"]["device_type"])
res = SmartDevice._get_device_type_from_components( res = SmartDevice._get_device_type_from_components(

View File

@ -18,13 +18,13 @@ from kasa.exceptions import (
KasaException, KasaException,
SmartErrorCode, SmartErrorCode,
) )
from kasa.experimental.sslaestransport import ( from kasa.httpclient import HttpClient
from kasa.transports.aestransport import AesEncyptionSession
from kasa.transports.sslaestransport import (
SslAesTransport, SslAesTransport,
TransportState, TransportState,
_sha256_hash, _sha256_hash,
) )
from kasa.httpclient import HttpClient
from kasa.transports.aestransport import AesEncyptionSession
# Transport tests are not designed for real devices # Transport tests are not designed for real devices
pytestmark = [pytest.mark.requires_dummy] pytestmark = [pytest.mark.requires_dummy]