BLE Beacon Experiment #1

Following up on my last post, I've decided to try going with option #1:

  1. I can find a way to get the sensor measurements inside the advertising packet. One idea I have for this is using the first four bytes as a node ID, one byte as a sensor ID and three bytes for the reading sequence number (to correlate multiple sensor readings), four bytes with the reading, and the last four as a CRC32 over the first 12 bytes. Then, the sensor will transmit one advertisement for each physical measurement it's taken.

The actual format I decided on uses four four-byte fields inside the beacon ID, essentially:

struct Beacon {
    uint8_t     header[4];
    uint32_t    nodeID;
    uint32_t    sensor;
    uint32_t    checksum;
};

I don't use the major value yet (but more on that later), and use the minor mode to identify which sensor we're reading values from. My (annotated) proof-of-concept sketch was largely lifted from the Adafruit example as a starting point, with my hacks bolted on.

#include <beaconsense.h> // my beacon utility library
#include <bluefruit.h>   // Adafruit's bluetooth library

// The manufacturer ID is just Nordic's, because that's who makes the
// nRF52.
#define MANUFACTURER_ID 0x0059

uint8_t beaconID[16] = {
    0x68, 0x65, 0x6c, 0x6c,
    0x6f, 0x2c, 0x20, 0x77,
    0x6f, 0x72, 0x6c, 0x64,
    0x21, 0x6b, 0x64, 0x69
};

The major mode isn't yet being used. One idea I've had is to replace the node ID in the above struct with a network ID, and to use the major number to identify the node. Another option is to have the controllers manage a list of UUIDs they know are sensor nodes in a network (maybe by using the scan response field to associate to a particular network?) and use the major as the sensor ID and the minor as the sensor value. I think most of the sensors are going to return at best a uint16_t anyways. Maybe one of these gets a sequence number. Options! I have them.

Note that the sensor list is just some fake readings that will get incremented to simulate changing values.

static uint16_t major = 1;
static uint16_t minor = 0;
static uint32_t sensors[] = {100, 1000, 10000};
static uint32_t id = 0x42;
BLEBeacon beacon(beaconID, major, minor, -45);

I was using the serial port to log changes in beacons to make sure they were changing. There was a brief period where my BLE beacon app (Locate by Radius Networks) wasn't picking up the changing beacons, but that might have been because there was a brief disconnect between saving the file and uploading it to the board.

void setup() {
    Serial.begin(9600);
    Bluefruit.begin();
    Bluefruit.setTxPower(0);
    Bluefruit.setName("env-node-proto");
    beacon.setManufacturer(MANUFACTURER_ID);

    setupBeacon();
}

setupBeacon handles some of the initial beacon setup. I haven't really touched most of this from the example code, and only barely understand it (for now).

void setupBeacon() {
    updateBeacon();
    Bluefruit.ScanResponse.addName();
    Bluefruit.Advertising.restartOnDisconnect(true);
    Bluefruit.Advertising.setInterval(160, 160);
    Bluefruit.Advertising.setFastTimeout(30);
    Bluefruit.Advertising.start(0);
}

On updateBeacon, the advertising is stopped temporarily to allow for changes to propagate. I don't know if this is strictly needed, but it seemed to be helpful in getting the changes picked up. write_beacon comes from my library, and I make sure to walk through the sensor list. Finally, advertising is restarted.

void updateBeacon() {
    Bluefruit.Advertising.stop();
    logBeacon();
    beacon.setMajorMinor(major, minor);
    write_beacon(beaconID, id, sensors[minor]);
    sensors[minor]++;
    beacon.setUuid(beaconID);
    minor = (minor + 1) % 3;
    Bluefruit.Advertising.setBeacon(beacon);
    Bluefruit.Advertising.start(0);
}

void logBeacon() {
    Serial.print("major: ");
    Serial.print(major);
    Serial.print(", minor: ");
    Serial.print(minor);
    Serial.print(", reading: ");
    Serial.println(sensors[minor]);

}

I update the beacon every second. This is largely because I'm watching the app to see changes, and it's easier for me too see it with this delay. I'd probably do a shorter interval when I do the real thing.

void loop() {
    delay(1000);
    updateBeacon();
}

But does it blend?

Locating beacons.

One of the next questions is going to be the storage cost. I wrote a script to dump some stats out

Let's assume the logging node stores the time it saw the reading (uint64_t), the node ID (uint32_t), the sensor ID (uint16_t), and the reading (we can cast this to a uint16_t if it's a uint32_t). This comes out to 16 bytes per beacon per node. If I update every 100ms (which may be overkill):

$ python beacon-storage.py 
Bytes per second: 160 B
Bytes per minute: 9 KB
Bytes per minute: 562 KB
   Bytes per day: 13.2 MB
  Bytes per year: 4.7 GB

However, if I send only one beacon every second, like I'm doing now:

$ python beacon-storage.py 1
Bytes per second: 16 B
Bytes per minute: 960 B
Bytes per minute: 56 KB
   Bytes per day: 1.3 MB
  Bytes per year: 481.5 MB

I'll have to see what ends up actually being practical and useful when I deploy the network.

Next up: I need to actually writing something to pick up beacons and log them. I'll probably have to use the bluez stack. I also need to strip out the library, make it into something cross platform that also generates and unpacks the library into my sketchbook.


Tags: , , ,