Silence Hikvision videoloss watchdog warning (#180234)

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Paul Tarjan
2026-08-26 17:44:50 +02:00
committed by GitHub
co-authored by Claude Joost Lekkerkerker
parent 2fcb86ffc8
commit 58151f5583
2 changed files with 52 additions and 24 deletions
@@ -3,6 +3,7 @@
import logging
from typing import Any, override
from pyhik.constants import SENSOR_MAP
import voluptuous as vol
from homeassistant.components.binary_sensor import (
@@ -43,115 +44,120 @@ DEFAULT_DELAY = 0
DEFAULT_IGNORED = False
# Entity descriptions for known Hikvision event types
# The key matches the sensor_type from pyhik (the friendly name from SENSOR_MAP)
# Keyed by the friendly names pyhik emits in `current_event_states`. Going
# through SENSOR_MAP keeps each description attached to the right event if
# pyhik renames a label.
BINARY_SENSOR_DESCRIPTIONS: dict[str, BinarySensorEntityDescription] = {
"Motion": BinarySensorEntityDescription(
SENSOR_MAP["vmd"]: BinarySensorEntityDescription(
key="motion",
device_class=BinarySensorDeviceClass.MOTION,
),
"Line Crossing": BinarySensorEntityDescription(
SENSOR_MAP["linedetection"]: BinarySensorEntityDescription(
key="line_crossing",
translation_key="line_crossing",
device_class=BinarySensorDeviceClass.MOTION,
),
"Field Detection": BinarySensorEntityDescription(
SENSOR_MAP["fielddetection"]: BinarySensorEntityDescription(
key="field_detection",
translation_key="field_detection",
device_class=BinarySensorDeviceClass.MOTION,
),
"Tamper Detection": BinarySensorEntityDescription(
SENSOR_MAP["tamperdetection"]: BinarySensorEntityDescription(
key="tamper_detection",
device_class=BinarySensorDeviceClass.TAMPER,
),
"Disk Full": BinarySensorEntityDescription(
SENSOR_MAP["diskfull"]: BinarySensorEntityDescription(
key="disk_full",
translation_key="disk_full",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"Disk Error": BinarySensorEntityDescription(
SENSOR_MAP["diskerror"]: BinarySensorEntityDescription(
key="disk_error",
translation_key="disk_error",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"Net Interface Broken": BinarySensorEntityDescription(
SENSOR_MAP["nicbroken"]: BinarySensorEntityDescription(
key="net_interface_broken",
translation_key="net_interface_broken",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
),
"IP Conflict": BinarySensorEntityDescription(
SENSOR_MAP["ipconflict"]: BinarySensorEntityDescription(
key="ip_conflict",
translation_key="ip_conflict",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"Illegal Access": BinarySensorEntityDescription(
SENSOR_MAP["illaccess"]: BinarySensorEntityDescription(
key="illegal_access",
translation_key="illegal_access",
device_class=BinarySensorDeviceClass.SAFETY,
),
"Video Mismatch": BinarySensorEntityDescription(
SENSOR_MAP["videomismatch"]: BinarySensorEntityDescription(
key="video_mismatch",
translation_key="video_mismatch",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"Bad Video": BinarySensorEntityDescription(
SENSOR_MAP["badvideo"]: BinarySensorEntityDescription(
key="bad_video",
translation_key="bad_video",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"PIR Alarm": BinarySensorEntityDescription(
SENSOR_MAP["pir"]: BinarySensorEntityDescription(
key="pir_alarm",
translation_key="pir_alarm",
device_class=BinarySensorDeviceClass.MOTION,
),
"Face Detection": BinarySensorEntityDescription(
SENSOR_MAP["facedetection"]: BinarySensorEntityDescription(
key="face_detection",
translation_key="face_detection",
device_class=BinarySensorDeviceClass.MOTION,
),
"Scene Change Detection": BinarySensorEntityDescription(
SENSOR_MAP["scenechangedetection"]: BinarySensorEntityDescription(
key="scene_change_detection",
translation_key="scene_change_detection",
device_class=BinarySensorDeviceClass.MOTION,
),
"I/O": BinarySensorEntityDescription(
SENSOR_MAP["io"]: BinarySensorEntityDescription(
key="io",
translation_key="io",
),
"Unattended Baggage": BinarySensorEntityDescription(
SENSOR_MAP["unattendedbaggage"]: BinarySensorEntityDescription(
key="unattended_baggage",
translation_key="unattended_baggage",
device_class=BinarySensorDeviceClass.MOTION,
),
"Attended Baggage": BinarySensorEntityDescription(
SENSOR_MAP["attendedbaggage"]: BinarySensorEntityDescription(
key="attended_baggage",
translation_key="attended_baggage",
device_class=BinarySensorDeviceClass.MOTION,
),
"Recording Failure": BinarySensorEntityDescription(
SENSOR_MAP["recordingfailure"]: BinarySensorEntityDescription(
key="recording_failure",
translation_key="recording_failure",
device_class=BinarySensorDeviceClass.PROBLEM,
entity_category=EntityCategory.DIAGNOSTIC,
),
"Exiting Region": BinarySensorEntityDescription(
SENSOR_MAP["regionexiting"]: BinarySensorEntityDescription(
key="exiting_region",
translation_key="exiting_region",
device_class=BinarySensorDeviceClass.MOTION,
),
"Entering Region": BinarySensorEntityDescription(
SENSOR_MAP["regionentrance"]: BinarySensorEntityDescription(
key="entering_region",
translation_key="entering_region",
device_class=BinarySensorDeviceClass.MOTION,
),
}
# pyhik uses videoloss as a connection watchdog, not a user-facing event, so a
# leaked entry gets no entity and no unknown-sensor warning.
IGNORED_SENSOR_TYPES: frozenset[str] = frozenset({SENSOR_MAP["videoloss"]})
_LOGGER = logging.getLogger(__name__)
CUSTOMIZE_SCHEMA = vol.Schema(
@@ -243,9 +249,11 @@ async def async_setup_entry(
)
return
# Log warnings for unknown sensor types and skip them
for sensor_type in sensors:
if sensor_type not in BINARY_SENSOR_DESCRIPTIONS:
if (
sensor_type not in BINARY_SENSOR_DESCRIPTIONS
and sensor_type not in IGNORED_SENSOR_TYPES
):
_LOGGER.warning(
"Unknown Hikvision sensor type '%s', please report this at "
"https://github.com/home-assistant/core/issues",
@@ -3,6 +3,7 @@
import logging
from unittest.mock import MagicMock
from pyhik.constants import SENSOR_MAP
import pytest
from syrupy.assertion import SnapshotAssertion
@@ -249,6 +250,25 @@ async def test_binary_sensor_device_class_unknown(
assert "Unknown Hikvision sensor type 'Unknown Event'" in caplog.text
async def test_binary_sensor_videoloss_silently_skipped(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_hikcamera: MagicMock,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test pyhik's videoloss watchdog event is skipped without warning."""
mock_hikcamera.return_value.current_event_states = {
SENSOR_MAP["videoloss"]: [(False, 1)],
}
with caplog.at_level(logging.WARNING):
await setup_integration(hass, mock_config_entry)
states = hass.states.async_entity_ids("binary_sensor")
assert len(states) == 0
assert "Unknown Hikvision sensor type" not in caplog.text
async def test_yaml_import_creates_deprecation_issue(
hass: HomeAssistant,
mock_hikcamera: MagicMock,