diff --git a/README.md b/README.md index c7768db..98df81d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The Home Assistant integration for Hikvision NVRs and IP cameras. Receives and s - Image entities for the latest snapshots - Tracking HDD and NAS status - Tracking Notifications Host settings for diagnostic purposes +- Remote reboot device - Basic and digest authentication support ### Supported events @@ -34,9 +35,18 @@ Events must be set to alert the surveillance center in Linkage Action for Home A ### Blueprints -Take Multiple Snapshots On Detection Event +#### Take Multiple Snapshots On Detection Event + +Creates automation that allows to take snapshots from selected cameras when an event sensor is triggered. [](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https://github.com/maciej-or/hikvision_next/blob/main/blueprints/take_pictures_on_motion_detection.yaml) + +#### Display Sensor State On Hikvision Video + +Creates an automation that allows to display text overlay on a selected video stream with the state of a selected sensor. Refreshes every 15 minutes. + +[](https://my.home-assistant.io/redirect/blueprint_import/?blueprint_url=https://github.com/maciej-or/hikvision_next/blob/main/blueprints/display_sensor_state_on_hikvision_video.yaml) + ## Preview ### IP Camera device view diff --git a/blueprints/display_sensor_state_on_hikvision_video.yaml b/blueprints/display_sensor_state_on_hikvision_video.yaml new file mode 100644 index 0000000..d6e02db --- /dev/null +++ b/blueprints/display_sensor_state_on_hikvision_video.yaml @@ -0,0 +1,81 @@ +blueprint: + name: Display Sensor State On Hikvision Video + description: | + Sets an overlay text on a Hikvision camera video. + domain: automation + input: + config_entry_id: + name: "Device" + description: "The Hikvision device." + selector: + config_entry: + integration: hikvision_next + camera_no: + name: "Camera" + description: "The camera number." + default: 1 + selector: + number: + min: 1 + max: 32 + step: 1 + mode: box + sensor_entity: + name: Sensor + description: "The sensor entity to display on the video." + selector: + entity: + domain: sensor + position_x: + name: "X" + description: "The X position of the overlay text." + default: 16 + selector: + number: + min: 0 + max: 1920 + step: 1 + position_y: + name: "Y" + description: "The Y position of the overlay text." + default: 570 + selector: + number: + min: 0 + max: 1080 + step: 1 + enabled: + name: "State" + description: "Enable or disable the overlay text." + default: true + selector: + boolean: {} +mode: single +variables: + entity_id: !input sensor_entity + camera_no: !input camera_no + position_x: !input position_x + position_y: !input position_y + enabled: !input enabled +trigger: + - platform: time_pattern + minutes: /15 +action: + - service: hikvision_next.isapi_request + data: + method: PUT + config_entry_id: !input config_entry_id + path: "/System/Video/inputs/channels/{{camera_no}}/overlays/text" + payload: > + + + + 1 + {{ enabled | lower }} + {{ position_x }} + {{ position_y }} + {{ states(entity_id, with_unit=True) }} + + + + response_variable: response diff --git a/custom_components/hikvision_next/__init__.py b/custom_components/hikvision_next/__init__.py index f7da136..04cd1bf 100644 --- a/custom_components/hikvision_next/__init__.py +++ b/custom_components/hikvision_next/__init__.py @@ -5,6 +5,7 @@ import asyncio from contextlib import suppress import logging +import traceback from httpx import TimeoutException @@ -18,6 +19,7 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers.httpx_client import get_async_client +from homeassistant.helpers.typing import ConfigType from .const import ( ALARM_SERVER_PATH, @@ -31,6 +33,7 @@ from .coordinator import EventsCoordinator, SecondaryCoordinator from .isapi import ISAPI from .notifications import EventNotificationsView +from .services import setup_services _LOGGER = logging.getLogger(__name__) @@ -43,6 +46,14 @@ ] +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the Hikvision component.""" + + setup_services(hass) + + return True + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up integration from a config entry.""" hass.data.setdefault(DOMAIN, {}) @@ -59,12 +70,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: device_info = isapi.hass_device_info() device_registry = dr.async_get(hass) device_registry.async_get_or_create(config_entry_id=entry.entry_id, **device_info) - except (asyncio.TimeoutError, TimeoutException) as ex: + except (TimeoutError, TimeoutException) as ex: raise ConfigEntryNotReady(f"Timeout while connecting to {host}. Cannot initialize {DOMAIN}") from ex except Exception as ex: # pylint: disable=broad-except - raise ConfigEntryNotReady( - f"Unknown error connecting to {host}. Cannot initialize {DOMAIN}. Error is {ex}" - ) from ex + msg = f"Cannot initialize {DOMAIN} {host}. Error: {ex}\n" + _LOGGER.error(msg + traceback.format_exc()) + raise ConfigEntryNotReady(msg) from ex coordinators = {} diff --git a/custom_components/hikvision_next/config_flow.py b/custom_components/hikvision_next/config_flow.py index 46d984c..33d9330 100755 --- a/custom_components/hikvision_next/config_flow.py +++ b/custom_components/hikvision_next/config_flow.py @@ -14,6 +14,7 @@ from homeassistant.config_entries import ConfigEntry, ConfigFlow from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers.httpx_client import get_async_client from .const import DATA_ALARM_SERVER_HOST, DATA_SET_ALARM_SERVER, DOMAIN from .isapi import ISAPI @@ -62,7 +63,8 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None) -> Flo CONF_HOST: host, } - isapi = ISAPI(host, username, password) + session = get_async_client(self.hass) + isapi = ISAPI(host, username, password, session) await isapi.get_device_info() if self._reauth_entry: diff --git a/custom_components/hikvision_next/const.py b/custom_components/hikvision_next/const.py index cb4dca9..3227e10 100644 --- a/custom_components/hikvision_next/const.py +++ b/custom_components/hikvision_next/const.py @@ -18,6 +18,11 @@ CONNECTION_TYPE_DIRECT = "Direct" CONNECTION_TYPE_PROXIED = "Proxied" +ATTR_CONFIG_ENTRY_ID = "config_entry_id" +ACTION_REBOOT = "reboot" +ACTION_ISAPI_REQUEST = "isapi_request" +ACTION_UPDATE_SNAPSHOT = "update_snapshot" + HIKVISION_EVENT = f"{DOMAIN}_event" EVENT_BASIC: Final = "basic" EVENT_IO: Final = "io" diff --git a/custom_components/hikvision_next/coordinator.py b/custom_components/hikvision_next/coordinator.py index 5996d0f..72dc854 100644 --- a/custom_components/hikvision_next/coordinator.py +++ b/custom_components/hikvision_next/coordinator.py @@ -45,8 +45,8 @@ async def _async_update_data(self): if event.disabled: continue try: - entity_id = ENTITY_ID_FORMAT.format(event.unique_id) - data[entity_id] = await self.isapi.get_event_enabled_state(event) + _id = ENTITY_ID_FORMAT.format(event.unique_id) + data[_id] = await self.isapi.get_event_enabled_state(event) except Exception as ex: # pylint: disable=broad-except self.isapi.handle_exception(ex, f"Cannot fetch state for {event.id}") @@ -55,18 +55,18 @@ async def _async_update_data(self): if event.disabled: continue try: - entity_id = ENTITY_ID_FORMAT.format(event.unique_id) - data[entity_id] = await self.isapi.get_event_enabled_state(event) + _id = ENTITY_ID_FORMAT.format(event.unique_id) + data[_id] = await self.isapi.get_event_enabled_state(event) except Exception as ex: # pylint: disable=broad-except self.isapi.handle_exception(ex, f"Cannot fetch state for {event.id}") # Get output port(s) status for i in range(1, self.isapi.device_info.output_ports + 1): try: - entity_id = ENTITY_ID_FORMAT.format( + _id = ENTITY_ID_FORMAT.format( f"{slugify(self.isapi.device_info.serial_no.lower())}_{i}_alarm_output" ) - data[entity_id] = await self.isapi.get_port_status("output", i) + data[_id] = await self.isapi.get_port_status("output", i) except Exception as ex: # pylint: disable=broad-except self.isapi.handle_exception(ex, f"Cannot fetch state for alarm output {i}") diff --git a/custom_components/hikvision_next/image.py b/custom_components/hikvision_next/image.py index 719ef7d..0272d30 100644 --- a/custom_components/hikvision_next/image.py +++ b/custom_components/hikvision_next/image.py @@ -15,7 +15,7 @@ from homeassistant.helpers.template import Template from homeassistant.util import slugify -from .const import DATA_ISAPI, DOMAIN +from .const import ACTION_UPDATE_SNAPSHOT, DATA_ISAPI, DOMAIN from .isapi import ISAPI, CameraStreamInfo _LOGGER = logging.getLogger(__name__) @@ -37,7 +37,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_e platform = entity_platform.async_get_current_platform() platform.async_register_entity_service( - "update_snapshot", + ACTION_UPDATE_SNAPSHOT, {vol.Required(CONF_FILENAME): cv.template}, "update_snapshot_filename", ) diff --git a/custom_components/hikvision_next/isapi.py b/custom_components/hikvision_next/isapi.py index 58081d6..6fa1d63 100644 --- a/custom_components/hikvision_next/isapi.py +++ b/custom_components/hikvision_next/isapi.py @@ -8,12 +8,12 @@ import datetime from functools import reduce from http import HTTPStatus -import httpx import json import logging from typing import Any, Optional from urllib.parse import quote, urlparse +import httpx from httpx import HTTPStatusError, TimeoutException import xmltodict @@ -21,7 +21,6 @@ from homeassistant.helpers import device_registry as dr from homeassistant.helpers.entity import DeviceInfo from homeassistant.util import slugify -from .isapi_client import ISAPI_Client from .const import ( CONNECTION_TYPE_DIRECT, @@ -35,6 +34,7 @@ MUTEX_ALTERNATE_IDS, STREAM_TYPE, ) +from .isapi_client import ISAPI_Client Node = dict[str, Any] @@ -410,6 +410,11 @@ def get_event(event_trigger: dict): if not event_type: return None + if event_type.lower() == EVENT_PIR: + is_supported = str_to_bool(deep_get(system_capabilities, "WLAlarmCap.isSupportPIR", False)) + if not is_supported: + return None + channel = event_trigger.get("videoInputChannelID", event_trigger.get("dynVideoInputChannelID", 0)) io_port = event_trigger.get("inputIOPortID", event_trigger.get("dynInputIOPortID", 0)) notifications = notification_list.get("EventTriggerNotification", []) @@ -435,6 +440,8 @@ def get_event(event_trigger: dict): supported_events = deep_get(event_notification, "EventTriggerList.EventTrigger", []) else: supported_events = deep_get(event_triggers, "EventTriggerList.EventTrigger", []) + if not isinstance(supported_events, list): + supported_events = [supported_events] for event_trigger in supported_events: if event := get_event(event_trigger): @@ -772,6 +779,10 @@ async def set_alarm_server(self, base_url: str, path: str) -> None: xml = xmltodict.unparse(data) await self.request(PUT, "Event/notification/httpHosts", present="xml", data=xml) + async def reboot(self): + """Reboot device.""" + await self.request(PUT, "System/reboot", present="xml") + async def request( self, method: str, diff --git a/custom_components/hikvision_next/isapi_client.py b/custom_components/hikvision_next/isapi_client.py index 6e373f2..53e3dc3 100644 --- a/custom_components/hikvision_next/isapi_client.py +++ b/custom_components/hikvision_next/isapi_client.py @@ -1,10 +1,13 @@ import httpx +import logging from typing import Any, AsyncIterator, List, Union from urllib.parse import urljoin import json import xmltodict from dataclasses import dataclass +_LOGGER = logging.getLogger(__name__) + def response_parser(response, present="dict"): """Parse Hikvision results.""" @@ -42,16 +45,21 @@ async def _detect_auth_method(self): if not self.session: self.session = httpx.AsyncClient(timeout=self.timeout) - url = urljoin(self.host, self.isapi_prefix + "/System/status") - for method in [ - httpx.BasicAuth(self.username, self.password), - httpx.DigestAuth(self.username, self.password), - ]: - response = await self.session.get(url, auth=method) - if response.status_code == 200: - self._auth_method = method + url = urljoin(self.host, self.isapi_prefix + "/System/deviceInfo") + _LOGGER.debug("--- [WWW-Authenticate detection] %s", self.host) + response = await self.session.get(url) + if response.status_code == 401: + www_authenticate = response.headers.get("WWW-Authenticate", "") + _LOGGER.debug("WWW-Authenticate header: %s", www_authenticate) + if "Basic" in www_authenticate: + self._auth_method = httpx.BasicAuth(self.username, self.password) + elif "Digest" in www_authenticate: + self._auth_method = httpx.DigestAuth(self.username, self.password) if not self._auth_method: + _LOGGER.error("Authentication method not detected, %s", response.status_code) + if response.headers: + _LOGGER.error("response.headers %s", response.headers) response.raise_for_status() def get_url(self, relative_url: str) -> str: diff --git a/custom_components/hikvision_next/manifest.json b/custom_components/hikvision_next/manifest.json index 42cc16c..ea88d2d 100644 --- a/custom_components/hikvision_next/manifest.json +++ b/custom_components/hikvision_next/manifest.json @@ -13,5 +13,5 @@ "xmltodict==0.13.0", "requests-toolbelt==1.0.0" ], - "version": "1.0.18" + "version": "1.0.19" } diff --git a/custom_components/hikvision_next/services.py b/custom_components/hikvision_next/services.py new file mode 100644 index 0000000..8b5aa09 --- /dev/null +++ b/custom_components/hikvision_next/services.py @@ -0,0 +1,73 @@ +"Integration actions." + +from httpx import HTTPStatusError +import voluptuous as vol + +from homeassistant.core import ( + HomeAssistant, + ServiceCall, + ServiceResponse, + SupportsResponse, +) +from homeassistant.exceptions import HomeAssistantError + +from .const import ( + ACTION_ISAPI_REQUEST, + ACTION_REBOOT, + ATTR_CONFIG_ENTRY_ID, + DATA_ISAPI, + DOMAIN, +) + +ACTION_ISAPI_REQUEST_SCHEMA = vol.Schema( + { + vol.Required(ATTR_CONFIG_ENTRY_ID): str, + vol.Required("method"): str, + vol.Required("path"): str, + vol.Optional("payload"): str, + } +) + + +def setup_services(hass: HomeAssistant) -> None: + """Set up the services for the Hikvision component.""" + + async def handle_reboot(call: ServiceCall): + """Handle the reboot action call.""" + entries = hass.data[DOMAIN] + entry_id = call.data.get(ATTR_CONFIG_ENTRY_ID) + isapi = entries[entry_id][DATA_ISAPI] + try: + await isapi.reboot() + except HTTPStatusError as ex: + raise HomeAssistantError(ex.response.content) from ex + + async def handle_isapi_request(call: ServiceCall) -> ServiceResponse: + """Handle the custom ISAPI request action call.""" + entries = hass.data[DOMAIN] + entry_id = call.data.get(ATTR_CONFIG_ENTRY_ID) + isapi = entries[entry_id][DATA_ISAPI] + method = call.data.get("method", "POST") + path = call.data["path"].strip("/") + payload = call.data.get("payload") + try: + response = await isapi.request(method, path, present="xml", data=payload) + except HTTPStatusError as ex: + if isinstance(ex.response.content, bytes): + response = ex.response.content.decode("utf-8") + else: + response = ex.response.content + return {"data": response.replace("\r", "")} + + hass.services.async_register( + DOMAIN, + ACTION_REBOOT, + handle_reboot, + ) + hass.services.async_register( + DOMAIN, + ACTION_ISAPI_REQUEST, + handle_isapi_request, + schema=ACTION_ISAPI_REQUEST_SCHEMA, + supports_response=SupportsResponse.ONLY, + ) diff --git a/custom_components/hikvision_next/services.yaml b/custom_components/hikvision_next/services.yaml index e54d6cf..21d4e36 100644 --- a/custom_components/hikvision_next/services.yaml +++ b/custom_components/hikvision_next/services.yaml @@ -1,6 +1,6 @@ update_snapshot: name: Update snapshot - description: Update the snapshot file in an image entity. + description: Update the snapshot file in an image entity. Used by take_pictures_on_motion_detection blueprint. target: entity: domain: image @@ -13,3 +13,52 @@ update_snapshot: example: "/media/hikvision_next/snapshots/2024-08/07/2024-08-07__11-54-19__camera01.jpg" selector: text: + +reboot: + name: Reboot + description: Reboot the device. + fields: + config_entry_id: + required: true + name: "Device" + description: "Permission needed - Remote: Shutdown/Reboot" + selector: + config_entry: + integration: hikvision_next + +isapi_request: + name: ISAPI request + description: "Send a custom ISAPI request to the device." + fields: + config_entry_id: + required: true + name: "Device" + description: "The Hikvision device." + selector: + config_entry: + integration: hikvision_next + method: + required: true + name: "HTTP method" + description: "WARNING: You do POST and PUT at your own risk!" + default: GET + selector: + select: + mode: dropdown + options: + - GET + - POST + - PUT + path: + required: true + name: "ISAPI path" + description: "The ISAPI endpoint to request. Example: /System/deviceInfo" + selector: + text: + payload: + required: false + name: "Request payload" + description: "XML data" + selector: + text: + multiline: true diff --git a/custom_components/hikvision_next/switch.py b/custom_components/hikvision_next/switch.py index c135ce0..8ce2baa 100644 --- a/custom_components/hikvision_next/switch.py +++ b/custom_components/hikvision_next/switch.py @@ -13,10 +13,10 @@ from .const import ( DOMAIN, + EVENT_IO, EVENTS_COORDINATOR, HOLIDAY_MODE, SECONDARY_COORDINATOR, - EVENT_IO, ) from .isapi import EventInfo @@ -73,7 +73,7 @@ def __init__(self, device_id: int, event: EventInfo, coordinator) -> None: @property def is_on(self) -> bool | None: """Return True if the binary sensor is on.""" - return self.coordinator.data.get(self.entity_id) + return self.coordinator.data.get(self.unique_id) async def async_turn_on(self, **kwargs: Any) -> None: """Turn on.""" @@ -93,6 +93,7 @@ async def async_turn_off(self, **kwargs: Any) -> None: finally: await self.coordinator.async_request_refresh() + class NVROutputSwitch(CoordinatorEntity, SwitchEntity): """Detection events switch.""" @@ -114,7 +115,7 @@ def __init__(self, coordinator, port_no: int) -> None: @property def is_on(self) -> bool | None: """Turn on.""" - return self.coordinator.data.get(self.entity_id) == "active" + return self.coordinator.data.get(self.unique_id) == "active" async def async_turn_on(self, **kwargs: Any) -> None: """Turn on.""" diff --git a/tests/conftest.py b/tests/conftest.py index e35069e..67c51cb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -76,7 +76,7 @@ def mock_device_endpoints(model): def mock_isapi(): """Mock ISAPI instance.""" - respx.get(f"{TEST_HOST}/ISAPI/System/status").respond(status_code=200) + respx.get(f"{TEST_HOST}/ISAPI/System/deviceInfo").respond(status_code=200) isapi = ISAPI(**TEST_CLIENT) return isapi diff --git a/tests/fixtures/devices/DS-2CD2532F-IWS.json b/tests/fixtures/devices/DS-2CD2532F-IWS.json new file mode 100644 index 0000000..43bfbd9 --- /dev/null +++ b/tests/fixtures/devices/DS-2CD2532F-IWS.json @@ -0,0 +1,1222 @@ +{ + "data": { + "ISAPI": { + "System/deviceInfo": { + "response": { + "DeviceInfo": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "deviceName": "SCD14 #TUIN", + "deviceID": "c8e3b8e9-59b0-00b0-0000-e0b0f0b0f0a0", + "deviceDescription": "IPCamera", + "deviceLocation": "hangzhou", + "systemContact": "Hikvision.China", + "model": "DS-2CD2532F-IWS", + "serialNumber": "DS-2CD2532F-IWS00000000CCCH000000000", + "macAddress": "16:33:90:0d:9b:e8", + "firmwareVersion": "V5.2.0", + "firmwareReleasedDate": "build 140721", + "encoderVersion": "V5.0", + "encoderReleasedDate": "build 140714", + "bootVersion": "V1.3.4", + "bootReleasedDate": "100316", + "hardwareVersion": "0x0", + "deviceType": "IPCamera", + "telecontrolID": "2", + "supportBeep": "false", + "supportVideoLoss": "false" + } + } + }, + "System/capabilities": { + "response": { + "DeviceCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "SysCap": { + "NetworkCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "isSupportWireless": "true", + "isSupportPPPoE": "true", + "isSupportBond": "false", + "isSupport802_1x": "false", + "isSupportNtp": "true", + "isSupportFtp": "true", + "isSupportUpnp": "true", + "isSupportDdns": "true", + "isSupportHttps": "false", + "SnmpCap": { + "isSupport": "true" + }, + "isSupportIPFilter": "true", + "isSupportEZVIZ": "true", + "isSupportEhome": "false" + }, + "IOCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "IOInputPortNums": "1", + "IOOutputPortNums": "1", + "isSupportStrobeLamp": "false" + }, + "VideoCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "videoInputPortNums": "0", + "videoOutputPortNums": "0" + }, + "AudioCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "audioInputNums": "1", + "audioOutputNums": "1" + } + }, + "voicetalkNums": "1", + "isSupportSnapshot": "true", + "SecurityCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "supportUserNums": "32", + "userBondIpNums": "0", + "userBondMacNums": "0", + "isSupCertificate": "true" + }, + "EventCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "isSupportHDFull": "true", + "isSupportHDError": "true", + "isSupportNicBroken": "true", + "isSupportIpConflict": "true", + "isSupportIllAccess": "true", + "isSupportViException": "false", + "isSupportViMismatch": "false", + "isSupportRecordException": "false", + "isSupportTriggerFocus": "false" + }, + "SmartCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "isSupportROI": "true", + "isSupportFaceDetect": "false", + "isSupportIntelliTrace": "false", + "isSupportFieldDetection": "true", + "isSupportDefocusDetection": "false", + "isSupportAudioDetection": "false", + "isSupportSceneChangeDetection": "false", + "isSupportLineDetection": "true" + }, + "WLAlarmCap": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "isSupportPIR": "false", + "isSupportWLSensors": "false", + "isSupportCallHelp": "false" + } + } + } + }, + "System/IO/inputs/1/status": { + "response": { + "IOPortStatus": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "ioPortID": "1", + "ioPortType": "input", + "ioState": "inactive" + } + } + }, + "System/IO/outputs/1/status": { + "response": { + "IOPortStatus": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "ioPortID": "1", + "ioPortType": "output", + "ioState": "active" + } + } + }, + "System/Holidays": { + "status_code": 403 + }, + "System/Video/inputs/channels": { + "response": { + "VideoInputChannelList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "VideoInputChannel": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "inputPort": "1", + "name": "SCD14", + "videoFormat": "PAL" + } + } + } + }, + "ContentMgmt/InputProxy/channels": { + "status_code": 403 + }, + "ContentMgmt/Storage": { + "response": { + "storage": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "hddList": { + "@version": "1.0", + "@xmlns": "http://www.hikvision.com/ver10/XMLSchema", + "@size": "8", + "hdd": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "hddName": "hdde", + "hddPath": null, + "hddType": "SATA", + "status": "ok", + "capacity": "30223", + "freeSpace": "0", + "property": "RW" + } + }, + "nasList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "supportMountType": { + "@opt": "NFS,SMB/CIFS" + }, + "authentication": { + "@opt": "SMB/CIFS" + } + }, + "workMode": "quota" + } + } + }, + "Security/adminAccesses": { + "response": { + "AdminAccessProtocolList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "AdminAccessProtocol": [ + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "enabled": "true", + "protocol": "HTTP", + "portNo": "80" + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "2", + "enabled": "true", + "protocol": "HTTPS", + "portNo": "443" + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "3", + "enabled": "true", + "protocol": "DEV_MANAGE", + "portNo": "8000" + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "4", + "enabled": "true", + "protocol": "RTSP", + "portNo": "554" + } + ] + } + } + }, + "Event/triggers": { + "response": { + "EventNotification": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTrigger": [ + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "IO-1", + "eventType": "IO", + "eventDescription": "IO Event trigger Information", + "inputIOPortID": "1", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "VMD-1", + "eventType": "VMD", + "eventDescription": "VMD Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "IO-1", + "notificationMethod": "IO", + "notificationRecurrence": "beginning", + "outputIOPortID": "1" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "videoloss-1", + "eventType": "videoloss", + "eventDescription": "Videoloss Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "tamper-1", + "eventType": "tamperdetection", + "eventDescription": "shelteralarm Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "diskfull", + "eventType": "diskfull", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "diskerror", + "eventType": "diskerror", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + } + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "nicbroken", + "eventType": "nicbroken", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "ipconflict", + "eventType": "ipconflict", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "illaccess", + "eventType": "illaccess", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "Linedetection-1", + "eventType": "linedetection", + "eventDescription": "Linedetection Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "IO-1", + "notificationMethod": "IO", + "notificationRecurrence": "beginning", + "outputIOPortID": "1" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "fielddetection-1", + "eventType": "fielddetection", + "eventDescription": "fielddetection Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "videomismatch", + "eventType": "videomismatch", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + } + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "badvideo", + "eventType": "badvideo", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "PIR", + "eventType": "PIR", + "eventDescription": "PIR Event trigger Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "callhelp", + "eventType": "callhelp", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "facedetection", + "eventType": "facedetection", + "eventDescription": "exception Information", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema" + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-1", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "1", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-2", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "2", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-3", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "3", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-4", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "4", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-5", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "5", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-6", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "6", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-7", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "7", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + }, + { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "WLSensor-8", + "eventType": "WLSensor", + "eventDescription": "WLSensor Event trigger Information", + "WLSensorID": "8", + "videoInputChannelID": "1", + "dynVideoInputChannelID": "1", + "EventTriggerNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "EventTriggerNotification": [ + { + "id": "email", + "notificationMethod": "email", + "notificationRecurrence": "beginning" + }, + { + "id": "record-1", + "notificationMethod": "record", + "videoInputID": "1", + "notificationRecurrence": "beginning" + }, + { + "id": "beep", + "notificationMethod": "beep", + "notificationRecurrence": "beginning" + }, + { + "id": "center", + "notificationMethod": "center", + "notificationRecurrence": "beginning" + }, + { + "id": "FTP", + "notificationMethod": "FTP", + "notificationRecurrence": "beginning" + } + ] + } + } + ] + } + } + } + }, + "Event/triggers/scenechangedetection-1": { + "status_code": 403 + }, + "Event/notification/httpHosts": { + "response": { + "HttpHostNotificationList": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "HttpHostNotification": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "url": "/api/hikvision", + "protocolType": "HTTP", + "parameterFormatType": "XML", + "addressingFormatType": "ipaddress", + "ipAddress": "1.0.0.248", + "portNo": "8123", + "userName": null, + "httpAuthenticationMethod": "none" + } + } + } + }, + "Streaming/channels/101": { + "response": { + "StreamingChannel": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "101", + "channelName": "SCD14", + "enabled": "true", + "Transport": { + "maxPacketSize": "1000", + "ControlProtocolList": { + "ControlProtocol": [ + { + "streamingTransport": "RTSP" + }, + { + "streamingTransport": "HTTP" + }, + { + "streamingTransport": "SHTTP" + } + ] + }, + "Unicast": { + "enabled": "true", + "rtpTransportType": "RTP/TCP" + }, + "Multicast": { + "enabled": "true", + "destIPAddress": "0.0.0.0", + "videoDestPortNo": "8600", + "audioDestPortNo": "8600" + }, + "Security": { + "enabled": "true" + } + }, + "Video": { + "enabled": "true", + "videoInputChannelID": "1", + "videoCodecType": "H.264", + "videoScanType": "progressive", + "videoResolutionWidth": "1920", + "videoResolutionHeight": "1080", + "videoQualityControlType": "CBR", + "constantBitRate": "4096", + "fixedQuality": "60", + "maxFrameRate": "2500", + "keyFrameInterval": "2000", + "snapShotImageType": "JPEG", + "H264Profile": "Main", + "GovLength": "50", + "SVC": { + "enabled": "false" + }, + "PacketType": [ + "PS", + "RTP" + ], + "smoothing": "50" + }, + "Audio": { + "enabled": "true", + "audioInputChannelID": "1", + "audioCompressionType": "G.711ulaw" + } + } + } + }, + "Streaming/channels/102": { + "response": { + "StreamingChannel": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "102", + "channelName": "SCD14", + "enabled": "true", + "Transport": { + "maxPacketSize": "1000", + "ControlProtocolList": { + "ControlProtocol": [ + { + "streamingTransport": "RTSP" + }, + { + "streamingTransport": "HTTP" + }, + { + "streamingTransport": "SHTTP" + } + ] + }, + "Unicast": { + "enabled": "true", + "rtpTransportType": "RTP/TCP" + }, + "Multicast": { + "enabled": "true", + "destIPAddress": "0.0.0.0", + "videoDestPortNo": "8600", + "audioDestPortNo": "8600" + }, + "Security": { + "enabled": "true" + } + }, + "Video": { + "enabled": "true", + "videoInputChannelID": "1", + "videoCodecType": "MJPEG", + "videoScanType": "progressive", + "videoResolutionWidth": "640", + "videoResolutionHeight": "480", + "videoQualityControlType": "VBR", + "constantBitRate": "256", + "fixedQuality": "60", + "vbrUpperCap": "256", + "vbrLowerCap": "32", + "maxFrameRate": "1200", + "keyFrameInterval": "4166", + "snapShotImageType": "JPEG", + "H264Profile": "Main", + "GovLength": "50", + "SVC": { + "enabled": "false" + }, + "PacketType": [ + "PS", + "RTP" + ], + "smoothing": "50" + }, + "Audio": { + "enabled": "true", + "audioInputChannelID": "1", + "audioCompressionType": "G.711ulaw" + } + } + } + }, + "Streaming/channels/103": { + "status_code": 500 + }, + "Streaming/channels/104": { + "status_code": 500 + }, + "System/IO/inputs/1": { + "response": { + "IOInputPort": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "enabled": "true", + "triggering": "high", + "name": null + } + } + }, + "System/Video/inputs/channels/1/motionDetection": { + "response": { + "MotionDetection": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "enabled": "true", + "enableHighlight": "false", + "samplingInterval": "2", + "startTriggerTime": "500", + "endTriggerTime": "500", + "regionType": "grid", + "Grid": { + "rowGranularity": "18", + "columnGranularity": "22" + }, + "MotionDetectionLayout": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "sensitivityLevel": "20", + "layout": { + "gridMap": "fffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffcfffffc" + } + } + } + } + }, + "System/Video/inputs/channels/1/videoLoss": { + "response": { + "VideoLoss": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "enabled": "false" + } + } + }, + "System/Video/inputs/channels/1/tamperDetection": { + "response": { + "TamperDetection": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "enabled": "false", + "normalizedScreenSize": { + "normalizedScreenWidth": "704", + "normalizedScreenHeight": "576" + }, + "TamperDetectionRegionList": { + "TamperDetectionRegion": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "enabled": "false", + "sensitivityLevel": "0", + "RegionCoordinatesList": null + } + } + } + } + }, + "Smart/LineDetection/1": { + "response": { + "LineDetection": { + "id": "1", + "enabled": "true", + "normalizedScreenSize": { + "normalizedScreenWidth": "1000", + "normalizedScreenHeight": "1000" + }, + "LineItemList": { + "@size": "1", + "LineItem": { + "id": "1", + "enabled": "false", + "sensitivityLevel": "50", + "directionSensitivity": "any", + "CoordinatesList": { + "Coordinates": [ + { + "positionX": "231", + "positionY": "835" + }, + { + "positionX": "740", + "positionY": "832" + } + ] + } + } + } + } + } + }, + "Smart/FieldDetection/1": { + "response": { + "FieldDetection": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "enabled": "false", + "startTriggerTime": "500", + "endTriggerTime": "500", + "normalizedScreenSize": { + "normalizedScreenWidth": "1000", + "normalizedScreenHeight": "1000" + }, + "FieldDetectionRegionList": { + "@size": "1", + "FieldDetectionRegion": { + "@version": "2.0", + "@xmlns": "http://www.hikvision.com/ver20/XMLSchema", + "id": "1", + "enabled": "false", + "sensitivityLevel": "50", + "timeThreshold": "0", + "objectOccupation": "1" + } + } + } + } + }, + "WLAlarm/PIR": { + "status_code": 403 + } + } + } +} \ No newline at end of file diff --git a/tests/test_init.py b/tests/test_init.py index d31666d..c032df6 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -15,6 +15,7 @@ "DS-2CD2386G2-IU", "DS-2CD2146G2-ISU", "DS-2CD2443G0-IW", + "DS-2CD2532F-IWS", "DS-7616NI-K2", "DS-7616NI-Q2", "DS-7732NI-M4", diff --git a/tests/test_pir.py b/tests/test_pir.py index b235762..a4100d6 100644 --- a/tests/test_pir.py +++ b/tests/test_pir.py @@ -4,12 +4,13 @@ import pytest from http import HTTPStatus from homeassistant.core import HomeAssistant +from custom_components.hikvision_next.const import DOMAIN, EVENT_PIR from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from pytest_homeassistant_custom_component.common import MockConfigEntry import homeassistant.helpers.entity_registry as er from custom_components.hikvision_next.notifications import EventNotificationsView from tests.test_notifications import mock_event_notification -from tests.conftest import TEST_CONFIG +from tests.conftest import TEST_HOST from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TURN_OFF, @@ -62,7 +63,7 @@ async def test_pir_switch(hass: HomeAssistant, init_integration: MockConfigEntry assert (switch := hass.states.get(entity_id)) assert switch.state == STATE_ON - url = f"{TEST_CONFIG['host']}/ISAPI/WLAlarm/PIR" + url = f"{TEST_HOST}/ISAPI/WLAlarm/PIR" endpoint = respx.put(url) # switch to off @@ -73,3 +74,31 @@ async def test_pir_switch(hass: HomeAssistant, init_integration: MockConfigEntry blocking=True, ) assert endpoint.called + + +@pytest.mark.parametrize("init_integration", ["DS-2CD2443G0-IW", "DS-2CD2532F-IWS", "DS-2CD2386G2-IU"], indirect=True) +async def test_pir_support_detection( + hass: HomeAssistant, + init_integration: MockConfigEntry, +) -> None: + """Test PIR entities creation """ + + device_data = { + "DS-2CD2443G0-IW": { + "isSupportPIR": True, + }, + "DS-2CD2532F-IWS": { + "isSupportPIR": False, + }, + "DS-2CD2386G2-IU": { + "isSupportPIR": False, + } + } + + entry = init_integration + isapi = hass.data[DOMAIN][entry.entry_id]["isapi"] + data = device_data[init_integration.title] + pir_events = [ + s for s in isapi.supported_events if (s.event_id == EVENT_PIR) + ] + assert (len(pir_events) == 1) == data["isSupportPIR"] diff --git a/tests/test_services.py b/tests/test_services.py new file mode 100644 index 0000000..bb51b98 --- /dev/null +++ b/tests/test_services.py @@ -0,0 +1,32 @@ +"""Tests for actions.""" + +import pytest +import respx +from homeassistant.core import HomeAssistant +from pytest_homeassistant_custom_component.common import MockConfigEntry +from custom_components.hikvision_next.const import ( + ACTION_REBOOT, + ATTR_CONFIG_ENTRY_ID, + DOMAIN, +) +from tests.conftest import TEST_HOST + + +@respx.mock +@pytest.mark.parametrize("init_integration", ["DS-7608NXI-I2"], indirect=True) +async def test_reboot_action(hass: HomeAssistant, init_integration: MockConfigEntry) -> None: + """Test sending reboot request on reboot action.""" + + mock_config_entry = init_integration + + url = f"{TEST_HOST}/ISAPI/System/reboot" + endpoint = respx.put(url).respond() + + await hass.services.async_call( + DOMAIN, + ACTION_REBOOT, + {ATTR_CONFIG_ENTRY_ID: mock_config_entry.entry_id}, + blocking=True, + ) + + assert endpoint.called diff --git a/tests/test_switch.py b/tests/test_switch.py index 19f4eae..e3499e9 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -4,7 +4,7 @@ import pytest import httpx from homeassistant.core import HomeAssistant -from tests.conftest import TEST_CONFIG +from tests.conftest import TEST_HOST from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from pytest_homeassistant_custom_component.common import MockConfigEntry import homeassistant.helpers.entity_registry as er @@ -64,7 +64,7 @@ def update_side_effect(request, route): raise AssertionError("Request content does not match expected payload") return httpx.Response(200) - url = f"{TEST_CONFIG['host']}/ISAPI/ContentMgmt/InputProxy/channels/1/video/videoLoss" + url = f"{TEST_HOST}/ISAPI/ContentMgmt/InputProxy/channels/1/video/videoLoss" endpoint = respx.put(url).mock(side_effect=update_side_effect) # do not call if already on