Initial commit

This commit is contained in:
jptrsn 2018-10-01 17:26:07 -04:00
parent a81cbda7ea
commit 989a82ed0e
6 changed files with 667 additions and 0 deletions

134
BLEEddystoneTLM.cpp Normal file
View File

@ -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 <string.h>
#include <esp_log.h>
#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

50
BLEEddystoneTLM.h Normal file
View File

@ -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_ */

149
BLEEddystoneURL.cpp Normal file
View File

@ -0,0 +1,149 @@
/*
* BLEEddystoneURL.cpp
*
* Created on: Mar 12, 2018
* Author: pcbreflux
*/
#include "sdkconfig.h"
#if defined(CONFIG_BT_ENABLED)
#include <string.h>
#include <esp_log.h>
#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;i<lengthURL;i++) {
if (m_eddystoneData.url[i]>33&&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

42
BLEEddystoneURL.h Normal file
View File

@ -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_ */

269
ESP32-mqtt-room.ino Normal file
View File

@ -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 <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
#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;i<strServiceData.length();i++) {
Serial.printf("[%X]",cServiceData[i]);
}
}
Serial.printf("\n");
} 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 ) {
BLEBeacon oBeacon = BLEBeacon();
oBeacon.setData(strManufacturerData);
String proximityUUID = getProximityUUIDString(oBeacon);
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()),proximityUUID.c_str(),oBeacon.getSignalPower());
float distance = calculateDistance(rssi, oBeacon.getSignalPower());
Serial.print("RSSI: ");
Serial.print(rssi);
Serial.print("\txPower: ");
Serial.print(oBeacon.getSignalPower());
Serial.print("\tDistance: ");
Serial.println(distance);
int major = ENDIAN_CHANGE_U16(oBeacon.getMajor());
int minor = ENDIAN_CHANGE_U16(oBeacon.getMinor());
JSONencoder["major"] = major;
JSONencoder["minor"] = minor;
JSONencoder["uuid"] = proximityUUID;
JSONencoder["id"] = proximityUUID + "-" + String(major) + "-0";
JSONencoder["txPower"] = oBeacon.getSignalPower();
JSONencoder["distance"] = distance;
} else {
if (advertisedDevice.haveTXPower()) {
float distance = calculateDistance(rssi, advertisedDevice.getTXPower());
JSONencoder["distance"] = distance;
} else {
float distance = calculateDistance(rssi, -59);
JSONencoder["distance"] = distance;
}
Serial.printf("strManufacturerData: %d \n",strManufacturerData.length());
// TODO: parse manufacturer data
// for (int i=0;i<strManufacturerData.length();i++) {
// Serial.printf("[%X]",cManufacturerData[i]);
// }
// Serial.printf("\n");
}
} else {
if (advertisedDevice.haveTXPower()) {
float distance = calculateDistance(rssi, advertisedDevice.getTXPower());
JSONencoder["distance"] = distance;
} else {
float distance = calculateDistance(rssi, -59);
JSONencoder["distance"] = distance;
}
Serial.printf("no Beacon Advertised ServiceDataUUID: %d %s \n", advertisedDevice.getServiceDataUUID().bitSize(), advertisedDevice.getServiceDataUUID().toString().c_str());
}
}
char JSONmessageBuffer[500];
JSONencoder.printTo(JSONmessageBuffer, sizeof(JSONmessageBuffer));
String publishTopic = String(base_topic) + "/" + room;
if (client.publish(publishTopic.c_str(), JSONmessageBuffer) == true) {
// Serial.print("Success sending message to topic: "); Serial.println(publishTopic);
// Serial.print("Message: "); Serial.println(JSONmessageBuffer);
} else {
Serial.print("Error sending message: "); Serial.println(publishTopic);
Serial.print("Message: "); Serial.println(JSONmessageBuffer);
}
}
};
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("Connecting to WiFi..");
}
Serial.println("Connected to the WiFi network");
client.setServer(mqttServer, mqttPort);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("ESP32Client", mqttUser, mqttPassword )) {
Serial.println("connected");
} else {
Serial.print("failed with state ");
Serial.print(client.state());
delay(2000);
}
}
if (client.publish(base_topic, "Hello from ESP32") == true) { //TODO base_topic + mac_address
Serial.println("Success sending message to topic");
} else {
Serial.println("Error sending message");
}
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
pBLEScan->setAdvertisedDeviceCallbacks(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);
}

23
Settings.h Normal file
View File

@ -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$"