This blog is inspired by Geertjan's Blog on Track the Closest Beacon on Android via Cordova

In this blog, I will be using Oracle Mobile Cloud Services (MCS) version 16.3.x and above, Oracle JavaScript Extension toolkit (JET) hybrid cordova based application version 1.0.2 and Bluetooth low energy (BLE) beacon hardware device to demonstrate a sample use case to develop proximity based marketing and engagement for a retail store.

 

Using proximity marketing campaigns, retailers can keep consumers informed about the products, services, and actual special offers from each of the respective store sections. Retailers place these beacons in different store section which in turn continously broadcast a unique identifier that a Smartphone app can use to trigger appropriate action.

 

Key Concepts

Below tables summarizes the key components involved in developing proximity-based engagement

 

Components

Details

Beacon

Beacons use Bluetooth Low Energy transmissions to send out their identity. It basically broadcast four pieces of information:

  • UUID that identifies the beacon.
  • A Major number identifying a subset of beacons within a large group.
  • A Minor number identifying a specific beacon.
  • A TX power level in 2's compliment, indicating the signal strength few meter's from the device.

Oracle MCS

Oracle MCS Location services will be used to fetch list of devices by the given place ( latitude, longitude and radius)

Mobile app – Oracle JET Hybrid app

An application on the mobile phone picks up the nearby beacon information , and calculates its distance based on signal strength (txpower and rssi)

 

Proximity-based engagement flow

Below diagram shows the life-cycle flow for implementing a basic proximity-based engagement:

flow.png

 

 

Below are the main steps involved:

  1. Mobile application sends it’s current location – latitude, longitude and radius to Oracle MCS
  2. Oracle MCS sends back the beacon registry details and that get’s stored in the mobile application.
  3. Mobile application starts listening to these devices over Bluetooth
  4. Mobile application starts picking up signals from nearby beacons
  5. On getting micro-location and context ,the application triggers appropriate action

 

Below table summarizes key terms used:

Terms

Details

UUID

UUID stands for Universally Unique Identifier.  It contains 32 hexadecimal digits, split into 5 groups, separated by dashes , example: F4826da6-4fa2-4e98-8024-bc5b71e0893f

 

Component: Oracle Mobile Cloud Services (MCS)

The retail store decides to deploy iBeacon inside its store and make a mobile application that can tell the user once they arrive at a specific store, they would define a UUID that is unique to their app and the beacons inside their stores. Inside the stores, they would place beacon devices and configure each of them to use a different “minor” value.

 

For example, at the store A, they would place all beacon devices broadcasting the RDXApparel UUID, major value 1, minor 1 near the Kid’s toy section , minor 2 near the handbags section and minor value 3 near the cashier. At store B, they would use the same UUID, but major 2 and minor values according to the location inside the store.

 

In order to achieve this, at first we need to create a mobile backend, assign location of our retail store and then assign the device details that are present in that particular store.

 

Step 1: Create a mobile backend

Create a mobile backend to access and configure services, in this example we will incorporate mobile user management and location services

 

Step 2: Add Location and associate device(s)

The retail store location and related device detailed needs to be uploaded/added into Oracle MCS. In order to do that,  we need to first add a new place under "Location" section in Oracle MCS.

A place is a physical location associated with one or more location devices.

Add a Location

OracleLocationOverview.png

 

Associate device(s) with the location

Add a device.png

 

Step 3: Test the added/uploaded data

Once data is added, we need to test if user is able to download this information. We will use Location services platform, POST API Return Places by Query. Here is sample request:

 

POST /mobile/platform/location/places/query HTTP/1.1
Host: xxx.mobileenv.us2.oraclecloud.com:443
oracle-mobile-backend-id: 12345678-3297-4d93-87a8-e36dc7d6842c
Authorization: Basic abc=
Content-Type: application/json
Cache-Control: no-cache
{
  "inGeoFence" : {
    "gpsCircle" : {
      "longitude":77.3211437,
      "latitude": 28.5591575,
      "radius": 10000
    }
  }
}

 

Here is sample output:

{
  "items": [
    {
      "id": 10,
      "createdOn": "2016-08-28T07:54:55.826+0000",
      "createdBy": "xxx@oracle.com",
      "modifiedOn": "2016-08-31T05:23:20.785+0000",
      "modifiedBy": "xxx@oracle.com",
      "name": "RDxApparel Noida",
      "label": "RDApparelx",
      "description": "RDxApparel store in Noida Sector 127",
      "hasChildren": false,
      "address": {
        "gpsCircle": {
          "longitude": 77.321,
          "latitude": 28.549,
          "radius": 999.99999999
        }
      },
      "devices": [
        {
          "id": 10,
          "createdOn": "2016-08-28T08:16:12.302+0000",
          "createdBy": "xxx@oracle.com",
          "modifiedOn": "2016-08-28T08:19:32.341+0000",
          "modifiedBy": "xxx@oracle.com",
          "name": "RDxApparel Kids section",
          "description": "Thank-you for visiting kid's section! \nCongrats, you are now eligible for 10% discount on kid's section items , your coupon code is RDXKIDS10",
          "beacon": {
            "iBeacon": {
              "major": "65504",
              "minor": "65505",
              "uid": "74278BDA-1114-4520-8F0C-720EAF059935"
            }
          },
          "properties": {},
          "links": [
            {
              "rel": "canonical",
              "href": "/mobile/platform/location/devices/10"
            },
            {
              "rel": "self",
              "href": "/mobile/platform/location/devices/10"
            }
          ]
        },
        {
          "id": 11,
          "createdOn": "2016-08-29T08:53:49.813+0000",
          "createdBy": "xxx@oracle.com",
          "modifiedOn": "2016-08-31T05:20:08.898+0000",
          "modifiedBy": "xxxx@oracle.com",
          "name": "RDxApparel Handbag section",
          "description": "Thank-you for visiting Handbag section! Congrats, you are now eligible for 40% discount on handbags , your coupon code is RDXHANDBAGS40",
          "beacon": {
            "iBeacon": {
              "major": "16808",
              "minor": "19400",
              "uid": "748BEEA2-1111-44C2-A59C-706FFACA6A3B"
            }
          },
          "properties": {},
          "links": [
            {
              "rel": "canonical",
              "href": "/mobile/platform/location/devices/11"
            },
            {
              "rel": "self",
              "href": "/mobile/platform/location/devices/11"
            }
          ]
        }
      ],
      "properties": {},
      "links": [
        {
          "rel": "canonical",
          "href": "/mobile/platform/location/places/10"
        },
        {
          "rel": "self",
          "href": "/mobile/platform/location/places/10"
        }
      ],
      "children": []
    }
  ],
  "totalResults": 1,
  "limit": 100,
  "count": 1,
  "hasMore": false
}

 

Once we get status 200 OK, our back-end part is ready!

 

Component: Developing client-side Oracle JET Hybrid application

We will start implementing from scratch, you may please refer to Troubleshooting while developing your First JET based Hybrid Application blog in case of initial issues faced during development/configuration issues.

 

Project Setup using Yeoman

Yeoman generator for Oracle JET lets you quickly set up a project for use as a Web application or mobile-hybrid application for Android and iOS. Use following command to generate hybrid application for Android:

 

yo oraclejet:hybrid proximitysample --appId=com.rdh.proximitysample --appName="proximitysample" --template=navBar --platforms=android

 

The navBar template based native hybrid application code will be placed in “proximitysample” folder.

 

Cordova Plugin Required

Following cordova plugin needs to be added in our application:

 

  1. cordova-plugin-geolocation: This plugin provides information about the device's location, such as latitude and longitude. Link: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-geolocation/
  2. cordova-plugin-ibeacon: iBeacon plugin for cordova based hybrid application. Link: https://github.com/petermetz/cordova-plugin-ibeacon
  3. Cordova Local-Notification Plugin:  Plugin to display local notification, especially when application is not running in foreground. Link: https://github.com/katzer/cordova-plugin-local-notifications

 

cd hybrid
cordova plugin add cordova-plugin-geolocation 
cordova plugin add https://github.com/petermetz/cordova-plugin-ibeacon.git
cordova plugin add https://github.com/katzer/cordova-plugin-local-notifications

 

Implementation Steps

 

We will be implementing the entire code in dashboard.html and dashboard.js for easy implementation.

 

Below is the updated dashboard.html file

 

Untitled.png

 

Add below variables in dashboard.js file DashboardViewModel() function, they will be used in various function in given steps:

       var mRegions = [];
                var mNearestBeacon = null;
                var mNearestBeaconDisplayTimer = null;
                var beaconID = '00000';
                var winningCounter = 0;
                self.viewbeaconName = ko.observable();
                self.viewDiscountCoupon = ko.observable();

 

 

Below are the implementation steps to fetch micro-location and context and generating a location notification in the app to display relevant information:

 

Step 1:  Fetch the current location – latitude, longitude and radius and send it to Oracle MCS

 

               // fetches current latitude and longitude
                function getGeoLocationCoordinates()
                {
                    var onSuccess = function (position) {
                        var lat = position.coords.latitude;
                        var long = position.coords.longitude;
                        console.log(lat);
                        console.log(long);
                        // Pass the latitude and logitude to MCS location services
                        downloadBeaconRegistry(lat, long);
                    };
                    function onError(error) {
                        alert('code: ' + error.code + '\n' +
                                'message: ' + error.message + '\n');
                    }
                    navigator.geolocation.getCurrentPosition(onSuccess, onError);
                }
                function onDeviceReady() {
                    // Will execute when device is ready, or immediately if the device is already ready.
                    getGeoLocationCoordinates();
                }

 

Step 2:  Fetch all the iBeacon device details received from Oracle MCS Location Services Platform API in the application

 

// downloads device deatails based on location given from Oracle MCS Location Platform Service
                function downloadBeaconRegistry(gpsLat, gpsLong)
                {
                    var settings = {
                        "async": true,
                        "crossDomain": true,
                        "url": "https://xxx.mobileenv.us2.oraclecloud.com:443/mobile/platform/location/places/query",
                        "method": "POST",
                        "headers": {
                            "content-type": "application/json",
                            "oracle-mobile-backend-id": "your-oracle-mobile-backend-id",
                            "authorization": "Basic " + btoa("mobile-user-name" + ":" + "mobile-user-password")
                        },
                        "data": JSON.stringify({
                            inGeoFence: {
                                gpsCircle: {
                                    longitude: gpsLong,
                                    latitude: gpsLat,
                                    radius: 1000 // assuming the radius to be around 1000 meter
                                }
                            }
                        })
                    };
                    $.ajax(settings).done(function (response) {
                        // populate beacon registry in an array
                        for (var i in response.items[0].devices)
                        {
                            // save all device details in mRegions array
                            mRegions.push(
                                    {
                                        id: response.items[0].devices[i].id,
                                        name: response.items[0].devices[i].name,
                                        description: response.items[0].devices[i].description,
                                        uid: response.items[0].devices[i].beacon.iBeacon.uid,
                                        major: response.items[0].devices[i].beacon.iBeacon.major,
                                        minor: response.items[0].devices[i].beacon.iBeacon.minor,
                                        beaconID: response.items[0].devices[i].beacon.iBeacon.uid + ':' +
                                         response.items[0].devices[i].beacon.iBeacon.major + ':' + response.items[0].devices[i].beacon.iBeacon.minor
                                    });
                        }
                        startDetectingBeacons();
                    });
                }

 

Step 3: Start listening to devices over Bluetooth

 

 // start detecting given beacons
                function startDetectingBeacons()
                {
                    function onDidRangeBeaconsInRegion(result)
                    {                      
                        updateNearestBeacon(result.beacons);
                    }
                    var delegate = new cordova.plugins.locationManager.Delegate();
                    cordova.plugins.locationManager.setDelegate(delegate);
                    delegate.didRangeBeaconsInRegion = onDidRangeBeaconsInRegion;
                    for (var i in mRegions)
                    {
                        var beaconRegion = new cordova.plugins.locationManager.BeaconRegion(
                                mRegions[i].beaconID,
                                mRegions[i].uid,
                                mRegions[i].major,
                                mRegions[i].minor);
                        cordova.plugins.locationManager.startRangingBeaconsInRegion(beaconRegion)
                                .fail()
                                .done();
                    }
                    mNearestBeaconDisplayTimer = setInterval(displayNearestBeacon, 1000);
                }

 

 

Step 4: Find the nearest Beacon

 

 // generates uniqueId based on uuid, major and minor number
               function getBeaconId(beacon)
                {
                    return beacon.uuid + ':' + beacon.major + ':' + beacon.minor;
                }               
                function isSameBeacon(beacon1, beacon2)
                {
                    return getBeaconId(beacon1) === getBeaconId(beacon2);
                }
               // beacon.accuracy returns the distance of beacon from mobile device
                function isNearerThan(beacon1, beacon2)
                {
                    return beacon1.accuracy > 0
                            && beacon2.accuracy > 0
                            && beacon1.accuracy < beacon2.accuracy;
                }
                function updateNearestBeacon(beacons)
                {
                    for (var i = 0; i < beacons.length; ++i)
                    {
                        var beacon = beacons[i];
                        if (!mNearestBeacon)
                        {
                            mNearestBeacon = beacon;
                        } else
                        {
                            if (isSameBeacon(beacon, mNearestBeacon) ||
                                    isNearerThan(beacon, mNearestBeacon))
                            {
                                mNearestBeacon = beacon;
                            }
                        }
                    }
                }

 

Step 5: On getting micro-location and context, calculate how far the user is from a given beacon (i.e. retail store section) and display relevant information on the mobile app

 

 function findBeacon(beaconObj)
               {                    
                    return (beaconObj.beaconID).toUpperCase() === beaconID.toUpperCase();
                }
                function displayNearestBeacon()
                {
                    if (!mNearestBeacon) {                        
                        return;
                    }                    
                    beaconID = getBeaconId(mNearestBeacon);              
                    var objBeacon = mRegions.find(findBeacon);                   
                   self.viewbeaconName('You are now just '+ mNearestBeacon.accuracy + ' meters away from our ' + objBeacon.name +'!');
                   if(mNearestBeacon.proximity === 'ProximityNear' || mNearestBeacon.proximity === 'ProximityImmediate' )
                    {
                        self.viewbeaconName('Welcome to '+ objBeacon.name +'!');                        
                        winningCounter++;                        
                    }                    
                    if(winningCounter > 30)
                    {
                        winningCounter = 0;
                        showLocalNotification(objBeacon.name, objBeacon.description);                 
                     }
                }
Step 6: Trigger a local notification displaying important information based on time spent by user near a section

 

 function showLocalNotification(beacontitle, beacondescription)
                {
                    cordova.plugins.notification.local.schedule({
                        title: beacontitle,
                        message: beacondescription
                    });                 
                   self.viewDiscountCoupon(beacondescription);                    
                }

 

Finally, update handleDetached function to reset the timer interval:

 self.handleDetached = function () {
                    document.removeEventListener('deviceready', onDeviceReady);
                    clearInterval(mNearestBeaconDisplayTimer);
                    mNearestBeaconDisplayTimer = null;
                };

 

This is the one of the easiest way to interact. However, the user context information is of real advantage as we can send this information to big data systems for extracting information like user behavior , analytics.

 

Build and Run the application on Android device

In your command prompt, please change directory to project folder and run the following command:

 

  1. Build the application using following command
grunt build --platform=android

 

Please ensure to turn ON your mobile's Bluetooth, GPS and mobile data  before running the application. Also please allow the app to use Bluetooth and GPS when app runs.

  1. Once build is success, then run the application using following command:
grunt serve --platform=android  --disableLiveReload=true

Demo Video

Below is the video output of the Mobile application:

 

 

**The views expressed in this post are my own and do not necessarily reflect the views of Oracle.