-
-
Notifications
You must be signed in to change notification settings - Fork 37.6k
DoorBird #8871
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DoorBird #8871
Changes from all commits
39c6484
1d8575b
d7979ef
206904d
96313d2
44b1969
a7e70f1
648927e
4c0baf7
b141ab1
14b7fa0
a34a9b2
54483fb
3bafed3
a70ac13
5d381a3
e952b15
98e1fdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
| 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() | ||
| 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 |
| 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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 | ||
| 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=[]): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not make switches optional with
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If one was to be the default, I'd say |
||
| 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 | ||
There was a problem hiding this comment.
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.