de|en

Eddystone TLM

Martin Kompf

Bestandteil der Eddystone Spezifikation für Bluetooth Low Energy ist das Versenden von Telemetriedaten, wie Temperatur und Betriebsspannung des Beacons.

Screenshot

Bluetooth 4.0 Low Energy (BLE) erlaubt das Versenden von Advertising Frames, die auch Nutzdaten transportieren können. Für ihre Kodierung gibt es mehrere konkurrierende Standards, zum Beispiel iBeacon von Apple und das Eddystone Format von Google. Eine Besonderheit von Eddystone ist, dass neben reinen Nutzdaten, wie einer URL, auch die Übertragung von Telemetriedaten möglich ist, die über den internen Zustand des Beacons Auskunft geben. Dazu dienen spezielle Eddystone TLM Pakete, die in der ersten Version enthaltenen Informationen sind:

Die Daten sind im big-endian Format (MSB first, Motorola) abgelegt.

Das Beispiel unten zeigt die Programmierung eines Sketches für den RFduino. Das Programm sendet die TLM Frames nicht permanent aus, sondern abwechselnd mit Eddystone URL Frames (Interleaving Telemetry). Das Setzen der Advertising Daten sowie das Starten und Stoppen des Bluetooth Stacks ist daher in die Funktion advertise() ausgelagert. Die Hauptschleife in loop() ruft dann advertise() abwechselnd mit URL und TLM Daten auf. Außerdem kümmert sich dieser Teil um die Erfassung der Messwerte für Temperatur und Batteriespannung. Die Funktionen ulong2adv() und so weiter übernehmen die Konvertierung der Messwerte in das für Eddystone TLM erforderliche Format.

Zum Empfang und Testen der Eddystone Frames eignet sich ein Smartphone mit Android 4.4 oder höher und einer entsprechenden App. Die Abbildung zeigt den Empfang eines Eddystone TLM Frames mit dem nRF Master Control Panel von Nordic Semiconductor - einem sehr hilfreichen Tool für den Entwickler von Anwendungen für 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;
}