Skip to content
Closed
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 @@ -47,6 +47,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
62 changes: 62 additions & 0 deletions homeassistant/components/binary_sensor/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""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
from homeassistant.const import STATE_UNKNOWN
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",
STATE_UNKNOWN: "bell-outline"
}
}
}


def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the DoorBird binary sensor component."""
device = hass.data.get(DOMAIN)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment on the component about this.

add_devices([DoorBirdBinarySensor(device, "doorbell")], True)
return 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 = STATE_UNKNOWN
super(DoorBirdBinarySensor, self).__init__()

@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."""
icon = SENSOR_TYPES[self._sensor_type]["icon"][self._state]
return "mdi:{}".format(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()
106 changes: 106 additions & 0 deletions homeassistant/components/camera/doorbird.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""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
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.async import run_coroutine_threadsafe

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(DOMAIN)

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

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

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


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

def __init__(self, hass, url, name, interval=None):
"""Initialize the camera on a DoorBird device."""
self._hass = hass
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

def camera_image(self):
"""Get the bytes of a camera image."""
return run_coroutine_threadsafe(
self.async_camera_image(), self._hass.loop).result()

@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

def enable_motion_detection(self):
"""Network callbacks are not supported by HA yet."""
pass

def disable_motion_detection(self):
"""Network callbacks are not supported by HA yet."""
pass
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."""
ip = config[DOMAIN].get(CONF_HOST)
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)

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

if status[0]:
_LOGGER.info("Connected to DoorBird at %s as %s", ip, username)
hass.data[DOMAIN] = device
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use hass.data[DATA_YOUR_DOMAIN] instead hass.data[DOMAIN].

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this means. Do I define a new constant called DATA_DOORBIRD and use that here? But what would its value be? I'm using the Ring component as an example, and this is what is used there.

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

from homeassistant.components.doorbird import 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=[]):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make switches optional with open_door as default? This seems to be one of the core features of DoorBird. Or is this just one of the two built-in relays?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If one was to be the default, I'd say light_on would be a better choice. It turns on the IR array for night vision, which all DoorBird devices include. The open_door switch is only useful if you have an electronic door strike installed.

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(DOMAIN)

switches = []
for switch in SWITCHES:
if switch in config.get(CONF_SWITCHES):
_LOGGER.debug("Adding DoorBird switch " +
SWITCHES[switch]["name"])
switches.append(DoorBirdSwitch(device, switch))

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


class DoorBirdSwitch(SwitchDevice):
"""A relay in a DoorBird device."""
def __init__(self, device, switch):
"""Initialize a relay in a DoorBird device."""
if switch not in SWITCHES:
msg = switch + " is not a valid DoorBird switch"
raise NotImplementedError(msg)

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:" + 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"]
return True

def turn_off(self, **kwargs):
"""The relays are time-based and cannot be turned off."""
return False

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.7

Expand Down