Stick an Estimote Beacon at your desk, in your car, or on a package, and the Estimote Proximity SDK will let your app know when you enter or exit its range. Works indoors, in the background, and is accurate up to a few meters.
Powered by Estimote Monitoring: Estimote’s own signal-processing technology, with emphasis on maximum reliability. (up to 3 times better than other beacon-based technologies we’ve benchmarked against.
Other Proximity SDK highlights include:
Estimote Proximity SDK uses tag-based identification to allow for dynamic setup changes.
You monitor beacons by tags, which you assign in Estimote Cloud. For example, instead of saying "monitor for beacon 123 and beacon 456", you say, "monitor for beacons tagged as lobby
". This way, if you need to replace or add more beacons to the lobby, you just add/change tags in Estimote Cloud. Your app will pick up the new setup the next time the ProximityObserver is started.
As our SDK is still in version
0.x.x
, we're constantly modifying our API according to your feedback. Our latest iteration is based on simple tags, backed up with attachments as an optional additional information. From the version0.6.0
, the method.forAttachmentKeyAndValue(...)
is deprecated - please use.forTag(...)
instead.
Estimote Proximity SDK is built on the top of three key components: observer, zone, and zone's context.
Below there’s a representation of two zones:
blueberry
zone with two Proximity Contexts,mint
zone with only one Proximity Context.Starting with version 1.0.6, Estimote Proximity SDK is available in our JFrog Artifactory. In order to gain access to it, our Artifactory repository must be added to the list of Maven repositories in your top level build.gradle file:
allprojects {
repositories {
maven {
url "https://estimote.jfrog.io/artifactory/android-proximity-sdk"
}
...
}
...
}
Add the below line to your build.gradle
file
implementation 'com.estimote:proximity-sdk:1.0.7'
If you are using Gradle version below
3.0.0
then you should usecompile
instead ofimplementation
.
Or use our Example app to download a ready, pre-integrated demo
Edit settings
buttonTags
and put your desired tag/tags. The ProximityObserver
is the main object for performing proximity observations. Build it using ProximityObserverBuilder
- and don't forget to put in your Estimote Cloud credentials!
// Kotlin
val cloudCredentials = EstimoteCloudCredentials(YOUR_APP_ID_HERE , YOUR_APP_TOKEN_HERE)
val proximityObserver = ProximityObserverBuilder(applicationContext, cloudCredentials)
.withBalancedPowerMode()
.onError { /* handle errors here */ }
.build()
// Java
EstimoteCloudCredentials cloudCredentials = new EstimoteCloudCredentials(YOUR_APP_ID_HERE, YOUR_APP_TOKEN_HERE);
ProximityObserver proximityObserver = new ProximityObserverBuilder(getApplicationContext(), cloudCredentials)
.withBalancedPowerMode()
.onError(new Function1<Throwable, Unit>() {
@Override
public Unit invoke(Throwable throwable) {
return null;
}
})
.build();
You can customize your ProximityObserver
using the available options:
ProximityObserver
will automatically send telemetry data from your beacons, such as light level, or temperature, to our cloud. This is also an important data for beacon health check (such as tracking battery life for example).ProximityObserver
will not try to resolve encrypted packets using Estimote Secure Monitoring protocol. Only unencrypted packets will be observed.Create your own proximity zones using proximityObserver.zoneBuilder()
// Kotlin
val venueZone = ProximityZoneBuilder()
.forTag("venue")
.inFarRange()
.onEnter {/* do something here */}
.onExit {/* do something here */}
.onContextChange {/* do something here */}
.build()
// Java
ProximityZone venueZone =
new ProximityZoneBuilder()
.forTag("venue")
.inFarRange()
.onEnter(new Function1<ProximityContext, Unit>() {
@Override public Unit invoke(ProximityContext proximityContext) {
/* Do something here */
return null;
}
})
.onExit(new Function1<ProximityContext, Unit>() {
@Override
public Unit invoke(ProximityContext proximityContext) {
/* Do something here */
return null;
}
})
.onContextChange(new Function1<List<? extends ProximityContext>, Unit>() {
@Override
public Unit invoke(List<? extends ProximityContext> proximityContexts) {
/* Do something here */
return null;
}
})
.build();
You zones can be defined with the below options:
onEnter
and one onExit
event for the whole zone in general. Notice that due to the nature of Bluetooth Low Energy, the range is "desired" and not "exact". We are constantly improving the precision.
When you are done defining your zones, you will need to start the observation process:
// Kotlin
val observationHandler = proximityObserver.startObserving(myZone)
// Java
ProximityObserver.Handler observationHandler =
proximityObserver
.startObserving(venueZone);
The ProximityObserver
will return ProximityObserver.Handler
that you can use to stop scanning later. For example:
// Kotlin
override fun onDestroy() {
observationHandler.stop()
super.onDestroy()
}
// Java
@Override
protected void onDestroy() {
observationHandler.stop();
super.onDestroy();
}
While zone identification is based on tags, attachments are a way to add additional content to a beacon and a zone it defines. Think of it as a custom backend where you can assign any additional data to a particular beacon.
Edit settings
buttonBeacon attachment
tab and click add attachment
When you enter the proximity zone of any beacon with this attachment, you will get a ProximityContext
as an parameter to your onEnter
or onExit
actions. The attachment will be there. Here is an example on how to use it:
val exhibitionZone = ProximityZoneBuilder()
.forTag("exhibit")
.inNearRange()
.onEnter { proximityContext ->
val title = proximityContext.getAttachments()["title"]
val description = proximityContext.getAttachments()["description"]
val imageUrl = proximityContext.getAttachments()["image_url"]
// Use all above data to update your app's UI
}
.create()
In order for ProximitySDK to work, you need to grant your app a location permission. You can ask your user for the permission by yourself, or use our RequirementsWizard to do it for you.
IMPORTANT: Since version 1.0.8 targeting Android SDK 31+ requires new permissions, that are not (yet) checked by RequirementsWizard. Please make sure your app has granted access to following permissions:
- android.permission.BLUETOOTH_CONNECT,
- android.permission.BLUETOOTH_SCAN,
- android.permission.ACCESS_FINE_LOCATION
Use case: Scanning when your app is in the background (not yet killed). Scanning attached to the notification object even when all activities are destroyed.
It is now possible to scan when the app is in the background, but it needs to be handled properly according to the Android official guidelines.
IMPORTANT: Launching "silent bluetooth scan" without the knowledge of the user is not permitted by the system - if you do so, your service might be killed in any moment, without your permission. We don't want this behaviour, so we decided to only allow scanning in the background using a foreground service with a notification. You can implement your own solution, based on any kind of different service/API, but you must bear in mind, that the system might kill it if you don't handle it properly.
Declare an notification object like this:
// KOTLIN
val notification = Notification.Builder(this)
.setSmallIcon(R.drawable.notification_icon_background)
.setContentTitle("Beacon scan")
.setContentText("Scan is running...")
.setPriority(Notification.PRIORITY_HIGH)
.build()
Use .withScannerInForegroundService(notification)
when building ProximityObserver
via ProximityObserverBuilder
:
To keep scanning active while the user is not in your activity (home button pressed) put start/stop in onCreate()
/onDestroy()
of your desired ACTIVITY.
To scan even after the user has killed your activity (swipe in app manager) put start/stop in onCreate()
/onDestroy()
of your CLASS EXTENDING APPLICATION CLASS.
Tip: You can control the lifecycle of scanning by starting/stopping it in the different places of your app. If you happen to never stop it, the underlying
foreground service
will keep running, and the notification will be still visible to the user. If you want such behaviour, remember to initialize thenotification
object correctly - add button to it that stops the service. Please, read more in the official android documentation about managing notification objects.
Use case: Displaying your notification when user enters the zone while having your app KILLED - the notification allows him to open your app (if you create it in such way). Triggering your PendingIntent
when user enters the zone.
Since Android version 8.0 there is a possibility to display a notification to the user when he enters the specified zone. This may allow him to open your app (by clicking the notification for example) that will start the proper foreground scanning.
You can do this by using our ProximityTrigger
, and here is how:
Declare an notification object like this:
// KOTLIN
val notification = Notification.Builder(this)
.setSmallIcon(R.drawable.notification_icon_background)
.setContentTitle("Beacon scan")
.setContentText("Scan is running...")
// you can add here an action to open your app when user clicks the notification
.setPriority(Notification.PRIORITY_HIGH)
.build()
Tip: Remember that on Android 8.0 you will also need to create a notification channel. Read more here.
Use ProximityTriggerBuilder
to build ProximityTrigger
:
// KOTLIN
val triggerHandle = ProximityTriggerBuilder(applicationContext)
// you can handle potential scanning error here. By default it is logging error message.
.onError { ... }
.displayNotificationWhenInProximity(notification)
.build()
.start()
This will register the notification to be invoked when the user enters the zone of your beacons. You can use the triggerHandle
to call stop()
- this will deregister the system callback for you.
Also, bear in mind, that the system callback may be invoked many times, thus displaying your notification again and again. In order to avoid this problem, you should add a button to your notification that will call trigger.stop()
to stop the system scan. On the other hand, you can use displayOnlyOnce()
method when building the ProximityTrigger
object - this will fire your notification only once, and then you will need to call start()
again.
Known problems: The scan registration gets cancelled when user disables bluetooth and WiFi on his phone. After that, the trigger may not work, and your app will need to be opened once again to reschedule the
ProximityTrigger
.This feature is still experimental and in development.
Since the version 0.5.0
the ProximityObserver
will persist necessary data locally, so that when there is no internet access, it may still be able to do proximity observation using that data. The only need is to call proximityObserver.start()
at least once when the internet connection is available - it will fetch all the necessary data from the Estimote Cloud, and will store them locally for the later use.
Use case: Getting sensors data from your Estimote beacons.
You can easily scan for raw Estimote Telemetry
packets that contain your beacons' sensor data. All this data is broadcasted in the two separate sub-packets, called frame A
and frame B
. Our SDK allows you to scan for both of them separately, or to scan for the whole merged data at once (containing frame A and B data, and also the full device identifier).
Here is how to launch scanning for full telemetry data:
// KOTLIN
bluetoothScanner = EstimoteBluetoothScannerFactory(applicationContext).getSimpleScanner()
telemetryFullScanHandler =
bluetoothScanner
.estimoteTelemetryFullScan()
.withOnPacketFoundAction {
Log.d("Full Telemetry", "Got Full Telemetry packet: $it")
}
.withOnScanErrorAction {
Log.e("Full Telemetry", "Full Telemetry scan failed: $it")
}
.start()
You can use telemetryFullScanHandler.stop()
to stop the scanning. Similarily to the ProximityObserver
you can also start this scan in the foreground service using getScannerWithForegroundService(notification)
method instead of .getSimpleScanner()
.
Basic info about possible scanning modes:
estimoteTelemetryFullScan()
- contains merged data from frame A and B, as well as full device id. Will be less frequently reported than individual frames.
estimoteTelemetryFrameAScan()
- data from frame A + short device id. Reported on every new frame A.
estimoteTelemetryFrameBScan()
- data from frame B + short device id. Reported on every new frame B.
Tip: Read more about the Estimote Telemetry protocol specification here. You can also check our tutorial about how to use the telemetry scanning on your Android Things device (RaspberryPi 3.0 for example).
IMPORTANT: Since version 1.0.8 targeting Android SDK 31+ requires new permissions, that are not (yet) checked by RequirementsWizard. Please make sure your app has granted access to following permissions:
- android.permission.BLUETOOTH_CONNECT,
- android.permission.BLUETOOTH_SCAN,
- android.permission.ACCESS_FINE_LOCATION
Use case: Making sure that everything needed for Bluetooth scanning to work is set up - the user has Bluetooth enabled, location permissions were granted, etc. Displaying default popup dialogs to enable Bluetooth and give needed permissions.
The ProximityObserver
won't work without the certain requirements fulfilled. Bluetooth needs to be enabled on a phone, Location permissions need to be granted, etc. You can do this either manually, by checking this before starting the ProximityObserver
, or use our support library named Mustard, which contains handy Kotlin recipes for Android's UI-related stuff.
The RequirementsWizard
comes in handy, when you need to check all the necessary requirements. It will automatically display default dialogs for the user to enable needed stuff (like bluetooth) for you.
Mustard
support library to your module's build.gradle
file:implementation 'com.estimote:mustard:0.2.1'
RequirementsWizard
before starting the ProximityObserver
:// KOTLIN
RequirementsWizardFactory.createEstimoteRequirementsWizard().fulfillRequirements(
YOUR_ACTIVITY_CONTEXT_HERE,
onRequirementsFulfilled : { /* start the ProximityObserver here! */ },
onRequirementsMissing: { /* scanning won't work, handle this case in your app */ },
onError: { /* Oops, some error occurred, handle it here! */ })
// JAVA
RequirementsWizardFactory.createEstimoteRequirementsWizard().fulfillRequirements(
this,
new Function0<Unit>() {
@Override
public Unit invoke() {
proximityObserver.addProximityZone(venueZone).start();
return null;
}
},
new Function1<List<? extends Requirement>, Unit>() {
@Override
public Unit invoke(List<? extends Requirement> requirements) {
/* scanning won't work, handle this case in your app */
return null;
}
},
new Function1<Throwable, Unit>() {
@Override
public Unit invoke(Throwable throwable) {
/* Oops, some error occurred, handle it here! */ }
return null;
}
});
Why a separate library? - Mustard library depends on Android support libraries to display proper dialogs for the user. Some of you might don't want to add additional Android support libraries to your project, or some unwanted version confilicts might appear. This is why we decided to keep it as a separate thing.
Why "Mustard"? - The name "Kotlin" is coincidentally the same as the popular brand of ketchup in Poland. This is why we named our first support library "Ketchup". It's basically a place for our Kotlin/RX utils shared across our stack. When we decided to create a separate library for UI-related stuff, we thought of how much we love hot-dogs. And you know, hot-dogs come best with both ketchup and mustard :)
If you want to use ProGuard
with our SDK, make sure to add additional rules to your proguard-rules.pro
file.
-keepattributes Signature, InternalClasses, Exceptions
-keep class com.estimote.proximity_sdk.internals.proximity.cloud.model.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontwarn retrofit2.Platform$Java8
-dontwarn kotlin.**
To get a working prototype, download a ready-made app template in the Estimote Cloud. App ID & App Token credentials are generated automatically.
Demos require Estimote Beacons configured with Estimote Monitoring.
Our Kdoc is available here.
At Estimote we're massive believers in feedback! Here are some common ways to share your thoughts with us:
To see what has changed in recent versions of our SDK, see the CHANGELOG.