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 check command to POST commands #772

Merged
merged 8 commits into from
Oct 13, 2023
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Pipfile
Pipfile.lock
blink.json
blinktest.py
.vscode/*
60 changes: 50 additions & 10 deletions blinkpy/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import logging
import string
from json import dumps
from asyncio import sleep
from blinkpy.helpers.util import (
get_time,
Throttle,
Expand All @@ -13,6 +14,8 @@
_LOGGER = logging.getLogger(__name__)

MIN_THROTTLE_TIME = 5
COMMAND_POLL_TIME = 1
MAX_RETRY = 120


async def request_login(
Expand Down Expand Up @@ -94,7 +97,9 @@ async def request_network_update(blink, network):
:param network: Sync module network id.
"""
url = f"{blink.urls.base_url}/network/{network}/update"
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def request_user(blink):
Expand Down Expand Up @@ -137,7 +142,9 @@ async def request_system_arm(blink, network):
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}"
f"/networks/{network}/state/arm"
)
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


@Throttle(seconds=MIN_THROTTLE_TIME)
Expand All @@ -152,7 +159,9 @@ async def request_system_disarm(blink, network):
f"{blink.urls.base_url}/api/v1/accounts/{blink.account_id}"
f"/networks/{network}/state/disarm"
)
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def request_command_status(blink, network, command_id):
Expand Down Expand Up @@ -196,7 +205,9 @@ async def request_new_image(blink, network, camera_id):
:param camera_id: Camera ID of camera to request new image from.
"""
url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/thumbnail"
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


@Throttle(seconds=MIN_THROTTLE_TIME)
Expand All @@ -209,7 +220,9 @@ async def request_new_video(blink, network, camera_id):
:param camera_id: Camera ID of camera to request new video from.
"""
url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/clip"
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


@Throttle(seconds=MIN_THROTTLE_TIME)
Expand Down Expand Up @@ -280,7 +293,9 @@ async def request_camera_liveview(blink, network, camera_id):
f"{blink.urls.base_url}/api/v5/accounts/{blink.account_id}"
f"/networks/{network}/cameras/{camera_id}/liveview"
)
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def request_camera_sensors(blink, network, camera_id):
Expand All @@ -305,7 +320,9 @@ async def request_motion_detection_enable(blink, network, camera_id):
:param camera_id: Camera ID of camera to enable.
"""
url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/enable"
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


@Throttle(seconds=MIN_THROTTLE_TIME)
Expand All @@ -317,7 +334,9 @@ async def request_motion_detection_disable(blink, network, camera_id):
:param camera_id: Camera ID of camera to disable.
"""
url = f"{blink.urls.base_url}/network/{network}/camera/{camera_id}/disable"
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def request_local_storage_manifest(blink, network, sync_id):
Expand All @@ -335,7 +354,9 @@ async def request_local_storage_manifest(blink, network, sync_id):
f"/networks/{network}/sync_modules/{sync_id}"
f"/local_storage/manifest/request"
)
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def get_local_storage_manifest(blink, network, sync_id, manifest_request_id):
Expand Down Expand Up @@ -373,7 +394,9 @@ async def request_local_storage_clip(blink, network, sync_id, manifest_id, clip_
manifest_id=manifest_id,
clip_id=clip_id,
)
return await http_post(blink, url)
response = await http_post(blink, url)
await wait_for_command(blink, response)
return response


async def request_get_config(blink, network, camera_id, product_type="owl"):
Expand Down Expand Up @@ -467,3 +490,20 @@ async def http_post(blink, url, is_retry=False, data=None, json=True, timeout=TI
json_resp=json,
data=data,
)


async def wait_for_command(blink, json_data: dict) -> bool:
"""Wait for command to complete."""
_LOGGER.debug("Command Wait %s", json_data)
network_id = json_data.get("network_id")
command_id = json_data.get("id")
if command_id and network_id:
for _ in range(0, MAX_RETRY):
_LOGGER.debug("Making GET request waiting for command")
status = await request_command_status(blink, network_id, command_id)
_LOGGER.debug("command status %s", status)
if status.get("status_code", 0) != 908:
return False
if status.get("complete"):
return True
await sleep(COMMAND_POLL_TIME)
20 changes: 16 additions & 4 deletions blinkpy/camera.py
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,9 @@ async def async_arm(self, value):
f"{self.network_id}/owls/{self.camera_id}/config"
)
data = dumps({"enabled": value})
return await api.http_post(self.sync.blink, url, json=False, data=data)
response = await api.http_post(self.sync.blink, url, data=data)
await api.wait_for_command(self.sync.blink, response)
return response

async def snap_picture(self):
"""Snap picture for a blink mini camera."""
Expand All @@ -480,7 +482,9 @@ async def snap_picture(self):
f"{self.sync.blink.account_id}/networks/"
f"{self.network_id}/owls/{self.camera_id}/thumbnail"
)
return await api.http_post(self.sync.blink, url)
response = await api.http_post(self.sync.blink, url)
await api.wait_for_command(self.sync.blink, response)
return response

async def get_sensor_info(self):
"""Get sensor info for blink mini camera."""
Expand All @@ -493,6 +497,7 @@ async def get_liveview(self):
f"{self.network_id}/owls/{self.camera_id}/liveview"
)
response = await api.http_post(self.sync.blink, url)
await api.wait_for_command(self.sync.blink, response)
server = response["server"]
server_split = server.split(":")
server_split[0] = "rtsps:"
Expand Down Expand Up @@ -524,7 +529,10 @@ async def async_arm(self, value):
url = f"{url}/enable"
else:
url = f"{url}/disable"
return await api.http_post(self.sync.blink, url)

response = await api.http_post(self.sync.blink, url)
await api.wait_for_command(self.sync.blink, response)
return response

async def snap_picture(self):
"""Snap picture for a blink doorbell camera."""
Expand All @@ -533,7 +541,10 @@ async def snap_picture(self):
f"{self.sync.blink.account_id}/networks/"
f"{self.sync.network_id}/doorbells/{self.camera_id}/thumbnail"
)
return await api.http_post(self.sync.blink, url)

response = await api.http_post(self.sync.blink, url)
await api.wait_for_command(self.sync.blink, response)
return response

async def get_sensor_info(self):
"""Get sensor info for blink doorbell camera."""
Expand All @@ -546,6 +557,7 @@ async def get_liveview(self):
f"{self.sync.network_id}/doorbells/{self.camera_id}/liveview"
)
response = await api.http_post(self.sync.blink, url)
await api.wait_for_command(self.sync.blink, response)
server = response["server"]
link = server.replace("immis://", "rtsps://")
return link
14 changes: 4 additions & 10 deletions blinkpy/sync_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,17 +679,11 @@ def url(self, manifest_id=None):

async def prepare_download(self, blink, max_retries=4):
"""Initiate upload of media item from the sync module to Blink cloud servers."""
if max_retries == 0:
return None
url = blink.urls.base_url + self.url()
response = None
for retry in range(max_retries):
response = await api.http_post(blink, url)
if "id" in response:
break
seconds = backoff_seconds(retry=retry, default_time=3)
_LOGGER.debug(
"[retry=%d] Retrying in %d seconds: %s", retry + 1, seconds, url
)
await asyncio.sleep(seconds)
response = await api.http_post(blink, url)
await api.wait_for_command(blink, response)
return response

async def delete_video(self, blink, max_retries=4) -> bool:
Expand Down
4 changes: 4 additions & 0 deletions tests/mock_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ def __init__(self, json_data, status_code, headers={}, raw_data=None):
async def json(self):
"""Return json data from get_request."""
return self.json_data

def get(self, name):
"""Return field for json."""
return self.json_data[name]
48 changes: 41 additions & 7 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from blinkpy.auth import Auth
import tests.mock_responses as mresp

COMMAND_RESPONSE = {"network_id": "12345", "id": "54321"}
COMMAND_COMPLETE = {"complete": True, "status_code": 908}
COMMAND_COMPLETE_BAD = {"complete": True, "status_code": 999}
COMMAND_NOT_COMPLETE = {"complete": False, "status_code": 908}


@mock.patch("blinkpy.auth.Auth.query")
class TestAPI(IsolatedAsyncioTestCase):
Expand Down Expand Up @@ -57,21 +62,27 @@ async def test_request_network_status(self, mock_resp):

async def test_request_command_status(self, mock_resp):
"""Test command_status."""
mock_resp.return_value = {"command": "done"}
mock_resp.side_effect = ({"command": "done"}, COMMAND_COMPLETE)
self.assertEqual(
await api.request_command_status(self.blink, "network", "command"),
{"command": "done"},
)

async def test_request_new_image(self, mock_resp):
"""Test api request new image."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.side_effect = (
mresp.MockResponse(COMMAND_RESPONSE, 200),
COMMAND_COMPLETE,
)
response = await api.request_new_image(self.blink, "network", "camera")
self.assertEqual(response.status, 200)

async def test_request_new_video(self, mock_resp):
"""Test api request new Video."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.side_effect = (
mresp.MockResponse(COMMAND_RESPONSE, 200),
COMMAND_COMPLETE,
)
response = await api.request_new_video(self.blink, "network", "camera")
self.assertEqual(response.status, 200)

Expand All @@ -97,23 +108,32 @@ async def test_request_camera_usage(self, mock_resp):

async def test_request_motion_detection_enable(self, mock_resp):
"""Test Motion detect enable."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.side_effect = (
mresp.MockResponse(COMMAND_RESPONSE, 200),
COMMAND_COMPLETE,
)
response = await api.request_motion_detection_enable(
self.blink, "network", "camera"
)
self.assertEqual(response.status, 200)

async def test_request_motion_detection_disable(self, mock_resp):
"""Test Motion detect enable."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.side_effect = (
mresp.MockResponse(COMMAND_RESPONSE, 200),
COMMAND_COMPLETE,
)
response = await api.request_motion_detection_disable(
self.blink, "network", "camera"
)
self.assertEqual(response.status, 200)

async def test_request_local_storage_clip(self, mock_resp):
"""Test Motion detect enable."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.side_effect = (
mresp.MockResponse(COMMAND_RESPONSE, 200),
COMMAND_COMPLETE,
)
response = await api.request_local_storage_clip(
self.blink, "network", "sync_id", "manifest_id", "clip_id"
)
Expand All @@ -135,7 +155,7 @@ async def test_request_get_config(self, mock_resp):

async def test_request_update_config(self, mock_resp):
"""Test Motion detect enable."""
mock_resp.return_value = mresp.MockResponse({}, 200)
mock_resp.return_value = mresp.MockResponse(COMMAND_RESPONSE, 200)
response = await api.request_update_config(
self.blink, "network", "camera_id", "owl"
)
Expand All @@ -149,3 +169,17 @@ async def test_request_update_config(self, mock_resp):
self.blink, "network", "camera_id", "other_camera"
)
)

async def test_wait_for_command(self, mock_resp):
"""Test Motion detect enable."""
mock_resp.side_effect = (COMMAND_NOT_COMPLETE, COMMAND_COMPLETE)
response = await api.wait_for_command(self.blink, COMMAND_RESPONSE)
assert response

mock_resp.side_effect = (COMMAND_NOT_COMPLETE, {})
response = await api.wait_for_command(self.blink, COMMAND_RESPONSE)
self.assertFalse(response)

mock_resp.side_effect = (COMMAND_COMPLETE_BAD, {})
response = await api.wait_for_command(self.blink, COMMAND_RESPONSE)
self.assertFalse(response)
Loading
Loading