Library for scanning BLE beacons (iBeacons, Eddystone, others)

I recently had a project where I needed to scan BLE beacons, so decided to make it into a more general purpose library for scanning multiple types of beacons.

The library is called BeaconScanner, and can scan and publish (or return a Vector) of iBeacons, Eddystone beacons, or custom Kontakt asset tags.

Here’s the source code:

And an application note using it:

7 Likes

Nice!

Very good. Please keep the libraries and ideas for BLE coming.

1 Like

Trying to get rssi from a tlm beacon. Not having any luck modifying the library to add it.

Never mind I got it working once I made Particle Workbench use a local copy of the library.

@mariano
I have a project where 5 Argon are communicating to Boron via BLE. They mostly sleep and wake on INT. What I have noticed is that when the BORON drops cellular, the BLE stops working reliably.

I am wondering what happens in the case you present using beacons? Do you see similar behavior when the cellular is dropped on the Boron?

Thank you in advance

@jackpot

What I have noticed is that when the BORON drops cellular, the BLE stops working reliably.

I haven't seen this behavior. BLE works even with Cellular.off() called. Are you seeing the issue when turning off cellular, or when cellular connectivity drops? Is it possible that the device is trying to reconnect to cellular, and blocking the loop? If so, make sure that you are running with SYSTEM_THREAD(ENABLED)

https://docs.particle.io/reference/device-os/firmware/boron/#system-thread

@mariano

Thanks for the reply. I am in fact using SYSTEM_THREAD(ENABLED). I am seeing the issue when the cellular connection drops.

1 Like

Any update on this BLE behavior?

I’m not able to reproduce this with either the Beacon Scanner library or the BLE Group library. Both have BLE continuing to work, even when Cellular connectivity drops. Do you have more details on the application to help replicate?

To be clear, I am not using the beacon scanner library. I am using BLE to communicate between 5 Argon and 1 Boron. The Argon sleep for 1m wake up, send a TX to the Boron. The Boron in turn sends and ACK back to the Argon. When the Argon receives the ACK, it goes back to sleep. There is also an INT that can wake the Argon at any time but the process is the same- wake, send TX, receive ACK, sleep. Cycle repeats every 1m. As long as I have a cellular connection, it runs perfectly. When the cellular connection is lost and while the Boron is struggling to find and reconnect to cellular, the Argons eventually fail to connect to the Boron. The only remedy is to manually RESET the Argons once the Boron has re-established the cellular connection.

If you DM me, I can send you serial data showing the issue. I can also send you firmware if you like but I will not post either in the public forum because of NDA.

Thanks for your help and consideration.

@jackpot Would you mind going to Support and use the Submit a request link on the top right?

@mariano, request submitted. Thank you

Hi there, I’m using this lib and it works great, except that I have 4 iBeacons (ELA innovation), and keep only seing 2 of them (always the same ones)… If I use my iphone (ELA innovation app), I can see the 4 beacons. Same using the NRF Connect app…

Any reason why the system would be missing the two other beacons?


details: I’m running it on the Tracker SoM (dev board currently) under 3.1.0 (tracker-edge base code)

Can you post the code that you’re using, and where you see only two? That will help with figuring out if you’re using the scanAndPublish API, or if it’s in continuous mode, if the output is in Log or if it’s published to the Cloud, etc.

I’m using continuous mode and running scanner.loop regularly…

code sample:

/**
 * @brief main setup
 *
 */
void setup()
{
  Tracker::instance().init();
  Tracker::instance().location.regLocGenCallback(locationGenerationCallback);
  TrackerLocation::instance().regEnhancedLocCallback(enhancedCb);
  memory.begin();
  Scanner.setScanPeriod(SCAN_PERIOD);
  Scanner.setMissedCount(MAX_MISSED_PERIODS);
  Scanner.setCallback(scanCB);
  Scanner.startContinuous(SCAN_IBEACON);
  publishInterval = PUBLISH_INTERVAL_MS;
}

/**
 * @brief main loop
 *
 */
void loop()
{
    // call tracker loop
    Tracker::instance().loop();

    Scanner.loop();
    if (!publishedTime || millis() - publishedTime > publishInterval) {
        publishData();
    }
}

callback:

void scanCB(Beacon& beacon, callback_type type) {
    // filter unwanted beacon types
    if (beacon.type != SCAN_IBEACON) {
        return;
    }
    iBeaconScan ibeacon = (iBeaconScan&)beacon;
    int numTag = -1;
    bool newTag = false;
    for (int i = 0; i < numTags; i++) {
        if (tag[i].beacon.getAddress() == ibeacon.getAddress() && !tag[i].doNotTrack) {
            numTag = i;
            tag[i].power = ibeacon.getPower();
            tag[i].rssi = ibeacon.getRssi();
            break;
        }
    }
    if (numTag < 0) {
        numTag = numTags;
        newTag = true;
        memcpy((void*)&tag[numTag].beacon, (void*)&ibeacon, sizeof(ibeacon));
        tag[numTag].power = ibeacon.getPower();
        tag[numTag].rssi = ibeacon.getRssi();
        numTags++;
    }
    Log.info("UUID: %s, Address: %s, major: %u, minor: %u", ibeacon.getUuid(), ibeacon.getAddress().toString().c_str(), ibeacon.getMajor(), ibeacon.getMinor());
    if (type == NEW) {
        if (strstr(tag[numTag].beacon.getUuid(),"01020304-0506-0708")) {
            tag[numTag].seen();
            if (tag[numTag].alarm) {
                onTagFound(tag[numTag]);
                Log.info("Tag found again");
            }
            if (newTag) {
                onNewTag(tag[numTag]);
                Log.info("New tag scanned");
            }
        }
        else {
            tag[numTag].doNotTrack = true;
        }
    }
    else if (type == REMOVED) {
        if (!tag[numTag].doNotTrack) {
            // this beacon was not updated -> missing
            onMissingTag(tag[numTag]);
        }
    }
}

Note I do my own handling of which beacons show up and disappear, based on the NEW/REMOVED callback, but that shouldn’t interfere…
that line:

    Log.info("UUID: %s, Address: %s, major: %u, minor: %u", ibeacon.getUuid(), ibeacon.getAddress().toString().c_str(), ibeacon.getMajor(), ibeacon.getMinor());

should show me any beacon passed to the callback.

some output:

0000003225 [app.gps.ubx] INFO: enable PUBX-POSITION
0000003232 [app.gps.ubx] INFO: enable PUBX-SVSTATUS
0000003235 [app.gps.ubx] INFO: enable PUBX-TIME
0000003258 [app.gps.ubx] INFO: enable GPS
0000003258 [app.gps.ubx] INFO: enable QZSS
0000003259 [app.gps.ubx] INFO: enable SBAS
0000003259 [app.gps.ubx] INFO: enable Galileo
0000003259 [app.gps.ubx] INFO: enable BeiDou
0000003260 [app.gps.ubx] INFO: enable GLONASS
0000003263 [app.gps.ubx] INFO: set to power management mode 0
0000003266 [app.gps.ubx] INFO: set dynamic platform model to 0
0000010728 [app] INFO: UUID: 50765CB7-D9EA-4E21-99A4-FA879613A492, Address: 10:5B:AD:A5:BF:D2, major: 42462, minor: 38685
0000010732 [app] INFO: UUID: 01020304-0506-0708-090A-0B0C0D0E0F5B, Address: E4:2C:C4:EA:22:C2, major: 523, minor: 266
0000010733 [app] INFO: New tag discovered: 01020304-0506-0708-090A-0B0C0D0E0F5B
0000010734 [app] INFO: New tag scanned
0000010734 [app] INFO: UUID: 01020304-0506-0708-090A-0B0C0D0E0FDC, Address: DC:15:4E:1A:DC:DF, major: 523, minor: 266
0000010735 [app] INFO: New tag discovered: 01020304-0506-0708-090A-0B0C0D0E0FDC
0000010736 [app] INFO: New tag scanned
0000090958 [ncp.client] ERROR: Failed to perform early initialization
0000102924 [net.pppncp] ERROR: Failed to initialize cellular NCP client: -210
0000108236 [mux] INFO: Stopping GSM07.10 muxer
0000108237 [mux] INFO: Gracefully stopping GSM07.10 muxer
0000108237 [mux] INFO: Closing all muxed channels
0000108237 [mux] INFO: Closing mux channel 1

I wonder if the cell module interferes somehow with BLE… read about someone complaining about that in another thread…

The log shows 3 different iBeacons scanned, so it looks like sometimes it finds 2 and sometimes more. How long is SCAN_PERIOD? Also, it appears that in the callback you’re trying to keep track of multiple calls to the callback for the same address. The callback will only be called when a device appears for the first time, or disappears (after missing the MAX_MISSED_PERIODS number). It’s not called each time the iBeacon is scanned.

If you’re trying to keep an array/vector of scanned iBeacons, you don’t need to use the callback. Set it to continuous mode, and then the currently scanned iBeacons will be accessible by calling getiBeacons(). That will return a Vector<iBeaconScan> that you can iterate over. I would suggest when iterating over the Vector that you don’t remove any and let the library manage removing them when they go away. You should use Vector methods such as at() instead of takeAt().

No, I’m only interested in the 01-02-03… ones… the 3rd one is my TV :slight_smile: but yes, it seems to see that one… but the 01-02-03 are 4 and all up and running.

I initially set SCAN_PERIOD to 30s and MAX_MISSED_PERIODS to 2, since I want to get a status every 60s… also the beacons are supposedly emitting every 10s… I tried with different values (inclusive the default, removing these 2 calls) but that still doesn’t work.

I’m fine with not being called when there’s no change, but I still expect the 2 missing one to show at least once…

I’ll try removing the callback as you mention and just implement a one-time read every minute, all the more that my process kind of duplicates your own NEW/REMOVED handling…

Yet that still doesn’t explain why the BLE doesn’t see the two missing beacons (there are all 4 close to each other). The signals on the other ones are pretty much around the same strength too… if they were broadcasting at the exact same time (which I doubt), I should still occasionally see one of the missing one and maybe one of the usual one would disappear, but no. Could there be a bug in the BLE lib? I’m trying to figure how to debug that at a lower level, but I haven’t been successful yet.

I thought for a moment it could be a beacon configuration thing, but it doesn’t look like it, they are standard beacons. (Blue coin ID)

I agree that it should see all 4. I’m not sure why it wouldn’t, but I would periodically look at getiBeacons().size(). Is that always 2, or do you ever see that change?

Just tested with getiBeacons().size()… I never get more than 3 (2 + my TV)
I’m really starting to think something is different with the 2 that don’t show up…