From de93d1d52a989ee4deadcc716602bcf58d6610b9 Mon Sep 17 00:00:00 2001 From: Paul Tarjan Date: Thu, 19 Mar 2026 11:39:28 -0600 Subject: [PATCH] Skip unmapped and watchdog event types in Hikvision NVR event injection (#165009) Co-authored-by: Claude Opus 4.6 --- .../components/hikvision/__init__.py | 12 +++- tests/components/hikvision/conftest.py | 2 +- tests/components/hikvision/test_init.py | 55 +++++++++++++++++++ 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/hikvision/__init__.py b/homeassistant/components/hikvision/__init__.py index af4f788d255..b6cb1e7617d 100644 --- a/homeassistant/components/hikvision/__init__.py +++ b/homeassistant/components/hikvision/__init__.py @@ -117,13 +117,21 @@ async def async_setup_entry(hass: HomeAssistant, entry: HikvisionConfigEntry) -> # Map raw event type names to friendly names using SENSOR_MAP mapped_events: dict[str, list[int]] = {} for event_type, channels in nvr_events.items(): - friendly_name = SENSOR_MAP.get(event_type.lower(), event_type) + event_key = event_type.lower() + # Skip videoloss - used as watchdog by pyhik, not a real sensor + if event_key == "videoloss": + continue + friendly_name = SENSOR_MAP.get(event_key) + if friendly_name is None: + _LOGGER.debug("Skipping unmapped event type: %s", event_type) + continue if friendly_name in mapped_events: mapped_events[friendly_name].extend(channels) else: mapped_events[friendly_name] = list(channels) _LOGGER.debug("Mapped NVR events: %s", mapped_events) - camera.inject_events(mapped_events) + if mapped_events: + camera.inject_events(mapped_events) else: _LOGGER.debug( "No event triggers returned from %s. " diff --git a/tests/components/hikvision/conftest.py b/tests/components/hikvision/conftest.py index 86f250735d7..da827a70779 100644 --- a/tests/components/hikvision/conftest.py +++ b/tests/components/hikvision/conftest.py @@ -141,5 +141,5 @@ def mock_hik_nvr(mock_hikcamera: MagicMock) -> MagicMock: camera = mock_hikcamera.return_value camera.get_type = "NVR" camera.current_event_states = {} - camera.get_event_triggers.return_value = {"Motion": [1, 2]} + camera.get_event_triggers.return_value = {"VMD": [1, 2]} return mock_hikcamera diff --git a/tests/components/hikvision/test_init.py b/tests/components/hikvision/test_init.py index a2842ec44d5..7508dac4923 100644 --- a/tests/components/hikvision/test_init.py +++ b/tests/components/hikvision/test_init.py @@ -1,5 +1,6 @@ """Test Hikvision integration setup and unload.""" +import logging from unittest.mock import MagicMock from xml.etree.ElementTree import ParseError @@ -106,6 +107,60 @@ async def test_setup_entry_nvr_fetches_events( mock_hik_nvr.return_value.inject_events.assert_called_once() +async def test_setup_entry_nvr_skips_videoloss( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_hik_nvr: MagicMock, +) -> None: + """Test NVR event injection skips videoloss (watchdog event).""" + mock_hik_nvr.return_value.get_event_triggers.return_value = { + "VMD": [1, 2], + "videoloss": [1, 2, 3], + } + + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.LOADED + mock_hik_nvr.return_value.inject_events.assert_called_once_with({"Motion": [1, 2]}) + + +async def test_setup_entry_nvr_skips_unmapped_events( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_hik_nvr: MagicMock, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test NVR event injection skips events not in SENSOR_MAP.""" + mock_hik_nvr.return_value.get_event_triggers.return_value = { + "VMD": [1], + "audioexception": [1, 2], + } + + with caplog.at_level(logging.DEBUG): + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.LOADED + mock_hik_nvr.return_value.inject_events.assert_called_once_with({"Motion": [1]}) + assert "Skipping unmapped event type: audioexception" in caplog.text + + +async def test_setup_entry_nvr_skips_all_unknown_events( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_hik_nvr: MagicMock, +) -> None: + """Test NVR event injection with only unknown events does not inject.""" + mock_hik_nvr.return_value.get_event_triggers.return_value = { + "videoloss": [1], + "audioexception": [2], + } + + await setup_integration(hass, mock_config_entry) + + assert mock_config_entry.state is ConfigEntryState.LOADED + mock_hik_nvr.return_value.inject_events.assert_not_called() + + async def test_setup_entry_nvr_event_fetch_request_error( hass: HomeAssistant, mock_config_entry: MockConfigEntry,