diff --git a/homeassistant/components/binary_sensor/ring.py b/homeassistant/components/binary_sensor/ring.py
new file mode 100644
index 00000000000..429e92afa7f
--- /dev/null
+++ b/homeassistant/components/binary_sensor/ring.py
@@ -0,0 +1,109 @@
+"""
+This component provides HA sensor support for Ring Door Bell/Chimes.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/binary_sensor.ring/
+"""
+import logging
+from datetime import timedelta
+
+import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
+
+from homeassistant.components.ring import (
+ CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
+
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
+
+from homeassistant.components.binary_sensor import (
+ BinarySensorDevice, PLATFORM_SCHEMA)
+
+DEPENDENCIES = ['ring']
+
+_LOGGER = logging.getLogger(__name__)
+
+SCAN_INTERVAL = timedelta(seconds=5)
+
+# Sensor types: Name, category, device_class
+SENSOR_TYPES = {
+ 'ding': ['Ding', ['doorbell'], 'occupancy'],
+ 'motion': ['Motion', ['doorbell'], 'motion'],
+}
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
+ cv.string,
+ vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
+ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
+})
+
+
+def setup_platform(hass, config, add_devices, discovery_info=None):
+ """Set up a sensor for a Ring device."""
+ ring = hass.data.get('ring')
+
+ sensors = []
+ for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
+ for device in ring.doorbells:
+ if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
+ sensors.append(RingBinarySensor(hass,
+ device,
+ sensor_type))
+ add_devices(sensors, True)
+ return True
+
+
+class RingBinarySensor(BinarySensorDevice):
+ """A binary sensor implementation for Ring device."""
+
+ def __init__(self, hass, data, sensor_type):
+ """Initialize a sensor for Ring device."""
+ super(RingBinarySensor, self).__init__()
+ self._sensor_type = sensor_type
+ self._data = data
+ self._name = "{0} {1}".format(self._data.name,
+ SENSOR_TYPES.get(self._sensor_type)[0])
+ self._device_class = SENSOR_TYPES.get(self._sensor_type)[2]
+ self._state = None
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return True if the binary sensor is on."""
+ return self._state
+
+ @property
+ def device_class(self):
+ """Return the class of the binary sensor."""
+ return self._device_class
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ attrs = {}
+ attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION
+
+ attrs['device_id'] = self._data.id
+ attrs['firmware'] = self._data.firmware
+ attrs['timezone'] = self._data.timezone
+
+ if self._data.alert and self._data.alert_expires_at:
+ attrs['expires_at'] = self._data.alert_expires_at
+ attrs['state'] = self._data.alert.get('state')
+
+ return attrs
+
+ def update(self):
+ """Get the latest data and updates the state."""
+ self._data.check_alerts()
+
+ if self._data.alert:
+ self._state = (self._sensor_type ==
+ self._data.alert.get('kind'))
+ else:
+ self._state = False
diff --git a/homeassistant/components/ring.py b/homeassistant/components/ring.py
new file mode 100644
index 00000000000..61c772eced7
--- /dev/null
+++ b/homeassistant/components/ring.py
@@ -0,0 +1,63 @@
+"""
+Support for Ring Doorbell/Chimes.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/ring/
+"""
+from datetime import timedelta
+import logging
+import voluptuous as vol
+import homeassistant.helpers.config_validation as cv
+
+from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
+import homeassistant.loader as loader
+
+from requests.exceptions import HTTPError, ConnectTimeout
+
+REQUIREMENTS = ['ring_doorbell==0.1.3']
+
+_LOGGER = logging.getLogger(__name__)
+
+CONF_ATTRIBUTION = "Data provided by Ring.com"
+
+NOTIFICATION_ID = 'ring_notification'
+NOTIFICATION_TITLE = 'Ring Sensor Setup'
+
+DOMAIN = 'ring'
+DEFAULT_CACHEDB = '.ring_cache.pickle'
+DEFAULT_ENTITY_NAMESPACE = 'ring'
+DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up Ring component."""
+ conf = config[DOMAIN]
+ username = conf.get(CONF_USERNAME)
+ password = conf.get(CONF_PASSWORD)
+
+ persistent_notification = loader.get_component('persistent_notification')
+ try:
+ from ring_doorbell import Ring
+
+ cache = hass.config.path(DEFAULT_CACHEDB)
+ ring = Ring(username=username, password=password, cache_file=cache)
+ if not ring.is_connected:
+ return False
+ hass.data['ring'] = ring
+ except (ConnectTimeout, HTTPError) as ex:
+ _LOGGER.error("Unable to connect to Ring service: %s", str(ex))
+ persistent_notification.create(
+ hass, 'Error: {}
'
+ 'You will need to restart hass after fixing.'
+ ''.format(ex),
+ title=NOTIFICATION_TITLE,
+ notification_id=NOTIFICATION_ID)
+ return False
+ return True
diff --git a/homeassistant/components/sensor/ring.py b/homeassistant/components/sensor/ring.py
index 7c342a75f13..665fb167bcc 100644
--- a/homeassistant/components/sensor/ring.py
+++ b/homeassistant/components/sensor/ring.py
@@ -5,43 +5,32 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.ring/
"""
import logging
-from datetime import timedelta
import voluptuous as vol
-import homeassistant.loader as loader
import homeassistant.helpers.config_validation as cv
+from homeassistant.components.ring import (
+ CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DEFAULT_SCAN_INTERVAL)
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS, CONF_SCAN_INTERVAL,
- CONF_USERNAME, CONF_PASSWORD, STATE_UNKNOWN,
- ATTR_ATTRIBUTION)
+ STATE_UNKNOWN, ATTR_ATTRIBUTION)
from homeassistant.helpers.entity import Entity
-from requests.exceptions import HTTPError, ConnectTimeout
-
-REQUIREMENTS = ['ring_doorbell==0.1.0']
+DEPENDENCIES = ['ring']
_LOGGER = logging.getLogger(__name__)
-NOTIFICATION_ID = 'ring_notification'
-NOTIFICATION_TITLE = 'Ring Sensor Setup'
-
-DEFAULT_ENTITY_NAMESPACE = 'ring'
-DEFAULT_SCAN_INTERVAL = timedelta(seconds=30)
-
-CONF_ATTRIBUTION = "Data provided by Ring.com"
-
-# Sensor types: Name, category, units, icon
+# Sensor types: Name, category, units, icon, kind
SENSOR_TYPES = {
- 'battery': ['Battery', ['doorbell'], '%', 'battery-50'],
- 'last_activity': ['Last Activity', ['doorbell'], None, 'history'],
- 'volume': ['Volume', ['chime', 'doorbell'], None, 'bell-ring'],
+ 'battery': ['Battery', ['doorbell'], '%', 'battery-50', None],
+ 'last_activity': ['Last Activity', ['doorbell'], None, 'history', None],
+ 'last_ding': ['Last Ding', ['doorbell'], None, 'history', 'ding'],
+ 'last_motion': ['Last Motion', ['doorbell'], None, 'history', 'motion'],
+ 'volume': ['Volume', ['chime', 'doorbell'], None, 'bell-ring', None],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
@@ -53,22 +42,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up a sensor for a Ring device."""
- from ring_doorbell import Ring
-
- ring = Ring(config.get(CONF_USERNAME), config.get(CONF_PASSWORD))
-
- persistent_notification = loader.get_component('persistent_notification')
- try:
- ring.is_connected
- except (ConnectTimeout, HTTPError) as ex:
- _LOGGER.error("Unable to connect to Ring service: %s", str(ex))
- persistent_notification.create(
- hass, 'Error: {}
'
- 'You will need to restart hass after fixing.'
- ''.format(ex),
- title=NOTIFICATION_TITLE,
- notification_id=NOTIFICATION_ID)
- return False
+ ring = hass.data.get('ring')
sensors = []
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
@@ -98,6 +72,7 @@ class RingSensor(Entity):
self._data = data
self._extra = None
self._icon = 'mdi:{}'.format(SENSOR_TYPES.get(self._sensor_type)[3])
+ self._kind = SENSOR_TYPES.get(self._sensor_type)[4]
self._name = "{0} {1}".format(self._data.name,
SENSOR_TYPES.get(self._sensor_type)[0])
self._state = STATE_UNKNOWN
@@ -125,7 +100,7 @@ class RingSensor(Entity):
attrs['timezone'] = self._data.timezone
attrs['type'] = self._data.family
- if self._extra and self._sensor_type == 'last_activity':
+ if self._extra and self._sensor_type.startswith('last_'):
attrs['created_at'] = self._extra['created_at']
attrs['answered'] = self._extra['answered']
attrs['recording_status'] = self._extra['recording']['status']
@@ -153,8 +128,11 @@ class RingSensor(Entity):
if self._sensor_type == 'battery':
self._state = self._data.battery_life
- if self._sensor_type == 'last_activity':
- self._extra = self._data.history(limit=1, timezone=self._tz)[0]
- created_at = self._extra['created_at']
- self._state = '{0:0>2}:{1:0>2}'.format(created_at.hour,
- created_at.minute)
+ if self._sensor_type.startswith('last_'):
+ history = self._data.history(timezone=self._tz,
+ kind=self._kind)
+ if history:
+ self._extra = history[0]
+ created_at = self._extra['created_at']
+ self._state = '{0:0>2}:{1:0>2}'.format(created_at.hour,
+ created_at.minute)
diff --git a/requirements_all.txt b/requirements_all.txt
index cb2f9fdff5c..ac0d6a3752f 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -664,8 +664,8 @@ radiotherm==1.2
# homeassistant.components.rflink
rflink==0.0.31
-# homeassistant.components.sensor.ring
-ring_doorbell==0.1.0
+# homeassistant.components.ring
+ring_doorbell==0.1.3
# homeassistant.components.switch.rpi_rf
# rpi-rf==0.9.6
diff --git a/tests/components/binary_sensor/test_ring.py b/tests/components/binary_sensor/test_ring.py
new file mode 100644
index 00000000000..75c7aced369
--- /dev/null
+++ b/tests/components/binary_sensor/test_ring.py
@@ -0,0 +1,63 @@
+"""The tests for the Ring binary sensor platform."""
+import unittest
+import requests_mock
+
+from homeassistant.components.binary_sensor import ring
+from homeassistant.components import ring as base_ring
+
+from tests.components.test_ring import ATTRIBUTION, VALID_CONFIG
+from tests.common import get_test_home_assistant, load_fixture
+
+
+class TestRingBinarySensorSetup(unittest.TestCase):
+ """Test the Ring Binary Sensor platform."""
+
+ DEVICES = []
+
+ def add_devices(self, devices, action):
+ """Mock add devices."""
+ for device in devices:
+ self.DEVICES.append(device)
+
+ def setUp(self):
+ """Initialize values for this testcase class."""
+ self.hass = get_test_home_assistant()
+ self.config = {
+ 'username': 'foo',
+ 'password': 'bar',
+ 'monitored_conditions': ['ding', 'motion'],
+ }
+
+ def tearDown(self):
+ """Stop everything that was started."""
+ self.hass.stop()
+
+ @requests_mock.Mocker()
+ def test_binary_sensor(self, mock):
+ """Test the Ring sensor class and methods."""
+ mock.post('https://api.ring.com/clients_api/session',
+ text=load_fixture('ring_session.json'))
+ mock.get('https://api.ring.com/clients_api/ring_devices',
+ text=load_fixture('ring_devices.json'))
+ mock.get('https://api.ring.com/clients_api/dings/active',
+ text=load_fixture('ring_ding_active.json'))
+
+ base_ring.setup(self.hass, VALID_CONFIG)
+ ring.setup_platform(self.hass,
+ self.config,
+ self.add_devices,
+ None)
+
+ for device in self.DEVICES:
+ device.update()
+ if device.name == 'Front Door Ding':
+ self.assertEqual('on', device.state)
+ self.assertEqual('America/New_York',
+ device.device_state_attributes['timezone'])
+ elif device.name == 'Front Door Motion':
+ self.assertEqual('off', device.state)
+ self.assertEqual('motion', device.device_class)
+
+ self.assertIsNone(device.entity_picture)
+ self.assertEqual(ATTRIBUTION,
+ device.device_state_attributes['attribution'])
diff --git a/tests/components/sensor/test_ring.py b/tests/components/sensor/test_ring.py
index c7bf966a3e9..0ee72107413 100644
--- a/tests/components/sensor/test_ring.py
+++ b/tests/components/sensor/test_ring.py
@@ -1,171 +1,17 @@
"""The tests for the Ring sensor platform."""
import unittest
-from unittest import mock
+import requests_mock
from homeassistant.components.sensor import ring
-from tests.common import get_test_home_assistant
+from homeassistant.components import ring as base_ring
-VALID_CONFIG = {
- "platform": "ring",
- "username": "foo",
- "password": "bar",
- "monitored_conditions": [
- "battery", "last_activity", "volume"
- ]
-}
-
-ATTRIBUTION = 'Data provided by Ring.com'
+from tests.components.test_ring import ATTRIBUTION, VALID_CONFIG
+from tests.common import get_test_home_assistant, load_fixture
-def mocked_requests_get(*args, **kwargs):
- """Mock requests.get invocations."""
- class MockResponse:
- """Class to represent a mocked response."""
-
- def __init__(self, json_data, status_code):
- """Initialize the mock response class."""
- self.json_data = json_data
- self.status_code = status_code
-
- def json(self):
- """Return the json of the response."""
- return self.json_data
-
- if str(args[0]).startswith('https://api.ring.com/clients_api/session'):
- return MockResponse({
- "profile": {
- "authentication_token": "12345678910",
- "email": "foo@bar.org",
- "features": {
- "chime_dnd_enabled": False,
- "chime_pro_enabled": True,
- "delete_all_enabled": True,
- "delete_all_settings_enabled": False,
- "device_health_alerts_enabled": True,
- "floodlight_cam_enabled": True,
- "live_view_settings_enabled": True,
- "lpd_enabled": True,
- "lpd_motion_announcement_enabled": False,
- "multiple_calls_enabled": True,
- "multiple_delete_enabled": True,
- "nw_enabled": True,
- "nw_larger_area_enabled": False,
- "nw_user_activated": False,
- "owner_proactive_snoozing_enabled": True,
- "power_cable_enabled": False,
- "proactive_snoozing_enabled": False,
- "reactive_snoozing_enabled": False,
- "remote_logging_format_storing": False,
- "remote_logging_level": 1,
- "ringplus_enabled": True,
- "starred_events_enabled": True,
- "stickupcam_setup_enabled": True,
- "subscriptions_enabled": True,
- "ujet_enabled": False,
- "video_search_enabled": False,
- "vod_enabled": False},
- "first_name": "Home",
- "id": 999999,
- "last_name": "Assistant"}
- }, 201)
- elif str(args[0])\
- .startswith("https://api.ring.com/clients_api/ring_devices"):
- return MockResponse({
- "authorized_doorbots": [],
- "chimes": [
- {
- "address": "123 Main St",
- "alerts": {"connection": "online"},
- "description": "Downstairs",
- "device_id": "abcdef123",
- "do_not_disturb": {"seconds_left": 0},
- "features": {"ringtones_enabled": True},
- "firmware_version": "1.2.3",
- "id": 999999,
- "kind": "chime",
- "latitude": 12.000000,
- "longitude": -70.12345,
- "owned": True,
- "owner": {
- "email": "foo@bar.org",
- "first_name": "Marcelo",
- "id": 999999,
- "last_name": "Assistant"},
- "settings": {
- "ding_audio_id": None,
- "ding_audio_user_id": None,
- "motion_audio_id": None,
- "motion_audio_user_id": None,
- "volume": 2},
- "time_zone": "America/New_York"}],
- "doorbots": [
- {
- "address": "123 Main St",
- "alerts": {"connection": "online"},
- "battery_life": 4081,
- "description": "Front Door",
- "device_id": "aacdef123",
- "external_connection": False,
- "features": {
- "advanced_motion_enabled": False,
- "motion_message_enabled": False,
- "motions_enabled": True,
- "people_only_enabled": False,
- "shadow_correction_enabled": False,
- "show_recordings": True},
- "firmware_version": "1.4.26",
- "id": 987652,
- "kind": "lpd_v1",
- "latitude": 12.000000,
- "longitude": -70.12345,
- "motion_snooze": None,
- "owned": True,
- "owner": {
- "email": "foo@bar.org",
- "first_name": "Home",
- "id": 999999,
- "last_name": "Assistant"},
- "settings": {
- "chime_settings": {
- "duration": 3,
- "enable": True,
- "type": 0},
- "doorbell_volume": 1,
- "enable_vod": True,
- "live_view_preset_profile": "highest",
- "live_view_presets": [
- "low",
- "middle",
- "high",
- "highest"],
- "motion_announcement": False,
- "motion_snooze_preset_profile": "low",
- "motion_snooze_presets": [
- "none",
- "low",
- "medium",
- "high"]},
- "subscribed": True,
- "subscribed_motions": True,
- "time_zone": "America/New_York"}]
- }, 200)
- elif str(args[0]).startswith("https://api.ring.com/clients_api/doorbots"):
- return MockResponse([{
- "answered": False,
- "created_at": "2017-03-05T15:03:40.000Z",
- "events": [],
- "favorite": False,
- "id": 987654321,
- "kind": "motion",
- "recording": {"status": "ready"},
- "snapshot_url": ""
- }], 200)
-
-
-class TestRingSetup(unittest.TestCase):
+class TestRingSensorSetup(unittest.TestCase):
"""Test the Ring platform."""
- # pylint: disable=invalid-name
DEVICES = []
def add_devices(self, devices, action):
@@ -176,25 +22,35 @@ class TestRingSetup(unittest.TestCase):
def setUp(self):
"""Initialize values for this testcase class."""
self.hass = get_test_home_assistant()
- self.config = VALID_CONFIG
+ self.config = {
+ 'username': 'foo',
+ 'password': 'bar',
+ 'monitored_conditions': [
+ 'battery',
+ 'last_activity',
+ 'last_ding',
+ 'last_motion',
+ 'volume']
+ }
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
- @mock.patch('requests.Session.get', side_effect=mocked_requests_get)
- @mock.patch('requests.Session.post', side_effect=mocked_requests_get)
- def test_setup(self, get_mock, post_mock):
- """Test if component loaded successfully."""
- self.assertTrue(
- ring.setup_platform(self.hass, VALID_CONFIG,
- self.add_devices, None))
-
- @mock.patch('requests.Session.get', side_effect=mocked_requests_get)
- @mock.patch('requests.Session.post', side_effect=mocked_requests_get)
- def test_sensor(self, get_mock, post_mock):
- """Test the Ring sensor class and methods."""
- ring.setup_platform(self.hass, VALID_CONFIG, self.add_devices, None)
+ @requests_mock.Mocker()
+ def test_sensor(self, mock):
+ """Test the Ring senskor class and methods."""
+ mock.post('https://api.ring.com/clients_api/session',
+ text=load_fixture('ring_session.json'))
+ mock.get('https://api.ring.com/clients_api/ring_devices',
+ text=load_fixture('ring_devices.json'))
+ mock.get('https://api.ring.com/clients_api/doorbots/987652/history',
+ text=load_fixture('ring_doorbots.json'))
+ base_ring.setup(self.hass, VALID_CONFIG)
+ ring.setup_platform(self.hass,
+ self.config,
+ self.add_devices,
+ None)
for device in self.DEVICES:
device.update()
diff --git a/tests/components/test_ring.py b/tests/components/test_ring.py
new file mode 100644
index 00000000000..e10e5c20aea
--- /dev/null
+++ b/tests/components/test_ring.py
@@ -0,0 +1,56 @@
+"""The tests for the Ring component."""
+import unittest
+import requests_mock
+
+from homeassistant import setup
+import homeassistant.components.ring as ring
+
+from tests.common import get_test_home_assistant, load_fixture
+
+ATTRIBUTION = 'Data provided by Ring.com'
+
+VALID_CONFIG = {
+ "ring": {
+ "username": "foo",
+ "password": "bar",
+ }
+}
+
+
+class TestRing(unittest.TestCase):
+ """Tests the Ring component."""
+
+ def setUp(self):
+ """Initialize values for this test case class."""
+ self.hass = get_test_home_assistant()
+ self.config = VALID_CONFIG
+
+ def tearDown(self): # pylint: disable=invalid-name
+ """Stop everything that was started."""
+ self.hass.stop()
+
+ @requests_mock.Mocker()
+ def test_setup(self, mock):
+ """Test the setup."""
+ mock.post('https://api.ring.com/clients_api/session',
+ text=load_fixture('ring_session.json'))
+ response = ring.setup(self.hass, self.config)
+ self.assertTrue(response)
+
+ @requests_mock.Mocker()
+ def test_setup_component_no_login(self, mock):
+ """Test the setup when no login is configured."""
+ mock.post('https://api.ring.com/clients_api/session',
+ text=load_fixture('ring_session.json'))
+ conf = self.config.copy()
+ del conf['ring']['username']
+ assert not setup.setup_component(self.hass, ring.DOMAIN, conf)
+
+ @requests_mock.Mocker()
+ def test_setup_component_no_pwd(self, mock):
+ """Test the setup when no password is configured."""
+ mock.post('https://api.ring.com/clients_api/session',
+ text=load_fixture('ring_session.json'))
+ conf = self.config.copy()
+ del conf['ring']['password']
+ assert not setup.setup_component(self.hass, ring.DOMAIN, conf)
diff --git a/tests/fixtures/ring_devices.json b/tests/fixtures/ring_devices.json
new file mode 100644
index 00000000000..4d204ba5250
--- /dev/null
+++ b/tests/fixtures/ring_devices.json
@@ -0,0 +1,79 @@
+{
+ "authorized_doorbots": [],
+ "chimes": [
+ {
+ "address": "123 Main St",
+ "alerts": {"connection": "online"},
+ "description": "Downstairs",
+ "device_id": "abcdef123",
+ "do_not_disturb": {"seconds_left": 0},
+ "features": {"ringtones_enabled": true},
+ "firmware_version": "1.2.3",
+ "id": 999999,
+ "kind": "chime",
+ "latitude": 12.000000,
+ "longitude": -70.12345,
+ "owned": true,
+ "owner": {
+ "email": "foo@bar.org",
+ "first_name": "Marcelo",
+ "id": 999999,
+ "last_name": "Assistant"},
+ "settings": {
+ "ding_audio_id": null,
+ "ding_audio_user_id": null,
+ "motion_audio_id": null,
+ "motion_audio_user_id": null,
+ "volume": 2},
+ "time_zone": "America/New_York"}],
+ "doorbots": [
+ {
+ "address": "123 Main St",
+ "alerts": {"connection": "online"},
+ "battery_life": 4081,
+ "description": "Front Door",
+ "device_id": "aacdef123",
+ "external_connection": false,
+ "features": {
+ "advanced_motion_enabled": false,
+ "motion_message_enabled": false,
+ "motions_enabled": true,
+ "people_only_enabled": false,
+ "shadow_correction_enabled": false,
+ "show_recordings": true},
+ "firmware_version": "1.4.26",
+ "id": 987652,
+ "kind": "lpd_v1",
+ "latitude": 12.000000,
+ "longitude": -70.12345,
+ "motion_snooze": null,
+ "owned": true,
+ "owner": {
+ "email": "foo@bar.org",
+ "first_name": "Home",
+ "id": 999999,
+ "last_name": "Assistant"},
+ "settings": {
+ "chime_settings": {
+ "duration": 3,
+ "enable": true,
+ "type": 0},
+ "doorbell_volume": 1,
+ "enable_vod": true,
+ "live_view_preset_profile": "highest",
+ "live_view_presets": [
+ "low",
+ "middle",
+ "high",
+ "highest"],
+ "motion_announcement": false,
+ "motion_snooze_preset_profile": "low",
+ "motion_snooze_presets": [
+ "null",
+ "low",
+ "medium",
+ "high"]},
+ "subscribed": true,
+ "subscribed_motions": true,
+ "time_zone": "America/New_York"}]
+}
diff --git a/tests/fixtures/ring_ding_active.json b/tests/fixtures/ring_ding_active.json
new file mode 100644
index 00000000000..6bbcc0ee3f9
--- /dev/null
+++ b/tests/fixtures/ring_ding_active.json
@@ -0,0 +1,26 @@
+[{
+ "audio_jitter_buffer_ms": 0,
+ "device_kind": "lpd_v1",
+ "doorbot_description": "Front Door",
+ "doorbot_id": 12345,
+ "expires_in": 180,
+ "id": 123456789,
+ "id_str": "123456789",
+ "kind": "ding",
+ "motion": false,
+ "now": 1490949469.5498993,
+ "optimization_level": 1,
+ "protocol": "sip",
+ "sip_ding_id": "123456789",
+ "sip_endpoints": null,
+ "sip_from": "sip:abc123@ring.com",
+ "sip_server_ip": "192.168.0.1",
+ "sip_server_port": "15063",
+ "sip_server_tls": "false",
+ "sip_session_id": "28qdvjh-2043",
+ "sip_to": "sip:28qdvjh-2043@192.168.0.1:15063;transport=tcp",
+ "sip_token": "adecc24a428ed704b2d80adb621b5775755915529639e",
+ "snapshot_url": "",
+ "state": "ringing",
+ "video_jitter_buffer_ms": 0
+}]
diff --git a/tests/fixtures/ring_doorbots.json b/tests/fixtures/ring_doorbots.json
new file mode 100644
index 00000000000..7ec2d4fd0b7
--- /dev/null
+++ b/tests/fixtures/ring_doorbots.json
@@ -0,0 +1,10 @@
+[{
+ "answered": false,
+ "created_at": "2017-03-05T15:03:40.000Z",
+ "events": [],
+ "favorite": false,
+ "id": 987654321,
+ "kind": "motion",
+ "recording": {"status": "ready"},
+ "snapshot_url": ""
+}]
diff --git a/tests/fixtures/ring_session.json b/tests/fixtures/ring_session.json
new file mode 100644
index 00000000000..21ae51c6bf6
--- /dev/null
+++ b/tests/fixtures/ring_session.json
@@ -0,0 +1,36 @@
+{
+ "profile": {
+ "authentication_token": "12345678910",
+ "email": "foo@bar.org",
+ "features": {
+ "chime_dnd_enabled": false,
+ "chime_pro_enabled": true,
+ "delete_all_enabled": true,
+ "delete_all_settings_enabled": false,
+ "device_health_alerts_enabled": true,
+ "floodlight_cam_enabled": true,
+ "live_view_settings_enabled": true,
+ "lpd_enabled": true,
+ "lpd_motion_announcement_enabled": false,
+ "multiple_calls_enabled": true,
+ "multiple_delete_enabled": true,
+ "nw_enabled": true,
+ "nw_larger_area_enabled": false,
+ "nw_user_activated": false,
+ "owner_proactive_snoozing_enabled": true,
+ "power_cable_enabled": false,
+ "proactive_snoozing_enabled": false,
+ "reactive_snoozing_enabled": false,
+ "remote_logging_format_storing": false,
+ "remote_logging_level": 1,
+ "ringplus_enabled": true,
+ "starred_events_enabled": true,
+ "stickupcam_setup_enabled": true,
+ "subscriptions_enabled": true,
+ "ujet_enabled": false,
+ "video_search_enabled": false,
+ "vod_enabled": false},
+ "first_name": "Home",
+ "id": 999999,
+ "last_name": "Assistant"}
+}