diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index fdd5d1b5d3a..6cd7a2196cf 100644 --- a/homeassistant/components/notify/__init__.py +++ b/homeassistant/components/notify/__init__.py @@ -23,6 +23,9 @@ DEPENDENCIES = [] ATTR_TITLE = "title" ATTR_TITLE_DEFAULT = "Home Assistant" +# Target of the notification (user, device, etc) +ATTR_TARGET = 'target' + # Text to notify user of ATTR_MESSAGE = "message" @@ -69,8 +72,9 @@ def setup(hass, config): return title = call.data.get(ATTR_TITLE, ATTR_TITLE_DEFAULT) + target = call.data.get(ATTR_TARGET) - notify_service.send_message(message, title=title) + notify_service.send_message(message, title=title, target=target) # register service service_call_handler = partial(notify_message, notify_service) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 916e2a34003..9e9b941394e 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -8,15 +8,18 @@ https://home-assistant.io/components/notify.pushbullet/ """ import logging -from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService +from homeassistant.components.notify import ( + ATTR_TITLE, ATTR_TARGET, BaseNotificationService) from homeassistant.const import CONF_API_KEY _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pushbullet.py==0.8.1'] +REQUIREMENTS = ['pushbullet.py==0.9.0'] +# pylint: disable=unused-argument def get_service(hass, config): """ Get the PushBullet notification service. """ + from pushbullet import PushBullet from pushbullet import InvalidKeyError if CONF_API_KEY not in config: @@ -24,27 +27,95 @@ def get_service(hass, config): return None try: - return PushBulletNotificationService(config[CONF_API_KEY]) - + pushbullet = PushBullet(config[CONF_API_KEY]) except InvalidKeyError: _LOGGER.error( "Wrong API key supplied. " "Get it at https://www.pushbullet.com/account") return None + return PushBulletNotificationService(pushbullet) + # pylint: disable=too-few-public-methods class PushBulletNotificationService(BaseNotificationService): """ Implements notification service for Pushbullet. """ - def __init__(self, api_key): - from pushbullet import Pushbullet + def __init__(self, pb): + self.pushbullet = pb + self.pbtargets = {} + self.refresh() - self.pushbullet = Pushbullet(api_key) + def refresh(self): + ''' + Refresh devices, contacts, channels, etc - def send_message(self, message="", **kwargs): - """ Send a message to a user. """ + pbtargets stores all targets available from this pushbullet instance + into a dict. These are PB objects!. It sacrifices a bit of memory + for faster processing at send_message + ''' + self.pushbullet.refresh() + self.pbtargets = { + 'device': + {tgt.nickname: tgt for tgt in self.pushbullet.devices}, + 'contact': + {tgt.email: tgt for tgt in self.pushbullet.contacts}, + 'channel': + {tgt.channel_tag: tgt for tgt in self.pushbullet.channels}, + } + def send_message(self, message=None, **kwargs): + """ + Send a message to a specified target. + If no target specified, a 'normal' push will be sent to all devices + linked to the PB account. + """ + targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE) + refreshed = False - self.pushbullet.push_note(title, message) + if not targets: + # Backward compatebility, notify all devices in own account + self.pushbullet.push_note(title, message) + _LOGGER.info('Sent notification to self') + return + + # Make list if not so + if not isinstance(targets, list): + targets = [targets] + + # Main loop, Process all targets specified + for target in targets: + + # Allow for untargeted push, combined with other types + if target in ['device', 'device/']: + self.pushbullet.push_note(title, message) + _LOGGER.info('Sent notification to self') + continue + + try: + ttype, tname = target.split('/', 1) + except ValueError: + _LOGGER.error('Invalid target syntax: %s', target) + continue + + # Refresh if name not found. While awaiting periodic refresh + # solution in component, poor mans refresh ;) + if ttype not in self.pbtargets: + _LOGGER.error('Invalid target syntax: %s', target) + continue + if tname not in self.pbtargets[ttype] and not refreshed: + self.refresh() + refreshed = True + + # Attempt push_note on a dict value. Keys are types & target + # name. Dict pbtargets has all *actual* targets. + try: + self.pbtargets[ttype][tname].push_note(title, message) + except KeyError: + _LOGGER.error('No such target: %s.%s', ttype, tname) + continue + except self.pushbullet.errors.PushError: + _LOGGER.error('Notify failed to: %s.%s', ttype, tname) + continue + _LOGGER.info('Sent notification to %s.%s', ttype, tname) diff --git a/homeassistant/components/notify/services.yaml b/homeassistant/components/notify/services.yaml index 3bfdb6d4de3..3b2734e2674 100644 --- a/homeassistant/components/notify/services.yaml +++ b/homeassistant/components/notify/services.yaml @@ -9,3 +9,7 @@ notify: title: description: Optional title for your notification. example: 'Your Garage Door Friend' + + target: + description: Target of the notification. Optional depending on the platform + example: platform specific diff --git a/requirements_all.txt b/requirements_all.txt index 907c79823bc..ce6cbfabc96 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -29,7 +29,7 @@ tellcore-py==1.1.2 python-nmap==0.4.3 # PushBullet (notify.pushbullet) -pushbullet.py==0.8.1 +pushbullet.py==0.9.0 # Nest Thermostat (thermostat.nest) python-nest==2.6.0