Merge remote-tracking branch 'upstream/master' into feat/smartcam_passthrough

This commit is contained in:
Steven B 2024-12-19 17:45:54 +00:00
commit 7f8f823eac
No known key found for this signature in database
GPG Key ID: 6D5B46B3679F2A43
15 changed files with 2994 additions and 49 deletions

View File

@ -201,7 +201,7 @@ The following devices have been tested and confirmed as working. If your device
- **Wall Switches**: S500D, S505, S505D
- **Bulbs**: L510B, L510E, L530E, L630
- **Light Strips**: L900-10, L900-5, L920-5, L930-5
- **Cameras**: C210, C520WS, TC65
- **Cameras**: C100, C210, C325WB, C520WS, TC65, TC70
- **Hubs**: H100, H200
- **Hub-Connected Devices[^3]**: S200B, S200D, T100, T110, T300, T310, T315

View File

@ -258,13 +258,19 @@ All Tapo devices require authentication.<br>Hub-Connected Devices may work acros
### Cameras
- **C100**
- Hardware: 4.0 / Firmware: 1.3.14
- **C210**
- Hardware: 2.0 (EU) / Firmware: 1.4.2
- Hardware: 2.0 (EU) / Firmware: 1.4.3
- **C325WB**
- Hardware: 1.0 (EU) / Firmware: 1.1.17
- **C520WS**
- Hardware: 1.0 (US) / Firmware: 1.2.8
- **TC65**
- Hardware: 1.0 / Firmware: 1.3.9
- **TC70**
- Hardware: 3.0 / Firmware: 1.3.11
### Hubs

View File

@ -123,14 +123,19 @@ async def list(ctx):
async def print_discovered(dev: Device):
cparams = dev.config.connection_type
infostr = (
f"{dev.host:<15} {cparams.device_family.value:<20} "
f"{cparams.encryption_type.value:<7}"
f"{dev.host:<15} {dev.model:<9} {cparams.device_family.value:<20} "
f"{cparams.encryption_type.value:<7} {cparams.https:<5} "
f"{cparams.login_version or '-':<3}"
)
async with sem:
try:
await dev.update()
except AuthenticationError:
echo(f"{infostr} - Authentication failed")
except TimeoutError:
echo(f"{infostr} - Timed out")
except Exception as ex:
echo(f"{infostr} - Error: {ex}")
else:
echo(f"{infostr} {dev.alias}")
@ -138,7 +143,10 @@ async def list(ctx):
if host := unsupported_exception.host:
echo(f"{host:<15} UNSUPPORTED DEVICE")
echo(f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}")
echo(
f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} "
f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}"
)
return await _discover(
ctx,
print_discovered=print_discovered,

View File

@ -159,6 +159,7 @@ class Module(ABC):
# SMARTCAM only modules
Camera: Final[ModuleName[smartcam.Camera]] = ModuleName("Camera")
LensMask: Final[ModuleName[smartcam.LensMask]] = ModuleName("LensMask")
def __init__(self, device: Device, module: str) -> None:
self._device = device

View File

@ -6,6 +6,7 @@ from .childdevice import ChildDevice
from .device import DeviceModule
from .homekit import HomeKit
from .led import Led
from .lensmask import LensMask
from .matter import Matter
from .pantilt import PanTilt
from .time import Time
@ -20,4 +21,5 @@ __all__ = [
"Time",
"HomeKit",
"Matter",
"LensMask",
]

View File

@ -1,16 +1,18 @@
"""Implementation of device module."""
"""Implementation of camera module."""
from __future__ import annotations
import base64
import logging
from enum import StrEnum
from typing import Annotated
from urllib.parse import quote_plus
from ...credentials import Credentials
from ...device_type import DeviceType
from ...feature import Feature
from ...json import loads as json_loads
from ...module import FeatureAttribute, Module
from ..smartcammodule import SmartCamModule
_LOGGER = logging.getLogger(__name__)
@ -29,17 +31,15 @@ class StreamResolution(StrEnum):
class Camera(SmartCamModule):
"""Implementation of device module."""
QUERY_GETTER_NAME = "getLensMaskConfig"
QUERY_MODULE_NAME = "lens_mask"
QUERY_SECTION_NAMES = "lens_mask_info"
def _initialize_features(self) -> None:
"""Initialize features after the initial update."""
if Module.LensMask in self._device.modules:
self._add_feature(
Feature(
self._device,
id="state",
name="State",
container=self,
attribute_getter="is_on",
attribute_setter="set_state",
type=Feature.Type.Switch,
@ -49,8 +49,20 @@ class Camera(SmartCamModule):
@property
def is_on(self) -> bool:
"""Return the device id."""
return self.data["lens_mask_info"]["enabled"] == "off"
"""Return the device on state."""
if lens_mask := self._device.modules.get(Module.LensMask):
return not lens_mask.enabled
return True
async def set_state(self, on: bool) -> Annotated[dict, FeatureAttribute()]:
"""Set the device on state.
If the device does not support setting state will do nothing.
"""
if lens_mask := self._device.modules.get(Module.LensMask):
# Turning off enables the privacy mask which is why value is reversed.
return await lens_mask.set_enabled(not on)
return {}
def _get_credentials(self) -> Credentials | None:
"""Get credentials from ."""
@ -109,14 +121,6 @@ class Camera(SmartCamModule):
"""Return the onvif url."""
return f"http://{self._device.host}:{ONVIF_PORT}/onvif/device_service"
async def set_state(self, on: bool) -> dict:
"""Set the device state."""
# Turning off enables the privacy mask which is why value is reversed.
params = {"enabled": "off" if on else "on"}
return await self._device._query_setter_helper(
"setLensMaskConfig", self.QUERY_MODULE_NAME, "lens_mask_info", params
)
async def _check_supported(self) -> bool:
"""Additional check to see if the module is supported by the device."""
return self._device.device_type is DeviceType.Camera

View File

@ -0,0 +1,31 @@
"""Implementation of lens mask privacy module."""
from __future__ import annotations
import logging
from ..smartcammodule import SmartCamModule
_LOGGER = logging.getLogger(__name__)
class LensMask(SmartCamModule):
"""Implementation of lens mask module."""
REQUIRED_COMPONENT = "lensMask"
QUERY_GETTER_NAME = "getLensMaskConfig"
QUERY_MODULE_NAME = "lens_mask"
QUERY_SECTION_NAMES = "lens_mask_info"
@property
def enabled(self) -> bool:
"""Return the lens mask state."""
return self.data["lens_mask_info"]["enabled"] == "on"
async def set_enabled(self, enable: bool) -> dict:
"""Set the lens mask state."""
params = {"enabled": "on" if enable else "off"}
return await self._device._query_setter_helper(
"setLensMaskConfig", self.QUERY_MODULE_NAME, "lens_mask_info", params
)

View File

@ -134,6 +134,11 @@ class SmartCamDevice(SmartDevice):
if (
mod.REQUIRED_COMPONENT
and mod.REQUIRED_COMPONENT not in self._components
# Always add Camera module to cameras
and (
mod._module_name() != Module.Camera
or self._device_type is not DeviceType.Camera
)
):
continue
module = mod(self, mod._module_name())

View File

@ -160,6 +160,17 @@ def create_discovery_mock(ip: str, fixture_data: dict):
login_version: int | None = None
port_override: int | None = None
@property
def model(self) -> str:
dd = self.discovery_data
model_region = (
dd["result"]["device_model"]
if self.discovery_port == 20002
else dd["system"]["get_sysinfo"]["model"]
)
model, _, _ = model_region.partition("(")
return model
@property
def _datagram(self) -> bytes:
if self.default_port == 9999:
@ -178,7 +189,10 @@ def create_discovery_mock(ip: str, fixture_data: dict):
"encrypt_type", discovery_result.get("encrypt_info", {}).get("sym_schm")
)
login_version = discovery_result["mgt_encrypt_schm"].get("lv")
if not (login_version := discovery_result["mgt_encrypt_schm"].get("lv")) and (
et := discovery_result.get("encrypt_type")
):
login_version = max([int(i) for i in et])
https = discovery_result["mgt_encrypt_schm"]["is_support_https"]
dm = _DiscoveryMock(
ip,

View File

@ -0,0 +1,779 @@
{
"discovery_result": {
"error_code": 0,
"result": {
"decrypted_data": {
"connect_ssid": "#MASKED_SSID#",
"connect_type": "wireless",
"device_id": "0000000000000000000000000000000000000000",
"http_port": 443,
"owner": "00000000000000000000000000000000",
"sd_status": "offline"
},
"device_id": "00000000000000000000000000000000",
"device_model": "C100",
"device_name": "#MASKED_NAME#",
"device_type": "SMART.IPCAMERA",
"encrypt_info": {
"data": "",
"key": "",
"sym_schm": "AES"
},
"encrypt_type": [
"3"
],
"factory_default": false,
"firmware_version": "1.3.14 Build 240513 Rel.43631n(5553)",
"hardware_version": "4.0",
"ip": "127.0.0.123",
"is_support_iot_cloud": true,
"mac": "F0-A7-31-00-00-00",
"mgt_encrypt_schm": {
"is_support_https": true
},
"protocol_version": 1
}
},
"getAlertPlan": {
"msg_alarm_plan": {
"chn1_msg_alarm_plan": {
".name": "chn1_msg_alarm_plan",
".type": "plan",
"alarm_plan_1": "0000-0000,127",
"enabled": "off"
}
}
},
"getAppComponentList": {
"app_component": {
"app_component_list": [
{
"name": "sdCard",
"version": 1
},
{
"name": "timezone",
"version": 1
},
{
"name": "system",
"version": 3
},
{
"name": "led",
"version": 1
},
{
"name": "playback",
"version": 4
},
{
"name": "detection",
"version": 3
},
{
"name": "alert",
"version": 1
},
{
"name": "firmware",
"version": 2
},
{
"name": "account",
"version": 2
},
{
"name": "quickSetup",
"version": 1
},
{
"name": "video",
"version": 2
},
{
"name": "lensMask",
"version": 2
},
{
"name": "lightFrequency",
"version": 1
},
{
"name": "dayNightMode",
"version": 1
},
{
"name": "osd",
"version": 2
},
{
"name": "record",
"version": 1
},
{
"name": "videoRotation",
"version": 1
},
{
"name": "audio",
"version": 2
},
{
"name": "diagnose",
"version": 1
},
{
"name": "msgPush",
"version": 3
},
{
"name": "deviceShare",
"version": 1
},
{
"name": "tapoCare",
"version": 1
},
{
"name": "blockZone",
"version": 1
},
{
"name": "personDetection",
"version": 2
},
{
"name": "babyCryDetection",
"version": 1
},
{
"name": "needSubscriptionServiceList",
"version": 1
},
{
"name": "iotCloud",
"version": 1
},
{
"name": "recordDownload",
"version": 1
},
{
"name": "detectionRegion",
"version": 2
},
{
"name": "staticIp",
"version": 1
}
]
}
},
"getAudioConfig": {
"audio_config": {
"microphone": {
".name": "microphone",
".type": "audio_config",
"channels": "1",
"encode_type": "G711alaw",
"mute": "off",
"noise_cancelling": "on",
"sampling_rate": "8",
"volume": "100"
},
"speaker": {
".name": "speaker",
".type": "audio_config",
"volume": "40"
}
}
},
"getBCDConfig": {
"sound_detection": {
"bcd": {
".name": "bcd",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "off",
"sensitivity": "medium"
}
}
},
"getCircularRecordingConfig": {
"harddisk_manage": {
"harddisk": {
".name": "harddisk",
".type": "storage",
"loop": "on"
}
}
},
"getClockStatus": {
"system": {
"clock_status": {
"local_time": "2024-12-15 11:11:55",
"seconds_from_1970": 1734279115
}
}
},
"getConnectionType": {
"link_type": "wifi",
"rssi": "4",
"rssiValue": -15,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
"getDetectionConfig": {
"motion_detection": {
"motion_det": {
".name": "motion_det",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "off",
"sensitivity": "medium"
}
}
},
"getDeviceInfo": {
"device_info": {
"basic_info": {
"avatar": "camera c100",
"barcode": "",
"dev_id": "0000000000000000000000000000000000000000",
"device_alias": "#MASKED_NAME#",
"device_info": "C100 4.0 IPC",
"device_model": "C100",
"device_name": "#MASKED_NAME#",
"device_type": "SMART.IPCAMERA",
"features": "3",
"ffs": false,
"has_set_location_info": 1,
"hw_desc": "00000000000000000000000000000000",
"hw_version": "4.0",
"is_cal": true,
"latitude": 0,
"longitude": 0,
"mac": "F0-A7-31-00-00-00",
"oem_id": "00000000000000000000000000000000",
"sw_version": "1.3.14 Build 240513 Rel.43631n(5553)"
}
}
},
"getFirmwareAutoUpgradeConfig": {
"auto_upgrade": {
"common": {
".name": "common",
".type": "on_off",
"enabled": "off",
"random_range": "120",
"time": "03:00"
}
}
},
"getFirmwareUpdateStatus": {
"cloud_config": {
"upgrade_status": {
"lastUpgradingSuccess": true,
"state": "normal"
}
}
},
"getLastAlarmInfo": {
"system": {
"last_alarm_info": {
"last_alarm_time": "",
"last_alarm_type": ""
}
}
},
"getLdc": {
"image": {
"common": {
".name": "common",
".type": "para",
"area_compensation": "default",
"chroma": "50",
"contrast": "50",
"dehaze": "off",
"exp_gain": "0",
"exp_type": "auto",
"focus_limited": "600",
"focus_type": "semi_auto",
"high_light_compensation": "off",
"inf_delay": "10",
"inf_end_time": "21600",
"inf_sensitivity": "1",
"inf_start_time": "64800",
"inf_type": "auto",
"light_freq_mode": "auto",
"lock_blue_colton": "0",
"lock_blue_gain": "0",
"lock_gb_gain": "0",
"lock_gr_gain": "0",
"lock_green_colton": "0",
"lock_red_colton": "0",
"lock_red_gain": "0",
"lock_source": "local",
"luma": "50",
"saturation": "50",
"sharpness": "50",
"shutter": "1/25",
"smartir": "off",
"smartir_level": "100",
"wb_B_gain": "50",
"wb_G_gain": "50",
"wb_R_gain": "50",
"wb_type": "auto",
"wd_gain": "50",
"wide_dynamic": "off"
},
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getLedStatus": {
"led": {
"config": {
".name": "config",
".type": "led",
"enabled": "on"
}
}
},
"getLensMaskConfig": {
"lens_mask": {
"lens_mask_info": {
".name": "lens_mask_info",
".type": "lens_mask_info",
"enabled": "off"
}
}
},
"getLightFrequencyInfo": {
"image": {
"common": {
".name": "common",
".type": "para",
"area_compensation": "default",
"chroma": "50",
"contrast": "50",
"dehaze": "off",
"exp_gain": "0",
"exp_type": "auto",
"focus_limited": "600",
"focus_type": "semi_auto",
"high_light_compensation": "off",
"inf_delay": "10",
"inf_end_time": "21600",
"inf_sensitivity": "1",
"inf_start_time": "64800",
"inf_type": "auto",
"light_freq_mode": "auto",
"lock_blue_colton": "0",
"lock_blue_gain": "0",
"lock_gb_gain": "0",
"lock_gr_gain": "0",
"lock_green_colton": "0",
"lock_red_colton": "0",
"lock_red_gain": "0",
"lock_source": "local",
"luma": "50",
"saturation": "50",
"sharpness": "50",
"shutter": "1/25",
"smartir": "off",
"smartir_level": "100",
"wb_B_gain": "50",
"wb_G_gain": "50",
"wb_R_gain": "50",
"wb_type": "auto",
"wd_gain": "50",
"wide_dynamic": "off"
}
}
},
"getMediaEncrypt": {
"cet": {
"media_encrypt": {
".name": "media_encrypt",
".type": "on_off",
"enabled": "on"
}
}
},
"getMsgPushConfig": {
"msg_push": {
"chn1_msg_push_info": {
".name": "chn1_msg_push_info",
".type": "on_off",
"notification_enabled": "off",
"rich_notification_enabled": "off"
}
}
},
"getNightVisionModeConfig": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getPersonDetectionConfig": {
"people_detection": {
"detection": {
".name": "detection",
".type": "on_off",
"enabled": "off",
"sensitivity": "60"
}
}
},
"getRecordPlan": {
"record_plan": {
"chn1_channel": {
".name": "chn1_channel",
".type": "plan",
"enabled": "on",
"friday": "[\"0000-2400:2\"]",
"monday": "[\"0000-2400:2\"]",
"saturday": "[\"0000-2400:2\"]",
"sunday": "[\"0000-2400:2\"]",
"thursday": "[\"0000-2400:2\"]",
"tuesday": "[\"0000-2400:2\"]",
"wednesday": "[\"0000-2400:2\"]"
}
}
},
"getRotationStatus": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getSdCardStatus": {
"harddisk_manage": {
"hd_info": [
{
"hd_info_1": {
"detect_status": "offline",
"disk_name": "1",
"free_space": "0B",
"loop_record_status": "0",
"msg_push_free_space": "0B",
"msg_push_total_space": "0B",
"percent": "0",
"picture_free_space": "0B",
"picture_total_space": "0B",
"record_duration": "0",
"record_free_duration": "0",
"record_start_time": "0",
"rw_attr": "r",
"status": "offline",
"total_space": "0B",
"type": "local",
"video_free_space": "0B",
"video_total_space": "0B",
"write_protect": "0"
}
}
]
}
},
"getTamperDetectionConfig": {
"tamper_detection": {
"tamper_det": {
".name": "tamper_det",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "off",
"sensitivity": "medium"
}
}
},
"getTimezone": {
"system": {
"basic": {
".name": "basic",
".type": "setting",
"timezone": "UTC-05:00",
"timing_mode": "manual",
"zone_id": "America/New_York"
}
}
},
"getVideoCapability": {
"video_capability": {
"main": {
".name": "main",
".type": "capability",
"bitrate_types": [
"cbr",
"vbr"
],
"bitrates": [
"256",
"512",
"1024",
"2048"
],
"encode_types": [
"H264"
],
"frame_rates": [
"65537",
"65546",
"65551"
],
"qualitys": [
"1",
"3",
"5"
],
"resolutions": [
"1920*1080",
"1280*720",
"640*360"
]
}
}
},
"getVideoQualities": {
"video": {
"main": {
".name": "main",
".type": "stream",
"bitrate": "1024",
"bitrate_type": "vbr",
"encode_type": "H264",
"frame_rate": "65551",
"gop_factor": "2",
"name": "VideoEncoder_1",
"quality": "3",
"resolution": "1920*1080",
"stream_type": "general"
}
}
},
"getWhitelampConfig": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getWhitelampStatus": {
"rest_time": 0,
"status": 0
},
"get_audio_capability": {
"get": {
"audio_capability": {
"device_microphone": {
".name": "device_microphone",
".type": "capability",
"aec": "1",
"channels": "1",
"echo_cancelling": "0",
"encode_type": [
"G711alaw"
],
"half_duplex": "1",
"mute": "1",
"noise_cancelling": "1",
"sampling_rate": [
"8"
],
"volume": "1"
},
"device_speaker": {
".name": "device_speaker",
".type": "capability",
"channels": "1",
"decode_type": [
"G711"
],
"mute": "0",
"sampling_rate": [
"8"
],
"volume": "1"
}
}
}
},
"get_audio_config": {
"get": {
"audio_config": {
"microphone": {
".name": "microphone",
".type": "audio_config",
"channels": "1",
"encode_type": "G711alaw",
"mute": "off",
"noise_cancelling": "on",
"sampling_rate": "8",
"volume": "100"
},
"speaker": {
".name": "speaker",
".type": "audio_config",
"volume": "40"
}
}
}
},
"get_cet": {
"get": {
"cet": {
"vhttpd": {
".name": "vhttpd",
".type": "server",
"port": "8800"
}
}
}
},
"get_function": {
"get": {
"function": {
"module_spec": {
".name": "module_spec",
".type": "module-spec",
"ae_weighting_table_resolution": "5*5",
"ai_enhance_capability": "1",
"app_version": "1.0.0",
"audio": [
"speaker",
"microphone"
],
"audioexception_detection": "0",
"auth_encrypt": "1",
"backlight_coexistence": "1",
"change_password": "1",
"client_info": "1",
"cloud_storage_version": "1.0",
"custom_area_compensation": "1",
"custom_auto_mode_exposure_level": "0",
"device_share": [
"preview",
"playback",
"voice",
"cloud_storage"
],
"download": [
"video"
],
"events": [
"motion",
"tamper"
],
"greeter": "1.0",
"http_system_state_audio_support": "1",
"intrusion_detection": "1",
"led": "1",
"lens_mask": "1",
"linecrossing_detection": "1",
"linkage_capability": "1",
"local_storage": "1",
"media_encrypt": "1",
"msg_alarm": "1",
"msg_alarm_list": [
"sound",
"light"
],
"msg_alarm_separate_list": [
"light",
"sound"
],
"msg_push": "1",
"multi_user": "0",
"multicast": "0",
"network": [
"wifi"
],
"ota_upgrade": "1",
"p2p_support_versions": [
"1.1"
],
"playback": [
"local",
"p2p",
"relay"
],
"playback_scale": "1",
"preview": [
"local",
"p2p",
"relay"
],
"privacy_mask_api_version": "1.0",
"record_max_slot_cnt": "10",
"record_type": [
"timing",
"motion"
],
"relay_support_versions": [
"1.3"
],
"reonboarding": "1",
"smart_detection": "1",
"smart_msg_push_capability": "1",
"ssl_cer_version": "1.0",
"storage_api_version": "2.2",
"stream_max_sessions": "10",
"streaming_support_versions": [
"1.0"
],
"target_track": "0",
"timing_reboot": "1",
"verification_change_password": "1",
"video_codec": [
"h264"
],
"video_detection_digital_sensitivity": "1",
"wifi_cascade_connection": "1",
"wifi_connection_info": "1",
"wireless_hotspot": "1"
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -7,8 +7,8 @@
"connect_type": "wireless",
"device_id": "0000000000000000000000000000000000000000",
"http_port": 443,
"last_alarm_time": "0",
"last_alarm_type": "",
"last_alarm_time": "1734386954",
"last_alarm_type": "motion",
"owner": "00000000000000000000000000000000",
"sd_status": "offline"
},
@ -283,15 +283,22 @@
"getClockStatus": {
"system": {
"clock_status": {
"local_time": "2024-12-02 13:12:15",
"seconds_from_1970": 1733163135
"local_time": "2024-12-16 17:09:43",
"seconds_from_1970": 1734386983
}
}
},
"getConnectStatus": {
"onboarding": {
"get_connect_status": {
"status": 0
}
}
},
"getConnectionType": {
"link_type": "wifi",
"rssi": "4",
"rssiValue": -47,
"rssiValue": -45,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
"getDetectionConfig": {
@ -355,8 +362,8 @@
"getLastAlarmInfo": {
"system": {
"last_alarm_info": {
"last_alarm_time": "0",
"last_alarm_type": ""
"last_alarm_time": "1734386954",
"last_alarm_type": "motion"
}
}
},
@ -1025,5 +1032,119 @@
}
}
}
},
"scanApList": {
"onboarding": {
"scan": {
"ap_list": [
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 2,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 2,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 3,
"bssid": "000000000000",
"encryption": 2,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
{
"auth": 4,
"bssid": "000000000000",
"encryption": 3,
"rssi": 1,
"ssid": "I01BU0tFRF9TU0lEIw=="
}
],
"wpa3_supported": "false"
}
}
}
}

View File

@ -0,0 +1,870 @@
{
"discovery_result": {
"error_code": 0,
"result": {
"decrypted_data": {
"connect_ssid": "#MASKED_SSID#",
"connect_type": "wireless",
"device_id": "0000000000000000000000000000000000000000",
"http_port": 443,
"last_alarm_time": "1734271551",
"last_alarm_type": "motion",
"owner": "00000000000000000000000000000000",
"sd_status": "offline"
},
"device_id": "00000000000000000000000000000000",
"device_model": "TC70",
"device_name": "#MASKED_NAME#",
"device_type": "SMART.IPCAMERA",
"encrypt_info": {
"data": "",
"key": "",
"sym_schm": "AES"
},
"encrypt_type": [
"3"
],
"factory_default": false,
"firmware_version": "1.3.11 Build 231121 Rel.39429n(4555)",
"hardware_version": "3.0",
"ip": "127.0.0.123",
"is_support_iot_cloud": true,
"mac": "5C-E9-31-00-00-00",
"mgt_encrypt_schm": {
"is_support_https": true
}
}
},
"getAlertPlan": {
"msg_alarm_plan": {
"chn1_msg_alarm_plan": {
".name": "chn1_msg_alarm_plan",
".type": "plan",
"alarm_plan_1": "0000-0000,127",
"enabled": "off"
}
}
},
"getAppComponentList": {
"app_component": {
"app_component_list": [
{
"name": "sdCard",
"version": 1
},
{
"name": "timezone",
"version": 1
},
{
"name": "system",
"version": 3
},
{
"name": "led",
"version": 1
},
{
"name": "playback",
"version": 4
},
{
"name": "detection",
"version": 1
},
{
"name": "alert",
"version": 1
},
{
"name": "firmware",
"version": 2
},
{
"name": "account",
"version": 1
},
{
"name": "quickSetup",
"version": 1
},
{
"name": "ptz",
"version": 1
},
{
"name": "video",
"version": 2
},
{
"name": "lensMask",
"version": 2
},
{
"name": "lightFrequency",
"version": 1
},
{
"name": "dayNightMode",
"version": 1
},
{
"name": "osd",
"version": 2
},
{
"name": "record",
"version": 1
},
{
"name": "videoRotation",
"version": 1
},
{
"name": "audio",
"version": 2
},
{
"name": "diagnose",
"version": 1
},
{
"name": "msgPush",
"version": 3
},
{
"name": "deviceShare",
"version": 1
},
{
"name": "tapoCare",
"version": 1
},
{
"name": "blockZone",
"version": 1
},
{
"name": "personDetection",
"version": 1
},
{
"name": "targetTrack",
"version": 1
},
{
"name": "babyCryDetection",
"version": 1
},
{
"name": "needSubscriptionServiceList",
"version": 1
},
{
"name": "iotCloud",
"version": 1
},
{
"name": "recordDownload",
"version": 1
}
]
}
},
"getAudioConfig": {
"audio_config": {
"microphone": {
".name": "microphone",
".type": "audio_config",
"channels": "1",
"encode_type": "G711alaw",
"mute": "off",
"noise_cancelling": "on",
"sampling_rate": "8",
"volume": "100"
},
"speaker": {
".name": "speaker",
".type": "audio_config",
"volume": "100"
}
}
},
"getBCDConfig": {
"sound_detection": {
"bcd": {
".name": "bcd",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "off",
"sensitivity": "medium"
}
}
},
"getCircularRecordingConfig": {
"harddisk_manage": {
"harddisk": {
".name": "harddisk",
".type": "storage",
"loop": "on"
}
}
},
"getClockStatus": {
"system": {
"clock_status": {
"local_time": "2024-12-18 22:59:11",
"seconds_from_1970": 1734562751
}
}
},
"getConnectionType": {
"link_type": "wifi",
"rssi": "4",
"rssiValue": -50,
"ssid": "I01BU0tFRF9TU0lEIw=="
},
"getDetectionConfig": {
"motion_detection": {
"motion_det": {
".name": "motion_det",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "on",
"sensitivity": "medium"
}
}
},
"getDeviceInfo": {
"device_info": {
"basic_info": {
"avatar": "camera c212",
"barcode": "",
"dev_id": "0000000000000000000000000000000000000000",
"device_alias": "#MASKED_NAME#",
"device_info": "TC70 3.0 IPC",
"device_model": "TC70",
"device_name": "#MASKED_NAME#",
"device_type": "SMART.IPCAMERA",
"features": "3",
"ffs": false,
"has_set_location_info": 1,
"hw_desc": "00000000000000000000000000000000",
"hw_version": "3.0",
"is_cal": true,
"latitude": 0,
"longitude": 0,
"mac": "5C-E9-31-00-00-00",
"oem_id": "00000000000000000000000000000000",
"sw_version": "1.3.11 Build 231121 Rel.39429n(4555)"
}
}
},
"getFirmwareAutoUpgradeConfig": {
"auto_upgrade": {
"common": {
".name": "common",
".type": "on_off",
"enabled": "off",
"random_range": "120",
"time": "03:00"
}
}
},
"getFirmwareUpdateStatus": {
"cloud_config": {
"upgrade_status": {
"lastUpgradingSuccess": false,
"state": "normal"
}
}
},
"getLastAlarmInfo": {
"system": {
"last_alarm_info": {
"last_alarm_time": "1734271551",
"last_alarm_type": "motion"
}
}
},
"getLdc": {
"image": {
"common": {
".name": "common",
".type": "para",
"area_compensation": "default",
"chroma": "50",
"contrast": "50",
"dehaze": "off",
"exp_gain": "0",
"exp_type": "auto",
"focus_limited": "600",
"focus_type": "semi_auto",
"high_light_compensation": "off",
"inf_delay": "10",
"inf_end_time": "21600",
"inf_sensitivity": "1",
"inf_start_time": "64800",
"inf_type": "auto",
"light_freq_mode": "auto",
"lock_blue_colton": "0",
"lock_blue_gain": "0",
"lock_gb_gain": "0",
"lock_gr_gain": "0",
"lock_green_colton": "0",
"lock_red_colton": "0",
"lock_red_gain": "0",
"lock_source": "local",
"luma": "50",
"saturation": "50",
"sharpness": "50",
"shutter": "1/25",
"smartir": "off",
"smartir_level": "100",
"wb_B_gain": "50",
"wb_G_gain": "50",
"wb_R_gain": "50",
"wb_type": "auto",
"wd_gain": "50",
"wide_dynamic": "off"
},
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getLedStatus": {
"led": {
"config": {
".name": "config",
".type": "led",
"enabled": "on"
}
}
},
"getLensMaskConfig": {
"lens_mask": {
"lens_mask_info": {
".name": "lens_mask_info",
".type": "lens_mask_info",
"enabled": "on"
}
}
},
"getLightFrequencyInfo": {
"image": {
"common": {
".name": "common",
".type": "para",
"area_compensation": "default",
"chroma": "50",
"contrast": "50",
"dehaze": "off",
"exp_gain": "0",
"exp_type": "auto",
"focus_limited": "600",
"focus_type": "semi_auto",
"high_light_compensation": "off",
"inf_delay": "10",
"inf_end_time": "21600",
"inf_sensitivity": "1",
"inf_start_time": "64800",
"inf_type": "auto",
"light_freq_mode": "auto",
"lock_blue_colton": "0",
"lock_blue_gain": "0",
"lock_gb_gain": "0",
"lock_gr_gain": "0",
"lock_green_colton": "0",
"lock_red_colton": "0",
"lock_red_gain": "0",
"lock_source": "local",
"luma": "50",
"saturation": "50",
"sharpness": "50",
"shutter": "1/25",
"smartir": "off",
"smartir_level": "100",
"wb_B_gain": "50",
"wb_G_gain": "50",
"wb_R_gain": "50",
"wb_type": "auto",
"wd_gain": "50",
"wide_dynamic": "off"
}
}
},
"getMediaEncrypt": {
"cet": {
"media_encrypt": {
".name": "media_encrypt",
".type": "on_off",
"enabled": "on"
}
}
},
"getMsgPushConfig": {
"msg_push": {
"chn1_msg_push_info": {
".name": "chn1_msg_push_info",
".type": "on_off",
"notification_enabled": "on",
"rich_notification_enabled": "off"
}
}
},
"getNightVisionModeConfig": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getPersonDetectionConfig": {
"people_detection": {
"detection": {
".name": "detection",
".type": "on_off",
"enabled": "off"
}
}
},
"getPresetConfig": {
"preset": {
"preset": {
"id": [
"1"
],
"name": [
"Viewpoint 1"
],
"position_pan": [
"0.088935"
],
"position_tilt": [
"-1.000000"
],
"read_only": [
"0"
]
}
}
},
"getRecordPlan": {
"record_plan": {
"chn1_channel": {
".name": "chn1_channel",
".type": "plan",
"enabled": "on",
"friday": "[\"0000-2400:2\"]",
"monday": "[\"0000-2400:2\"]",
"saturday": "[\"0000-2400:2\"]",
"sunday": "[\"0000-2400:2\"]",
"thursday": "[\"0000-2400:2\"]",
"tuesday": "[\"0000-2400:2\"]",
"wednesday": "[\"0000-2400:2\"]"
}
}
},
"getRotationStatus": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getSdCardStatus": {
"harddisk_manage": {
"hd_info": [
{
"hd_info_1": {
"detect_status": "offline",
"disk_name": "1",
"free_space": "0B",
"loop_record_status": "0",
"msg_push_free_space": "0B",
"msg_push_total_space": "0B",
"percent": "0",
"picture_free_space": "0B",
"picture_total_space": "0B",
"record_duration": "0",
"record_free_duration": "0",
"record_start_time": "0",
"rw_attr": "r",
"status": "offline",
"total_space": "0B",
"type": "local",
"video_free_space": "0B",
"video_total_space": "0B",
"write_protect": "0"
}
}
]
}
},
"getTamperDetectionConfig": {
"tamper_detection": {
"tamper_det": {
".name": "tamper_det",
".type": "on_off",
"digital_sensitivity": "50",
"enabled": "off",
"sensitivity": "medium"
}
}
},
"getTargetTrackConfig": {
"target_track": {
"target_track_info": {
".name": "target_track_info",
".type": "target_track_info",
"enabled": "off"
}
}
},
"getTimezone": {
"system": {
"basic": {
".name": "basic",
".type": "setting",
"timezone": "UTC-00:00",
"timing_mode": "ntp",
"zone_id": "Europe/London"
}
}
},
"getVideoCapability": {
"video_capability": {
"main": {
".name": "main",
".type": "capability",
"bitrate_types": [
"cbr",
"vbr"
],
"bitrates": [
"256",
"512",
"1024",
"2048"
],
"encode_types": [
"H264"
],
"frame_rates": [
"65537",
"65546",
"65551"
],
"qualitys": [
"1",
"3",
"5"
],
"resolutions": [
"1920*1080",
"1280*720",
"640*360"
]
}
}
},
"getVideoQualities": {
"video": {
"main": {
".name": "main",
".type": "stream",
"bitrate": "1024",
"bitrate_type": "vbr",
"encode_type": "H264",
"frame_rate": "65551",
"gop_factor": "2",
"name": "VideoEncoder_1",
"quality": "3",
"resolution": "1280*720",
"stream_type": "general"
}
}
},
"getWhitelampConfig": {
"image": {
"switch": {
".name": "switch",
".type": "switch_type",
"flip_type": "off",
"ldc": "off",
"night_vision_mode": "inf_night_vision",
"rotate_type": "off",
"schedule_end_time": "64800",
"schedule_start_time": "21600",
"switch_mode": "common",
"wtl_intensity_level": "5"
}
}
},
"getWhitelampStatus": {
"rest_time": 0,
"status": 0
},
"get_audio_capability": {
"get": {
"audio_capability": {
"device_microphone": {
".name": "device_microphone",
".type": "capability",
"aec": "1",
"channels": "1",
"echo_cancelling": "0",
"encode_type": [
"G711alaw"
],
"half_duplex": "1",
"mute": "1",
"noise_cancelling": "1",
"sampling_rate": [
"8"
],
"volume": "1"
},
"device_speaker": {
".name": "device_speaker",
".type": "capability",
"channels": "1",
"decode_type": [
"G711"
],
"mute": "0",
"sampling_rate": [
"8"
],
"volume": "1"
}
}
}
},
"get_audio_config": {
"get": {
"audio_config": {
"microphone": {
".name": "microphone",
".type": "audio_config",
"channels": "1",
"encode_type": "G711alaw",
"mute": "off",
"noise_cancelling": "on",
"sampling_rate": "8",
"volume": "100"
},
"speaker": {
".name": "speaker",
".type": "audio_config",
"volume": "100"
}
}
}
},
"get_cet": {
"get": {
"cet": {
"vhttpd": {
".name": "vhttpd",
".type": "server",
"port": "8800"
}
}
}
},
"get_function": {
"get": {
"function": {
"module_spec": {
".name": "module_spec",
".type": "module-spec",
"ae_weighting_table_resolution": "5*5",
"ai_enhance_capability": "1",
"app_version": "1.0.0",
"audio": [
"speaker",
"microphone"
],
"audioexception_detection": "0",
"auth_encrypt": "1",
"backlight_coexistence": "1",
"change_password": "1",
"client_info": "1",
"cloud_storage_version": "1.0",
"custom_area_compensation": "1",
"custom_auto_mode_exposure_level": "0",
"device_share": [
"preview",
"playback",
"voice",
"motor",
"cloud_storage"
],
"download": [
"video"
],
"events": [
"motion",
"tamper"
],
"greeter": "1.0",
"http_system_state_audio_support": "1",
"intrusion_detection": "1",
"led": "1",
"lens_mask": "1",
"linecrossing_detection": "1",
"linkage_capability": "1",
"local_storage": "1",
"media_encrypt": "1",
"msg_alarm": "1",
"msg_alarm_list": [
"sound",
"light"
],
"msg_alarm_separate_list": [
"light",
"sound"
],
"msg_push": "1",
"multi_user": "0",
"multicast": "0",
"network": [
"wifi"
],
"ota_upgrade": "1",
"p2p_support_versions": [
"1.1"
],
"playback": [
"local",
"p2p",
"relay"
],
"playback_scale": "1",
"preview": [
"local",
"p2p",
"relay"
],
"privacy_mask_api_version": "1.0",
"ptz": "1",
"record_max_slot_cnt": "10",
"record_type": [
"timing",
"motion"
],
"relay_support_versions": [
"1.3"
],
"reonboarding": "1",
"smart_detection": "1",
"smart_msg_push_capability": "1",
"ssl_cer_version": "1.0",
"storage_api_version": "2.2",
"stream_max_sessions": "10",
"streaming_support_versions": [
"1.0"
],
"target_track": "1.0",
"timing_reboot": "1",
"verification_change_password": "1",
"video_codec": [
"h264"
],
"video_detection_digital_sensitivity": "1",
"wifi_cascade_connection": "1",
"wifi_connection_info": "1",
"wireless_hotspot": "1"
}
}
}
},
"get_motor": {
"get": {
"motor": {
"capability": {
".name": "capability",
".type": "ptz",
"absolute_move_supported": "1",
"calibrate_supported": "1",
"continuous_move_supported": "1",
"eflip_mode": [
"off",
"on"
],
"home_position_mode": "none",
"limit_supported": "0",
"manual_control_level": [
"low",
"normal",
"high"
],
"manual_control_mode": [
"compatible",
"pedestrian",
"motor_vehicle",
"non_motor_vehicle",
"self_adaptive"
],
"park_supported": "0",
"pattern_supported": "0",
"plan_supported": "0",
"position_pan_range": [
"-1.000000",
"1.000000"
],
"position_tilt_range": [
"-1.000000",
"1.000000"
],
"poweroff_save_supported": "1",
"poweroff_save_time_range": [
"10",
"600"
],
"preset_number_max": "8",
"preset_supported": "1",
"relative_move_supported": "1",
"reverse_mode": [
"off",
"on",
"auto"
],
"scan_supported": "0",
"speed_pan_max": "1.00000",
"speed_tilt_max": "1.000000",
"tour_supported": "0"
}
}
}
}
}

View File

@ -17,11 +17,21 @@ async def test_state(dev: Device):
if dev.device_type is DeviceType.Hub:
pytest.skip("Hubs cannot be switched on and off")
if Module.LensMask in dev.modules:
state = dev.is_on
await dev.set_state(not state)
await dev.update()
assert dev.is_on is not state
dev.modules.pop(Module.LensMask) # type: ignore[attr-defined]
# Test with no lens mask module. Device is always on.
assert dev.is_on is True
res = await dev.set_state(False)
assert res == {}
await dev.update()
assert dev.is_on is True
@device_smartcam
async def test_alias(dev):

View File

@ -122,8 +122,15 @@ async def test_list_devices(discovery_mock, runner):
catch_exceptions=False,
)
assert res.exit_code == 0
header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}"
row = f"{discovery_mock.ip:<15} {discovery_mock.device_type:<20} {discovery_mock.encrypt_type:<7}"
header = (
f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} "
f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}"
)
row = (
f"{discovery_mock.ip:<15} {discovery_mock.model:<9} {discovery_mock.device_type:<20} "
f"{discovery_mock.encrypt_type:<7} {discovery_mock.https:<5} "
f"{discovery_mock.login_version or '-':<3}"
)
assert header in res.output
assert row in res.output
@ -158,14 +165,26 @@ async def test_discover_raw(discovery_mock, runner, mocker):
redact_spy.assert_called()
@pytest.mark.parametrize(
("exception", "expected"),
[
pytest.param(
AuthenticationError("Failed to authenticate"),
"Authentication failed",
id="auth",
),
pytest.param(TimeoutError(), "Timed out", id="timeout"),
pytest.param(Exception("Foobar"), "Error: Foobar", id="other-error"),
],
)
@new_discovery
async def test_list_auth_failed(discovery_mock, mocker, runner):
async def test_list_update_failed(discovery_mock, mocker, runner, exception, expected):
"""Test that device update is called on main."""
device_class = Discover._get_device_class(discovery_mock.discovery_data)
mocker.patch.object(
device_class,
"update",
side_effect=AuthenticationError("Failed to authenticate"),
side_effect=exception,
)
res = await runner.invoke(
cli,
@ -173,10 +192,17 @@ async def test_list_auth_failed(discovery_mock, mocker, runner):
catch_exceptions=False,
)
assert res.exit_code == 0
header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}"
row = f"{discovery_mock.ip:<15} {discovery_mock.device_type:<20} {discovery_mock.encrypt_type:<7} - Authentication failed"
assert header in res.output
assert row in res.output
header = (
f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} "
f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}"
)
row = (
f"{discovery_mock.ip:<15} {discovery_mock.model:<9} {discovery_mock.device_type:<20} "
f"{discovery_mock.encrypt_type:<7} {discovery_mock.https:<5} "
f"{discovery_mock.login_version or '-':<3} - {expected}"
)
assert header in res.output.replace("\n", "")
assert row in res.output.replace("\n", "")
async def test_list_unsupported(unsupported_device_info, runner):
@ -187,7 +213,10 @@ async def test_list_unsupported(unsupported_device_info, runner):
catch_exceptions=False,
)
assert res.exit_code == 0
header = f"{'HOST':<15} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} {'ALIAS'}"
header = (
f"{'HOST':<15} {'MODEL':<9} {'DEVICE FAMILY':<20} {'ENCRYPT':<7} "
f"{'HTTPS':<5} {'LV':<3} {'ALIAS'}"
)
row = f"{'127.0.0.1':<15} UNSUPPORTED DEVICE"
assert header in res.output
assert row in res.output