diff --git a/.coveragerc b/.coveragerc index 58bdac1ff31..bb82830bbbf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -169,6 +169,9 @@ omit = homeassistant/components/scsgate.py homeassistant/components/*/scsgate.py + + homeassistant/components/skybell.py + homeassistant/components/*/skybell.py homeassistant/components/tado.py homeassistant/components/*/tado.py diff --git a/homeassistant/components/binary_sensor/skybell.py b/homeassistant/components/binary_sensor/skybell.py new file mode 100644 index 00000000000..734f8e03375 --- /dev/null +++ b/homeassistant/components/binary_sensor/skybell.py @@ -0,0 +1,97 @@ +""" +Binary sensor support for the Skybell HD Doorbell. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/binary_sensor.skybell/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.binary_sensor import ( + BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.skybell import ( + DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) +from homeassistant.const import ( + CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['skybell'] + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=5) + +# Sensor types: Name, device_class, event +SENSOR_TYPES = { + 'button': ['Button', 'occupancy', 'device:sensor:button'], + 'motion': ['Motion', 'motion', 'device:sensor: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 the platform for a Skybell device.""" + skybell = hass.data.get(SKYBELL_DOMAIN) + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + for device in skybell.get_devices(): + sensors.append(SkybellBinarySensor(device, sensor_type)) + + add_devices(sensors, True) + + +class SkybellBinarySensor(SkybellDevice, BinarySensorDevice): + """A binary sensor implementation for Skybell devices.""" + + def __init__(self, device, sensor_type): + """Initialize a binary sensor for a Skybell device.""" + super().__init__(device) + self._sensor_type = sensor_type + self._name = "{0} {1}".format(self._device.name, + SENSOR_TYPES[self._sensor_type][0]) + self._device_class = SENSOR_TYPES[self._sensor_type][1] + self._event = {} + 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 = super().device_state_attributes + + attrs['event_date'] = self._event.get('createdAt') + + return attrs + + def update(self): + """Get the latest data and updates the state.""" + super().update() + + event = self._device.latest(SENSOR_TYPES[self._sensor_type][2]) + + self._state = bool(event and event.get('id') != self._event.get('id')) + + self._event = event diff --git a/homeassistant/components/camera/skybell.py b/homeassistant/components/camera/skybell.py new file mode 100644 index 00000000000..be3504dab78 --- /dev/null +++ b/homeassistant/components/camera/skybell.py @@ -0,0 +1,67 @@ +""" +Camera support for the Skybell HD Doorbell. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/camera.skybell/ +""" +from datetime import timedelta +import logging + +import requests + +from homeassistant.components.camera import Camera +from homeassistant.components.skybell import ( + DOMAIN as SKYBELL_DOMAIN, SkybellDevice) + +DEPENDENCIES = ['skybell'] + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=90) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the platform for a Skybell device.""" + skybell = hass.data.get(SKYBELL_DOMAIN) + + sensors = [] + for device in skybell.get_devices(): + sensors.append(SkybellCamera(device)) + + add_devices(sensors, True) + + +class SkybellCamera(SkybellDevice, Camera): + """A camera implementation for Skybell devices.""" + + def __init__(self, device): + """Initialize a camera for a Skybell device.""" + SkybellDevice.__init__(self, device) + Camera.__init__(self) + self._name = self._device.name + self._url = None + self._response = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + def camera_image(self): + """Get the latest camera image.""" + super().update() + + if self._url != self._device.image: + self._url = self._device.image + + try: + self._response = requests.get( + self._url, stream=True, timeout=10) + except requests.HTTPError as err: + _LOGGER.warning("Failed to get camera image: %s", err) + self._response = None + + if not self._response: + return None + + return self._response.content diff --git a/homeassistant/components/light/skybell.py b/homeassistant/components/light/skybell.py new file mode 100644 index 00000000000..012190023fa --- /dev/null +++ b/homeassistant/components/light/skybell.py @@ -0,0 +1,87 @@ +""" +Light/LED support for the Skybell HD Doorbell. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/light.skybell/ +""" +import logging + + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, ATTR_RGB_COLOR, + SUPPORT_BRIGHTNESS, SUPPORT_RGB_COLOR, Light) +from homeassistant.components.skybell import ( + DOMAIN as SKYBELL_DOMAIN, SkybellDevice) + +DEPENDENCIES = ['skybell'] + +_LOGGER = logging.getLogger(__name__) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the platform for a Skybell device.""" + skybell = hass.data.get(SKYBELL_DOMAIN) + + sensors = [] + for device in skybell.get_devices(): + sensors.append(SkybellLight(device)) + + add_devices(sensors, True) + + +def _to_skybell_level(level): + """Convert the given HASS light level (0-255) to Skybell (0-100).""" + return int((level * 100) / 255) + + +def _to_hass_level(level): + """Convert the given Skybell (0-100) light level to HASS (0-255).""" + return int((level * 255) / 100) + + +class SkybellLight(SkybellDevice, Light): + """A binary sensor implementation for Skybell devices.""" + + def __init__(self, device): + """Initialize a light for a Skybell device.""" + super().__init__(device) + self._name = self._device.name + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + def turn_on(self, **kwargs): + """Turn on the light.""" + if ATTR_RGB_COLOR in kwargs: + self._device.led_rgb = kwargs[ATTR_RGB_COLOR] + elif ATTR_BRIGHTNESS in kwargs: + self._device.led_intensity = _to_skybell_level( + kwargs[ATTR_BRIGHTNESS]) + else: + self._device.led_intensity = _to_skybell_level(255) + + def turn_off(self, **kwargs): + """Turn off the light.""" + self._device.led_intensity = 0 + + @property + def is_on(self): + """Return true if device is on.""" + return self._device.led_intensity > 0 + + @property + def brightness(self): + """Return the brightness of the light.""" + return _to_hass_level(self._device.led_intensity) + + @property + def rgb_color(self): + """Return the color of the light.""" + return self._device.led_rgb + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_BRIGHTNESS | SUPPORT_RGB_COLOR diff --git a/homeassistant/components/sensor/skybell.py b/homeassistant/components/sensor/skybell.py new file mode 100644 index 00000000000..dc7295f463a --- /dev/null +++ b/homeassistant/components/sensor/skybell.py @@ -0,0 +1,82 @@ +""" +Sensor support for Skybell Doorbells. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/sensor.skybell/ +""" +from datetime import timedelta +import logging + +import voluptuous as vol + +from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.skybell import ( + DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) +from homeassistant.const import ( + CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['skybell'] + +_LOGGER = logging.getLogger(__name__) + +SCAN_INTERVAL = timedelta(seconds=30) + +# Sensor types: Name, icon +SENSOR_TYPES = { + 'chime_level': ['Chime Level', 'bell-ring'], +} + +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 the platform for a Skybell device.""" + skybell = hass.data.get(SKYBELL_DOMAIN) + + sensors = [] + for sensor_type in config.get(CONF_MONITORED_CONDITIONS): + for device in skybell.get_devices(): + sensors.append(SkybellSensor(device, sensor_type)) + + add_devices(sensors, True) + + +class SkybellSensor(SkybellDevice): + """A sensor implementation for Skybell devices.""" + + def __init__(self, device, sensor_type): + """Initialize a sensor for a Skybell device.""" + super().__init__(device) + self._sensor_type = sensor_type + self._icon = 'mdi:{}'.format(SENSOR_TYPES[self._sensor_type][1]) + self._name = "{0} {1}".format(self._device.name, + SENSOR_TYPES[self._sensor_type][0]) + self._state = None + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + @property + def state(self): + """Return the state of the sensor.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon + + def update(self): + """Get the latest data and updates the state.""" + super().update() + + if self._sensor_type == 'chime_level': + self._state = self._device.outdoor_chime_level diff --git a/homeassistant/components/skybell.py b/homeassistant/components/skybell.py new file mode 100644 index 00000000000..854abdda7bc --- /dev/null +++ b/homeassistant/components/skybell.py @@ -0,0 +1,93 @@ +""" +Support for the Skybell HD Doorbell. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/skybell/ +""" +import logging + +from requests.exceptions import HTTPError, ConnectTimeout +import voluptuous as vol + +from homeassistant.const import ( + ATTR_ATTRIBUTION, CONF_USERNAME, CONF_PASSWORD) +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entity import Entity + +REQUIREMENTS = ['skybellpy==0.1.1'] + +_LOGGER = logging.getLogger(__name__) + +CONF_ATTRIBUTION = "Data provided by Skybell.com" + +NOTIFICATION_ID = 'skybell_notification' +NOTIFICATION_TITLE = 'Skybell Sensor Setup' + +DOMAIN = 'skybell' +DEFAULT_CACHEDB = './skybell_cache.pickle' +DEFAULT_ENTITY_NAMESPACE = 'skybell' + +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 the Skybell component.""" + conf = config[DOMAIN] + username = conf.get(CONF_USERNAME) + password = conf.get(CONF_PASSWORD) + + try: + from skybellpy import Skybell + + cache = hass.config.path(DEFAULT_CACHEDB) + skybell = Skybell(username=username, password=password, + get_devices=True, cache_path=cache) + + hass.data[DOMAIN] = skybell + except (ConnectTimeout, HTTPError) as ex: + _LOGGER.error("Unable to connect to Skybell service: %s", str(ex)) + hass.components.persistent_notification.create( + 'Error: {}
' + 'You will need to restart hass after fixing.' + ''.format(ex), + title=NOTIFICATION_TITLE, + notification_id=NOTIFICATION_ID) + return False + return True + + +class SkybellDevice(Entity): + """A HA implementation for Skybell devices.""" + + def __init__(self, device): + """Initialize a sensor for Skybell device.""" + self._device = device + + @property + def should_poll(self): + """Return the polling state.""" + return True + + def update(self): + """Update automation state.""" + self._device.refresh() + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return { + ATTR_ATTRIBUTION: CONF_ATTRIBUTION, + 'device_id': self._device.device_id, + 'status': self._device.status, + 'location': self._device.location, + 'wifi_ssid': self._device.wifi_ssid, + 'wifi_status': self._device.wifi_status, + 'last_check_in': self._device.last_check_in, + 'motion_threshold': self._device.motion_threshold, + 'video_profile': self._device.video_profile, + } diff --git a/homeassistant/components/switch/skybell.py b/homeassistant/components/switch/skybell.py new file mode 100644 index 00000000000..726a5e7446e --- /dev/null +++ b/homeassistant/components/switch/skybell.py @@ -0,0 +1,75 @@ +""" +Switch support for the Skybell HD Doorbell. + +For more details about this platform, please refer to the documentation at +https://home-assistant.io/components/switch.skybell/ +""" +import logging + +import voluptuous as vol + + +from homeassistant.components.skybell import ( + DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) +from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice +from homeassistant.const import ( + CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) +import homeassistant.helpers.config_validation as cv + +DEPENDENCIES = ['skybell'] + +_LOGGER = logging.getLogger(__name__) + +# Switch types: Name +SWITCH_TYPES = { + 'do_not_disturb': ['Do Not Disturb'], + 'motion_sensor': ['Motion Sensor'], +} + +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(SWITCH_TYPES)]), +}) + + +def setup_platform(hass, config, add_devices, discovery_info=None): + """Set up the platform for a Skybell device.""" + skybell = hass.data.get(SKYBELL_DOMAIN) + + sensors = [] + for switch_type in config.get(CONF_MONITORED_CONDITIONS): + for device in skybell.get_devices(): + sensors.append(SkybellSwitch(device, switch_type)) + + add_devices(sensors, True) + + +class SkybellSwitch(SkybellDevice, SwitchDevice): + """A switch implementation for Skybell devices.""" + + def __init__(self, device, switch_type): + """Initialize a light for a Skybell device.""" + super().__init__(device) + self._switch_type = switch_type + self._name = "{0} {1}".format(self._device.name, + SWITCH_TYPES[self._switch_type][0]) + + @property + def name(self): + """Return the name of the sensor.""" + return self._name + + def turn_on(self, **kwargs): + """Turn on the switch.""" + setattr(self._device, self._switch_type, True) + + def turn_off(self, **kwargs): + """Turn on the switch.""" + setattr(self._device, self._switch_type, False) + + @property + def is_on(self): + """Return true if device is on.""" + return getattr(self._device, self._switch_type) diff --git a/requirements_all.txt b/requirements_all.txt index 80563dd41e9..47c0c8ccf0e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -931,6 +931,9 @@ simplepush==1.1.3 # homeassistant.components.alarm_control_panel.simplisafe simplisafe-python==1.0.5 +# homeassistant.components.skybell +skybellpy==0.1.1 + # homeassistant.components.notify.slack slacker==0.9.60