diff --git a/README.md b/README.md
index 704f7b3..725f8eb 100644
--- a/README.md
+++ b/README.md
@@ -9,8 +9,9 @@ The Home Assistant integration for Hikvision NVRs and IP cameras. Receives and s
- Camera entities for main and sub streams
- Real-time Acusense events notifications through binary sensors and HA events (hikvision_next_event)
- Switches for Acusense events detection
-- Switches for NVR Outputs
+- Switches for NVR Outputs and PIR sensor
- Holiday mode switch (allows to switch continuous recording with appropriate NVR setup)
+- Image entities for the latest snapshots
- Tracking HDD and NAS status
- Tracking Notifications Host settings for diagnostic purposes
- Basic and digest authentication support
@@ -26,10 +27,16 @@ The Home Assistant integration for Hikvision NVRs and IP cameras. Receives and s
- Region Entrance
- Region Exiting
- NVR Input Triggers
+- PIR
**NOTE**
Events must be set to alert the surveillance center in Linkage Action for Home Assistant to be notified. Otherwise related binary sensors and switches will appear as disabled entities.
+### Blueprints
+
+Take Multiple Snapshots On Detection Event
+
+[](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)
## Preview
### IP Camera device view
@@ -42,6 +49,8 @@ The scope supported features depends on device model, setup and firmware version
## Installation
+[](https://my.home-assistant.io/redirect/hacs_repository/?owner=maciej-or&repository=hikvision_next&category=integration)
+
### With HACS
1. This integration you will find in the default HACS store. Search for `Hikvision NVR / IP Camera` on `HACS / Integrations` page and press `Download` button
@@ -93,6 +102,7 @@ Download logs from `Settings / System / Logs`
### NVR
+- Annke N46PCK
- DS-7108NI-Q1/8P
- DS-7608NI-I2
- DS-7608NI-I2/8P
@@ -100,10 +110,10 @@ Download logs from `Settings / System / Logs`
- DS-7608NXI-K1/8P
- DS-7616NI-E2/16P
- DS-7616NI-I2/16P
+- DS-7616NI-Q2
- DS-7616NI-Q2/16P
- DS-7616NXI-I2/16P/S
- DS-7716NI-I4/16P
-- Annke N46PCK
- ERI-K104-P4
### DVR
@@ -113,8 +123,10 @@ Download logs from `Settings / System / Logs`
### IP Camera
+- Annke C800 (I91BM)
- DS-2CD2047G2-LU/SL
- DS-2CD2087G2-LU
+- DS-2CD2146G2-ISU
- DS-2CD2155FWD-I
- DS-2CD2346G2-IU
- DS-2CD2386G2-IU
@@ -127,4 +139,3 @@ Download logs from `Settings / System / Logs`
- DS-2CD2T87G2-L
- DS-2CD2T87G2P-LSU/SL
- DS-2DE4425IW-DE (PTZ)
-- Annke C800 (I91BM)
diff --git a/blueprints/automation/hikvision_next/take_pictures_on_motion_detection.yaml b/blueprints/take_pictures_on_motion_detection.yaml
similarity index 79%
rename from blueprints/automation/hikvision_next/take_pictures_on_motion_detection.yaml
rename to blueprints/take_pictures_on_motion_detection.yaml
index 8b0def2..148e652 100755
--- a/blueprints/automation/hikvision_next/take_pictures_on_motion_detection.yaml
+++ b/blueprints/take_pictures_on_motion_detection.yaml
@@ -40,9 +40,15 @@ action:
sequence:
- variables:
camera_entity: "{{ camera_entities[repeat.index - 1] }}"
+ snapshot_entity: "{{ camera_entities[repeat.index - 1] |regex_replace(find='camera', replace='image') }}_snapshot"
camera_name: "{{ states[camera_entity].name }}"
- service: camera.snapshot
data:
filename: /media/hikvision_next/snapshots/{{month}}/{{day}}/{{timestamp}}__{{camera_name}}.jpg
target:
entity_id: "{{ camera_entity }}"
+ - service: hikvision_next.update_snapshot
+ data:
+ filename: /media/hikvision_next/snapshots/{{month}}/{{day}}/{{timestamp}}__{{camera_name}}.jpg
+ target:
+ entity_id: "{{ snapshot_entity }}"
diff --git a/custom_components/hikvision_next/__init__.py b/custom_components/hikvision_next/__init__.py
index 431ecd7..a93a0a3 100644
--- a/custom_components/hikvision_next/__init__.py
+++ b/custom_components/hikvision_next/__init__.py
@@ -8,14 +8,15 @@
from httpx import TimeoutException
+from homeassistant.components.binary_sensor import (
+ ENTITY_ID_FORMAT as BINARY_SENSOR_ENTITY_ID_FORMAT,
+)
+from homeassistant.components.switch import ENTITY_ID_FORMAT as SWITCH_ENTITY_ID_FORMAT
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
-from homeassistant.helpers import device_registry as dr
-from homeassistant.helpers import entity_registry as er
-from homeassistant.components.switch import ENTITY_ID_FORMAT as SWITCH_ENTITY_ID_FORMAT
-from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT as BINARY_SENSOR_ENTITY_ID_FORMAT
+from homeassistant.helpers import device_registry as dr, entity_registry as er
from .const import (
ALARM_SERVER_PATH,
@@ -37,6 +38,7 @@
Platform.CAMERA,
Platform.SENSOR,
Platform.SWITCH,
+ Platform.IMAGE,
]
diff --git a/custom_components/hikvision_next/image.py b/custom_components/hikvision_next/image.py
new file mode 100644
index 0000000..719ef7d
--- /dev/null
+++ b/custom_components/hikvision_next/image.py
@@ -0,0 +1,89 @@
+"""Image entities with camera snapshots."""
+
+from datetime import datetime
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.camera import Camera
+from homeassistant.components.image import ImageEntity
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import ATTR_ENTITY_ID, CONF_FILENAME
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers import config_validation as cv, entity_platform
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+from homeassistant.helpers.template import Template
+from homeassistant.util import slugify
+
+from .const import DATA_ISAPI, DOMAIN
+from .isapi import ISAPI, CameraStreamInfo
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback) -> None:
+ """Add images with snapshots."""
+
+ config = hass.data[DOMAIN][entry.entry_id]
+ isapi: ISAPI = config[DATA_ISAPI]
+
+ entities = []
+ for camera in isapi.cameras:
+ for stream in camera.streams:
+ if stream.type_id == 1:
+ entities.append(SnapshotFile(hass, isapi, camera, stream))
+
+ async_add_entities(entities)
+
+ platform = entity_platform.async_get_current_platform()
+ platform.async_register_entity_service(
+ "update_snapshot",
+ {vol.Required(CONF_FILENAME): cv.template},
+ "update_snapshot_filename",
+ )
+
+
+class SnapshotFile(ImageEntity):
+ """An entity for displaying snapshot files."""
+
+ _attr_has_entity_name = True
+ file_path = None
+
+ def __init__(
+ self,
+ hass: HomeAssistant,
+ isapi: ISAPI,
+ camera: Camera,
+ stream_info: CameraStreamInfo,
+ ) -> None:
+ """Initialize the snapshot file."""
+
+ ImageEntity.__init__(self, hass)
+
+ self._attr_unique_id = slugify(f"{isapi.device_info.serial_no.lower()}_{stream_info.id}_snapshot")
+ self.entity_id = f"camera.{self.unique_id}"
+ self._attr_translation_key = "snapshot"
+ self._attr_translation_placeholders = {"camera": camera.name}
+
+ def image(self) -> bytes | None:
+ """Return bytes of image."""
+ try:
+ if self.file_path:
+ with open(self.file_path, "rb") as file:
+ return file.read()
+ except FileNotFoundError:
+ _LOGGER.warning(
+ "Could not read camera %s image from file: %s",
+ self.name,
+ self.file_path,
+ )
+ return None
+
+ async def update_snapshot_filename(
+ self,
+ filename: Template,
+ ) -> None:
+ """Update the file_path."""
+ self.file_path = filename.async_render(variables={ATTR_ENTITY_ID: self.entity_id})
+ self._attr_image_last_updated = datetime.now()
+ self.schedule_update_ha_state()
diff --git a/custom_components/hikvision_next/services.yaml b/custom_components/hikvision_next/services.yaml
new file mode 100644
index 0000000..e54d6cf
--- /dev/null
+++ b/custom_components/hikvision_next/services.yaml
@@ -0,0 +1,15 @@
+update_snapshot:
+ name: Update snapshot
+ description: Update the snapshot file in an image entity.
+ target:
+ entity:
+ domain: image
+ integration: hikvision_next
+ fields:
+ filename:
+ required: true
+ name: Filename
+ description: The filename of the snapshot to display.
+ example: "/media/hikvision_next/snapshots/2024-08/07/2024-08-07__11-54-19__camera01.jpg"
+ selector:
+ text:
diff --git a/custom_components/hikvision_next/translations/en.json b/custom_components/hikvision_next/translations/en.json
index 1d738ed..3917540 100644
--- a/custom_components/hikvision_next/translations/en.json
+++ b/custom_components/hikvision_next/translations/en.json
@@ -114,6 +114,11 @@
"notifications_host_url": {
"name": "Notifications Host Path"
}
+ },
+ "image": {
+ "snapshot": {
+ "name": "{camera} Snapshot"
+ }
}
}
}
diff --git a/custom_components/hikvision_next/translations/pl.json b/custom_components/hikvision_next/translations/pl.json
index dcffbe2..8e115bf 100644
--- a/custom_components/hikvision_next/translations/pl.json
+++ b/custom_components/hikvision_next/translations/pl.json
@@ -114,6 +114,11 @@
"notifications_host_url": {
"name": "Host powiadomień ścieżka"
}
+ },
+ "image": {
+ "snapshot": {
+ "name": "{camera} zdjęcie"
+ }
}
}
}