Introducing Ring Door Bell Camera (including StickUp cameras) and WiFi sensors (#9962)
* Extended Ring DoorBell to support camera playback and wifi sensors * Bump python-ringdoorbell to version 0.1.6 * Support to camera playback via ffmpeg * Extended ringdoorbell sensors to report WiFi attributes * Extended unittests * Makes lint happy * Added support to stickup cameras and fixed logic * Fixed unittests for stickup cameras * Makes lint happy * Refactored attributions and removed extra refresh method.
This commit is contained in:
parent
222cc4c393
commit
51a65ee8e9
12 changed files with 399 additions and 19 deletions
|
@ -272,6 +272,7 @@ omit =
|
||||||
homeassistant/components/camera/mjpeg.py
|
homeassistant/components/camera/mjpeg.py
|
||||||
homeassistant/components/camera/rpi_camera.py
|
homeassistant/components/camera/rpi_camera.py
|
||||||
homeassistant/components/camera/onvif.py
|
homeassistant/components/camera/onvif.py
|
||||||
|
homeassistant/components/camera/ring.py
|
||||||
homeassistant/components/camera/synology.py
|
homeassistant/components/camera/synology.py
|
||||||
homeassistant/components/camera/yi.py
|
homeassistant/components/camera/yi.py
|
||||||
homeassistant/components/climate/eq3btsmart.py
|
homeassistant/components/climate/eq3btsmart.py
|
||||||
|
|
|
@ -11,7 +11,7 @@ import voluptuous as vol
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
from homeassistant.components.ring import (
|
from homeassistant.components.ring import (
|
||||||
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
|
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING)
|
||||||
|
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
|
ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS)
|
||||||
|
@ -27,21 +27,21 @@ SCAN_INTERVAL = timedelta(seconds=5)
|
||||||
|
|
||||||
# Sensor types: Name, category, device_class
|
# Sensor types: Name, category, device_class
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'ding': ['Ding', ['doorbell'], 'occupancy'],
|
'ding': ['Ding', ['doorbell', 'stickup_cams'], 'occupancy'],
|
||||||
'motion': ['Motion', ['doorbell'], 'motion'],
|
'motion': ['Motion', ['doorbell', 'stickup_cams'], 'motion'],
|
||||||
}
|
}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
|
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
|
||||||
cv.string,
|
cv.string,
|
||||||
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
|
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up a sensor for a Ring device."""
|
"""Set up a sensor for a Ring device."""
|
||||||
ring = hass.data.get('ring')
|
ring = hass.data[DATA_RING]
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||||
|
@ -50,6 +50,12 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
sensors.append(RingBinarySensor(hass,
|
sensors.append(RingBinarySensor(hass,
|
||||||
device,
|
device,
|
||||||
sensor_type))
|
sensor_type))
|
||||||
|
|
||||||
|
for device in ring.stickup_cams:
|
||||||
|
if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]:
|
||||||
|
sensors.append(RingBinarySensor(hass,
|
||||||
|
device,
|
||||||
|
sensor_type))
|
||||||
add_devices(sensors, True)
|
add_devices(sensors, True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
141
homeassistant/components/camera/ring.py
Normal file
141
homeassistant/components/camera/ring.py
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
"""
|
||||||
|
This component provides support to the Ring Door Bell camera.
|
||||||
|
|
||||||
|
For more details about this platform, please refer to the documentation at
|
||||||
|
https://home-assistant.io/components/camera.ring/
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
from homeassistant.components.ring import DATA_RING, CONF_ATTRIBUTION
|
||||||
|
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||||
|
from homeassistant.components.ffmpeg import DATA_FFMPEG
|
||||||
|
from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
|
||||||
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
|
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
|
||||||
|
|
||||||
|
DEPENDENCIES = ['ring', 'ffmpeg']
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SCAN_INTERVAL = timedelta(seconds=90)
|
||||||
|
|
||||||
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
|
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
|
||||||
|
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
|
||||||
|
cv.time_period,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
|
||||||
|
"""Set up a Ring Door Bell and StickUp Camera."""
|
||||||
|
ring = hass.data[DATA_RING]
|
||||||
|
|
||||||
|
cams = []
|
||||||
|
for camera in ring.doorbells:
|
||||||
|
cams.append(RingCam(hass, camera, config))
|
||||||
|
|
||||||
|
for camera in ring.stickup_cams:
|
||||||
|
cams.append(RingCam(hass, camera, config))
|
||||||
|
|
||||||
|
async_add_devices(cams, True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class RingCam(Camera):
|
||||||
|
"""An implementation of a Ring Door Bell camera."""
|
||||||
|
|
||||||
|
def __init__(self, hass, camera, device_info):
|
||||||
|
"""Initialize a Ring Door Bell camera."""
|
||||||
|
super(RingCam, self).__init__()
|
||||||
|
self._camera = camera
|
||||||
|
self._hass = hass
|
||||||
|
self._name = self._camera.name
|
||||||
|
self._ffmpeg = hass.data[DATA_FFMPEG]
|
||||||
|
self._ffmpeg_arguments = device_info.get(CONF_FFMPEG_ARGUMENTS)
|
||||||
|
self._last_video_id = self._camera.last_recording_id
|
||||||
|
self._video_url = self._camera.recording_url(self._last_video_id)
|
||||||
|
self._expires_at = None
|
||||||
|
self._utcnow = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def name(self):
|
||||||
|
"""Return the name of this camera."""
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def device_state_attributes(self):
|
||||||
|
"""Return the state attributes."""
|
||||||
|
return {
|
||||||
|
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
|
||||||
|
'device_id': self._camera.id,
|
||||||
|
'firmware': self._camera.firmware,
|
||||||
|
'kind': self._camera.kind,
|
||||||
|
'timezone': self._camera.timezone,
|
||||||
|
'type': self._camera.family,
|
||||||
|
'video_url': self._video_url,
|
||||||
|
'video_id': self._last_video_id
|
||||||
|
}
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_camera_image(self):
|
||||||
|
"""Return a still image response from the camera."""
|
||||||
|
from haffmpeg import ImageFrame, IMAGE_JPEG
|
||||||
|
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
|
||||||
|
|
||||||
|
if self._video_url is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
image = yield from asyncio.shield(ffmpeg.get_image(
|
||||||
|
self._video_url, output_format=IMAGE_JPEG,
|
||||||
|
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
|
||||||
|
return image
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def handle_async_mjpeg_stream(self, request):
|
||||||
|
"""Generate an HTTP MJPEG stream from the camera."""
|
||||||
|
from haffmpeg import CameraMjpeg
|
||||||
|
|
||||||
|
if self._video_url is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||||
|
yield from stream.open_camera(
|
||||||
|
self._video_url, extra_cmd=self._ffmpeg_arguments)
|
||||||
|
|
||||||
|
yield from async_aiohttp_proxy_stream(
|
||||||
|
self.hass, request, stream,
|
||||||
|
'multipart/x-mixed-replace;boundary=ffserver')
|
||||||
|
yield from stream.close()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def should_poll(self):
|
||||||
|
"""Update the image periodically."""
|
||||||
|
return True
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
"""Update camera entity and refresh attributes."""
|
||||||
|
# extract the video expiration from URL
|
||||||
|
x_amz_expires = int(self._video_url.split('&')[0].split('=')[-1])
|
||||||
|
x_amz_date = self._video_url.split('&')[1].split('=')[-1]
|
||||||
|
|
||||||
|
self._utcnow = dt_util.utcnow()
|
||||||
|
self._expires_at = \
|
||||||
|
timedelta(seconds=x_amz_expires) + \
|
||||||
|
dt_util.as_utc(datetime.strptime(x_amz_date, "%Y%m%dT%H%M%SZ"))
|
||||||
|
|
||||||
|
if self._last_video_id != self._camera.last_recording_id:
|
||||||
|
_LOGGER.debug("Updated Ring DoorBell last_video_id")
|
||||||
|
self._last_video_id = self._camera.last_recording_id
|
||||||
|
|
||||||
|
if self._utcnow >= self._expires_at:
|
||||||
|
_LOGGER.debug("Updated Ring DoorBell video_url")
|
||||||
|
self._video_url = self._camera.recording_url(self._last_video_id)
|
|
@ -12,7 +12,7 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD
|
||||||
|
|
||||||
from requests.exceptions import HTTPError, ConnectTimeout
|
from requests.exceptions import HTTPError, ConnectTimeout
|
||||||
|
|
||||||
REQUIREMENTS = ['ring_doorbell==0.1.4']
|
REQUIREMENTS = ['ring_doorbell==0.1.6']
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ CONF_ATTRIBUTION = "Data provided by Ring.com"
|
||||||
NOTIFICATION_ID = 'ring_notification'
|
NOTIFICATION_ID = 'ring_notification'
|
||||||
NOTIFICATION_TITLE = 'Ring Sensor Setup'
|
NOTIFICATION_TITLE = 'Ring Sensor Setup'
|
||||||
|
|
||||||
|
DATA_RING = 'ring'
|
||||||
DOMAIN = 'ring'
|
DOMAIN = 'ring'
|
||||||
DEFAULT_CACHEDB = '.ring_cache.pickle'
|
DEFAULT_CACHEDB = '.ring_cache.pickle'
|
||||||
DEFAULT_ENTITY_NAMESPACE = 'ring'
|
DEFAULT_ENTITY_NAMESPACE = 'ring'
|
||||||
|
|
|
@ -11,7 +11,7 @@ import voluptuous as vol
|
||||||
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.components.ring import (
|
from homeassistant.components.ring import (
|
||||||
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE)
|
CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING)
|
||||||
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
from homeassistant.components.sensor import PLATFORM_SCHEMA
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS,
|
CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS,
|
||||||
|
@ -27,24 +27,43 @@ SCAN_INTERVAL = timedelta(seconds=30)
|
||||||
|
|
||||||
# Sensor types: Name, category, units, icon, kind
|
# Sensor types: Name, category, units, icon, kind
|
||||||
SENSOR_TYPES = {
|
SENSOR_TYPES = {
|
||||||
'battery': ['Battery', ['doorbell'], '%', 'battery-50', None],
|
'battery': [
|
||||||
'last_activity': ['Last Activity', ['doorbell'], None, 'history', None],
|
'Battery', ['doorbell', 'stickup_cams'], '%', 'battery-50', None],
|
||||||
'last_ding': ['Last Ding', ['doorbell'], None, 'history', 'ding'],
|
|
||||||
'last_motion': ['Last Motion', ['doorbell'], None, 'history', 'motion'],
|
'last_activity': [
|
||||||
'volume': ['Volume', ['chime', 'doorbell'], None, 'bell-ring', None],
|
'Last Activity', ['doorbell', 'stickup_cams'], None, 'history', None],
|
||||||
|
|
||||||
|
'last_ding': [
|
||||||
|
'Last Ding', ['doorbell', 'stickup_cams'], None, 'history', 'ding'],
|
||||||
|
|
||||||
|
'last_motion': [
|
||||||
|
'Last Motion', ['doorbell', 'stickup_cams'], None,
|
||||||
|
'history', 'motion'],
|
||||||
|
|
||||||
|
'volume': [
|
||||||
|
'Volume', ['chime', 'doorbell', 'stickup_cams'], None,
|
||||||
|
'bell-ring', None],
|
||||||
|
|
||||||
|
'wifi_signal_category': [
|
||||||
|
'WiFi Signal Category', ['chime', 'doorbell', 'stickup_cams'], None,
|
||||||
|
'wifi', None],
|
||||||
|
|
||||||
|
'wifi_signal_strength': [
|
||||||
|
'WiFi Signal Strength', ['chime', 'doorbell', 'stickup_cams'], 'dBm',
|
||||||
|
'wifi', None],
|
||||||
}
|
}
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||||
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
|
vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE):
|
||||||
cv.string,
|
cv.string,
|
||||||
vol.Required(CONF_MONITORED_CONDITIONS, default=[]):
|
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
|
||||||
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_devices, discovery_info=None):
|
def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
"""Set up a sensor for a Ring device."""
|
"""Set up a sensor for a Ring device."""
|
||||||
ring = hass.data.get('ring')
|
ring = hass.data[DATA_RING]
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
|
||||||
|
@ -56,6 +75,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
|
||||||
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
|
if 'doorbell' in SENSOR_TYPES[sensor_type][1]:
|
||||||
sensors.append(RingSensor(hass, device, sensor_type))
|
sensors.append(RingSensor(hass, device, sensor_type))
|
||||||
|
|
||||||
|
for device in ring.stickup_cams:
|
||||||
|
if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]:
|
||||||
|
sensors.append(RingSensor(hass, device, sensor_type))
|
||||||
|
|
||||||
add_devices(sensors, True)
|
add_devices(sensors, True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -97,6 +120,7 @@ class RingSensor(Entity):
|
||||||
attrs['kind'] = self._data.kind
|
attrs['kind'] = self._data.kind
|
||||||
attrs['timezone'] = self._data.timezone
|
attrs['timezone'] = self._data.timezone
|
||||||
attrs['type'] = self._data.family
|
attrs['type'] = self._data.family
|
||||||
|
attrs['wifi_name'] = self._data.wifi_name
|
||||||
|
|
||||||
if self._extra and self._sensor_type.startswith('last_'):
|
if self._extra and self._sensor_type.startswith('last_'):
|
||||||
attrs['created_at'] = self._extra['created_at']
|
attrs['created_at'] = self._extra['created_at']
|
||||||
|
@ -132,10 +156,18 @@ class RingSensor(Entity):
|
||||||
self._state = self._data.battery_life
|
self._state = self._data.battery_life
|
||||||
|
|
||||||
if self._sensor_type.startswith('last_'):
|
if self._sensor_type.startswith('last_'):
|
||||||
history = self._data.history(timezone=self._tz,
|
history = self._data.history(limit=5,
|
||||||
kind=self._kind)
|
timezone=self._tz,
|
||||||
|
kind=self._kind,
|
||||||
|
enforce_limit=True)
|
||||||
if history:
|
if history:
|
||||||
self._extra = history[0]
|
self._extra = history[0]
|
||||||
created_at = self._extra['created_at']
|
created_at = self._extra['created_at']
|
||||||
self._state = '{0:0>2}:{1:0>2}'.format(
|
self._state = '{0:0>2}:{1:0>2}'.format(
|
||||||
created_at.hour, created_at.minute)
|
created_at.hour, created_at.minute)
|
||||||
|
|
||||||
|
if self._sensor_type == 'wifi_signal_category':
|
||||||
|
self._state = self._data.wifi_signal_category
|
||||||
|
|
||||||
|
if self._sensor_type == 'wifi_signal_strength':
|
||||||
|
self._state = self._data.wifi_signal_strength
|
||||||
|
|
|
@ -900,7 +900,7 @@ restrictedpython==4.0a3
|
||||||
rflink==0.0.34
|
rflink==0.0.34
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring_doorbell==0.1.4
|
ring_doorbell==0.1.6
|
||||||
|
|
||||||
# homeassistant.components.notify.rocketchat
|
# homeassistant.components.notify.rocketchat
|
||||||
rocketchat-API==0.6.1
|
rocketchat-API==0.6.1
|
||||||
|
|
|
@ -130,7 +130,7 @@ restrictedpython==4.0a3
|
||||||
rflink==0.0.34
|
rflink==0.0.34
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring_doorbell==0.1.4
|
ring_doorbell==0.1.6
|
||||||
|
|
||||||
# homeassistant.components.media_player.yamaha
|
# homeassistant.components.media_player.yamaha
|
||||||
rxv==0.5.1
|
rxv==0.5.1
|
||||||
|
|
|
@ -50,6 +50,8 @@ class TestRingBinarySensorSetup(unittest.TestCase):
|
||||||
text=load_fixture('ring_devices.json'))
|
text=load_fixture('ring_devices.json'))
|
||||||
mock.get('https://api.ring.com/clients_api/dings/active',
|
mock.get('https://api.ring.com/clients_api/dings/active',
|
||||||
text=load_fixture('ring_ding_active.json'))
|
text=load_fixture('ring_ding_active.json'))
|
||||||
|
mock.get('https://api.ring.com/clients_api/doorbots/987652/health',
|
||||||
|
text=load_fixture('ring_doorboot_health_attrs.json'))
|
||||||
|
|
||||||
base_ring.setup(self.hass, VALID_CONFIG)
|
base_ring.setup(self.hass, VALID_CONFIG)
|
||||||
ring.setup_platform(self.hass,
|
ring.setup_platform(self.hass,
|
||||||
|
|
|
@ -38,7 +38,9 @@ class TestRingSensorSetup(unittest.TestCase):
|
||||||
'last_activity',
|
'last_activity',
|
||||||
'last_ding',
|
'last_ding',
|
||||||
'last_motion',
|
'last_motion',
|
||||||
'volume']
|
'volume',
|
||||||
|
'wifi_signal_category',
|
||||||
|
'wifi_signal_strength']
|
||||||
}
|
}
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
@ -55,6 +57,10 @@ class TestRingSensorSetup(unittest.TestCase):
|
||||||
text=load_fixture('ring_devices.json'))
|
text=load_fixture('ring_devices.json'))
|
||||||
mock.get('https://api.ring.com/clients_api/doorbots/987652/history',
|
mock.get('https://api.ring.com/clients_api/doorbots/987652/history',
|
||||||
text=load_fixture('ring_doorbots.json'))
|
text=load_fixture('ring_doorbots.json'))
|
||||||
|
mock.get('https://api.ring.com/clients_api/doorbots/987652/health',
|
||||||
|
text=load_fixture('ring_doorboot_health_attrs.json'))
|
||||||
|
mock.get('https://api.ring.com/clients_api/chimes/999999/health',
|
||||||
|
text=load_fixture('ring_chime_health_attrs.json'))
|
||||||
base_ring.setup(self.hass, VALID_CONFIG)
|
base_ring.setup(self.hass, VALID_CONFIG)
|
||||||
ring.setup_platform(self.hass,
|
ring.setup_platform(self.hass,
|
||||||
self.config,
|
self.config,
|
||||||
|
@ -63,6 +69,12 @@ class TestRingSensorSetup(unittest.TestCase):
|
||||||
|
|
||||||
for device in self.DEVICES:
|
for device in self.DEVICES:
|
||||||
device.update()
|
device.update()
|
||||||
|
if device.name == 'Front Battery':
|
||||||
|
self.assertEqual(80, device.state)
|
||||||
|
self.assertEqual('hp_cam_v1',
|
||||||
|
device.device_state_attributes['kind'])
|
||||||
|
self.assertEqual('stickup_cams',
|
||||||
|
device.device_state_attributes['type'])
|
||||||
if device.name == 'Front Door Battery':
|
if device.name == 'Front Door Battery':
|
||||||
self.assertEqual(100, device.state)
|
self.assertEqual(100, device.state)
|
||||||
self.assertEqual('lpd_v1',
|
self.assertEqual('lpd_v1',
|
||||||
|
@ -73,6 +85,8 @@ class TestRingSensorSetup(unittest.TestCase):
|
||||||
self.assertEqual(2, device.state)
|
self.assertEqual(2, device.state)
|
||||||
self.assertEqual('1.2.3',
|
self.assertEqual('1.2.3',
|
||||||
device.device_state_attributes['firmware'])
|
device.device_state_attributes['firmware'])
|
||||||
|
self.assertEqual('ring_mock_wifi',
|
||||||
|
device.device_state_attributes['wifi_name'])
|
||||||
self.assertEqual('mdi:bell-ring', device.icon)
|
self.assertEqual('mdi:bell-ring', device.icon)
|
||||||
self.assertEqual('chimes',
|
self.assertEqual('chimes',
|
||||||
device.device_state_attributes['type'])
|
device.device_state_attributes['type'])
|
||||||
|
@ -81,6 +95,15 @@ class TestRingSensorSetup(unittest.TestCase):
|
||||||
self.assertEqual('America/New_York',
|
self.assertEqual('America/New_York',
|
||||||
device.device_state_attributes['timezone'])
|
device.device_state_attributes['timezone'])
|
||||||
|
|
||||||
|
if device.name == 'Downstairs WiFi Signal Strength':
|
||||||
|
self.assertEqual(-39, device.state)
|
||||||
|
|
||||||
|
if device.name == 'Front Door WiFi Signal Category':
|
||||||
|
self.assertEqual('good', device.state)
|
||||||
|
|
||||||
|
if device.name == 'Front Door WiFi Signal Strength':
|
||||||
|
self.assertEqual(-58, device.state)
|
||||||
|
|
||||||
self.assertIsNone(device.entity_picture)
|
self.assertIsNone(device.entity_picture)
|
||||||
self.assertEqual(ATTRIBUTION,
|
self.assertEqual(ATTRIBUTION,
|
||||||
device.device_state_attributes['attribution'])
|
device.device_state_attributes['attribution'])
|
||||||
|
|
18
tests/fixtures/ring_chime_health_attrs.json
vendored
Normal file
18
tests/fixtures/ring_chime_health_attrs.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"device_health": {
|
||||||
|
"average_signal_category": "good",
|
||||||
|
"average_signal_strength": -39,
|
||||||
|
"battery_percentage": 100,
|
||||||
|
"battery_percentage_category": null,
|
||||||
|
"battery_voltage": null,
|
||||||
|
"battery_voltage_category": null,
|
||||||
|
"firmware": "1.2.3",
|
||||||
|
"firmware_out_of_date": false,
|
||||||
|
"id": 999999,
|
||||||
|
"latest_signal_category": "good",
|
||||||
|
"latest_signal_strength": -39,
|
||||||
|
"updated_at": "2017-09-30T07:05:03Z",
|
||||||
|
"wifi_is_ring_network": false,
|
||||||
|
"wifi_name": "ring_mock_wifi"
|
||||||
|
}
|
||||||
|
}
|
138
tests/fixtures/ring_devices.json
vendored
138
tests/fixtures/ring_devices.json
vendored
|
@ -75,5 +75,143 @@
|
||||||
"high"]},
|
"high"]},
|
||||||
"subscribed": true,
|
"subscribed": true,
|
||||||
"subscribed_motions": true,
|
"subscribed_motions": true,
|
||||||
|
"time_zone": "America/New_York"}],
|
||||||
|
"stickup_cams": [
|
||||||
|
{
|
||||||
|
"address": "123 Main St",
|
||||||
|
"alerts": {"connection": "online"},
|
||||||
|
"battery_life": 80,
|
||||||
|
"description": "Front",
|
||||||
|
"device_id": "aacdef123",
|
||||||
|
"external_connection": false,
|
||||||
|
"features": {
|
||||||
|
"advanced_motion_enabled": false,
|
||||||
|
"motion_message_enabled": false,
|
||||||
|
"motions_enabled": true,
|
||||||
|
"night_vision_enabled": false,
|
||||||
|
"people_only_enabled": false,
|
||||||
|
"shadow_correction_enabled": false,
|
||||||
|
"show_recordings": true},
|
||||||
|
"firmware_version": "1.9.3",
|
||||||
|
"id": 987652,
|
||||||
|
"kind": "hp_cam_v1",
|
||||||
|
"latitude": 12.000000,
|
||||||
|
"led_status": "off",
|
||||||
|
"location_id": null,
|
||||||
|
"longitude": -70.12345,
|
||||||
|
"motion_snooze": {"scheduled": true},
|
||||||
|
"night_mode_status": "false",
|
||||||
|
"owned": true,
|
||||||
|
"owner": {
|
||||||
|
"email": "foo@bar.org",
|
||||||
|
"first_name": "Foo",
|
||||||
|
"id": 999999,
|
||||||
|
"last_name": "Bar"},
|
||||||
|
"ring_cam_light_installed": "false",
|
||||||
|
"ring_id": null,
|
||||||
|
"settings": {
|
||||||
|
"chime_settings": {
|
||||||
|
"duration": 10,
|
||||||
|
"enable": true,
|
||||||
|
"type": 0},
|
||||||
|
"doorbell_volume": 11,
|
||||||
|
"enable_vod": true,
|
||||||
|
"floodlight_settings": {
|
||||||
|
"duration": 30,
|
||||||
|
"priority": 0},
|
||||||
|
"light_schedule_settings": {
|
||||||
|
"end_hour": 0,
|
||||||
|
"end_minute": 0,
|
||||||
|
"start_hour": 0,
|
||||||
|
"start_minute": 0},
|
||||||
|
"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"],
|
||||||
|
"motion_zones": {
|
||||||
|
"active_motion_filter": 1,
|
||||||
|
"advanced_object_settings": {
|
||||||
|
"human_detection_confidence": {
|
||||||
|
"day": 0.7,
|
||||||
|
"night": 0.7},
|
||||||
|
"motion_zone_overlap": {
|
||||||
|
"day": 0.1,
|
||||||
|
"night": 0.2},
|
||||||
|
"object_size_maximum": {
|
||||||
|
"day": 0.8,
|
||||||
|
"night": 0.8},
|
||||||
|
"object_size_minimum": {
|
||||||
|
"day": 0.03,
|
||||||
|
"night": 0.05},
|
||||||
|
"object_time_overlap": {
|
||||||
|
"day": 0.1,
|
||||||
|
"night": 0.6}
|
||||||
|
},
|
||||||
|
"enable_audio": false,
|
||||||
|
"pir_settings": {
|
||||||
|
"sensitivity1": 1,
|
||||||
|
"sensitivity2": 1,
|
||||||
|
"sensitivity3": 1,
|
||||||
|
"zone_mask": 6},
|
||||||
|
"sensitivity": 5,
|
||||||
|
"zone1": {
|
||||||
|
"name": "Zone 1",
|
||||||
|
"state": 2,
|
||||||
|
"vertex1": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex2": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex3": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex4": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex5": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex6": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex7": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex8": {"x": 0.0, "y": 0.0}},
|
||||||
|
"zone2": {
|
||||||
|
"name": "Zone 2",
|
||||||
|
"state": 2,
|
||||||
|
"vertex1": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex2": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex3": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex4": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex5": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex6": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex7": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex8": {"x": 0.0, "y": 0.0}},
|
||||||
|
"zone3": {
|
||||||
|
"name": "Zone 3",
|
||||||
|
"state": 2,
|
||||||
|
"vertex1": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex2": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex3": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex4": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex5": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex6": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex7": {"x": 0.0, "y": 0.0},
|
||||||
|
"vertex8": {"x": 0.0, "y": 0.0}}},
|
||||||
|
"pir_motion_zones": [0, 1, 1],
|
||||||
|
"pir_settings": {
|
||||||
|
"sensitivity1": 1,
|
||||||
|
"sensitivity2": 1,
|
||||||
|
"sensitivity3": 1,
|
||||||
|
"zone_mask": 6},
|
||||||
|
"stream_setting": 0,
|
||||||
|
"video_settings": {
|
||||||
|
"ae_level": 0,
|
||||||
|
"birton": null,
|
||||||
|
"brightness": 0,
|
||||||
|
"contrast": 64,
|
||||||
|
"saturation": 80}},
|
||||||
|
"siren_status": {"seconds_remaining": 0},
|
||||||
|
"stolen": false,
|
||||||
|
"subscribed": true,
|
||||||
|
"subscribed_motions": true,
|
||||||
"time_zone": "America/New_York"}]
|
"time_zone": "America/New_York"}]
|
||||||
}
|
}
|
||||||
|
|
18
tests/fixtures/ring_doorboot_health_attrs.json
vendored
Normal file
18
tests/fixtures/ring_doorboot_health_attrs.json
vendored
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"device_health": {
|
||||||
|
"average_signal_category": "good",
|
||||||
|
"average_signal_strength": -39,
|
||||||
|
"battery_percentage": 100,
|
||||||
|
"battery_percentage_category": null,
|
||||||
|
"battery_voltage": null,
|
||||||
|
"battery_voltage_category": null,
|
||||||
|
"firmware": "1.9.2",
|
||||||
|
"firmware_out_of_date": false,
|
||||||
|
"id": 987652,
|
||||||
|
"latest_signal_category": "good",
|
||||||
|
"latest_signal_strength": -58,
|
||||||
|
"updated_at": "2017-09-30T07:05:03Z",
|
||||||
|
"wifi_is_ring_network": false,
|
||||||
|
"wifi_name": "ring_mock_wifi"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue