-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
DoorBird Component #9281
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 Component #9281
Changes from 1 commit
80fa29c
db3bd1a
b2f2c9d
991b63e
4abe294
6dcb009
d4736af
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) | ||
|
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. Update. |
||
| add_devices([DoorBirdBinarySensor(device, "doorbell")], True) | ||
| return True | ||
|
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. 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 | ||
|
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. Init state as None. |
||
| super(DoorBirdBinarySensor, self).__init__() | ||
|
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. 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() | ||
|
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. no newline at end of file |
||
| 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 | ||
|
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. 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) | ||
|
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. Update according to the domain name import change above. |
||
|
|
||
| _LOGGER.debug("Adding DoorBird camera " + _CAMERA_LIVE) | ||
|
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. _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) | ||
|
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. _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 | ||
|
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. Remove. |
||
|
|
||
|
|
||
| class DoorBirdCamera(Camera): | ||
| """The camera on a DoorBird device.""" | ||
|
|
||
| def __init__(self, hass, url, name, interval=None): | ||
|
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. Don't pass in
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. What do I replace the references with to access that?
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. 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): | ||
|
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. 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 | ||
|
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. Don't overwrite if you don't support it. |
||
|
|
||
| def disable_motion_detection(self): | ||
| """Network callbacks are not supported by HA yet.""" | ||
| pass | ||
|
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. no newline at end of file |
||
| 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 | ||
|
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. no newline at end of file |
||
| 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 | ||
|
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. 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) | ||
|
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. Fix. See above. |
||
|
|
||
| switches = [] | ||
| for switch in SWITCHES: | ||
| if switch in config.get(CONF_SWITCHES): | ||
| _LOGGER.debug("Adding DoorBird switch " + | ||
|
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. _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 | ||
|
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. Platforms for |
||
|
|
||
|
|
||
| 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: | ||
|
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. 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] | ||
|
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. 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 | ||
|
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. Remove. |
||
|
|
||
| def turn_off(self, **kwargs): | ||
| """The relays are time-based and cannot be turned off.""" | ||
| return False | ||
|
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. 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 | ||
|
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. no newline at end of file |
||
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.
You're not allowed to overwrite the
DOMAINof 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: