From 43972fab69e3db5cdd6920bb11a9e11e64dbda89 Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 02:28:39 +0200 Subject: [PATCH 1/7] added motion detection setting --- .../reolink_dev/ReolinkPyPi/camera.py | 44 ++++++++++++++++++- custom_components/reolink_dev/camera.py | 41 +++++++++++++++++ custom_components/reolink_dev/services.yaml | 14 ++++++ 3 files changed, 98 insertions(+), 1 deletion(-) diff --git a/custom_components/reolink_dev/ReolinkPyPi/camera.py b/custom_components/reolink_dev/ReolinkPyPi/camera.py index d478ab1..348bf0f 100644 --- a/custom_components/reolink_dev/ReolinkPyPi/camera.py +++ b/custom_components/reolink_dev/ReolinkPyPi/camera.py @@ -26,6 +26,7 @@ def __init__(self, ip, channel): self._rtspport = None self._rtmpport = None self._ptzpresets = dict() + self._motion_detection_state = None def session_active(self): return self._token is not None @@ -41,7 +42,8 @@ async def get_settings(self): {"cmd": "GetEmail", "action": 1, "param": param_channel}, {"cmd": "GetIrLights", "action": 1, "param": param_channel}, {"cmd": "GetRec", "action": 1, "param": param_channel}, - {"cmd": "GetPtzPreset", "action": 1, "param": param_channel}] + {"cmd": "GetPtzPreset", "action": 1, "param": param_channel}, + {"cmd": "GetAlarm","action":1,"param":{"Alarm":{"channel": param_channel ,"type":"md"}}}] param = {"token": self._token} response = await self.send(body, param) @@ -101,6 +103,13 @@ async def get_settings(self): _LOGGER.debug(f"Got preset {preset_name} with ID {preset_id}") else: _LOGGER.debug(f"Preset is not enabled: {preset}") + + elif data["cmd"] == "GetPtzPreset": + self._motion_detection_settings = data + if (data["value"]["Alarm"]["enable"] == 1): + self._motion_detection_state = True + else: + self._motion_detection_state = False except: continue @@ -181,6 +190,11 @@ def last_motion(self): def ptzpresets(self): return self._ptzpresets + @property + def motion_detection_state(self): + """Camera motion detection setting status.""" + return self._motion_detection_state + async def login(self, username, password): body = [{"cmd": "Login", "action": 0, "param": {"User": {"userName": username, "password": password}}}] param = {"cmd": "Login", "token": "null"} @@ -312,6 +326,34 @@ async def set_recording(self, enabled): _LOGGER.error(f"Error translating Recording response to json") return False + async def set_motion_detection(self, enabled): + await self.get_settings() + + if not self._motion_detection_setting: + _LOGGER.error("Error while fetching current motion detection settings") + return + + if enabled == True: + newValue = 1 + else: + newValue = 0 + + body = [{"cmd":"SetAlarm","action":0,"param": self._recording_settings["value"] }] + body[0]["param"]["Alarm"]["enable"] = newValue + body[0]["param"]["Alarm"]["channel"] = 0 + body[0]["param"]["Alarm"]["type"] = "md" + _LOGGER.debug(f"body: " body) + response = await self.send(body, {"cmd": "SetAlarm", "token": self._token} ) + try: + json_data = json.loads(response) + if json_data[0]["value"]["rspCode"] == 200: + return True + else: + return False + except: + _LOGGER.error(f"Error translating Recording response to json") + return False + async def send(self, body, param, stream=False): if (self._token is None and (body is None or body[0]["cmd"] != "Login")): diff --git a/custom_components/reolink_dev/camera.py b/custom_components/reolink_dev/camera.py index 6812434..0868d5c 100644 --- a/custom_components/reolink_dev/camera.py +++ b/custom_components/reolink_dev/camera.py @@ -35,6 +35,8 @@ SERVICE_DISABLE_IR_LIGHTS = 'disable_ir_lights' SERVICE_ENABLE_RECORDING = 'enable_recording' SERVICE_DISABLE_RECORDING = 'disable_recording' +SERVICE_ENABLE_MOTION_DETECTION = 'enable_motion_detection' +SERVICE_DISABLE_MOTION_DETECTION = 'disable_motion_detection' DEFAULT_BRAND = 'Reolink' DOMAIN_DATA = 'reolink_devices' @@ -141,6 +143,25 @@ def handler_disable_recording(call): entity.disable_recording() hass.services.async_register(DOMAIN, SERVICE_DISABLE_RECORDING, handler_disable_recording) +# Event enable motion detection + def handler_enable_motion_detection(call): + component = hass.data.get(DOMAIN) + entity = component.get_entity(call.data.get(ATTR_ENTITY_ID)) + + if entity: + entity.enable_motion_detection() + hass.services.async_register(DOMAIN, SERVICE_ENABLE_RECORDING, handler_enable_motion_detection) + +# Event disable recording + def handler_disable_motion_detection(call): + component = hass.data.get(DOMAIN) + entity = component.get_entity(call.data.get(ATTR_ENTITY_ID)) + + if entity: + entity.disable_motion_detection() + hass.services.async_register(DOMAIN, SERVICE_DISABLE_RECORDING, handler_disable_motion_detection) + + class ReolinkCamera(Camera): """An implementation of a Reolink IP camera.""" @@ -167,6 +188,7 @@ def __init__(self, hass, session, host, username, password, stream, protocol, ch self._ir_state = None self._recording_state = None self._ptzpresets = dict() + self._motion_detection_state = None self._state = STATE_IDLE self._hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, self.disconnect) @@ -187,6 +209,7 @@ def state_attributes(self): attrs["ir_lights_enabled"] = self._ir_state attrs["recording_enabled"] = self._recording_state attrs["ptzpresets"] = self._ptzpresets + attrs["motion_detection_enabled"] = self._motion_detection_state return attrs @@ -230,6 +253,11 @@ def ptzpresets(self): """Camera PTZ presets list.""" return self._ptzpresets + @property + def motion_detection_state(self): + """Camera motion detection setting status.""" + return self._motion_detection_state + async def stream_source(self): """Return the source of the stream.""" if self._protocol == "rtsp": @@ -314,6 +342,18 @@ def disable_recording(self): self._recording_state = False self._hass.states.set(self.entity_id, self.state, self.state_attributes) + def enable_motion_detection(self): + """Enable motion_detecion.""" + if asyncio.run_coroutine_threadsafe(self._reolinkSession.set_motion_detection(True), self.hass.loop).result(): + self._motion_detection_state = True + self._hass.states.set(self.entity_id, self.state, self.state_attributes) + + def disable_motion_detection(self): + """Disable motion detecion.""" + if asyncio.run_coroutine_threadsafe(self._reolinkSession.set_motion_detecion(False), self.hass.loop).result(): + self._motion_detection_state = False + self._hass.states.set(self.entity_id, self.state, self.state_attributes) + async def update_motion_state(self): await self._reolinkSession.get_motion_state() @@ -332,6 +372,7 @@ async def update_status(self): self._ir_state = self._reolinkSession.ir_state self._recording_state = self._reolinkSession.recording_state self._ptzpresets = self._reolinkSession.ptzpresets + self._motion_detection_state = self._reolinkSession.motion_detection_state async def async_update(self): """Update the data from the camera.""" diff --git a/custom_components/reolink_dev/services.yaml b/custom_components/reolink_dev/services.yaml index 139e15f..37c8877 100644 --- a/custom_components/reolink_dev/services.yaml +++ b/custom_components/reolink_dev/services.yaml @@ -53,3 +53,17 @@ disable_recording: entity_id: description: Name of the Reolink camera entity to set. example: 'camera.frontdoor' + +enable_motion_detection: + description: Enable the motion detection of the Reolink camera. + fields: + entity_id: + description: Name of the Reolink camera entity to set. + example: 'camera.frontdoor' + +disable_motion_detection: + description: Disable the motion detection of the Reolink camera. + fields: + entity_id: + description: Name of the Reolink camera entity to set. + example: 'camera.frontdoor' \ No newline at end of file From 7bcfa0055c5a7f7451b60be7f2bc387cc774d23d Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 02:47:52 +0200 Subject: [PATCH 2/7] fix indentation --- custom_components/reolink_dev/camera.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/custom_components/reolink_dev/camera.py b/custom_components/reolink_dev/camera.py index 6812434..ee4f2d2 100644 --- a/custom_components/reolink_dev/camera.py +++ b/custom_components/reolink_dev/camera.py @@ -230,6 +230,11 @@ def ptzpresets(self): """Camera PTZ presets list.""" return self._ptzpresets + @property + def motion_detection_state(self): + """Camera motion detection setting status.""" + return self._motion_detection_state + async def stream_source(self): """Return the source of the stream.""" if self._protocol == "rtsp": From e11952692caec846e0aae129cc06270e5466744c Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 12:32:20 +0200 Subject: [PATCH 3/7] typos and fixes --- custom_components/reolink_dev/ReolinkPyPi/camera.py | 13 ++++++++----- custom_components/reolink_dev/camera.py | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/custom_components/reolink_dev/ReolinkPyPi/camera.py b/custom_components/reolink_dev/ReolinkPyPi/camera.py index 348bf0f..b77a306 100644 --- a/custom_components/reolink_dev/ReolinkPyPi/camera.py +++ b/custom_components/reolink_dev/ReolinkPyPi/camera.py @@ -43,7 +43,10 @@ async def get_settings(self): {"cmd": "GetIrLights", "action": 1, "param": param_channel}, {"cmd": "GetRec", "action": 1, "param": param_channel}, {"cmd": "GetPtzPreset", "action": 1, "param": param_channel}, - {"cmd": "GetAlarm","action":1,"param":{"Alarm":{"channel": param_channel ,"type":"md"}}}] + {"cmd": "GetAlarm","action":1,"param":{"Alarm":{"channel": self._channel ,"type":"md"}}}] + # the call must be like this: + #[{"cmd":"GetAlarm","action":1,"param":{"Alarm":{"channel":0,"type":"md"}}}] + #so we cannot use param_channel param = {"token": self._token} response = await self.send(body, param) @@ -104,8 +107,9 @@ async def get_settings(self): else: _LOGGER.debug(f"Preset is not enabled: {preset}") - elif data["cmd"] == "GetPtzPreset": + elif data["cmd"] == "GetAlarm": self._motion_detection_settings = data + self._pippo = data if (data["value"]["Alarm"]["enable"] == 1): self._motion_detection_state = True else: @@ -329,7 +333,7 @@ async def set_recording(self, enabled): async def set_motion_detection(self, enabled): await self.get_settings() - if not self._motion_detection_setting: + if not self._motion_detection_settings: _LOGGER.error("Error while fetching current motion detection settings") return @@ -338,11 +342,10 @@ async def set_motion_detection(self, enabled): else: newValue = 0 - body = [{"cmd":"SetAlarm","action":0,"param": self._recording_settings["value"] }] + body = [{"cmd":"SetAlarm","action":0,"param": self._motion_detection_settings["value"] }] body[0]["param"]["Alarm"]["enable"] = newValue body[0]["param"]["Alarm"]["channel"] = 0 body[0]["param"]["Alarm"]["type"] = "md" - _LOGGER.debug(f"body: " body) response = await self.send(body, {"cmd": "SetAlarm", "token": self._token} ) try: json_data = json.loads(response) diff --git a/custom_components/reolink_dev/camera.py b/custom_components/reolink_dev/camera.py index 456e42d..0248b9f 100644 --- a/custom_components/reolink_dev/camera.py +++ b/custom_components/reolink_dev/camera.py @@ -350,7 +350,7 @@ def enable_motion_detection(self): def disable_motion_detection(self): """Disable motion detecion.""" - if asyncio.run_coroutine_threadsafe(self._reolinkSession.set_motion_detecion(False), self.hass.loop).result(): + if asyncio.run_coroutine_threadsafe(self._reolinkSession.set_motion_detection(False), self.hass.loop).result(): self._motion_detection_state = False self._hass.states.set(self.entity_id, self.state, self.state_attributes) From 028f3a37abe5545f82997119f14275994c0cab3f Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 12:43:23 +0200 Subject: [PATCH 4/7] added example of motion detection switch --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 5edb3ad..b5f27f3 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,23 @@ switch: {% else %} mdi:flashlight-off {% endif %} + + camera_motion_detection: + value_template: "{{ is_state_attr('camera.frontdoor', 'motion_detection_enabled', true) }}" + turn_on: + service: camera.enable_motion_detection + data: + entity_id: camera.frontdoor + turn_off: + service: camera.disable_motion_detection + data: + entity_id: camera.frontdoor + icon_template: >- + {% if is_state_attr('camera.frontdoor', 'motion_detection_enabled', true) %} + mdi:motion-sensor + {% else %} + mdi:motion-sensor-off + {% endif %} ``` 2. Restart Home Assistant. From d3403bb798292126ab4986468086a1a3f4f33bff Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 12:44:47 +0200 Subject: [PATCH 5/7] completed example --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5f27f3..784e0c0 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ switch: mdi:flashlight-off {% endif %} - camera_motion_detection: + camera_frontdoor_motion_detection: value_template: "{{ is_state_attr('camera.frontdoor', 'motion_detection_enabled', true) }}" turn_on: service: camera.enable_motion_detection @@ -165,6 +165,7 @@ entities: - switch.camera_frontdoor_ir_lights - switch.camera_frontdoor_email - switch.camera_frontdoor_ftp + - switch.camera_frontdoor_motion_detection - binary_sensor.motion_frontdoor ``` From 166f0da609de8aa4706ed1a23cdb819e5078c4ca Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 12:53:22 +0200 Subject: [PATCH 6/7] Update services.yaml added newline at end of file --- custom_components/reolink_dev/services.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/custom_components/reolink_dev/services.yaml b/custom_components/reolink_dev/services.yaml index 37c8877..e1d818f 100644 --- a/custom_components/reolink_dev/services.yaml +++ b/custom_components/reolink_dev/services.yaml @@ -66,4 +66,4 @@ disable_motion_detection: fields: entity_id: description: Name of the Reolink camera entity to set. - example: 'camera.frontdoor' \ No newline at end of file + example: 'camera.frontdoor' From 211c92777850e130483e9e0d88ed94d3d8df5246 Mon Sep 17 00:00:00 2001 From: nick2k3 <42848864+nick2k3@users.noreply.github.com> Date: Sun, 5 Jul 2020 13:23:13 +0200 Subject: [PATCH 7/7] Update camera.py removed unneeded assignments --- custom_components/reolink_dev/ReolinkPyPi/camera.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/custom_components/reolink_dev/ReolinkPyPi/camera.py b/custom_components/reolink_dev/ReolinkPyPi/camera.py index b77a306..c700e36 100644 --- a/custom_components/reolink_dev/ReolinkPyPi/camera.py +++ b/custom_components/reolink_dev/ReolinkPyPi/camera.py @@ -344,8 +344,6 @@ async def set_motion_detection(self, enabled): body = [{"cmd":"SetAlarm","action":0,"param": self._motion_detection_settings["value"] }] body[0]["param"]["Alarm"]["enable"] = newValue - body[0]["param"]["Alarm"]["channel"] = 0 - body[0]["param"]["Alarm"]["type"] = "md" response = await self.send(body, {"cmd": "SetAlarm", "token": self._token} ) try: json_data = json.loads(response)