You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
919 lines
31 KiB
919 lines
31 KiB
"""Ezviz API.""" |
|
from __future__ import annotations |
|
|
|
import hashlib |
|
import logging |
|
from typing import Any |
|
from uuid import uuid4 |
|
|
|
import requests |
|
|
|
from pyezviz.camera import EzvizCamera |
|
from pyezviz.cas import EzvizCAS |
|
from pyezviz.constants import ( |
|
DEFAULT_TIMEOUT, |
|
FEATURE_CODE, |
|
MAX_RETRIES, |
|
DefenseModeType, |
|
DeviceCatagories, |
|
) |
|
from pyezviz.exceptions import HTTPError, InvalidURL, PyEzvizError |
|
|
|
API_ENDPOINT_CLOUDDEVICES = "/api/cloud/v2/cloudDevices/getAll" |
|
API_ENDPOINT_PAGELIST = "/v3/userdevices/v1/devices/pagelist" |
|
API_ENDPOINT_DEVICES = "/v3/devices/" |
|
API_ENDPOINT_LOGIN = "/v3/users/login/v5" |
|
API_ENDPOINT_REFRESH_SESSION_ID = "/v3/apigateway/login" |
|
API_ENDPOINT_SWITCH_STATUS = "/switchStatus" |
|
API_ENDPOINT_PTZCONTROL = "/ptzControl" |
|
API_ENDPOINT_ALARM_SOUND = "/alarm/sound" |
|
API_ENDPOINT_DETECTION_SENSIBILITY = "/api/device/configAlgorithm" |
|
API_ENDPOINT_DETECTION_SENSIBILITY_GET = "/api/device/queryAlgorithmConfig" |
|
API_ENDPOINT_ALARMINFO_GET = "/v3/alarms/v2/advanced" |
|
API_ENDPOINT_SET_DEFENCE_SCHEDULE = "/api/device/defence/plan2" |
|
API_ENDPOINT_SWITCH_DEFENCE_MODE = "/v3/userdevices/v1/group/switchDefenceMode" |
|
API_ENDPOINT_SWITCH_SOUND_ALARM = "/sendAlarm" |
|
API_ENDPOINT_SERVER_INFO = "/v3/configurations/system/info" |
|
|
|
|
|
class EzvizClient: |
|
"""Initialize api client object.""" |
|
|
|
def __init__( |
|
self, |
|
account: str | None = None, |
|
password: str | None = None, |
|
url: str = "apiieu.ezvizlife.com", |
|
timeout: int = DEFAULT_TIMEOUT, |
|
token: dict | None = None, |
|
) -> None: |
|
"""Initialize the client object.""" |
|
self.account = account |
|
self.password = password |
|
self._session = requests.session() |
|
# Set Android generic user agent. |
|
self._session.headers.update({"User-Agent": "okhttp/3.12.1"}) |
|
self._token = token or { |
|
"session_id": None, |
|
"rf_session_id": None, |
|
"username": None, |
|
"api_url": url, |
|
} |
|
self._timeout = timeout |
|
|
|
def _login(self, account: str, password: str) -> dict[Any, Any]: |
|
"""Login to Ezviz API.""" |
|
|
|
# Region code to url. |
|
if len(self._token["api_url"].split(".")) == 1: |
|
self._token["api_url"] = "apii" + self._token["api_url"] + ".ezvizlife.com" |
|
|
|
# Ezviz API sends md5 of password |
|
temp = hashlib.md5() |
|
temp.update(password.encode("utf-8")) |
|
md5pass = temp.hexdigest() |
|
payload = { |
|
"account": account, |
|
"password": md5pass, |
|
"featureCode": FEATURE_CODE, |
|
"msgType": "0", |
|
"cuName": "SFRDIDEw", |
|
} |
|
|
|
try: |
|
req = self._session.post( |
|
"https://" + self._token["api_url"] + API_ENDPOINT_LOGIN, |
|
allow_redirects=False, |
|
headers={ |
|
"clientType": "3", |
|
"customno": "1000001", |
|
"clientNo": "web_site", |
|
"appId": "ys7", |
|
"lang": "en", |
|
}, |
|
data=payload, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.ConnectionError as err: |
|
raise InvalidURL("A Invalid URL or Proxy error occured") from err |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
try: |
|
json_result = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_result["meta"]["code"] == 1100: |
|
self._token["api_url"] = json_result["loginArea"]["apiDomain"] |
|
print("Region incorrect!") |
|
print(f"Your region url: {self._token['api_url']}") |
|
self.close_session() |
|
return self.login() |
|
|
|
if json_result["meta"]["code"] == 1013: |
|
raise PyEzvizError("Incorrect Username.") |
|
|
|
if json_result["meta"]["code"] == 1014: |
|
raise PyEzvizError("Incorrect Password.") |
|
|
|
if json_result["meta"]["code"] == 1015: |
|
raise PyEzvizError("The user is locked.") |
|
|
|
self._token["session_id"] = str(json_result["loginSession"]["sessionId"]) |
|
self._token["rf_session_id"] = str(json_result["loginSession"]["rfSessionId"]) |
|
self._token["username"] = str(json_result["loginUser"]["username"]) |
|
self._token["api_url"] = str(json_result["loginArea"]["apiDomain"]) |
|
if not self._token["session_id"]: |
|
raise PyEzvizError( |
|
f"Login error: Please check your username/password: {req.text}" |
|
) |
|
|
|
self._token["service_urls"] = self.get_service_urls() |
|
|
|
return self._token |
|
|
|
def get_service_urls(self) -> Any: |
|
"""Get Ezviz service urls.""" |
|
|
|
try: |
|
req = self._session.get( |
|
f"https://{self._token['api_url']}{API_ENDPOINT_SERVER_INFO}", |
|
headers={ |
|
"sessionId": self._token["session_id"], |
|
"featureCode": FEATURE_CODE, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.ConnectionError as err: |
|
raise InvalidURL("A Invalid URL or Proxy error occured") from err |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
|
|
raise HTTPError from err |
|
|
|
if not req.text: |
|
raise PyEzvizError("No data") |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("meta").get("code") != 200: |
|
logging.info("Json request error") |
|
|
|
service_urls = json_output["systemConfigInfo"] |
|
service_urls["sysConf"] = service_urls["sysConf"].split("|") |
|
|
|
return service_urls |
|
|
|
def _api_get_pagelist( |
|
self, page_filter: str, json_key: str | None = None, max_retries: int = 0 |
|
) -> Any: |
|
"""Get data from pagelist API.""" |
|
|
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
if page_filter is None: |
|
raise PyEzvizError("Trying to call get_pagelist without filter") |
|
|
|
try: |
|
req = self._session.get( |
|
"https://" + self._token["api_url"] + API_ENDPOINT_PAGELIST, |
|
headers={"sessionId": self._token["session_id"]}, |
|
params={"filter": page_filter}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self._api_get_pagelist(page_filter, json_key, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
if not req.text: |
|
raise PyEzvizError("No data") |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("meta").get("code") != 200: |
|
# session is wrong, need to relogin |
|
self.login() |
|
logging.info( |
|
"Json request error, relogging (max retries: %s)", str(max_retries) |
|
) |
|
return self._api_get_pagelist(page_filter, json_key, max_retries + 1) |
|
|
|
if json_key is None: |
|
json_result = json_output |
|
else: |
|
json_result = json_output[json_key] |
|
|
|
if not json_result: |
|
# session is wrong, need to relogin |
|
self.login() |
|
logging.info( |
|
"Impossible to load the devices, here is the returned response: %s", |
|
str(req.text), |
|
) |
|
return self._api_get_pagelist(page_filter, json_key, max_retries + 1) |
|
|
|
return json_result |
|
|
|
def get_alarminfo(self, serial: str, max_retries: int = 0) -> Any: |
|
"""Get data from alarm info API.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
params: dict[str, int | str] = { |
|
"deviceSerials": serial, |
|
"queryType": -1, |
|
"limit": 1, |
|
"stype": -1, |
|
} |
|
|
|
try: |
|
req = self._session.get( |
|
"https://" + self._token["api_url"] + API_ENDPOINT_ALARMINFO_GET, |
|
headers={"sessionId": self._token["session_id"]}, |
|
params=params, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self.get_alarminfo(serial, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
if req.text == "": |
|
raise PyEzvizError("No data") |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
return json_output |
|
|
|
def _switch_status( |
|
self, serial: str, status_type: int, enable: int, max_retries: int = 0 |
|
) -> bool: |
|
"""Switch status on a device.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
try: |
|
req = self._session.put( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DEVICES |
|
+ serial |
|
+ "/1/1/" |
|
+ str(status_type) |
|
+ API_ENDPOINT_SWITCH_STATUS, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"enable": enable, |
|
"serial": serial, |
|
"channelNo": "1", |
|
"type": status_type, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self._switch_status(serial, status_type, enable, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("meta").get("code") != 200: |
|
raise PyEzvizError( |
|
f"Could not set the switch: Got {req.status_code} : {req.text})" |
|
) |
|
|
|
return True |
|
|
|
def sound_alarm(self, serial: str, enable: int = 1, max_retries: int = 0) -> bool: |
|
"""Sound alarm on a device.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
try: |
|
req = self._session.put( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DEVICES |
|
+ serial |
|
+ "/0" |
|
+ API_ENDPOINT_SWITCH_SOUND_ALARM, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"enable": enable, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self.sound_alarm(serial, enable, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("meta").get("code") != 200: |
|
raise PyEzvizError( |
|
f"Could not set the alarm sound: Got {req.status_code} : {req.text})" |
|
) |
|
|
|
return True |
|
|
|
def load_cameras(self) -> dict[Any, Any]: |
|
"""Load and return all cameras objects.""" |
|
|
|
devices = self._get_all_device_infos() |
|
cameras = {} |
|
supported_categories = [ |
|
DeviceCatagories.COMMON_DEVICE_CATEGORY.value, |
|
DeviceCatagories.CAMERA_DEVICE_CATEGORY.value, |
|
DeviceCatagories.BATTERY_CAMERA_DEVICE_CATEGORY.value, |
|
DeviceCatagories.DOORBELL_DEVICE_CATEGORY.value, |
|
DeviceCatagories.BASE_STATION_DEVICE_CATEGORY.value, |
|
] |
|
|
|
for device, data in devices.items(): |
|
if data["deviceInfos"]["deviceCategory"] in supported_categories: |
|
# Add support for connected HikVision cameras |
|
if ( |
|
data["deviceInfos"]["deviceCategory"] |
|
== DeviceCatagories.COMMON_DEVICE_CATEGORY.value |
|
and not data["deviceInfos"]["hik"] |
|
): |
|
continue |
|
|
|
# Create camera object |
|
|
|
camera = EzvizCamera(self, device, data) |
|
|
|
camera.load() |
|
cameras[device] = camera.status() |
|
|
|
return cameras |
|
|
|
def _get_all_device_infos(self) -> dict[Any, Any]: |
|
"""Load all devices and build dict per device serial.""" |
|
|
|
devices = self._get_page_list() |
|
result: dict[Any, Any] = {} |
|
|
|
for device in devices["deviceInfos"]: |
|
result[device["deviceSerial"]] = {} |
|
result[device["deviceSerial"]]["deviceInfos"] = device |
|
result[device["deviceSerial"]]["connectionInfos"] = devices.get( |
|
"connectionInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["p2pInfos"] = devices.get("p2pInfos").get( |
|
device["deviceSerial"] |
|
) |
|
result[device["deviceSerial"]]["alarmNodisturbInfos"] = devices.get( |
|
"alarmNodisturbInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["kmsInfos"] = devices.get("kmsInfos").get( |
|
device["deviceSerial"] |
|
) |
|
result[device["deviceSerial"]]["timePlanInfos"] = devices.get( |
|
"timePlanInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["statusInfos"] = devices.get( |
|
"statusInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["wifiInfos"] = devices.get("wifiInfos").get( |
|
device["deviceSerial"] |
|
) |
|
result[device["deviceSerial"]]["switchStatusInfos"] = devices.get( |
|
"switchStatusInfos" |
|
).get(device["deviceSerial"]) |
|
for item in devices["cameraInfos"]: |
|
if item["deviceSerial"] == device["deviceSerial"]: |
|
result[device["deviceSerial"]]["cameraInfos"] = item |
|
|
|
return result |
|
|
|
def get_all_per_serial_infos(self, serial: str) -> dict[Any, Any] | None: |
|
"""Load all devices and build dict per device serial.""" |
|
|
|
if serial is None: |
|
raise PyEzvizError("Need serial number for this query") |
|
|
|
devices = self._get_page_list() |
|
result: dict[str, dict] = {serial: {}} |
|
|
|
for device in devices["deviceInfos"]: |
|
if device["deviceSerial"] == serial: |
|
result[device["deviceSerial"]]["deviceInfos"] = device |
|
result[device["deviceSerial"]]["deviceInfos"] = device |
|
result[device["deviceSerial"]]["connectionInfos"] = devices.get( |
|
"connectionInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["p2pInfos"] = devices.get( |
|
"p2pInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["alarmNodisturbInfos"] = devices.get( |
|
"alarmNodisturbInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["kmsInfos"] = devices.get( |
|
"kmsInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["timePlanInfos"] = devices.get( |
|
"timePlanInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["statusInfos"] = devices.get( |
|
"statusInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["wifiInfos"] = devices.get( |
|
"wifiInfos" |
|
).get(device["deviceSerial"]) |
|
result[device["deviceSerial"]]["switchStatusInfos"] = devices.get( |
|
"switchStatusInfos" |
|
).get(device["deviceSerial"]) |
|
for item in devices["cameraInfos"]: |
|
if item["deviceSerial"] == device["deviceSerial"]: |
|
result[device["deviceSerial"]]["cameraInfos"] = item |
|
|
|
return result.get(serial) |
|
|
|
def ptz_control( |
|
self, command: str, serial: str, action: str, speed: int = 5 |
|
) -> Any: |
|
"""PTZ Control by API.""" |
|
if command is None: |
|
raise PyEzvizError("Trying to call ptzControl without command") |
|
if action is None: |
|
raise PyEzvizError("Trying to call ptzControl without action") |
|
|
|
try: |
|
req = self._session.put( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DEVICES |
|
+ serial |
|
+ API_ENDPOINT_PTZCONTROL, |
|
data={ |
|
"command": command, |
|
"action": action, |
|
"channelNo": "1", |
|
"speed": speed, |
|
"uuid": str(uuid4()), |
|
"serial": serial, |
|
}, |
|
headers={"sessionId": self._token["session_id"], "clientType": "1"}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
return req.text |
|
|
|
def login(self) -> dict[Any, Any]: |
|
"""Get or refresh ezviz login token.""" |
|
if self._token["session_id"] and self._token["rf_session_id"]: |
|
try: |
|
req = self._session.put( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_REFRESH_SESSION_ID, |
|
data={ |
|
"refreshSessionId": self._token["rf_session_id"], |
|
"featureCode": FEATURE_CODE, |
|
}, |
|
headers={"sessionId": self._token["session_id"]}, |
|
timeout=self._timeout, |
|
) |
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
raise HTTPError from err |
|
|
|
try: |
|
json_result = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
self._token["session_id"] = str(json_result["sessionInfo"]["sessionId"]) |
|
self._token["rf_session_id"] = str( |
|
json_result["sessionInfo"]["refreshSessionId"] |
|
) |
|
if not self._token["session_id"]: |
|
raise PyEzvizError(f"Relogin required: {req.text}") |
|
|
|
if not self._token.get("service_urls"): |
|
self._token["service_urls"] = self.get_service_urls() |
|
|
|
return self._token |
|
|
|
if self.account and self.password: |
|
return self._login(account=self.account, password=self.password) |
|
|
|
raise PyEzvizError("Login with account and password required") |
|
|
|
def set_camera_defence(self, serial: str, enable: int) -> bool: |
|
"""Enable/Disable motion detection on camera.""" |
|
cas_client = EzvizCAS(self._token) |
|
cas_client.set_camera_defence_state(serial, enable) |
|
|
|
return True |
|
|
|
def api_set_defence_schedule( |
|
self, serial: str, schedule: str, enable: int, max_retries: int = 0 |
|
) -> bool: |
|
"""Set defence schedules.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
schedulestring = ( |
|
'{"CN":0,"EL":' |
|
+ str(enable) |
|
+ ',"SS":"' |
|
+ serial |
|
+ '","WP":[' |
|
+ schedule |
|
+ "]}]}" |
|
) |
|
try: |
|
req = self._session.post( |
|
"https://" + self._token["api_url"] + API_ENDPOINT_SET_DEFENCE_SCHEDULE, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"devTimingPlan": schedulestring, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self.api_set_defence_schedule( |
|
serial, schedule, enable, max_retries + 1 |
|
) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("resultCode") != 0: |
|
raise PyEzvizError( |
|
f"Could not set the schedule: Got {req.status_code} : {req.text})" |
|
) |
|
|
|
return True |
|
|
|
def api_set_defence_mode(self, mode: DefenseModeType, max_retries: int = 0) -> bool: |
|
"""Set defence mode.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
try: |
|
req = self._session.post( |
|
"https://" + self._token["api_url"] + API_ENDPOINT_SWITCH_DEFENCE_MODE, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"groupId": -1, |
|
"mode": mode, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to relogin |
|
self.login() |
|
return self.api_set_defence_mode(mode, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
json_output = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError( |
|
"Impossible to decode response: " |
|
+ str(err) |
|
+ "\nResponse was: " |
|
+ str(req.text) |
|
) from err |
|
|
|
if json_output.get("meta").get("code") != 200: |
|
raise PyEzvizError( |
|
f"Could not set defence mode: Got {req.status_code} : {req.text})" |
|
) |
|
|
|
return True |
|
|
|
def detection_sensibility( |
|
self, |
|
serial: str, |
|
sensibility: int = 3, |
|
type_value: int = 3, |
|
max_retries: int = 0, |
|
) -> bool | str: |
|
"""Set detection sensibility.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
if sensibility not in [0, 1, 2, 3, 4, 5, 6] and type_value == 0: |
|
raise PyEzvizError( |
|
"Unproper sensibility for type 0 (should be within 1 to 6)." |
|
) |
|
|
|
try: |
|
req = self._session.post( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DETECTION_SENSIBILITY, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"subSerial": serial, |
|
"type": type_value, |
|
"channelNo": "1", |
|
"value": sensibility, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to re-log-in |
|
self.login() |
|
return self.detection_sensibility( |
|
serial, sensibility, type_value, max_retries + 1 |
|
) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
response_json = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError("Could not decode response:" + str(err)) from err |
|
|
|
if response_json["resultCode"] and response_json["resultCode"] != "0": |
|
return "Unknown value" |
|
|
|
return True |
|
|
|
def get_detection_sensibility( |
|
self, serial: str, type_value: str = "0", max_retries: int = 0 |
|
) -> Any: |
|
"""Get detection sensibility notifications.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
try: |
|
req = self._session.post( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DETECTION_SENSIBILITY_GET, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"subSerial": serial, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to re-log-in. |
|
self.login() |
|
return self.get_detection_sensibility( |
|
serial, type_value, max_retries + 1 |
|
) |
|
|
|
raise HTTPError from err |
|
|
|
try: |
|
response_json = req.json() |
|
|
|
except ValueError as err: |
|
raise PyEzvizError("Could not decode response:" + str(err)) from err |
|
|
|
if response_json["resultCode"] != "0": |
|
return "Unknown" |
|
|
|
if response_json["algorithmConfig"]["algorithmList"]: |
|
for idx in response_json["algorithmConfig"]["algorithmList"]: |
|
if idx["type"] == type_value: |
|
return idx["value"] |
|
|
|
return "Unknown" |
|
|
|
# soundtype: 0 = normal, 1 = intensive, 2 = disabled ... don't ask me why... |
|
def alarm_sound( |
|
self, serial: str, sound_type: int, enable: int = 1, max_retries: int = 0 |
|
) -> bool: |
|
"""Enable alarm sound by API.""" |
|
if max_retries > MAX_RETRIES: |
|
raise PyEzvizError("Can't gather proper data. Max retries exceeded.") |
|
|
|
if sound_type not in [0, 1, 2]: |
|
raise PyEzvizError( |
|
"Invalid sound_type, should be 0,1,2: " + str(sound_type) |
|
) |
|
|
|
try: |
|
req = self._session.put( |
|
"https://" |
|
+ self._token["api_url"] |
|
+ API_ENDPOINT_DEVICES |
|
+ serial |
|
+ API_ENDPOINT_ALARM_SOUND, |
|
headers={"sessionId": self._token["session_id"]}, |
|
data={ |
|
"enable": enable, |
|
"soundType": sound_type, |
|
"voiceId": "0", |
|
"deviceSerial": serial, |
|
}, |
|
timeout=self._timeout, |
|
) |
|
|
|
req.raise_for_status() |
|
|
|
except requests.HTTPError as err: |
|
if err.response.status_code == 401: |
|
# session is wrong, need to re-log-in |
|
self.login() |
|
return self.alarm_sound(serial, sound_type, enable, max_retries + 1) |
|
|
|
raise HTTPError from err |
|
|
|
return True |
|
|
|
def switch_status(self, serial: str, status_type: int, enable: int = 0) -> bool: |
|
"""Switch status of a device.""" |
|
return self._switch_status(serial, status_type, enable) |
|
|
|
def _get_page_list(self) -> Any: |
|
"""Get ezviz device info broken down in sections.""" |
|
return self._api_get_pagelist( |
|
page_filter="CLOUD, TIME_PLAN, CONNECTION, SWITCH," |
|
"STATUS, WIFI, NODISTURB, KMS, P2P," |
|
"TIME_PLAN, CHANNEL, VTM, DETECTOR," |
|
"FEATURE, UPGRADE, VIDEO_QUALITY, QOS", |
|
json_key=None, |
|
) |
|
|
|
def get_device(self) -> Any: |
|
"""Get ezviz devices filter.""" |
|
return self._api_get_pagelist(page_filter="CLOUD", json_key="deviceInfos") |
|
|
|
def get_connection(self) -> Any: |
|
"""Get ezviz connection infos filter.""" |
|
return self._api_get_pagelist( |
|
page_filter="CONNECTION", json_key="connectionInfos" |
|
) |
|
|
|
def _get_status(self) -> Any: |
|
"""Get ezviz status infos filter.""" |
|
return self._api_get_pagelist(page_filter="STATUS", json_key="statusInfos") |
|
|
|
def get_switch(self) -> Any: |
|
"""Get ezviz switch infos filter.""" |
|
return self._api_get_pagelist( |
|
page_filter="SWITCH", json_key="switchStatusInfos" |
|
) |
|
|
|
def _get_wifi(self) -> Any: |
|
"""Get ezviz wifi infos filter.""" |
|
return self._api_get_pagelist(page_filter="WIFI", json_key="wifiInfos") |
|
|
|
def _get_nodisturb(self) -> Any: |
|
"""Get ezviz nodisturb infos filter.""" |
|
return self._api_get_pagelist( |
|
page_filter="NODISTURB", json_key="alarmNodisturbInfos" |
|
) |
|
|
|
def _get_p2p(self) -> Any: |
|
"""Get ezviz P2P infos filter.""" |
|
return self._api_get_pagelist(page_filter="P2P", json_key="p2pInfos") |
|
|
|
def _get_kms(self) -> Any: |
|
"""Get ezviz KMS infos filter.""" |
|
return self._api_get_pagelist(page_filter="KMS", json_key="kmsInfos") |
|
|
|
def _get_time_plan(self) -> Any: |
|
"""Get ezviz TIME_PLAN infos filter.""" |
|
return self._api_get_pagelist(page_filter="TIME_PLAN", json_key="timePlanInfos") |
|
|
|
def close_session(self) -> None: |
|
"""Clear current session.""" |
|
if self._session: |
|
self._session.close() |
|
|
|
self._session = requests.session() |
|
self._session.headers.update( |
|
{"User-Agent": "okhttp/3.12.1"} |
|
) # Android generic user agent.
|
|
|