ESPresense/ESP32-mqtt-room.ino

557 lines
16 KiB
Arduino
Raw Normal View History

2018-10-01 23:26:07 +02:00
/*
2019-03-19 23:06:31 +01:00
ESP32 Bluetooth Low Energy presence detection, for use with MQTT.
Version 0.0.6
2018-10-05 17:21:18 +02:00
Major thank you to the following contributors for their efforts:
pcbreflux for the original version of this code, as well as the eddystone handlers.
2019-05-22 02:32:12 +02:00
Andreis Speiss for his work on YouTube and his invaluable github at sensorsiot.
Sidddy for the implementation of Mi Flora plant sensor support. https://github.com/sidddy/flora
2018-10-01 23:26:07 +02:00
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>
2018-10-13 04:25:00 +02:00
extern "C" {
#include "freertos/FreeRTOS.h"
#include "freertos/timers.h"
}
#include "soc/timer_group_struct.h"
#include "soc/timer_group_reg.h"
#include <AsyncTCP.h>
2018-10-01 23:26:07 +02:00
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
2018-10-13 04:25:00 +02:00
#include <AsyncMqttClient.h>
#include <ArduinoJSON.h>
2018-12-10 22:29:24 +01:00
#include <ArduinoOTA.h>
2018-10-01 23:26:07 +02:00
#include "BLEBeacon.h"
#include "BLEEddystoneTLM.h"
#include "BLEEddystoneURL.h"
#include "Settings_local.h"
2018-10-01 23:26:07 +02:00
BLEScan* pBLEScan;
int scanTime = singleScanTime; //In seconds
2018-10-03 22:53:25 +02:00
int waitTime = scanInterval; //In seconds
2018-12-10 22:29:24 +01:00
bool updateInProgress = false;
String localIp;
2018-10-01 23:26:07 +02:00
2018-12-26 20:55:33 +01:00
uint16_t beaconUUID = 0xFEAA;
2018-10-01 23:26:07 +02:00
#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8))
WiFiClient espClient;
AsyncMqttClient mqttClient;
TimerHandle_t mqttReconnectTimer;
TimerHandle_t wifiReconnectTimer;
byte retryAttempts = 0;
2018-10-01 23:26:07 +02:00
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;
2018-10-03 22:53:25 +02:00
2018-10-01 23:26:07 +02:00
i -= 2;
}
2018-10-03 22:53:25 +02:00
2018-10-01 23:26:07 +02:00
return returnedString;
}
float calculateDistance(int rssi, int txPower) {
2019-05-22 02:32:12 +02:00
float distFl;
2018-10-01 23:26:07 +02:00
if (rssi == 0) {
return -1.0;
}
if (!txPower) {
// somewhat reasonable default value
txPower = -59;
2019-01-13 15:42:24 +01:00
} else if (txPower > 0) {
txPower = txPower * -1;
}
2018-10-01 23:26:07 +02:00
const float ratio = rssi * 1.0 / txPower;
if (ratio < 1.0) {
2019-05-22 02:32:12 +02:00
distFl = pow(ratio, 10);
2018-10-01 23:26:07 +02:00
} else {
2019-05-22 02:32:12 +02:00
distFl = (0.89976) * pow(ratio, 7.7095) + 0.111;
2018-10-01 23:26:07 +02:00
}
2019-05-22 02:32:12 +02:00
return round(distFl * 100) / 100;
2018-10-01 23:26:07 +02:00
}
2019-06-25 15:13:22 +02:00
bool sendTelemetry(int deviceCount = -1, int reportCount = -1) {
StaticJsonDocument<256> tele;
tele["room"] = room;
tele["ip"] = localIp;
tele["hostname"] = WiFi.getHostname();
2019-06-25 15:13:22 +02:00
tele["scan_dur"] = scanTime;
tele["wait_dur"] = waitTime;
tele["max_dist"] = maxDistance;
if (deviceCount > -1) {
2019-06-25 15:13:22 +02:00
Serial.printf("devices_discovered: %d\n\r",deviceCount);
tele["disc_ct"] = deviceCount;
}
if (reportCount > -1) {
Serial.printf("devices_reported: %d\n\r",reportCount);
tele["rept_ct"] = reportCount;
}
char teleMessageBuffer[258];
serializeJson(tele, teleMessageBuffer);
if (mqttClient.publish(telemetryTopic, 0, 1, teleMessageBuffer) == true) {
2019-05-22 02:32:12 +02:00
Serial.println("Telemetry sent");
return true;
} else {
Serial.println("Error sending telemetry");
return false;
}
}
void connectToWifi() {
Serial.println("Connecting to WiFi...");
2018-12-19 02:53:46 +01:00
WiFi.begin(ssid, password);
WiFi.setHostname(hostname);
}
void connectToMqtt() {
Serial.println("Connecting to MQTT");
if (WiFi.isConnected() && !updateInProgress) {
mqttClient.setCredentials(mqttUser, mqttPassword);
mqttClient.setClientId(hostname);
mqttClient.connect();
} else {
Serial.println("Cannot reconnect MQTT - WiFi error");
handleWifiDisconnect();
}
}
bool handleMqttDisconnect() {
2019-07-19 01:09:43 +02:00
if (updateInProgress) {
Serial.println("Not retrying MQTT connection - OTA update in progress");
return true;
}
if (retryAttempts > 10) {
Serial.println("Too many retries. Restarting");
ESP.restart();
} else {
retryAttempts++;
}
if (WiFi.isConnected() && !updateInProgress) {
Serial.println("Starting MQTT reconnect timer");
if (xTimerReset(mqttReconnectTimer, 0) == pdFAIL) {
Serial.println("failed to restart");
xTimerStart(mqttReconnectTimer, 0);
} else {
Serial.println("restarted");
}
} else {
Serial.print("Disconnected from WiFi; starting WiFi reconnect timiler\t");
handleWifiDisconnect();
}
}
bool handleWifiDisconnect() {
2019-07-19 01:09:43 +02:00
if (WiFi.isConnected()) {
Serial.println("WiFi appears to be connected. Not retrying.");
return true;
}
if (retryAttempts > 10) {
Serial.println("Too many retries. Restarting");
ESP.restart();
} else {
retryAttempts++;
}
if (mqttClient.connected()) {
mqttClient.disconnect();
}
if (xTimerIsTimerActive(mqttReconnectTimer) != pdFALSE) {
xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
}
if (xTimerReset(wifiReconnectTimer, 0) == pdFAIL) {
Serial.println("failed to restart");
xTimerStart(wifiReconnectTimer, 0);
return false;
} else {
Serial.println("restarted");
return true;
}
}
void WiFiEvent(WiFiEvent_t event) {
2019-07-19 01:09:43 +02:00
Serial.printf("[WiFi-event] event: %x\n\r", event);
2019-06-28 16:42:16 +02:00
switch(event) {
2018-12-19 02:53:46 +01:00
case SYSTEM_EVENT_STA_GOT_IP:
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, !LED_ON);
2019-03-19 23:03:51 +01:00
Serial.print("IP address: \t");
2018-12-19 02:53:46 +01:00
Serial.println(WiFi.localIP());
localIp = WiFi.localIP().toString().c_str();
2019-03-19 23:03:51 +01:00
Serial.print("Hostname: \t");
Serial.println(WiFi.getHostname());
2018-12-19 02:53:46 +01:00
connectToMqtt();
if (xTimerIsTimerActive(wifiReconnectTimer) != pdFALSE) {
Serial.println("Stopping wifi reconnect timer");
xTimerStop(wifiReconnectTimer, 0);
}
retryAttempts = 0;
2018-12-19 02:53:46 +01:00
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, LED_ON);
Serial.println("WiFi lost connection, resetting timer\t");
handleWifiDisconnect();
break;
2018-12-19 02:53:46 +01:00
case SYSTEM_EVENT_WIFI_READY:
Serial.println("Wifi Ready");
handleWifiDisconnect();
2018-12-19 02:53:46 +01:00
break;
case SYSTEM_EVENT_STA_START:
Serial.println("STA Start");
tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, hostname);
if (xTimerIsTimerActive(wifiReconnectTimer) != pdFALSE) {
TickType_t xRemainingTime = xTimerGetExpiryTime( wifiReconnectTimer ) - xTaskGetTickCount();
Serial.print("WiFi Time remaining: ");
Serial.println(xRemainingTime);
} else {
Serial.println("WiFi Timer is inactive; resetting\t");
handleWifiDisconnect();
}
2018-12-19 02:53:46 +01:00
break;
case SYSTEM_EVENT_STA_STOP:
Serial.println("STA Stop");
handleWifiDisconnect();
2018-12-19 02:53:46 +01:00
break;
}
}
void onMqttConnect(bool sessionPresent) {
Serial.println("Connected to MQTT.");
retryAttempts = 0;
if (mqttClient.publish(availabilityTopic, 0, 1, "CONNECTED") == true) {
Serial.print("Success sending message to topic:\t");
2018-12-12 14:34:58 +01:00
Serial.println(availabilityTopic);
} else {
Serial.println("Error sending message");
}
2019-06-25 15:13:22 +02:00
sendTelemetry();
}
void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
Serial.println("Disconnected from MQTT.");
handleMqttDisconnect();
}
bool reportDevice(BLEAdvertisedDevice advertisedDevice) {
// Serial.printf("\n\n");
2019-03-19 23:03:51 +01:00
StaticJsonDocument<500> doc;
String mac_address = advertisedDevice.getAddress().toString().c_str();
mac_address.replace(":","");
mac_address.toLowerCase();
2019-06-28 16:42:16 +02:00
// Serial.print("mac:\t");
// Serial.println(mac_address);
int rssi = advertisedDevice.getRSSI();
2019-01-13 15:42:24 +01:00
float distance;
2019-03-19 23:03:51 +01:00
doc["id"] = mac_address;
doc["uuid"] = mac_address;
doc["rssi"] = rssi;
if (advertisedDevice.haveName()){
String nameBLE = String(advertisedDevice.getName().c_str());
// Serial.print("Name: ");
// Serial.println(nameBLE);
2019-03-19 23:03:51 +01:00
doc["name"] = nameBLE;
2018-12-19 02:53:46 +01:00
// } else {
2019-03-19 23:03:51 +01:00
// doc["name"] = "unknown";
}
2019-06-28 16:42:16 +02:00
// Serial.printf("\n\r");
// Serial.printf("Advertised Device: %s \n\r", advertisedDevice.toString().c_str());
std::string strServiceData = advertisedDevice.getServiceData();
uint8_t cServiceData[100];
strServiceData.copy((char *)cServiceData, strServiceData.length(), 0);
2018-12-26 20:55:33 +01:00
if (advertisedDevice.getServiceDataUUID().equals(BLEUUID(beaconUUID))==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());
2019-03-19 23:03:51 +01:00
doc["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 {
2019-06-28 16:42:16 +02:00
// Serial.println("service data");
for (int i=0;i<strServiceData.length();i++) {
2019-06-28 16:42:16 +02:00
// Serial.printf("[%X]",cServiceData[i]);
}
}
// Serial.printf("\n");
} else {
if (advertisedDevice.haveManufacturerData()==true) {
std::string strManufacturerData = advertisedDevice.getManufacturerData();
// Serial.println("Got manufacturer data");
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);
2019-01-13 15:42:24 +01:00
distance = calculateDistance(rssi, oBeacon.getSignalPower());
2019-05-22 02:45:03 +02:00
// Serial.print("RSSI: ");
// Serial.print(rssi);
// Serial.print("\ttxPower: ");
// Serial.print(oBeacon.getSignalPower());
// Serial.print("\tDistance: ");
// Serial.println(distance);
int major = ENDIAN_CHANGE_U16(oBeacon.getMajor());
int minor = ENDIAN_CHANGE_U16(oBeacon.getMinor());
2019-03-19 23:03:51 +01:00
doc["major"] = major;
doc["minor"] = minor;
2019-03-19 23:03:51 +01:00
doc["uuid"] = proximityUUID;
doc["id"] = proximityUUID + "-" + String(major) + "-" + String(minor);
2019-03-19 23:03:51 +01:00
doc["txPower"] = oBeacon.getSignalPower();
doc["distance"] = distance;
2019-01-13 15:42:24 +01:00
} else {
if (advertisedDevice.haveTXPower()) {
distance = calculateDistance(rssi, advertisedDevice.getTXPower());
2019-03-19 23:03:51 +01:00
doc["txPower"] = advertisedDevice.getTXPower();
} else {
distance = calculateDistance(rssi, -59);
}
2019-03-19 23:03:51 +01:00
doc["distance"] = distance;
2019-06-28 16:42:16 +02:00
// Serial.printf("strManufacturerData: %d \n\r",strManufacturerData.length());
// TODO: parse manufacturer data
}
} else {
if (advertisedDevice.haveTXPower()) {
2019-01-13 15:42:24 +01:00
distance = calculateDistance(rssi, advertisedDevice.getTXPower());
2019-03-19 23:03:51 +01:00
doc["txPower"] = advertisedDevice.getTXPower();
doc["distance"] = distance;
} else {
2019-01-13 15:42:24 +01:00
distance = calculateDistance(rssi, -59);
2019-03-19 23:03:51 +01:00
doc["distance"] = distance;
}
2019-06-28 16:42:16 +02:00
// Serial.printf("no Beacon Advertised ServiceDataUUID: %d %s \n\r", advertisedDevice.getServiceDataUUID().bitSize(), advertisedDevice.getServiceDataUUID().toString().c_str());
}
}
char JSONmessageBuffer[512];
2019-03-19 23:03:51 +01:00
serializeJson(doc, JSONmessageBuffer);
String publishTopic = String(channel) + "/" + room;
if (mqttClient.connected()) {
if (maxDistance == 0 || doc["distance"] < maxDistance) {
if (mqttClient.publish((char *)publishTopic.c_str(), 0, 0, JSONmessageBuffer) == true) {
2018-12-19 02:53:46 +01:00
// Serial.print("Success sending message to topic: "); Serial.println(publishTopic);
return true;
} else {
Serial.print("Error sending message: ");
Serial.println(publishTopic);
Serial.print("Message: ");
Serial.println(JSONmessageBuffer);
return false;
}
} else {
Serial.printf("%s exceeded distance threshold %.2f\n\r", mac_address.c_str(), distance);
return false;
}
2018-12-19 02:53:46 +01:00
} else {
Serial.println("MQTT disconnected.");
if (xTimerIsTimerActive(mqttReconnectTimer) != pdFALSE) {
TickType_t xRemainingTime = xTimerGetExpiryTime( mqttReconnectTimer ) - xTaskGetTickCount();
Serial.print("Time remaining: ");
Serial.println(xRemainingTime);
} else {
handleMqttDisconnect();
}
2018-12-19 02:53:46 +01:00
}
return false;
}
2018-10-01 23:26:07 +02:00
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
2018-10-03 22:53:25 +02:00
void onResult(BLEAdvertisedDevice advertisedDevice) {
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, LED_ON);
2018-12-10 22:29:24 +01:00
// Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
2018-12-19 02:53:46 +01:00
vTaskDelay(10 / portTICK_PERIOD_MS);
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, !LED_ON);
}
2018-10-01 23:26:07 +02:00
};
unsigned long last = 0;
TaskHandle_t BLEScan;
2018-10-03 22:53:25 +02:00
void scanForDevices(void * parameter) {
while(1) {
2018-12-26 20:55:33 +01:00
if (!updateInProgress && WiFi.isConnected() && (millis() - last > (waitTime * 1000) || last == 0)) {
2019-05-22 02:32:12 +02:00
Serial.print("Scanning...\t");
2018-12-10 22:29:24 +01:00
BLEScanResults foundDevices = pBLEScan->start(scanTime);
int devicesCount = foundDevices.getCount();
2019-05-22 02:45:03 +02:00
Serial.printf("Scan done! Devices found: %d\n\r",devicesCount);
2019-05-22 02:32:12 +02:00
int devicesReported = 0;
2019-03-19 23:03:51 +01:00
if (mqttClient.connected()) {
for (uint32_t i = 0; i < devicesCount; i++) {
bool included = reportDevice(foundDevices.getDevice(i));
if (included) {
devicesReported++;
}
}
2019-06-25 15:13:22 +02:00
sendTelemetry(devicesCount, devicesReported);
pBLEScan->clearResults();
2019-03-19 23:03:51 +01:00
} else {
Serial.println("Cannot report; mqtt disconnected");
if (xTimerIsTimerActive(mqttReconnectTimer) != pdFALSE) {
TickType_t xRemainingTime = xTimerGetExpiryTime( mqttReconnectTimer ) - xTaskGetTickCount();
Serial.print("Time remaining: ");
Serial.println(xRemainingTime);
} else {
handleMqttDisconnect();
}
}
2019-03-19 23:03:51 +01:00
last = millis();
}
}
}
2018-10-03 22:53:25 +02:00
2018-12-10 22:29:24 +01:00
void configureOTA() {
ArduinoOTA
.onStart([]() {
2018-12-19 02:53:46 +01:00
Serial.println("OTA Start");
pBLEScan->stop();
2018-12-10 22:29:24 +01:00
updateInProgress = true;
2018-12-19 02:53:46 +01:00
mqttClient.disconnect(true);
xTimerStop(mqttReconnectTimer, 0); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
2018-12-10 22:29:24 +01:00
})
.onEnd([]() {
updateInProgress = false;
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, !LED_ON);
2019-05-22 02:45:03 +02:00
Serial.println("\n\rEnd");
2018-12-10 22:29:24 +01:00
})
.onProgress([](unsigned int progress, unsigned int total) {
byte percent = (progress / (total / 100));
2019-05-22 02:45:03 +02:00
Serial.printf("Progress: %u% \n\r", percent);
2018-12-10 22:29:24 +01:00
digitalWrite(LED_BUILTIN, percent % 2);
})
.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
ESP.restart();
});
ArduinoOTA.setHostname(hostname);
ArduinoOTA.setPort(8266);
2018-12-10 22:29:24 +01:00
ArduinoOTA.begin();
}
2018-10-01 23:26:07 +02:00
void setup() {
2018-10-01 23:26:07 +02:00
Serial.begin(115200);
2018-10-03 22:53:25 +02:00
2018-12-10 22:29:24 +01:00
pinMode(LED_BUILTIN, OUTPUT);
2018-12-26 20:55:33 +01:00
digitalWrite(LED_BUILTIN, LED_ON);
2018-12-10 22:29:24 +01:00
mqttReconnectTimer = xTimerCreate("mqttTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToMqtt));
wifiReconnectTimer = xTimerCreate("wifiTimer", pdMS_TO_TICKS(2000), pdFALSE, (void*)0, reinterpret_cast<TimerCallbackFunction_t>(connectToWifi));
2018-10-01 23:26:07 +02:00
WiFi.onEvent(WiFiEvent);
2018-10-01 23:26:07 +02:00
mqttClient.onConnect(onMqttConnect);
mqttClient.onDisconnect(onMqttDisconnect);
2018-10-03 22:53:25 +02:00
mqttClient.setServer(mqttHost, mqttPort);
2019-06-28 16:42:16 +02:00
mqttClient.setWill(availabilityTopic, 0, 1, "DISCONNECTED");
2018-12-19 02:53:46 +01:00
mqttClient.setKeepAlive(60);
connectToWifi();
2018-10-03 22:53:25 +02:00
2018-12-10 22:29:24 +01:00
configureOTA();
2018-10-01 23:26:07 +02:00
BLEDevice::init("");
pBLEScan = BLEDevice::getScan(); //create new scan
2018-12-10 22:29:24 +01:00
pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
pBLEScan->setActiveScan(activeScan);
pBLEScan->setInterval(bleScanInterval);
pBLEScan->setWindow(bleScanWindow);
xTaskCreatePinnedToCore(
scanForDevices,
"BLE Scan",
4096,
pBLEScan,
1,
&BLEScan,
1);
2018-10-01 23:26:07 +02:00
}
void loop() {
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE;
TIMERG0.wdt_feed=1;
TIMERG0.wdt_wprotect=0;
2018-12-10 22:29:24 +01:00
ArduinoOTA.handle();
2018-10-01 23:26:07 +02:00
}