Skip to content
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
32 changes: 19 additions & 13 deletions homeassistant/components/camera/doorbird.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

DEPENDENCIES = ['doorbird']

_CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
_CAMERA_LIVE = "DoorBird Live"
_CAMERA_LAST_VISITOR = "{} Last Ring"
_CAMERA_LAST_MOTION = "{} Last Motion"
_CAMERA_LIVE = "{} Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1)
Expand All @@ -30,16 +30,22 @@
@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)
async_add_devices([
DoorBirdCamera(device.live_image_url, _CAMERA_LIVE, _LIVE_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR,
_LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION,
_LAST_MOTION_INTERVAL),
])
for doorstation in hass.data[DOORBIRD_DOMAIN]:
device = doorstation.device
async_add_devices([
DoorBirdCamera(
device.live_image_url,
_CAMERA_LIVE.format(doorstation.name),
_LIVE_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'doorbell'),
_CAMERA_LAST_VISITOR.format(doorstation.name),
_LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'),
_CAMERA_LAST_MOTION.format(doorstation.name),
_LAST_MOTION_INTERVAL),
])


class DoorBirdCamera(Camera):
Expand Down
177 changes: 131 additions & 46 deletions homeassistant/components/doorbird.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/doorbird/
"""
import asyncio
import logging

import asyncio

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 sort imports 🔤 within groups standard library, 3rd party and homeassistant. There should be a blank line between the three groups.

import voluptuous as vol
import homeassistant.helpers.config_validation as cv

from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
from homeassistant.components.http import HomeAssistantView
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_HOST, CONF_USERNAME, \
CONF_PASSWORD, CONF_NAME, CONF_DEVICES, CONF_MONITORED_CONDITIONS
from homeassistant.util import slugify

REQUIREMENTS = ['DoorBirdPy==0.1.3']

Expand All @@ -24,62 +25,144 @@
CONF_DOORBELL_EVENTS = 'doorbell_events'
CONF_CUSTOM_URL = 'hass_url_override'

DOORBELL_EVENT = 'doorbell'
MOTION_EVENT = 'motionsensor'

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.

Could you call the event motion? I will approved and merge the PR afterwards.

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.

That is the event name required by the device API for event subscriptions.

image

https://www.doorbird.com/downloads/api_lan.pdf?rev=0.20

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.

Got it. Sounds a bit weird.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@oblogic7 That's only for monitor.cgi. The new schedule.cgi uses 'motion'. No need to update this PR, I'm fixing it in mine 🙂

image


# Sensor types: Name, device_class, event
SENSOR_TYPES = {
'doorbell': ['Button', 'occupancy', DOORBELL_EVENT],
'motion': ['Motion', 'motion', MOTION_EVENT],
}

DEVICE_SCHEMA = vol.Schema({
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_CUSTOM_URL): cv.string,
vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_MONITORED_CONDITIONS, default=[]):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
})

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_HOST): cv.string,

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.

I would like to suggest this schema:

doorbird:
  gateways:
    - host: <host_ip>
      username: <username>
      password: <password>
      doorbell_events: 1
      name: Side Entry
    - host: <host_ip>
      username: <username>
      password: <password>
      doorbell_events: 1
      name: Front Door

In this case the schema can be extended in future with parameters applied to all gateways without another beaking change of the configuration.

cp. #13517

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This schema is better (extensible)
Although these are not really gateways I think. So gateways should be devices probably.

vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_DOORBELL_EVENTS): cv.boolean,
vol.Optional(CONF_CUSTOM_URL): cv.string,
})
vol.Required(CONF_DEVICES): vol.All(cv.ensure_list, [DEVICE_SCHEMA])
}),
}, extra=vol.ALLOW_EXTRA)

SENSOR_DOORBELL = 'doorbell'


def setup(hass, config):
"""Set up the DoorBird component."""
from doorbirdpy import DoorBird

device_ip = config[DOMAIN].get(CONF_HOST)
username = config[DOMAIN].get(CONF_USERNAME)
password = config[DOMAIN].get(CONF_PASSWORD)

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

if config[DOMAIN].get(CONF_DOORBELL_EVENTS):
# Provide an endpoint for the device to call to trigger events
hass.http.register_view(DoorbirdRequestView())

# Get the URL of this server
hass_url = hass.config.api.base_url

# Override it if another is specified in the component configuration
if config[DOMAIN].get(CONF_CUSTOM_URL):
hass_url = config[DOMAIN].get(CONF_CUSTOM_URL)
_LOGGER.info("DoorBird will connect to this instance via %s",
hass_url)
# Provide an endpoint for the doorstations to call to trigger events
hass.http.register_view(DoorbirdRequestView())

doorstations = []

# This will make HA the only service that gets doorbell events
url = '{}{}/{}'.format(hass_url, API_URL, SENSOR_DOORBELL)
device.reset_notifications()
device.subscribe_notification(SENSOR_DOORBELL, url)
for index, doorstation_config in enumerate(config[DOMAIN][CONF_DEVICES]):
device_ip = doorstation_config.get(CONF_HOST)
username = doorstation_config.get(CONF_USERNAME)
password = doorstation_config.get(CONF_PASSWORD)
custom_url = doorstation_config.get(CONF_CUSTOM_URL)
events = doorstation_config.get(CONF_MONITORED_CONDITIONS)
name = (doorstation_config.get(CONF_NAME)
or 'DoorBird {}'.format(index + 1))

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

if status[0]:
_LOGGER.info("Connected to DoorBird at %s as %s", device_ip,
username)
doorstation = ConfiguredDoorbird(device, name, events, custom_url)
doorstations.append(doorstation)
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

# SETUP EVENT SUBSCRIBERS

# This will make HA the only service that receives events.
doorstation.device.reset_notifications()

# Subscribe to doorbell or motion events
DoorbirdEventSubscriber(hass, doorstation)

hass.data[DOMAIN] = doorstations

return True


class ConfiguredDoorbird():

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

expected 2 blank lines, found 1

"""Attach additional information to pass along with configured device."""

def __init__(self, device, name, events=None, custom_url=None):
"""Initialize configured device."""
self._name = name
self._device = device
self._custom_url = custom_url
self._monitored_events = events

@property
def name(self):
"""Custom device name."""
return self._name

@property
def device(self):
"""The configured device."""
return self._device

@property
def custom_url(self):
"""Custom url for device."""
return self._custom_url

@property
def monitored_events(self):
"""Get monitored events."""
if self._monitored_events is None:
return []

return self._monitored_events


class DoorbirdEventSubscriber():

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 is this a class? Looks like you just need a function.

"""Doorbird event subscriber."""

def __init__(self, hass, doorstation):
"""Initialize the subscriber."""
for sensor_type in doorstation.monitored_events:
name = '{} {}'.format(doorstation.name,
SENSOR_TYPES[sensor_type][0])
event_type = SENSOR_TYPES[sensor_type][2]

# Get the URL of this server
hass_url = hass.config.api.base_url

# Override url if another is specified onth configuration
if doorstation.custom_url is not None:
hass_url = doorstation.custom_url

slug = slugify(name)

url = '{}{}/{}'.format(hass_url, API_URL, slug)

_LOGGER.info("DoorBird will connect to this instance via %s",
url)

_LOGGER.info("You may use the following event name for automations"
": %s_%s", DOMAIN, slug)

doorstation.device.subscribe_notification(event_type, url)


class DoorbirdRequestView(HomeAssistantView):
"""Provide a page for the device to call."""

Expand All @@ -93,5 +176,7 @@ class DoorbirdRequestView(HomeAssistantView):
def get(self, request, sensor):
"""Respond to requests from the device."""
hass = request.app['hass']

hass.bus.async_fire('{}_{}'.format(DOMAIN, sensor))

return 'OK'
29 changes: 16 additions & 13 deletions homeassistant/components/switch/doorbird.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
"""Support for powering relays in a DoorBird video doorbell."""
import datetime
import logging

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 remove blank line between standard library and 3rd party imports.

import voluptuous as vol

import homeassistant.helpers.config_validation as cv
from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN
from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
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",
"name": "{} Open Door",
"icon": {
True: "lock-open",
False: "lock"
},
"time": datetime.timedelta(seconds=3)
},
"open_door_2": {
"name": "Open Door 2",
"name": "{} Open Door 2",
"icon": {
True: "lock-open",
False: "lock"
},
"time": datetime.timedelta(seconds=3)
},
"light_on": {
"name": "Light On",
"name": "{} Light On",
"icon": {
True: "lightbulb-on",
False: "lightbulb"
Expand All @@ -48,31 +47,35 @@

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

device = doorstation.device

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.

Move this out of the loop. It's accessed outside the loop.

for switch in SWITCHES:

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

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


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

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

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

@property
def icon(self):
Expand Down