Beacon Bulk Updater

We’re always looking for solutions to make it easier to manage bulk quantities of beacons. And let’s face it, the easiest way to do something is when it happens all by itself. And that’s exactly what the Bulk Updater is for. The aim of this function is simple: to let you apply updates to your beacons without the need to connect to them individually. In short, once you have the BulkUpdater function from our SDK included in your app, all you need to do is start it, and it will automatically detect all of your beacons in range of the device it’s running on, and sync all settings changes with your Estimote Cloud. It takes just 5 minutes, on average, to update 40 beacons — that’s just a few seconds for one beacon.

And to top it off, it can even update the beacons’ firmware for you!

What does it do in practical terms? Let’s see:

  • you can very quickly apply any settings changes to your beacon dev kit;
  • you can update your entire indoor location infrastructure with new settings pretty much just walking around and admiring the scenery—since you don’t even need to look at your phone;
  • and you can pre-configure your bulkbag of beacons in mere minutes, significantly cutting down the preparation time for deployment.

What’s ahead (aka Table of Contents)


  • 1 x Mac with Xcode OR 1 x computer with Android Studio.
  • 1 x iPhone 4S or newer OR 1 x device with Android 4.3 or newer with BLE support.
  • 1 x Estimote Account (sign up here).
  • 2 or more Estimote Beacons (hardware revision G1.8, F2.3, or later) assigned to your account.

iOS: Create your Xcode project and add the Estimote SDK

Start with either your existing app, or with a new, blank project in Xcode (to keep things simple, let’s call it Bulkr). Once the project is ready, be sure to import the Estimote SDK. To do that:

  • Download the Github iOS repo
  • Create a new group named Estimote in your project
  • Drag the EstimoteSDK.framework file into the Estimote group.

Configuring a blank Xcode project

Swift users: add an Objective-C bridging header

Estimote SDK is written in Objective-C. It still works perfectly fine with Swift projects, but you need to take the additional step of adding a bridging header:

Right-click on the project’s group in the project navigator, and choose “New File…” Pick a “Header File” from the “iOS - Source” section, and save it as ObjCBridge.h. Add #import <EstimoteSDK/EstimoteSDK.h> to the newly created file. Select your project in the navigator and go to “Build Settings.” Find the “Objective-C Bridging Header” setting and set it to ${PROJECT_NAME}/ObjCBridge.h. If you can’t find the “Swift Compiler” section in “Build Settings,” you might not have any Swift elements in your project.

Android: Create a new project and add the Estimote SDK

Start by creating a new Android Studio project. Name it however you want; “TimeSaver” might be a good one. Set “Minimum SDK” to “API 18: Android 4.3 (Jelly Bean)” and choose “Blank Activity” to begin with.

Open the “build.gradle (Module: app)” Gradle Script and declare Estimote SDK dependency:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile ''

    // add the following line, and replace "1.0.1" with the latest version
    // of Estimote Android SDK; you'll find the latest version number on:
    compile 'com.estimote:sdk:1.0.1@aar'

Android Studio will now show a “Gradle files have changed since last project sync …” warning. Just click “Sync Now” and the Estimote SDK will be automatically added to the project.

Runtime permissions (Android 6.0 or later)

There’s one extra step you need to perform if your app’s targetSdkVersion is 23 (i.e., Android 6.0) or greater—and that’s to request the ACCESS_COARSE_LOCATION permission at runtime. You can read more in Google’s Requesting Permissions at Run Time article, or in the Android 6.0 and runtime permissions section of our own Estimote Android SDK readme.

We can’t automatically request these permissions for you, like we do with the manifest file, but we do provide helper methods to make it really easy. One of these methods is SystemRequirementsChecker’s checkWithDefaultDialogs. It goes through a predefined checklist and makes sure everything is accounted for, including the runtime permissions (and also things like, is Bluetooth on, is Location on, etc.)

Let’s call it in the MainActivity:

protected void onResume() {

Info: The checkWithDefaultDialogs will use default system dialogs if it needs to ask the user for anything—e.g., to turn Bluetooth or Location on, or … yes, ask for ACCESS_COARSE_LOCATION permission.

This is great for this tutorial, as we want to get up and running quickly—but in a production app, you might want to design your own UI, or even an entire onboarding process, to explain why your app requires location and Bluetooth, and what’s in it for the user.

The check method might be a better option then. It won’t trigger any dialogs, but will simply report anything that’s missing to a callback—leaving how to handle it up to you.

Configure your beacons

Adjust the settings of your beacons through the Estimote Cloud. Remember to “Save” them once you’re done! These will create pending settings for your beacon. These need to be applied to the beacon. If not for the BulkUpdater, you’d need to connect to each beacon manually.

It’s also possible to apply firmware update through the BulkUpdater. This will make the process somewhat longer, so you might want to schedule it at a convenient time. For reference, it’s taken us 1-1.5 minutes to update the firmware on a single beacon this way.

Note: Firmware is updated only if the beacon’s Automatic Firmware Update setting is ON and the BulkUpdater’s skipFirmwareUpdateStep flag is set to FALSE.

Run the Bulk Updater

The BulkUpdater has two modes of operation: fully automatic, and manual.

Automatic bulk updates

Let’s start with the simpler option, since it will often cover most of your needs, and requires a bare minimum of effort. This mode will apply all the settings you’ve configured in your Cloud account for all your beacons. It’s a quicker way to get going, but the tradeoff is a lower degree of fine control.


Once you’ve set up your beacon configuration, either through the Estimote Cloud or through the Cloud API, just include the Estimote SDK which—contains BulkUpdater feature—in your app and initialize the ESTLocationBeaconBulkUpdater with start.

// Declare Bulk Updater as a property
let bulkUpdater = ESTLocationBeaconBulkUpdater()

// Set it up and start it in your viewDidLoad like this:
bulkUpdater.delegate = self
// Declare Bulk Updater as a property
@property (nonatomic, strong) ESTLocationBeaconBulkUpdater *bulkUpdater;

// Set it up and start it in your viewDidLoad like this:
self.bulkUpdater = [ESTLocationBeaconBulkUpdater new];
self.bulkUpdater.delegate = self;
[self.bulkUpdater start];

This will make the Bulk Updater run a bit like the Duracell bunny—it will keep going, and going, and going… until you explicitly cancel it. To avoid that, you might want to start it with timeout—this lets you specify the time after which the Bulk Updater will stop looking for beacons to update. Addtionally, you can also set the fetchInterval, i.e. specify the time between the Bulk Updater’s checking for new pending settings in your Esitmote Cloud account.


The Android implementation follows the same approach. You’ll be able to fetch all the settings that are pending in your account, and quickly apply them to your beacons. To begin with, build the BulkUpdater object as follows:

BulkUpdater bulkUpdater = new BulkUpdaterBuilder(this)
    .withCloudFetchInterval(5, TimeUnit.SECONDS)

For a detailed description of the parameters, check out our repo’s docs.

You can then start listening for beacons (a.k.a ConfigurableDevices), and have your code react to each device status change. You can pass a listener while starting the BulkUpdater, and we recommend starting it in your Activity’s onResume() method.

protected void onResume() {

    bulkUpdater.start(new BulkUpdater.BulkUpdaterCallback() {
        public void onDeviceStatusChange(ConfigurableDevice device,
                BulkUpdater.Status newStatus, String message) {
            Log.d("BulkUpdater", device.deviceId + ": " + newStatus);

        public void onFinished(int updatedCount, int failedCount) {
            Log.d("BulkUpdater", "Finished. Updated: " +
                    updatedCount + " Failed: " + failedCount );

        public void onError(DeviceConnectionException e) {
            Log.d("BulkUpdater", "Error: " + e.getMessage());

You can also stop bulk updater whenever you want. Just use the stop() method:


Don’t forget : Because BulkUpdater uses an underlying service for connecting to devices, so it is necessary to call destroy() in your activity’s onDestroy method. This will prevent any memory leaks.

protected void onDestroy() {

Manual bulk operations


You might be wondering why we provide two modes, since the automatic one seems to cover most needs. Well, the second mode is for those of us who want greater control over the specific settings that are applied. Let’s say there are certain settings you’ve configured in your Cloud account that you don’t want applied just yet—the manual mode lets you skip them. With this mode, you can also choose the specific beacons you want updated. This works great for testing, for instance.

Additonally, with manual operation you have greater visiblility into the progress of the update process.

And finally, you can select the interval at which new settings are read from the cloud. This is what we refer to as the fetch interval, and its function is just that—to define how frequently the BulkUpdater function checks for new settings to be applied.

In short, this mode is the one for you, if you’re looking for more granular control and visibility, as opposed to the automatic mode, which is more fire-and-forget (just don’t actually forget that it’s running!).

Now, let’s imagine you’d like to set the value of Major for all your beacons to 102. This is how you’d go about doing that:

func customConfigurations() -> [ESTLocationBeaconBulkUpdateConfiguration] {
    let beaconIdentifiers = ["02bd06ced9e57f8945b881c11e6d5518",

    // Prepare configurations for each of them
    var configurations = [ESTLocationBeaconBulkUpdateConfiguration]()

    for identifier in beaconIdentifiers {
        // Prepare an operation for changing major to 102
        let major: ushort = 102;
        let majorSetting = ESTSettingIBeaconMajor(value: major)!
        let majorOperation = ESTBeaconOperationIBeaconMajor.writeOperation(
                withSetting: majorSetting, completion: { (setting, error) in
            // handle the completion here

        let configuration = ESTLocationBeaconBulkUpdateConfiguration(
            deviceIdentifier: identifier,
            settingsOperations: [enableOperation, majorOperation],
            firmwareUpdateAvailable: false)

    return configurations
- (NSArray<ESTLocationBeaconBulkUpdateConfiguration *> *)customConfigurations {
    NSArray *beaconIdentifiers = @[@"833bad3133274abaa47cfd5c59b1432e",

    // Prepare configurations for each of them
    NSMutableArray<ESTLocationBeaconBulkUpdateConfiguration *> *configurations =
        [NSMutableArray new];

    for (NSString *identifier in beaconIdentifiers) {
        // Prepare an operation for changing major to 102
        unsigned short major = 102;
        ESTSettingIBeaconMajor *majorSetting =
            [[ESTSettingIBeaconMajor alloc] initWithValue:major];
        ESTBeaconOperationIBeaconMajor *majorOperation =
             completion:^(ESTSettingIBeaconMajor * _Nullable major,
                          NSError * _Nullable error) {
                 // handle the completion here

        ESTLocationBeaconBulkUpdateConfiguration *configuration =
            [[ESTLocationBeaconBulkUpdateConfiguration alloc]
             settingsOperations:@[enableOperation, majorOperation]
        [configurations addObject:configuration];

    return [NSArray arrayWithArray:configurations];

And finally, to initialize the BulkUpdater, include

self.bulkUpdater.start(with: self.customConfigurations())
[self.bulkUpdater startWithUpdateConfigurations:[self customConfigurations]];


You can get similar results on Android, just incorporate POST calls to our API for the specific devices you want to focus on.

In other words:

  • determine which devices you want to update
  • prepare the relevant settings and format them as a JSON
  • send them to the Estimote Cloud
  • run BulkUpdater, which will fetch the new configuration and apply it to the relevant beacons.

This mode—manual bulk updates—is what we’re using in our Deployment App for iOS.

Info: You can fetch the details of multiple devices on every fetch interval by incorporating a request to our API at /v2/devices?ids=. The easiest way to do it on iOS is to use the ESTRequestV2DevicesUpdate method, as documented here.

For a detailed description of the class, visit our SDK reference for iOS and Android and the Readme of our SDK repos: