Skip to content

Commit

Permalink
Add motion detection enabled switch
Browse files Browse the repository at this point in the history
  • Loading branch information
sdb9696 committed Sep 11, 2023
1 parent 9efa9da commit c7bf3a2
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 2 deletions.
8 changes: 8 additions & 0 deletions ring_doorbell/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# vim:sw=4:ts=4:et:
"""Python Ring Auth Class."""
from uuid import uuid4 as uuid
from json import dumps as json_dumps
from requests_oauthlib import OAuth2Session
from oauthlib.oauth2 import LegacyApplicationClient, TokenExpiredError
from ring_doorbell.const import OAuth, API_VERSION, TIMEOUT
Expand Down Expand Up @@ -90,6 +91,13 @@ def query(
if data is not None:
kwargs["data"] = data

if method == "PATCH":
# PATCH method of requests library does not have a json argument
if json is not None:
kwargs["data"] = json_dumps(json)
if data is not None:
kwargs["data"] = data

try:
req = getattr(self._oauth, method.lower())(url, **kwargs)
except TokenExpiredError:
Expand Down
2 changes: 2 additions & 0 deletions ring_doorbell/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class OAuth:
URL_RECORDING = "/clients_api/dings/{0}/recording"
URL_RECORDING_SHARE_PLAY = "/clients_api/dings/{0}/share/play"
GROUP_DEVICES_ENDPOINT = GROUPS_ENDPOINT + "/{1}/devices"
SETTINGS_ENDPOINT = "/devices/v1/devices/{0}/settings"

# chime test sound kinds
KIND_DING = "ding"
Expand Down Expand Up @@ -94,6 +95,7 @@ class OAuth:
FILE_EXISTS = "The file {0} already exists."
MSG_VOL_OUTBOUND = "Must be within the {0}-{1}."
MSG_ALLOWED_VALUES = "Only the following values are allowed: {0}."
MSG_EXPECTED_ATTRIBUTE_NOT_FOUND = "Couldn't find expected attribute: {0}."

POST_DATA = {
"api_version": API_VERSION,
Expand Down
47 changes: 47 additions & 0 deletions ring_doorbell/doorbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
MSG_BOOLEAN_REQUIRED,
MSG_EXISTING_TYPE,
MSG_VOL_OUTBOUND,
MSG_ALLOWED_VALUES,
MSG_EXPECTED_ATTRIBUTE_NOT_FOUND,
PEEPHOLE_CAM_KINDS,
SNAPSHOT_ENDPOINT,
SNAPSHOT_TIMESTAMP_ENDPOINT,
Expand All @@ -35,6 +37,7 @@
URL_RECORDING_SHARE_PLAY,
DEFAULT_VIDEO_DOWNLOAD_TIMEOUT,
HEALTH_DOORBELL_ENDPOINT,
SETTINGS_ENDPOINT,
)

_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -95,6 +98,14 @@ def has_capability(self, capability):
return self.kind in DOORBELL_3_PLUS_KINDS
if capability == "volume":
return True
if capability == "motion_detection":
return self.kind in (
DOORBELL_KINDS
+ DOORBELL_2_KINDS
+ DOORBELL_3_KINDS
+ DOORBELL_3_PLUS_KINDS
+ PEEPHOLE_CAM_KINDS
)
return False

@property
Expand Down Expand Up @@ -447,3 +458,39 @@ def get_snapshot(self, retries=3, delay=1, filename=None):
return True
return snapshot
return False

def _motion_detection_state(self):
if "settings" in self._attrs and "motion_detection_enabled" in self._attrs.get(
"settings"
):
return self._attrs.get("settings")["motion_detection_enabled"]
return None

@property
def motion_detection(self):
"""Return motion detection enabled state."""
return self._motion_detection_state()

@motion_detection.setter
def motion_detection(self, state):
"""Set the motion detection enabled state."""
values = [True, False]
if state not in values:
_LOGGER.error("%s", MSG_ALLOWED_VALUES.format(", ".join(values)))
return False

if self._motion_detection_state() is None:
_LOGGER.warning(
"%s",
MSG_EXPECTED_ATTRIBUTE_NOT_FOUND.format(
"settings[motion_detection_enabled]"
),
)
return False

url = SETTINGS_ENDPOINT.format(self.id)
payload = {"motion_settings": {"motion_detection_enabled": state}}

self._ring.query(url, method="PATCH", json=payload)
self._ring.update_devices()
return True
10 changes: 10 additions & 0 deletions ring_doorbell/stickup_cam.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ def has_capability(self, capability):
+ STICKUP_CAM_BATTERY_KINDS
+ STICKUP_CAM_WIRED_KINDS
)
if capability == "motion_detection":
return self.kind in (
FLOODLIGHT_CAM_KINDS
+ FLOODLIGHT_CAM_PRO_KINDS
+ INDOOR_CAM_KINDS
+ SPOTLIGHT_CAM_BATTERY_KINDS
+ SPOTLIGHT_CAM_WIRED_KINDS
+ STICKUP_CAM_BATTERY_KINDS
+ STICKUP_CAM_WIRED_KINDS
)
return False

@property
Expand Down
6 changes: 4 additions & 2 deletions test.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ def otp_callback():

def main():
if cache_file.is_file():
auth = Auth("MyProject/1.0", json.loads(cache_file.read_text()), token_updated)
auth = Auth(
"RingAPI2023/1.0", json.loads(cache_file.read_text()), token_updated
)
else:
username = input("Username: ")
password = getpass.getpass("Password: ")
auth = Auth("MyProject/1.0", None, token_updated)
auth = Auth("RingAPI2023/1.0", None, token_updated)
try:
auth.fetch_token(username, password)
except MissingTokenError:
Expand Down
3 changes: 3 additions & 0 deletions tests/fixtures/ring_devices.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"middle",
"high",
"highest"],
"motion_detection_enabled": true,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": [
Expand Down Expand Up @@ -115,6 +116,7 @@
"middle",
"high",
"highest"],
"motion_detection_enabled": true,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": [
Expand Down Expand Up @@ -179,6 +181,7 @@
"middle",
"high",
"highest"],
"motion_detection_enabled": true,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": [
Expand Down
19 changes: 19 additions & 0 deletions tests/test_ring.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ def mock_ring_requests():
"https://api.ring.com/groups/v1/locations/mock-location-id/groups/mock-group-id/devices",
text="ok",
)
mock.patch(
"https://api.ring.com/devices/v1/devices/987652/settings",
text="ok",
)
yield mock


Expand Down Expand Up @@ -204,3 +208,18 @@ def test_light_groups(ring):

# Attempt setting lights to invalid value
group.lights = 30


def test_motion_detection_enable(ring, mock_ring_requests):
dev = ring.devices()["doorbots"][0]

dev.motion_detection = True
dev.motion_detection = False

history = list(
filter(lambda x: x.method == "PATCH", mock_ring_requests.request_history)
)
assert history[0].path == "/devices/v1/devices/987652/settings"
assert history[0].text == '{"motion_settings": {"motion_detection_enabled": true}}'
assert history[1].path == "/devices/v1/devices/987652/settings"
assert history[1].text == '{"motion_settings": {"motion_detection_enabled": false}}'

0 comments on commit c7bf3a2

Please sign in to comment.