From 0a586bd9197f681786575d9de73d62c192c5d7b4 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 17:50:36 +0000 Subject: [PATCH 1/7] Initial commit of pushbullet enhancement --- homeassistant/components/notify/__init__.py | 6 +- homeassistant/components/notify/pushbullet.py | 61 ++++++++++++++++--- 2 files changed, 58 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py index 44a0639c001..f3be1c87d19 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..d8c07c066fd 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -12,11 +12,13 @@ from homeassistant.components.notify import ATTR_TITLE, BaseNotificationService from homeassistant.const import CONF_API_KEY _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pushbullet.py==0.8.1'] +REQUIREMENTS = ['pushbullet.py==0.9.0'] +ATTR_TARGET='target' 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,7 +26,7 @@ def get_service(hass, config): return None try: - return PushBulletNotificationService(config[CONF_API_KEY]) + pb = PushBullet(config[CONF_API_KEY]) except InvalidKeyError: _LOGGER.error( @@ -32,19 +34,62 @@ def get_service(hass, config): "Get it at https://www.pushbullet.com/account") return None + return PushBulletNotificationService(pb) + # 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.refresh() - self.pushbullet = Pushbullet(api_key) + def refresh(self): + ''' Refresh devices, contacts, channels, etc ''' + self.pushbullet.refresh() - def send_message(self, message="", **kwargs): + self.pbtargets = { + 'devices' : + {target.nickname: target for target in self.pushbullet.devices}, + 'contacts' : + {target.email: target for target in self.pushbullet.contacts}, + 'channels' : + {target.channel_tag: target for target in self.pushbullet.channels}, + } + import pprint + _LOGGER.error(pprint.pformat(self.pbtargets)) + + def send_message(self, message=None, **kwargs): """ Send a message to a user. """ - + targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE) - self.pushbullet.push_note(title, message) + if targets: + # Make list if not so + if not isinstance(targets, list): + targets = [targets] + + # Main loop, Process all targets specified + for ttype,tname in [target.split('.') for target in targets]: + if ttype = 'device' and tname = '': + # Allow for 'normal' push, combined with other targets + self.pushbullet.push_note(None, message) + continue + + try: + self.pbtargets[ttype+'s'][tname].push_note(None, message) + except KeyError: + _LOGGER.error('No such target: %s.%s'%(ttype, tname)) + continue + except self.pushbullet.errors.PushError as e: + _LOGGER.error('Sending message failed to: %s.%s, %s'% + (ttype, tname, e)) + self.refresh() + continue + _LOGGER.info('Sent notification to: %s.%s'%(ttype, tname)) + + else: + # Backward compatebility, notify all devices in own account + self.pushbullet.push_note(None, message) + From 3a85bebbf65bb7e16178b3dab86962f211acc6c0 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 18:57:16 +0000 Subject: [PATCH 2/7] pushbullet; styling and minor fixed before PR --- homeassistant/components/notify/pushbullet.py | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index d8c07c066fd..a34d21408c3 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -13,9 +13,9 @@ from homeassistant.const import CONF_API_KEY _LOGGER = logging.getLogger(__name__) REQUIREMENTS = ['pushbullet.py==0.9.0'] -ATTR_TARGET='target' - +ATTR_TARGET = 'target' +# pylint: disable=unused-argument def get_service(hass, config): """ Get the PushBullet notification service. """ from pushbullet import PushBullet @@ -26,15 +26,14 @@ def get_service(hass, config): return None try: - pb = PushBullet(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(pb) + return PushBulletNotificationService(pushbullet) # pylint: disable=too-few-public-methods @@ -43,6 +42,7 @@ class PushBulletNotificationService(BaseNotificationService): def __init__(self, pb): self.pushbullet = pb + self.pbtargets = {} self.refresh() def refresh(self): @@ -50,20 +50,20 @@ class PushBulletNotificationService(BaseNotificationService): self.pushbullet.refresh() self.pbtargets = { - 'devices' : - {target.nickname: target for target in self.pushbullet.devices}, - 'contacts' : - {target.email: target for target in self.pushbullet.contacts}, - 'channels' : - {target.channel_tag: target for target in self.pushbullet.channels}, + 'devices': + {tgt.nickname: tgt for tgt in self.pushbullet.devices}, + 'contacts': + {tgt.email: tgt for tgt in self.pushbullet.contacts}, + 'channels': + {tgt.channel_tag: tgt for tgt in self.pushbullet.channels}, } - import pprint - _LOGGER.error(pprint.pformat(self.pbtargets)) def send_message(self, message=None, **kwargs): """ Send a message to a user. """ targets = kwargs.get(ATTR_TARGET) title = kwargs.get(ATTR_TITLE) + # Disabeling title + title = None if targets: # Make list if not so @@ -71,25 +71,25 @@ class PushBulletNotificationService(BaseNotificationService): targets = [targets] # Main loop, Process all targets specified - for ttype,tname in [target.split('.') for target in targets]: - if ttype = 'device' and tname = '': + for ttype, tname in [target.split('.') for target in targets]: + if ttype == 'device' and not tname: # Allow for 'normal' push, combined with other targets - self.pushbullet.push_note(None, message) + self.pushbullet.push_note(title, message) + _LOGGER.info('Sent notification to self') continue try: - self.pbtargets[ttype+'s'][tname].push_note(None, message) + self.pbtargets[ttype+'s'][tname].push_note(title, message) except KeyError: - _LOGGER.error('No such target: %s.%s'%(ttype, tname)) + _LOGGER.error('No such target: %s.%s', ttype, tname) continue - except self.pushbullet.errors.PushError as e: - _LOGGER.error('Sending message failed to: %s.%s, %s'% - (ttype, tname, e)) + except self.pushbullet.errors.PushError: + _LOGGER.error('Notify failed to: %s.%s', ttype, tname) self.refresh() continue - _LOGGER.info('Sent notification to: %s.%s'%(ttype, tname)) + _LOGGER.info('Sent notification to %s.%s', ttype, tname) else: # Backward compatebility, notify all devices in own account - self.pushbullet.push_note(None, message) - + self.pushbullet.push_note(title, message) + _LOGGER.info('Sent notification to self') From 0b0fd2490d10f368071364dbfbc96d34b2c0fa75 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 19:14:10 +0000 Subject: [PATCH 3/7] Pushbullet; styling, requirements, example --- homeassistant/components/notify/pushbullet.py | 7 ++++--- homeassistant/components/notify/services.yaml | 4 ++++ requirements_all.txt | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index a34d21408c3..9eea56894ae 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -8,12 +8,13 @@ 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.9.0'] -ATTR_TARGET = 'target' + # pylint: disable=unused-argument def get_service(hass, config): @@ -61,8 +62,8 @@ class PushBulletNotificationService(BaseNotificationService): def send_message(self, message=None, **kwargs): """ Send a message to a user. """ targets = kwargs.get(ATTR_TARGET) - title = kwargs.get(ATTR_TITLE) # Disabeling title + title = kwargs.get(ATTR_TITLE) title = None if targets: 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 571708ab873..efb2366cdb0 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 From 9b4650afd4cd05ec30de9a1e373c06edbc8d0551 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 20:49:42 +0000 Subject: [PATCH 4/7] Added comments --- homeassistant/components/notify/pushbullet.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 9eea56894ae..8ac2a88a216 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -47,9 +47,14 @@ class PushBulletNotificationService(BaseNotificationService): self.refresh() def refresh(self): - ''' Refresh devices, contacts, channels, etc ''' - self.pushbullet.refresh() + ''' + Refresh devices, contacts, channels, etc + 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 = { 'devices': {tgt.nickname: tgt for tgt in self.pushbullet.devices}, @@ -60,7 +65,11 @@ class PushBulletNotificationService(BaseNotificationService): } def send_message(self, message=None, **kwargs): - """ Send a message to a user. """ + """ + 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) # Disabeling title title = kwargs.get(ATTR_TITLE) @@ -79,6 +88,8 @@ class PushBulletNotificationService(BaseNotificationService): _LOGGER.info('Sent notification to self') continue + # Attempt push_note on a dict value. Keys are types & target + # name. The pbtargets have all *actual* targets. try: self.pbtargets[ttype+'s'][tname].push_note(title, message) except KeyError: From aee4411cfb5a287e72141a4104573afa8f70d125 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 22:44:51 +0000 Subject: [PATCH 5/7] . split only on first separator --- homeassistant/components/notify/pushbullet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 8ac2a88a216..019182ec486 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -81,7 +81,7 @@ class PushBulletNotificationService(BaseNotificationService): targets = [targets] # Main loop, Process all targets specified - for ttype, tname in [target.split('.') for target in targets]: + for ttype, tname in [target.split('.', 1) for target in targets]: if ttype == 'device' and not tname: # Allow for 'normal' push, combined with other targets self.pushbullet.push_note(title, message) From cc5dec3c59a8651c3aa297a3f58102d9f9e7b48b Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Sun, 15 Nov 2015 23:46:16 +0000 Subject: [PATCH 6/7] Processed feedback from PR comments --- homeassistant/components/notify/pushbullet.py | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 019182ec486..4f2a80021a7 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -56,11 +56,11 @@ class PushBulletNotificationService(BaseNotificationService): ''' self.pushbullet.refresh() self.pbtargets = { - 'devices': + 'device': {tgt.nickname: tgt for tgt in self.pushbullet.devices}, - 'contacts': + 'contact': {tgt.email: tgt for tgt in self.pushbullet.contacts}, - 'channels': + 'channel': {tgt.channel_tag: tgt for tgt in self.pushbullet.channels}, } @@ -71,37 +71,43 @@ class PushBulletNotificationService(BaseNotificationService): linked to the PB account. """ targets = kwargs.get(ATTR_TARGET) - # Disabeling title title = kwargs.get(ATTR_TITLE) - title = None + refreshed = False - if targets: - # Make list if not so - if not isinstance(targets, list): - targets = [targets] - - # Main loop, Process all targets specified - for ttype, tname in [target.split('.', 1) for target in targets]: - if ttype == 'device' and not tname: - # Allow for 'normal' push, combined with other targets - self.pushbullet.push_note(title, message) - _LOGGER.info('Sent notification to self') - continue - - # Attempt push_note on a dict value. Keys are types & target - # name. The pbtargets have all *actual* targets. - try: - self.pbtargets[ttype+'s'][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) - self.refresh() - continue - _LOGGER.info('Sent notification to %s.%s', ttype, tname) - - else: + 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 + + ttype, tname = target.split('/', 1) + + # Refresh if name not found. Poor mans refresh ;) + 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) From f4d832508486ccb1efebef546aa5bba6626b2879 Mon Sep 17 00:00:00 2001 From: Tom Duijf Date: Mon, 16 Nov 2015 00:29:04 +0000 Subject: [PATCH 7/7] Pushbullet; code cleanup & better errors on config typos --- homeassistant/components/notify/pushbullet.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/notify/pushbullet.py b/homeassistant/components/notify/pushbullet.py index 4f2a80021a7..9e9b941394e 100644 --- a/homeassistant/components/notify/pushbullet.py +++ b/homeassistant/components/notify/pushbullet.py @@ -93,9 +93,17 @@ class PushBulletNotificationService(BaseNotificationService): _LOGGER.info('Sent notification to self') continue - ttype, tname = target.split('/', 1) + try: + ttype, tname = target.split('/', 1) + except ValueError: + _LOGGER.error('Invalid target syntax: %s', target) + continue - # Refresh if name not found. Poor mans refresh ;) + # 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