From 11dbdba8dd2245a9e854c9d91b944b11c61ea489 Mon Sep 17 00:00:00 2001 From: tropicoo Date: Mon, 17 Oct 2022 11:38:38 +0300 Subject: [PATCH] Version 1.5.2. Details in /releases/release_1.5.2.md --- README.md | 12 +++++++++- configs/config-template.json | 2 ++ docker-compose.yml | 2 +- hikcamerabot/clients/hikvision/api_client.py | 5 ++-- .../hikvision/endpoints/config_switch.py | 18 +++++++------- .../clients/hikvision/endpoints/endpoints.py | 12 ++++++---- hikcamerabot/config/schemas/main_config.py | 1 + hikcamerabot/event_engine/handlers/inbound.py | 10 ++++---- hikcamerabot/services/alarm/alarm.py | 8 +++---- .../services/stream/dvr/file_wrapper.py | 2 +- hikcamerabot/version.py | 2 +- releases/release_1.5.2.md | 24 +++++++++++++++++++ 12 files changed, 70 insertions(+), 28 deletions(-) create mode 100644 releases/release_1.5.2.md diff --git a/README.md b/README.md index bd06fdb..05fda42 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Hikvision Telegram Camera Bot Telegram Bot which sends snapshots from your Hikvision cameras. -Version: 1.5. [Release details](releases/release_1.5.md). +Version: 1.5.2. [Release details](releases/release_1.5.2.md). ## Features 1. Send full/resized pictures on request. @@ -86,6 +86,7 @@ Configuration files are stored in JSON format and can be found in the `configs` "group": "Default group", "api": { "host": "http://192.168.1.1", + "port": 80, "auth": { "user": "dummy-user", "password": "dummy-password" @@ -195,8 +196,17 @@ If you want to use the default UTC time format, set Greenwich Mean Time timezone 2. Build an image and run a container in a detached mode ```bash + # Build container and start sudo docker-compose build && sudo docker-compose up -d && sudo docker-compose logs -f --tail=1000 + + # Stop running containers while you're in the bot directory + sudo docker-compose stop + + # Check whether any containers are running + sudo docker ps ``` + + # Commands | Command | Description | diff --git a/configs/config-template.json b/configs/config-template.json index 2ad488c..5181c07 100644 --- a/configs/config-template.json +++ b/configs/config-template.json @@ -26,6 +26,7 @@ "group": "Default group", "api": { "host": "http://192.168.1.1", + "port": 80, "auth": { "user": "dummy-user", "password": "dummy-password" @@ -129,6 +130,7 @@ "group": "Default group", "api": { "host": "http://192.168.1.2", + "port": 80, "auth": { "user": "dummy-user", "password": "dummy-password" diff --git a/docker-compose.yml b/docker-compose.yml index 3876cfe..b968f49 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3" +version: "3.8" services: hikvision-camera-bot: diff --git a/hikcamerabot/clients/hikvision/api_client.py b/hikcamerabot/clients/hikvision/api_client.py index c2c7543..2d1b99b 100644 --- a/hikcamerabot/clients/hikvision/api_client.py +++ b/hikcamerabot/clients/hikvision/api_client.py @@ -19,6 +19,7 @@ def __init__(self, conf: Dict) -> None: self._log = logging.getLogger(self.__class__.__name__) self._conf = conf self.host: str = self._conf.host + self.port: int = self._conf.port self.session = httpx.AsyncClient( auth=DigestAuthCached( username=self._conf.auth.user, @@ -36,8 +37,8 @@ async def request( method: str = 'GET', timeout: float = CONN_TIMEOUT, ) -> httpx.Response: - url = urljoin(self.host, endpoint) - self._log.debug('Request: %s - %s - %s', method, endpoint, data) + url = urljoin(f'{self.host}:{self.port}', endpoint) + self._log.debug('Request: %s - %s - %s', method, url, data) try: response = await self.session.request( method, diff --git a/hikcamerabot/clients/hikvision/endpoints/config_switch.py b/hikcamerabot/clients/hikvision/endpoints/config_switch.py index 698baa3..6fd1824 100644 --- a/hikcamerabot/clients/hikvision/endpoints/config_switch.py +++ b/hikcamerabot/clients/hikvision/endpoints/config_switch.py @@ -25,12 +25,12 @@ class CameraConfigSwitch: ) XML_HEADERS = {'Content-Type': 'application/xml'} - def __init__(self, api: 'HikvisionAPIClient') -> None: + def __init__(self, api_client: 'HikvisionAPIClient') -> None: self._log = logging.getLogger(self.__class__.__name__) - self._api = api + self._api_client = api_client async def switch_enabled_state( - self, trigger: Detection, enable: bool + self, trigger: Detection, state: bool ) -> Optional[str]: endpoint = Endpoint[trigger.value.upper()].value full_name: str = DETECTION_SWITCH_MAP[trigger]['name'].value @@ -45,19 +45,19 @@ async def switch_enabled_state( self._log.error(err_msg) raise HikvisionAPIError(err_msg) - if is_enabled and enable: + if is_enabled and state: return f'{full_name} already enabled' - if not is_enabled and not enable: + if not is_enabled and not state: return f'{full_name} already disabled' - xml_payload = self._prepare_xml_payload(xml, enable) + xml_payload = self._prepare_xml_payload(xml, state) try: - response = await self._api.request( + response = await self._api_client.request( endpoint, headers=self.XML_HEADERS, data=xml_payload, method='PUT' ) response = response.text except APIRequestError: - action = 'enable' if enable else 'disable' + action = 'enable' if state else 'disable' err_msg = f'Failed to {action} {full_name}.' self._log.error(err_msg) raise @@ -68,7 +68,7 @@ async def switch_enabled_state( async def _get_switch_state( self, name: Detection, endpoint: str ) -> tuple[bool, str]: - response = await self._api.request(endpoint, method='GET') + response = await self._api_client.request(endpoint, method='GET') xml = response.text xml_dict = xmltodict.parse(xml) state: str = xml_dict[DETECTION_SWITCH_MAP[name]['method'].value]['enabled'] diff --git a/hikcamerabot/clients/hikvision/endpoints/endpoints.py b/hikcamerabot/clients/hikvision/endpoints/endpoints.py index ddef81d..27d131f 100644 --- a/hikcamerabot/clients/hikvision/endpoints/endpoints.py +++ b/hikcamerabot/clients/hikvision/endpoints/endpoints.py @@ -138,6 +138,7 @@ class AlertStreamEndpoint(AbstractEndpoint): async def __call__(self) -> AsyncGenerator[str, None]: url = urljoin(self._api_client.host, Endpoint.ALERT_STREAM.value) timeout = httpx.Timeout(CONN_TIMEOUT, read=300) + response: httpx.Response async with self._api_client.session.stream( 'GET', url, timeout=timeout ) as response: @@ -149,8 +150,11 @@ async def __call__(self) -> AsyncGenerator[str, None]: class SwitchEndpoint(AbstractEndpoint): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self._switch = CameraConfigSwitch(api=self._api_client) + self._switch = CameraConfigSwitch(api_client=self._api_client) - async def __call__(self, trigger: Detection, enable: bool) -> Optional[str]: - """Switch method to enable/disable Hikvision functions.""" - return await self._switch.switch_enabled_state(trigger, enable) + async def __call__(self, trigger: Detection, state: bool) -> Optional[str]: + """Switch method to enable/disable Hikvision functions. + + :param state: Boolean value indicating on/off switch state. + """ + return await self._switch.switch_enabled_state(trigger, state) diff --git a/hikcamerabot/config/schemas/main_config.py b/hikcamerabot/config/schemas/main_config.py index b19013d..5407939 100644 --- a/hikcamerabot/config/schemas/main_config.py +++ b/hikcamerabot/config/schemas/main_config.py @@ -90,6 +90,7 @@ class CamAPIAuth(Schema): class CamAPI(Schema): host = f.Str(required=True, validate=non_empty_str) + port = f.Int(required=True, validate=int_min_1) auth = f.Nested(CamAPIAuth(), required=True) stream_timeout = f.Int(required=True, validate=int_min_1) diff --git a/hikcamerabot/event_engine/handlers/inbound.py b/hikcamerabot/event_engine/handlers/inbound.py index bb405a4..6a42a65 100644 --- a/hikcamerabot/event_engine/handlers/inbound.py +++ b/hikcamerabot/event_engine/handlers/inbound.py @@ -72,19 +72,19 @@ async def _handle(self, event: DetectionConfEvent) -> None: cam = event.cam trigger = event.type name = DETECTION_SWITCH_MAP[trigger]['name'].value - enable = event.switch + state = event.state self._log.info( '%s camera\'s %s has been requested', - 'Enabling' if enable else 'Disabling', + 'Enabling' if state else 'Disabling', name, ) - text = await cam.services.alarm.trigger_switch(enable, trigger) + text = await cam.services.alarm.trigger_switch(trigger=trigger, state=state) await self._result_queue.put( DetectionConfOutboundEvent( event=event.event, type=event.type, - switch=event.switch, + state=state, cam=cam, message=event.message, text=text, @@ -156,6 +156,6 @@ async def _handle(self, event: IrcutConfEvent) -> None: SendTextOutboundEvent( event=Event.SEND_TEXT, message=event.message, - text=bold('OK'), + text=bold(f'IrcutFilter set to "{event.filter_type.value}"'), ) ) diff --git a/hikcamerabot/services/alarm/alarm.py b/hikcamerabot/services/alarm/alarm.py index 6ed05a0..da65475 100644 --- a/hikcamerabot/services/alarm/alarm.py +++ b/hikcamerabot/services/alarm/alarm.py @@ -82,7 +82,7 @@ def _start_service_task(self) -> None: async def _enable_triggers_on_camera(self) -> None: for trigger in self.ALARM_TRIGGERS: if self._conf[trigger].enabled: - await self.trigger_switch(enable=True, trigger=Detection(trigger)) + await self.trigger_switch(trigger=Detection(trigger), state=True) async def stop(self) -> None: """Disable alarm.""" @@ -95,12 +95,12 @@ async def alert_stream(self) -> AsyncGenerator[str, None]: async for chunk in self._api.alert_stream(): yield chunk - async def trigger_switch(self, enable: bool, trigger: Detection) -> Optional[str]: + async def trigger_switch(self, trigger: Detection, state: bool) -> Optional[str]: """Trigger switch.""" full_name: str = DETECTION_SWITCH_MAP[trigger]['name'].value - self._log.debug('%s %s', 'Enabling' if enable else 'Disabling', full_name) + self._log.debug('%s %s', 'Enabling' if state else 'Disabling', full_name) try: - return await self._api.switch(enable=enable, trigger=trigger) + return await self._api.switch(trigger=trigger, state=state) except HikvisionAPIError as err: err_msg = f'{full_name} Switch encountered an error: {err}' self._log.error(err_msg) diff --git a/hikcamerabot/services/stream/dvr/file_wrapper.py b/hikcamerabot/services/stream/dvr/file_wrapper.py index 861eb89..a1c06f3 100644 --- a/hikcamerabot/services/stream/dvr/file_wrapper.py +++ b/hikcamerabot/services/stream/dvr/file_wrapper.py @@ -25,7 +25,7 @@ def __init__(self, filename: str, lock_count: int, cam: 'HikvisionCam') -> None: self._storage_path: str = self._cam.conf.livestream.dvr.local_storage_path self._full_path = os.path.join(self._storage_path, self._filename) - self._thumbnail = os.path.join(self._storage_path, f'{self.name}.jpg') + self._thumbnail = os.path.join(self._storage_path, f'{self.name}-thumb.jpg') self._duration: Optional[int] = None self._width: Optional[int] = None diff --git a/hikcamerabot/version.py b/hikcamerabot/version.py index 51ed7c4..c3b3841 100644 --- a/hikcamerabot/version.py +++ b/hikcamerabot/version.py @@ -1 +1 @@ -__version__ = '1.5.1' +__version__ = '1.5.2' diff --git a/releases/release_1.5.2.md b/releases/release_1.5.2.md new file mode 100644 index 0000000..0e90f0b --- /dev/null +++ b/releases/release_1.5.2.md @@ -0,0 +1,24 @@ +# Release info + +Version: 1.5.2 + +Release date: October 17, 2022 + +# Important +1. Added required `port` config variable for camera API section. Defaults to `80` in the `config-template.json` template. Just add it to your existing config and you're good. + +```json + ... + "camera_list": { + "cam_1": { + "api": { + "host": "http://192.168.1.1", + "port": 80, + ... +``` + +# New features +N/A + +# Misc +N/A