Added support for the Xioami Flower Care sensor (#779)

* Remove room assistant support since irks are far superior
* Bunch of refactors and reorgs

---------

Co-authored-by: Yashar Zenker <yashar.zenker@gmail.com>
Co-authored-by: DTTerastar <DT@Terastar.biz>
This commit is contained in:
flush 2023-08-22 12:44:43 +02:00 committed by GitHub
parent d55c1451c0
commit 15a3f8c529
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 324 additions and 118 deletions

View File

@ -1,5 +1,7 @@
#include "BleFingerprint.h" #include "BleFingerprint.h"
#include "../handlers/MiFloraHandler.h"
#include "../handlers/NameModelHandler.h"
#include "BleFingerprintCollection.h" #include "BleFingerprintCollection.h"
#include "mbedtls/aes.h" #include "mbedtls/aes.h"
#include "rssi.h" #include "rssi.h"
@ -76,13 +78,13 @@ BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmi
addressType = advertisedDevice->getAddressType(); addressType = advertisedDevice->getAddressType();
newest = recent = oldest = rssi = advertisedDevice->getRSSI(); newest = recent = oldest = rssi = advertisedDevice->getRSSI();
seenCount = 1; seenCount = 1;
queryReport = nullptr;
fingerprintAddress(); fingerprintAddress();
} }
void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice) { void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice) {
if (advertisedDevice->haveName()) { if (advertisedDevice->haveName()) {
std::string name = advertisedDevice->getName(); const std::string name = advertisedDevice->getName();
if (!name.empty()) setId(String("name:") + kebabify(name).c_str(), ID_TYPE_NAME, String(name.c_str())); if (!name.empty()) setId(String("name:") + kebabify(name).c_str(), ID_TYPE_NAME, String(name.c_str()));
} }
@ -100,20 +102,20 @@ void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice) {
} }
int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_data) { int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_data) {
mbedtls_aes_context s = {0}; mbedtls_aes_context ctx;
mbedtls_aes_init(&s); mbedtls_aes_init(&ctx);
if (mbedtls_aes_setkey_enc(&s, key, 128) != 0) { if (mbedtls_aes_setkey_enc(&ctx, key, 128) != 0) {
mbedtls_aes_free(&s); mbedtls_aes_free(&ctx);
return BLE_HS_EUNKNOWN; return BLE_HS_EUNKNOWN;
} }
if (mbedtls_aes_crypt_ecb(&s, MBEDTLS_AES_ENCRYPT, plaintext, enc_data) != 0) { if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, plaintext, enc_data) != 0) {
mbedtls_aes_free(&s); mbedtls_aes_free(&ctx);
return BLE_HS_EUNKNOWN; return BLE_HS_EUNKNOWN;
} }
mbedtls_aes_free(&s); mbedtls_aes_free(&ctx);
return 0; return 0;
} }
@ -144,7 +146,7 @@ bool ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk) {
ecb.plain_text[14] = rpa[4]; ecb.plain_text[14] = rpa[4];
ecb.plain_text[13] = rpa[5]; ecb.plain_text[13] = rpa[5];
auto err = bt_encrypt_be(ecb.key, ecb.plain_text, ecb.cipher_text); bt_encrypt_be(ecb.key, ecb.plain_text, ecb.cipher_text);
if (ecb.cipher_text[15] != rpa[0] || ecb.cipher_text[14] != rpa[1] || ecb.cipher_text[13] != rpa[2]) return false; if (ecb.cipher_text[15] != rpa[0] || ecb.cipher_text[14] != rpa[1] || ecb.cipher_text[13] != rpa[2]) return false;
@ -165,7 +167,7 @@ void BleFingerprint::fingerprintAddress() {
break; break;
case BLE_ADDR_RANDOM: case BLE_ADDR_RANDOM:
case BLE_ADDR_RANDOM_ID: { case BLE_ADDR_RANDOM_ID: {
auto naddress = address.getNative(); const auto *naddress = address.getNative();
if ((naddress[5] & 0xc0) == 0xc0) if ((naddress[5] & 0xc0) == 0xc0)
setId(mac, ID_TYPE_RAND_STATIC_MAC); setId(mac, ID_TYPE_RAND_STATIC_MAC);
else { else {
@ -193,18 +195,7 @@ void BleFingerprint::fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *ad
#ifdef VERBOSE #ifdef VERBOSE
Serial.printf("Verbose | %s | %-58s%ddBm AD: %s\r\n", getMac().c_str(), getId().c_str(), rssi, advertisedDevice->getServiceUUID(i).toString().c_str()); Serial.printf("Verbose | %s | %-58s%ddBm AD: %s\r\n", getMac().c_str(), getId().c_str(), rssi, advertisedDevice->getServiceUUID(i).toString().c_str());
#endif #endif
if (uuid == roomAssistantService) { if (uuid == tileUUID) {
asRssi = BleFingerprintCollection::rxRefRssi + RM_ASST_TX;
if (!rmAsst) {
rmAsst = true;
if (didQuery) {
qryDelayMillis = 0;
qryAttempts = 0;
didQuery = false;
}
}
return;
} else if (uuid == tileUUID) {
asRssi = BleFingerprintCollection::rxRefRssi + TILE_TX; asRssi = BleFingerprintCollection::rxRefRssi + TILE_TX;
setId("tile:" + getMac(), ID_TYPE_TILE); setId("tile:" + getMac(), ID_TYPE_TILE);
return; return;
@ -428,7 +419,6 @@ bool BleFingerprint::seen(BLEAdvertisedDevice *advertisedDevice) {
auto the_min = min(min(oldest, recent), newest); auto the_min = min(min(oldest, recent), newest);
auto the_max = max(max(oldest, recent), newest); auto the_max = max(max(oldest, recent), newest);
auto the_median = the_max ^ the_min ^ oldest ^ recent ^ newest; auto the_median = the_max ^ the_min ^ oldest ^ recent ^ newest;
auto range = the_max - the_min;
rssi = the_median; rssi = the_median;
@ -504,14 +494,14 @@ bool BleFingerprint::report(JsonObject *doc) {
} }
bool BleFingerprint::query() { bool BleFingerprint::query() {
if (!(allowQuery || rmAsst) || didQuery) return false; if (!allowQuery || isQuerying) return false;
if (rssi < -90) return false; if (rssi < -90) return false; // Too far away
auto now = millis(); auto now = millis();
if (now - lastSeenMillis > 5) return false; // Haven't seen lately
if (now - lastQryMillis < qryDelayMillis) return false; // Too soon
if (now - lastSeenMillis > 5) return false; isQuerying = true;
if (now - lastQryMillis < qryDelayMillis) return false;
didQuery = true;
lastQryMillis = now; lastQryMillis = now;
bool success = false; bool success = false;
@ -526,48 +516,25 @@ bool BleFingerprint::query() {
pClient->setConnectTimeout(5); pClient->setConnectTimeout(5);
NimBLEDevice::getScan()->stop(); NimBLEDevice::getScan()->stop();
if (pClient->connect(address)) { if (pClient->connect(address)) {
bool iphone = true;
if (allowQuery) { if (allowQuery) {
std::string sMdl = pClient->getValue(deviceInformationService, modelChar); if (id.startsWith("flora:"))
std::string sName = pClient->getValue(genericAccessService, nameChar); success = MiFloraHandler::requestData(pClient, this);
iphone = sMdl.find("iPhone") == 0; else
if (!sName.empty() && sMdl.find(sName) == std::string::npos && sName != "Apple Watch") { success = NameModelHandler::requestData(pClient, this);
if (setId(String("name:") + kebabify(sName).c_str(), ID_TYPE_QUERY_NAME, String(sName.c_str()))) {
Serial.printf("\u001b[38;5;104m%u Name | %s | %-58s%ddBm %s\u001b[0m\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, sName.c_str());
}
success = true;
}
if (!sMdl.empty()) {
if (setId(String("apple:") + kebabify(sMdl).c_str(), ID_TYPE_QUERY_MODEL, String(sMdl.c_str()))) {
Serial.printf("\u001b[38;5;136m%u Model | %s | %-58s%ddBm %s\u001b[0m\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, sMdl.c_str());
}
success = true;
}
}
if (rmAsst || iphone) // For some reason we often don't get room assistant's service advertisement
{
std::string sRmAst = pClient->getValue(roomAssistantService, rootAssistantCharacteristic);
if (!sRmAst.empty()) {
if (setId(String("roomAssistant:") + kebabify(sRmAst).c_str(), ID_TYPE_RM_ASST)) {
Serial.printf("\u001b[38;5;129m%u RmAst | %s | %-58s%ddBm %s\u001b[0m\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, sRmAst.c_str());
}
success = true;
}
} }
} }
NimBLEDevice::deleteClient(pClient); NimBLEDevice::deleteClient(pClient);
if (success) return true; if (success) {
qryAttempts = 0;
qryAttempts++; qryDelayMillis = BleFingerprintCollection::requeryMs;
qryDelayMillis = min(int(pow(10, qryAttempts)), 60000); } else {
Serial.printf("%u QryErr | %s | %-58s%ddBm Try %d, retry after %dms\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, qryAttempts, qryDelayMillis); qryAttempts++;
qryDelayMillis = min(int(pow(10, qryAttempts)), 60000);
didQuery = false; Serial.printf("%u QryErr | %s | %-58s%ddBm Try %d, retry after %dms\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, qryAttempts, qryDelayMillis);
}
isQuerying = false;
return true; return true;
} }

View File

@ -1,8 +1,5 @@
#ifndef _BLEFINGERPRINT_ #ifndef _BLEFINGERPRINT_
#define _BLEFINGERPRINT_ #define _BLEFINGERPRINT_
#include "rssi.h"
#include "string_utils.h"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <NimBLEAdvertisedDevice.h> #include <NimBLEAdvertisedDevice.h>
#include <NimBLEBeacon.h> #include <NimBLEBeacon.h>
@ -11,7 +8,13 @@
#include <NimBLEEddystoneURL.h> #include <NimBLEEddystoneURL.h>
#include <SoftFilters.h> #include <SoftFilters.h>
#define NO_RSSI (-128) #include <memory>
#include "QueryReport.h"
#include "rssi.h"
#include "string_utils.h"
#define NO_RSSI int8_t(-128)
#define ID_TYPE_TX_POW short(1) #define ID_TYPE_TX_POW short(1)
@ -111,7 +114,10 @@ public:
bool getAllowQuery() const { return allowQuery; }; bool getAllowQuery() const { return allowQuery; };
bool getRmAsst() const { return rmAsst; }; const bool hasReport() { return queryReport != nullptr; };
const QueryReport getReport() { return *queryReport; };
void setReport(const QueryReport &report) { queryReport = std::unique_ptr<QueryReport>(new QueryReport {report}); };
void clearReport() { queryReport.reset(); };
unsigned int getSeenCount() unsigned int getSeenCount()
{ {
@ -129,7 +135,7 @@ private:
static bool shouldHide(const String &s); static bool shouldHide(const String &s);
bool hasValue = false, added = false, close = false, reported = false, ignore = false, allowQuery = false, didQuery = false, rmAsst = false, hidden = false, connectable = false, countable = false, counting = false; bool hasValue = false, added = false, close = false, reported = false, ignore = false, allowQuery = false, isQuerying = false, hidden = false, connectable = false, countable = false, counting = false;
NimBLEAddress address; NimBLEAddress address;
String id, name, disc; String id, name, disc;
short int idType = NO_ID_TYPE; short int idType = NO_ID_TYPE;
@ -147,6 +153,7 @@ private:
OneEuroFilter<float, unsigned long> oneEuro; OneEuroFilter<float, unsigned long> oneEuro;
DifferentialFilter<float, unsigned long> diffFilter; DifferentialFilter<float, unsigned long> diffFilter;
std::unique_ptr<QueryReport> queryReport = nullptr;
bool filter(); bool filter();
void fingerprint(NimBLEAdvertisedDevice *advertisedDevice); void fingerprint(NimBLEAdvertisedDevice *advertisedDevice);

View File

@ -8,7 +8,7 @@ namespace BleFingerprintCollection {
String include{}, exclude{}, query{}, knownMacs{}, knownIrks{}, countIds{}; String include{}, exclude{}, query{}, knownMacs{}, knownIrks{}, countIds{};
float skipDistance = 0.0f, maxDistance = 0.0f, absorption = 3.5f, countEnter = 2, countExit = 4; float skipDistance = 0.0f, maxDistance = 0.0f, absorption = 3.5f, countEnter = 2, countExit = 4;
int8_t rxRefRssi = -65, rxAdjRssi = 0, txRefRssi = -59; int8_t rxRefRssi = -65, rxAdjRssi = 0, txRefRssi = -59;
int forgetMs = 0, skipMs = 0, countMs = 10000; int forgetMs = 0, skipMs = 0, countMs = 10000, requeryMs = 300000;
std::vector<DeviceConfig> deviceConfigs; std::vector<DeviceConfig> deviceConfigs;
std::vector<uint8_t *> irks; std::vector<uint8_t *> irks;
std::vector<BleFingerprint *> fingerprints; std::vector<BleFingerprint *> fingerprints;

View File

@ -47,7 +47,7 @@ extern TCallbackFingerprint onCountDel;
extern String include, exclude, query, knownMacs, knownIrks, countIds; extern String include, exclude, query, knownMacs, knownIrks, countIds;
extern float skipDistance, maxDistance, absorption, countEnter, countExit; extern float skipDistance, maxDistance, absorption, countEnter, countExit;
extern int8_t rxRefRssi, rxAdjRssi, txRefRssi; extern int8_t rxRefRssi, rxAdjRssi, txRefRssi;
extern int forgetMs, skipMs, countMs; extern int forgetMs, skipMs, countMs, requeryMs;
extern std::vector<DeviceConfig> deviceConfigs; extern std::vector<DeviceConfig> deviceConfigs;
extern std::vector<uint8_t *> irks; extern std::vector<uint8_t *> irks;
extern std::vector<BleFingerprint *> fingerprints; extern std::vector<BleFingerprint *> fingerprints;

View File

@ -0,0 +1,17 @@
#pragma once
#include <NimBLEAddress.h>
class QueryReport {
public:
QueryReport(const String& id, const String& payload) : id(id), payload(payload) {}
String getId() const { return id; }
String getPayload() const { return payload; }
void setId(const String& id) { this->id = id; }
void setPayload(const String& payload) { this->payload = payload; }
private:
String id;
String payload;
};

View File

@ -1,19 +1,19 @@
#ifndef _RSSI_ #ifndef _RSSI_
#define _RSSI_ #define _RSSI_
#define CLOSE_RSSI (-40) #define CLOSE_RSSI int8_t(-40)
#define LEFT_RSSI (-50) #define LEFT_RSSI int8_t(-50)
#define DEFAULT_TX (-6) #define DEFAULT_TX int8_t(-6)
#define APPLE_TX 0 #define APPLE_TX int8_t(0)
#define RM_ASST_TX 0 #define RM_ASST_TX int8_t(0)
#define TILE_TX (-4) #define TILE_TX int8_t(-4)
#define EXPOSURE_TX (-12) #define EXPOSURE_TX int8_t(-12)
#define ITAG_TX (-10) #define ITAG_TX int8_t(-10)
#define NUT_TX (-12) #define NUT_TX int8_t(-12)
#define FLORA_TX (-10) #define FLORA_TX int8_t(-10)
#define EDDYSTONE_ADD_1M (-41) #define EDDYSTONE_ADD_1M int8_t(-41)
#endif #endif

View File

@ -10,7 +10,7 @@
#define Stdprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); const std::string r = s; free(s); r; }) #define Stdprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); const std::string r = s; free(s); r; })
#define SMacf(f) ( \ #define SMacf(f) ( \
{ \ { \
auto nativeAddress = (f).getNative(); \ const auto nativeAddress = (f).getNative(); \
Sprintf("%02x%02x%02x%02x%02x%02x", nativeAddress[5], nativeAddress[4], nativeAddress[3], nativeAddress[2], nativeAddress[1], nativeAddress[0]); \ Sprintf("%02x%02x%02x%02x%02x%02x", nativeAddress[5], nativeAddress[4], nativeAddress[3], nativeAddress[2], nativeAddress[1], nativeAddress[0]); \
}) })

View File

@ -0,0 +1,137 @@
#include "MiFloraHandler.h"
namespace MiFloraHandler {
std::vector<std::string> addresses;
bool readSensorData(BLERemoteService* floraService, DynamicJsonDocument* doc) {
BLERemoteCharacteristic* floraCharacteristic = nullptr;
// get the main device data characteristic
floraCharacteristic = floraService->getCharacteristic(uuid_sensor_data);
if (floraCharacteristic == nullptr) {
Serial.println("-- Can't read characteristics");
return false;
}
// read characteristic value
NimBLEAttValue value;
value = floraCharacteristic->readValue();
if (value.size() == 0) {
Serial.println("Reading Value failed");
return false;
}
const char* val = value.c_str();
float temperature = (val[0] + val[1] * 256) / ((float)10.0);
uint8_t moisture = val[7];
uint32_t brightness = val[3] + val[4] * 256;
float conductivity = val[8] + val[9] * 256;
(*doc)[F("temperature")] = temperature;
(*doc)[F("moisture")] = moisture;
(*doc)[F("light")] = brightness;
(*doc)[F("conductivity")] = conductivity;
floraService->deleteCharacteristics();
return true;
}
bool readBatteryData(BLERemoteService* floraService, DynamicJsonDocument* doc) {
BLERemoteCharacteristic* floraCharacteristic = nullptr;
floraCharacteristic = floraService->getCharacteristic(uuid_version_battery);
if (floraCharacteristic == nullptr) {
Serial.println("-- Can't read characteristics");
return false;
}
NimBLEAttValue val;
val = floraCharacteristic->readValue();
if (val.size() == 0) {
Serial.println("Reading Value failed");
return false;
}
int8_t battery = val.c_str()[0];
(*doc)[F("battery")] = battery;
floraService->deleteCharacteristics();
return true;
}
bool forceFloraServiceDataMode(BLERemoteService* floraService) { // Setting the mi flora to data reading mode
BLERemoteCharacteristic* floraCharacteristic;
// get device mode characteristic, needs to be changed to read data
// Serial.println("- Force device in data mode");
floraCharacteristic = nullptr;
floraCharacteristic = floraService->getCharacteristic(uuid_write_mode);
if (floraCharacteristic == nullptr) {
// Serial.println("-- Failed, skipping device");
return false;
}
uint8_t buf[2] = {0xA0, 0x1F};
floraCharacteristic->writeValue(buf, 2, true);
delay(500);
floraService->deleteCharacteristics();
return true;
}
void fillDeviceData(DynamicJsonDocument* doc, BleFingerprint* f) {
(*doc)[F("id")] = f->getId();
(*doc)[F("mac")] = f->getMac();
(*doc)[F("rssi")] = f->getRssi();
}
bool getFloraData(DynamicJsonDocument* doc, BLERemoteService* floraService, BleFingerprint* f) {
// Force miFlora to data mode
fillDeviceData(doc, f);
if (!MiFloraHandler::readBatteryData(floraService, doc))
Serial.println("Failed reading battery data");
if (MiFloraHandler::forceFloraServiceDataMode(floraService)) {
} else {
Serial.println("Failed to force data reading mode");
}
if (!MiFloraHandler::readSensorData(floraService, doc))
Serial.println("Failed reading sensor data");
return true;
}
static DynamicJsonDocument document(1024);
bool requestData(NimBLEClient* pClient, BleFingerprint* fingerprint) // Getting mi flora data
{
NimBLERemoteService* floraService = pClient->getService(serviceUUID);
if (floraService == nullptr) {
Serial.println("Getting MiFlora service failed");
return false;
}
document.clear();
// Retriving the actual data
if (!getFloraData(&document, floraService, fingerprint)) // Getting flora data
return false;
String buf = String();
serializeJson(document, buf);
// Sending buffer over mqtt
fingerprint->setReport(QueryReport{"miflora", buf});
return true;
}
} // namespace MiFloraHandler

View File

@ -0,0 +1,18 @@
#pragma once
#include <ArduinoJson.h>
#include <AsyncMqttClient.h>
#include <AsyncWiFiSettings.h>
#include <BleFingerprint.h>
#include <NimBLEClient.h>
#include <NimBLEDevice.h>
#include <SoftFilters.h>
namespace MiFloraHandler
{
static BLEUUID serviceUUID(0x00001204, 0x0000, 0x1000, 0x800000805f9b34fb);
static BLEUUID uuid_version_battery(0x00001a02, 0x0000, 0x1000, 0x800000805f9b34fb);
static BLEUUID uuid_sensor_data(0x00001a01, 0x0000, 0x1000, 0x800000805f9b34fb);
static BLEUUID uuid_write_mode(0x00001a00, 0x0000, 0x1000, 0x800000805f9b34fb);
bool requestData(NimBLEClient* pClient, BleFingerprint* fingerprint);
} // namespace MiFloraHandler

View File

@ -0,0 +1,24 @@
#include "NameModelHandler.h"
#include "util.h"
namespace NameModelHandler {
bool requestData(NimBLEClient* pClient, BleFingerprint* f) {
std::string sMdl = pClient->getValue(deviceInformationService, modelChar);
std::string sName = pClient->getValue(genericAccessService, nameChar);
if (!sName.empty() && sMdl.find(sName) == std::string::npos && sName != "Apple Watch") {
if (f->setId(String("name:") + kebabify(sName).c_str(), ID_TYPE_QUERY_NAME, String(sName.c_str()))) {
Serial.printf("\u001b[38;5;104m%u Name | %s | %-58s%ddBm %s\u001b[0m\r\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getRssi(), sName.c_str());
}
return true;
}
if (!sMdl.empty()) {
if (f->setId(String("apple:") + kebabify(sMdl).c_str(), ID_TYPE_QUERY_MODEL, String(sMdl.c_str()))) {
Serial.printf("\u001b[38;5;136m%u Model | %s | %-58s%ddBm %s\u001b[0m\r\n", xPortGetCoreID(), f->getMac().c_str(), f->getId().c_str(), f->getRssi(), sMdl.c_str());
}
return true;
}
return false;
}
} // namespace NameModelHandler

View File

@ -0,0 +1,14 @@
#pragma once
#include<NimBLEClient.h>
#include <NimBLEDevice.h>
#include <ArduinoJson.h>
#include <sstream>
#include <SoftFilters.h>
#include <AsyncMqttClient.h>
#include <BleFingerprint.h>
#include <AsyncWiFiSettings.h>
namespace NameModelHandler {
bool requestData(NimBLEClient* pClient, BleFingerprint* fingerprint);
}

View File

@ -27,8 +27,8 @@ build_flags =
-Wformat-truncation -Wformat-truncation
-D ARDUINOJSON_ENABLE_NAN=0 -D ARDUINOJSON_ENABLE_NAN=0
-D CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096 -D CONFIG_BT_NIMBLE_TASK_STACK_SIZE=4096
-D SCAN_TASK_STACK_SIZE=2096 -D SCAN_TASK_STACK_SIZE=2562
-D ARDUINO_LOOP_STACK_SIZE=6144 -D ARDUINO_LOOP_STACK_SIZE=3584
-D MQTT_MIN_FREE_MEMORY=12192 -D MQTT_MIN_FREE_MEMORY=12192
-D CONFIG_ASYNC_TCP_USE_WDT=0 -D CONFIG_ASYNC_TCP_USE_WDT=0
-D CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=20 -D CONFIG_BT_NIMBLE_MSYS1_BLOCK_COUNT=20
@ -38,13 +38,13 @@ build_flags =
; -D CONFIG_NIMBLE_CPP_LOG_LEVEL=4 ; -D CONFIG_NIMBLE_CPP_LOG_LEVEL=4
; -D CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT=1 ; -D CONFIG_NIMBLE_CPP_ENABLE_GAP_EVENT_CODE_TEXT=1
; -D CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT=1 ; -D CONFIG_NIMBLE_CPP_ENABLE_RETURN_CODE_TEXT=1
; -D CHECK_FOR_UPDATES_INTERVAL=1 ; -D CHECK_FOR_UPDATES_INTERVAL=1
build_unflags = build_unflags =
framework = arduino framework = arduino
lib_deps = lib_deps =
AsyncTCP = https://github.com/pbolduc/AsyncTCP.git@^1.2.0 AsyncTCP = https://github.com/pbolduc/AsyncTCP.git@^1.2.0
https://github.com/ESPresense/ESPAsyncWebServer.git https://github.com/ESPresense/ESPAsyncWebServer.git
https://github.com/ESPresense/AsyncWiFiSettings.git#1.0.6 https://github.com/ESPresense/AsyncWiFiSettings.git@^1.0.7
https://github.com/ESPresense/SoftFilters.git https://github.com/ESPresense/SoftFilters.git
https://github.com/ESPresense/NimBLE-Arduino.git https://github.com/ESPresense/NimBLE-Arduino.git
marvinroger/AsyncMqttClient@^0.9.0 marvinroger/AsyncMqttClient@^0.9.0
@ -120,8 +120,6 @@ lib_deps =
paulstoffregen/OneWire@^2.3.7 paulstoffregen/OneWire@^2.3.7
milesburton/DallasTemperature@^3.11.0 milesburton/DallasTemperature@^3.11.0
[env:esp32] [env:esp32]
extends = esp32 extends = esp32
lib_deps = lib_deps =

View File

@ -53,7 +53,7 @@ void Loop() {
void Added(BleFingerprint *f) { void Added(BleFingerprint *f) {
if (f->getIgnore()) return; if (f->getIgnore()) return;
Serial.printf("%u New %s | %s | %-58s%ddBm %s\r\n", xPortGetCoreID(), f->getRmAsst() ? "R" : (f->getAllowQuery() ? "Q" : " "), f->getMac().c_str(), f->getId().c_str(), f->getRssi(), f->getDiscriminator().c_str()); Serial.printf("%u New %s | %s | %-58s%ddBm %s\r\n", xPortGetCoreID(), f->getAllowQuery() ? "Q" : " ", f->getMac().c_str(), f->getId().c_str(), f->getRssi(), f->getDiscriminator().c_str());
} }
void Removed(BleFingerprint *f) { void Removed(BleFingerprint *f) {

View File

@ -27,11 +27,11 @@ void serializeState(JsonObject &root) {
} }
void serializeConfigs(JsonObject &root) { void serializeConfigs(JsonObject &root) {
JsonArray devices = root.createNestedArray("configs"); JsonArray configs = root.createNestedArray("configs");
auto f = BleFingerprintCollection::deviceConfigs; auto deviceConfigs = BleFingerprintCollection::deviceConfigs;
for (auto it = f.begin(); it != f.end(); ++it) { for (auto it = deviceConfigs.begin(); it != deviceConfigs.end(); ++it) {
JsonObject node = devices.createNestedObject(); const JsonObject& node = configs.createNestedObject();
node["id"] = it->id; node["id"] = it->id;
node["alias"] = it->alias; node["alias"] = it->alias;
node["name"] = it->name; node["name"] = it->name;

View File

@ -18,7 +18,7 @@ int I2C_Bus_2_SDA = 0;
int I2C_Bus_2_SCL = 0; int I2C_Bus_2_SCL = 0;
void ConnectToWifi() { void ConnectToWifi() {
AsyncWiFiSettings.heading("I2C Settings <a href='https://espresense.com/configuration/settings#i2c-settings' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#i2c-settings' target='_blank'>I2C Settings</a>", false);
AsyncWiFiSettings.html("h4", "Bus 1:"); AsyncWiFiSettings.html("h4", "Bus 1:");

View File

@ -34,7 +34,7 @@ void ConnectToWifi() {
std::vector<String> ledTypes = {"PWM", "PWM Inverted", "Addressable GRB", "Addressable GRBW", "Addressable RGB", "Addressable RGBW"}; std::vector<String> ledTypes = {"PWM", "PWM Inverted", "Addressable GRB", "Addressable GRBW", "Addressable RGB", "Addressable RGBW"};
std::vector<String> ledControlTypes = {"MQTT", "Status", "Motion", "Count"}; std::vector<String> ledControlTypes = {"MQTT", "Status", "Motion", "Count"};
AsyncWiFiSettings.heading("LEDs <a href='https://espresense.com/configuration/settings#leds' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#leds' target='_blank'>LEDs</a>", false);
AsyncWiFiSettings.html("h4", "LED 1:"); AsyncWiFiSettings.html("h4", "LED 1:");

View File

@ -45,9 +45,9 @@ void ConnectToWifi() {
} }
void SerialReport() { void SerialReport() {
Serial.print("Switch One Sensor: "); Serial.print("Switch One: ");
Serial.println(switch_1Pin >= 0 ? "enabled" : "disabled"); Serial.println(switch_1Pin >= 0 ? "enabled" : "disabled");
Serial.print("Switch Two Sensor: "); Serial.print("Switch Two: ");
Serial.println(switch_2Pin >= 0 ? "enabled" : "disabled"); Serial.println(switch_2Pin >= 0 ? "enabled" : "disabled");
} }
@ -118,4 +118,4 @@ bool SendOnline() {
online = true; online = true;
return true; return true;
} }
} // namespace Switch } // namespace Switch

View File

@ -102,10 +102,12 @@ void firmwareUpdate() {
case HTTP_UPDATE_FAILED: case HTTP_UPDATE_FAILED:
Serial.printf("Http Update Failed (Error=%d): %s\r\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); Serial.printf("Http Update Failed (Error=%d): %s\r\n", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str());
break; break;
case HTTP_UPDATE_NO_UPDATES: case HTTP_UPDATE_NO_UPDATES:
Serial.printf("No Update!\r\n"); Serial.printf("No Update!\r\n");
break; break;
case HTTP_UPDATE_OK:
Serial.printf("Update OK!\r\n");
break;
} }
} }
} }

View File

@ -1,6 +1,13 @@
#define VAR_DECLS #define VAR_DECLS
#include "main.h" #include "main.h"
#include "esp_heap_caps.h"
void heapCapsAllocFailedHook(size_t requestedSize, uint32_t caps, const char *functionName)
{
printf("%s was called but failed to allocate %d bytes with 0x%X capabilities. \n",functionName, requestedSize, caps);
}
bool sendTelemetry(unsigned int totalSeen, unsigned int totalFpSeen, int unsigned totalFpQueried, int unsigned totalFpReported, unsigned int count) { bool sendTelemetry(unsigned int totalSeen, unsigned int totalFpSeen, int unsigned totalFpQueried, int unsigned totalFpReported, unsigned int count) {
if (!online) { if (!online) {
if ( if (
@ -107,9 +114,9 @@ bool sendTelemetry(unsigned int totalSeen, unsigned int totalFpSeen, int unsigne
auto maxHeap = ESP.getMaxAllocHeap(); auto maxHeap = ESP.getMaxAllocHeap();
auto freeHeap = ESP.getFreeHeap(); auto freeHeap = ESP.getFreeHeap();
doc["freeHeap"] = freeHeap; doc["freeHeap"] = freeHeap;
doc["maxAllocHeap"] = maxHeap; doc["maxHeap"] = maxHeap;
doc["memFrag"] = 100 - (maxHeap * 100.0 / freeHeap); doc["scanStack"] = uxTaskGetStackHighWaterMark(scanTaskHandle);
doc["scanHighWater"] = uxTaskGetStackHighWaterMark(scanTaskHandle); doc["loopStack"] = uxTaskGetStackHighWaterMark(nullptr);
serializeJson(doc, buffer); serializeJson(doc, buffer);
if (pub(teleTopic.c_str(), 0, false, buffer)) return true; if (pub(teleTopic.c_str(), 0, false, buffer)) return true;
@ -133,7 +140,7 @@ void setupNetwork() {
std::vector<String> ethernetTypes = {"None", "WT32-ETH01", "ESP32-POE", "WESP32", "QuinLED-ESP32", "TwilightLord-ESP32", "ESP32Deux", "KIT-VE", "LilyGO-T-ETH-POE", "GL-inet GL-S10 v2.1 Ethernet"}; std::vector<String> ethernetTypes = {"None", "WT32-ETH01", "ESP32-POE", "WESP32", "QuinLED-ESP32", "TwilightLord-ESP32", "ESP32Deux", "KIT-VE", "LilyGO-T-ETH-POE", "GL-inet GL-S10 v2.1 Ethernet"};
ethernetType = AsyncWiFiSettings.dropdown("eth", ethernetTypes, 0, "Ethernet Type"); ethernetType = AsyncWiFiSettings.dropdown("eth", ethernetTypes, 0, "Ethernet Type");
AsyncWiFiSettings.heading("MQTT <a href='https://espresense.com/configuration/settings#mqtt' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#mqtt' target='_blank'>MQTT</a>", false);
mqttHost = AsyncWiFiSettings.string("mqtt_host", DEFAULT_MQTT_HOST, "Server"); mqttHost = AsyncWiFiSettings.string("mqtt_host", DEFAULT_MQTT_HOST, "Server");
mqttPort = AsyncWiFiSettings.integer("mqtt_port", DEFAULT_MQTT_PORT, "Port"); mqttPort = AsyncWiFiSettings.integer("mqtt_port", DEFAULT_MQTT_PORT, "Port");
mqttUser = AsyncWiFiSettings.pstring("mqtt_user", DEFAULT_MQTT_USER, "Username"); mqttUser = AsyncWiFiSettings.pstring("mqtt_user", DEFAULT_MQTT_USER, "Username");
@ -144,28 +151,31 @@ void setupNetwork() {
publishRooms = AsyncWiFiSettings.checkbox("pub_rooms", true, "Send to rooms topic"); publishRooms = AsyncWiFiSettings.checkbox("pub_rooms", true, "Send to rooms topic");
publishDevices = AsyncWiFiSettings.checkbox("pub_devices", true, "Send to devices topic"); publishDevices = AsyncWiFiSettings.checkbox("pub_devices", true, "Send to devices topic");
AsyncWiFiSettings.heading("Updating <a href='https://espresense.com/configuration/settings#updating' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#updating' target='_blank'>Updating</a>", false);
Updater::ConnectToWifi(); Updater::ConnectToWifi();
AsyncWiFiSettings.heading("Scanning <a href='https://espresense.com/configuration/settings#scanning' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#scanning' target='_blank'>Scanning</a>", false);
BleFingerprintCollection::knownMacs = AsyncWiFiSettings.string("known_macs", "", "Known BLE mac addresses (no colons, space seperated)"); BleFingerprintCollection::knownMacs = AsyncWiFiSettings.string("known_macs", "", "Known BLE mac addresses (no colons, space seperated)");
BleFingerprintCollection::knownIrks = AsyncWiFiSettings.string("known_irks", "", "Known BLE identity resolving keys, should be 32 hex chars space seperated"); BleFingerprintCollection::knownIrks = AsyncWiFiSettings.string("known_irks", "", "Known BLE identity resolving keys, should be 32 hex chars space seperated");
BleFingerprintCollection::query = AsyncWiFiSettings.string("query", DEFAULT_QUERY, "Query device ids for characteristics (eg. apple:1005:9-26)");
AsyncWiFiSettings.heading("Counting <a href='https://espresense.com/configuration/settings#counting' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#querying' target='_blank'>Querying</a>", false);
BleFingerprintCollection::query = AsyncWiFiSettings.string("query", DEFAULT_QUERY, "Query device ids for characteristics (eg. flora:)");
BleFingerprintCollection::requeryMs = AsyncWiFiSettings.integer("requery_ms", 30, 3600, 300, "Requery interval in seconds") * 1000;
AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#counting' target='_blank'>Counting</a>", false);
BleFingerprintCollection::countIds = AsyncWiFiSettings.string("count_ids", "", "Include id prefixes (space seperated)"); BleFingerprintCollection::countIds = AsyncWiFiSettings.string("count_ids", "", "Include id prefixes (space seperated)");
BleFingerprintCollection::countEnter = AsyncWiFiSettings.floating("count_enter", 0, 100, 2, "Start counting devices less than distance (in meters)"); BleFingerprintCollection::countEnter = AsyncWiFiSettings.floating("count_enter", 0, 100, 2, "Start counting devices less than distance (in meters)");
BleFingerprintCollection::countExit = AsyncWiFiSettings.floating("count_exit", 0, 100, 4, "Stop counting devices greater than distance (in meters)"); BleFingerprintCollection::countExit = AsyncWiFiSettings.floating("count_exit", 0, 100, 4, "Stop counting devices greater than distance (in meters)");
BleFingerprintCollection::countMs = AsyncWiFiSettings.integer("count_ms", 0, 3000000, 30000, "Include devices with age less than (in ms)"); BleFingerprintCollection::countMs = AsyncWiFiSettings.integer("count_ms", 0, 3000000, 30000, "Include devices with age less than (in ms)");
AsyncWiFiSettings.heading("Filtering <a href='https://espresense.com/configuration/settings#filtering' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#filtering' target='_blank'>Filtering</a>", false);
BleFingerprintCollection::include = AsyncWiFiSettings.string("include", DEFAULT_INCLUDE, "Include only sending these ids to mqtt (eg. apple:iphone10-6 apple:iphone13-2)"); BleFingerprintCollection::include = AsyncWiFiSettings.string("include", DEFAULT_INCLUDE, "Include only sending these ids to mqtt (eg. apple:iphone10-6 apple:iphone13-2)");
BleFingerprintCollection::exclude = AsyncWiFiSettings.string("exclude", DEFAULT_EXCLUDE, "Exclude sending these ids to mqtt (eg. exp:20 apple:iphone10-6)"); BleFingerprintCollection::exclude = AsyncWiFiSettings.string("exclude", DEFAULT_EXCLUDE, "Exclude sending these ids to mqtt (eg. exp:20 apple:iphone10-6)");
BleFingerprintCollection::maxDistance = AsyncWiFiSettings.floating("max_dist", 0, 100, DEFAULT_MAX_DISTANCE, "Maximum distance to report (in meters)"); BleFingerprintCollection::maxDistance = AsyncWiFiSettings.floating("max_dist", 0, 100, DEFAULT_MAX_DISTANCE, "Maximum distance to report (in meters)");
BleFingerprintCollection::skipDistance = AsyncWiFiSettings.floating("skip_dist", 0, 10, DEFAULT_SKIP_DISTANCE, "Report early if beacon has moved more than this distance (in meters)"); BleFingerprintCollection::skipDistance = AsyncWiFiSettings.floating("skip_dist", 0, 10, DEFAULT_SKIP_DISTANCE, "Report early if beacon has moved more than this distance (in meters)");
BleFingerprintCollection::skipMs = AsyncWiFiSettings.integer("skip_ms", 0, 3000000, DEFAULT_SKIP_MS, "Skip reporting if message age is less that this (in milliseconds)"); BleFingerprintCollection::skipMs = AsyncWiFiSettings.integer("skip_ms", 0, 3000000, DEFAULT_SKIP_MS, "Skip reporting if message age is less that this (in milliseconds)");
AsyncWiFiSettings.heading("Calibration <a href='https://espresense.com/configuration/settings#calibration' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#calibration' target='_blank'>Calibration</a>", false);
BleFingerprintCollection::rxRefRssi = AsyncWiFiSettings.integer("ref_rssi", -100, 100, DEFAULT_RX_REF_RSSI, "Rssi expected from a 0dBm transmitter at 1 meter (NOT used for iBeacons or Eddystone)"); BleFingerprintCollection::rxRefRssi = AsyncWiFiSettings.integer("ref_rssi", -100, 100, DEFAULT_RX_REF_RSSI, "Rssi expected from a 0dBm transmitter at 1 meter (NOT used for iBeacons or Eddystone)");
BleFingerprintCollection::rxAdjRssi = AsyncWiFiSettings.integer("rx_adj_rssi", -100, 100, 0, "Rssi adjustment for receiver (use only if you know this device has a weak antenna)"); BleFingerprintCollection::rxAdjRssi = AsyncWiFiSettings.integer("rx_adj_rssi", -100, 100, 0, "Rssi adjustment for receiver (use only if you know this device has a weak antenna)");
BleFingerprintCollection::absorption = AsyncWiFiSettings.floating("absorption", -100, 100, DEFAULT_ABSORPTION, "Factor used to account for absorption, reflection, or diffraction"); BleFingerprintCollection::absorption = AsyncWiFiSettings.floating("absorption", -100, 100, DEFAULT_ABSORPTION, "Factor used to account for absorption, reflection, or diffraction");
@ -174,7 +184,7 @@ void setupNetwork() {
GUI::ConnectToWifi(); GUI::ConnectToWifi();
AsyncWiFiSettings.heading("GPIO Sensors <a href='https://espresense.com/configuration/settings#gpio-sensors' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#gpio-sensors' target='_blank'>GPIO Sensors</a>", false);
BleFingerprintCollection::ConnectToWifi(); BleFingerprintCollection::ConnectToWifi();
Motion::ConnectToWifi(); Motion::ConnectToWifi();
@ -185,7 +195,7 @@ void setupNetwork() {
DHT::ConnectToWifi(); DHT::ConnectToWifi();
I2C::ConnectToWifi(); I2C::ConnectToWifi();
AsyncWiFiSettings.heading("I2C Sensors <a href='https://espresense.com/configuration/settings#i2c-sensors' target='_blank'></a>", false); AsyncWiFiSettings.heading("<a href='https://espresense.com/configuration/settings#i2c-sensors' target='_blank'>I2C Sensors</a>", false);
AHTX0::ConnectToWifi(); AHTX0::ConnectToWifi();
BH1750::ConnectToWifi(); BH1750::ConnectToWifi();
@ -393,6 +403,13 @@ void connectToMqtt() {
mqttClient.connect(); mqttClient.connect();
} }
bool reportBuffer(BleFingerprint *f) {
if (!mqttClient.connected()) return false;
auto report = f->getReport();
String topic = Sprintf(CHANNEL "/devices/%s/%s/%s", f->getId().c_str(), id.c_str(), report.getId().c_str());
return mqttClient.publish(topic.c_str(), 0, false, report.getPayload().c_str());
}
bool reportDevice(BleFingerprint *f) { bool reportDevice(BleFingerprint *f) {
doc.clear(); doc.clear();
JsonObject obj = doc.to<JsonObject>(); JsonObject obj = doc.to<JsonObject>();
@ -404,9 +421,7 @@ bool reportDevice(BleFingerprint *f) {
bool p1 = false, p2 = false; bool p1 = false, p2 = false;
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
if (!mqttClient.connected()) if (!mqttClient.connected()) return false;
return false;
if (!p1 && (!publishRooms || mqttClient.publish(roomsTopic.c_str(), 0, false, buffer))) if (!p1 && (!publishRooms || mqttClient.publish(roomsTopic.c_str(), 0, false, buffer)))
p1 = true; p1 = true;
@ -417,6 +432,7 @@ bool reportDevice(BleFingerprint *f) {
return true; return true;
delay(20); delay(20);
} }
teleFails++; teleFails++;
return false; return false;
} }
@ -456,6 +472,11 @@ void reportLoop() {
totalSeen += seen; totalSeen += seen;
totalFpSeen++; totalFpSeen++;
} }
if (f->hasReport()) {
if (reportBuffer(f))
f->clearReport();
}
if (reportDevice(f)) { if (reportDevice(f)) {
totalFpReported++; totalFpReported++;
reported++; reported++;
@ -515,6 +536,7 @@ void setup() {
esp_log_level_set("*", ESP_LOG_ERROR); esp_log_level_set("*", ESP_LOG_ERROR);
#endif #endif
Serial.printf("Pre-Setup Free Mem: %d\r\n", ESP.getFreeHeap()); Serial.printf("Pre-Setup Free Mem: %d\r\n", ESP.getFreeHeap());
heap_caps_register_failed_alloc_callback(heapCapsAllocFailedHook);
#if M5STICK #if M5STICK
AXP192::Setup(); AXP192::Setup();