Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Per Camera Snooze Functionality #975

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions blinkpy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,58 @@ async def request_update_config(
return await http_post(blink, url, json=False, data=data)


async def request_camera_snooze(
blink, network, camera_id, product_type="owl", data=None
):
"""
Update camera snooze configuration.

:param blink: Blink instance.
:param network: Sync module network id.
:param camera_id: ID of camera
:param product_type: Camera product type "owl" or "catalina"
:param data: string w/JSON dict of parameters/values to update
"""
if product_type == "catalina":
url = (
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/"
f"networks/{network}/cameras/{camera_id}/snooze"
)
elif product_type == "owl":
url = (
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/"
f"networks/{network}/owls/{camera_id}/snooze"
)
elif product_type == "doorbell":
url = (
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}/"
f"networks/{network}/doorbells/{camera_id}/snooze"
)
else:
_LOGGER.info(
"Camera %s with product type %s snooze update not implemented.",
camera_id,
product_type,
)
return None
return await http_post(blink, url, json=True, data=data)


async def request_sync_snooze(blink, network, data=None):
"""
Update sync snooze configuration.

:param blink: Blink instance.
:param network: Sync module network id.
:param data: string w/JSON dict of parameters/values to update
"""
url = (
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}"
f"/networks/{network}/snooze"
)
return await http_post(blink, url, json=True, data=data)


async def http_get(
blink, url, stream=False, json=True, is_retry=False, timeout=TIMEOUT
):
Expand Down
43 changes: 43 additions & 0 deletions blinkpy/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,35 @@ async def async_set_night_vision(self, value):
return await res.json()
return None

@property
async def snooze_till(self):
"""Return snooze_till status."""
res = await api.request_get_config(
self.sync.blink,
self.network_id,
self.camera_id,
product_type=self.product_type,
)
if res is None:
return None
if self.product_type == "catalina":
res = res.get("camera", [{}])[0]["snooze_till"]
return res

async def async_snooze(self, snooze_time=240):
"""Set camera snooze status."""
data = dumps({"snooze_time": snooze_time})
res = await api.request_camera_snooze(
self.sync.blink,
self.network_id,
self.camera_id,
product_type=self.product_type,
data=data,
)
if res and res.status == 200:
return await res.json()
return None

async def record(self):
"""Initiate clip recording."""
return await api.request_new_video(
Expand Down Expand Up @@ -625,3 +654,17 @@ async def get_liveview(self):
server = response["server"]
link = server.replace("immis://", "rtsps://")
return link

async def async_snooze(self):
"""Set camera snooze status."""
data = dumps({"snooze_time": 240})
res = await api.request_camera_snooze(
self.sync.blink,
self.network_id,
self.camera_id,
product_type="doorbell",
data=data,
)
if res and res.status == 200:
return await res.json()
return None
25 changes: 25 additions & 0 deletions blinkpy/sync_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import string
import datetime
from json import dumps
import traceback
import asyncio
import aiofiles
Expand Down Expand Up @@ -127,6 +128,30 @@ async def async_arm(self, value):
return await api.request_system_arm(self.blink, self.network_id)
return await api.request_system_disarm(self.blink, self.network_id)

@property
async def snooze_till(self):
"""Return snooze_till status."""
res = await api.request_sync_snooze(
self.blink,
self.network_id,
)
if res is None:
return None
res = res.get("snooze_till")
return res

async def async_snooze(self, snooze_time=240):
"""Set sync snooze status."""
data = dumps({"snooze_time": snooze_time})
res = await api.request_sync_snooze(
self.blink,
self.network_id,
data=data,
)
if res and res.status == 200:
return await res.json()
return None

async def start(self):
"""Initialize the system."""
_LOGGER.debug("Initializing the sync module")
Expand Down
22 changes: 22 additions & 0 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,3 +203,25 @@ async def test_wait_for_command(self, mock_resp):

response = await api.wait_for_command(self.blink, None)
self.assertFalse(response)

async def test_request_camera_snooze(self, mock_resp):
"""Test request_camera_snooze."""
mock_resp.return_value = mresp.MockResponse({}, 200)
response = await api.request_camera_snooze(
self.blink, "network", "camera_id", "owl", {}
)
self.assertEqual(response.status, 200)
response = await api.request_camera_snooze(
self.blink, "network", "camera_id", "catalina", {}
)
self.assertEqual(response.status, 200)
response = await api.request_camera_snooze(
self.blink, "network", "camera_id", "doorbell", {}
)
self.assertEqual(response.status, 200)

async def test_request_sync_snooze(self, mock_resp):
"""Test sync snooze update."""
mock_resp.return_value = mresp.MockResponse({}, 200)
response = await api.request_sync_snooze(self.blink, "network", {})
self.assertEqual(response.status, 200)
25 changes: 25 additions & 0 deletions tests/test_camera_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import datetime
from unittest import mock
from unittest import IsolatedAsyncioTestCase
from blinkpy import api
from blinkpy.blinkpy import Blink
from blinkpy.helpers.util import BlinkURLHandler
from blinkpy.sync_module import BlinkSyncModule
Expand Down Expand Up @@ -222,6 +223,30 @@ async def test_night_vision(self, mock_resp):
mock_resp.return_value = mresp.MockResponse({"code": 400}, 400)
self.assertIsNone(await self.camera.async_set_night_vision("on"))

async def test_snooze_till(self, mock_resp):
"""Test snooze_till property."""
mock_resp = {"camera": [{"snooze_till": 1234567890}]}
with mock.patch.object(
api,
"request_get_config",
return_value=mock_resp,
):
result = await self.camera.snooze_till
self.assertEqual(result, {"camera": [{"snooze_till": 1234567890}]})

async def test_async_snooze(self, mock_resp):
"""Test async_snooze function."""
mock_resp = mresp.MockResponse({}, 200)
with mock.patch("blinkpy.api.request_camera_snooze", return_value=mock_resp):
response = await self.camera.async_snooze()
self.assertEqual(response, {})
mock_resp = mresp.MockResponse({}, 200)
with mock.patch("blinkpy.api.request_camera_snooze", return_value=mock_resp):
response = await self.camera.async_snooze()
self.assertEqual(response, {})
response = await self.camera.async_snooze("invalid_value")
self.assertIsNone(response)

async def test_record(self, mock_resp):
"""Test camera record function."""
with mock.patch(
Expand Down
40 changes: 40 additions & 0 deletions tests/test_sync_module.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Tests camera and system functions."""

import datetime
from json import dumps
import logging
from unittest import IsolatedAsyncioTestCase
from unittest import mock
Expand Down Expand Up @@ -653,3 +654,42 @@ async def test_download_delete(self, mock_prepdl, mock_del, mock_dl, mock_resp):
mock_del.return_value = mock.AsyncMock()
mock_dl.return_value = False
self.assertFalse(await item.download_video_delete(self.blink, "filename.mp4"))

async def test_async_snooze(self, mock_resp):
"""Test successful snooze."""
with mock.patch(
"blinkpy.api.request_sync_snooze", new_callable=mock.AsyncMock
) as mock_resp_local:
mock_resp_local.return_value.status = 200
mock_resp_local.return_value.json.return_value = {"status": 200}
snooze_time = 240
expected_data = dumps({"snooze_time": snooze_time})
expected_response = {"status": 200}

self.assertEqual(
await self.blink.sync["test"].async_snooze(snooze_time),
expected_response,
)
mock_resp_local.assert_called_once_with(
self.blink,
self.blink.sync["test"].network_id,
data=expected_data,
)

mock_resp_local.return_value.status = 400
mock_resp_local.return_value.json.return_value = None
expected_response = None

self.assertEqual(
await self.blink.sync["test"].async_snooze(snooze_time),
expected_response,
)

async def test_snooze_till(self, mock_resp) -> None:
"""Test snooze_till method."""
mock_resp.return_value = {"snooze_till": "2022-01-01T00:00:00Z"}
self.assertEqual(
await self.blink.sync["test"].snooze_till, "2022-01-01T00:00:00Z"
)
mock_resp.return_value = None
self.assertIsNone(await self.blink.sync["test"].snooze_till)
Loading