-
-
Notifications
You must be signed in to change notification settings - Fork 37.8k
Add support for multiple Doorbird stations #13994
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
Changes from 21 commits
3a85104
2d9fb0a
332e6d1
c3907b0
21c5c38
0dfade4
a37e86e
98e7339
645bfac
ccac2c4
8fa3267
783b268
1780f30
08ec5e1
9aa0e05
e55556f
e485192
8966636
3e29719
1b19410
1ef8fff
334a3f0
d0e511e
26a93bd
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 |
|---|---|---|
|
|
@@ -4,14 +4,16 @@ | |
| For more details about this component, please refer to the documentation at | ||
| https://home-assistant.io/components/doorbird/ | ||
| """ | ||
| import asyncio | ||
| import logging | ||
|
|
||
| import asyncio | ||
| import voluptuous as vol | ||
|
|
||
| from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD | ||
| from homeassistant.components.http import HomeAssistantView | ||
| from homeassistant.const import CONF_HOST, CONF_USERNAME, \ | ||
| CONF_PASSWORD, CONF_NAME, CONF_DEVICES, CONF_MONITORED_CONDITIONS | ||
| import homeassistant.helpers.config_validation as cv | ||
| from homeassistant.util import slugify | ||
|
|
||
| REQUIREMENTS = ['DoorBirdPy==0.1.3'] | ||
|
|
||
|
|
@@ -24,60 +26,138 @@ | |
| CONF_DOORBELL_EVENTS = 'doorbell_events' | ||
| CONF_CUSTOM_URL = 'hass_url_override' | ||
|
|
||
| DOORBELL_EVENT = 'doorbell' | ||
| MOTION_EVENT = 'motionsensor' | ||
|
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. Could you call the event motion? I will approved and merge the PR afterwards.
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. That is the event name required by the device API for event subscriptions.
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. Got it. Sounds a bit weird.
Contributor
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. @oblogic7 That's only for |
||
|
|
||
| # 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, | ||
|
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. I would like to suggest this schema: 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
Contributor
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. This schema is better (extensible) |
||
| 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) | ||
| # Provide an endpoint for the doorstations to call to trigger events | ||
| hass.http.register_view(DoorbirdRequestView()) | ||
|
|
||
| doorstations = [] | ||
|
|
||
| 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() | ||
|
|
||
| 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 | ||
|
|
||
| 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 | ||
| # SETUP EVENT SUBSCRIBERS | ||
|
|
||
| if config[DOMAIN].get(CONF_DOORBELL_EVENTS): | ||
| # Provide an endpoint for the device to call to trigger events | ||
| hass.http.register_view(DoorbirdRequestView()) | ||
| # This will make HA the only service that receives events. | ||
| doorstation.device.reset_notifications() | ||
|
|
||
| # Subscribe to doorbell or motion events | ||
| subscribe_events(hass, doorstation) | ||
|
|
||
| hass.data[DOMAIN] = doorstations | ||
|
|
||
| return True | ||
|
|
||
| def subscribe_events(hass, doorstation): | ||
|
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. expected 2 blank lines, found 1 |
||
| """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 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) | ||
| # Override url if another is specified onth configuration | ||
| if doorstation.custom_url is not None: | ||
| hass_url = doorstation.custom_url | ||
|
|
||
| # 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) | ||
| slug = slugify(name) | ||
|
|
||
| return True | ||
| 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 ConfiguredDoorbird(): | ||
|
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. 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 DoorbirdRequestView(HomeAssistantView): | ||
|
|
@@ -93,5 +173,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' | ||
| 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 | ||
|
|
||
|
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 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" | ||
|
|
@@ -48,31 +47,36 @@ | |
|
|
||
| def setup_platform(hass, config, add_devices, discovery_info=None): | ||
| """Set up the DoorBird switch platform.""" | ||
| device = hass.data.get(DOORBIRD_DOMAIN) | ||
|
|
||
| switches = [] | ||
| for switch in SWITCHES: | ||
| _LOGGER.debug("Adding DoorBird switch %s", SWITCHES[switch]["name"]) | ||
| switches.append(DoorBirdSwitch(device, switch)) | ||
|
|
||
| for doorstation in hass.data[DOORBIRD_DOMAIN]: | ||
|
|
||
| device = doorstation.device | ||
|
|
||
| for switch in SWITCHES: | ||
|
|
||
| _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): | ||
|
|
||


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