diff --git a/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino b/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino new file mode 100644 index 00000000..b46baa7c --- /dev/null +++ b/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino @@ -0,0 +1,153 @@ +/* + Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleScan.cpp + Ported to Arduino ESP32 by Evandro Copercini + Changed to a beacon scanner to report iBeacon, EddystoneURL and EddystoneTLM beacons by beegee-tokyo +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8)) + +int scanTime = 5; //In seconds +BLEScan *pBLEScan; + +class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks +{ + void onResult(BLEAdvertisedDevice advertisedDevice) + { + if (advertisedDevice.haveName()) + { + Serial.print("Device name: "); + Serial.println(advertisedDevice.getName().c_str()); + Serial.println(""); + } + + if (advertisedDevice.haveServiceUUID()) + { + BLEUUID devUUID = advertisedDevice.getServiceUUID(); + Serial.print("Found ServiceUUID: "); + Serial.println(devUUID.toString().c_str()); + Serial.println(""); + } + else + { + if (advertisedDevice.haveManufacturerData() == true) + { + std::string strManufacturerData = advertisedDevice.getManufacturerData(); + + uint8_t cManufacturerData[100]; + strManufacturerData.copy((char *)cManufacturerData, strManufacturerData.length(), 0); + + if (strManufacturerData.length() == 25 && cManufacturerData[0] == 0x4C && cManufacturerData[1] == 0x00) + { + Serial.println("Found an iBeacon!"); + BLEBeacon oBeacon = BLEBeacon(); + oBeacon.setData(strManufacturerData); + Serial.printf("iBeacon Frame\n"); + 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()); + } + else + { + Serial.println("Found another manufacturers beacon!"); + Serial.printf("strManufacturerData: %d ", strManufacturerData.length()); + for (int i = 0; i < strManufacturerData.length(); i++) + { + Serial.printf("[%X]", cManufacturerData[i]); + } + Serial.printf("\n"); + } + } + return; + } + + uint8_t *payLoad = advertisedDevice.getPayload(); + + BLEUUID checkUrlUUID = (uint16_t)0xfeaa; + + if (advertisedDevice.getServiceUUID().equals(checkUrlUUID)) + { + if (payLoad[11] == 0x10) + { + Serial.println("Found an EddystoneURL beacon!"); + BLEEddystoneURL foundEddyURL = BLEEddystoneURL(); + std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! + + foundEddyURL.setData(eddyContent); + std::string bareURL = foundEddyURL.getURL(); + if (bareURL[0] == 0x00) + { + size_t payLoadLen = advertisedDevice.getPayloadLength(); + Serial.println("DATA-->"); + for (int idx = 0; idx < payLoadLen; idx++) + { + Serial.printf("0x%08X ", payLoad[idx]); + } + Serial.println("\nInvalid Data"); + return; + } + + Serial.printf("Found URL: %s\n", foundEddyURL.getURL().c_str()); + Serial.printf("Decoded URL: %s\n", foundEddyURL.getDecodedURL().c_str()); + Serial.printf("TX power %d\n", foundEddyURL.getPower()); + Serial.println("\n"); + } + else if (payLoad[11] == 0x20) + { + Serial.println("Found an EddystoneTLM beacon!"); + BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM(); + std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! + + eddyContent = "01234567890123"; + + for (int idx = 0; idx < 14; idx++) + { + eddyContent[idx] = payLoad[idx + 11]; + } + + foundEddyURL.setData(eddyContent); + Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt()); + Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp()); + int temp = (int)payLoad[16] + (int)(payLoad[15] << 8); + float calcTemp = temp / 256.0f; + Serial.printf("Reported temperature from data: %.2fC\n", calcTemp); + Serial.printf("Reported advertise count: %d\n", foundEddyURL.getCount()); + Serial.printf("Reported time since last reboot: %ds\n", foundEddyURL.getTime()); + Serial.println("\n"); + Serial.print(foundEddyURL.toString().c_str()); + Serial.println("\n"); + } + } + } +}; + +void setup() +{ + Serial.begin(115200); + Serial.println("Scanning..."); + + BLEDevice::init(""); + pBLEScan = BLEDevice::getScan(); //create new scan + pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster + pBLEScan->setInterval(100); + pBLEScan->setWindow(99); // less or equal setInterval value +} + +void loop() +{ + // put your main code here, to run repeatedly: + BLEScanResults foundDevices = pBLEScan->start(scanTime, false); + Serial.print("Devices found: "); + Serial.println(foundDevices.getCount()); + Serial.println("Scan done!"); + pBLEScan->clearResults(); // delete results fromBLEScan buffer to release memory + delay(2000); +} diff --git a/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.md b/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.md new file mode 100644 index 00000000..558c3e7a --- /dev/null +++ b/libraries/BLE/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.md @@ -0,0 +1,9 @@ +## BLE Beacon Scanner + +Initiates a BLE device scan. +Checks if the discovered devices are +- an iBeacon +- an Eddystone TLM beacon +- an Eddystone URL beacon + +and sends the decoded beacon information over Serial log \ No newline at end of file diff --git a/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.ino b/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.ino new file mode 100644 index 00000000..96be28cd --- /dev/null +++ b/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.ino @@ -0,0 +1,116 @@ +/* + EddystoneTLM beacon by BeeGee based on https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino + EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md +*/ + +/* + Create a BLE server that will send periodic Eddystone URL frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep + +*/ +#include "sys/time.h" + +#include + +#include "BLEDevice.h" +#include "BLEUtils.h" +#include "BLEBeacon.h" +#include "BLEAdvertising.h" +#include "BLEEddystoneURL.h" + +#include "esp_sleep.h" + +#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up +RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory +RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +BLEAdvertising *pAdvertising; +struct timeval nowTimeStruct; + +time_t lastTenth; + +#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/) + +// Check +// https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md +// and http://www.hugi.scene.org/online/coding/hugi%2015%20-%20cmtadfix.htm +// for the temperature value. It is a 8.8 fixed-point notation +void setBeacon() +{ + char beacon_data[25]; + uint16_t beconUUID = 0xFEAA; + uint16_t volt = random(2800, 3700); // 3300mV = 3.3V + float tempFloat = random(2000, 3100) / 100.0f; + Serial.printf("Random temperature is %.2fC\n", tempFloat); + int temp = (int)(tempFloat * 256); //(uint16_t)((float)23.00); + Serial.printf("Converted to 8.8 format %0X%0X\n", (temp >> 8), (temp & 0xFF)); + + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); + + oScanResponseData.setFlags(0x06); // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04 + oScanResponseData.setCompleteServices(BLEUUID(beconUUID)); + + beacon_data[0] = 0x20; // Eddystone Frame Type (Unencrypted Eddystone-TLM) + beacon_data[1] = 0x00; // TLM version + beacon_data[2] = (volt >> 8); // Battery voltage, 1 mV/bit i.e. 0xCE4 = 3300mV = 3.3V + beacon_data[3] = (volt & 0xFF); // + beacon_data[4] = (temp >> 8); // Beacon temperature + beacon_data[5] = (temp & 0xFF); // + beacon_data[6] = ((bootcount & 0xFF000000) >> 24); // Advertising PDU count + beacon_data[7] = ((bootcount & 0xFF0000) >> 16); // + beacon_data[8] = ((bootcount & 0xFF00) >> 8); // + beacon_data[9] = (bootcount & 0xFF); // + beacon_data[10] = ((lastTenth & 0xFF000000) >> 24); // Time since power-on or reboot as 0.1 second resolution counter + beacon_data[11] = ((lastTenth & 0xFF0000) >> 16); // + beacon_data[12] = ((lastTenth & 0xFF00) >> 8); // + beacon_data[13] = (lastTenth & 0xFF); // + + oScanResponseData.setServiceData(BLEUUID(beconUUID), std::string(beacon_data, 14)); + oAdvertisementData.setName("TLMBeacon"); + pAdvertising->setAdvertisementData(oAdvertisementData); + pAdvertising->setScanResponseData(oScanResponseData); +} + +void setup() +{ + + Serial.begin(115200); + gettimeofday(&nowTimeStruct, NULL); + + Serial.printf("start ESP32 %d\n", bootcount++); + + Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n", nowTimeStruct.tv_sec, nowTimeStruct.tv_sec - last); + + last = nowTimeStruct.tv_sec; + lastTenth = nowTimeStruct.tv_sec * 10; // Time since last reset as 0.1 second resolution counter + + // Create the BLE Device + BLEDevice::init("TLMBeacon"); + + BLEDevice::setPower(ESP_PWR_LVL_N12); + + pAdvertising = BLEDevice::getAdvertising(); + + setBeacon(); + // Start advertising + pAdvertising->start(); + Serial.println("Advertizing started for 10s ..."); + delay(10000); + pAdvertising->stop(); + Serial.printf("enter deep sleep for 10s\n"); + esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION); + Serial.printf("in deep sleep\n"); +} + +void loop() +{ +} diff --git a/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.md b/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.md new file mode 100644 index 00000000..2e34029d --- /dev/null +++ b/libraries/BLE/examples/BLE_EddystoneTLM_Beacon/BLE_EddystoneTLM_Beacon.md @@ -0,0 +1,14 @@ +## Eddystone TLM beacon +EddystoneTLM beacon by BeeGee based on +[pcbreflux ESP32 Eddystone TLM deepsleep](https://github.com/pcbreflux/espressif/blob/master/esp32/arduino/sketchbook/ESP32_Eddystone_TLM_deepsleep/ESP32_Eddystone_TLM_deepsleep.ino) + +[EddystoneTLM frame specification](https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md) + + Create a BLE server that will send periodic Eddystone TLM frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep diff --git a/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.ino b/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.ino new file mode 100644 index 00000000..335ea1ff --- /dev/null +++ b/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.ino @@ -0,0 +1,192 @@ +/* + EddystoneURL beacon by BeeGee + EddystoneURL frame specification https://github.com/google/eddystone/blob/master/eddystone-url/README.md + +*/ + +/* + Create a BLE server that will send periodic Eddystone URL frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep + +*/ +#include "sys/time.h" + +#include + +#include "BLEDevice.h" +#include "BLEUtils.h" +#include "BLEBeacon.h" +#include "BLEAdvertising.h" +#include "BLEEddystoneURL.h" + +#include "esp_sleep.h" + +#define GPIO_DEEP_SLEEP_DURATION 10 // sleep x seconds and then wake up +RTC_DATA_ATTR static time_t last; // remember last boot in RTC Memory +RTC_DATA_ATTR static uint32_t bootcount; // remember number of boots in RTC Memory + +// See the following for generating UUIDs: +// https://www.uuidgenerator.net/ +BLEAdvertising *pAdvertising; +struct timeval now; + +#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/) + +static const char *eddystone_url_prefix_subs[] = { + "http://www.", + "https://www.", + "http://", + "https://", + "urn:uuid:", + NULL +}; + +static const char *eddystone_url_suffix_subs[] = { + ".com/", + ".org/", + ".edu/", + ".net/", + ".info/", + ".biz/", + ".gov/", + ".com", + ".org", + ".edu", + ".net", + ".info", + ".biz", + ".gov", + NULL +}; + +static int string_begin_with(const char *str, const char *prefix) +{ + int prefix_len = strlen(prefix); + if (strncmp(prefix, str, prefix_len) == 0) + { + return prefix_len; + } + return 0; +} + +void setBeacon() +{ + BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); + BLEAdvertisementData oScanResponseData = BLEAdvertisementData(); + + const char url[] = "https://d.giesecke.tk"; + + int scheme_len, ext_len = 1, i, idx, url_idx; + char *ret_data; + int url_len = strlen(url); + + ret_data = (char *)calloc(1, url_len + 13); + + ret_data[0] = 2; // Len + ret_data[1] = 0x01; // Type Flags + ret_data[2] = 0x06; // GENERAL_DISC_MODE 0x02 | BR_EDR_NOT_SUPPORTED 0x04 + ret_data[3] = 3; // Len + ret_data[4] = 0x03; // Type 16-Bit UUID + ret_data[5] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB + ret_data[6] = 0xFE; // Eddystone UUID 1 MSB + ret_data[7] = 19; // Length of Beacon Data + ret_data[8] = 0x16; // Type Service Data + ret_data[9] = 0xAA; // Eddystone UUID 2 -> 0xFEAA LSB + ret_data[10] = 0xFE; // Eddystone UUID 1 MSB + ret_data[11] = 0x10; // Eddystone Frame Type + ret_data[12] = 0xF4; // Beacons TX power at 0m + + i = 0, idx = 13, url_idx = 0; + + //replace prefix + scheme_len = 0; + while (eddystone_url_prefix_subs[i] != NULL) + { + if ((scheme_len = string_begin_with(url, eddystone_url_prefix_subs[i])) > 0) + { + ret_data[idx] = i; + idx++; + url_idx += scheme_len; + break; + } + i++; + } + while (url_idx < url_len) + { + i = 0; + ret_data[idx] = url[url_idx]; + ext_len = 1; + while (eddystone_url_suffix_subs[i] != NULL) + { + if ((ext_len = string_begin_with(&url[url_idx], eddystone_url_suffix_subs[i])) > 0) + { + ret_data[idx] = i; + break; + } + else + { + ext_len = 1; //inc 1 + } + i++; + } + url_idx += ext_len; + idx++; + } + ret_data[7] = idx - 8; + + Serial.printf("struct size %d url size %d reported len %d\n", + url_len + 13, + url_len, ret_data[7]); + + Serial.printf("URL in data %s\n", &ret_data[13]); + + std::string eddyStoneData(ret_data); + + oAdvertisementData.addData(eddyStoneData); + oScanResponseData.setName("URLBeacon"); + pAdvertising->setAdvertisementData(oAdvertisementData); + pAdvertising->setScanResponseData(oScanResponseData); +} + +void setup() +{ + + Serial.begin(115200); + gettimeofday(&now, NULL); + + Serial.printf("start ESP32 %d\n", bootcount++); + + Serial.printf("deep sleep (%lds since last reset, %lds since last boot)\n", now.tv_sec, now.tv_sec - last); + + last = now.tv_sec; + + // Create the BLE Device + BLEDevice::init("URLBeacon"); + + BLEDevice::setPower(ESP_PWR_LVL_N12); + + // Create the BLE Server + // BLEServer *pServer = BLEDevice::createServer(); // <-- no longer required to instantiate BLEServer, less flash and ram usage + + pAdvertising = BLEDevice::getAdvertising(); + + setBeacon(); + // Start advertising + pAdvertising->start(); + Serial.println("Advertizing started..."); + delay(10000); + pAdvertising->stop(); + Serial.printf("enter deep sleep\n"); + esp_deep_sleep(1000000LL * GPIO_DEEP_SLEEP_DURATION); + Serial.printf("in deep sleep\n"); +} + +void loop() +{ +} diff --git a/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.md b/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.md new file mode 100644 index 00000000..2baf1cc5 --- /dev/null +++ b/libraries/BLE/examples/BLE_EddystoneURL_Beacon/BLE_EddystoneURL_Beacon.md @@ -0,0 +1,14 @@ +## Eddystone URL beacon +EddystoneURL beacon by BeeGee based on +[pcbreflux ESP32 Eddystone URL deepsleep](https://github.com/pcbreflux/espressif/tree/master/esp32/arduino/sketchbook/ESP32_Eddystone_URL_deepsleep) + +[EddystoneURL frame specification](https://github.com/google/eddystone/blob/master/eddystone-url/README.md) + + Create a BLE server that will send periodic Eddystone URL frames. + The design of creating the BLE server is: + 1. Create a BLE Server + 2. Create advertising data + 3. Start advertising. + 4. wait + 5. Stop advertising. + 6. deep sleep diff --git a/libraries/BLE/src/BLEEddystoneTLM.cpp b/libraries/BLE/src/BLEEddystoneTLM.cpp index 1ab79493..accc4db1 100644 --- a/libraries/BLE/src/BLEEddystoneTLM.cpp +++ b/libraries/BLE/src/BLEEddystoneTLM.cpp @@ -3,6 +3,11 @@ * * Created on: Mar 12, 2018 * Author: pcbreflux + * Edited on: Mar 20, 2020 by beegee-tokyo + * Fix temperature value (8.8 fixed format) + * Fix time stamp (0.1 second resolution) + * Fixes based on EddystoneTLM frame specification https://github.com/google/eddystone/blob/master/eddystone-tlm/tlm-plain.md + * */ #include "sdkconfig.h" #if defined(CONFIG_BT_ENABLED) @@ -20,7 +25,7 @@ BLEEddystoneTLM::BLEEddystoneTLM() { m_eddystoneData.frameType = EDDYSTONE_TLM_FRAME_TYPE; m_eddystoneData.version = 0; m_eddystoneData.volt = 3300; // 3300mV = 3.3V - m_eddystoneData.temp = (uint16_t) ((float) 23.00); + m_eddystoneData.temp = (uint16_t) ((float) 23.00)/256; m_eddystoneData.advCount = 0; m_eddystoneData.tmil = 0; } // BLEEddystoneTLM @@ -38,41 +43,50 @@ uint8_t BLEEddystoneTLM::getVersion() { } // getVersion uint16_t BLEEddystoneTLM::getVolt() { - return m_eddystoneData.volt; + return ENDIAN_CHANGE_U16(m_eddystoneData.volt); } // getVolt float BLEEddystoneTLM::getTemp() { - return (float)m_eddystoneData.temp; + return ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f; } // getTemp uint32_t BLEEddystoneTLM::getCount() { - return m_eddystoneData.advCount; + return ENDIAN_CHANGE_U32(m_eddystoneData.advCount); } // getCount uint32_t BLEEddystoneTLM::getTime() { - return m_eddystoneData.tmil; + return (ENDIAN_CHANGE_U32(m_eddystoneData.tmil)) / 10; } // getTime std::string BLEEddystoneTLM::toString() { std::string out = ""; uint32_t rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); - char val[6]; + char val[12]; - out += "Version " + m_eddystoneData.version; + out += "Version "; // + std::string(m_eddystoneData.version); + snprintf(val, sizeof(val), "%d", m_eddystoneData.version); + out += val; out += "\n"; - out += "Battery Voltage " + ENDIAN_CHANGE_U16(m_eddystoneData.volt); + out += "Battery Voltage "; // + ENDIAN_CHANGE_U16(m_eddystoneData.volt); + snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U16(m_eddystoneData.volt)); + out += val; out += " mV\n"; out += "Temperature "; - snprintf(val, sizeof(val), "%d", m_eddystoneData.temp); + snprintf(val, sizeof(val), "%.2f", ENDIAN_CHANGE_U16(m_eddystoneData.temp) / 256.0f); out += val; - out += ".0 °C\n"; + out += " C\n"; out += "Adv. Count "; snprintf(val, sizeof(val), "%d", ENDIAN_CHANGE_U32(m_eddystoneData.advCount)); out += val; out += "\n"; + out += "Time in seconds "; + snprintf(val, sizeof(val), "%d", rawsec/10); + out += val; + out += "\n"; + out += "Time "; snprintf(val, sizeof(val), "%04d", rawsec / 864000);