Connecting to Estimote Beacons

Most of the time, Estimote Beacons happily advertise their packets all around them, for any BLE-enabled device in range to detect those packets. There’s no explicit connection between the beacon and the smartphone or other receiver.

You can however establish a connection between your smartphone and the beacon. This mostly allows you to access the configuration options of the beacons, but also to access certain other features—e.g., control the output of the GPIO ports, read and write data from the beacon’s EEPROM memory, etc.

In this guide, we’ll learn how to use the Estimote iOS and Android SDKs to establish the connection.

What’s ahead (aka Table of Contents)

Prerequisites

  • iOS or Android device with Bluetooth 4.0 support.
  • Location (hardware revision “F” or “I”) or next-generation Proximity (hardware revision “G”) Beacons.
    • Note: connecting to “legacy” Proximity Beacons (hardware revision “D”) works a bit differently, and we don’t cover it in this guide. Consult the SDK documentation for that.
  • Estimote Account (sign up here).
  • Estimote iOS SDK (≥ 4.1.0) or Android SDK (≥ 0.8.0).

Connection process explained

Our SDKs abstract most of the connection process away, so that you don’t need to know or worry about the details. But we’re all curious people, aren’t we? Yes, we thought so. Here’s what exactly is going on when you’re connecting to an Estimote Beacons:

  1. Establishing Bluetooth connection. Seems pretty straightforward, right? Estimote Beacons are Bluetooth Low Energy devices, so if you want to connect to them, you do it over Bluetooth. Note that this is slightly different from “pairing” with a “Classic” Bluetooth device, but we won’t bore you with the details. What we will emphasize though is that even if your beacons are set to 200 m advertising range, Bluetooth connections work best and most reliably when pretty close—up to a few meters.

  2. Authorization. It wouldn’t be fun if everyone could simply connect to your beacon and change its settings, would it? Luckily, just like going to gmail.com is not enough to access somebody’s emails—you still need to know the password—it works the same with Estimote Beacons. Think about it like this: each beacon has a unique “password” programmed into it in our factory, and that “password” is stored securely on your Estimote Cloud account. When you give your iOS or Android app access to your Cloud account, by setting the App ID and App Token, our SDK will automatically retrieve that “password” and perform the authorization.

    This is also why if you try to connect to an Estimote Beacon from a generic BLE scanner, you’ll likely see the beacon disconnecting from your after 10 seconds—which is how long the beacon waits for the authorization to happen. (No worries, during those 10 seconds, it’s still impossible to access any options!)

  3. Estimote Cloud synchronization. Estimote Cloud is the ultimate source of truth when it comes to your beacons’ settings. After successful authorization, our SDKs will make sure that the beacon’s settings match what’s in the Cloud (overriding any mis-matched settings if needed). This is also when the “pending settings” are being applied.

Okay, now that you know what’s going on “under the hood”, it’s time to learn how to actually use the Estimote SDK to initiate all that!

iOS

Step 1: Detect the beacon you want to connect to

This is slightly different than your regular detection of iBeacon or Eddystone or Telemetry packets. Well, not that different—you simply deal with a special Estimote Connectivity packet instead.

Info: Estimote Beacons can broadcast many different packets in many configurations, and having a dedicated packet for connectivity makes sure that you can always connect to your beacon, no matter what. Estimote Connectivity is always on. Even if you disable all the other packets. Even if you enable conditional broadcasting (e.g., motion-only). Even if you enable power-saving modes (e.g., scheduled advertising).

We’ll use ESTDeviceManager to discover an ESTDeviceLocationBeacon object, which represents the beacon.

Info: It’s called ESTDeviceLocationBeacon, but you can use it for both Location Beacons and the next-gen Proximity Beacons. Yes, we know it’s a bit weird. Yes, we want to fix that in the future. For now, please trust us on this one ¯\_(ツ)_/¯

Tip: Even though not covered in this tutorial, you can also use ESTDeviceManager to discover ESTDeviceNearable objects, and this allows you to connect to and manage your Estimote Stickers.

class ViewController: UIViewController, ESTDeviceManagerDelegate,
                      // we'll use the DeviceConnectable delegate later
                      ESTDeviceConnectableDelegate {

    var deviceManager: ESTDeviceManager!
    var device: ESTDeviceLocationBeacon!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.deviceManager = ESTDeviceManager()
        self.deviceManager.delegate = self

        let deviceIdentifier = "36a0066e4f0ca3fa41c4bfd6dbd6c920"
        let deviceFilter = ESTDeviceFilterLocationBeacon(
                identifier: deviceIdentifier)
        self.deviceManager.startDeviceDiscovery(with: deviceFilter)
    }

    func deviceManager(_ manager: ESTDeviceManager,
                       didDiscoverDevices devices: [ESTDevice]) {
        guard let device = devices.first as? ESTDeviceLocationBeacon else { return }
        self.deviceManager.stopDeviceDiscovery()
        self.device = device // to be continued ...
    }
}
@interface ViewController () <ESTDeviceManagerDelegate,
                              // we'll use the DeviceConnectable delegate later
                              ESTDeviceConnectableDelegate>

@property (nonatomic, strong) ESTDeviceManager *deviceManager;
@property (nonatomic, strong) ESTDeviceLocationBeacon *device;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.deviceManager = [ESTDeviceManager new];
    self.deviceManager.delegate = self;

    NSString *deviceIdentifier = @"36a0066e4f0ca3fa41c4bfd6dbd6c920";
    ESTDeviceFilterLocationBeacon *deviceFilter =
        [[ESTDeviceFilterLocationBeacon alloc] initWithIdentifier:deviceIdentifier];
    [self.deviceManager startDeviceDiscoveryWithFilter:deviceFilter];
}

- (void)deviceManager:(ESTDeviceManager *)manager
   didDiscoverDevices:(NSArray<ESTDevice *> *)devices {

     ESTDevice *device = devices.firstObject;
     if (![device isKindOfClass:[ESTDeviceLocationBeacon class]]) { return; }

     [self.deviceManager stopDeviceDiscovery];
     self.device = (ESTDeviceLocationBeacon *)device; // to be continued ...
}

@end

Now we have a ESTDeviceLocationBeacon safely stored in the self.device property, waiting for the next step.

Step 2: Connect to the beacon

This step is really all about appointing a delegate to handle the connection callbacks:

// add these methods inside our ViewController class

func estDeviceConnectionDidSucceed(_ device: ESTDeviceConnectable) {
    print("Connected")
}

func estDevice(_ device: ESTDeviceConnectable,
               didFailConnectionWithError error: Error) {
    print("Connnection failed with error: \(error)")
}

func estDevice(_ device: ESTDeviceConnectable,
               didDisconnectWithError error: Error?) {
    print("Disconnected")
    // disconnection can happen via the `disconnect` method
    //     => in which case `error` will be nil
    // or for other reasons
    //     => in which case `error` will say what went wrong
}
// add these methods inside our ViewController implementation

- (void)estDeviceConnectionDidSucceed:(ESTDeviceConnectable *)device {
    NSLog(@"Connected");
}

- (void)estDevice:(ESTDeviceConnectable *)device
didFailConnectionWithError:(NSError *)error {
    NSLog(@"Connnection failed with error: %@", error);
}

- (void)estDevice:(ESTDeviceConnectable *)device
didDisconnectWithError:(NSError *)error {
    NSLog(@"Disconnected");
    // disconnection can happen via the `disconnect` method
    //     => in which case `error` will be nil
    // or for other reasons
    //     => in which case `error` will say what went wrong
}

… and then connect-ing to the beacon:

// => inside the `didDiscoverDevices` method
// this is where we left off in step 1:
self.device = device
// add this below:
self.device.delegate = self
self.device.connect()
// => inside the `didDiscoverDevices` method
// this is where we left off in step 1:
self.device = (ESTDeviceLocationBeacon *)device;
// add this below:
self.device.delegate = self;
[self.device connect];

Now, as soon as we discover the beacon (thanks to all the step 1 code), we initiate an asynchronous connection process. The Estimote SDK will let us know when it succeeds (or fails) via the delegate methods implemented above.

Important: Since the connection process is asynchronous, it’s important that we store a strong reference to our ESTDeviceLocationBeacon object inside our class property. Otherwise, as soon as the execution leaves the didDiscoverDevices method, that object would get deallocated, interrupting the connection process.

If you’re wondering why despite calling connect you’re not getting any calls to your delegate methods, double-check for the strong reference!

What next? Take a look at the documentation of our settings APIs.

Android

Step 1: Detect the beacon you want to connect to

This is slightly different than your regular detection of iBeacon or Eddystone or Telemetry packets. Well, not that different—you simply deal with a special Estimote Connectivity packet instead.

Info: Estimote Beacons can broadcast many different packets in many configurations, and having a dedicated packet for connectivity makes sure that you can always connect to your beacon, no matter what. Estimote Connectivity is always on. Even if you disable all the other packets. Even if you enable conditional broadcasting (e.g., motion-only). Even if you enable power-saving modes (e.g., scheduled advertising).

We’ll use ConfigurableDevicesScanner to discover a ConfigurableDevice object, which represents the beacon.

ConfigurableDevicesScanner deviceScanner = new ConfigurableDevicesScanner(context);

// Only report your own devices. You need to set App ID and Token for this, so
// that the SDK can download the list of your own devices from your Cloud account.
deviceScanner.setOwnDevicesFiltering(true);
// Only report Location Beacons and next-gen Proximity Beacons.
// Yes, we know it's a bit weird that "LOCATION_BEACON" also discovers Proximity
// Beacons. Yes, we want to fix that in the future. For now, please trust us on
// this one ¯\_(ツ)_/¯
deviceScanner.setDeviceTypes(DeviceType.LOCATION_BEACON);

deviceScanner.scanForDevices(new ConfigurableDevicesScanner.ScannerCallback() {
    @Override
    public void onDevicesFound(List<ConfigurableDevicesScanner
                                    .ScanResultItem> devices) {
        String deviceIdentifier = "36a0066e4f0ca3fa41c4bfd6dbd6c920";
        for (ScanResultItem item : devices) {
            if (item.device.deviceId.toString().equals(deviceIdentifier)) {
                this.deviceToConnectTo = item.device;
                break;
            }
        }
    }
});

Now we have a ConfigurableDevice safely stored in the this.deviceToConnectTo property, waiting for the next step.

Keep in mind: ConfigurableDevicesScanner should not be used for long-term scanning in the background, as it uses low latency scanning—which drains quite some energy. A good practice is to start it in your activity’s onResume, and remember to stop it in onPause.

Step 2: Connect to the beacon

In this step, we’ll use a DeviceConnectionProvider, and feed it the ConfigurableDevice obtained in the previous step, to connect to the beacon.

Before we connect to the beacon however, we have to connect to … the Connection Provider! (This is because Connection Provider creates a simple service, in order to reliably handle multiple beacon connections being open at the same time.)

We can do that in our activity’s onCreate:

@Override
protected void onCreate(Bundle savedInstanceState) {
    DeviceConnectionProvider connectionProvider = new DeviceConnectionProvider(this);
    connectionProvider.connectToService(
            new DeviceConnectionProvider.ConnectionProviderCallback() {
        @Override
        public void onConnectedToService() {
            this.connectedToTheConnectionProvider = true;
        }
    });
}

Remember to clean up in your activity’s onDestroy:

@Override
protected void onDestroy() {
    connectionProvider.destroy();
    super.onDestroy();
}

Once we’re connected to the Connection Provider, we can connect to the beacon:

if (this.connectedToTheConnectionProvider) {
    DeviceConnection connection = connectionProvider.getConnection(
            this.deviceToConnectTo);
    connection.connect(new DeviceConnectionCallback() {
        @Override
        public void onConnected() {
            Log.d("DeviceConnection", "Connected");
        }

        @Override
        public void onDisconnected() {
            Log.d("DeviceConnection", "Disconnected");
        }

        @Override
        public void onConnectionFailed(DeviceConnectionException exception) {
            Log.d("DeviceConnection",
                    "Connection failed with error: " + exception.toString());
        }
    });  
}

With this code, we make sure that we’re connected to the Connection Provider, then we take the deviceToConnectTo obtained in step 1, and we initiate the connection. The Estimote SDK will let us know when it succeeds (or fails) via the callback methods.

What next? Read what you can do when connected.

Improving connection performance

There’s a few extra features available to make the process of connecting to a beacon more smooth … which can make a huge difference when you want to configure, say, 100 beacons.

Near to Connect

Estimote Beacons are really smart and can detect when a smartphone is very close to them. With Near to Connect enabled, when you’re within one meter of a beacon, it will advertise its Connectivity packet with much stronger broadcasting power and lower interval. This should significantly cut the time it takes to discover the beacon with the ESTDeviceManager or ConfigurableDevicesScanner.

Shake to Connect

Shake to Connect, when enabled, makes the beacon broadcast its Connectivity packet with much stronger power and lower advertising interval … when you shake it 3 times! On Location Beacons, you’ll know it worked when the LED on the beacon starts blinking very rapidly. With stronger broadcasting power and lower advertising interval, it’ll take less time for the ESTDeviceManager or ConfigurableDevicesScanner to discover the beacon.

Keep in mind: This feature is disabled by default. To turn it on:

Of course, you need to connect to the beacon first, but you already know how to do that, don’t you? (-:

Connection troubleshooting

iOS-only for now. Android version coming soon!

  • My device is not detected with the ESTDeviceManager scan:

    • Is Bluetooth turned on? (-:
    • Double-check the filters. If you provide identifier in the filter, make sure there are no typos.
    • Are you trying to connect to Proximity Beacons with hardware revision “D”? This first generation of Proximity Beacons has a different API for connection: ESTBeaconConnection.
  • Connection fails with one of these errors:

    • “Cloud ownership verification failed”

      • Remember to set an App ID and Token:
        [ESTConfig setupAppID:@"app-id" andAppToken:@"app-token"]
        Without it, the SDK can’t access your Estimote Cloud account, where the connection keys reside.
      • Double-check if the App ID and Token are valid. Typos are the nightmare of all programmers!
      • Does the beacon you’re trying to connect to appear on your Estimote Cloud dashboard? If not, it means the beacon is not assigned to your account. Learn more about beacon ownership.
    • “Connection to the device over Bluetooth failed” and “Bluetooth services discovery failed”

      • It may have been a temporary fluke—try again.
      • Try moving closer to the beacon.
      • In rare cases, it might be a problem with the iOS Bluetooth stack. Try restarting Bluetooth and/or your device.
    • “Device authorization with Estimote Cloud failed.”

      • Most commonly, something went wrong with the beacon and Estimote Cloud exchanging authorization data, e.g., due to unstable Internet connection, or a strict firewall, etc.
    • “Device settings synchronization with Estimote Cloud failed.”

      • Something went wrong with the write operations we automatically perform after establishing the connection, to synchronize the beacon’s settings with Estimote Cloud’s. This sometimes means there’s some problem with our records of the beacon’s settings in Estimote Cloud. Shoot us an email, and we’ll check it out.

Here’s how you can contact us if connection keeps failing: contact@estimote.com. Or, post on our forums for a blazing-fast response from our community or ourselves.