Gimbal SDK Proximity Beacons Deep Dive

Important

This article only applies to the Gimbal Android SDK with Proximity Beta v1.21.1 up to v1.33. Gimbal has just released v2.x Beta which uses a different set of interfaces. Please refer to this new article on upgrading to the new SDK.

Before Beginning

Keep in mind that as of this writing, the Gimbal Proximity SDK for Android is in Beta. Things are most certainly going to change and features will be potentially added or removed. This article is designed to provide a deep dive into the Gimbal Proximity SDK for Android. This allows the use of Gimbal beacons to provide contextual location awareness and the creation of logic around the beacons. This article assumes the reader understands the concepts of object-oriented programming and has a proficient knowledge level of Java.

Introduction

The Internet of Things (IoT) is very exciting and is going to explode greatly. In the near future, it will not be uncommon to see many normal household devices connected to the internet (thank goodness for IPv6). The amount of data available for consumption will be out of this world, and the things that could be done with it will be endless. One concept of IoT is beacons. These devices use Bluetooth Low Energy (BLE), a more efficient version of classic Bluetooth, to transmit signals to BLE equipped devices.

So what do they transmit? They transmit proximity data. In other words, any device that is set to pick up that signal will be able to tell how far away it is from the beacon. This is based upon the signal strength. At a high-level view, this is basic. However, the possible applications with this technology are immense. Each beacon is unique and depending upon how the client device is set up, different things can happen with disparate beacons.

Location Awareness

Mobile application development presents a great deal of opportunities to innovate. Pretty much every smartphone out there worth its salt has Wi-Fi and GPS, which offers the ability to create rich location aware applications that do some wonderful things.

Beacons can be strategically placed at a location, such as inside a store, so that if a user gets near them something exciting happens on their mobile device. Apple is currently doing this inside their stores so that if a customer has their application installed on their phone it will communicate with these beacons to provide options such as getting help if a store associate isn’t available, a barcode scanner to get product details, or allowing digital payments on the spot. This article discusses this in more detail and provides additional details on beacons in general which supplements this post.

Something else to consider outside of beacons is geofencing which uses the location abilities (Wi-Fi, GPS, etc.) of a mobile device to track the user. If that user gets inside a geofence (picture a circle with a varying size, dependent upon how it was created), something can happen such as an event firing, lights turning on, and so forth. Keep in mind this concept is not anywhere near the accuracy of a beacon, so it makes setting something up inside a store very difficult.

Solving the Accuracy Problem

The geofencing technique found on Android devices uses a combination of Wi-Fi, cell service, and GPS (depending on which is available) to get the most accurate location. However, the problem of accuracy quickly causes headaches. For example, GPS is accurate within 5 meters but doesn’t work well (if at all) indoors. All that remains is your Wi-Fi and cell service, which decreases the accuracy quite a bit. This makes it difficult to be very granular in areas where accuracy is critical such as the inside of a building. The table below sums up the common methods of getting geolocation data and the potential use cases for each.

Common Geolocation Providers
Common Geolocation Providers – Accuracy Source: Applidium

Welcome to Gimbal

Qualcomm has created a beacon product called Gimbal (also a separate company) which works with both iOS and Android. Similarly, Apple has a product named the iBeacon but is cracking down on its use with Android devices. The issue mainly seems to be a trademark and marketing dispute. Even though the iBeacon is built using BLE, meaning that all BLE capable devices should be able to theoretically pick up iBeacon signals, Apple’s investment in the iBeacon name and other related technology puts the company in a position to disassociate anything iBeacon with non-Apple devices. Qualcomm ensures the Gimbal is built to work with Apple’s iBeacon specifications (in addition to its own proprietary technology) but doesn’t restrict its usage to one OS. The problem with accuracy suddenly disappears when Gimbal is at play. Take a look at the table below.

Gimbal Location Provider
Gimbal Location Provider

Prerequisites

As mentioned previously, it is assumed the reader has grasped the most important concepts of object-oriented programming and the Java language before tackling the information presented here. Moreover, this post focuses on Android and not iOS. One of the great things about Gimbal is that it’s cross-platform, but this article focuses strictly on Android. The steps needed to be taken are below to implement the Gimbal SDK with Proximity for Android. Be sure to complete these steps before continuing.

  1. Create a Gimbal Developer Account.
  2. Download the Android SDK with Proximity (Beta as of this post). The link to download SDKs will be seen on the Gimbal dashboard when logged in.
  3. Make sure the project targets at least Android 4.4.3 or higher, or else the Gimbal Proximity SDK will not function. This is because BLE support is only in Android 4.4.3 or higher.
  4. Set up the Gimbal SDK in the project. Instructions: Android Studio | Eclipse.
  5. Ensure the client device which hosts the application has access to the internet.
  6. Procure a Gimbal beacon. This is to properly test the Gimbal SDK. These can be purchased at the Gimbal Store.

How the Gimbal SDK Works

When compiling a project with the Gimbal SDK, the application will have a service attached to it. This service belongs to the Gimbal SDK. To use the service, it must be called using the various Gimbal SDK static methods. In addition, a Proximity Listener will need to be implemented to handle the various callback methods which determine what will happen when a device comes into proximity of the Gimbal beacons.

Furthermore, implementing a Bluetooth Broadcast Receiver to handle the various Bluetooth states is recommended. Should the Gimbal service be running if Bluetooth has been turned off? Moreover, what about politely informing the user in a non-annoying and useful way that the application uses proximity beacons, and they will have a sub-par experience when Bluetooth is off? These are reasons a Bluetooth Broadcast Receiver will be helpful.

The Gimbal SDK must be configured to use a Gimbal Developer Account. After using the web GUI to create an application entry from this account, a Gimbal API key will be displayed. This will be used by the SDK to tie into the account. Upon setting up the SDK in an Android project, copying the API key into a special file was required. The SDK will not work if this hasn’t been done. Moreover, in order for the SDK to work, it has to call home to the Gimbal backend service once every 24 hours.

In practice, the Gimbal SDK will call the backend very often to upload analytic data. It will also retrieve other configuration data. Keep in mind that using the Gimbal backend service is required in order for these beacons to function. It is likely users will want their applications to send the beacon ID in proximity to their backend service to perform custom logic such as tracking how many users are currently at a beacon (this is a simple example, but so much more can be done). All this is up to the developer to implement if desired, but hopefully the seed has been planted for potential ideas.

Examining the Gimbal SDK Proximity Methods

It is recommended to create a wrapper or helper class for the Gimbal SDK proximity methods. This will keep things tidier and reduce code duplication. Before getting into the helper class, please review the Gimbal methods shown in the table below.

Method Static Method Description Parameters
Proximity.initialize(context, applicationId, proximitySecret) Yes Registers application to work with Gimbal and its backend service. It is required to start the service successfully before each call to Proximity.startService(). context — The application context.
applicationId —
Application ID taken from the Gimbal Developer Account.
proximitySecret —
Proximity secret taken from the Gimbal Developer Account.
Proximity.startService(proximityListener) Yes Starts the Proximity Service. Once this starts, the application will listen for beacons. proximityListener — Proximity Listener object. This object has the callback methods which respond to various proximity events.
Proximity.stopService() Yes Stops the Proximity Service. Once the service is stopped, the application will no longer listen for beacons.  
GimbalLogConfig.setLogLevel(GimbalLogLevel.OPTION) Yes Sets the current logging level for the Proximity Service. GimbalLogLevel.OPTION — Value for the logging level. The 5 available options are DEBUG, WARN, ERROR, INFO, and TRACE. For example, to use DEBUG then the argument should be GimbalLogLevel.DEBUG.
Proximity.optimizeWithApplicationLifecycle(context) Yes Optimizes proximity scanning based upon mode of application. If the application is in the background, the beacon scanning interval will be reduced. If the application is in the foreground, the beacon scanning interval will be increased. context — The application context.
ProximityOptions.setOption(various) No Sets the specified options for the Proximity Service. First a ProximityOptions object must be instantiated, and then a call made to its setOption() method. This object will be used by the VisitManager. Please refer to the official Gimbal SDK documentation for all the various options which can be set and examples.

Creating the Helper Class

Now it’s time to wrap everything up into some usable methods which can be called when needed. Furthermore, logic to regulate the calling of these methods and to decrease the chance for any errors will be included. This helper class will need to keep global state because the Gimbal Proximity Service will likely (found in most common implementations) continue running between activities. Thus, it will be implemented as a singleton.

Only one instance will ever be created or accessible at one time. The variables used for remembering state or error checking will hold their values throughout the life cycle of the application, since they belong to the only global instance of the helper class. It is recommended to read up on Java singletons before proceeding if these aren’t already familiar.

Below is the ProximityHelper class used to wrap some important Gimbal functionality. Each piece of the class will be dissected and explained.

 

Initializing

Before starting the service, it must be initialized first. Below is the method used to wrap this up.

The method first checks that the reference to mApplication has been set (setting this reference will be discussed later). If it is null, then an Illegal Argument Exception is thrown. Otherwise, the method checks if the service hasn’t already been initialized. If it hasn’t then the static method Proximity.initialize() will be called with the following arguments: the application context reference, a string with the application ID (found in the Gimbal Developer Account dashboard), and a string with the proximity secret key (also found in the Gimbal Developer Account dashboard). Finally, if the service is already initialized then logcat will be used to output a debug log with useful information.

Starting the Service

The method shown below starts the service and has some simple error checking. Situations where the service may already be running are accounted for.

The method begins by checking if the service isn’t already running. If it isn’t, then it checks if it hasn’t already been initialized. If it hasn’t, then it will call the initialize() method. Thereafter, it will check to see if the mApplication reference has been set. If not, it will throw an Illegal Argument Exception. Next, it optimizes the service with the application’s life cycle. This is done by calling the static method Proximity.optimizeWithApplicationLifecycle() and passing in the application context.

Then the method checks to see if the mGimbalProximityListener reference has been set and if not, it will create a new Proximity Listener object using the application context reference. Finally, the static method Proximity.startService() is called using the Proximity Listener reference. In addition, like the previous method if all else fails some useful information will be logged.

Stopping the Service

The method shown below stops the proximity service and has some simple error checking. Situations where the service may not already be running are accounted for.

The method first checks to see if the service is already running. If it is, then the mGimbalProximityListener reference will be set to null. Then the Visit Manager will be stopped by calling mVisitManager.stop(). The mVisitManager reference will also be set to null. After this, the static method Proximity.stopService() will be called and then the mIsProximityServiceRunning boolean set to false. Finally, if all else fails, some useful information will be logged.

Setting the Logging Level

The method shown below assists with setting the specified logging level used for the proximity service.

Upon running this method, the logging level will be set based upon the logging level argument passed in. The different logging levels were defined as static final integers at the begging of the class. It’s recommended to define a set of constants that symbolize the following logging levels: DEBUG, WARN, ERROR, INFO, and TRACE. Using an enum is not currently recommended due to memory related reasons. See this article for more details. On more modern devices this is likely not a problem, but still following this approach is recommended.

Setting Options

The below table outlines the important options that can be used for setting up the Proximity Service. Keep in mind setting these options are not required. If no options are specified, then default options will be used which have already been defined by Gimbal.

Option Description
Sighting Message Frequency (seconds) This is the interval, in seconds, at which the Gimbal Manager will send a Sighting event to the server at the Sighting Callback URL.
Exit Interval (seconds) The amount of time in seconds it takes to successfully trip a departure or exit.
Entry Signal Strength Only sightings whose values are “Entry Signal Strength” or larger will trigger an arrival event.
Exit Signal Strength Sightings whose values are “Exit Signal Strength” or smaller will be ignored for visit processing and will act similar to not being sighted. Eventually, if no sightings occur for X amount of seconds, a departure will be triggered.
Signal Strength Window Smoothing of signal strengths using historic sliding window averaging. Basically, this takes the reported signals coming from a beacon and smooths them out using an averaging algorithm. This prevents major differences in signal strength when receiving sightings. This is set using ProximityOptions.VisitOptionSignalStrengthWindowKey and the associated companion parameter.

Before going through the code to set options, some things need to be understood. The beacons work on the idea of arrivals, sightings, and departures. If the beacon is within range, then a sighting will occur. The signal strength required to trip an arrival can be specified. This is the Entry Signal Strength. Departures are the inverse of arrivals, and the signal strength required to trigger one can also be specified using the Exit Signal Strength.

If outside the Exit Signal Strength for X number of seconds, defined via the Exit Interval, a departure will occur. Because signals will fluctuate based upon various factors, it’s important to define an exit interval (in seconds) before a true exit occurs, otherwise false departures may be the result.

There will be callback methods to override which allows custom logic to be implemented for arrivals, sightings, and departures. This will be discussed later upon implementing the Proximity Listener.

The method show below allows options to be specified manually. Again, if this method isn’t called then the default options provided by Gimbal will be used.

This method takes the following parameters: entrySignal, exitSignal, foregroundTimeUntilExit, and backgroundTimeUntilExit. The entrySignal and exitSignal parameters were explained previously and take on the same functionality here. However, the Exit Interval can be set up in two ways, one when the application is in the background and the other when it’s in the foreground. Different values for this can be assigned based upon the mode of the application.

The passed in parameter values along with the keys defined in the static variables found in the ProximityOptions class are used to set the options. For more information on these keys, read this article. Some error checking is used to ensure the entry and exit signals are in the proper accepted range and if no errors are present then the options are assigned to the mProximityOptions object.

Something important to note is that this method doesn’t take a parameter to specify the signal smoothing rate. It currently uses the argument ProximityOptions.VisitOptionSignalStrengthWindowLarge by default. This is the recommended signal smoothing value, but it can be changed to other values such as small and medium. It is up to the reader to decide to customize this method to allow the specification of the signal smoothing rate.

Creating the Proximity Listener

Now that the helper class used to wrap some common functionality has been created the Proximity Listener needs to be implemented. This will provide important callback methods for arrivals, sightings, and departures in addition to other useful methods. Below is the full listing of the ProximityServiceListener class. It implements ProximityListener and VisitListener, combining all the necessary logic into one easy to use class.

Diving Into the serviceStarted() Callback Method

First, if the service is successfully started, then the overridden method serviceStarted() is called. This is a great place to record changes in state for error tracking. The call to ProximityHelper.getInstance().setIsProximityServiceRunning(true) keeps track of the service state and is used by other methods. After this call, a reference to the Visit Manager created in the Proximity Helper is obtained and the Visit Listener is set on that object by calling ProximityHelper.getInstance().getVisitManager().setVisitListener(this). Passing this will provide a reference to ProximityServiceListener, the very class currently being discussed.

Finally, after setting the Visit Listener reference on the Visit Manager object, the Visit Manager is started by calling ProximityHelper.getInstance().getVisitManager().start(). Next, if any options for the service were specified then the Visit Manager is started again but with options via the call to ProximityHelper.getInstance().getVisitManager().startWithOptions(ProximityHelper.getInstance().getProximityOptions()). These method calls are chained together due to ProximityHelper being a singleton. Occasionally, these long calls may be undesirable but being able to store global state is more important, thus the necessity of a singleton.

Diving Into the startServiceFailed() Callback Method

If the service failed to start, there could be various reasons. Perhaps the right version of Android (4.4.3 or higher) isn’t running, or maybe the device doesn’t have BLE support. These are only a few of the potential problems. Thus, if the service didn’t start, then the call to ProximityHelper.getInstance().setIsProximityServiceRunning(false) is made. The error code and message sent to the method are logged via logcat. The mLogMessage variable is just a string formatted to display the relevant error information for debugging purposes.

If an error has occurred, then the passed value to the errorCode parameter can be checked to see if it matches any of the static variables found in the ProximityError class. If it matches then appropriate response can be taken such as notifying the user, silently failing but recording relevant information, and so forth.

Diving Into the didArrive() Callback Method

This is a basic method that just sends a message via logcat getting the name and ID of the beacon and stating an arrival has occurred. It is up to the reader to implement the desired logic here. Remember, this method will only be called once upon a successful arrival. The only way this can be called again is for a successful departure to be tripped.

Diving Into the receivedSighting() Callback Method

This method will be called every time a sighting has occurred. Again, it is up to the reader to apply the desired logic here. The beacon name and ID with the signal strength are logged via logcat for debugging purposes.

Diving Into the didDepart() Callback Method

Once outside the specified Exit Signal for X number of seconds defined in the Exit Interval, then this method will be called. It is only called once and then can be called again after the didArrive() method has fired and when the Exit Signal and Exit Interval values have been followed. In this case, more useful information for debugging purposes is again logged via logcat. Any desired logic can also be implemented here.

Properly Starting/Stopping the Proximity Service

After creating the Proximity Helper class and implementing the Proximity Listener, the Proximity Service can be started. It is up to the reader where to start this service in the application. Usually, this is the first Activity that gets called where the Proximity Service is desired. Generally, it is a good idea to start the service as early into the application as possible. This allows enough time for the service to start up and beacons to be sighted, with all this information available to execute the desired logic.

Below is the Activity class used to start and stop the Proximity Service.

When the Activity’s onResume() method is called, the ProximityHelper is used to check if the service is already running. If it’s not running, then options are set via the call to ProximityHelper.getInstance().setOptions(). Then the logging level is set via the call to ProximityHelper.getInstance().setLoggingLevel(). Finally, the Proximity Service is started via the call to ProximityHelper.getInstance().startProximityService().

Something important to keep in mind is the order these methods are to be called. The call to setOptions() must happen before the call to startProximityService(). Moreover, it is recommended to call setLoggingLevel() before the call to start the service, but this isn’t required. The final call should be to start the service.

Regarding stopping the service, doing it from the same Activity used to start it is a good idea. This is because if the application is built correctly then each Activity will be added to the back stack. Eventually, when the application is terminated or the user backs out of the application, the onDestroy() method will be called. This is a great place to stop the service and all it takes is the call to ProximityHelper.getInstance().stopProximityService().

The exact way of starting and stopping the service can be different if desired, but it is up to the reader to make these kinds of decisions, as they could significantly affect the way the application runs with the Proximity Service.

Remember: Be careful of objects with a long life span which have a reference to an Activity or Context. In that case, make sure to use Weak References. Otherwise, ending up with a memory leak may happen where the garbage collector cannot clean things up properly since a reference still exists to an object in memory.

Creating the Custom Application Class

It is recommended to create a custom application class so that logic related to the Proximity Service can be embedded within it. This is also another great place to store global state if the ProximityHelper singleton class becomes too bloated. Granted, additional singleton classes can be created for this purpose, so it is up to the reader to decide upon which approach to use.

Below is the custom application class.

This class may look basic at this point, but don’t underestimate its usefulness. Currently, when the application first runs the Application class will get instantiated before most other classes. Since the ProximityHelper is a singleton which is instantiated lazily, two birds can be killed with one stone via the call to ProximityHelper.getInstance().setApplication(this).

First, the very act of calling ProximityHelper’s getInstance() method will instantiate the only instance of this class. This prevents any lag from getting the first instance and is taken care of almost immediately upon application start-up. The next method in the chain is setApplication() which uses the instance of the custom Application class to set the Application reference stored in the ProximityHelper instance. This reference will be used by the Proximity Service but can also be used for just about anything else that needs a context. In this case, the Application context has a long-running scope. Keep in mind, it is up to the reader to decide what the best context to use is depending on the scenario. Not all contexts are the same.

Something else that is very useful with a custom Application class is having it store global variables, keeping track if a Broadcast Receiver has been registered or if a dialog box has been dismissed, etc. It can also be used to store Activity references. For example, custom methods could be created in the custom Application class, which each Activity can call on the appropriate callback method. In that case, the custom Application would always have a relevant Activity reference that utility classes can take advantage of.

However, for the sake of this article, the custom Application class is used to set the Application reference as early as possible so the Proximity Service classes can use it without error.

One final thing that must be done is to modify the AndroidManifest.xml, so the custom application class can be used. Look for the <Application></Application> section in the manifest and make the following modifications:

The name will vary depending upon how the application was created and what was specified. Modify the name to match the fully qualified namespace plus class name, in this case YourApplication as the class name. For more details, see this article. In addition, for more details on the manifest in general, see this article as well.

A Few Cautions

Below are a few cautions to keep in mind when developing an application using Gimbal’s Proximity SDK:

  1. The method Proximity.initialize() must be called before calling the Proximity.optimizeWithApplicationLifecycle() method.
  2. The method Proximity.startService() must be called before starting the Visit Manager, with or without options. In other words, the service must be running first.

Conclusion

This article discussed how the Gimbal SDK works with Gimbal beacons. A helper class was implemented which wrapped the common Gimbal Proximity static methods. Then, a Proximity Listener was implemented and properly starting and stopping the service was discussed. Finally, a custom Application class was created which offered many benefits. One thing not covered was setting up a Bluetooth Broadcast Receiver and handling the various Bluetooth state changes. These state changes could have started or stopped the service where desired. This is an exercise left to the reader.

Any questions should be left in the comments section below. Knowledge is power, and everyone benefits from open learning and active collaboration. Happy Gimbaling!

Resources

  1. Setting Up the Gimbal SDK with Proximity in Android Studio
  2. Gimbal Documentation Home
Scroll to Top