#include "BleFingerprintCollection.h" #include "esphome/core/application.h" #include "esphome/core/defines.h" #include "esphome/core/util.h" #include "esphome/core/log.h" #include "esphome/components/network/util.h" #include "esp_system.h" #include namespace BleFingerprintCollection { // Public (externed) std::string include{}, exclude{}, query{}, knownMacs{}, knownIrks{}, countIds{}; float skipDistance = 0.0f, maxDistance = 0.0f, absorption = 3.5f, countEnter = 2, countExit = 4; int8_t rxRefRssi = -65, rxAdjRssi = 0, txRefRssi = -59; int forgetMs = 0, skipMs = 0, countMs = 10000, requeryMs = 300000; std::vector deviceConfigs; std::vector irks; std::vector fingerprints; TCallbackBool onSeen = nullptr; TCallbackFingerprint onAdd = nullptr; TCallbackFingerprint onDel = nullptr; TCallbackFingerprint onClose = nullptr; TCallbackFingerprint onLeft = nullptr; TCallbackFingerprint onCountAdd = nullptr; TCallbackFingerprint onCountDel = nullptr; // Private const TickType_t MAX_WAIT = portTICK_PERIOD_MS * 100; unsigned long lastCleanup = 0; SemaphoreHandle_t fingerprintMutex; SemaphoreHandle_t deviceConfigMutex; void Setup() { fingerprintMutex = xSemaphoreCreateMutex(); deviceConfigMutex = xSemaphoreCreateMutex(); } void Count(BleFingerprint *f, bool counting) { if (counting) { if (onCountAdd) onCountAdd(f); } else { if (onCountDel) onCountDel(f); } } void Close(BleFingerprint *f, bool close) { if (close) { if (onClose) onClose(f); } else { if (onLeft) onLeft(f); } } void Seen(BLEAdvertisedDevice *advertisedDevice) { BLEAdvertisedDevice copy = *advertisedDevice; if (onSeen) onSeen(true); BleFingerprint *f = GetFingerprint(©); if (f->seen(©) && onAdd) onAdd(f); if (onSeen) onSeen(false); } bool addOrReplace(DeviceConfig config) { if (xSemaphoreTake(deviceConfigMutex, MAX_WAIT) != pdTRUE) ESP_LOGE(TAG, "Couldn't take deviceConfigMutex in addOrReplace!"); for (auto &it : deviceConfigs) { if (it.id == config.id) { it = config; xSemaphoreGive(deviceConfigMutex); return false; } } deviceConfigs.push_back(config); xSemaphoreGive(deviceConfigMutex); return true; } bool Config(std::string &id, std::string &json) { DynamicJsonDocument doc(1024); deserializeJson(doc, json); DeviceConfig config = {}; config.id = id; if (doc.containsKey("id")) { auto alias = doc["id"].as(); if (alias != id) config.alias = alias; } if (doc.containsKey("rssi@1m")) config.calRssi = doc["rssi@1m"].as(); if (doc.containsKey("name")) config.name = doc["name"].as(); auto isNew = addOrReplace(config); if (isNew) { auto p = id.find("irk:"); if (p == 0) { auto irk_hex = id.substr(4); auto *irk = new uint8_t[16]; if (!hextostr(irk_hex, irk, 16)) return false; irks.push_back(irk); } } for (auto &it : fingerprints) { auto it_id = it->getId(); if (it_id == id || it_id == config.alias) { it->setName(config.name); it->setId(config.alias.length() > 0 ? config.alias : config.id, ID_TYPE_ALIAS, config.name); if (config.calRssi != NO_RSSI) it->set1mRssi(config.calRssi); } else it->fingerprintAddress(); } return true; } void ConnectToWifi() { std::istringstream iss(knownIrks.c_str()); std::string irk_hex; while (iss >> irk_hex) { auto *irk = new uint8_t[16]; if (!hextostr(irk_hex.c_str(), irk, 16)) continue; irks.push_back(irk); } } bool Command(std::string &command, std::string &pay) { if (command == "skip_ms") { BleFingerprintCollection::skipMs = std::stoi(pay); spurt("/skip_ms", pay); } else if (command == "skip_distance") { BleFingerprintCollection::skipDistance = std::stof(pay); spurt("/skip_dist", pay); } else if (command == "max_distance") { maxDistance = std::stof(pay); spurt("/max_dist", pay); } else if (command == "absorption") { absorption = std::stof(pay); spurt("/absorption", pay); } else if (command == "rx_adj_rssi") { rxAdjRssi = (int8_t)std::stoi(pay); spurt("/rx_adj_rssi", pay); } else if (command == "ref_rssi") { rxRefRssi = (int8_t)std::stoi(pay); spurt("/ref_rssi", pay); } else if (command == "tx_ref_rssi") { txRefRssi = (int8_t)std::stoi(pay); spurt("/tx_ref_rssi", pay); } else if (command == "query") { query = pay; spurt("/query", pay); } else if (command == "include") { include = pay; spurt("/include", pay); } else if (command == "exclude") { exclude = pay; spurt("/exclude", pay); } else if (command == "known_macs") { knownMacs = pay; spurt("/known_macs", pay); } else if (command == "known_irks") { knownIrks = pay; spurt("/known_irks", pay); } else if (command == "count_ids") { countIds = pay; spurt("/count_ids", pay); } else return false; return true; } void CleanupOldFingerprints() { auto now = millis(); if (now - lastCleanup < 5000) return; lastCleanup = now; auto it = fingerprints.begin(); bool any = false; while (it != fingerprints.end()) { auto age = (*it)->getMsSinceLastSeen(); if (age > forgetMs) { if (onDel) onDel((*it)); delete *it; it = fingerprints.erase(it); } else { any = true; ++it; } } if (!any) { auto uptime = (unsigned long)(esp_timer_get_time() / 1000000ULL); if (uptime > ALLOW_BLE_CONTROLLER_RESTART_AFTER_SECS) { ESP_LOGE(TAG, "Bluetooth controller seems stuck, restarting"); esp_restart(); } } } BleFingerprint *getFingerprintInternal(BLEAdvertisedDevice *advertisedDevice) { auto mac = advertisedDevice->getAddress(); auto it = std::find_if(fingerprints.rbegin(), fingerprints.rend(), [mac](BleFingerprint *f) { return f->getAddress() == mac; }); if (it != fingerprints.rend()) return *it; auto created = new BleFingerprint(advertisedDevice, ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF); auto it2 = std::find_if(fingerprints.begin(), fingerprints.end(), [created](BleFingerprint *f) { return f->getId() == created->getId(); }); if (it2 != fingerprints.end()) { auto found = *it2; // Serial.printf("Detected mac switch for fingerprint id %s\r\n", found->getId().c_str()); created->setInitial(*found); if (found->getIdType() > ID_TYPE_UNIQUE) found->expire(); } fingerprints.push_back(created); return created; } BleFingerprint *GetFingerprint(BLEAdvertisedDevice *advertisedDevice) { if (xSemaphoreTake(fingerprintMutex, MAX_WAIT) != pdTRUE) ESP_LOGE(TAG, "Couldn't take semaphore!"); auto f = getFingerprintInternal(advertisedDevice); xSemaphoreGive(fingerprintMutex); return f; } const std::vector GetCopy() { if (xSemaphoreTake(fingerprintMutex, MAX_WAIT) != pdTRUE) ESP_LOGE(TAG, "Couldn't take fingerprintMutex!"); CleanupOldFingerprints(); std::vector copy(fingerprints); xSemaphoreGive(fingerprintMutex); return std::move(copy); } bool FindDeviceConfig(const std::string &id, DeviceConfig &config) { if (xSemaphoreTake(deviceConfigMutex, MAX_WAIT) == pdTRUE) { auto it = std::find_if(deviceConfigs.begin(), deviceConfigs.end(), [id](DeviceConfig dc) { return dc.id == id; }); if (it != deviceConfigs.end()) { config = *it; xSemaphoreGive(deviceConfigMutex); return true; } xSemaphoreGive(deviceConfigMutex); return false; } ESP_LOGE(TAG, "Couldn't take deviceConfigMutex!"); return false; } } // namespace BleFingerprintCollection