From 989a82ed0e520f5b24e807ab8bb9d7cb004c9198 Mon Sep 17 00:00:00 2001 From: jptrsn Date: Mon, 1 Oct 2018 17:26:07 -0400 Subject: [PATCH] Initial commit --- BLEEddystoneTLM.cpp | 134 ++++++++++++++++++++++ BLEEddystoneTLM.h | 50 ++++++++ BLEEddystoneURL.cpp | 149 ++++++++++++++++++++++++ BLEEddystoneURL.h | 42 +++++++ ESP32-mqtt-room.ino | 269 ++++++++++++++++++++++++++++++++++++++++++++ Settings.h | 23 ++++ 6 files changed, 667 insertions(+) create mode 100644 BLEEddystoneTLM.cpp create mode 100644 BLEEddystoneTLM.h create mode 100644 BLEEddystoneURL.cpp create mode 100644 BLEEddystoneURL.h create mode 100644 ESP32-mqtt-room.ino create mode 100644 Settings.h diff --git a/BLEEddystoneTLM.cpp b/BLEEddystoneTLM.cpp new file mode 100644 index 0000000..2fa1e03 --- /dev/null +++ b/BLEEddystoneTLM.cpp @@ -0,0 +1,134 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "Arduino.h" +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEEddystoneTLM.h" + +static const char LOG_TAG[] = "BLEEddystoneTLM"; +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) +#define ENDIAN_CHANGE_U32(x) ((((x)&0xFF000000)>>24) + (((x)&0x00FF0000)>>8)) + ((((x)&0xFF00)<<8) + (((x)&0xFF)<<24)) + +BLEEddystoneTLM::BLEEddystoneTLM() { + beconUUID = 0xFEAA; + 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.advCount = 0; + m_eddystoneData.tmil = 0; +} // BLEEddystoneTLM + +std::string BLEEddystoneTLM::getData() { + return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneTLM::getUUID() { + return BLEUUID(beconUUID); +} // getUUID + +uint8_t BLEEddystoneTLM::getVersion() { + return m_eddystoneData.version; +} // getVersion + +uint16_t BLEEddystoneTLM::getVolt() { + return m_eddystoneData.volt; +} // getVolt + +float BLEEddystoneTLM::getTemp() { + return (float)m_eddystoneData.temp; +} // getTemp + +uint32_t BLEEddystoneTLM::getCount() { + return m_eddystoneData.advCount; +} // getCount + +uint32_t BLEEddystoneTLM::getTime() { + return m_eddystoneData.tmil; +} // getTime + +std::string BLEEddystoneTLM::toString() { + std::string out = ""; + String buff; + uint32_t rawsec; + + out += "Version "; + buff = String(m_eddystoneData.version, DEC); + out += buff.c_str(); + out += "\n"; + + out += "Battery Voltage "; + buff = String(ENDIAN_CHANGE_U16(m_eddystoneData.volt), DEC); + out += buff.c_str(); + out += " mV\n"; + + out += "Temperature "; + buff = String((float)m_eddystoneData.temp, 1); + out += buff.c_str(); + out += " °C\n"; + + out += "Adv. Count "; + buff = String(ENDIAN_CHANGE_U32(m_eddystoneData.advCount), DEC); + out += buff.c_str(); + out += "\n"; + + out += "Time "; + rawsec = ENDIAN_CHANGE_U32(m_eddystoneData.tmil); + buff = "0000"+String(rawsec/864000, DEC); + out += buff.substring(buff.length()-4,buff.length()).c_str(); + out += "."; + buff = "00"+String((rawsec/36000)%24, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += ":"; + buff = "00"+String((rawsec/600)%60, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += ":"; + buff = "00"+String((rawsec/10)%60, DEC); + out += buff.substring(buff.length()-2,buff.length()).c_str(); + out += "\n"; + + return out; +} // toString + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneTLM::setData(std::string data) { + if (data.length() != sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memcpy(&m_eddystoneData, data.data(), data.length()); +} // setData + +void BLEEddystoneTLM::setUUID(BLEUUID l_uuid) { + beconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneTLM::setVersion(uint8_t version) { + m_eddystoneData.version = version; +} // setVersion + +void BLEEddystoneTLM::setVolt(uint16_t volt) { + m_eddystoneData.volt = volt; +} // setVolt + +void BLEEddystoneTLM::setTemp(float temp) { + m_eddystoneData.temp = (uint16_t)temp; +} // setTemp + +void BLEEddystoneTLM::setCount(uint32_t advCount) { + m_eddystoneData.advCount = advCount; +} // setCount + +void BLEEddystoneTLM::setTime(uint32_t tmil) { + m_eddystoneData.tmil = tmil; +} // setTime + +#endif diff --git a/BLEEddystoneTLM.h b/BLEEddystoneTLM.h new file mode 100644 index 0000000..de65d6f --- /dev/null +++ b/BLEEddystoneTLM.h @@ -0,0 +1,50 @@ +/* + * BLEEddystoneTLM.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneTLM_H_ +#define _BLEEddystoneTLM_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_TLM_FRAME_TYPE 0x20 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneTLM { +private: + uint16_t beconUUID; + struct { + uint8_t frameType; + int8_t version; + uint16_t volt; + uint16_t temp; + uint32_t advCount; + uint32_t tmil; + } __attribute__((packed))m_eddystoneData; +public: + BLEEddystoneTLM(); + std::string getData(); + BLEUUID getUUID(); + uint8_t getVersion(); + uint16_t getVolt(); + float getTemp(); + uint32_t getCount(); + uint32_t getTime(); + std::string toString(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setVersion(uint8_t version); + void setVolt(uint16_t volt); + void setTemp(float temp); + void setCount(uint32_t advCount); + void setTime(uint32_t tmil); + +}; // BLEEddystoneTLM + +#endif /* _BLEEddystoneTLM_H_ */ diff --git a/BLEEddystoneURL.cpp b/BLEEddystoneURL.cpp new file mode 100644 index 0000000..ceb4f7a --- /dev/null +++ b/BLEEddystoneURL.cpp @@ -0,0 +1,149 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ +#include "sdkconfig.h" +#if defined(CONFIG_BT_ENABLED) +#include +#include +#include "BLEEddystoneURL.h" + +static const char LOG_TAG[] = "BLEEddystoneURL"; + +BLEEddystoneURL::BLEEddystoneURL() { + beconUUID = 0xFEAA; + lengthURL = 0; + m_eddystoneData.frameType = EDDYSTONE_URL_FRAME_TYPE; + m_eddystoneData.advertisedTxPower = 0; + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); +} // BLEEddystoneURL + +std::string BLEEddystoneURL::getData() { + return std::string((char*)&m_eddystoneData, sizeof(m_eddystoneData)); +} // getData + +BLEUUID BLEEddystoneURL::getUUID() { + return BLEUUID(beconUUID); +} // getUUID + +int8_t BLEEddystoneURL::getPower() { + return m_eddystoneData.advertisedTxPower; +} // getPower + +std::string BLEEddystoneURL::getURL() { + return std::string((char*)&m_eddystoneData.url, sizeof(m_eddystoneData.url)); +} // getURL + +std::string BLEEddystoneURL::getDecodedURL() { + std::string decodedURL = ""; + + switch (m_eddystoneData.url[0]) { + case 0x00: + decodedURL += "http://www."; + break; + case 0x01: + decodedURL += "https://www."; + break; + case 0x02: + decodedURL += "http://"; + break; + case 0x03: + decodedURL += "https://"; + break; + default: + decodedURL += m_eddystoneData.url[0]; + } + + for (int i=1;i33&&m_eddystoneData.url[i]<127) { + decodedURL += m_eddystoneData.url[i]; + } else { + switch (m_eddystoneData.url[i]) { + case 0x00: + decodedURL += ".com/"; + break; + case 0x01: + decodedURL += ".org/"; + break; + case 0x02: + decodedURL += ".edu/"; + break; + case 0x03: + decodedURL += ".net/"; + break; + case 0x04: + decodedURL += ".info/"; + break; + case 0x05: + decodedURL += ".biz/"; + break; + case 0x06: + decodedURL += ".gov/"; + break; + case 0x07: + decodedURL += ".com"; + break; + case 0x08: + decodedURL += ".org"; + break; + case 0x09: + decodedURL += ".edu"; + break; + case 0x0A: + decodedURL += ".net"; + break; + case 0x0B: + decodedURL += ".info"; + break; + case 0x0C: + decodedURL += ".biz"; + break; + case 0x0D: + decodedURL += ".gov"; + break; + } + } + } + + + return decodedURL; +} // getDecodedURL + + + +/** + * Set the raw data for the beacon record. + */ +void BLEEddystoneURL::setData(std::string data) { + if (data.length() > sizeof(m_eddystoneData)) { + ESP_LOGE(LOG_TAG, "Unable to set the data ... length passed in was %d and max expected %d", data.length(), sizeof(m_eddystoneData)); + return; + } + memset(&m_eddystoneData, 0, sizeof(m_eddystoneData)); + memcpy(&m_eddystoneData, data.data(), data.length()); + lengthURL=data.length()-(sizeof(m_eddystoneData)-sizeof(m_eddystoneData.url)); + +} // setData + +void BLEEddystoneURL::setUUID(BLEUUID l_uuid) { + beconUUID = l_uuid.getNative()->uuid.uuid16; +} // setUUID + +void BLEEddystoneURL::setPower(int8_t advertisedTxPower) { + m_eddystoneData.advertisedTxPower = advertisedTxPower; +} // setPower + +void BLEEddystoneURL::setURL(std::string url) { + if (url.length() > sizeof(m_eddystoneData.url)) { + ESP_LOGE(LOG_TAG, "Unable to set the url ... length passed in was %d and max expected %d", url.length(), sizeof(m_eddystoneData.url)); + return; + } + memset(m_eddystoneData.url, 0, sizeof(m_eddystoneData.url)); + memcpy(m_eddystoneData.url, url.data(), url.length()); + lengthURL=url.length(); +} // setURL + + +#endif diff --git a/BLEEddystoneURL.h b/BLEEddystoneURL.h new file mode 100644 index 0000000..764fdac --- /dev/null +++ b/BLEEddystoneURL.h @@ -0,0 +1,42 @@ +/* + * BLEEddystoneURL.cpp + * + * Created on: Mar 12, 2018 + * Author: pcbreflux + */ + +#ifndef _BLEEddystoneURL_H_ +#define _BLEEddystoneURL_H_ +#include "BLEUUID.h" + +#define EDDYSTONE_URL_FRAME_TYPE 0x10 + +/** + * @brief Representation of a beacon. + * See: + * * https://github.com/google/eddystone + */ +class BLEEddystoneURL { +private: + uint16_t beconUUID; + uint8_t lengthURL; + struct { + uint8_t frameType; + int8_t advertisedTxPower; + uint8_t url[16]; + } __attribute__((packed))m_eddystoneData; +public: + BLEEddystoneURL(); + std::string getData(); + BLEUUID getUUID(); + int8_t getPower(); + std::string getURL(); + std::string getDecodedURL(); + void setData(std::string data); + void setUUID(BLEUUID l_uuid); + void setPower(int8_t advertisedTxPower); + void setURL(std::string url); + +}; // BLEEddystoneURL + +#endif /* _BLEEddystoneURL_H_ */ diff --git a/ESP32-mqtt-room.ino b/ESP32-mqtt-room.ino new file mode 100644 index 0000000..c27cccf --- /dev/null +++ b/ESP32-mqtt-room.ino @@ -0,0 +1,269 @@ +/* + 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 +*/ +#include +#include +#include + +#include +#include +#include +#include +#include "BLEBeacon.h" +#include "BLEEddystoneTLM.h" +#include "BLEEddystoneURL.h" +#include "Settings_local.h" + +BLEScan* pBLEScan; +int scanTime = 5; //In seconds +int waitTime = 15; //In seconds + +uint16_t beconUUID = 0xFEAA; +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) +#define base_topic "esp32" // No trailing slash + +WiFiClient espClient; +PubSubClient client(espClient); + +String getProximityUUIDString(BLEBeacon beacon) { + std::string serviceData = beacon.getProximityUUID().toString().c_str(); + int serviceDataLength = serviceData.length(); + String returnedString = ""; + int i = serviceDataLength; + while (i > 0) + { + if (serviceData[i-1] == '-') { + i--; + } + char a = serviceData[i-1]; + char b = serviceData[i-2]; + returnedString += b; + returnedString += a; + + i -= 2; + } + + return returnedString; +} + +float calculateDistance(int rssi, int txPower) { + + if (rssi == 0) { + return -1.0; + } + + if (!txPower) { + // somewhat reasonable default value + txPower = -59; + } + + const float ratio = rssi * 1.0 / txPower; + if (ratio < 1.0) { + return pow(ratio, 10); + } else { + return (0.89976) * pow(ratio, 7.7095) + 0.111; + } + +} + +void reconnect() { + while (!client.connected()) { + Serial.print("Attempting MQTT connection..."); + if (client.connect("ESP32Client", mqttUser, mqttPassword )) { + Serial.println("connected"); + } else { + Serial.print("failed, rc="); + Serial.print(client.state()); + Serial.println(" try again in 5 seconds"); + // Wait 5 seconds before retrying + delay(5000); + } + } +} + +class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { + + void onResult(BLEAdvertisedDevice advertisedDevice) { + + StaticJsonBuffer<500> JSONbuffer; + JsonObject& JSONencoder = JSONbuffer.createObject(); + + String mac_adress = advertisedDevice.getAddress().toString().c_str(); + mac_adress.replace(":",""); + mac_adress.toLowerCase(); + int rssi = advertisedDevice.getRSSI(); + + JSONencoder["id"] = mac_adress; + JSONencoder["rssi"] = rssi; + + if (advertisedDevice.haveName()){ + String nameBLE = advertisedDevice.getName().c_str(); + JSONencoder["name"] = nameBLE; + } + + Serial.printf("\n\n"); + Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str()); + std::string strServiceData = advertisedDevice.getServiceData(); + uint8_t cServiceData[100]; + strServiceData.copy((char *)cServiceData, strServiceData.length(), 0); + + if (advertisedDevice.getServiceDataUUID().equals(BLEUUID(beconUUID))==true) { // found Eddystone UUID + Serial.printf("is Eddystone: %d %s length %d\n", advertisedDevice.getServiceDataUUID().bitSize(), advertisedDevice.getServiceDataUUID().toString().c_str(),strServiceData.length()); + if (cServiceData[0]==0x10) { + BLEEddystoneURL oBeacon = BLEEddystoneURL(); + oBeacon.setData(strServiceData); + Serial.printf("Eddystone Frame Type (Eddystone-URL) "); + Serial.printf(oBeacon.getDecodedURL().c_str()); + JSONencoder["url"] = oBeacon.getDecodedURL().c_str(); + + } else if (cServiceData[0]==0x20) { + BLEEddystoneTLM oBeacon = BLEEddystoneTLM(); + oBeacon.setData(strServiceData); + Serial.printf("Eddystone Frame Type (Unencrypted Eddystone-TLM) \n"); + Serial.printf(oBeacon.toString().c_str()); + } else { + for (int i=0;isetAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); + pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster +} + +unsigned long last = 0; + +void loop() { + if (!client.connected()) { + reconnect(); + } + client.loop(); + + if (millis() - last > (waitTime * 1000)) { + Serial.println("Scanning..."); + BLEScanResults foundDevices = pBLEScan->start(scanTime); + Serial.printf("\nScan done! Devices found: %d\n",foundDevices.getCount()); + last = millis(); + } + delay(1); +} diff --git a/Settings.h b/Settings.h new file mode 100644 index 0000000..e5ef454 --- /dev/null +++ b/Settings.h @@ -0,0 +1,23 @@ +//Replace with your Wifi SSID; example: #define ssid "MyWifi" +#define ssid "$SSID$" + +//Replace with your Wifi password; example: #define password "12345678" +#define password "$WIFI_PASSWORD$" + +//Replace with a human-friendly host name. +#define hostname "esp32_room_presence" + +//Replace with your MQTT Broker address; example: #define mqttServer "192.168.0.112" +#define mqttServer "$MQTT_BROKER_ADDRESS$" + +//Replace with your MQTT Broker port; example: #define mqttPort 1883 +#define mqttPort $MQTT_PORT$ + +//Replace with your MQTT Broker user; example: #define mqttUser "homeassistant" +#define mqttUser "§MQTT_USER$" + +//Replace with your MQTT Broker password; example: #define mqttPassword "12345678" +#define mqttPassword "$MQTT_PASSWORD" + +//Replace with the room name where the node will be placed; example: #define room "living-room" +#define room "$ROOM_NAME$"