Bluetooth scanning and advertising with LTE Beacon

If there’s one thing all Estimote Beacons have in common, it’s their Bluetooth capabilities.

The LTE Beacon is no different. It can advertise any Bluetooth data, and it can also detect advertisements from other Bluetooth devices.

What’s ahead (aka Table of Contents)


Before we jump into the scanning examples, we’ll need something to scan for. If you already have any other Estimote Beacons, or maybe some other iBeacon-compatible beacons, you should be all set!

If you don’t, you can make one of your LTE Beacon advertise, and then we’ll have the other one scan and detect it.

In Estimote Cloud, create a new IoT App, call it “Advertising”, and use this micro-app code:

var packet =;
var handle = ble.advertise(packet);

This will make the LTE Beacon broadcast an Estimote Location packet with the LTE Beacon’s identifier. We also set this to happen every 500 ms, and the transmit power to -4 dBm.

Tip: -4 is relatively strong, so the beacon should be detectable at a longer distance. Other options are, from the weakest to the strongest: -40, -20, -16, -12, -8, -4, 0, 4. You can experiment with them if you want, to find something that suits you!


Let’s look at two examples of how you can use the Bluetooth scanning in your LTE Beacon.

We’ll assume you either have some Estimote Proximity or Location beacons nearby with Estimote Monitoring enabled, or that you programmed one of your LTE Beacons to broadcast the Estimote Location packet as shown above.

Asset tracking example

Our idea here is, every hour, let’s start Bluetooth scanning for a short moment, detect all the other beacons nearby, and queue an “assets-update” event with the identifiers of the beacons we found.

var SCAN_INTERVAL = 60 * 60 * 1000; // 60 minutes in milliseconds
var SCAN_DURATION = 10 * 1000; // 10 seconds in milliseconds

var detectedBeacons = {};

timers.repeat(SCAN_INTERVAL, () => {
        /* 1 */ (scanResult) => {
            var packet = ble.parse(scanResult);
            if (packet && packet.type == 'est_loc') {
                detectedBeacons[] = true;
        /* 2 */ SCAN_DURATION,
        /* 3 */ () => {
            cloud.enqueue('assets-update', detectedBeacons.keys());
            detectedBeacons = {};

Let’s go over the 3 arguments we pass to the startScan function:

  1. First comes the callback function to handle scan results. We try to parse the raw data, and if the result is a valid Estimote Location packet, we add the identifier from that packet to detectedBeacons.

    We use a JavaScript object instead of an array for detectedBeacons, as a simple way to handle duplicate detections.

  2. Second arguments determines how the long the scan will run.

  3. Third argument is a function to call when the scan stops. This gives us the perfect opportunity to “sum up” our findings, and enqueue the “assets-update” event.

    We use detectedBeacons.keys() to transform our JavaScript object to a simple array of identifiers. In other words, it’ll go from something like this:

    {"1a15b246a7190f625c4fdbf29b030f06": true,
     "f04c16c4905ae0790bcd4302da86762a": true}

    to something like this:

    ["1a15b246a7190f625c4fdbf29b030f06", "f04c16c4905ae0790bcd4302da86762a"]

    Finally, we reset detectedBeacons back to an empty object, so that next time startScan runs, old data won’t mix with the new.

And, since the whole startScan code is inside timers.repeat, it’ll run periodically—in our case, every hour. You can adjust the SCAN_INTERVAL in the code above if you want, but remember that the more frequently you scan, the shorter bettery life of your LTE Beacon is going to be. You may want to consider just keeping it plugged to power.

One last thing to consider: when/how often to sync the “assets-update” event to Estimote Cloud. You could opt to do this periodicially, for example, once a day:

var oneDay = 24 * 60 * 60; // sync period is in seconds

Or, you could force a sync on every event:

cloud.enqueue('assets-update', detectedBeacons.keys());; // <== add this below the 'enqueue'

Just remember about the battery life implications, and that both the trial and the base-tier Estimote Cloud subscription come with 100 syncs per each LTE Beacon per month.

Simple indoor positioning example

Another idea is: just like the LTE Beacon can determine its position outdoors via GPS and satellites, it can also determine its position indoors via Bluetooth and other beacons deployed throughout the venue. This is very similar to smartphone apps and Estimote’s Proximity and Indoor Location SDKs!

There’s more than one way to approach this, but let’s try something simple: upon a button press, the LTE Beacon will scan for other Estimote Beacons nearby. When it finds one, it’ll queue an “indoor-position-update” event, and immediately sync it to Estimote Cloud.

var SCAN_DURATION = 10 * 1000; // 10 seconds in milliseconds

var foundBeacon = null; => {
    var bleScan = ble.startScan(
        /* 1 */ (scanResult) => {
            var packet = ble.parse(scanResult);
            if (packet && packet.type == 'est_loc') {
                foundBeacon =;
        /* 2 */ SCAN_DURATION,
        /* 3 */ () => {
            cloud.enqueue('indoor-position-update', foundBeacon);
            foundBeacon = null;

As you can see, the code is actually pretty similar to the “asset tracking” example. It’s mostly the situation that’s different: instead of a static LTE Beacon scanning for roaming beacons–assets, here we assume a roaming LTE Beacon scanning for static beacons–anchor-points.

In any case, let’s once again look at the 3 arguments we pass to the startScan function:

  1. First, in the scan-handling callback, we say that when the LTE Beacon finds a valid Estimote Location packet, it should store the identifier from that beacon under foundBeacon, and stop the scan.

  2. Second, we say the the scan should run for SCAN_DURATION (here: 10 seconds). Note that in this example, the scan can also stop sooner, per our code in the scan-handling callback from #1. So the 10 seconds here mean that if we can’t find anything in that time, we’ll give up and foundBeacon will remain null.

  3. Finally, some code to run when the scanning stops. Note that it doesn’t matter if the scan stopped because it’s run its course (= 10 seconds), or because we stopped it ourselves in #1, with bleScan.stop(). That’s the nice thing about this stop-handling callback!

    Here we queue an “indoor-position-update” event, and attach the foundBeacon to it—which will be either an identifier of a beacon we detected, or null if we haven’t found anything within the 10 seconds. We also immediately force a sync to Estimote Cloud. And, after all that, we reset foundBeacon back to null.

You could try to further improve this solution by adding some basic RSSI filtering to pick a beacon with the strongest signal. You can access the RSSI via scanResult.rssi. Consider this excercise a completely optional homework ;-)