2

I am using Azure notification hub to send push notifications from Raspberry pi to android application.

I am using python client app using REST API as per this post How to use Notification Hubs from Python

Everything works fine but there is, almost 2 seconds of delay in every notification from Raspberry pi to Android application!! How to avoid this delay and get the notification on Android device within same second.

My python script

import time
import hmac
import base64
import hashlib
import json
import urllib.parse
import http.client


class Notification:
    def __init__(self, notification_format=None, payload=None, debug=0):
        valid_formats = ['template', 'apple', 'gcm', 'windows', 'windowsphone', "adm", "baidu"]
        if not any(x in notification_format for x in valid_formats):
            raise Exception(
                "Invalid Notification format. " +
                "Must be one of the following - 'template', 'apple', 'gcm', 'windows', 'windowsphone', 'adm', 'baidu'")

        self.format = notification_format
        self.payload = payload

        # array with keynames for headers
        # Note: Some headers are mandatory: Windows: X-WNS-Type, WindowsPhone: X-NotificationType
        # Note: For Apple you can set Expiry with header: ServiceBusNotification-ApnsExpiry
        # in W3C DTF, YYYY-MM-DDThh:mmTZD (for example, 1997-07-16T19:20+01:00).
        self.headers = None


class NotificationHub:
    API_VERSION = "?api-version=2013-10"
    DEBUG_SEND = "&test"

    def __init__(self, connection_string=None, hub_name=None, debug=0):
        self.HubName = hub_name
        self.Debug = debug

        # Parse connection string
        parts = connection_string.split(';')
        if len(parts) != 3:
            raise Exception("Invalid ConnectionString.")

        for part in parts:
            if part.startswith('Endpoint'):
                self.Endpoint = 'https' + part[11:]
            if part.startswith('SharedAccessKeyName'):
                self.SasKeyName = part[20:]
            if part.startswith('SharedAccessKey'):
                self.SasKeyValue = part[16:]

    @staticmethod
    def get_expiry():
        # By default returns an expiration of 5 minutes (=300 seconds) from now
        return int(round(time.time() + 300))

    @staticmethod
    def encode_base64(data):
        return base64.b64encode(data)

    def sign_string(self, to_sign):
        key = self.SasKeyValue.encode('utf-8')
        to_sign = to_sign.encode('utf-8')
        signed_hmac_sha256 = hmac.HMAC(key, to_sign, hashlib.sha256)
        digest = signed_hmac_sha256.digest()
        encoded_digest = self.encode_base64(digest)
        return encoded_digest

    def generate_sas_token(self):
        target_uri = self.Endpoint + self.HubName
        my_uri = urllib.parse.quote(target_uri, '').lower()
        expiry = str(self.get_expiry())
        to_sign = my_uri + '\n' + expiry
        signature = urllib.parse.quote(self.sign_string(to_sign))
        auth_format = 'SharedAccessSignature sig={0}&se={1}&skn={2}&sr={3}'
        sas_token = auth_format.format(signature, expiry, self.SasKeyName, my_uri)
        return sas_token

    def make_http_request(self, url, payload, headers):
        parsed_url = urllib.parse.urlparse(url)
        connection = http.client.HTTPSConnection(parsed_url.hostname, parsed_url.port)

        if self.Debug > 0:
            connection.set_debuglevel(self.Debug)
            # adding this querystring parameter gets detailed information about the PNS send notification outcome
            url += self.DEBUG_SEND
            print("--- REQUEST ---")
            print("URI: " + url)
            print("Headers: " + json.dumps(headers, sort_keys=True, indent=4, separators=(' ', ': ')))
            print("--- END REQUEST ---\n")

        connection.request('POST', url, payload, headers)
        response = connection.getresponse()

        if self.Debug > 0:
            # print out detailed response information for debugging purpose
            print("\n\n--- RESPONSE ---")
            print(str(response.status) + " " + response.reason)
            print(response.msg)
            print(response.read())
            print("--- END RESPONSE ---")

        elif response.status != 201:
            # Successful outcome of send message is HTTP 201 - Created
            raise Exception(
                "Error sending notification. Received HTTP code " + str(response.status) + " " + response.reason)

        connection.close()

    def send_notification(self, notification, tag_or_tag_expression=None):
        url = self.Endpoint + self.HubName + '/messages' + self.API_VERSION

        json_platforms = ['template', 'apple', 'gcm', 'adm', 'baidu']

        if any(x in notification.format for x in json_platforms):
            content_type = "application/json"
            payload_to_send = json.dumps(notification.payload)
        else:
            content_type = "application/xml"
            payload_to_send = notification.payload

        headers = {
            'Content-type': content_type,
            'Authorization': self.generate_sas_token(),
            'ServiceBusNotification-Format': notification.format
        }

        if isinstance(tag_or_tag_expression, set):
            tag_list = ' || '.join(tag_or_tag_expression)
        else:
            tag_list = tag_or_tag_expression

        # add the tags/tag expressions to the headers collection
        if tag_list != "":
            headers.update({'ServiceBusNotification-Tags': tag_list})

        # add any custom headers to the headers collection that the user may have added
        if notification.headers is not None:
            headers.update(notification.headers)

        self.make_http_request(url, payload_to_send, headers)

    def send_apple_notification(self, payload, tags=""):
        nh = Notification("apple", payload)
        self.send_notification(nh, tags)

    def send_gcm_notification(self, payload, tags=""):
        nh = Notification("gcm", payload)
        self.send_notification(nh, tags)

    def send_adm_notification(self, payload, tags=""):
        nh = Notification("adm", payload)
        self.send_notification(nh, tags)

    def send_baidu_notification(self, payload, tags=""):
        nh = Notification("baidu", payload)
        self.send_notification(nh, tags)

    def send_mpns_notification(self, payload, tags=""):
        nh = Notification("windowsphone", payload)

        if "<wp:Toast>" in payload:
            nh.headers = {'X-WindowsPhone-Target': 'toast', 'X-NotificationClass': '2'}
        elif "<wp:Tile>" in payload:
            nh.headers = {'X-WindowsPhone-Target': 'tile', 'X-NotificationClass': '1'}

        self.send_notification(nh, tags)

    def send_windows_notification(self, payload, tags=""):
        nh = Notification("windows", payload)

        if "<toast>" in payload:
            nh.headers = {'X-WNS-Type': 'wns/toast'}
        elif "<tile>" in payload:
            nh.headers = {'X-WNS-Type': 'wns/tile'}
        elif "<badge>" in payload:
            nh.headers = {'X-WNS-Type': 'wns/badge'}

        self.send_notification(nh, tags)

    def send_template_notification(self, properties, tags=""):
        nh = Notification("template", properties)
        self.send_notification(nh, tags)



isDebug = True
myConnectionString =  "connection string"
hub = NotificationHub(myConnectionString, "cavenotificationhub", isDebug)
data = {}
data['response'] = 'data: R1|X1|S1,1|$'
json_data = json.dumps(data,separators=(',',':'))
print(json_data)
#gcm_payload = {"response":R1|X1|S1,1}
val= "R1|X1|S1,1|$"
gcm_payload = { 'data' : {'response': ''+val+''}}
hub.send_gcm_notification(gcm_payload)

Attached logs:

Windows 10 Surface Pro 3 - Python 3.4 script

**** Send GCM Notitification START>  2016-05-08 10:42:07.883226
*** make_http_request connection OPEN>  2016-05-08 10:42:08.139328

--- REQUEST ---
#Request header
--- END REQUEST ---

*** make_http_request START>  2016-05-08 10:42:08.165356
#Sending request to Azure
*** make_http_request END>  2016-05-08 10:42:09.016024

--- RESPONSE ---
#Response received from Azure
--- END RESPONSE ---

*** make_http_request connection CLOSE>  2016-05-08 10:42:09.184785
**** Send GCM Notitification END>  2016-05-08 10:42:09.188788

################################################################################################################################ Raspberry Pi Model B+ V1.2 - Python 2.7 script

('*** Send GCM Notitification START> ', '2016-05-08 10:46:32.692844') ('*** make_http_request connection OPEN> ', '2016-05-08 10:46:32.698456')

--- REQUEST ---
#Request header
--- END REQUEST ---

('*** make_http_request START> ', '2016-05-08 10:46:32.705946')
#Sending request to Azure ('*** make_http_request END> ', '2016-05-08 10:46:39.557759')

--- RESPONSE ---
#Response received from Azure
--- END RESPONSE ---

('*** make_http_request connection CLOSE> ', '2016-05-08 10:46:39.569713') ('*** Send GCM Notitification END> ', '2016-05-08 10:46:39.570986')

################################################################################################################################ Raspberry Pi 2 Model B V1.1 - Python 2.7 script

('*** Send GCM Notitification START> ', '2016-05-08 05:36:49.721024') ('*** make_http_request connection OPEN> ', '2016-05-08 05:36:49.732056')

--- REQUEST ---
#Request header
--- END REQUEST ---

('*** make_http_request START> ', '2016-05-08 05:36:49.733069')
#Sending request to Azure ('*** make_http_request END> ', '2016-05-08 05:36:50.741605')

--- RESPONSE ---
#Response received from Azure
--- END RESPONSE ---

('*** make_http_request connection CLOSE> ', '2016-05-08 05:36:50.746248') ('*** Send GCM Notitification END> ', '2016-05-08 05:36:50.747000')
Satish Gunjal
  • 43
  • 1
  • 11
  • Thanks for putting up this question. Even I am facing the same issue. Notification arrives a bit late on my android device too. Is there anyone who can help us sort out this issue..? – Ali_Waris May 07 '16 at 11:00

1 Answers1

2

That is tough! First, we need to understand what can be the reason of that issue.

1) It can be long request from your Raspberry Pi 2 to the backend. For understand if that is the issue, use Service Bus Explorer, connect to your SB namespace, then open Notification Hub, select Android pane and try to send the push from that pane (see screenshot). If you will get the notification faster, then the RPI is somehow involved.

2) If you see that there is no difference in delay, and using RPI or SB for sending the notification resulting in the push after 2-3 sec, then i would say that it is absolutely OK. See that topic, for example. It really depends on many factors, and when we tried to decrease the time (we observed 5-10 seconds sometimes), we saw that the often issue is not the client or broker (NH in that case) but the notificationa platform.

And, of course, you should not expect delivering the notification the same second. Best we had - next or after 2 seconds. But it is really not guaranteed.

Community
  • 1
  • 1
Alex Belotserkovskiy
  • 4,012
  • 1
  • 13
  • 10
  • Thanks @Alex for your response. We did testing with Three different platforms and our logs are attached in the OP. As per the timestamps we are getting a approx 6 seconds delay (almost 1-2 seconds delay is acceptable) on Raspberry Pi Model B+ V1.2. Can you please share your insight on how to reduce this delay? – Satish Gunjal May 08 '16 at 06:25
  • 1
    @SatishGunjal i can't say that i know the ultimate truth. According to my past experience, there is not a lot to do to reduce the average latency. Apparently, the delay can be up to 10 minutes. See these threads - http://stackoverflow.com/questions/9204767/how-much-delay-of-ios-push-notification and http://stackoverflow.com/questions/19560448/how-to-avoid-delay-in-android-gcm-messages-change-heartbeat . You may try to implement the technique from the Android question, still and see how it is going. – Alex Belotserkovskiy May 08 '16 at 09:33
  • If you would like to have the stable mechanism that will deliver the notification immediately _always_, IMO, push is the wrong choice due to its "no guaranteed" nature. Did you consider some custom implementation of your notifications? – Alex Belotserkovskiy May 08 '16 at 09:34
  • 1
    @AlexBelotserkovskiy I tried to proceed with 'services' approach in android, but it has serious implications on battery life, and I have to keep pooling for checking whether there is a new notification available or not. Can you suggest any other approach where we can be fully assured of the quick notification arrival on android device? – Ali_Waris May 08 '16 at 09:56
  • @AlexBelotserkovskiy Can you suggest any custom implementation for push notifications. I want to try all the options before finalising the solution. – Satish Gunjal May 09 '16 at 05:13
  • May you guys share the scenario of your notifications? – Alex Belotserkovskiy May 09 '16 at 09:19
  • @SatishGunjal could you elaborate on the scenario? There is some techniques of notifications using Azure Service Bus or something more lightweight, for example. – Alex Belotserkovskiy May 10 '16 at 11:32
  • @AlexBelotserkovskiy we need to send device on or off status signals from Raspberry pi to mobile application and vice versa. One raspberry pi can have max 20 clients connected to it. I tried Azure Service Bus option also but couldn't found a way to add it in Android application. – Satish Gunjal May 10 '16 at 12:42
  • 1
    @SatishGunjal makes sense now. Use of SB on Android is possible, for example, you may see [that](http://stackoverflow.com/questions/34707380/connect-azure-service-bus-on-android-by-rest-api) for a reference. – Alex Belotserkovskiy May 10 '16 at 13:17
  • 1
    You may want to try Azure IoT Hub as well - it is the queue-like service optimized for IoT workloads. That will allow you to quickly notify app. However, i would try to offload the notifications stuff from your application for something like Web API backend to avoid support issues. See [link] (https://azure.microsoft.com/en-us/documentation/articles/notification-hubs-aspnet-backend-windows-dotnet-notify-users/#registering-for-notifications-using-the-webapi-backend) for the approach – Alex Belotserkovskiy May 10 '16 at 13:17