Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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

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.

You're not allowed to overwrite the DOMAIN of a platform. That should always be the base component domain, eg 'binary_sensor' in this case. You can import the doorbird domain as another name:

from homeassistant.components.doorbird import DOMAIN as DOORBIRD_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.

Update.

add_devices([DoorBirdBinarySensor(device, "doorbell")], True)
return True

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.

Remove. Sorry I missed it before.



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

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.

Init state as None.

super(DoorBirdBinarySensor, self).__init__()

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.

Remove. There's no init methods in the parent classes.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

no newline at end of file

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

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.

Fix. See above.

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)

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.

Update according to the domain name import change above.


_LOGGER.debug("Adding DoorBird camera " + _CAMERA_LIVE)

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.

_LOGGER.debug("Adding DoorBird camera %s", _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)

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.

_LOGGER.debug("Adding DoorBird camera %s", _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

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.

Remove.



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

def __init__(self, hass, url, name, interval=None):

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.

Don't pass in hass. It will be set on the entity when it has been added to home assistant.

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.

What do I replace the references with to access that? self.hass?

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.

Yes!

"""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):

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.

Remove this. Since you implement the async version you don't need the sync version.

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

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.

Don't overwrite if you don't support it.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

no newline at end of file

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

no newline at end of file

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

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.

Fix. See above.

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

Fix. See above.


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

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.

_LOGGER.debug("Adding DoorBird switch %s", SWITCHES[switch]["name"])

SWITCHES[switch]["name"])
switches.append(DoorBirdSwitch(device, switch))

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

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.

Platforms for EntityComponent should not return anything.



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:

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.

You already validate this in the config schema.

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]

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.

Use string formatting:

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

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.

Remove.


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

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.

Raise not implemented error.


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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

no newline at end of file

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