From b530e3c7be78aa170ebaf1b2b605f87617af93f7 Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Wed, 9 Jul 2025 20:13:10 -0400 Subject: [PATCH 1/6] Added privacy mode --- custom_components/dahua/camera.py | 13 ++++++++ custom_components/dahua/const.py | 1 + custom_components/dahua/rpc2.py | 26 ++++++++++++++++ custom_components/dahua/services.yaml | 17 +++++++++++ custom_components/dahua/switch.py | 44 ++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 1 deletion(-) diff --git a/custom_components/dahua/camera.py b/custom_components/dahua/camera.py index 1add93d..ec21b70 100755 --- a/custom_components/dahua/camera.py +++ b/custom_components/dahua/camera.py @@ -23,6 +23,7 @@ SERVICE_SET_VIDEO_PROFILE_MODE = "set_video_profile_mode" SERVICE_SET_FOCUS_ZOOM = "set_focus_zoom" SERVICE_SET_PRIVACY_MASKING = "set_privacy_masking" +SERVICE_SET_PRIVACY_MODE = "set_privacy_mode" SERVICE_SET_CHANNEL_TITLE = "set_channel_title" SERVICE_SET_TEXT_OVERLAY = "set_text_overlay" SERVICE_SET_CUSTOM_OVERLAY = "set_custom_overlay" @@ -93,6 +94,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry, async_add_entitie "async_set_privacy_masking" ) + platform.async_register_entity_service( + SERVICE_SET_PRIVACY_MODE, + { + vol.Required("enabled", default=False): bool, + }, + "async_set_privacy_mode" + ) + platform.async_register_entity_service( SERVICE_ENABLE_CHANNEL_TITLE, { @@ -330,6 +339,10 @@ async def async_set_privacy_masking(self, index: int, enabled: bool): """ Handles the service call from SERVICE_SET_PRIVACY_MASKING to control the privacy masking """ await self._coordinator.client.async_setprivacymask(index, enabled) + async def async_set_privacy_mode(self, enabled: bool): + """ Handles the service call from SERVICE_SET_PRIVACY_MODE to control the privacy mode """ + await self._coordinator.rpc2_client.set_privacy_mode(enabled) + async def async_set_enable_channel_title(self, enabled: bool): """ Handles the service call from SERVICE_ENABLE_CHANNEL_TITLE """ channel = self._coordinator.get_channel() diff --git a/custom_components/dahua/const.py b/custom_components/dahua/const.py index ec75962..99c982f 100755 --- a/custom_components/dahua/const.py +++ b/custom_components/dahua/const.py @@ -15,6 +15,7 @@ DISARMING_ICON = "mdi:alarm-check" VOLUME_HIGH_ICON = "mdi:volume-high" BELL_ICON = "mdi:bell-ring" +PRIVACY_MODE_ICON = "mdi:shield-lock" # Device classes - https://www.home-assistant.io/integrations/binary_sensor/#device-class MOTION_SENSOR_DEVICE_CLASS = "motion" diff --git a/custom_components/dahua/rpc2.py b/custom_components/dahua/rpc2.py index f5a7f85..7d5e5a6 100644 --- a/custom_components/dahua/rpc2.py +++ b/custom_components/dahua/rpc2.py @@ -135,3 +135,29 @@ async def get_coaxial_control_io_status(self, channel: int) -> CoaxialControlIOS """ async_get_coaxial_control_io_status returns the the current state of the speaker and white light. """ response = await self.request(method="CoaxialControlIO.getStatus", params={"channel": channel}) return CoaxialControlIOStatus(response) + + async def get_privacy_mode_config(self) -> dict: + """Gets the current privacy mode (LeLensMask) configuration.""" + response = await self.get_config({"name": "LeLensMask"}) + return response + + async def set_privacy_mode(self, enabled: bool) -> bool: + """Sets privacy mode on or off.""" + # Default time sections for all days, all hours + default_time_sections = [ + ["1 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", + "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59"] + ] * 7 + + params = { + "name": "LeLensMask", + "table": [{ + "Enable": enabled, + "LastPosition": [-0.5861111111111111, -0.2061111111111111, 0.0078125], + "TimeSection": default_time_sections + }], + "options": [] + } + + response = await self.request(method="configManager.setConfig", params=params) + return response['result'] diff --git a/custom_components/dahua/services.yaml b/custom_components/dahua/services.yaml index 0eac315..163541d 100755 --- a/custom_components/dahua/services.yaml +++ b/custom_components/dahua/services.yaml @@ -444,3 +444,20 @@ set_privacy_masking: default: true selector: boolean: + +set_privacy_mode: + name: Set Privacy Mode + description: Enables or disables the camera's privacy mode (lens masking) + target: + entity: + integration: dahua + domain: camera + fields: + enabled: + name: Enabled + description: "If true enables privacy mode, otherwise disables it" + example: true + required: true + default: true + selector: + boolean: diff --git a/custom_components/dahua/switch.py b/custom_components/dahua/switch.py index 16eca97..5fd9b80 100755 --- a/custom_components/dahua/switch.py +++ b/custom_components/dahua/switch.py @@ -4,7 +4,7 @@ from homeassistant.components.switch import SwitchEntity from custom_components.dahua import DahuaDataUpdateCoordinator -from .const import DOMAIN, DISARMING_ICON, MOTION_DETECTION_ICON, SIREN_ICON, BELL_ICON +from .const import DOMAIN, DISARMING_ICON, MOTION_DETECTION_ICON, SIREN_ICON, BELL_ICON, PRIVACY_MODE_ICON from .entity import DahuaBaseEntity from .client import SIREN_TYPE @@ -31,6 +31,10 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): except ClientError as exception: pass + # Add privacy mode switch if RPC2 client is available + if hasattr(coordinator, 'rpc2_client') and coordinator.rpc2_client: + devices.append(DahuaPrivacyModeBinarySwitch(coordinator, entry)) + async_add_devices(devices) @@ -243,3 +247,41 @@ def is_on(self): Value is fetched from api.get_motion_detection_config """ return self._coordinator.is_siren_on() + + +class DahuaPrivacyModeBinarySwitch(DahuaBaseEntity, SwitchEntity): + """Dahua privacy mode switch class. Used to enable or disable privacy mode.""" + + async def async_turn_on(self, **kwargs): + """Turn on privacy mode.""" + await self._coordinator.rpc2_client.set_privacy_mode(True) + await self._coordinator.async_refresh() + + async def async_turn_off(self, **kwargs): + """Turn off privacy mode.""" + await self._coordinator.rpc2_client.set_privacy_mode(False) + await self._coordinator.async_refresh() + + @property + def name(self): + """Return the name of the switch.""" + return self._coordinator.get_device_name() + " Privacy Mode" + + @property + def unique_id(self): + """Return unique ID.""" + return self._coordinator.get_serial_number() + "_privacy_mode" + + @property + def icon(self): + """Return the icon of this switch.""" + return PRIVACY_MODE_ICON + + @property + def is_on(self): + """Return true if privacy mode is on.""" + try: + # Since we don't have a coordinator method yet, we'll check directly + return self._coordinator.data.get("privacy_mode_enabled", False) + except (KeyError, AttributeError): + return False From 7a3b0837b4f96c3eec57eef8b35697c70b8e57ec Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Wed, 9 Jul 2025 20:32:00 -0400 Subject: [PATCH 2/6] bugfixes --- custom_components/dahua/__init__.py | 33 +++++++++++++++ custom_components/dahua/switch.py | 8 +--- test_privacy_mode.py | 62 +++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 test_privacy_mode.py diff --git a/custom_components/dahua/__init__.py b/custom_components/dahua/__init__.py index 318b438..09e2512 100755 --- a/custom_components/dahua/__init__.py +++ b/custom_components/dahua/__init__.py @@ -24,6 +24,7 @@ from custom_components.dahua.thread import DahuaEventThread, DahuaVtoEventThread from . import dahua_utils from .client import DahuaClient +from .rpc2 import DahuaRpc2Client from .const import ( CONF_EVENTS, @@ -102,6 +103,10 @@ def __init__(self, hass: HomeAssistant, events: list, address: str, port: int, r # The client used to communicate with Dahua devices self.client: DahuaClient = DahuaClient(username, password, address, port, rtsp_port, self._session) + + # RPC2 client for advanced features like privacy mode + self.rpc2_client: DahuaRpc2Client = DahuaRpc2Client(username, password, address, port, rtsp_port, self._session) + self._rpc2_available = False self.platforms = [] self.initialized = False @@ -281,6 +286,14 @@ async def _async_update_data(self): pass _LOGGER.info("Device supports Lighting_V2=%s", self._supports_lighting_v2) + # Test RPC2 client availability + try: + await self.rpc2_client.login() + self._rpc2_available = True + _LOGGER.info("RPC2 client initialized successfully") + except Exception as exception: + self._rpc2_available = False + _LOGGER.debug("RPC2 client not available: %s", exception) if not is_doorbell: # Start the event listeners for IP cameras @@ -342,6 +355,8 @@ async def _async_update_data(self): coros.append(asyncio.ensure_future(self.client.async_get_light_global_enabled())) if self._supports_lighting_v2: #add lighing_v2 API if it is supported coros.append(asyncio.ensure_future(self.client.async_get_lighting_v2())) + if self._rpc2_available: + coros.append(asyncio.ensure_future(self._fetch_privacy_mode_status())) # Gather results and update the data map @@ -725,6 +740,24 @@ def supports_smart_motion_detection_amcrest(self) -> bool: """ True if smart motion detection is supported for an amcrest device""" return self.model == "AD410" or self.model == "DB61i" + def supports_privacy_mode(self) -> bool: + """ True if privacy mode is supported (RPC2 client available)""" + return self._rpc2_available + + def is_privacy_mode_enabled(self) -> bool: + """ True if privacy mode is enabled""" + return self.data.get("privacy_mode_enabled", False) + + async def _fetch_privacy_mode_status(self) -> dict: + """ Fetch the current privacy mode status from the camera""" + try: + config = await self.rpc2_client.get_privacy_mode_config() + enabled = config.get("table", [{}])[0].get("Enable", False) + return {"privacy_mode_enabled": enabled} + except Exception as exception: + _LOGGER.debug("Failed to fetch privacy mode status: %s", exception) + return {"privacy_mode_enabled": False} + def get_vto_client(self) -> DahuaVTOClient: """ Returns an instance of the connected VTO client if this is a VTO device. We need this because there's different diff --git a/custom_components/dahua/switch.py b/custom_components/dahua/switch.py index 5fd9b80..0121b1c 100755 --- a/custom_components/dahua/switch.py +++ b/custom_components/dahua/switch.py @@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): pass # Add privacy mode switch if RPC2 client is available - if hasattr(coordinator, 'rpc2_client') and coordinator.rpc2_client: + if coordinator.supports_privacy_mode(): devices.append(DahuaPrivacyModeBinarySwitch(coordinator, entry)) async_add_devices(devices) @@ -280,8 +280,4 @@ def icon(self): @property def is_on(self): """Return true if privacy mode is on.""" - try: - # Since we don't have a coordinator method yet, we'll check directly - return self._coordinator.data.get("privacy_mode_enabled", False) - except (KeyError, AttributeError): - return False + return self._coordinator.is_privacy_mode_enabled() diff --git a/test_privacy_mode.py b/test_privacy_mode.py new file mode 100644 index 0000000..1708c30 --- /dev/null +++ b/test_privacy_mode.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +""" +Test script for privacy mode functionality +""" +import asyncio +import aiohttp +import ssl +from custom_components.dahua.rpc2 import DahuaRpc2Client + +async def test_privacy_mode(): + """Test the privacy mode functionality""" + # SSL context for self-signed certificates + ssl_context = ssl.create_default_context() + ssl_context.set_ciphers("DEFAULT") + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + connector = aiohttp.TCPConnector(ssl=ssl_context) + + async with aiohttp.ClientSession(connector=connector) as session: + # Replace these with your camera credentials + username = "admin" + password = "your_password" + address = "your_camera_ip" + port = 80 + rtsp_port = 554 + + client = DahuaRpc2Client(username, password, address, port, rtsp_port, session) + + try: + # Test login + print("Testing RPC2 login...") + await client.login() + print("✓ RPC2 login successful") + + # Test get privacy mode config + print("\nTesting get privacy mode config...") + config = await client.get_privacy_mode_config() + print(f"✓ Privacy mode config: {config}") + + # Test set privacy mode + print("\nTesting set privacy mode...") + result = await client.set_privacy_mode(True) + print(f"✓ Set privacy mode result: {result}") + + # Test logout + print("\nTesting logout...") + await client.logout() + print("✓ Logout successful") + + except Exception as e: + print(f"✗ Error: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + print("Privacy Mode Test Script") + print("=" * 30) + print("Make sure to update the credentials in the script!") + print() + + asyncio.run(test_privacy_mode()) \ No newline at end of file From 139cd6c3988ffa47125f7530799239f56fd2c9f9 Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Thu, 10 Jul 2025 18:49:55 -0400 Subject: [PATCH 3/6] Fixed toggle issue --- custom_components/dahua/__init__.py | 7 +++++- custom_components/dahua/rpc2.py | 36 +++++++++++++++++++++++++---- custom_components/dahua/switch.py | 21 +++++++++++++---- 3 files changed, 54 insertions(+), 10 deletions(-) diff --git a/custom_components/dahua/__init__.py b/custom_components/dahua/__init__.py index 09e2512..b1b1252 100755 --- a/custom_components/dahua/__init__.py +++ b/custom_components/dahua/__init__.py @@ -752,7 +752,12 @@ async def _fetch_privacy_mode_status(self) -> dict: """ Fetch the current privacy mode status from the camera""" try: config = await self.rpc2_client.get_privacy_mode_config() - enabled = config.get("table", [{}])[0].get("Enable", False) + # Extract the Enable flag from the table array + table = config.get("table", []) + if table and len(table) > 0: + enabled = table[0].get("Enable", False) + else: + enabled = False return {"privacy_mode_enabled": enabled} except Exception as exception: _LOGGER.debug("Failed to fetch privacy mode status: %s", exception) diff --git a/custom_components/dahua/rpc2.py b/custom_components/dahua/rpc2.py index 7d5e5a6..2dcd635 100644 --- a/custom_components/dahua/rpc2.py +++ b/custom_components/dahua/rpc2.py @@ -53,10 +53,18 @@ async def request(self, method, params=None, object_id=None, extra=None, url=Non url = "{0}/RPC2".format(self._base) resp = await self._session.post(url, data=json.dumps(data)) - resp_json = json.loads(await resp.text()) + resp_text = await resp.text() + + try: + resp_json = json.loads(resp_text) + except json.JSONDecodeError as e: + _LOGGER.error("Failed to parse JSON response: %s", resp_text) + raise ConnectionError(f"Invalid JSON response: {resp_text}") - if verify_result and resp_json['result'] is False: - raise ConnectionError(str(resp)) + if verify_result and resp_json.get('result') is False: + error_msg = resp_json.get('error', {}) + _LOGGER.error("RPC2 request failed: %s", error_msg) + raise ConnectionError(f"RPC2 error: {error_msg}") return resp_json @@ -138,11 +146,19 @@ async def get_coaxial_control_io_status(self, channel: int) -> CoaxialControlIOS async def get_privacy_mode_config(self) -> dict: """Gets the current privacy mode (LeLensMask) configuration.""" + await self._ensure_logged_in() response = await self.get_config({"name": "LeLensMask"}) return response + async def _ensure_logged_in(self): + """Ensure we have a valid session, login if needed.""" + if not self._session_id: + await self.login() + async def set_privacy_mode(self, enabled: bool) -> bool: """Sets privacy mode on or off.""" + await self._ensure_logged_in() + # Default time sections for all days, all hours default_time_sections = [ ["1 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", @@ -159,5 +175,15 @@ async def set_privacy_mode(self, enabled: bool) -> bool: "options": [] } - response = await self.request(method="configManager.setConfig", params=params) - return response['result'] + try: + response = await self.request(method="configManager.setConfig", params=params) + # For configManager.setConfig, success is indicated by result being True or the method completing without error + return response.get('result', True) is not False + except ConnectionError as e: + # If session expired, try to login again and retry once + if "session" in str(e).lower() or "login" in str(e).lower(): + _LOGGER.info("Session expired, re-logging in and retrying") + await self.login() + response = await self.request(method="configManager.setConfig", params=params) + return response.get('result', True) is not False + raise diff --git a/custom_components/dahua/switch.py b/custom_components/dahua/switch.py index 0121b1c..6b5fa6c 100755 --- a/custom_components/dahua/switch.py +++ b/custom_components/dahua/switch.py @@ -1,4 +1,5 @@ """Switch platform for dahua.""" +import logging from aiohttp import ClientError from homeassistant.core import HomeAssistant from homeassistant.components.switch import SwitchEntity @@ -8,6 +9,8 @@ from .entity import DahuaBaseEntity from .client import SIREN_TYPE +_LOGGER: logging.Logger = logging.getLogger(__package__) + async def async_setup_entry(hass: HomeAssistant, entry, async_add_devices): """Setup sensor platform.""" @@ -254,13 +257,23 @@ class DahuaPrivacyModeBinarySwitch(DahuaBaseEntity, SwitchEntity): async def async_turn_on(self, **kwargs): """Turn on privacy mode.""" - await self._coordinator.rpc2_client.set_privacy_mode(True) - await self._coordinator.async_refresh() + try: + result = await self._coordinator.rpc2_client.set_privacy_mode(True) + if result: + await self._coordinator.async_refresh() + except Exception as e: + _LOGGER.error("Failed to turn on privacy mode: %s", e) + raise async def async_turn_off(self, **kwargs): """Turn off privacy mode.""" - await self._coordinator.rpc2_client.set_privacy_mode(False) - await self._coordinator.async_refresh() + try: + result = await self._coordinator.rpc2_client.set_privacy_mode(False) + if result: + await self._coordinator.async_refresh() + except Exception as e: + _LOGGER.error("Failed to turn off privacy mode: %s", e) + raise @property def name(self): From 66fac18a97cbda6598eb7c563f2c3f57ce174050 Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Sun, 20 Jul 2025 23:02:57 -0400 Subject: [PATCH 4/6] Fix session expiry --- custom_components/dahua/rpc2.py | 48 +++++++++++----- debug_privacy_mode.py | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 14 deletions(-) create mode 100644 debug_privacy_mode.py diff --git a/custom_components/dahua/rpc2.py b/custom_components/dahua/rpc2.py index 2dcd635..15017f1 100644 --- a/custom_components/dahua/rpc2.py +++ b/custom_components/dahua/rpc2.py @@ -63,8 +63,37 @@ async def request(self, method, params=None, object_id=None, extra=None, url=Non if verify_result and resp_json.get('result') is False: error_msg = resp_json.get('error', {}) - _LOGGER.error("RPC2 request failed: %s", error_msg) - raise ConnectionError(f"RPC2 error: {error_msg}") + error_code = error_msg.get('code', 0) + + # Check if it's a session error (code 287637505 means "Invalid session") + if error_code == 287637505 or 'session' in str(error_msg).lower(): + _LOGGER.info("Session expired, attempting to re-login") + # Clear the session and try to login again + self._session_id = None + await self.login() + + # Retry the request with the new session + if self._session_id: + data['session'] = self._session_id + resp = await self._session.post(url, data=json.dumps(data)) + resp_text = await resp.text() + try: + resp_json = json.loads(resp_text) + except json.JSONDecodeError as e: + _LOGGER.error("Failed to parse JSON response after re-login: %s", resp_text) + raise ConnectionError(f"Invalid JSON response: {resp_text}") + + # Check the result again + if resp_json.get('result') is False: + error_msg = resp_json.get('error', {}) + _LOGGER.error("RPC2 request failed after re-login: %s", error_msg) + raise ConnectionError(f"RPC2 error: {error_msg}") + else: + _LOGGER.error("Failed to re-login after session expiry") + raise ConnectionError(f"RPC2 error: {error_msg}") + else: + _LOGGER.error("RPC2 request failed: %s", error_msg) + raise ConnectionError(f"RPC2 error: {error_msg}") return resp_json @@ -175,15 +204,6 @@ async def set_privacy_mode(self, enabled: bool) -> bool: "options": [] } - try: - response = await self.request(method="configManager.setConfig", params=params) - # For configManager.setConfig, success is indicated by result being True or the method completing without error - return response.get('result', True) is not False - except ConnectionError as e: - # If session expired, try to login again and retry once - if "session" in str(e).lower() or "login" in str(e).lower(): - _LOGGER.info("Session expired, re-logging in and retrying") - await self.login() - response = await self.request(method="configManager.setConfig", params=params) - return response.get('result', True) is not False - raise + response = await self.request(method="configManager.setConfig", params=params) + # For configManager.setConfig, success is indicated by result being True or the method completing without error + return response.get('result', True) is not False diff --git a/debug_privacy_mode.py b/debug_privacy_mode.py new file mode 100644 index 0000000..5f5ad22 --- /dev/null +++ b/debug_privacy_mode.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +""" +Debug script for privacy mode functionality +""" +import asyncio +import aiohttp +import ssl +import json +from custom_components.dahua.rpc2 import DahuaRpc2Client + +async def debug_privacy_mode(): + """Debug the privacy mode functionality""" + # SSL context for self-signed certificates + ssl_context = ssl.create_default_context() + ssl_context.set_ciphers("DEFAULT") + ssl_context.check_hostname = False + ssl_context.verify_mode = ssl.CERT_NONE + + connector = aiohttp.TCPConnector(ssl=ssl_context) + + async with aiohttp.ClientSession(connector=connector) as session: + # Replace these with your camera credentials + username = "admin" + password = "your_password" + address = "your_camera_ip" + port = 80 + rtsp_port = 554 + + client = DahuaRpc2Client(username, password, address, port, rtsp_port, session) + + try: + # Test login + print("Step 1: Testing RPC2 login...") + login_response = await client.login() + print(f"✓ Login response: {json.dumps(login_response, indent=2)}") + + # Test get privacy mode config (initial state) + print("\nStep 2: Getting initial privacy mode config...") + config = await client.get_privacy_mode_config() + print(f"✓ Initial config: {json.dumps(config, indent=2)}") + + current_state = config.get("table", [{}])[0].get("Enable", False) + print(f"Current privacy mode state: {current_state}") + + # Test set privacy mode to opposite state + new_state = not current_state + print(f"\nStep 3: Setting privacy mode to {new_state}...") + + # Let's manually make the request to see the raw response + params = { + "name": "LeLensMask", + "table": [{ + "Enable": new_state, + "LastPosition": [-0.5861111111111111, -0.2061111111111111, 0.0078125], + "TimeSection": [ + ["1 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", + "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59"] + ] * 7 + }], + "options": [] + } + + print(f"Request params: {json.dumps(params, indent=2)}") + + response = await client.request(method="configManager.setConfig", params=params) + print(f"✓ Set privacy mode response: {json.dumps(response, indent=2)}") + + # Test get privacy mode config (after change) + print("\nStep 4: Getting privacy mode config after change...") + config_after = await client.get_privacy_mode_config() + print(f"✓ Config after change: {json.dumps(config_after, indent=2)}") + + new_current_state = config_after.get("table", [{}])[0].get("Enable", False) + print(f"New privacy mode state: {new_current_state}") + + if new_current_state == new_state: + print("✓ Privacy mode change successful!") + else: + print("✗ Privacy mode change failed!") + + # Test logout + print("\nStep 5: Testing logout...") + logout_response = await client.logout() + print(f"✓ Logout response: {json.dumps(logout_response, indent=2)}") + + except Exception as e: + print(f"✗ Error: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + print("Privacy Mode Debug Script") + print("=" * 40) + print("Make sure to update the credentials in the script!") + print() + + asyncio.run(debug_privacy_mode()) \ No newline at end of file From 6ca241e938135e0f9491a80a1d8cd5d17d5c663e Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Sun, 12 Oct 2025 10:40:40 -0400 Subject: [PATCH 5/6] Removing test and debug files --- debug_privacy_mode.py | 97 ------------------------------------------- test_privacy_mode.py | 62 --------------------------- 2 files changed, 159 deletions(-) delete mode 100644 debug_privacy_mode.py delete mode 100644 test_privacy_mode.py diff --git a/debug_privacy_mode.py b/debug_privacy_mode.py deleted file mode 100644 index 5f5ad22..0000000 --- a/debug_privacy_mode.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env python3 -""" -Debug script for privacy mode functionality -""" -import asyncio -import aiohttp -import ssl -import json -from custom_components.dahua.rpc2 import DahuaRpc2Client - -async def debug_privacy_mode(): - """Debug the privacy mode functionality""" - # SSL context for self-signed certificates - ssl_context = ssl.create_default_context() - ssl_context.set_ciphers("DEFAULT") - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - connector = aiohttp.TCPConnector(ssl=ssl_context) - - async with aiohttp.ClientSession(connector=connector) as session: - # Replace these with your camera credentials - username = "admin" - password = "your_password" - address = "your_camera_ip" - port = 80 - rtsp_port = 554 - - client = DahuaRpc2Client(username, password, address, port, rtsp_port, session) - - try: - # Test login - print("Step 1: Testing RPC2 login...") - login_response = await client.login() - print(f"✓ Login response: {json.dumps(login_response, indent=2)}") - - # Test get privacy mode config (initial state) - print("\nStep 2: Getting initial privacy mode config...") - config = await client.get_privacy_mode_config() - print(f"✓ Initial config: {json.dumps(config, indent=2)}") - - current_state = config.get("table", [{}])[0].get("Enable", False) - print(f"Current privacy mode state: {current_state}") - - # Test set privacy mode to opposite state - new_state = not current_state - print(f"\nStep 3: Setting privacy mode to {new_state}...") - - # Let's manually make the request to see the raw response - params = { - "name": "LeLensMask", - "table": [{ - "Enable": new_state, - "LastPosition": [-0.5861111111111111, -0.2061111111111111, 0.0078125], - "TimeSection": [ - ["1 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", - "0 00:00:00-23:59:59", "0 00:00:00-23:59:59", "0 00:00:00-23:59:59"] - ] * 7 - }], - "options": [] - } - - print(f"Request params: {json.dumps(params, indent=2)}") - - response = await client.request(method="configManager.setConfig", params=params) - print(f"✓ Set privacy mode response: {json.dumps(response, indent=2)}") - - # Test get privacy mode config (after change) - print("\nStep 4: Getting privacy mode config after change...") - config_after = await client.get_privacy_mode_config() - print(f"✓ Config after change: {json.dumps(config_after, indent=2)}") - - new_current_state = config_after.get("table", [{}])[0].get("Enable", False) - print(f"New privacy mode state: {new_current_state}") - - if new_current_state == new_state: - print("✓ Privacy mode change successful!") - else: - print("✗ Privacy mode change failed!") - - # Test logout - print("\nStep 5: Testing logout...") - logout_response = await client.logout() - print(f"✓ Logout response: {json.dumps(logout_response, indent=2)}") - - except Exception as e: - print(f"✗ Error: {e}") - import traceback - traceback.print_exc() - -if __name__ == "__main__": - print("Privacy Mode Debug Script") - print("=" * 40) - print("Make sure to update the credentials in the script!") - print() - - asyncio.run(debug_privacy_mode()) \ No newline at end of file diff --git a/test_privacy_mode.py b/test_privacy_mode.py deleted file mode 100644 index 1708c30..0000000 --- a/test_privacy_mode.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 -""" -Test script for privacy mode functionality -""" -import asyncio -import aiohttp -import ssl -from custom_components.dahua.rpc2 import DahuaRpc2Client - -async def test_privacy_mode(): - """Test the privacy mode functionality""" - # SSL context for self-signed certificates - ssl_context = ssl.create_default_context() - ssl_context.set_ciphers("DEFAULT") - ssl_context.check_hostname = False - ssl_context.verify_mode = ssl.CERT_NONE - - connector = aiohttp.TCPConnector(ssl=ssl_context) - - async with aiohttp.ClientSession(connector=connector) as session: - # Replace these with your camera credentials - username = "admin" - password = "your_password" - address = "your_camera_ip" - port = 80 - rtsp_port = 554 - - client = DahuaRpc2Client(username, password, address, port, rtsp_port, session) - - try: - # Test login - print("Testing RPC2 login...") - await client.login() - print("✓ RPC2 login successful") - - # Test get privacy mode config - print("\nTesting get privacy mode config...") - config = await client.get_privacy_mode_config() - print(f"✓ Privacy mode config: {config}") - - # Test set privacy mode - print("\nTesting set privacy mode...") - result = await client.set_privacy_mode(True) - print(f"✓ Set privacy mode result: {result}") - - # Test logout - print("\nTesting logout...") - await client.logout() - print("✓ Logout successful") - - except Exception as e: - print(f"✗ Error: {e}") - import traceback - traceback.print_exc() - -if __name__ == "__main__": - print("Privacy Mode Test Script") - print("=" * 30) - print("Make sure to update the credentials in the script!") - print() - - asyncio.run(test_privacy_mode()) \ No newline at end of file From ffc4756980eeffb1f5d7ca53e7c2e7a88261d1b6 Mon Sep 17 00:00:00 2001 From: AdrianCzebiniak Date: Mon, 13 Oct 2025 21:08:07 -0400 Subject: [PATCH 6/6] Removed hardcoded position --- custom_components/dahua/rpc2.py | 1 - 1 file changed, 1 deletion(-) diff --git a/custom_components/dahua/rpc2.py b/custom_components/dahua/rpc2.py index 15017f1..02bffa8 100644 --- a/custom_components/dahua/rpc2.py +++ b/custom_components/dahua/rpc2.py @@ -198,7 +198,6 @@ async def set_privacy_mode(self, enabled: bool) -> bool: "name": "LeLensMask", "table": [{ "Enable": enabled, - "LastPosition": [-0.5861111111111111, -0.2061111111111111, 0.0078125], "TimeSection": default_time_sections }], "options": []