diff --git a/README.md b/README.md index 661458a9..70316e1b 100644 --- a/README.md +++ b/README.md @@ -12,169 +12,71 @@ $ pip3 install git+https://github.com/tchellomello/python-ring-doorbell@dev --up ## Usage ```python -from ring_doorbell import Ring -myring = Ring('user@email.com', 'secret') - -myring.devices -{'chimes': ['Downstairs'], 'doorbells': ['Front Door']} - -myring.chimes -['Downstairs'] - -myring.doorbells -['Front Door'] - -myring.chime_attributes('Downstairs') -{'address': '123 Example St, New York, NY, USA', - 'alerts': {'connection': 'online'}, - 'description': 'Downstairs', - 'device_id': '1234567', - 'do_not_disturb': {'seconds_left': 0}, - 'features': {'ringtones_enabled': True}, - 'firmware_version': '1.1.31', - 'id': 123456, - 'kind': 'chime', - 'latitude': 00.0000000, - 'longitude': -00.000000, - 'owned': True, - 'owner': {'email': 'owner@email.com', - 'first_name': 'Owner Name', - 'id': 123456, - 'last_name': 'Owner Surname'}, - 'settings': {'ding_audio_id': None, - 'ding_audio_user_id': None, - 'motion_audio_id': None, - 'motion_audio_user_id': None, - 'volume': 10}, - 'time_zone': 'America/New_York'} - -# for battery powered it will show a range 0-100 -myring.doorbell_battery_life('Front Door') -'4107 - -myring.doorbell_attributes('Front Door') -{'address': '123 Example St, New York, NY, USA', - 'alerts': {'connection': 'online'}, - 'battery_life': '4107', - 'description': 'Front Door', - 'device_id': '12345678', - 'external_connection': False, - 'features': {'advanced_motion_enabled': False, - 'motions_enabled': True, - 'people_only_enabled': False, - 'shadow_correction_enabled': False, - 'show_recordings': True}, - 'firmware_version': '1.3.91', - - '....SNIP....' : '....SNIP....', - - 'video_settings': {'ae_level': 32, - 'birton': None, - 'brightness': 16, - 'contrast': 80, - 'saturation': 48}}, - 'subscribed': True, - 'subscribed_motions': True, - 'time_zone': 'America/New_York'} - -myring.history(limit=2, timezone='America/Sao_Paulo') -[{'answered': False, - 'created_at': datetime.datetime(2017, 2, 14, 19, 51, 31, tzinfo=), - 'doorbot': {'description': 'Front Door', 'id': 12345}, - 'events': [], - 'favorite': False, - 'id': 1234, - 'kind': 'motion', - 'recording': {'status': 'ready'}, - 'snapshot_url': ''}, - {'answered': False, - 'created_at': datetime.datetime(2017, 2, 14, 18, 26, 6, tzinfo=), - 'doorbot': {'description': 'Front Door', 'id': 12345}, - 'events': [], - 'favorite': False, - 'id': 12345, - 'kind': 'motion', - 'recording': {'status': 'ready'}, - 'snapshot_url': ''}] - -myring.history(name='Front Door', limit=1, timezone='America/Sao_Paulo') -[{'answered': False, - 'created_at': datetime.datetime(2017, 2, 14, 19, 51, 31, tzinfo=), - 'doorbot': {'description': 'Front Door', 'id': 12345}, - 'events': [], - 'favorite': False, - 'id': 1234, - 'kind': 'motion', - 'recording': {'status': 'ready'}, - 'snapshot_url': ''}] - -# download video -myring.doorbell_download_recording(123456, '/home/user/test.mp4') -True - -# show video URL -myring.doorbell_recording_url(123456) -'https://ring-transcoded-videos.s3.amazonaws.com/123456.mp4?X-Amz-Expires=3600&X-Amz-Date=20170210T000928Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=zzzzzzzzzzzzAAA/20170210/us-east-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' - -# generate live streaming -myring.live_streaming('Front Door') -[{'audio_jitter_buffer_ms': 0, - 'device_kind': 'lpd_v1', - 'doorbot_description': 'Front Door', - 'doorbot_id': 12345, - 'expires_in': 179, - 'id': 123456, - 'id_str': '12345', - 'kind': 'on_demand', - 'motion': False, - 'now': 1486710809.55569, - 'optimization_level': 3, - 'protocol': 'sip', - 'sip_ding_id': '1234563', - 'sip_endpoints': None, - 'sip_from': 'sip:12345@ring.com', - 'sip_server_ip': '1.2.3.4', - 'sip_server_port': '15063', - 'sip_server_tls': 'false', - 'sip_session_id': '1iaaaaaaaaaq', - 'sip_to': 'sip:1iaaaaaaaaaq@1.2.3.4.5:15063;transport=tcp', - 'sip_token': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab', - 'snapshot_url': '', - 'state': 'ringing', - 'video_jitter_buffer_ms': 0}] - -# push notifications (motion or ring) - while True: - time.sleep(3) - print(myring.check_activity) -[] -[] -[{'audio_jitter_buffer_ms': 0, - 'device_kind': 'lpd_v1', - 'doorbot_description': 'Front Door', - 'doorbot_id': 12345, - 'expires_in': 179, - 'id': 123456, - 'id_str': '12345', - 'kind': 'on_demand', - 'motion': False, - 'now': 1486710809.55569, - 'optimization_level': 3, - 'protocol': 'sip', - 'sip_ding_id': '1234563', - 'sip_endpoints': None, - 'sip_from': 'sip:12345@ring.com', - 'sip_server_ip': '1.2.3.4', - 'sip_server_port': '15063', - 'sip_server_tls': 'false', - 'sip_session_id': '1iaaaaaaaaaq', - 'sip_to': 'sip:1iaaaaaaaaaq@1.2.3.4.5:15063;transport=tcp', - 'sip_token': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab', - 'snapshot_url': '', - 'state': 'ringing', - 'video_jitter_buffer_ms': 0}] -[] -[] +In [1]: from ring_doorbell import Ring +In [2]: myring = Ring('user@email.com', 'password') + +In [3]: myring.devices +Out[3]: +{'chimes': [], + 'doorbells': []} + +In [4]: myring.chimes +Out[4]: [] + +In [5]: myring.doorbells +Out[5]: [] + +In [6]: mychime = myring.chimes[0] + +In [7]: mychime. + mychime.account_id mychime.firmware mychime.linked_tree mychime.subscribed_motions + mychime.address mychime.id mychime.longitude mychime.timezone + mychime.debug mychime.kind mychime.name mychime.update + mychime.family mychime.latitude mychime.subscribed mychime.volume + mychime.test_sound + +In [7]: mychime.volume +Out[7]: 5 + +#updating volume +In [8]: mychime.volume = 200 +Must be within the 0-10. + +In [9]: mychime.volume = 4 + +In [10]: mychime.volume +Out[10]: 4 + +# DoorBells +In [11]: mydoorbell = myring.doorbells[0] + +In [12]: mydoorbell. + mydoorbell.account_id mydoorbell.kind + mydoorbell.address mydoorbell.last_recording_id + mydoorbell.battery_life mydoorbell.latitude + mydoorbell.check_activity mydoorbell.live_streaming_json + mydoorbell.debug mydoorbell.longitude + mydoorbell.existing_doorbell_type mydoorbell.name + mydoorbell.existing_doorbell_type_duration mydoorbell.recording_download + mydoorbell.existing_doorbell_type_enabled mydoorbell.recording_url + mydoorbell.family mydoorbell.timezone + mydoorbell.firmware mydoorbell.update + mydoorbell.history mydoorbell.volume + mydoorbell.id + +In [12]: mydoorbell.last_recording_id +Out[12]: 2222222221 + +In [14]: mydoorbell.existing_doorbell_type +Out[14]: 'Mechanical' + +In [15]: mydoorbell.existing_doorbell_type_enabled +Out[15]: True + +In [16]: mydoorbell.existing_doorbell_type_enabled = False + +In [17]: mydoorbell.existing_doorbell_type_enabled +Out[17]: False ``` ## Credits diff --git a/ring_doorbell/__init__.py b/ring_doorbell/__init__.py index 16722f74..98363464 100644 --- a/ring_doorbell/__init__.py +++ b/ring_doorbell/__init__.py @@ -13,13 +13,16 @@ import requests import pytz -from ring_doorbell.const import ( - API_VERSION, API_URI, DEVICES_ENDPOINT, DINGS_ENDPOINT, - FILE_EXISTS, GENERIC_FAIL, HEADERS, LINKED_CHIMES_ENDPOINT, - LIVE_STREAMING_ENDPOINT, NEW_SESSION_ENDPOINT, NOT_FOUND, - URL_HISTORY, URL_DOORBELL_HISTORY, URL_RECORDING, - POST_DATA, RETRY_TOKEN) from ring_doorbell.utils import _locator +from ring_doorbell.const import ( + API_VERSION, API_URI, CHIMES_ENDPOINT, CHIME_VOL_MIN, CHIME_VOL_MAX, + DEVICES_ENDPOINT, DOORBELLS_ENDPOINT, DOORBELL_VOL_MIN, DOORBELL_VOL_MAX, + DOORBELL_EXISTING_TYPE, DINGS_ENDPOINT, FILE_EXISTS, + HEADERS, LINKED_CHIMES_ENDPOINT, LIVE_STREAMING_ENDPOINT, + NEW_SESSION_ENDPOINT, MSG_BOOLEAN_REQUIRED, MSG_EXISTING_TYPE, + MSG_GENERIC_FAIL, MSG_VOL_OUTBOUND, + NOT_FOUND, URL_DOORBELL_HISTORY, URL_RECORDING, + POST_DATA, RETRY_TOKEN, TESTSOUND_CHIME_ENDPOINT) _LOGGER = logging.getLogger(__name__) @@ -33,7 +36,7 @@ def __init__(self, username, password, debug=False): self.is_connected = None self._id = None self.token = None - self._params = None + self.params = None self.debug = debug self.username = username @@ -62,15 +65,19 @@ def _authenticate(self, attempts=RETRY_TOKEN): self._id = data.get('id') self.is_connected = True self.token = data.get('authentication_token') - self._params = {'api_version': API_VERSION, - 'auth_token': self.token} + self.params = {'api_version': API_VERSION, + 'auth_token': self.token} return True self.is_connected = False req.raise_for_status() - def _query(self, url, attempts=RETRY_TOKEN, - raw=False, extra_params=None): + def query(self, + url, + attempts=RETRY_TOKEN, + method='GET', + raw=False, + extra_params=None): """Query data from Ring API.""" if self.debug: _LOGGER.debug("Querying %s", url) @@ -82,18 +89,28 @@ def _query(self, url, attempts=RETRY_TOKEN, response = None loop = 0 while loop <= attempts: + if self.debug: + _LOGGER.debug("running query loop %s", loop) # allow to override params when necessary - # and update self._params globally for the next connection + # and update self.params globally for the next connection if extra_params: - params = self._params + params = self.params params.update(extra_params) else: - params = self._params + params = self.params loop += 1 try: - req = self.session.get((url), params=urlencode(params)) + if method == 'GET': + req = self.session.get((url), params=urlencode(params)) + elif method == 'PUT': + req = self.session.put((url), params=urlencode(params)) + elif method == 'POST': + req = self.session.post((url), params=urlencode(params)) + + if self.debug: + _LOGGER.debug("_query %s ret %s", loop, req.status_code) except: raise @@ -103,18 +120,16 @@ def _query(self, url, attempts=RETRY_TOKEN, self._authenticate() continue - if req.status_code == 200: + if req.status_code == 200 or req.status_code == 204: # if raw, return session object otherwise return JSON if raw: response = req else: - response = req.json() - - break - - if response is None and self.debug: - _LOGGER.debug(GENERIC_FAIL) - return response + if method == 'GET': + response = req.json() + return response + _LOGGER.error("%s", MSG_GENERIC_FAIL) + return None @property def has_subscription(self): @@ -132,192 +147,306 @@ def devices(self): devs['doorbells'] = self.doorbells return devs - @property - def __devices(self): + def __devices(self, device_type): """Private method to query devices.""" + lst = [] url = API_URI + DEVICES_ENDPOINT - return self._query(url) + try: + if device_type == 'chime': + req = self.query(url).get('chimes') + for member in list((obj['description'] for obj in req)): + lst.append(RingChime(self, member)) + + if device_type == 'doorbell': + req = self.query(url).get('doorbots') + for member in list((obj['description'] for obj in req)): + lst.append(RingDoorBell(self, member)) + + except AttributeError: + pass + return lst @property def chimes(self): - """Return list of chimes by name.""" - try: - req = self.__devices.get('chimes') - return list((obj['description'] for obj in req)) - except AttributeError: - return NOT_FOUND + """Return a list of RingDoorChime objects.""" + return self.__devices('chime') - def chime_id(self, name): - """Return chime ID.""" - try: - return self.chime_attributes(name).get('id') - except AttributeError: - return NOT_FOUND + @property + def doorbells(self): + """Return a list of RingDoorBell objects.""" + return self.__devices('doorbell') - def chime_attributes(self, name): + +class RingGeneric(object): + """Generic Implementation for Ring Chime/Doorbell.""" + + def __init__(self): + """Initialize Ring Generic.""" + self._attrs = None + self.debug = None + self.family = None + self.name = None + + def __repr__(self): + """Return __repr__.""" + return "<{0}: {1}>".format(self.__class__.__name__, self.name) + + def update(self): + """Refresh attributes.""" + self._get_attrs() + + def _get_attrs(self): """Return chime attributes.""" + url = API_URI + DEVICES_ENDPOINT try: - lst = self.__devices.get('chimes') - index = _locator(lst, 'description', name) + lst = self._ring.query(url).get(self.family) + index = _locator(lst, 'description', self.name) if index == NOT_FOUND: - return NOT_FOUND - return lst[index] + return None except AttributeError: - return NOT_FOUND + return None - def chime_get_volume(self, name): - """Return if chime volume.""" - try: - return self.chime_attributes(name).get('settings').get('volume') - except AttributeError: - return NOT_FOUND + self._attrs = lst[index] + return True - def chime_firmware(self, name): + @property + def account_id(self): + """Return account ID.""" + return self._attrs.get('id') + + @property + def address(self): + """Return address.""" + return self._attrs.get('address') + + @property + def firmware(self): """Return firmware.""" - try: - return self.chime_attributes(name).get('firmware_version') - except AttributeError: - return NOT_FOUND + return self._attrs.get('firmware_version') + + # pylint: disable=invalid-name + @property + def id(self): + """Return ID.""" + return self._attrs.get('device_id') + + @property + def latitude(self): + """Return latitude attr.""" + return self._attrs.get('latitude') + + @property + def longitude(self): + """Return longitude attr.""" + return self._attrs.get('longitude') + + @property + def kind(self): + """Return kind attr.""" + return self._attrs.get('kind') + + @property + def timezone(self): + """Return timezone.""" + return self._attrs.get('time_zone') + + +class RingChime(RingGeneric): + """Implementation for Ring Chime.""" + + def __init__(self, ring, name): + """Initilize Ring chime object.""" + super(RingChime, self).__init__() + self._attrs = None + self._ring = ring + self.debug = self._ring.debug + self.family = 'chimes' + self.name = name + self.update() + + @property + def volume(self): + """Return if chime volume.""" + return self._attrs.get('settings').get('volume') + + @volume.setter + def volume(self, value): + if not ((isinstance(value, int)) and + (value >= CHIME_VOL_MIN and value <= CHIME_VOL_MAX)): + _LOGGER.error("%s", MSG_VOL_OUTBOUND.format(CHIME_VOL_MIN, + CHIME_VOL_MAX)) + return False + + params = { + 'chime[description]': self.name, + 'chime[settings][volume]': str(value)} + url = API_URI + CHIMES_ENDPOINT.format(self.account_id) + self._ring.query(url, extra_params=params, method='PUT') + self.update() + return True - def is_chime_online(self, name): + @property + def subscribed(self): """Return if chime is online.""" - try: - result = self.chime_attributes(name).get('subscribed') - if result is None: - return False - except AttributeError: - return NOT_FOUND + result = self._attrs.get('firmware_version') + if result is None: + return False return True - def is_chime_subscribed_motions(self, name): + @property + def subscribed_motions(self): """Return if chime is subscribed_motions.""" - try: - result = self.chime_attributes(name).get('subscribed_motions') - if result is None: - return False - except AttributeError: - return NOT_FOUND + result = self._attrs.get('subscribed_motions') + if result is None: + return False return True - def chime_tree(self, name): + @property + def linked_tree(self): """Return doorbell data linked to chime.""" - chime_id = self.chime_id(name) - url = API_URI + LINKED_CHIMES_ENDPOINT.format(chime_id) - return self._query(url) + url = API_URI + LINKED_CHIMES_ENDPOINT.format(self.account_id) + return self._ring.query(url) @property - def doorbells(self): - """Return list of doorbells by name.""" - try: - req = self.__devices.get('doorbots') - return list((obj['description'] for obj in req)) - except AttributeError: - return NOT_FOUND + def test_sound(self): + """Play chime to test sound.""" + url = API_URI + TESTSOUND_CHIME_ENDPOINT.format(self.account_id) + self._ring.query(url, method='POST') + return True - def doorbell_attributes(self, name): - """Return doorbell attributes.""" - try: - lst = self.__devices.get('doorbots') - index = _locator(lst, 'description', name) - if index == NOT_FOUND: - return NOT_FOUND - return lst[index] - except AttributeError: - return NOT_FOUND - def doorbell_id(self, name): - """Return doorbell ID.""" - try: - return self.doorbell_attributes(name).get('id') - except AttributeError: - return NOT_FOUND +class RingDoorBell(RingGeneric): + """Implementation for Ring Doorbell.""" - def doorbell_get_volume(self, name): - """Return volume.""" - try: - return self.doorbell_attributes(name). \ - get('settings').get('doorbell_volume') - except AttributeError: - return NOT_FOUND + def __init__(self, ring, name): + """Initilize Ring doorbell object.""" + super(RingDoorBell, self).__init__() + self._attrs = None + self._ring = ring + self.debug = self._ring.debug + self.family = 'doorbots' + self.name = name + self.update() - def is_doorbell_online(self, name): - """Return state for doorbell is online.""" - try: - result = self.doorbell_attributes(name).get('subscribed') - if result is None: - return False - except AttributeError: - return NOT_FOUND - return True + @property + def battery_life(self): + """Return battery life.""" + value = int(self._attrs.get('battery_life')) + if value > 100: + value = 100 + return value - def is_doorbell_subscribed_motions(self, name): - """Return if doorbell is subscribed.""" - try: - result = self.doorbell_attributes(name).get('subscribed_motions') - if result is None: - return False - except AttributeError: - return NOT_FOUND - return True + @property + def check_activity(self): + """Return JSON when motion or ring is detected.""" + url = API_URI + DINGS_ENDPOINT + return self._ring.query(url) - def doorbell_battery_life(self, name): - """Return doorbell battery life.""" + @property + def existing_doorbell_type(self): + """ + Return existing doorbell type. + + 0: Mechanical + 1: Digital + 2: Not Present + """ try: - value = int(self.doorbell_attributes(name).get('battery_life')) - if value > 100: - value = 100 - return value + return DOORBELL_EXISTING_TYPE[ + self._attrs.get('settings').get('chime_settings').get('type')] except AttributeError: - return NOT_FOUND + return None + + @existing_doorbell_type.setter + def existing_doorbell_type(self, value): + """ + Return existing doorbell type. + + 0: Mechanical + 1: Digital + 2: Not Present + """ + if value not in DOORBELL_EXISTING_TYPE.keys(): + _LOGGER.error("%s", MSG_EXISTING_TYPE) + return False + params = { + 'doorbot[description]': self.name, + 'doorbot[settings][chime_settings][type]': value} + if self.existing_doorbell_type: + url = API_URI + DOORBELLS_ENDPOINT.format(self.account_id) + self._ring.query(url, extra_params=params, method='PUT') + self.update() + return True + return None - def doorbell_firmware(self, name): - """Return doorbell firmware.""" - try: - return self.doorbell_attributes(name).get('firmware_version') - except AttributeError: - return NOT_FOUND + @property + def existing_doorbell_type_enabled(self): + """Return if existing doorbell type is enabled.""" + if self.existing_doorbell_type: + if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[2]: + return None + return \ + self._attrs.get('settings').get('chime_settings').get('enable') + return False - def doorbell_timezone(self, name): - """Return doorbell timezone.""" - try: - return self.doorbell_attributes(name).get('time_zone') - except AttributeError: - return NOT_FOUND + @existing_doorbell_type_enabled.setter + def existing_doorbell_type_enabled(self, value): + """Enable/disable the existing doorbell if Digital/Mechanical.""" + if self.existing_doorbell_type: - def __live_streaming_create_session(self, name): - """Initiate session live streaming URL.""" - door_id = self.doorbell_id(name) - url = API_URI + LIVE_STREAMING_ENDPOINT.format(door_id) - req = self.session.post((url), params=urlencode(self._params)) - if req.status_code == 204: - return True - return False + if not isinstance(value, bool): + _LOGGER.error("%s", MSG_BOOLEAN_REQUIRED) + return None - def live_streaming(self, name): - """Return JSON for live streaming.""" - ret = self.__live_streaming_create_session(name) - if ret: - url = API_URI + DINGS_ENDPOINT - return self._query(url) + if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[2]: + return None + + params = { + 'doorbot[description]': self.name, + 'doorbot[settings][chime_settings][enable]': value} + url = API_URI + DOORBELLS_ENDPOINT.format(self.account_id) + self._ring.query(url, extra_params=params, method='PUT') + self.update() + return True return False @property - def check_activity(self): - """Return JSON when motion or ring is detected.""" - url = API_URI + DINGS_ENDPOINT - return self._query(url) + def existing_doorbell_type_duration(self): + """Return duration for Digital chime.""" + if self.existing_doorbell_type: + if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[1]: + return self._attrs.get('settings').\ + get('chime_settings').get('duration') + return None + + @existing_doorbell_type_duration.setter + def existing_doorbell_type_duration(self, value): + """Set duration for Digital chime.""" + if self.existing_doorbell_type: - def history(self, name=None, limit=30, timezone=None): + if not ((isinstance(value, int)) and + (value >= DOORBELL_VOL_MIN and value <= DOORBELL_VOL_MAX)): + _LOGGER.error("%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN, + DOORBELL_VOL_MAX)) + return False + + if self.existing_doorbell_type == DOORBELL_EXISTING_TYPE[1]: + params = { + 'doorbot[description]': self.name, + 'doorbot[settings][chime_settings][duration]': value} + url = API_URI + DOORBELLS_ENDPOINT.format(self.account_id) + self._ring.query(url, extra_params=params, method='PUT') + self.update() + return True + return None + + def history(self, limit=30, timezone=None): """Return history with datetime objects.""" # allow modify the items to return params = {'limit': str(limit)} - if name: - url = API_URI + URL_DOORBELL_HISTORY.format(self.doorbell_id(name)) - else: - url = API_URI + URL_HISTORY - - response = self._query(url, extra_params=params) + url = API_URI + URL_DOORBELL_HISTORY.format(self.account_id) + response = self._ring.query(url, extra_params=params) # convert for specific timezone utc = pytz.utc @@ -336,35 +465,67 @@ def history(self, name=None, limit=30, timezone=None): entry['created_at'] = utc_dt return response - def doorbell_recording(self, recording_id): - """Return recording in MP4 format.""" - url = API_URI + URL_RECORDING.format(recording_id) - req = self._query(url, raw=True) - if req.status_code == 200: - return req.content + @property + def last_recording_id(self): + """Return the last recording ID.""" + return self.history(limit=1)[0]['id'] + + @property + def live_streaming_json(self): + """Return JSON for live streaming.""" + url = API_URI + LIVE_STREAMING_ENDPOINT.format(self.account_id) + req = self._ring.query((url), method='POST', raw=True) + if req.status_code == 204: + url = API_URI + DINGS_ENDPOINT + return self._ring.query(url)[0] return None - def doorbell_download_recording(self, recording_id, - filename, override=False): - """Download and save recording in MP4 format to a file.""" + def recording_download(self, recording_id, filename=None, override=False): + """Save a recording in MP4 format to a file or return raw.""" + url = API_URI + URL_RECORDING.format(recording_id) try: - if os.path.isfile(filename) and not override: - _LOGGER.error("%s", FILE_EXISTS.format(filename)) - return False + req = self._ring.query(url, raw=True) + if req.status_code == 200: - with open(filename, 'wb') as recording: - mp4 = self.doorbell_recording(recording_id) - recording.write(mp4) + if filename: + if os.path.isfile(filename) and not override: + _LOGGER.error("%s", FILE_EXISTS.format(filename)) + return False + with open(filename, 'wb') as recording: + recording.write(req.content) + return True + else: + return req.content except IOError as error: _LOGGER.error("%s", error) - return False - return True + raise - def doorbell_recording_url(self, recording_id): + def recording_url(self, recording_id): """Return HTTPS recording URL.""" url = API_URI + URL_RECORDING.format(recording_id) - req = self._query(url, raw=True) + req = self._ring.query(url, raw=True) if req.status_code == 200: return req.url return False + + @property + def volume(self): + """Return volume.""" + return self._attrs.get('settings').get('doorbell_volume') + + @volume.setter + def volume(self, value): + if not ((isinstance(value, int)) and + (value >= DOORBELL_VOL_MIN and value <= DOORBELL_VOL_MAX)): + _LOGGER.error("%s", MSG_VOL_OUTBOUND.format(DOORBELL_VOL_MIN, + DOORBELL_VOL_MAX)) + return False + + params = { + 'doorbot[description]': self.name, + 'doorbot[settings][doorbell_volume]': str(value)} + url = API_URI + DOORBELLS_ENDPOINT.format(self.account_id) + self._ring.query(url, extra_params=params, method='PUT') + self.update() + return True diff --git a/ring_doorbell/const.py b/ring_doorbell/const.py index 658c8cd7..82317278 100644 --- a/ring_doorbell/const.py +++ b/ring_doorbell/const.py @@ -14,13 +14,16 @@ # API endpoints API_VERSION = '9' API_URI = 'https://api.ring.com' +CHIMES_ENDPOINT = '/clients_api/chimes/{0}' DEVICES_ENDPOINT = '/clients_api/ring_devices' DINGS_ENDPOINT = '/clients_api/dings/active' -LINKED_CHIMES_ENDPOINT = '/clients_api/chimes/{0}/linked_doorbots' -LIVE_STREAMING_ENDPOINT = '/clients_api/doorbots/{0}/vod' +DOORBELLS_ENDPOINT = '/clients_api/doorbots/{0}' + +LINKED_CHIMES_ENDPOINT = CHIMES_ENDPOINT + '/linked_doorbots' +LIVE_STREAMING_ENDPOINT = DOORBELLS_ENDPOINT + '/vod' NEW_SESSION_ENDPOINT = '/clients_api/session' -URL_HISTORY = '/clients_api/doorbots/history' -URL_DOORBELL_HISTORY = '/clients_api/doorbots/{0}/history' +TESTSOUND_CHIME_ENDPOINT = CHIMES_ENDPOINT + '/play_sound' +URL_DOORBELL_HISTORY = DOORBELLS_ENDPOINT + '/history' URL_RECORDING = '/clients_api/dings/{0}/recording' # structure acquired from reverse engineering to create auth token @@ -39,6 +42,23 @@ 'device[metadata][linphone_initialized]': 'true', 'device[metadata][language]': 'en'} + +# default values +CHIME_VOL_MIN = 0 +CHIME_VOL_MAX = 10 + +DOORBELL_VOL_MIN = 0 +DOORBELL_VOL_MAX = 11 + +DOORBELL_EXISTING_TYPE = { + 0: 'Mechanical', + 1: 'Digital', + 2: 'Not Present'} + + # error strings -GENERIC_FAIL = 'Sorry.. Something went wrong...' -FILE_EXISTS = 'The file {0} already exists' +MSG_BOOLEAN_REQUIRED = "Boolean value is required." +MSG_EXISTING_TYPE = "Integer value where {0}.".format(DOORBELL_EXISTING_TYPE) +MSG_GENERIC_FAIL = 'Sorry.. Something went wrong...' +FILE_EXISTS = 'The file {0} already exists.' +MSG_VOL_OUTBOUND = 'Must be within the {0}-{1}.' diff --git a/setup.py b/setup.py index f9d0bc02..2540eda4 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='ring_doorbell', packages=['ring_doorbell'], - version='0.0.4', + version='0.1.0', description='A Python library to communicate with Ring' + ' Door Bell (https://ring.com/)', author='Marcelo Moreira de Mello',