Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ omit =

homeassistant/components/digital_ocean.py
homeassistant/components/*/digital_ocean.py

homeassistant/components/doorbird.py
homeassistant/components/*/doorbird.py

homeassistant/components/dweet.py
homeassistant/components/*/dweet.py
Expand Down
60 changes: 60 additions & 0 deletions homeassistant/components/binary_sensor/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Support for reading binary states from a DoorBird video doorbell."""
from datetime import timedelta
import logging

from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN
from homeassistant.util import Throttle

DEPENDENCIES = ['doorbird']

_LOGGER = logging.getLogger(__name__)
_MIN_UPDATE_INTERVAL = timedelta(milliseconds=250)

SENSOR_TYPES = {
"doorbell": {
"name": "Doorbell Ringing",
"icon": {
True: "bell-ring",
False: "bell",
None: "bell-outline"
}
}
}


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the DoorBird binary sensor component."""
device = hass.data.get(DOORBIRD_DOMAIN)
add_devices([DoorBirdBinarySensor(device, "doorbell")], True)


class DoorBirdBinarySensor(BinarySensorDevice):
"""A binary sensor of a DoorBird device."""

def __init__(self, device, sensor_type):
"""Initialize a binary sensor on a DoorBird device."""
self._device = device
self._sensor_type = sensor_type
self._state = None

@property
def name(self):
"""Get the name of the sensor."""
return SENSOR_TYPES[self._sensor_type]["name"]

@property
def icon(self):
"""Get an icon to display."""
state_icon = SENSOR_TYPES[self._sensor_type]["icon"][self._state]
return "mdi:{}".format(state_icon)

@property
def is_on(self):
"""Get the state of the binary sensor."""
return self._state

@Throttle(_MIN_UPDATE_INTERVAL)
def update(self):
"""Pull the latest value from the device."""
self._state = self._device.doorbell_state()
90 changes: 90 additions & 0 deletions homeassistant/components/camera/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Support for viewing the camera feed from a DoorBird video doorbell."""

import asyncio
import datetime
import logging
import voluptuous as vol

import aiohttp
import async_timeout

from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession

DEPENDENCIES = ['doorbird']

_CAMERA_LIVE = "DoorBird Live"
_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LOGGER = logging.getLogger(__name__)
_TIMEOUT = 10 # seconds

CONF_SHOW_LAST_VISITOR = 'last_visitor'

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_SHOW_LAST_VISITOR, default=False): cv.boolean
})


@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the DoorBird camera platform."""
device = hass.data.get(DOORBIRD_DOMAIN)

_LOGGER.debug("Adding DoorBird camera %s", _CAMERA_LIVE)
entities = [DoorBirdCamera(device.live_image_url, _CAMERA_LIVE,
_LIVE_INTERVAL)]

if config.get(CONF_SHOW_LAST_VISITOR):
_LOGGER.debug("Adding DoorBird camera %s", _CAMERA_LAST_VISITOR)
entities.append(DoorBirdCamera(device.history_image_url(1),
_CAMERA_LAST_VISITOR,
_LAST_VISITOR_INTERVAL))

async_add_devices(entities)
_LOGGER.info("Added DoorBird camera(s)")


class DoorBirdCamera(Camera):
"""The camera on a DoorBird device."""

def __init__(self, url, name, interval=None):
"""Initialize the camera on a DoorBird device."""
self._url = url
self._name = name
self._last_image = None
self._interval = interval or datetime.timedelta
self._last_update = datetime.datetime.min
super().__init__()

@property
def name(self):
"""Get the name of the camera."""
return self._name

@asyncio.coroutine
def async_camera_image(self):
"""Pull a still image from the camera."""
now = datetime.datetime.now()

if self._last_image and now - self._last_update < self._interval:
return self._last_image

try:
websession = async_get_clientsession(self.hass)

with async_timeout.timeout(_TIMEOUT, loop=self.hass.loop):
response = yield from websession.get(self._url)

self._last_image = yield from response.read()
self._last_update = now
return self._last_image
except asyncio.TimeoutError:
_LOGGER.error("Camera image timed out")
return self._last_image
except aiohttp.ClientError as error:
_LOGGER.error("Error getting camera image: %s", error)
return self._last_image
44 changes: 44 additions & 0 deletions homeassistant/components/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
"""Support for a DoorBird video doorbell."""

import logging
import voluptuous as vol

from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['DoorBirdPy==0.0.4']

_LOGGER = logging.getLogger(__name__)

DOMAIN = 'doorbird'

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string
})
}, extra=vol.ALLOW_EXTRA)


def setup(hass, config):
"""Set up the DoorBird component."""
device_ip = config[DOMAIN].get(CONF_HOST)
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)

from doorbirdpy import DoorBird
device = DoorBird(device_ip, username, password)
status = device.ready()

if status[0]:
_LOGGER.info("Connected to DoorBird at %s as %s", device_ip, username)
hass.data[DOMAIN] = device
return True
elif status[1] == 401:
_LOGGER.error("Authorization rejected by DoorBird at %s", device_ip)
return False
else:
_LOGGER.error("Could not connect to DoorBird at %s: Error %s",
device_ip, str(status[1]))
return False
97 changes: 97 additions & 0 deletions homeassistant/components/switch/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Support for powering relays in a DoorBird video doorbell."""
import datetime
import logging
import voluptuous as vol

from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN
from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
from homeassistant.const import CONF_SWITCHES
import homeassistant.helpers.config_validation as cv

DEPENDENCIES = ['doorbird']

_LOGGER = logging.getLogger(__name__)

SWITCHES = {
"open_door": {
"name": "Open Door",
"icon": {
True: "lock-open",
False: "lock"
},
"time": datetime.timedelta(seconds=3)
},
"light_on": {
"name": "Light On",
"icon": {
True: "lightbulb-on",
False: "lightbulb"
},
"time": datetime.timedelta(minutes=5)
}
}

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SWITCHES, default=[]):
vol.All(cv.ensure_list([vol.In(SWITCHES)]))
})


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the DoorBird switch platform."""
device = hass.data.get(DOORBIRD_DOMAIN)

switches = []
for switch in SWITCHES:
_LOGGER.debug("Adding DoorBird switch %s", SWITCHES[switch]["name"])
switches.append(DoorBirdSwitch(device, switch))

add_devices(switches)
_LOGGER.info("Added DoorBird switches")


class DoorBirdSwitch(SwitchDevice):
"""A relay in a DoorBird device."""

def __init__(self, device, switch):
"""Initialize a relay in a DoorBird device."""
self._device = device
self._switch = switch
self._state = False
self._assume_off = datetime.datetime.min

@property
def name(self):
"""Get the name of the switch."""
return SWITCHES[self._switch]["name"]

@property
def icon(self):
"""Get an icon to display."""
return "mdi:{}".format(SWITCHES[self._switch]["icon"][self._state])

@property
def is_on(self):
"""Get the assumed state of the relay."""
return self._state

def turn_on(self, **kwargs):
"""Power the relay."""
if self._switch == "open_door":
self._state = self._device.open_door()
elif self._switch == "light_on":
self._state = self._device.turn_light_on()

now = datetime.datetime.now()
self._assume_off = now + SWITCHES[self._switch]["time"]

def turn_off(self, **kwargs):
"""The relays are time-based."""
raise NotImplementedError("DoorBird relays cannot be manually turned "
"off.")

def update(self):
"""Wait for the correct amount of assumed time to pass."""
if self._state and self._assume_off <= datetime.datetime.now():
self._state = False
self._assume_off = datetime.datetime.min
3 changes: 3 additions & 0 deletions requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ astral==1.4
# homeassistant.components.bbb_gpio
# Adafruit_BBIO==1.0.0

# homeassistant.components.doorbird
DoorBirdPy==0.0.4

# homeassistant.components.isy994
PyISY==1.0.8

Expand Down