diff --git a/homeassistant/components/ring/light.py b/homeassistant/components/ring/light.py new file mode 100644 index 00000000000..bd7ea3a3679 --- /dev/null +++ b/homeassistant/components/ring/light.py @@ -0,0 +1,97 @@ +"""This component provides HA switch support for Ring Door Bell/Chimes.""" +import logging +from datetime import datetime, timedelta +from homeassistant.components.light import Light +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.core import callback + +from . import DATA_RING_STICKUP_CAMS, SIGNAL_UPDATE_RING + +_LOGGER = logging.getLogger(__name__) + + +# It takes a few seconds for the API to correctly return an update indicating +# that the changes have been made. Once we request a change (i.e. a light +# being turned on) we simply wait for this time delta before we allow +# updates to take place. + +SKIP_UPDATES_DELAY = timedelta(seconds=5) + +ON_STATE = "on" +OFF_STATE = "off" + + +def setup_platform(hass, config, add_entities, discovery_info=None): + """Create the lights for the Ring devices.""" + cameras = hass.data[DATA_RING_STICKUP_CAMS] + lights = [] + + for device in cameras: + if device.has_capability("light"): + lights.append(RingLight(device)) + + add_entities(lights, True) + + +class RingLight(Light): + """Creates a switch to turn the ring cameras light on and off.""" + + def __init__(self, device): + """Initialize the light.""" + self._device = device + self._unique_id = self._device.id + self._light_on = False + self._no_updates_until = datetime.now() + + async def async_added_to_hass(self): + """Register callbacks.""" + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_RING, self._update_callback) + + @callback + def _update_callback(self): + """Call update method.""" + _LOGGER.debug("Updating Ring light %s (callback)", self.name) + self.async_schedule_update_ha_state(True) + + @property + def name(self): + """Name of the light.""" + return "{} light".format(self._device.name) + + @property + def unique_id(self): + """Return a unique ID.""" + return self._unique_id + + @property + def should_poll(self): + """Update controlled via the hub.""" + return False + + @property + def is_on(self): + """If the switch is currently on or off.""" + return self._light_on + + def _set_light(self, new_state): + """Update light state, and causes HASS to correctly update.""" + self._device.lights = new_state + self._light_on = new_state == ON_STATE + self._no_updates_until = datetime.now() + SKIP_UPDATES_DELAY + self.async_schedule_update_ha_state(True) + + def turn_on(self, **kwargs): + """Turn the light on for 30 seconds.""" + self._set_light(ON_STATE) + + def turn_off(self, **kwargs): + """Turn the light off.""" + self._set_light(OFF_STATE) + + def update(self): + """Update current state of the light.""" + if self._no_updates_until > datetime.now(): + _LOGGER.debug("Skipping update...") + return + + self._light_on = self._device.lights == ON_STATE diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py new file mode 100644 index 00000000000..e07867c19b2 --- /dev/null +++ b/tests/components/ring/test_light.py @@ -0,0 +1,75 @@ +"""The tests for the Ring light platform.""" +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from tests.common import load_fixture +from .common import setup_platform + + +async def test_entity_registry(hass, requests_mock): + """Tests that the devices are registed in the entity registry.""" + await setup_platform(hass, LIGHT_DOMAIN) + entity_registry = await hass.helpers.entity_registry.async_get_registry() + + entry = entity_registry.async_get("light.front_light") + assert entry.unique_id == "aacdef123" + + entry = entity_registry.async_get("light.internal_light") + assert entry.unique_id == "aacdef124" + + +async def test_light_off_reports_correctly(hass, requests_mock): + """Tests that the initial state of a device that should be off is correct.""" + await setup_platform(hass, LIGHT_DOMAIN) + + state = hass.states.get("light.front_light") + assert state.state == "off" + assert state.attributes.get("friendly_name") == "Front light" + + +async def test_light_on_reports_correctly(hass, requests_mock): + """Tests that the initial state of a device that should be on is correct.""" + await setup_platform(hass, LIGHT_DOMAIN) + + state = hass.states.get("light.internal_light") + assert state.state == "on" + assert state.attributes.get("friendly_name") == "Internal light" + + +async def test_light_can_be_turned_on(hass, requests_mock): + """Tests the light turns on correctly.""" + await setup_platform(hass, LIGHT_DOMAIN) + + # Mocks the response for turning a light on + requests_mock.put( + "https://api.ring.com/clients_api/doorbots/987652/floodlight_light_on", + text=load_fixture("ring_doorbot_siren_on_response.json"), + ) + + state = hass.states.get("light.front_light") + assert state.state == "off" + + await hass.services.async_call( + "light", "turn_on", {"entity_id": "light.front_light"}, blocking=True + ) + await hass.async_block_till_done() + + state = hass.states.get("light.front_light") + assert state.state == "on" + + +async def test_updates_work(hass, requests_mock): + """Tests the update service works correctly.""" + await setup_platform(hass, LIGHT_DOMAIN) + state = hass.states.get("light.front_light") + assert state.state == "off" + # Changes the return to indicate that the light is now on. + requests_mock.get( + "https://api.ring.com/clients_api/ring_devices", + text=load_fixture("ring_devices_updated.json"), + ) + + await hass.services.async_call("ring", "update", {}, blocking=True) + + await hass.async_block_till_done() + + state = hass.states.get("light.front_light") + assert state.state == "on" diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 59f4e9061f4..864d16466da 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -52,6 +52,7 @@ async def test_siren_can_be_turned_on(hass, requests_mock): "switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True ) + await hass.async_block_till_done() state = hass.states.get("switch.front_siren") assert state.state == "on" diff --git a/tests/fixtures/ring_devices.json b/tests/fixtures/ring_devices.json index f5dd1c1f091..557aef3535c 100644 --- a/tests/fixtures/ring_devices.json +++ b/tests/fixtures/ring_devices.json @@ -234,7 +234,7 @@ "id": 987652, "kind": "hp_cam_v1", "latitude": 12.000000, - "led_status": "off", + "led_status": "on", "location_id": null, "longitude": -70.12345, "motion_snooze": {"scheduled": true}, diff --git a/tests/fixtures/ring_devices_updated.json b/tests/fixtures/ring_devices_updated.json index bc621f81be8..fa3c0586101 100644 --- a/tests/fixtures/ring_devices_updated.json +++ b/tests/fixtures/ring_devices_updated.json @@ -96,7 +96,7 @@ "id": 987652, "kind": "hp_cam_v1", "latitude": 12.000000, - "led_status": "off", + "led_status": "on", "location_id": null, "longitude": -70.12345, "motion_snooze": {"scheduled": true},