en|de

Eddystone TLM

Martin Kompf

Part of Eddystone specification for Bluetooth Low Energy is sending telemetry data such as temperature and operating voltage of the Beacons.

Screenshot

Bluetooth 4.0 Low Energy (BLE) allows you to send advertising frames, which can also carry useful data. For their coding, there are several competing standards, such iBeacon of Apple and the Eddystone format of Google. A special feature of Eddystone is that in addition to pure user data such as a URL, the transmission of telemetry data is possible to provide information about the internal state of the Beacons. These are special Eddystone TLM packets that contain in their first version the information:

The data is stored in big-endian format (MSB first, Motorola).

The example below shows the programming of a sketch for the RFduino. The program is sending the TLM frames not permanently, but rather alternately with Eddystone URL frames (Interleaving Telemetry). Setting the advertising data and starting and stopping the Bluetooth stack is thus outsourced to the function advertise(). The main loop in loop() calls then advertise() alternating with URL and TLM data. In addition, this part takes care of the acquisition of the measured values for temperature and battery voltage. The functions ulong2adv() and so on are responsible for the conversion of the measured values into the format required for Eddystone TLM.

To receive and test the Eddystone frames you may use, for example, a smartphone with Android 4.4 or higher and a corresponding app. The figure shows the reception of an Eddystone TLM frame with the nRF Master Control Panel of Nordic Semiconductor - a very useful tool for developers of applications for Bluetooth beacons.

/*
 * RFduino as Eddystone TLM beacon.
 */
#include <RFduinoBLE.h>

/* Normal advertising data, see http://www.kompf.de/tech/eddystoneurl.html */
uint8_t advdata_url[] = { 
  0x03, 0x03, 0xAA, 0xFE, 0x13, 0x16, 0xAA, 0xFE, 0x10, 0xF8, 0x03,
  'g', 'o', 'o', '.', 'g', 'l', '/', '1', 'G', 'q', 's', 'y', 'i',
};

/* TLM advertising data: */
uint8_t advdata_tlm[] =
{
  0x03,  // Length
  0x03,  // Param: Service List
  0xAA, 0xFE,  // Eddystone ID
  0x11,  // Length
  0x16,  // Service Data
  0xAA, 0xFE, // Eddystone ID
  0x20,  // TLM flag
  0x00, // TLM version
  /* [10] */ 0x00, 0x00,  // Battery voltage
  /* [12] */ 0x80, 0x00,  // Beacon temperature
  /* [14] */ 0x00, 0x00, 0x00, 0x00, // Advertising PDU count
  /* [18] */ 0x00, 0x00, 0x00, 0x00 // Time since reboot
};

unsigned long pdu_count = 0;

void setup() {
  // Setup battery measurement
  // See http://forum.rfduino.com/index.php?topic=265.0
  analogReference(VBG);
  analogSelection(VDD_1_3_PS);
}

/*
 * Advertise the given data for the specified time ms
 */
void advertise(uint8_t *data, uint32_t len, uint32_t ms) {
  RFduinoBLE_advdata = data;
  RFduinoBLE_advdata_len = len;
  RFduinoBLE.advertisementInterval = 300; // ms
  
  // start the BLE stack
  RFduinoBLE.begin();
  
  // sleep for ms milliseconds
  RFduino_ULPDelay(ms);
  
  // stop the BLE stack
  RFduinoBLE.end();
}

void loop() {
  // advertise standard URL frames
  advertise(advdata_url, sizeof(advdata_url), SECONDS(10);
  
  // acquire data for TLM
  pdu_count++;
  float temp = RFduino_temperature(CELSIUS);
  // battery voltage
  NRF_ADC->TASKS_START = 1;
  int sensorValue = analogRead(1);
  float batteryVoltage = sensorValue * (3.6 / 1023.0);
  NRF_ADC->TASKS_STOP = 1;

  // convert data to TLM frame format
  int2adv(advdata_tlm, 10, (int) (1000 * batteryVoltage));
  float2adv(advdata_tlm, 12, temp);
  ulong2adv(advdata_tlm, 14, pdu_count);
  ulong2adv(advdata_tlm, 18, millis() / 100);
  
  // advertise TLM frames
  advertise(advdata_tlm, sizeof(advdata_tlm), SECONDS(1));
}

void ulong2adv(uint8_t* a, int off, unsigned long val) {
  off+=3;
  for (int i = 0; i < 4; ++i) {
    uint8_t bval = val & 0xff;
    a[off] = bval;
    val >>= 8;
    off--;
  }
}

void float2adv(uint8_t* a, int off, float val) {
  int2adv(a, off, (int) (256.0 * val));
}

void int2adv(uint8_t* a, int off, int val) {
  a[off+1] = val & 0xff;
  val >>= 8;
  a[off] = val & 0xff;
}