Skip to content

Commit 8199d1e

Browse files
authored
Fix data parsing of Eddystone TLM data frame (#2)
* Fix data parsing of Eddystone TLM data frame Add Beacon scanner example to show usage of BLEEddystoneTLM class and BLEEddystoneURL class Add EddystoneTLM beacon example Add EddystoneURL beacon example * Fix copy and paste error
1 parent f12c9e0 commit 8199d1e

File tree

7 files changed

+529
-9
lines changed

7 files changed

+529
-9
lines changed
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp
3+
Ported to Arduino ESP32 by Evandro Copercini
4+
*/
5+
6+
/** NimBLE differences highlighted in comment blocks **/
7+
8+
/*******original********
9+
#include <BLEDevice.h>
10+
#include <BLEUtils.h>
11+
#include <BLEScan.h>
12+
#include <BLEAdvertisedDevice.h>
13+
#include "BLEEddystoneURL.h"
14+
#include "BLEEddystoneTLM.h"
15+
#include "BLEBeacon.h"
16+
***********************/
17+
18+
#include <Arduino.h>
19+
20+
#include <NimBLEDevice.h>
21+
#include <NimBLEUtils.h>
22+
#include <NimBLEScan.h>
23+
#include <NimBLEAdvertisedDevice.h>
24+
#include "NimBLEEddystoneURL.h"
25+
#include "NimBLEEddystoneTLM.h"
26+
#include "NimBLEBeacon.h"
27+
28+
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
29+
30+
int scanTime = 5; //In seconds
31+
BLEScan *pBLEScan;
32+
33+
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks
34+
{
35+
/*** Only a reference to the advertised device is passed now
36+
void onResult(BLEAdvertisedDevice advertisedDevice) { **/
37+
void onResult(BLEAdvertisedDevice *advertisedDevice)
38+
{
39+
if (advertisedDevice->haveName())
40+
{
41+
Serial.print("Device name: ");
42+
Serial.println(advertisedDevice->getName().c_str());
43+
Serial.println("");
44+
}
45+
46+
if (advertisedDevice->haveServiceUUID())
47+
{
48+
BLEUUID devUUID = advertisedDevice->getServiceUUID();
49+
Serial.print("Found ServiceUUID: ");
50+
Serial.println(devUUID.toString().c_str());
51+
Serial.println("");
52+
}
53+
else
54+
{
55+
if (advertisedDevice->haveManufacturerData() == true)
56+
{
57+
std::string strManufacturerData = advertisedDevice->getManufacturerData();
58+
59+
uint8_t cManufacturerData[100];
60+
strManufacturerData.copy((char *)cManufacturerData, strManufacturerData.length(), 0);
61+
62+
if (strManufacturerData.length() == 25 && cManufacturerData[0] == 0x4C && cManufacturerData[1] == 0x00)
63+
{
64+
Serial.println("Found an iBeacon!");
65+
BLEBeacon oBeacon = BLEBeacon();
66+
oBeacon.setData(strManufacturerData);
67+
Serial.printf("iBeacon Frame\n");
68+
Serial.printf("ID: %04X Major: %d Minor: %d UUID: %s Power: %d\n", oBeacon.getManufacturerId(), ENDIAN_CHANGE_U16(oBeacon.getMajor()), ENDIAN_CHANGE_U16(oBeacon.getMinor()), oBeacon.getProximityUUID().toString().c_str(), oBeacon.getSignalPower());
69+
}
70+
else
71+
{
72+
Serial.println("Found another manufacturers beacon!");
73+
Serial.printf("strManufacturerData: %d ", strManufacturerData.length());
74+
for (int i = 0; i < strManufacturerData.length(); i++)
75+
{
76+
Serial.printf("[%X]", cManufacturerData[i]);
77+
}
78+
Serial.printf("\n");
79+
}
80+
}
81+
return;
82+
}
83+
84+
uint8_t *payLoad = advertisedDevice->getPayload();
85+
86+
BLEUUID checkUrlUUID = (uint16_t)0xfeaa;
87+
88+
if (advertisedDevice->getServiceUUID().equals(checkUrlUUID))
89+
{
90+
if (payLoad[11] == 0x10)
91+
{
92+
Serial.println("Found an EddystoneURL beacon!");
93+
BLEEddystoneURL foundEddyURL = BLEEddystoneURL();
94+
std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct!
95+
96+
foundEddyURL.setData(eddyContent);
97+
std::string bareURL = foundEddyURL.getURL();
98+
if (bareURL[0] == 0x00)
99+
{
100+
size_t payLoadLen = advertisedDevice->getPayloadLength();
101+
Serial.println("DATA-->");
102+
for (int idx = 0; idx < payLoadLen; idx++)
103+
{
104+
Serial.printf("0x%08X ", payLoad[idx]);
105+
}
106+
Serial.println("\nInvalid Data");
107+
return;
108+
}
109+
110+
Serial.printf("Found URL: %s\n", foundEddyURL.getURL().c_str());
111+
Serial.printf("Decoded URL: %s\n", foundEddyURL.getDecodedURL().c_str());
112+
Serial.printf("TX power %d\n", foundEddyURL.getPower());
113+
Serial.println("\n");
114+
}
115+
else if (payLoad[11] == 0x20)
116+
{
117+
Serial.println("Found an EddystoneTLM beacon!");
118+
BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM();
119+
std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct!
120+
121+
eddyContent = "01234567890123";
122+
123+
for (int idx = 0; idx < 14; idx++)
124+
{
125+
eddyContent[idx] = payLoad[idx + 11];
126+
}
127+
128+
foundEddyURL.setData(eddyContent);
129+
Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt());
130+
Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp());
131+
int temp = (int)payLoad[16] + (int)(payLoad[15] << 8);
132+
float calcTemp = temp / 256.0f;
133+
Serial.printf("Reported temperature from data: %.2fC\n", calcTemp);
134+
Serial.printf("Reported advertise count: %d\n", foundEddyURL.getCount());
135+
Serial.printf("Reported time since last reboot: %ds\n", foundEddyURL.getTime());
136+
Serial.println("\n");
137+
Serial.print(foundEddyURL.toString().c_str());
138+
Serial.println("\n");
139+
}
140+
}
141+
}
142+
};
143+
144+
void setup()
145+
{
146+
Serial.begin(115200);
147+
Serial.println("Scanning...");
148+
149+
BLEDevice::init("");
150+
pBLEScan = BLEDevice::getScan(); //create new scan
151+
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
152+
pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
153+
pBLEScan->setInterval(100);
154+
pBLEScan->setWindow(99); // less or equal setInterval value
155+
}
156+
157+
void loop()
158+
{
159+
// put your main code here, to run repeatedly:
160+
BLEScanResults foundDevices = pBLEScan->start(scanTime, false);
161+
Serial.print("Devices found: ");
162+
Serial.println(foundDevices.getCount());
163+
Serial.println("Scan done!");
164+
pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory
165+
delay(2000);
166+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## BLE Beacon Scanner
2+
3+
Initiates a BLE device scan.
4+
Checks if the discovered devices are
5+
- an iBeacon
6+
- an Eddystone TLM beacon
7+
- an Eddystone URL beacon
8+
9+
and sends the decoded beacon information over Serial log
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
EddystoneTLM beacon for NimBLE by BeeGee based on https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino
3+
EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
4+
*/
5+
6+
/*
7+
Create a BLE server that will send periodic Eddystone URL frames.
8+
The design of creating the BLE server is:
9+
1. Create a BLE Server
10+
2. Create advertising data
11+
3. Start advertising.
12+
4. wait
13+
5. Stop advertising.
14+
6. deep sleep
15+
16+
*/
17+
#include "sys/time.h"
18+
19+
#include <Arduino.h>
20+
21+
#include "NimBLEDevice.h"
22+
#include "NimBLEUtils.h"
23+
#include "NimBLEBeacon.h"
24+
#include "NimBLEAdvertising.h"
25+
#include "NimBLEEddystoneURL.h"
26+
27+
#include "esp_sleep.h"
28+
29+
#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up
30+
RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory
31+
RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory
32+
33+
// See the following for generating UUIDs:
34+
// https://www.uuidgenerator.net/
35+
BLEAdvertising *pAdvertising;
36+
struct timeval nowTimeStruct;
37+
38+
time_t lastTenth;
39+
40+
#define BEACON_UUID "8ec76ea3-6668-48da-9866-75be8bc86f4d" // UUID 1 128-Bit (may use linux tool uuidgen or random numbers via https://www.uuidgenerator.net/)
41+
42+
// Check
43+
// https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md
44+
// and http://www.hugi.scene.org/online/coding/hugi%2015%20-%20cmtadfix.htm
45+
// for the temperature value. It is a 8.8 fixed-point notation
46+
void setBeacon()
47+
{
48+
char beacon_data[25];
49+
uint16_t beconUUID = 0xFEAA;
50+
uint16_t volt = random(2800, 3700); // 3300mV = 3.3V
51+
float tempFloat = random(2000, 3100) / 100.0f;
52+
Serial.printf("Random temperature is %.2fC\n", tempFloat);
53+
int temp = (int)(tempFloat * 256); //(uint16_t)((float)23.00);
54+
Serial.printf("Converted to 8.8 format %0X%0X\n", (temp >> 8), (temp & 0xFF));
55+
56+
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
57+
BLEAdvertisementData oScanResponseData = BLEAdvertisementData();
58+
59+
oScanResponseData.setFlags(0x06); // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04
60+
oScanResponseData.setCompleteServices(BLEUUID(beconUUID));
61+
62+
beacon_data[0] = 0x20; // Eddystone Frame Type (Unencrypted Eddystone-TLM)
63+
beacon_data[1] = 0x00; // TLM version
64+
beacon_data[2] = (volt >> 8); // Battery voltage, 1 mV/bit i.e. 0xCE4 = 3300mV = 3.3V
65+
beacon_data[3] = (volt & 0xFF); //
66+
beacon_data[4] = (temp >> 8); // Beacon temperature
67+
beacon_data[5] = (temp & 0xFF); //
68+
beacon_data[6] = ((bootcount & 0xFF000000) >> 24); // Advertising PDU count
69+
beacon_data[7] = ((bootcount & 0xFF0000) >> 16); //
70+
beacon_data[8] = ((bootcount & 0xFF00) >> 8); //
71+
beacon_data[9] = (bootcount & 0xFF); //
72+
beacon_data[10] = ((lastTenth & 0xFF000000) >> 24); // Time since power-on or reboot as 0.1 second resolution counter
73+
beacon_data[11] = ((lastTenth & 0xFF0000) >> 16); //
74+
beacon_data[12] = ((lastTenth & 0xFF00) >> 8); //
75+
beacon_data[13] = (lastTenth & 0xFF); //
76+
77+
oScanResponseData.setServiceData(BLEUUID(beconUUID), std::string(beacon_data, 14));
78+
oAdvertisementData.setName("TLMBeacon");
79+
pAdvertising->setAdvertisementData(oAdvertisementData);
80+
pAdvertising->setScanResponseData(oScanResponseData);
81+
}
82+
83+
void setup()
84+
{
85+
86+
Serial.begin(115200);
87+
gettimeofday(&nowTimeStruct, NULL);
88+
89+
Serial.printf("start ESP32 %d\n", bootcount++);
90+
91+
Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n", nowTimeStruct.tv_sec, nowTimeStruct.tv_sec - last);
92+
93+
last = nowTimeStruct.tv_sec;
94+
lastTenth = nowTimeStruct.tv_sec * 10; // Time since last reset as 0.1 second resolution counter
95+
96+
// Create the BLE Device
97+
BLEDevice::init("TLMBeacon");
98+
99+
BLEDevice::setPower(ESP_PWR_LVL_N12);
100+
101+
pAdvertising = BLEDevice::getAdvertising();
102+
103+
setBeacon();
104+
// Start advertising
105+
pAdvertising->start();
106+
Serial.println("Advertizing started for 10s ...");
107+
delay(10000);
108+
pAdvertising->stop();
109+
Serial.printf("enter deep sleep for 10s\n");
110+
esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION);
111+
Serial.printf("in deep sleep\n");
112+
}
113+
114+
void loop()
115+
{
116+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Eddystone TLM beacon
2+
EddystoneTLM beacon by BeeGee based on
3+
[pcbreflux ESP32 Eddystone TLM deepsleep](https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino)
4+
5+
[EddystoneTLM frame specification](https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md)
6+
7+
Create a BLE server that will send periodic Eddystone TLM frames.
8+
The design of creating the BLE server is:
9+
1. Create a BLE Server
10+
2. Create advertising data
11+
3. Start advertising.
12+
4. wait
13+
5. Stop advertising.
14+
6. deep sleep

0 commit comments

Comments
 (0)