Skip to content

Commit

Permalink
Version 1.6. Details in /releases/release_1.6.md
Browse files Browse the repository at this point in the history
  • Loading branch information
tropicoo committed Oct 17, 2022
1 parent 98519d6 commit da14bc6
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 35 deletions.
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Hikvision Telegram Camera Bot
Telegram Bot which sends snapshots from your Hikvision cameras.

Version: 1.5.2. [Release details](releases/release_1.5.2.md).
Version: 1.6. [Release details](releases/release_1.6.md).

## Features
1. Send full/resized pictures on request.
Expand Down Expand Up @@ -94,6 +94,14 @@ Configuration files are stored in JSON format and can be found in the `configs`
"stream_timeout": 10
},
"rtsp_port": 554,
"picture": {
"on_demand": {
"channel": 101
},
"on_alert": {
"channel": 101
}
},
"video_gif": {
"on_demand": {
"channel": 101,
Expand All @@ -119,19 +127,22 @@ Configuration files are stored in JSON format and can be found in the `configs`
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
},
"line_crossing_detection": {
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
},
"intrusion_detection": {
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
}
},
"livestream": {
Expand Down
17 changes: 14 additions & 3 deletions configs/config-template.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
"stream_timeout": 10
},
"rtsp_port": 554,
"picture": {
"on_demand": {
"channel": 101
},
"on_alert": {
"channel": 101
}
},
"video_gif": {
"on_demand": {
"channel": 101,
Expand All @@ -59,19 +67,22 @@
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
},
"line_crossing_detection": {
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
},
"intrusion_detection": {
"enabled": false,
"sendpic": true,
"fullpic": false,
"send_videogif": true
"send_videogif": true,
"send_text": true
}
},
"livestream": {
Expand Down
3 changes: 1 addition & 2 deletions hikcamerabot/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ async def cmd_list_groups(bot: CameraBot, message: Message) -> None:
@authorization_check
async def cmd_list_cams(bot: CameraBot, message: Message) -> None:
"""List user's cameras."""
log.debug('Camera list has been requested')
log.debug('Camera list has been requested from %s', message.chat.id)
count = bot.cam_registry.count()
plural = '' if count == 1 else 's'
msg = [bold(f'You have {count} camera{plural}')]
Expand All @@ -194,7 +194,6 @@ async def cmd_list_cams(bot: CameraBot, message: Message) -> None:
)
msg.append('/groups, /help')
await send_text(text='\n\n'.join(msg), message=message, quote=True)
log.debug('Camera list has been sent')


@authorization_check
Expand Down
6 changes: 4 additions & 2 deletions hikcamerabot/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,13 @@ async def start_videogif_record(
async def set_ircut_filter(self, filter_type: IrcutFilterType) -> None:
await self._api.set_ircut_filter(filter_type)

async def take_snapshot(self, resize: bool = False) -> tuple[BytesIO, int]:
async def take_snapshot(
self, channel: int, resize: bool = False
) -> tuple[BytesIO, int]:
"""Take and return full or resized snapshot from the camera."""
self._log.debug('Taking snapshot from %s', self.description)
try:
image_obj = await self._api.take_snapshot()
image_obj = await self._api.take_snapshot(channel=channel)
except HikvisionAPIError as err:
err_msg = f'Failed to take snapshot from {self.description}'
self._log.error(err_msg)
Expand Down
9 changes: 5 additions & 4 deletions hikcamerabot/clients/hikvision/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from tenacity import retry, wait_fixed

from hikcamerabot.clients.hikvision.auth import DigestAuthCached
from hikcamerabot.clients.hikvision.enums import Endpoint
from hikcamerabot.clients.hikvision.enums import EndpointAddr
from hikcamerabot.constants import CONN_TIMEOUT
from hikcamerabot.exceptions import APIBadResponseCodeError, APIRequestError

Expand All @@ -32,13 +32,14 @@ def __init__(self, conf: Dict) -> None:
@retry(wait=wait_fixed(0.5))
async def request(
self,
endpoint: Endpoint,
endpoint: EndpointAddr | str,
data: Any = None,
headers: dict = None,
method: str = 'GET',
timeout: float = CONN_TIMEOUT,
) -> httpx.Response:
url = urljoin(f'{self.host}:{self.port}', endpoint.value)
endpoint_ = endpoint.value if isinstance(endpoint, EndpointAddr) else endpoint
url = urljoin(f'{self.host}:{self.port}', endpoint_)
self._log.debug('Request: %s - %s - %s', method, url, data)
try:
response = await self.session.request(
Expand All @@ -51,7 +52,7 @@ async def request(
except Exception as err:
err_msg = (
f'API encountered an unknown error for method {method}, '
f'endpoint {endpoint}, data {data}'
f'endpoint {endpoint_}, data {data}'
)
self._log.exception(err_msg)
raise APIRequestError(f'{err_msg}: {err}') from err
Expand Down
4 changes: 2 additions & 2 deletions hikcamerabot/clients/hikvision/endpoints/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from addict import Dict

from hikcamerabot.clients.hikvision import HikvisionAPIClient
from hikcamerabot.clients.hikvision.enums import Endpoint
from hikcamerabot.clients.hikvision.enums import EndpointAddr
from hikcamerabot.exceptions import HikvisionAPIError


Expand All @@ -32,7 +32,7 @@ async def __call__(self, *args, **kwargs) -> Any:
async def _get_channel_capabilities(self) -> Dict:
response = await self._api_client.request(
method='GET',
endpoint=Endpoint.CHANNEL_CAPABILITIES,
endpoint=EndpointAddr.CHANNEL_CAPABILITIES,
headers=self._XML_HEADERS,
)
return Dict(xmltodict.parse(response.text))
Expand Down
6 changes: 3 additions & 3 deletions hikcamerabot/clients/hikvision/endpoints/config_switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import xmltodict

from hikcamerabot.clients.hikvision.enums import Endpoint
from hikcamerabot.clients.hikvision.enums import EndpointAddr
from hikcamerabot.constants import DETECTION_SWITCH_MAP
from hikcamerabot.enums import Detection
from hikcamerabot.exceptions import APIRequestError, HikvisionAPIError
Expand Down Expand Up @@ -32,7 +32,7 @@ def __init__(self, api_client: 'HikvisionAPIClient') -> None:
async def switch_enabled_state(
self, trigger: Detection, state: bool
) -> Optional[str]:
endpoint: Endpoint = Endpoint[trigger.value.upper()]
endpoint: EndpointAddr = EndpointAddr[trigger.value.upper()]
full_name: str = DETECTION_SWITCH_MAP[trigger]['name'].value
try:
is_enabled, xml = await self._get_switch_state(trigger, endpoint)
Expand Down Expand Up @@ -66,7 +66,7 @@ async def switch_enabled_state(
return None

async def _get_switch_state(
self, name: Detection, endpoint: Endpoint
self, name: Detection, endpoint: EndpointAddr
) -> tuple[bool, str]:
response = await self._api_client.request(endpoint, method='GET')
xml = response.text
Expand Down
13 changes: 7 additions & 6 deletions hikcamerabot/clients/hikvision/endpoints/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from hikcamerabot.clients.hikvision.endpoints.abstract import AbstractEndpoint
from hikcamerabot.clients.hikvision.endpoints.config_switch import CameraConfigSwitch
from hikcamerabot.clients.hikvision.enums import (
Endpoint,
EndpointAddr,
ExposureType,
IrcutFilterType,
OverexposeSuppressEnabledType,
Expand All @@ -31,7 +31,7 @@ async def __call__(self, filter_type: IrcutFilterType) -> None:
current_capabilities = await self._get_channel_capabilities()
try:
response = await self._api_client.request(
endpoint=Endpoint.IRCUT_FILTER,
endpoint=EndpointAddr.IRCUT_FILTER,
headers=self._XML_HEADERS,
data=self._build_payload(filter_type, current_capabilities),
method='PUT',
Expand Down Expand Up @@ -92,7 +92,7 @@ async def __call__(
current_capabilities = await self._get_channel_capabilities()
try:
response = await self._api_client.request(
endpoint=Endpoint.IRCUT_FILTER,
endpoint=EndpointAddr.IRCUT_FILTER,
headers=self._XML_HEADERS,
data=self._build_payload(filtered_kwargs, current_capabilities),
method='PUT',
Expand Down Expand Up @@ -124,8 +124,9 @@ def _build_payload(self, kwargs: Dict, current_capabilities: Optional[Dict]) ->


class TakeSnapshotEndpoint(AbstractEndpoint):
async def __call__(self) -> BytesIO:
response = await self._api_client.request(Endpoint.PICTURE)
async def __call__(self, channel: int) -> BytesIO:
endpoint_str: str = EndpointAddr.PICTURE.value.format(channel=channel)
response = await self._api_client.request(endpoint=endpoint_str)
return self._response_to_bytes(response)

def _response_to_bytes(self, response: httpx.Response) -> BytesIO:
Expand All @@ -140,7 +141,7 @@ async def __call__(self) -> AsyncGenerator[str, None]:
method = 'GET'
url = urljoin(
f'{self._api_client.host}:{self._api_client.port}',
Endpoint.ALERT_STREAM.value,
EndpointAddr.ALERT_STREAM.value,
)
timeout = httpx.Timeout(CONN_TIMEOUT, read=300)
response: httpx.Response
Expand Down
4 changes: 2 additions & 2 deletions hikcamerabot/clients/hikvision/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@


@enum.unique
class Endpoint(enum.Enum):
class EndpointAddr(enum.Enum):
ALERT_STREAM = 'ISAPI/Event/notification/alertStream'
CHANNEL_CAPABILITIES = 'ISAPI/Image/channels/1/capabilities'
EXPOSURE = 'ISAPI/Image/channels/1/exposure'
IRCUT_FILTER = 'ISAPI/Image/channels/1/ircutFilter'
INTRUSION_DETECTION = 'ISAPI/Smart/FieldDetection/1'
LINE_CROSSING_DETECTION = 'ISAPI/Smart/LineDetection/1'
MOTION_DETECTION = 'ISAPI/System/Video/inputs/channels/1/motionDetection'
PICTURE = 'ISAPI/Streaming/channels/102/picture?snapShotImageType=JPEG'
PICTURE = 'ISAPI/Streaming/channels/{channel}/picture?snapShotImageType=JPEG'


@enum.unique
Expand Down
15 changes: 15 additions & 0 deletions hikcamerabot/config/schemas/main_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Detection(Schema):
sendpic = f.Boolean(required=True)
fullpic = f.Boolean(required=True)
send_videogif = f.Boolean(required=True)
send_text = f.Boolean(required=True)


class VideoGifOnDemand(Schema):
Expand All @@ -76,6 +77,19 @@ class VideoGif(Schema):
on_demand = f.Nested(VideoGifOnDemand(), required=True)


class PictureOnAlert(Schema):
channel = f.Int(required=True)


class PictureOnDemand(PictureOnAlert):
pass


class Picture(Schema):
on_alert = f.Nested(PictureOnAlert(), required=True)
on_demand = f.Nested(PictureOnDemand(), required=True)


class Alert(Schema):
delay = f.Int(required=True, validate=v.Range(min=0))
motion_detection = f.Nested(Detection(), required=True)
Expand Down Expand Up @@ -115,6 +129,7 @@ class _CameraListConfig(Schema):
group = f.Str(required=True, allow_none=True)
api = f.Nested(CamAPI(), required=True)
rtsp_port = f.Int(required=True)
picture = f.Nested(Picture(), required=True)
video_gif = f.Nested(VideoGif(), required=True)
alert = f.Nested(Alert(), required=True)
livestream = f.Nested(Livestream(), required=True)
Expand Down
5 changes: 5 additions & 0 deletions hikcamerabot/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ class ConfigFile(_BaseUniqueEnum):
ENCODING = 'encoding_templates.json'


class PictureType(_BaseUniqueEnum):
ON_ALERT = 'on_alert'
ON_DEMAND = 'on_demand'


class VideoGifType(_BaseUniqueEnum):
ON_ALERT = 'on_alert'
ON_DEMAND = 'on_demand'
Expand Down
5 changes: 4 additions & 1 deletion hikcamerabot/event_engine/handlers/inbound.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ async def _handle(self, event: BaseInboundEvent) -> None:

class TaskTakeSnapshot(AbstractTaskEvent):
async def _handle(self, event: GetPicEvent) -> None:
img, create_ts = await event.cam.take_snapshot(resize=event.resize)
channel: int = event.cam.conf.picture.on_demand.channel
img, create_ts = await event.cam.take_snapshot(
channel=channel, resize=event.resize
)
await self._result_queue.put(
SnapshotOutboundEvent(
event=event.event,
Expand Down
18 changes: 13 additions & 5 deletions hikcamerabot/services/alarm/tasks/notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ async def _run(self) -> None:

class AlarmTextMessageNotificationTask(AbstractAlertNotificationTask):
async def _run(self) -> None:
if self._cam.conf.alert[self._detection_type.value].send_text:
await self._send_alert_text()

async def _send_alert_text(self) -> None:
detection_name: str = DETECTION_SWITCH_MAP[self._detection_type]['name'].value
await self._result_queue.put(
SendTextOutboundEvent(
Expand All @@ -52,10 +56,13 @@ async def _run(self) -> None:
class AlarmVideoGifNotificationTask(AbstractAlertNotificationTask):
async def _run(self) -> None:
if self._cam.conf.alert[self._detection_type.value].send_videogif:
await self._cam.start_videogif_record(
video_type=VideoGifType.ON_ALERT,
rewind=self._cam.conf.video_gif[VideoGifType.ON_ALERT.value].rewind,
)
await self._start_videogif_record()

async def _start_videogif_record(self) -> None:
await self._cam.start_videogif_record(
video_type=VideoGifType.ON_ALERT,
rewind=self._cam.conf.video_gif[VideoGifType.ON_ALERT.value].rewind,
)


class AlarmPicNotificationTask(AbstractAlertNotificationTask):
Expand All @@ -64,8 +71,9 @@ async def _run(self) -> None:
await self._send_pic()

async def _send_pic(self) -> None:
channel: int = self._cam.conf.picture.on_alert.channel
resize = not self._cam.conf.alert[self._detection_type.value].fullpic
photo, ts = await self._cam.take_snapshot(resize=resize)
photo, ts = await self._cam.take_snapshot(channel=channel, resize=resize)
await self._result_queue.put(
AlertSnapshotOutboundEvent(
cam=self._cam,
Expand Down
2 changes: 1 addition & 1 deletion hikcamerabot/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.5.2'
__version__ = '1.6'
Loading

0 comments on commit da14bc6

Please sign in to comment.