Compare commits
	
		
			25 Commits
		
	
	
		
			c8d2c86908
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 4dbb575a15 | |||
| d021d7c29d | |||
| ae7e408b4e | |||
| 7b3b746a1b | |||
| 37989e25ae | |||
| 922dbf98b9 | |||
| 34089f1add | |||
| 4c710f7a0a | |||
| b766c0ce04 | |||
| 32fe442cce | |||
| 61f58a5963 | |||
| fbb0c27ce6 | |||
| 7d897ae764 | |||
| fee9d3d3ab | |||
| 38b0dc4092 | |||
| e47b9a9df9 | |||
| 0634843815 | |||
| 928ee7b152 | |||
| 3da62ad3c9 | |||
| 4c4e6b3862 | |||
| e464f33da3 | |||
| f0a839667d | |||
| bc6f6402eb | |||
| 121ca8f453 | |||
| 506b6f905a | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,3 +3,6 @@
 | 
				
			|||||||
# You can modify this file to suit your needs.
 | 
					# You can modify this file to suit your needs.
 | 
				
			||||||
/.esphome/
 | 
					/.esphome/
 | 
				
			||||||
/secrets.yaml
 | 
					/secrets.yaml
 | 
				
			||||||
 | 
					/config.yaml
 | 
				
			||||||
 | 
					/configa.yaml
 | 
				
			||||||
 | 
					**/__pycache__/
 | 
				
			||||||
							
								
								
									
										67
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "files.associations": {
 | 
				
			||||||
 | 
					        "array": "cpp",
 | 
				
			||||||
 | 
					        "atomic": "cpp",
 | 
				
			||||||
 | 
					        "bit": "cpp",
 | 
				
			||||||
 | 
					        "*.tcc": "cpp",
 | 
				
			||||||
 | 
					        "cctype": "cpp",
 | 
				
			||||||
 | 
					        "chrono": "cpp",
 | 
				
			||||||
 | 
					        "cinttypes": "cpp",
 | 
				
			||||||
 | 
					        "clocale": "cpp",
 | 
				
			||||||
 | 
					        "cmath": "cpp",
 | 
				
			||||||
 | 
					        "compare": "cpp",
 | 
				
			||||||
 | 
					        "concepts": "cpp",
 | 
				
			||||||
 | 
					        "condition_variable": "cpp",
 | 
				
			||||||
 | 
					        "cstdarg": "cpp",
 | 
				
			||||||
 | 
					        "cstddef": "cpp",
 | 
				
			||||||
 | 
					        "cstdint": "cpp",
 | 
				
			||||||
 | 
					        "cstdio": "cpp",
 | 
				
			||||||
 | 
					        "cstdlib": "cpp",
 | 
				
			||||||
 | 
					        "cstring": "cpp",
 | 
				
			||||||
 | 
					        "ctime": "cpp",
 | 
				
			||||||
 | 
					        "cwchar": "cpp",
 | 
				
			||||||
 | 
					        "cwctype": "cpp",
 | 
				
			||||||
 | 
					        "deque": "cpp",
 | 
				
			||||||
 | 
					        "list": "cpp",
 | 
				
			||||||
 | 
					        "map": "cpp",
 | 
				
			||||||
 | 
					        "unordered_map": "cpp",
 | 
				
			||||||
 | 
					        "unordered_set": "cpp",
 | 
				
			||||||
 | 
					        "vector": "cpp",
 | 
				
			||||||
 | 
					        "exception": "cpp",
 | 
				
			||||||
 | 
					        "algorithm": "cpp",
 | 
				
			||||||
 | 
					        "functional": "cpp",
 | 
				
			||||||
 | 
					        "iterator": "cpp",
 | 
				
			||||||
 | 
					        "memory": "cpp",
 | 
				
			||||||
 | 
					        "memory_resource": "cpp",
 | 
				
			||||||
 | 
					        "numeric": "cpp",
 | 
				
			||||||
 | 
					        "optional": "cpp",
 | 
				
			||||||
 | 
					        "random": "cpp",
 | 
				
			||||||
 | 
					        "ratio": "cpp",
 | 
				
			||||||
 | 
					        "string": "cpp",
 | 
				
			||||||
 | 
					        "string_view": "cpp",
 | 
				
			||||||
 | 
					        "system_error": "cpp",
 | 
				
			||||||
 | 
					        "tuple": "cpp",
 | 
				
			||||||
 | 
					        "type_traits": "cpp",
 | 
				
			||||||
 | 
					        "utility": "cpp",
 | 
				
			||||||
 | 
					        "initializer_list": "cpp",
 | 
				
			||||||
 | 
					        "iosfwd": "cpp",
 | 
				
			||||||
 | 
					        "iostream": "cpp",
 | 
				
			||||||
 | 
					        "istream": "cpp",
 | 
				
			||||||
 | 
					        "limits": "cpp",
 | 
				
			||||||
 | 
					        "mutex": "cpp",
 | 
				
			||||||
 | 
					        "new": "cpp",
 | 
				
			||||||
 | 
					        "ostream": "cpp",
 | 
				
			||||||
 | 
					        "ranges": "cpp",
 | 
				
			||||||
 | 
					        "stdexcept": "cpp",
 | 
				
			||||||
 | 
					        "stop_token": "cpp",
 | 
				
			||||||
 | 
					        "streambuf": "cpp",
 | 
				
			||||||
 | 
					        "thread": "cpp",
 | 
				
			||||||
 | 
					        "cfenv": "cpp",
 | 
				
			||||||
 | 
					        "typeinfo": "cpp",
 | 
				
			||||||
 | 
					        "variant": "cpp",
 | 
				
			||||||
 | 
					        "regex": "cpp",
 | 
				
			||||||
 | 
					        "bitset": "cpp",
 | 
				
			||||||
 | 
					        "sstream": "cpp",
 | 
				
			||||||
 | 
					        "netfwd": "cpp"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										41
									
								
								components/esp32_nimble_mqtt_room/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								components/esp32_nimble_mqtt_room/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					  CONF_ID,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.components import mqtt, nimble_tracker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["mqtt"]
 | 
				
			||||||
 | 
					AUTO_LOAD=["nimble_distance_custom"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_NIMBLE_ID = "esp32_nimble_mqtt_room"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_ROOM_KEY = 'room'
 | 
				
			||||||
 | 
					CONF_BASE_TOPIC_KEY = 'base_topic'
 | 
				
			||||||
 | 
					CONF_MAC_KEY = 'mac_addr'
 | 
				
			||||||
 | 
					CONF_MAX_DISTANCE = 'max_distance'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					esp32_nimble_tracker_ns = cg.esphome_ns.namespace("esp32_nimble_mqtt_room")
 | 
				
			||||||
 | 
					ESP32NimbleMQTTRoom = esp32_nimble_tracker_ns.class_(
 | 
				
			||||||
 | 
					    "ESP32NimbleMQTTRoom", cg.Component, nimble_tracker.NimbleDeviceListener
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema({
 | 
				
			||||||
 | 
					  cv.GenerateID(): cv.declare_id(ESP32NimbleMQTTRoom),
 | 
				
			||||||
 | 
					  cv.Required(CONF_ROOM_KEY): cv.string,
 | 
				
			||||||
 | 
					  cv.Required(CONF_MAC_KEY): cv.All(cv.ensure_list(cv.string)),
 | 
				
			||||||
 | 
					  cv.Optional(CONF_MAX_DISTANCE, default=16.0): cv.float_,
 | 
				
			||||||
 | 
					  cv.Optional(CONF_BASE_TOPIC_KEY, default="esphome_presense"): cv.string,
 | 
				
			||||||
 | 
					}).extend(cv.COMPONENT_SCHEMA).extend(nimble_tracker.NIMBLE_DEVICE_LISTENER_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					  var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					  await cg.register_component(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cg.add(var.set_room(config[CONF_ROOM_KEY]))
 | 
				
			||||||
 | 
					  cg.add(var.set_base_topic(config[CONF_BASE_TOPIC_KEY]))
 | 
				
			||||||
 | 
					  cg.add(var.set_addresses(config[CONF_MAC_KEY]))
 | 
				
			||||||
 | 
					  cg.add(var.set_max_distance(config[CONF_MAX_DISTANCE]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await nimble_tracker.device_listener_to_code(var, config)
 | 
				
			||||||
 | 
					  await nimble_tracker.register_ble_device(var, config)
 | 
				
			||||||
							
								
								
									
										20
									
								
								components/esp32_nimble_mqtt_room/esp32_nimble_mqtt_room.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								components/esp32_nimble_mqtt_room/esp32_nimble_mqtt_room.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					#include "esp32_nimble_mqtt_room.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace esp32_nimble_mqtt_room
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        void ESP32NimbleMQTTRoom::on_result(nimble_distance_custom::NimbleDistanceCustomResult& result)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto address = result.address.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this->publish_json(
 | 
				
			||||||
 | 
					                this->base_topic_ + "/devices/" + address + "/" + this->room_,
 | 
				
			||||||
 | 
					                [=](ArduinoJson::JsonObject root) -> void {
 | 
				
			||||||
 | 
					                    root["id"] = address;
 | 
				
			||||||
 | 
					                    root["distance"] = result.distance;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } // namespace esp32_nimble_tracker
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
							
								
								
									
										24
									
								
								components/esp32_nimble_mqtt_room/esp32_nimble_mqtt_room.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								components/esp32_nimble_mqtt_room/esp32_nimble_mqtt_room.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/mqtt/custom_mqtt_device.h"
 | 
				
			||||||
 | 
					#include "esphome/components/nimble_distance_custom/nimble_distance_custom.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace esp32_nimble_mqtt_room
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        class ESP32NimbleMQTTRoom : 
 | 
				
			||||||
 | 
					            public mqtt::CustomMQTTDevice, 
 | 
				
			||||||
 | 
					            public nimble_distance_custom::NimbleDistanceCustomComponent
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            protected:
 | 
				
			||||||
 | 
					                std::string room_;
 | 
				
			||||||
 | 
					                std::string base_topic_ = "esphome_presense";
 | 
				
			||||||
 | 
					            public:
 | 
				
			||||||
 | 
					                void on_result(nimble_distance_custom::NimbleDistanceCustomResult&) override;
 | 
				
			||||||
 | 
					                void set_room(std::string room) { room_ = room; }
 | 
				
			||||||
 | 
					                void set_base_topic(std::string base_topic) { base_topic_ = base_topic; }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } // namespace esp32_nimble_tracker
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
							
								
								
									
										555
									
								
								components/esp32_presense/BleFingerprint.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										555
									
								
								components/esp32_presense/BleFingerprint.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,555 @@
 | 
				
			|||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					#include <algorithm>
 | 
				
			||||||
 | 
					#include "BleFingerprint.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "MiFloraHandler.h"
 | 
				
			||||||
 | 
					#include "NameModelHandler.h"
 | 
				
			||||||
 | 
					#include "BleFingerprintCollection.h"
 | 
				
			||||||
 | 
					#include "mbedtls/aes.h"
 | 
				
			||||||
 | 
					#include "rssi.h"
 | 
				
			||||||
 | 
					#include "string_utils.h"
 | 
				
			||||||
 | 
					#include "util.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ClientCallbacks : public BLEClientCallbacks {
 | 
				
			||||||
 | 
					    bool onConnParamsUpdateRequest(NimBLEClient *pClient, const ble_gap_upd_params *params) {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static ClientCallbacks clientCB;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					BleFingerprint::BleFingerprint(BLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff) : filteredDistance{FilteredDistance(fcmin, beta, dcutoff)} {
 | 
				
			||||||
 | 
					    firstSeenMillis = esphome::millis();
 | 
				
			||||||
 | 
					    address = NimBLEAddress(advertisedDevice->getAddress());
 | 
				
			||||||
 | 
					    addressType = advertisedDevice->getAddressType();
 | 
				
			||||||
 | 
					    rssi = advertisedDevice->getRSSI();
 | 
				
			||||||
 | 
					    raw = dist = pow(10, float(get1mRssi() - rssi) / (10.0f * BleFingerprintCollection::absorption));
 | 
				
			||||||
 | 
					    seenCount = 1;
 | 
				
			||||||
 | 
					    queryReport = nullptr;
 | 
				
			||||||
 | 
					    fingerprintAddress();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::setInitial(const BleFingerprint &other) {
 | 
				
			||||||
 | 
					    rssi = other.rssi;
 | 
				
			||||||
 | 
					    dist = other.dist;
 | 
				
			||||||
 | 
					    raw = other.raw;
 | 
				
			||||||
 | 
					    filteredDistance = other.filteredDistance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::shouldHide(const std::string &s) {
 | 
				
			||||||
 | 
					    if (BleFingerprintCollection::include.length() > 0 && !prefixExists(BleFingerprintCollection::include, s)) return true;
 | 
				
			||||||
 | 
					    return (BleFingerprintCollection::exclude.length() > 0 && prefixExists(BleFingerprintCollection::exclude, s));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::setId(const std::string &newId, short newIdType, const std::string &newName) {
 | 
				
			||||||
 | 
					    if (idType < 0 && newIdType < 0 && newIdType >= idType) return false;
 | 
				
			||||||
 | 
					    if (idType > 0 && newIdType <= idType) return false;
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "setId: %s %d %s OLD idType: %d\r\n", newId.c_str(), newIdType, newName.c_str(), idType);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ignore = newIdType < 0;
 | 
				
			||||||
 | 
					    idType = newIdType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DeviceConfig dc;
 | 
				
			||||||
 | 
					    if (BleFingerprintCollection::FindDeviceConfig(newId, dc)) {
 | 
				
			||||||
 | 
					        if (dc.calRssi != NO_RSSI)
 | 
				
			||||||
 | 
					            calRssi = dc.calRssi;
 | 
				
			||||||
 | 
					        if (!dc.alias.empty())
 | 
				
			||||||
 | 
					            return setId(dc.alias, ID_TYPE_ALIAS, dc.name);
 | 
				
			||||||
 | 
					        if (!dc.name.empty())
 | 
				
			||||||
 | 
					            name = dc.name;
 | 
				
			||||||
 | 
					    } else if (!newName.empty() && name != newName)
 | 
				
			||||||
 | 
					        name = newName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (id != newId) {
 | 
				
			||||||
 | 
					        bool newHidden = shouldHide(newId);
 | 
				
			||||||
 | 
					        countable = !ignore && !hidden && !BleFingerprintCollection::countIds.empty() && prefixExists(BleFingerprintCollection::countIds, newId);
 | 
				
			||||||
 | 
					        bool newQuery = !ignore && !BleFingerprintCollection::query.empty() && prefixExists(BleFingerprintCollection::query, newId);
 | 
				
			||||||
 | 
					        if (newQuery != allowQuery) {
 | 
				
			||||||
 | 
					            allowQuery = newQuery;
 | 
				
			||||||
 | 
					            if (allowQuery) {
 | 
				
			||||||
 | 
					                qryAttempts = 0;
 | 
				
			||||||
 | 
					                if (rssi < -80) {
 | 
				
			||||||
 | 
					                    qryDelayMillis = 30000;
 | 
				
			||||||
 | 
					                    lastQryMillis = esphome::millis();
 | 
				
			||||||
 | 
					                } else if (rssi < -70) {
 | 
				
			||||||
 | 
					                    qryDelayMillis = 5000;
 | 
				
			||||||
 | 
					                    lastQryMillis = esphome::millis();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        id = newId;
 | 
				
			||||||
 | 
					        hidden = newHidden;
 | 
				
			||||||
 | 
					        added = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const std::string BleFingerprint::getMac() const {
 | 
				
			||||||
 | 
					    const auto nativeAddress = address.getNative();
 | 
				
			||||||
 | 
					    return Sprintf("%02x%02x%02x%02x%02x%02x", nativeAddress[5], nativeAddress[4], nativeAddress[3], nativeAddress[2], nativeAddress[1], nativeAddress[0]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const int BleFingerprint::get1mRssi() const {
 | 
				
			||||||
 | 
					    if (calRssi != NO_RSSI) return calRssi + BleFingerprintCollection::rxAdjRssi;
 | 
				
			||||||
 | 
					    if (bcnRssi != NO_RSSI) return bcnRssi + BleFingerprintCollection::rxAdjRssi;
 | 
				
			||||||
 | 
					    if (mdRssi != NO_RSSI) return mdRssi + BleFingerprintCollection::rxAdjRssi;
 | 
				
			||||||
 | 
					    if (asRssi != NO_RSSI) return asRssi + BleFingerprintCollection::rxAdjRssi;
 | 
				
			||||||
 | 
					    return BleFingerprintCollection::rxRefRssi + DEFAULT_TX + BleFingerprintCollection::rxAdjRssi;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::fingerprint(NimBLEAdvertisedDevice *advertisedDevice) {
 | 
				
			||||||
 | 
					    if (advertisedDevice->haveName()) {
 | 
				
			||||||
 | 
					        const std::string name = advertisedDevice->getName();
 | 
				
			||||||
 | 
					        if (!name.empty()) setId(std::string("name:") + kebabify(name).c_str(), ID_TYPE_NAME, std::string(name.c_str()));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (advertisedDevice->getAdvType() > 0)
 | 
				
			||||||
 | 
					        connectable = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t serviceAdvCount = advertisedDevice->getServiceUUIDCount();
 | 
				
			||||||
 | 
					    size_t serviceDataCount = advertisedDevice->getServiceDataCount();
 | 
				
			||||||
 | 
					    bool haveTxPower = advertisedDevice->haveTXPower();
 | 
				
			||||||
 | 
					    int8_t txPower = advertisedDevice->getTXPower();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (serviceAdvCount > 0) fingerprintServiceAdvertisements(advertisedDevice, serviceAdvCount, haveTxPower, txPower);
 | 
				
			||||||
 | 
					    if (serviceDataCount > 0) fingerprintServiceData(advertisedDevice, serviceDataCount, haveTxPower, txPower);
 | 
				
			||||||
 | 
					    if (advertisedDevice->haveManufacturerData()) fingerprintManufactureData(advertisedDevice, haveTxPower, txPower);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int bt_encrypt_be(const uint8_t *key, const uint8_t *plaintext, uint8_t *enc_data) {
 | 
				
			||||||
 | 
					    mbedtls_aes_context ctx;
 | 
				
			||||||
 | 
					    mbedtls_aes_init(&ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mbedtls_aes_setkey_enc(&ctx, key, 128) != 0) {
 | 
				
			||||||
 | 
					        mbedtls_aes_free(&ctx);
 | 
				
			||||||
 | 
					        return BLE_HS_EUNKNOWN;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mbedtls_aes_crypt_ecb(&ctx, MBEDTLS_AES_ENCRYPT, plaintext, enc_data) != 0) {
 | 
				
			||||||
 | 
					        mbedtls_aes_free(&ctx);
 | 
				
			||||||
 | 
					        return BLE_HS_EUNKNOWN;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mbedtls_aes_free(&ctx);
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct encryption_block {
 | 
				
			||||||
 | 
					    uint8_t key[16];
 | 
				
			||||||
 | 
					    uint8_t plain_text[16];
 | 
				
			||||||
 | 
					    uint8_t cipher_text[16];
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool ble_ll_resolv_rpa(const uint8_t *rpa, const uint8_t *irk) {
 | 
				
			||||||
 | 
					    struct encryption_block ecb;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto irk32 = (const uint32_t *)irk;
 | 
				
			||||||
 | 
					    auto key32 = (uint32_t *)&ecb.key[0];
 | 
				
			||||||
 | 
					    auto pt32 = (uint32_t *)&ecb.plain_text[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    key32[0] = irk32[0];
 | 
				
			||||||
 | 
					    key32[1] = irk32[1];
 | 
				
			||||||
 | 
					    key32[2] = irk32[2];
 | 
				
			||||||
 | 
					    key32[3] = irk32[3];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pt32[0] = 0;
 | 
				
			||||||
 | 
					    pt32[1] = 0;
 | 
				
			||||||
 | 
					    pt32[2] = 0;
 | 
				
			||||||
 | 
					    pt32[3] = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ecb.plain_text[15] = rpa[3];
 | 
				
			||||||
 | 
					    ecb.plain_text[14] = rpa[4];
 | 
				
			||||||
 | 
					    ecb.plain_text[13] = rpa[5];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ESP_LOGD(TAG, "RPA resolved %d %02x%02x%02x %02x%02x%02x\r\n", err, rpa[0], rpa[1], rpa[2], ecb.cipher_text[15], ecb.cipher_text[14], ecb.cipher_text[13]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::fingerprintAddress() {
 | 
				
			||||||
 | 
					    auto mac = getMac();
 | 
				
			||||||
 | 
					    if (!BleFingerprintCollection::knownMacs.empty() && prefixExists(BleFingerprintCollection::knownMacs, mac))
 | 
				
			||||||
 | 
					        setId("known:" + mac, ID_TYPE_KNOWN_MAC);
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        switch (addressType) {
 | 
				
			||||||
 | 
					            case BLE_ADDR_PUBLIC:
 | 
				
			||||||
 | 
					            case BLE_ADDR_PUBLIC_ID:
 | 
				
			||||||
 | 
					                setId(mac, ID_TYPE_PUBLIC_MAC);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            case BLE_ADDR_RANDOM:
 | 
				
			||||||
 | 
					            case BLE_ADDR_RANDOM_ID: {
 | 
				
			||||||
 | 
					                const auto *naddress = address.getNative();
 | 
				
			||||||
 | 
					                if ((naddress[5] & 0xc0) == 0xc0)
 | 
				
			||||||
 | 
					                    setId(mac, ID_TYPE_RAND_STATIC_MAC);
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    auto irks = BleFingerprintCollection::irks;
 | 
				
			||||||
 | 
					                    auto it = std::find_if(irks.begin(), irks.end(), [naddress](uint8_t *irk) { return ble_ll_resolv_rpa(naddress, irk); });
 | 
				
			||||||
 | 
					                    if (it != irks.end()) {
 | 
				
			||||||
 | 
					                        auto irk_hex = hexStr(*it, 16);
 | 
				
			||||||
 | 
					                        setId(std::string("irk:") + irk_hex.c_str(), ID_TYPE_KNOWN_IRK);
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    setId(mac, ID_TYPE_RAND_MAC);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                setId(mac, ID_TYPE_RAND_MAC);
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceAdvCount, bool haveTxPower, int8_t txPower) {
 | 
				
			||||||
 | 
					    for (auto i = 0; i < serviceAdvCount; i++) {
 | 
				
			||||||
 | 
					        auto uuid = advertisedDevice->getServiceUUID(i);
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Verbose | %s | %-58s%ddBm AD: %s\r\n", getMac().c_str(), getId().c_str(), rssi, advertisedDevice->getServiceUUID(i).toString().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					        if (uuid == tileUUID) {
 | 
				
			||||||
 | 
					            asRssi = BleFingerprintCollection::rxRefRssi + TILE_TX;
 | 
				
			||||||
 | 
					            setId("tile:" + getMac(), ID_TYPE_TILE);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == sonosUUID) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("sonos:" + getMac(), ID_TYPE_SONOS);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == itagUUID) {
 | 
				
			||||||
 | 
					            asRssi = BleFingerprintCollection::rxRefRssi + (haveTxPower ? txPower : ITAG_TX);
 | 
				
			||||||
 | 
					            setId("itag:" + getMac(), ID_TYPE_ITAG);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == trackrUUID) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("trackr:" + getMac(), ID_TYPE_TRACKR);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == tractiveUUID) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("tractive:" + getMac(), ID_TYPE_TRACTIVE);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == vanmoofUUID) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("vanmoof:" + getMac(), ID_TYPE_VANMOOF);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == (meaterService)) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("meater:" + getMac(), ID_TYPE_MEATER);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == nutUUID) {
 | 
				
			||||||
 | 
					            asRssi = BleFingerprintCollection::rxRefRssi + (haveTxPower ? txPower : NUT_TX);
 | 
				
			||||||
 | 
					            setId("nut:" + getMac(), ID_TYPE_NUT);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        } else if (uuid == miFloraUUID) {
 | 
				
			||||||
 | 
					            asRssi = BleFingerprintCollection::rxRefRssi + (haveTxPower ? txPower : FLORA_TX);
 | 
				
			||||||
 | 
					            setId("flora:" + getMac(), ID_TYPE_FLORA);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string fingerprint = "ad:";
 | 
				
			||||||
 | 
					    asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					    for (int i = 0; i < serviceAdvCount; i++) {
 | 
				
			||||||
 | 
					        std::string sid = advertisedDevice->getServiceUUID(i).toString();
 | 
				
			||||||
 | 
					        fingerprint = fingerprint + sid.c_str();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (haveTxPower) fingerprint = fingerprint + std::to_string(-txPower);
 | 
				
			||||||
 | 
					    setId(fingerprint, ID_TYPE_AD);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceDataCount, bool haveTxPower, int8_t txPower) {
 | 
				
			||||||
 | 
					    asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					    std::string fingerprint = "";
 | 
				
			||||||
 | 
					    for (int i = 0; i < serviceDataCount; i++) {
 | 
				
			||||||
 | 
					        BLEUUID uuid = advertisedDevice->getServiceDataUUID(i);
 | 
				
			||||||
 | 
					        std::string strServiceData = advertisedDevice->getServiceData(i);
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Verbose | %s | %-58s%ddBm SD: %s/%s\r\n", getMac().c_str(), getId().c_str(), rssi, uuid.toString().c_str(), hexStr(strServiceData).c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (uuid == exposureUUID) {  // found COVID-19 exposure tracker
 | 
				
			||||||
 | 
					            bcnRssi = BleFingerprintCollection::rxRefRssi + EXPOSURE_TX;
 | 
				
			||||||
 | 
					            setId("exp:" + std::to_string(strServiceData.length()), ID_TYPE_EXPOSURE);
 | 
				
			||||||
 | 
					            // disc = hexStr(strServiceData).c_str();
 | 
				
			||||||
 | 
					        } else if (uuid == smartTagUUID) {  // found Samsung smart tag
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("smarttag:" + std::to_string(strServiceData.length()), ID_TYPE_SMARTTAG);
 | 
				
			||||||
 | 
					        } else if (uuid == miThermUUID) {
 | 
				
			||||||
 | 
					            asRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            if (strServiceData.length() == 15) {  // custom format
 | 
				
			||||||
 | 
					                auto serviceData = strServiceData.c_str();
 | 
				
			||||||
 | 
					                temp = float(*(int16_t *)(serviceData + 6)) / 100.0f;
 | 
				
			||||||
 | 
					                humidity = float(*(uint16_t *)(serviceData + 8)) / 100.0f;
 | 
				
			||||||
 | 
					                mv = *(uint16_t *)(serviceData + 10);
 | 
				
			||||||
 | 
					                battery = serviceData[12];
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					                ESP_LOGD(TAG, "Temp: %.1f°, Humidity: %.1f%%, mV: %hu, Battery: %hhu%%, flg: 0x%02hhx, cout: %hhu\r\n", temp, humidity, mv, battery, serviceData[14], serviceData[13]);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					                setId("miTherm:" + getMac(), ID_TYPE_MITHERM);
 | 
				
			||||||
 | 
					            } else if (strServiceData.length() == 13) {  // format atc1441
 | 
				
			||||||
 | 
					                auto serviceData = strServiceData.c_str();
 | 
				
			||||||
 | 
					                int16_t x = (serviceData[6] << 8) | serviceData[7];
 | 
				
			||||||
 | 
					                temp = float(x) / 10.0f;
 | 
				
			||||||
 | 
					                humidity = serviceData[8];
 | 
				
			||||||
 | 
					                mv = x = (serviceData[10] << 8) | serviceData[11];
 | 
				
			||||||
 | 
					                battery = serviceData[9];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					                ESP_LOGD(TAG, "Temp: %.1f°, Humidity: %.1f%%, mV: %hu, Battery: %hhu%%, cout: %hhu\r\n", temp, humidity, mv, battery, serviceData[12]);
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					                setId("miTherm:" + getMac(), ID_TYPE_MITHERM);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (uuid == eddystoneUUID && strServiceData.length() > 0) {
 | 
				
			||||||
 | 
					            if (strServiceData[0] == EDDYSTONE_URL_FRAME_TYPE && strServiceData.length() <= 18) {
 | 
				
			||||||
 | 
					                BLEEddystoneURL oBeacon = BLEEddystoneURL();
 | 
				
			||||||
 | 
					                oBeacon.setData(strServiceData);
 | 
				
			||||||
 | 
					                bcnRssi = EDDYSTONE_ADD_1M + oBeacon.getPower();
 | 
				
			||||||
 | 
					            } else if (strServiceData[0] == EDDYSTONE_TLM_FRAME_TYPE) {
 | 
				
			||||||
 | 
					                BLEEddystoneTLM oBeacon = BLEEddystoneTLM();
 | 
				
			||||||
 | 
					                oBeacon.setData(strServiceData);
 | 
				
			||||||
 | 
					                temp = oBeacon.getTemp();
 | 
				
			||||||
 | 
					                mv = oBeacon.getVolt();
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					                Serial.println(oBeacon.toString().c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					            } else if (strServiceData[0] == 0x00) {
 | 
				
			||||||
 | 
					                auto serviceData = strServiceData.c_str();
 | 
				
			||||||
 | 
					                int8_t rss0m = *(int8_t *)(serviceData + 1);
 | 
				
			||||||
 | 
					                bcnRssi = EDDYSTONE_ADD_1M + rss0m;
 | 
				
			||||||
 | 
					                setId(Sprintf("eddy:%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x-%02x%02x%02x%02x%02x%02x",
 | 
				
			||||||
 | 
					                              strServiceData[2], strServiceData[3], strServiceData[4], strServiceData[5], strServiceData[6],
 | 
				
			||||||
 | 
					                              strServiceData[6], strServiceData[7], strServiceData[8], strServiceData[9], strServiceData[10],
 | 
				
			||||||
 | 
					                              strServiceData[11], strServiceData[12], strServiceData[13], strServiceData[14], strServiceData[15],
 | 
				
			||||||
 | 
					                              strServiceData[16], strServiceData[17]),
 | 
				
			||||||
 | 
					                      ID_TYPE_EBEACON);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            fingerprint = fingerprint + uuid.toString().c_str();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!fingerprint.empty()) {
 | 
				
			||||||
 | 
					        if (haveTxPower) fingerprint = fingerprint + std::to_string(-txPower);
 | 
				
			||||||
 | 
					        setId("sd:" + fingerprint, ID_TYPE_SD);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::fingerprintManufactureData(NimBLEAdvertisedDevice *advertisedDevice, bool haveTxPower, int8_t txPower) {
 | 
				
			||||||
 | 
					    std::string strManufacturerData = advertisedDevice->getManufacturerData();
 | 
				
			||||||
 | 
					#ifdef VERBOSE
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "Verbose | %s | %-58s%ddBm MD: %s\r\n", getMac().c_str(), getId().c_str(), rssi, hexStr(strManufacturerData).c_str());
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					    if (strManufacturerData.length() >= 2) {
 | 
				
			||||||
 | 
					        std::string manuf = Sprintf("%02x%02x", strManufacturerData[1], strManufacturerData[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (manuf == "004c")  // Apple
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            if (strManufacturerData.length() == 25 && strManufacturerData[2] == 0x02 && strManufacturerData[3] == 0x15) {
 | 
				
			||||||
 | 
					                BLEBeacon oBeacon = BLEBeacon();
 | 
				
			||||||
 | 
					                oBeacon.setData(strManufacturerData);
 | 
				
			||||||
 | 
					                bcnRssi = oBeacon.getSignalPower();
 | 
				
			||||||
 | 
					                setId(Sprintf("iBeacon:%s-%u-%u", std::string(oBeacon.getProximityUUID()).c_str(), ENDIAN_CHANGE_U16(oBeacon.getMajor()), ENDIAN_CHANGE_U16(oBeacon.getMinor())), bcnRssi != 3 ? ID_TYPE_IBEACON : ID_TYPE_ECHO_LOST);
 | 
				
			||||||
 | 
					            } else if (strManufacturerData.length() >= 4 && strManufacturerData[2] == 0x10) {
 | 
				
			||||||
 | 
					                std::string pid = Sprintf("apple:%02x%02x:%u", strManufacturerData[2], strManufacturerData[3], strManufacturerData.length());
 | 
				
			||||||
 | 
					                if (haveTxPower) pid += -txPower;
 | 
				
			||||||
 | 
					                setId(pid, ID_TYPE_APPLE_NEARBY);
 | 
				
			||||||
 | 
					                mdRssi = BleFingerprintCollection::rxRefRssi + APPLE_TX;
 | 
				
			||||||
 | 
					            } else if (strManufacturerData.length() >= 4 && strManufacturerData[2] == 0x12 && strManufacturerData.length() == 29) {
 | 
				
			||||||
 | 
					                std::string pid = "apple:findmy";
 | 
				
			||||||
 | 
					                setId(pid, ID_TYPE_FINDMY);
 | 
				
			||||||
 | 
					                mdRssi = BleFingerprintCollection::rxRefRssi + APPLE_TX;
 | 
				
			||||||
 | 
					            } else if (strManufacturerData.length() >= 4) {
 | 
				
			||||||
 | 
					                std::string pid = Sprintf("apple:%02x%02x:%u", strManufacturerData[2], strManufacturerData[3], strManufacturerData.length());
 | 
				
			||||||
 | 
					                if (haveTxPower) pid += -txPower;
 | 
				
			||||||
 | 
					                setId(pid, ID_TYPE_MISC_APPLE);
 | 
				
			||||||
 | 
					                mdRssi = BleFingerprintCollection::rxRefRssi + APPLE_TX;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (manuf == "05a7")  // Sonos
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("sonos:" + getMac(), ID_TYPE_SONOS);
 | 
				
			||||||
 | 
					        } else if (manuf == "0087")  // Garmin
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("garmin:" + getMac(), ID_TYPE_GARMIN);
 | 
				
			||||||
 | 
					        } else if (manuf == "4d4b")  // iTrack
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("iTrack:" + getMac(), ID_TYPE_ITRACK);
 | 
				
			||||||
 | 
					        } else if (manuf == "0157")  // Mi-fit
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("mifit:" + getMac(), ID_TYPE_MIFIT);
 | 
				
			||||||
 | 
					        } else if (manuf == "0006" && strManufacturerData.length() == 29)  // microsoft
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId(Sprintf("msft:cdp:%02x%02x", strManufacturerData[3], strManufacturerData[5]), ID_TYPE_MSFT);
 | 
				
			||||||
 | 
					            /*disc = Sprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
 | 
				
			||||||
 | 
					                           strManufacturerData[6], strManufacturerData[7], strManufacturerData[8], strManufacturerData[9], strManufacturerData[10],
 | 
				
			||||||
 | 
					                           strManufacturerData[11], strManufacturerData[12], strManufacturerData[13], strManufacturerData[14], strManufacturerData[15],
 | 
				
			||||||
 | 
					                           strManufacturerData[16], strManufacturerData[17], strManufacturerData[18], strManufacturerData[19], strManufacturerData[20],
 | 
				
			||||||
 | 
					                           strManufacturerData[21], strManufacturerData[22], strManufacturerData[23], strManufacturerData[24], strManufacturerData[25]);
 | 
				
			||||||
 | 
					                           */
 | 
				
			||||||
 | 
					        } else if (manuf == "0075")  // samsung
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            setId("samsung:" + getMac(), ID_TYPE_MISC);
 | 
				
			||||||
 | 
					        } else if (manuf == "beac" && strManufacturerData.length() == 26) {
 | 
				
			||||||
 | 
					            BLEBeacon oBeacon = BLEBeacon();
 | 
				
			||||||
 | 
					            oBeacon.setData(strManufacturerData.substr(0, 25));
 | 
				
			||||||
 | 
					            setId(Sprintf("altBeacon:%s-%u-%u", std::string(oBeacon.getProximityUUID()).c_str(), ENDIAN_CHANGE_U16(oBeacon.getMajor()), ENDIAN_CHANGE_U16(oBeacon.getMinor())), ID_TYPE_ABEACON);
 | 
				
			||||||
 | 
					            bcnRssi = oBeacon.getSignalPower();
 | 
				
			||||||
 | 
					        } else if (manuf != "0000") {
 | 
				
			||||||
 | 
					            mdRssi = haveTxPower ? BleFingerprintCollection::rxRefRssi + txPower : NO_RSSI;
 | 
				
			||||||
 | 
					            std::string fingerprint = Sprintf("md:%s:%u", manuf.c_str(), strManufacturerData.length());
 | 
				
			||||||
 | 
					            if (haveTxPower) fingerprint = fingerprint + std::to_string(-txPower);
 | 
				
			||||||
 | 
					            setId(fingerprint, ID_TYPE_MD);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::seen(BLEAdvertisedDevice *advertisedDevice) {
 | 
				
			||||||
 | 
					    lastSeenMillis = esphome::millis();
 | 
				
			||||||
 | 
					    reported = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    seenCount++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fingerprint(advertisedDevice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (ignore || hidden) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rssi = advertisedDevice->getRSSI();
 | 
				
			||||||
 | 
					    raw = pow(10, float(get1mRssi() - rssi) / (10.0f * BleFingerprintCollection::absorption));
 | 
				
			||||||
 | 
					    filteredDistance.addMeasurement(raw);
 | 
				
			||||||
 | 
					    dist = filteredDistance.getDistance();
 | 
				
			||||||
 | 
					    vari = filteredDistance.getVariance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!added) {
 | 
				
			||||||
 | 
					        added = true;
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::fill(JsonObject *doc) {
 | 
				
			||||||
 | 
					    (*doc)["mac"] = getMac();
 | 
				
			||||||
 | 
					    (*doc)["id"] = id;
 | 
				
			||||||
 | 
					    if (!name.empty()) (*doc)["name"] = name;
 | 
				
			||||||
 | 
					    if (idType) (*doc)["idType"] = idType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (*doc)["rssi@1m"] = get1mRssi();
 | 
				
			||||||
 | 
					    (*doc)["rssi"] = rssi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isnormal(raw)) (*doc)["raw"] = serialized(std::to_string(raw));
 | 
				
			||||||
 | 
					    if (isnormal(dist)) (*doc)["distance"] = serialized(std::to_string(dist));
 | 
				
			||||||
 | 
					    if (isnormal(vari)) (*doc)["var"] = serialized(std::to_string(vari));
 | 
				
			||||||
 | 
					    if (close) (*doc)["close"] = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    (*doc)["int"] = (millis() - firstSeenMillis) / seenCount;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (mv) (*doc)["mV"] = mv;
 | 
				
			||||||
 | 
					    if (battery != 0xFF) (*doc)["batt"] = battery;
 | 
				
			||||||
 | 
					    if (temp) (*doc)["temp"] = serialized(std::to_string(temp));
 | 
				
			||||||
 | 
					    if (humidity) (*doc)["rh"] = serialized(std::to_string(humidity));
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::report(JsonObject *doc) {
 | 
				
			||||||
 | 
					    if (ignore || idType <= ID_TYPE_RAND_MAC || hidden) return false;
 | 
				
			||||||
 | 
					    if (reported) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto maxDistance = BleFingerprintCollection::maxDistance;
 | 
				
			||||||
 | 
					    if (maxDistance > 0 && dist > maxDistance)
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto now = esphome::millis();
 | 
				
			||||||
 | 
					    if ((abs(dist - lastReported) < BleFingerprintCollection::skipDistance) && (lastReportedMillis > 0) && (now - lastReportedMillis < BleFingerprintCollection::skipMs))
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (fill(doc)) {
 | 
				
			||||||
 | 
					        lastReportedMillis = now;
 | 
				
			||||||
 | 
					        lastReported = dist;
 | 
				
			||||||
 | 
					        reported = true;
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::query() {
 | 
				
			||||||
 | 
					    if (!allowQuery || isQuerying) return false;
 | 
				
			||||||
 | 
					    if (rssi < -90) return false; // Too far away
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto now = esphome::millis();
 | 
				
			||||||
 | 
					    if (now - lastSeenMillis > 5) return false; // Haven't seen lately
 | 
				
			||||||
 | 
					    if (now - lastQryMillis < qryDelayMillis) return false; // Too soon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    isQuerying = true;
 | 
				
			||||||
 | 
					    lastQryMillis = now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool success = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ESP_LOGD(TAG, "%u Query  | %s | %-58s%ddBm %lums\r\n", xPortGetCoreID(), getMac().c_str(), id.c_str(), rssi, now - lastSeenMillis);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NimBLEClient *pClient = NimBLEDevice::getClientListSize() ? NimBLEDevice::getClientByPeerAddress(address) : nullptr;
 | 
				
			||||||
 | 
					    if (!pClient) pClient = NimBLEDevice::getDisconnectedClient();
 | 
				
			||||||
 | 
					    if (!pClient) pClient = NimBLEDevice::createClient();
 | 
				
			||||||
 | 
					    pClient->setClientCallbacks(&clientCB, false);
 | 
				
			||||||
 | 
					    pClient->setConnectionParams(12, 12, 0, 48);
 | 
				
			||||||
 | 
					    pClient->setConnectTimeout(5);
 | 
				
			||||||
 | 
					    NimBLEDevice::getScan()->stop();
 | 
				
			||||||
 | 
					    if (pClient->connect(address)) {
 | 
				
			||||||
 | 
					        if (allowQuery) {
 | 
				
			||||||
 | 
					            if (id.rfind("flora:", 0) == 0)
 | 
				
			||||||
 | 
					                success = MiFloraHandler::requestData(pClient, this);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                success = NameModelHandler::requestData(pClient, this);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NimBLEDevice::deleteClient(pClient);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (success) {
 | 
				
			||||||
 | 
					        qryAttempts = 0;
 | 
				
			||||||
 | 
					        qryDelayMillis = BleFingerprintCollection::requeryMs;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        qryAttempts++;
 | 
				
			||||||
 | 
					        qryDelayMillis = std::min(int(pow(10, qryAttempts)), 60000);
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "%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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool BleFingerprint::shouldCount() {
 | 
				
			||||||
 | 
					    if (!close && rssi > CLOSE_RSSI + BleFingerprintCollection::rxAdjRssi) {
 | 
				
			||||||
 | 
					        BleFingerprintCollection::Close(this, true);
 | 
				
			||||||
 | 
					        close = true;
 | 
				
			||||||
 | 
					    } else if (close && rssi < LEFT_RSSI + BleFingerprintCollection::rxAdjRssi) {
 | 
				
			||||||
 | 
					        BleFingerprintCollection::Close(this, false);
 | 
				
			||||||
 | 
					        close = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool prevCounting = counting;
 | 
				
			||||||
 | 
					    if (ignore || !countable)
 | 
				
			||||||
 | 
					        counting = false;
 | 
				
			||||||
 | 
					    else if (getMsSinceLastSeen() > BleFingerprintCollection::countMs)
 | 
				
			||||||
 | 
					        counting = false;
 | 
				
			||||||
 | 
					    else if (counting && dist > BleFingerprintCollection::countExit)
 | 
				
			||||||
 | 
					        counting = false;
 | 
				
			||||||
 | 
					    else if (!counting && dist <= BleFingerprintCollection::countEnter)
 | 
				
			||||||
 | 
					        counting = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (prevCounting != counting) {
 | 
				
			||||||
 | 
					        BleFingerprintCollection::Count(this, counting);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return counting;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void BleFingerprint::expire() {
 | 
				
			||||||
 | 
					    lastSeenMillis = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										161
									
								
								components/esp32_presense/BleFingerprint.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								components/esp32_presense/BleFingerprint.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,161 @@
 | 
				
			|||||||
 | 
					#ifndef _BLEFINGERPRINT_
 | 
				
			||||||
 | 
					#define _BLEFINGERPRINT_
 | 
				
			||||||
 | 
					#include <ArduinoJson.h>
 | 
				
			||||||
 | 
					#include <NimBLEDevice.h>
 | 
				
			||||||
 | 
					#include "NimBLEAdvertisedDevice.h"
 | 
				
			||||||
 | 
					#include "NimBLEBeacon.h"
 | 
				
			||||||
 | 
					#include "NimBLEEddystoneTLM.h"
 | 
				
			||||||
 | 
					#include "NimBLEEddystoneURL.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <memory>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "QueryReport.h"
 | 
				
			||||||
 | 
					#include "rssi.h"
 | 
				
			||||||
 | 
					#include "string_utils.h"
 | 
				
			||||||
 | 
					#include "FilteredDistance.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "esphome/core/log.h"
 | 
				
			||||||
 | 
					#include "esphome/core/helpers.h"
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					#include "esphome/core/time.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "esp32_presense"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NO_RSSI int8_t(-128)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ID_TYPE_TX_POW short(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NO_ID_TYPE short(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ID_TYPE_ECHO_LOST short(-10)
 | 
				
			||||||
 | 
					#define ID_TYPE_MISC_APPLE short(-5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ID_TYPE_RAND_MAC short(1)
 | 
				
			||||||
 | 
					#define ID_TYPE_RAND_STATIC_MAC short(5)
 | 
				
			||||||
 | 
					#define ID_TYPE_AD short(10)
 | 
				
			||||||
 | 
					#define ID_TYPE_SD short(15)
 | 
				
			||||||
 | 
					#define ID_TYPE_MD short(20)
 | 
				
			||||||
 | 
					#define ID_TYPE_MISC short(30)
 | 
				
			||||||
 | 
					#define ID_TYPE_FINDMY short(32)
 | 
				
			||||||
 | 
					#define ID_TYPE_NAME short(35)
 | 
				
			||||||
 | 
					#define ID_TYPE_MSFT short(40)
 | 
				
			||||||
 | 
					#define ID_TYPE_UNIQUE short(50)
 | 
				
			||||||
 | 
					#define ID_TYPE_PUBLIC_MAC short(55)
 | 
				
			||||||
 | 
					#define ID_TYPE_SONOS short(105)
 | 
				
			||||||
 | 
					#define ID_TYPE_GARMIN short(107)
 | 
				
			||||||
 | 
					#define ID_TYPE_MITHERM short(110)
 | 
				
			||||||
 | 
					#define ID_TYPE_MIFIT short(115)
 | 
				
			||||||
 | 
					#define ID_TYPE_EXPOSURE short(120)
 | 
				
			||||||
 | 
					#define ID_TYPE_SMARTTAG short(121)
 | 
				
			||||||
 | 
					#define ID_TYPE_ITAG short(125)
 | 
				
			||||||
 | 
					#define ID_TYPE_ITRACK short(127)
 | 
				
			||||||
 | 
					#define ID_TYPE_NUT short(128)
 | 
				
			||||||
 | 
					#define ID_TYPE_FLORA short(129)
 | 
				
			||||||
 | 
					#define ID_TYPE_TRACKR short(130)
 | 
				
			||||||
 | 
					#define ID_TYPE_TILE short(135)
 | 
				
			||||||
 | 
					#define ID_TYPE_MEATER short(140)
 | 
				
			||||||
 | 
					#define ID_TYPE_TRACTIVE short(142)
 | 
				
			||||||
 | 
					#define ID_TYPE_VANMOOF short(145)
 | 
				
			||||||
 | 
					#define ID_TYPE_APPLE_NEARBY short(150)
 | 
				
			||||||
 | 
					#define ID_TYPE_QUERY_MODEL short(155)
 | 
				
			||||||
 | 
					#define ID_TYPE_QUERY_NAME short(160)
 | 
				
			||||||
 | 
					#define ID_TYPE_RM_ASST short(165)
 | 
				
			||||||
 | 
					#define ID_TYPE_EBEACON short(170)
 | 
				
			||||||
 | 
					#define ID_TYPE_ABEACON short(175)
 | 
				
			||||||
 | 
					#define ID_TYPE_IBEACON short(180)
 | 
				
			||||||
 | 
					#define ID_TYPE_KNOWN_IRK short(200)
 | 
				
			||||||
 | 
					#define ID_TYPE_KNOWN_MAC short(210)
 | 
				
			||||||
 | 
					#define ID_TYPE_ALIAS short(250)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using namespace esphome;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BleFingerprint {
 | 
				
			||||||
 | 
					   public:
 | 
				
			||||||
 | 
					    BleFingerprint(NimBLEAdvertisedDevice *advertisedDevice, float fcmin, float beta, float dcutoff);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool seen(BLEAdvertisedDevice *advertisedDevice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool fill(JsonObject *doc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool report(JsonObject *doc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool query();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const std::string getId() const { return id; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const std::string getName() const { return name; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void setName(const std::string &name) { this->name = name; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool setId(const std::string &newId, short int newIdType, const std::string &newName = "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void setInitial(const BleFingerprint &other);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const std::string getMac() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const short getIdType() const { return idType; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const float getDistance() const { return dist; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const int getRssi() const { return rssi; }
 | 
				
			||||||
 | 
					    const int getRawRssi() const { return rssi; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const int get1mRssi() const;
 | 
				
			||||||
 | 
					    void set1mRssi(int8_t rssi) { calRssi = rssi; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const NimBLEAddress getAddress() const { return address; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const unsigned long getMsSinceLastSeen() const { return lastSeenMillis ? millis() - lastSeenMillis : 4294967295; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const unsigned long getMsSinceFirstSeen() const { return millis() - firstSeenMillis; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool getVisible() const { return !ignore && !hidden; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool getAdded() const { return added; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool getIgnore() const { return ignore; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const bool getAllowQuery() const { return allowQuery; };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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() {
 | 
				
			||||||
 | 
					        auto sc = seenCount - lastSeenCount;
 | 
				
			||||||
 | 
					        lastSeenCount = seenCount;
 | 
				
			||||||
 | 
					        return sc;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool shouldCount();
 | 
				
			||||||
 | 
					    void fingerprintAddress();
 | 
				
			||||||
 | 
					    void expire();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					   private:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool added = false, close = false, reported = false, ignore = false, allowQuery = false, isQuerying = false, hidden = false, connectable = false, countable = false, counting = false;
 | 
				
			||||||
 | 
					    NimBLEAddress address;
 | 
				
			||||||
 | 
					    std::string id, name;
 | 
				
			||||||
 | 
					    short int idType = NO_ID_TYPE;
 | 
				
			||||||
 | 
					    int rssi = NO_RSSI;
 | 
				
			||||||
 | 
					    int8_t calRssi = NO_RSSI, bcnRssi = NO_RSSI, mdRssi = NO_RSSI, asRssi = NO_RSSI;
 | 
				
			||||||
 | 
					    unsigned int qryAttempts = 0, qryDelayMillis = 0;
 | 
				
			||||||
 | 
					    float raw = 0, dist = 0, vari = 0, lastReported = 0, temp = 0, humidity = 0;
 | 
				
			||||||
 | 
					    unsigned long firstSeenMillis, lastSeenMillis = 0, lastReportedMillis = 0, lastQryMillis = 0;
 | 
				
			||||||
 | 
					    unsigned long seenCount = 1, lastSeenCount = 0;
 | 
				
			||||||
 | 
					    uint16_t mv = 0;
 | 
				
			||||||
 | 
					    uint8_t battery = 0xFF, addressType = 0xFF;
 | 
				
			||||||
 | 
					    FilteredDistance filteredDistance;
 | 
				
			||||||
 | 
					    std::unique_ptr<QueryReport> queryReport = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static bool shouldHide(const std::string &s);
 | 
				
			||||||
 | 
					    void fingerprint(NimBLEAdvertisedDevice *advertisedDevice);
 | 
				
			||||||
 | 
					    void fingerprintServiceAdvertisements(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceAdvCount, bool haveTxPower, int8_t txPower);
 | 
				
			||||||
 | 
					    void fingerprintServiceData(NimBLEAdvertisedDevice *advertisedDevice, size_t serviceDataCount, bool haveTxPower, int8_t txPower);
 | 
				
			||||||
 | 
					    void fingerprintManufactureData(NimBLEAdvertisedDevice *advertisedDevice, bool haveTxPower, int8_t txPower);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										260
									
								
								components/esp32_presense/BleFingerprintCollection.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								components/esp32_presense/BleFingerprintCollection.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,260 @@
 | 
				
			|||||||
 | 
					#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 <sstream>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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<DeviceConfig> deviceConfigs;
 | 
				
			||||||
 | 
					std::vector<uint8_t *> irks;
 | 
				
			||||||
 | 
					std::vector<BleFingerprint *> 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<std::string>();
 | 
				
			||||||
 | 
					        if (alias != id) config.alias = alias;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (doc.containsKey("rssi@1m"))
 | 
				
			||||||
 | 
					        config.calRssi = doc["rssi@1m"].as<int8_t>();
 | 
				
			||||||
 | 
					    if (doc.containsKey("name"))
 | 
				
			||||||
 | 
					        config.name = doc["name"].as<std::string>();
 | 
				
			||||||
 | 
					    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<BleFingerprint *> GetCopy() {
 | 
				
			||||||
 | 
					    if (xSemaphoreTake(fingerprintMutex, MAX_WAIT) != pdTRUE)
 | 
				
			||||||
 | 
					        ESP_LOGE(TAG, "Couldn't take fingerprintMutex!");
 | 
				
			||||||
 | 
					    CleanupOldFingerprints();
 | 
				
			||||||
 | 
					    std::vector<BleFingerprint *> 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
 | 
				
			||||||
							
								
								
									
										54
									
								
								components/esp32_presense/BleFingerprintCollection.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								components/esp32_presense/BleFingerprintCollection.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <ArduinoJson.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "BleFingerprint.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ONE_EURO_FCMIN 1e-1f
 | 
				
			||||||
 | 
					#define ONE_EURO_BETA 1e-3f
 | 
				
			||||||
 | 
					#define ONE_EURO_DCUTOFF 5e-3f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef ALLOW_BLE_CONTROLLER_RESTART_AFTER_SECS
 | 
				
			||||||
 | 
					#define ALLOW_BLE_CONTROLLER_RESTART_AFTER_SECS 1800
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct DeviceConfig {
 | 
				
			||||||
 | 
					    std::string id;
 | 
				
			||||||
 | 
					    std::string alias;
 | 
				
			||||||
 | 
					    std::string name;
 | 
				
			||||||
 | 
					    int8_t calRssi = NO_RSSI;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace BleFingerprintCollection {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef std::function<void(bool)> TCallbackBool;
 | 
				
			||||||
 | 
					typedef std::function<void(BleFingerprint *)> TCallbackFingerprint;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Setup();
 | 
				
			||||||
 | 
					void ConnectToWifi();
 | 
				
			||||||
 | 
					bool Command(std::string &command, std::string &pay);
 | 
				
			||||||
 | 
					bool Config(std::string &id, std::string &json);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Close(BleFingerprint *f, bool close);
 | 
				
			||||||
 | 
					void Count(BleFingerprint *f, bool counting);
 | 
				
			||||||
 | 
					void Seen(BLEAdvertisedDevice *advertisedDevice);
 | 
				
			||||||
 | 
					BleFingerprint *GetFingerprint(BLEAdvertisedDevice *advertisedDevice);
 | 
				
			||||||
 | 
					void CleanupOldFingerprints();
 | 
				
			||||||
 | 
					const std::vector<BleFingerprint *> GetCopy();
 | 
				
			||||||
 | 
					bool FindDeviceConfig(const std::string &id, DeviceConfig &config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern TCallbackBool onSeen;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onAdd;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onDel;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onClose;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onLeft;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onCountAdd;
 | 
				
			||||||
 | 
					extern TCallbackFingerprint onCountDel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern std::string include, exclude, query, knownMacs, knownIrks, countIds;
 | 
				
			||||||
 | 
					extern float skipDistance, maxDistance, absorption, countEnter, countExit;
 | 
				
			||||||
 | 
					extern int8_t rxRefRssi, rxAdjRssi, txRefRssi;
 | 
				
			||||||
 | 
					extern int forgetMs, skipMs, countMs, requeryMs;
 | 
				
			||||||
 | 
					extern std::vector<DeviceConfig> deviceConfigs;
 | 
				
			||||||
 | 
					extern std::vector<uint8_t *> irks;
 | 
				
			||||||
 | 
					extern std::vector<BleFingerprint *> fingerprints;
 | 
				
			||||||
 | 
					}  // namespace BleFingerprintCollection
 | 
				
			||||||
							
								
								
									
										76
									
								
								components/esp32_presense/FilteredDistance.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								components/esp32_presense/FilteredDistance.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					#include "FilteredDistance.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cmath>
 | 
				
			||||||
 | 
					#include <numeric>
 | 
				
			||||||
 | 
					#include <vector>
 | 
				
			||||||
 | 
					#include "esphome/core/hal.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FilteredDistance::FilteredDistance(float minCutoff, float beta, float dcutoff)
 | 
				
			||||||
 | 
					    : minCutoff(minCutoff), beta(beta), dcutoff(dcutoff), x(0), dx(0), lastDist(0), lastTime(0), readIndex(0), total(0), totalSquared(0) {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FilteredDistance::initSpike(float dist) {
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < NUM_READINGS; i++) {
 | 
				
			||||||
 | 
					        readings[i] = dist;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    total = dist * NUM_READINGS;
 | 
				
			||||||
 | 
					    totalSquared = dist * dist * NUM_READINGS;  // Initialize sum of squared distances
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float FilteredDistance::removeSpike(float dist) {
 | 
				
			||||||
 | 
					    total -= readings[readIndex];                               // Subtract the last reading
 | 
				
			||||||
 | 
					    totalSquared -= readings[readIndex] * readings[readIndex];  // Subtract the square of the last reading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    readings[readIndex] = dist;                                 // Read the sensor
 | 
				
			||||||
 | 
					    total += readings[readIndex];                               // Add the reading to the total
 | 
				
			||||||
 | 
					    totalSquared += readings[readIndex] * readings[readIndex];  // Add the square of the reading
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    readIndex = (readIndex + 1) % NUM_READINGS;  // Advance to the next position in the array
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    auto average = total / static_cast<float>(NUM_READINGS);  // Calculate the average
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (std::fabs(dist - average) > SPIKE_THRESHOLD)
 | 
				
			||||||
 | 
					        return average;  // Spike detected, use the average as the filtered value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return dist;  // No spike, return the new value
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void FilteredDistance::addMeasurement(float dist) {
 | 
				
			||||||
 | 
					    const bool initialized = lastTime != 0;
 | 
				
			||||||
 | 
					    const unsigned long now = esphome::micros();
 | 
				
			||||||
 | 
					    const unsigned long elapsed = now - lastTime;
 | 
				
			||||||
 | 
					    lastTime = now;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!initialized) {
 | 
				
			||||||
 | 
					        x = dist;  // Set initial filter state to the first reading
 | 
				
			||||||
 | 
					        dx = 0;    // Initial derivative is unknown, so we set it to zero
 | 
				
			||||||
 | 
					        lastDist = dist;
 | 
				
			||||||
 | 
					        initSpike(dist);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        float dT = std::max(elapsed * 0.000001f, 0.05f);  // Convert microseconds to seconds, enforce a minimum dT
 | 
				
			||||||
 | 
					        const float alpha = getAlpha(minCutoff, dT);
 | 
				
			||||||
 | 
					        const float dAlpha = getAlpha(dcutoff, dT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dist = removeSpike(dist);
 | 
				
			||||||
 | 
					        x += alpha * (dist - x);
 | 
				
			||||||
 | 
					        dx = dAlpha * ((dist - lastDist) / dT);
 | 
				
			||||||
 | 
					        lastDist = x + beta * dx;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const float FilteredDistance::getDistance() const {
 | 
				
			||||||
 | 
					    return lastDist;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					float FilteredDistance::getAlpha(float cutoff, float dT) {
 | 
				
			||||||
 | 
					    float tau = 1.0f / (2 * M_PI * cutoff);
 | 
				
			||||||
 | 
					    return 1.0f / (1.0f + tau / dT);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const float FilteredDistance::getVariance() const {
 | 
				
			||||||
 | 
					    auto mean = total / static_cast<float>(NUM_READINGS);
 | 
				
			||||||
 | 
					    auto meanOfSquares = totalSquared / static_cast<float>(NUM_READINGS);
 | 
				
			||||||
 | 
					    auto variance = meanOfSquares - (mean * mean);  // Variance formula: E(X^2) - (E(X))^2
 | 
				
			||||||
 | 
					    if (variance < 0.0f) return 0.0f;
 | 
				
			||||||
 | 
					    return variance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										37
									
								
								components/esp32_presense/FilteredDistance.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								components/esp32_presense/FilteredDistance.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#ifndef FILTEREDDISTANCE_H
 | 
				
			||||||
 | 
					#define FILTEREDDISTANCE_H
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define SPIKE_THRESHOLD 1.0f  // Threshold for spike detection
 | 
				
			||||||
 | 
					#define NUM_READINGS 12       // Number of readings to keep track of
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FilteredDistance {
 | 
				
			||||||
 | 
					   private:
 | 
				
			||||||
 | 
					    float minCutoff;
 | 
				
			||||||
 | 
					    float beta;
 | 
				
			||||||
 | 
					    float dcutoff;
 | 
				
			||||||
 | 
					    float x, dx;
 | 
				
			||||||
 | 
					    float lastDist;
 | 
				
			||||||
 | 
					    unsigned long lastTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    float getAlpha(float cutoff, float dT);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    float readings[NUM_READINGS];  // Array to store readings
 | 
				
			||||||
 | 
					    int readIndex;                 // Current position in the array
 | 
				
			||||||
 | 
					    float total;                   // Total of the readings
 | 
				
			||||||
 | 
					    float totalSquared;            // Total of the squared readings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void initSpike(float dist);
 | 
				
			||||||
 | 
					    float removeSpike(float dist);
 | 
				
			||||||
 | 
					   public:
 | 
				
			||||||
 | 
					    FilteredDistance(float minCutoff = 1.0f, float beta = 0.0f, float dcutoff = 1.0f);
 | 
				
			||||||
 | 
					    void addMeasurement(float dist);
 | 
				
			||||||
 | 
					    const float getMedianDistance() const;
 | 
				
			||||||
 | 
					    const float getDistance() const;
 | 
				
			||||||
 | 
					    const float getVariance() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool hasValue() const { return lastTime != 0; }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif  // FILTEREDDISTANCE_H
 | 
				
			||||||
							
								
								
									
										138
									
								
								components/esp32_presense/MiFloraHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								components/esp32_presense/MiFloraHandler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
				
			|||||||
 | 
					#include "MiFloraHandler.h"
 | 
				
			||||||
 | 
					#include "NimBLEAttValue.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) {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "-- Can't read characteristics");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // read characteristic value
 | 
				
			||||||
 | 
					    NimBLEAttValue value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value = floraCharacteristic->readValue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (value.size() == 0) {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "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)["temperature"] = temperature;
 | 
				
			||||||
 | 
					    (*doc)["moisture"] = moisture;
 | 
				
			||||||
 | 
					    (*doc)["light"] = brightness;
 | 
				
			||||||
 | 
					    (*doc)["conductivity"] = conductivity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    floraService->deleteCharacteristics();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool readBatteryData(BLERemoteService* floraService, DynamicJsonDocument* doc) {
 | 
				
			||||||
 | 
					    BLERemoteCharacteristic* floraCharacteristic = nullptr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    floraCharacteristic = floraService->getCharacteristic(uuid_version_battery);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (floraCharacteristic == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "-- Can't read characteristics");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    NimBLEAttValue val;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val = floraCharacteristic->readValue();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (val.size() == 0) {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Reading Value failed");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    int8_t battery = val.c_str()[0];
 | 
				
			||||||
 | 
					    (*doc)["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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    esphome::delay(500);
 | 
				
			||||||
 | 
					    floraService->deleteCharacteristics();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void fillDeviceData(DynamicJsonDocument* doc, BleFingerprint* f) {
 | 
				
			||||||
 | 
					    (*doc)["id"] = f->getId();
 | 
				
			||||||
 | 
					    (*doc)["mac"] = f->getMac();
 | 
				
			||||||
 | 
					    (*doc)["rssi"] = f->getRssi();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool getFloraData(DynamicJsonDocument* doc, BLERemoteService* floraService, BleFingerprint* f) {
 | 
				
			||||||
 | 
					    // Force miFlora to data mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fillDeviceData(doc, f);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!MiFloraHandler::readBatteryData(floraService, doc))
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Failed reading battery data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (MiFloraHandler::forceFloraServiceDataMode(floraService)) {
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Failed to force data reading mode");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!MiFloraHandler::readSensorData(floraService, doc))
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Failed reading sensor data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool requestData(NimBLEClient* pClient, BleFingerprint* fingerprint)  // Getting mi flora data
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    DynamicJsonDocument document(256);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NimBLERemoteService* floraService = pClient->getService(serviceUUID);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (floraService == nullptr) {
 | 
				
			||||||
 | 
					        ESP_LOGD(TAG, "Getting MiFlora service failed");
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Retriving the actual data
 | 
				
			||||||
 | 
					    if (!getFloraData(&document, floraService, fingerprint))  // Getting flora data
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    std::string buf = std::string();
 | 
				
			||||||
 | 
					    serializeJson(document, buf);
 | 
				
			||||||
 | 
					    // Sending buffer over mqtt
 | 
				
			||||||
 | 
					    fingerprint->setReport(QueryReport{"miflora", buf});
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}  // namespace MiFloraHandler
 | 
				
			||||||
							
								
								
									
										15
									
								
								components/esp32_presense/MiFloraHandler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								components/esp32_presense/MiFloraHandler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <ArduinoJson.h>
 | 
				
			||||||
 | 
					#include "BleFingerprint.h"
 | 
				
			||||||
 | 
					#include "NimBLEClient.h"
 | 
				
			||||||
 | 
					#include "NimBLEDevice.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
 | 
				
			||||||
							
								
								
									
										24
									
								
								components/esp32_presense/NameModelHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								components/esp32_presense/NameModelHandler.cpp
									
									
									
									
									
										Normal 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(std::string("name:") + kebabify(sName).c_str(), ID_TYPE_QUERY_NAME, std::string(sName.c_str()))) {
 | 
				
			||||||
 | 
					            // ESP_LOGD("\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(std::string("apple:") + kebabify(sMdl).c_str(), ID_TYPE_QUERY_MODEL, std::string(sMdl.c_str()))) {
 | 
				
			||||||
 | 
					            // ESP_LOGD("\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
 | 
				
			||||||
							
								
								
									
										11
									
								
								components/esp32_presense/NameModelHandler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								components/esp32_presense/NameModelHandler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <NimBLEClient.h>
 | 
				
			||||||
 | 
					#include <NimBLEDevice.h>
 | 
				
			||||||
 | 
					#include <ArduinoJson.h>
 | 
				
			||||||
 | 
					#include <sstream>
 | 
				
			||||||
 | 
					#include "BleFingerprint.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace NameModelHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool requestData(NimBLEClient* pClient, BleFingerprint* fingerprint);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								components/esp32_presense/QueryReport.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								components/esp32_presense/QueryReport.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <NimBLEAddress.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class QueryReport {
 | 
				
			||||||
 | 
					public:
 | 
				
			||||||
 | 
					    QueryReport(const std::string& id, const std::string& payload) : id(id), payload(payload) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    std::string getId() const { return id; }
 | 
				
			||||||
 | 
					    std::string getPayload() const { return payload; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    void setId(const std::string& id) { this->id = id; }
 | 
				
			||||||
 | 
					    void setPayload(const std::string& payload) { this->payload = payload; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private:
 | 
				
			||||||
 | 
					    std::string id;
 | 
				
			||||||
 | 
					    std::string payload;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										48
									
								
								components/esp32_presense/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								components/esp32_presense/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					  CONF_ID,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from esphome.components import mqtt, nimble_tracker
 | 
				
			||||||
 | 
					from esphome.components.esp32 import add_idf_sdkconfig_option
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["mqtt"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# CONF_NIMBLE_ID = "esp32_nimble_mqtt_room"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONF_ROOM_KEY = 'room'
 | 
				
			||||||
 | 
					CONF_BASE_TOPIC_KEY = 'base_topic'
 | 
				
			||||||
 | 
					CONF_MAC_KEY = 'mac_addr'
 | 
				
			||||||
 | 
					CONF_MAX_DISTANCE = 'max_distance'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					esp32_nimble_tracker_ns = cg.esphome_ns.namespace("esp32_presense")
 | 
				
			||||||
 | 
					ESP32Presense = esp32_nimble_tracker_ns.class_(
 | 
				
			||||||
 | 
					    "ESP32Presense", cg.Component
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema({
 | 
				
			||||||
 | 
					  cv.GenerateID(): cv.declare_id(ESP32Presense),
 | 
				
			||||||
 | 
					  cv.Required(CONF_ROOM_KEY): cv.string,
 | 
				
			||||||
 | 
					  cv.Required(CONF_MAC_KEY): cv.All(cv.ensure_list(cv.string)),
 | 
				
			||||||
 | 
					  cv.Optional(CONF_MAX_DISTANCE, default=16.0): cv.float_,
 | 
				
			||||||
 | 
					  cv.Optional(CONF_BASE_TOPIC_KEY, default="esphome_presense"): cv.string,
 | 
				
			||||||
 | 
					}).extend(cv.COMPONENT_SCHEMA)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def to_code(config):
 | 
				
			||||||
 | 
					  var = cg.new_Pvariable(config[CONF_ID])
 | 
				
			||||||
 | 
					  await cg.register_component(var, config)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cg.add(var.set_room(config[CONF_ROOM_KEY]))
 | 
				
			||||||
 | 
					  # cg.add(var.set_base_topic(config[CONF_BASE_TOPIC_KEY]))
 | 
				
			||||||
 | 
					  # cg.add(var.set_addresses(config[CONF_MAC_KEY]))
 | 
				
			||||||
 | 
					  cg.add(var.set_max_distance(config[CONF_MAX_DISTANCE]))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
 | 
				
			||||||
 | 
					  add_idf_sdkconfig_option("CONFIG_BT_BLUEDROID_ENABLED", False)
 | 
				
			||||||
 | 
					  add_idf_sdkconfig_option("CONFIG_BT_NIMBLE_ENABLED", True)
 | 
				
			||||||
 | 
					  add_idf_sdkconfig_option("CONFIG_MBEDTLS_HARDWARE_AES", False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  cg.add_library("esp-nimble-cpp", repository="https://github.com/h2zero/esp-nimble-cpp#v1.4.1", version="v1.4.1")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # await nimble_tracker.device_listener_to_code(var, config)
 | 
				
			||||||
 | 
					  # await nimble_tracker.register_ble_device(var, config)
 | 
				
			||||||
							
								
								
									
										5
									
								
								components/esp32_presense/defaults.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								components/esp32_presense/defaults.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#define BLE_SCAN_INTERVAL 0x80
 | 
				
			||||||
 | 
					#define BLE_SCAN_WINDOW 0x80
 | 
				
			||||||
 | 
					#define SCAN_TASK_STACK_SIZE 2562
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CHANNEL "espresense"
 | 
				
			||||||
							
								
								
									
										193
									
								
								components/esp32_presense/esp32_presense.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								components/esp32_presense/esp32_presense.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,193 @@
 | 
				
			|||||||
 | 
					#include "esp32_presense.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace esp32_presense
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        unsigned int totalSeen = 0;
 | 
				
			||||||
 | 
					        unsigned int totalFpSeen = 0;
 | 
				
			||||||
 | 
					        unsigned int totalFpQueried = 0;
 | 
				
			||||||
 | 
					        unsigned int totalFpReported = 0;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        TimerHandle_t reconnectTimer;
 | 
				
			||||||
 | 
					        TaskHandle_t scanTaskHandle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        unsigned long updateStartedMillis = 0;
 | 
				
			||||||
 | 
					        unsigned long lastTeleMillis = 0;
 | 
				
			||||||
 | 
					        int reconnectTries = 0;
 | 
				
			||||||
 | 
					        int teleFails = 0;
 | 
				
			||||||
 | 
					        int reportFailed = 0;
 | 
				
			||||||
 | 
					        bool online = false;         // Have we successfully sent status=online
 | 
				
			||||||
 | 
					        bool sentDiscovery = false;  // Have we successfully sent discovery
 | 
				
			||||||
 | 
					        UBaseType_t bleStack = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        DynamicJsonDocument doc(1024);
 | 
				
			||||||
 | 
					        std::string _id, roomsTopic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool discovery, publishTele, publishRooms, publishDevices;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
 | 
				
			||||||
 | 
					            void onResult(BLEAdvertisedDevice *advertisedDevice) {
 | 
				
			||||||
 | 
					                bleStack = uxTaskGetStackHighWaterMark(nullptr);
 | 
				
			||||||
 | 
					                BleFingerprintCollection::Seen(advertisedDevice);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void scanTask(void *parameter) {
 | 
				
			||||||
 | 
					            NimBLEDevice::init("ESPresense");
 | 
				
			||||||
 | 
					            // Enrollment::Setup();
 | 
				
			||||||
 | 
					            NimBLEDevice::setMTU(23);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					            auto pBLEScan = NimBLEDevice::getScan();
 | 
				
			||||||
 | 
					            pBLEScan->setInterval(BLE_SCAN_INTERVAL);
 | 
				
			||||||
 | 
					            pBLEScan->setWindow(BLE_SCAN_WINDOW);
 | 
				
			||||||
 | 
					            pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), true);
 | 
				
			||||||
 | 
					            pBLEScan->setActiveScan(false);
 | 
				
			||||||
 | 
					            pBLEScan->setDuplicateFilter(false);
 | 
				
			||||||
 | 
					            pBLEScan->setMaxResults(0);
 | 
				
			||||||
 | 
					            if (!pBLEScan->start(0, nullptr, false))
 | 
				
			||||||
 | 
					                ESP_LOGE(TAG, "Error starting continuous ble scan");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            while (true) {
 | 
				
			||||||
 | 
					                for (auto &f : BleFingerprintCollection::fingerprints)
 | 
				
			||||||
 | 
					                    if (f->query())
 | 
				
			||||||
 | 
					                        totalFpQueried++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // Enrollment::Loop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!pBLEScan->isScanning()) {
 | 
				
			||||||
 | 
					                    if (!pBLEScan->start(0, nullptr, true))
 | 
				
			||||||
 | 
					                        ESP_LOGE(TAG, "Error re-starting continuous ble scan");
 | 
				
			||||||
 | 
					                    delay(3000);  // If we stopped scanning, don't query for 3 seconds in order for us to catch any missed broadcasts
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    delay(100);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void ESP32Presense::set_room(std::string room) { 
 | 
				
			||||||
 | 
					            _id = slugify(room);
 | 
				
			||||||
 | 
					            roomsTopic = std::string(CHANNEL) + std::string("/rooms/") + _id;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        void ESP32Presense::set_max_distance(float maxDistance) {
 | 
				
			||||||
 | 
					            BleFingerprintCollection::maxDistance = maxDistance;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void ESP32Presense::setup()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            BleFingerprintCollection::Setup();
 | 
				
			||||||
 | 
					            xTaskCreatePinnedToCore(scanTask, "scanTask", SCAN_TASK_STACK_SIZE, nullptr, 1, &scanTaskHandle, CONFIG_BT_NIMBLE_PINNED_TO_CORE);
 | 
				
			||||||
 | 
					            publishDevices = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void ESP32Presense::loop()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            reportLoop();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool ESP32Presense::reportBuffer(BleFingerprint *f) {
 | 
				
			||||||
 | 
					            auto report = f->getReport();
 | 
				
			||||||
 | 
					            std::string topic = Sprintf(CHANNEL "/devices/%s/%s/%s", f->getId().c_str(), _id.c_str(), report.getId().c_str());
 | 
				
			||||||
 | 
					            return this->publish(topic, report.getPayload());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void ESP32Presense::reportLoop() 
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto copy = BleFingerprintCollection::GetCopy();
 | 
				
			||||||
 | 
					            unsigned int count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (auto &i : copy)
 | 
				
			||||||
 | 
					                if (i->shouldCount())
 | 
				
			||||||
 | 
					                    count++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            yield();
 | 
				
			||||||
 | 
					            sendTelemetry(totalSeen, totalFpSeen, totalFpQueried, totalFpReported, count);
 | 
				
			||||||
 | 
					            yield();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto reported = 0;
 | 
				
			||||||
 | 
					            for (auto &f : copy) {
 | 
				
			||||||
 | 
					                auto seen = f->getSeenCount();
 | 
				
			||||||
 | 
					                if (seen) {
 | 
				
			||||||
 | 
					                    totalSeen += seen;
 | 
				
			||||||
 | 
					                    totalFpSeen++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (f->hasReport()) {
 | 
				
			||||||
 | 
					                    if (reportBuffer(f)) {
 | 
				
			||||||
 | 
					                        f->clearReport();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this->reportDevice(f)) {
 | 
				
			||||||
 | 
					                    totalFpReported++;
 | 
				
			||||||
 | 
					                    reported++;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool ESP32Presense::reportDevice(BleFingerprint *f) {
 | 
				
			||||||
 | 
					            doc.clear();
 | 
				
			||||||
 | 
					            JsonObject obj = doc.to<JsonObject>();
 | 
				
			||||||
 | 
					            if (!f->report(&obj))
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            std::string buffer;
 | 
				
			||||||
 | 
					            serializeJson(doc, buffer);
 | 
				
			||||||
 | 
					            std::string devicesTopic = Sprintf(CHANNEL "/devices/%s/%s", f->getId().c_str(), _id.c_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            bool p1 = false, p2 = false;
 | 
				
			||||||
 | 
					            for (int i = 0; i < 10; i++) {
 | 
				
			||||||
 | 
					                if (!p1 && (!publishRooms || this->publish(roomsTopic.c_str(), buffer.c_str())))
 | 
				
			||||||
 | 
					                p1 = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!p2 && (!publishDevices || this->publish(devicesTopic.c_str(), buffer.c_str())))
 | 
				
			||||||
 | 
					                    p2 = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (p1 && p2)
 | 
				
			||||||
 | 
					                    return true;
 | 
				
			||||||
 | 
					                delay(20);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            reportFailed++;
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        bool ESP32Presense::sendTelemetry(
 | 
				
			||||||
 | 
					            unsigned int totalSeen, 
 | 
				
			||||||
 | 
					            unsigned int totalFpSeen, 
 | 
				
			||||||
 | 
					            unsigned int totalFpQueried, 
 | 
				
			||||||
 | 
					            unsigned int totalFpReported, 
 | 
				
			||||||
 | 
					            unsigned int count
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/status", "online");
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/max_distance", BleFingerprintCollection::maxDistance);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/absorption", BleFingerprintCollection::absorption);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/tx_ref_rssi", BleFingerprintCollection::txRefRssi);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/rx_adj_rssi", BleFingerprintCollection::rxAdjRssi);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/query", BleFingerprintCollection::query);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/include", BleFingerprintCollection::include);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/exclude", BleFingerprintCollection::exclude);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/known_macs", BleFingerprintCollection::knownMacs);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/known_irks", BleFingerprintCollection::knownIrks);
 | 
				
			||||||
 | 
					            this->publish(roomsTopic + "/count_ids", BleFingerprintCollection::countIds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        /*
 | 
				
			||||||
 | 
					        void ESP32NimbleMQTTRoom::on_result(nimble_distance_custom::NimbleDistanceCustomResult& result)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto address = result.address.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this->publish_json(
 | 
				
			||||||
 | 
					                this->base_topic_ + "/devices/" + address + "/" + this->room_,
 | 
				
			||||||
 | 
					                [=](ArduinoJson::JsonObject root) -> void {
 | 
				
			||||||
 | 
					                    root["id"] = address;
 | 
				
			||||||
 | 
					                    root["distance"] = result.distance;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        */
 | 
				
			||||||
 | 
					    } // namespace esp32_nimble_tracker
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
							
								
								
									
										41
									
								
								components/esp32_presense/esp32_presense.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								components/esp32_presense/esp32_presense.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <ArduinoJson.h>
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/mqtt/custom_mqtt_device.h"
 | 
				
			||||||
 | 
					#include "BleFingerprintCollection.h"
 | 
				
			||||||
 | 
					#include "defaults.h"
 | 
				
			||||||
 | 
					// #include "esphome/components/nimble_distance_custom/nimble_distance_custom.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace esp32_presense
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        class ESP32Presense :
 | 
				
			||||||
 | 
					            public Component,
 | 
				
			||||||
 | 
					            public mqtt::CustomMQTTDevice 
 | 
				
			||||||
 | 
					            // public nimble_distance_custom::NimbleDistanceCustomComponent
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            protected:
 | 
				
			||||||
 | 
					                BleFingerprint *ble_fingerprint = NULL;
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					                bool sendTelemetry(
 | 
				
			||||||
 | 
					                    unsigned int totalSeen, 
 | 
				
			||||||
 | 
					                    unsigned int totalFpSeen, 
 | 
				
			||||||
 | 
					                    unsigned int totalFpQueried, 
 | 
				
			||||||
 | 
					                    unsigned int totalFpReported, 
 | 
				
			||||||
 | 
					                    unsigned int count
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            public:
 | 
				
			||||||
 | 
					                void setup() override;
 | 
				
			||||||
 | 
					                void loop() override;
 | 
				
			||||||
 | 
					                void reportLoop();
 | 
				
			||||||
 | 
					                // void on_result(nimble_distance_custom::NimbleDistanceCustomResult&) override;
 | 
				
			||||||
 | 
					                void set_room(std::string room);
 | 
				
			||||||
 | 
					                bool reportBuffer(BleFingerprint *f);
 | 
				
			||||||
 | 
					                bool reportDevice(BleFingerprint *f);
 | 
				
			||||||
 | 
					                void set_max_distance(float maxDistance);
 | 
				
			||||||
 | 
					                //void set_base_topic(std::string base_topic) { base_topic_ = base_topic; }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    } // namespace esp32_nimble_tracker
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
							
								
								
									
										19
									
								
								components/esp32_presense/rssi.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								components/esp32_presense/rssi.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#ifndef _RSSI_
 | 
				
			||||||
 | 
					#define _RSSI_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CLOSE_RSSI int8_t(-40)
 | 
				
			||||||
 | 
					#define LEFT_RSSI int8_t(-50)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define DEFAULT_TX int8_t(-6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define APPLE_TX int8_t(0)
 | 
				
			||||||
 | 
					#define RM_ASST_TX int8_t(0)
 | 
				
			||||||
 | 
					#define TILE_TX int8_t(-4)
 | 
				
			||||||
 | 
					#define EXPOSURE_TX int8_t(-12)
 | 
				
			||||||
 | 
					#define ITAG_TX int8_t(-10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NUT_TX int8_t(-12)
 | 
				
			||||||
 | 
					#define FLORA_TX int8_t(-10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define EDDYSTONE_ADD_1M int8_t(-41)
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										134
									
								
								components/esp32_presense/string_utils.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										134
									
								
								components/esp32_presense/string_utils.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,134 @@
 | 
				
			|||||||
 | 
					#include <regex>
 | 
				
			||||||
 | 
					#include "string_utils.h"
 | 
				
			||||||
 | 
					// #include <SPIFFS.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static constexpr char hexmap[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string ltrim(const std::string &s, char toTrim)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    size_t start = s.find_first_not_of(toTrim);
 | 
				
			||||||
 | 
					    return (start == std::string::npos) ? "" : s.substr(start);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string rtrim(const std::string &s, char toTrim)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    size_t end = s.find_last_not_of(toTrim);
 | 
				
			||||||
 | 
					    return (end == std::string::npos) ? "" : s.substr(0, end + 1);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string lowertrim(std::string str, char toTrim)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c)
 | 
				
			||||||
 | 
					                   { return std::tolower(c); });
 | 
				
			||||||
 | 
					    return rtrim(ltrim(str, toTrim), toTrim);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::regex whitespace_re("[\\s\\W-]+");
 | 
				
			||||||
 | 
					std::string slugify(const std::string &text)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return lowertrim(std::regex_replace(text, whitespace_re, "_"), '_');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string kebabify(const std::string &text)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return lowertrim(std::regex_replace(text, whitespace_re, "-"), '-');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStr(const uint8_t *data, int len) {
 | 
				
			||||||
 | 
					    std::string s(len * 2, ' ');
 | 
				
			||||||
 | 
					    for (int i = 0; i < len; ++i) {
 | 
				
			||||||
 | 
					        s[2 * i] = hexmap[(data[i] & 0xF0) >> 4];
 | 
				
			||||||
 | 
					        s[2 * i + 1] = hexmap[data[i] & 0x0F];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStr(const char *data, unsigned int len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::string s(len * 2, ' ');
 | 
				
			||||||
 | 
					    for (int i = 0; i < len; ++i)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        s[2 * i] = hexmap[(data[i] & 0xF0) >> 4];
 | 
				
			||||||
 | 
					        s[2 * i + 1] = hexmap[data[i] & 0x0F];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStr(const std::string &s)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return hexStr(s.c_str(), s.length());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStr(const uint8_t *&s, unsigned int len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return hexStr(reinterpret_cast<const char *>(s), len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStrRev(const char *data, unsigned int len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    std::string s(len * 2, ' ');
 | 
				
			||||||
 | 
					    for (int i = 0; i < len; ++i)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        s[len - (2 * i + 1)] = hexmap[(data[i] & 0xF0) >> 4];
 | 
				
			||||||
 | 
					        s[len - (2 * i + 2)] = hexmap[data[i] & 0x0F];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStrRev(const uint8_t *&s, unsigned int len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return hexStrRev(reinterpret_cast<const char *>(s), len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string hexStrRev(const std::string &s)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return hexStrRev(s.c_str(), s.length());
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					uint8_t hextob(char ch)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (ch >= '0' && ch <= '9') return ch - '0';
 | 
				
			||||||
 | 
					    if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
 | 
				
			||||||
 | 
					    if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool hextostr(const std::string &hexStr, uint8_t* output, size_t len)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    if (hexStr.length() & 1) return false;
 | 
				
			||||||
 | 
					    if (hexStr.length() < len*2) return false;
 | 
				
			||||||
 | 
					    int k = 0;
 | 
				
			||||||
 | 
					    for (size_t i = 0; i < len*2; i+=2)
 | 
				
			||||||
 | 
					        output[k++] = (hextob(hexStr[i]) << 4) | hextob(hexStr[i+1]);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool prefixExists(const std::string &prefixes, const std::string &s)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    unsigned int start = 0;
 | 
				
			||||||
 | 
					    unsigned int space;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while ((space = prefixes.find(" ", start)) != -1)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (space > start)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            auto sub = prefixes.substr(start, space);
 | 
				
			||||||
 | 
					            if (s.find(sub) == 0) return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        start = space + 1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    auto sub = prefixes.substr(start);
 | 
				
			||||||
 | 
					    return !(sub.length() == 0) && s.find(sub) == 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool spurt(const std::string &fn, const std::string &content)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    /*
 | 
				
			||||||
 | 
					    File f = SPIFFS.open(fn, "w");
 | 
				
			||||||
 | 
					    if (!f) return false;
 | 
				
			||||||
 | 
					    auto w = f.print(content);
 | 
				
			||||||
 | 
					    f.close();
 | 
				
			||||||
 | 
					    return w == content.length();
 | 
				
			||||||
 | 
					    */
 | 
				
			||||||
 | 
					   return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										20
									
								
								components/esp32_presense/string_utils.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								components/esp32_presense/string_utils.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					#include <cstring>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define CHIPID (uint32_t)(ESP.getEfuseMac() >> 24)
 | 
				
			||||||
 | 
					#define ESPMAC (Sprintf("%06x", CHIPID))
 | 
				
			||||||
 | 
					#define Sprintf(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; })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					std::string slugify(const std::string& text);
 | 
				
			||||||
 | 
					std::string kebabify(const std::string& text);
 | 
				
			||||||
 | 
					std::string hexStr(const uint8_t *data, int len);
 | 
				
			||||||
 | 
					std::string hexStr(const char *data, int len);
 | 
				
			||||||
 | 
					std::string hexStr(const std::string& s);
 | 
				
			||||||
 | 
					std::string hexStrRev(const uint8_t *data, int len);
 | 
				
			||||||
 | 
					std::string hexStrRev(const char *data, int len);
 | 
				
			||||||
 | 
					std::string hexStrRev(const std::string &s);
 | 
				
			||||||
 | 
					bool hextostr(const std::string &hexStr, uint8_t* output, size_t len);
 | 
				
			||||||
 | 
					bool prefixExists(const std::string &prefixes, const std::string &s);
 | 
				
			||||||
 | 
					bool spurt(const std::string &fn, const std::string &content);
 | 
				
			||||||
							
								
								
									
										37
									
								
								components/esp32_presense/util.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								components/esp32_presense/util.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					#include <NimBLEBeacon.h>
 | 
				
			||||||
 | 
					#include <NimBLEDevice.h>
 | 
				
			||||||
 | 
					#include <string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00) >> 8) + (((x)&0xFF) << 8))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID eddystoneUUID((uint16_t)0xFEAA);
 | 
				
			||||||
 | 
					const BLEUUID tileUUID((uint16_t)0xFEED);
 | 
				
			||||||
 | 
					const BLEUUID exposureUUID((uint16_t)0xFD6F);
 | 
				
			||||||
 | 
					const BLEUUID smartTagUUID((uint16_t)0xFD5A);
 | 
				
			||||||
 | 
					const BLEUUID sonosUUID((uint16_t)0xFE07);
 | 
				
			||||||
 | 
					const BLEUUID itagUUID((uint16_t)0xffe0);
 | 
				
			||||||
 | 
					const BLEUUID miThermUUID(uint16_t(0x181A));
 | 
				
			||||||
 | 
					const BLEUUID trackrUUID((uint16_t)0x0F3E);
 | 
				
			||||||
 | 
					const BLEUUID vanmoofUUID(0x6acc5540, 0xe631, 0x4069, 0x944db8ca7598ad50);
 | 
				
			||||||
 | 
					const BLEUUID tractiveUUID(0x20130001, 0x0719, 0x4b6e, 0xbe5d158ab92fa5a4);
 | 
				
			||||||
 | 
					const BLEUUID espresenseUUID(0xe5ca1ade, 0xf007, 0xba11, 0x0000000000000000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID nutUUID((uint16_t)0x1803);
 | 
				
			||||||
 | 
					const BLEUUID miFloraUUID((uint16_t)0xfe95);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID fitbitUUID(0xadabfb00, 0x6e7d, 0x4601, 0xbda2bffaa68956ba);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID roomAssistantService(0x5403c8a7, 0x5c96, 0x47e9, 0x9ab859e373d875a7);
 | 
				
			||||||
 | 
					const BLEUUID rootAssistantCharacteristic(0x21c46f33, 0xe813, 0x4407, 0x86012ad281030052);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID meaterService(0xa75cc7fc, 0xc956, 0x488f, 0xac2a2dbc08b63a04);
 | 
				
			||||||
 | 
					const BLEUUID meaterCharacteristic(0x7edda774, 0x045e, 0x4bbf, 0x909b45d1991a2876);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID genericAccessService(uint16_t(0x1800));
 | 
				
			||||||
 | 
					const BLEUUID nameChar(uint16_t(0x2A00));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const BLEUUID deviceInformationService(uint16_t(0x180A));
 | 
				
			||||||
 | 
					const BLEUUID modelChar(uint16_t(0x2A24));
 | 
				
			||||||
 | 
					const BLEUUID fwRevChar(uint16_t(0x2A26));
 | 
				
			||||||
 | 
					const BLEUUID hwRevChar(uint16_t(0x2A27));
 | 
				
			||||||
 | 
					const BLEUUID manufChar(uint16_t(0x2A29));
 | 
				
			||||||
							
								
								
									
										21
									
								
								components/nimble_distance_custom/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								components/nimble_distance_custom/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import esphome.codegen as cg
 | 
				
			||||||
 | 
					import esphome.config_validation as cv
 | 
				
			||||||
 | 
					from esphome.components import nimble_tracker
 | 
				
			||||||
 | 
					from esphome.const import (
 | 
				
			||||||
 | 
					    DEVICE_CLASS_DISTANCE,
 | 
				
			||||||
 | 
					    STATE_CLASS_MEASUREMENT,
 | 
				
			||||||
 | 
					    UNIT_METER,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					DEPENDENCIES = ["nimble_tracker"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					nimble_custom_distance_ns = cg.esphome_ns.namespace("nimble_custom_distance")
 | 
				
			||||||
 | 
					NimbleDistanceCustomComponent = nimble_custom_distance_ns.class_(
 | 
				
			||||||
 | 
					    "NimbleDistanceCustomComponent",
 | 
				
			||||||
 | 
					    cg.Component,
 | 
				
			||||||
 | 
					    nimble_tracker.NimbleDeviceListener,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CONFIG_SCHEMA = cv.Schema({
 | 
				
			||||||
 | 
					  cv.GenerateID(): cv.declare_id(NimbleDistanceCustomComponent),  
 | 
				
			||||||
 | 
					}).extend(cv.COMPONENT_SCHEMA).extend(nimble_tracker.NIMBLE_DEVICE_LISTENER_SCHEMA)
 | 
				
			||||||
							
								
								
									
										95
									
								
								components/nimble_distance_custom/nimble_distance_custom.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								components/nimble_distance_custom/nimble_distance_custom.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,95 @@
 | 
				
			|||||||
 | 
					#include "nimble_distance_custom.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace nimble_distance_custom
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        static const char *const TAG = "nimble_distance_custom";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        static int median_of_3(int a, int b, int c)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            int the_max = std::max(std::max(a, b), c);
 | 
				
			||||||
 | 
					            int the_min = std::min(std::min(a, b), c);
 | 
				
			||||||
 | 
					            // unnecessarily clever code
 | 
				
			||||||
 | 
					            int the_median = the_max ^ the_min ^ a ^ b ^ c;
 | 
				
			||||||
 | 
					            return (the_median);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        int NimbleDistanceCustomComponent::get_1m_rssi(nimble_tracker::NimbleTrackerEvent *tracker_event)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return this->ref_rssi_; //+ tracker_event->getTXPower() + 99;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Filter::Filter(float fcmin, float beta, float dcutoff) : one_euro_{OneEuroFilter<float, unsigned long>(1, fcmin, beta, dcutoff)}
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool Filter::filter(float rssi)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Reading<float, unsigned long> inter1, inter2;
 | 
				
			||||||
 | 
					            // TODO: should we take into consideration micro seconds (returned from esp_timer_get_time())
 | 
				
			||||||
 | 
					            // vs mili seconds (implementation used in ESPresence?)
 | 
				
			||||||
 | 
					            inter1.timestamp = esp_timer_get_time();
 | 
				
			||||||
 | 
					            inter1.value = rssi;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return this->one_euro_.push(&inter1, &inter2) && this->diff_filter_.push(&inter2, &this->output);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void NimbleDistanceCustomComponent::setup()
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this->filter_ = new Filter(ONE_EURO_FCMIN, ONE_EURO_BETA, ONE_EURO_DCUTOFF);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        void NimbleDistanceCustomComponent::set_max_distance(float max_distance) {
 | 
				
			||||||
 | 
					            this->max_distance_ = max_distance;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Defined distance formula using
 | 
				
			||||||
 | 
					        // https://medium.com/beingcoders/convert-rssi-value-of-the-ble-bluetooth-low-energy-beacons-to-meters-63259f307283
 | 
				
			||||||
 | 
					        // and copied a lot of code from
 | 
				
			||||||
 | 
					        // https://github.com/ESPresense/ESPresense/blob/master/lib/BleFingerprint/BleFingerprint.cpp
 | 
				
			||||||
 | 
					        bool NimbleDistanceCustomComponent::update_state(nimble_tracker::NimbleTrackerEvent *tracker_event)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this->oldest_ = this->recent_;
 | 
				
			||||||
 | 
					            this->recent_ = this->newest_;
 | 
				
			||||||
 | 
					            this->newest_ = tracker_event->getRSSI();
 | 
				
			||||||
 | 
					            this->rssi_ = median_of_3(this->oldest_, this->recent_, this->newest_);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            float ratio = (this->get_1m_rssi(tracker_event) - this->rssi_) / (10.0f * this->absorption_);
 | 
				
			||||||
 | 
					            float raw = std::pow(10, ratio);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!this->filter_->filter(raw))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                ESP_LOGD(TAG, "Not enough data to calculate distance.");
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (this->max_distance_ > 0 && this->filter_->output.value.position > this->max_distance_)
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto skip_distance = 0.5f;
 | 
				
			||||||
 | 
					            auto skip_ms = 5000;
 | 
				
			||||||
 | 
					            auto skip_micro_seconds = skip_ms * 1000;
 | 
				
			||||||
 | 
					            auto now = esp_timer_get_time();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ((abs(this->filter_->output.value.position - this->last_reported_position_) < skip_distance) && (this->last_reported_micro_seconds_ > 0) && ((now - this->last_reported_micro_seconds_) < skip_micro_seconds))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this->last_reported_micro_seconds_ = now;
 | 
				
			||||||
 | 
					            this->last_reported_position_ = this->filter_->output.value.position;
 | 
				
			||||||
 | 
					            // /this->publish_state(this->filter_->output.value.position);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            auto res = NimbleDistanceCustomResult{
 | 
				
			||||||
 | 
					                tracker_event->getAddress(),
 | 
				
			||||||
 | 
					                this->filter_->output.value.position
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this->on_result(res);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } // namespace nimble_distance
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
@@ -18,12 +18,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// For NimbleDistanceSensor
 | 
					// For NimbleDistanceSensor
 | 
				
			||||||
#include "esphome/core/component.h"
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
#include "esphome/components/sensor/sensor.h"
 | 
					 | 
				
			||||||
#include "esphome/components/nimble_tracker/nimble_tracker.h"
 | 
					#include "esphome/components/nimble_tracker/nimble_tracker.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace esphome
 | 
					namespace esphome
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    namespace nimble_distance
 | 
					    namespace nimble_distance_custom
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        class Filter
 | 
					        class Filter
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@@ -37,23 +36,33 @@ namespace esphome
 | 
				
			|||||||
            DifferentialFilter<float, unsigned long> diff_filter_;
 | 
					            DifferentialFilter<float, unsigned long> diff_filter_;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        class NimbleDistanceSensor : public sensor::Sensor,
 | 
					        typedef struct
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            NimBLEAddress address;
 | 
				
			||||||
 | 
					            float distance;
 | 
				
			||||||
 | 
					        } NimbleDistanceCustomResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        class NimbleDistanceCustomComponent:
 | 
				
			||||||
                                     public Component,
 | 
					                                     public Component,
 | 
				
			||||||
                                     public nimble_tracker::NimbleDeviceListener
 | 
					                                     public nimble_tracker::NimbleDeviceListener
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
        public:
 | 
					        public:
 | 
				
			||||||
            void setup() override;
 | 
					            void setup() override;
 | 
				
			||||||
            int get_1m_rssi(nimble_tracker::NimbleTrackerEvent *tracker_event);
 | 
					            int get_1m_rssi(nimble_tracker::NimbleTrackerEvent *tracker_event);
 | 
				
			||||||
 | 
					            void set_max_distance(float);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected:
 | 
					        protected:
 | 
				
			||||||
            bool update_state(nimble_tracker::NimbleTrackerEvent *tracker_event) override;
 | 
					            bool update_state(nimble_tracker::NimbleTrackerEvent *tracker_event) override;
 | 
				
			||||||
 | 
					            virtual void on_result(NimbleDistanceCustomResult& result);
 | 
				
			||||||
            Filter *filter_;
 | 
					            Filter *filter_;
 | 
				
			||||||
            int rssi_ = NO_RSSI, newest_ = NO_RSSI, recent_ = NO_RSSI, oldest_ = NO_RSSI;
 | 
					            int rssi_ = NO_RSSI, newest_ = NO_RSSI, recent_ = NO_RSSI, oldest_ = NO_RSSI;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            int8_t ref_rssi_ = -65;
 | 
					            int8_t ref_rssi_ = -75;
 | 
				
			||||||
            float absorption_ = 3.5f;
 | 
					            float absorption_ = 3.5f;
 | 
				
			||||||
            float last_reported_position_ = 0;
 | 
					            float last_reported_position_ = 0;
 | 
				
			||||||
            int64_t last_reported_micro_seconds_ = 0;
 | 
					            int64_t last_reported_micro_seconds_ = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            float max_distance_ = 16.0;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    } // namespace nimble_distance
 | 
					    } // namespace nimble_distance
 | 
				
			||||||
} // namespace esphome
 | 
					} // namespace esphome
 | 
				
			||||||
@@ -111,4 +111,4 @@ async def device_listener_to_code(var, config):
 | 
				
			|||||||
    if CONF_IRK in config:
 | 
					    if CONF_IRK in config:
 | 
				
			||||||
        cg.add(var.set_irk(config[CONF_IRK]))
 | 
					        cg.add(var.set_irk(config[CONF_IRK]))
 | 
				
			||||||
    if CONF_MAC in config:
 | 
					    if CONF_MAC in config:
 | 
				
			||||||
        cg.add(var.set_irk(config[CONF_MAC]))
 | 
					        cg.add(var.set_address(config[CONF_MAC]))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,13 +19,15 @@ namespace esphome
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void NimbleDeviceListener::set_address(std::string address) {
 | 
					        void NimbleDeviceListener::set_addresses(std::vector<std::string> addresses) {
 | 
				
			||||||
            this->match_by_ = MATCH_BY_ADDRESS;
 | 
					            this->match_by_ = MATCH_BY_ADDRESS;
 | 
				
			||||||
            this->address_ = address;
 | 
					            this->addresses_ = addresses;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        bool NimbleDeviceListener::parse_event(NimbleTrackerEvent *tracker_event)
 | 
					        bool NimbleDeviceListener::parse_event(NimbleTrackerEvent *tracker_event)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
 | 
					            // ESP_LOGD(TAG, "Found device %s", tracker_event->toString().c_str());
 | 
				
			||||||
 | 
					            ESP_LOGD(TAG, "%d", this->match_by_);
 | 
				
			||||||
            if (this->match_by_ == MATCH_BY_IRK) {
 | 
					            if (this->match_by_ == MATCH_BY_IRK) {
 | 
				
			||||||
                if (tracker_event->getAddressType() != BLE_ADDR_RANDOM)
 | 
					                if (tracker_event->getAddressType() != BLE_ADDR_RANDOM)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -44,15 +46,21 @@ namespace esphome
 | 
				
			|||||||
                {
 | 
					                {
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } else if (this->match_by_ == MATCH_BY_ADDRESS) {
 | 
					            }
 | 
				
			||||||
                auto address = tracker_event->getAddress();
 | 
					            if (this->match_by_ == MATCH_BY_ADDRESS) {
 | 
				
			||||||
 | 
					                ESP_LOGD(TAG, "Found device %s", tracker_event->toString().c_str());
 | 
				
			||||||
 | 
					                auto address = tracker_event->getAddress().toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (this.address_ == address) {
 | 
					                auto &v = this->addresses_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if(std::find(v.begin(), v.end(), address) != v.end()) {
 | 
				
			||||||
                    return this->update_state(tracker_event);
 | 
					                    return this->update_state(tracker_event);
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    return false;
 | 
					                    return false;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return false;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    } // namespace nimble_tracker
 | 
					    } // namespace nimble_tracker
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ namespace esphome
 | 
				
			|||||||
        public:
 | 
					        public:
 | 
				
			||||||
            bool parse_event(NimbleTrackerEvent *tracker_event);
 | 
					            bool parse_event(NimbleTrackerEvent *tracker_event);
 | 
				
			||||||
            void set_irk(std::string irk_hex);
 | 
					            void set_irk(std::string irk_hex);
 | 
				
			||||||
            void set_address(std::stding address);
 | 
					            void set_addresses(std::vector<std::string>);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected:
 | 
					        protected:
 | 
				
			||||||
            virtual bool update_state(NimbleTrackerEvent *tracker_event) = 0;
 | 
					            virtual bool update_state(NimbleTrackerEvent *tracker_event) = 0;
 | 
				
			||||||
@@ -22,12 +22,12 @@ namespace esphome
 | 
				
			|||||||
            enum MatchType
 | 
					            enum MatchType
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                MATCH_BY_IRK,
 | 
					                MATCH_BY_IRK,
 | 
				
			||||||
                MATCH_BY_ADDRESS;
 | 
					                MATCH_BY_ADDRESS,
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            MatchType match_by_;
 | 
					            MatchType match_by_;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            uint8_t *irk_;
 | 
					            uint8_t *irk_;
 | 
				
			||||||
            std::string address_;
 | 
					            std::vector<std::string> addresses_;
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    } // namespace nimble_tracker
 | 
					    } // namespace nimble_tracker
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,7 +57,7 @@ namespace esphome
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // TODO: It takes some time to setup bluetooth? Why not just move this to loop?
 | 
					            // TODO: It takes some time to setup bluetooth? Why not just move this to loop?
 | 
				
			||||||
            delay(200);
 | 
					            esphome::delay(200);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        void NimbleTracker::loop()
 | 
					        void NimbleTracker::loop()
 | 
				
			||||||
@@ -71,7 +71,7 @@ namespace esphome
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // TODO: we shouldn't block the main thread here, instead work with a setTimeout callback?
 | 
					                // TODO: we shouldn't block the main thread here, instead work with a setTimeout callback?
 | 
				
			||||||
                delay(200);
 | 
					                esphome::delay(200);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            NimbleTrackerEvent *tracker_event = this->tracker_events_.pop();
 | 
					            NimbleTrackerEvent *tracker_event = this->tracker_events_.pop();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ namespace esphome
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        int NimbleDistanceSensor::get_1m_rssi(nimble_tracker::NimbleTrackerEvent *tracker_event)
 | 
					        int NimbleDistanceSensor::get_1m_rssi(nimble_tracker::NimbleTrackerEvent *tracker_event)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            return this->ref_rssi_ + tracker_event->getTXPower();
 | 
					            return this->ref_rssi_; //+ tracker_event->getTXPower() + 99;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Filter::Filter(float fcmin, float beta, float dcutoff) : one_euro_{OneEuroFilter<float, unsigned long>(1, fcmin, beta, dcutoff)}
 | 
					        Filter::Filter(float fcmin, float beta, float dcutoff) : one_euro_{OneEuroFilter<float, unsigned long>(1, fcmin, beta, dcutoff)}
 | 
				
			||||||
							
								
								
									
										31
									
								
								nimble_distance/nimble_distance_sensor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								nimble_distance/nimble_distance_sensor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// For Filter
 | 
				
			||||||
 | 
					#include <cstddef>
 | 
				
			||||||
 | 
					#include "esp_timer.h"
 | 
				
			||||||
 | 
					#include "SoftFilters.h"
 | 
				
			||||||
 | 
					// #define ONE_EURO_FCMIN 1e-5f
 | 
				
			||||||
 | 
					// #define ONE_EURO_BETA 1e-7f
 | 
				
			||||||
 | 
					// #define ONE_EURO_DCUTOFF 1e-5f
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// From https://github.com/rpatel3001/BleDistance/blob/master/ble_dist.h
 | 
				
			||||||
 | 
					#define ONE_EURO_FCMIN 0.0001
 | 
				
			||||||
 | 
					#define ONE_EURO_BETA 0.05
 | 
				
			||||||
 | 
					#define ONE_EURO_DCUTOFF 1.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NO_RSSI (-128)
 | 
				
			||||||
 | 
					#define DEFAULT_TX (-6)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// For NimbleDistanceSensor
 | 
				
			||||||
 | 
					#include "esphome/core/component.h"
 | 
				
			||||||
 | 
					#include "esphome/components/sensor/sensor.h"
 | 
				
			||||||
 | 
					#include "esphome.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					namespace esphome
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    namespace nimble_distance
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        class NimbleDistanceSensor 
 | 
				
			||||||
 | 
					            : public sensor::Sensor,
 | 
				
			||||||
 | 
					            public nimble_tracker::NimbleDistanceCustomComponent {}
 | 
				
			||||||
 | 
					} // namespace esphome
 | 
				
			||||||
@@ -7,7 +7,7 @@ from esphome.const import (
 | 
				
			|||||||
    UNIT_METER,
 | 
					    UNIT_METER,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDENCIES = ["nimble_tracker"]
 | 
					# DEPENDENCIES = ["nimble_custom_component"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nimble_distance_ns = cg.esphome_ns.namespace("nimble_distance")
 | 
					nimble_distance_ns = cg.esphome_ns.namespace("nimble_distance")
 | 
				
			||||||
NimbleDistanceSensor = nimble_distance_ns.class_(
 | 
					NimbleDistanceSensor = nimble_distance_ns.class_(
 | 
				
			||||||
@@ -1,19 +1,20 @@
 | 
				
			|||||||
substitutions:
 | 
					 | 
				
			||||||
  device_name: "nimble-shelly"
 | 
					 | 
				
			||||||
  entity_id: "nimble_shelly"
 | 
					 | 
				
			||||||
  room_name: "office"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
mqtt:
 | 
					 | 
				
			||||||
  broker: !secret mqtt_broker
 | 
					 | 
				
			||||||
  username: esphome
 | 
					 | 
				
			||||||
  password: !secret mqtt_password
 | 
					 | 
				
			||||||
  discovery: false
 | 
					 | 
				
			||||||
  id: mqtt_client
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
external_components:
 | 
					external_components:
 | 
				
			||||||
  - source:
 | 
					  - source:
 | 
				
			||||||
      type: local
 | 
					      type: local
 | 
				
			||||||
      path: ./components
 | 
					      path: components
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mqtt:
 | 
				
			||||||
 | 
					  broker: !secret mqtt_broker
 | 
				
			||||||
 | 
					  username: !secret mqtt_username
 | 
				
			||||||
 | 
					  password: !secret mqtt_password
 | 
				
			||||||
 | 
					  discovery: false
 | 
				
			||||||
 | 
					  id: mqtt_client
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					globals:
 | 
				
			||||||
 | 
					  - id: room_topic
 | 
				
			||||||
 | 
					    type: std::string
 | 
				
			||||||
 | 
					    initial_value: '"room_presence/${room_name}"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
nimble_tracker:
 | 
					nimble_tracker:
 | 
				
			||||||
  scan_parameters:
 | 
					  scan_parameters:
 | 
				
			||||||
@@ -24,32 +25,20 @@ nimble_tracker:
 | 
				
			|||||||
    interval: 100ms
 | 
					    interval: 100ms
 | 
				
			||||||
    active: false
 | 
					    active: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
globals:
 | 
					 | 
				
			||||||
  - id: room_topic
 | 
					 | 
				
			||||||
    type: std::string
 | 
					 | 
				
			||||||
    initial_value: '"room_presence/${room_name}"'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
sensor:
 | 
					sensor:
 | 
				
			||||||
  - platform: nimble_distance
 | 
					  - platform: nimble_distance
 | 
				
			||||||
    irk: !secret apple_watch_irk
 | 
					    name: "Часы"
 | 
				
			||||||
    name: "Apple Watch Distance"
 | 
					    mac: "dc:d3:24:8e:a0:05"
 | 
				
			||||||
    id: apple_watch_distance
 | 
					    id: maxim_watch_distance
 | 
				
			||||||
    internal: true
 | 
					 | 
				
			||||||
    # TODO: should we retain the mqtt message?
 | 
					 | 
				
			||||||
    on_value:
 | 
					    on_value:
 | 
				
			||||||
      then:
 | 
					      then:
 | 
				
			||||||
        - lambda: |-
 | 
					        - lambda: |-
 | 
				
			||||||
            id(mqtt_client)->publish_json(id(room_topic), [=](ArduinoJson::JsonObject root) -> void {
 | 
					            id(mqtt_client)->publish_json(id(room_topic), [=](ArduinoJson::JsonObject root) -> void {
 | 
				
			||||||
              root["id"] = "apple_watch";
 | 
					              root["id"] = "maxim_watch";
 | 
				
			||||||
              root["name"] = "Apple Watch";
 | 
					              root["name"] = "Maxim Watch";
 | 
				
			||||||
              root["distance"] = id(apple_watch_distance).state;
 | 
					              root["distance"] = id(maxim_watch_distance).state;
 | 
				
			||||||
            });            
 | 
					            });            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
esphome:
 | 
					 | 
				
			||||||
  name: ${device_name}
 | 
					 | 
				
			||||||
  platformio_options:
 | 
					 | 
				
			||||||
    board_build.f_cpu: 160000000L
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
esp32:
 | 
					esp32:
 | 
				
			||||||
  board: esp32dev
 | 
					  board: esp32dev
 | 
				
			||||||
  framework:
 | 
					  framework:
 | 
				
			||||||
@@ -65,27 +54,18 @@ esp32:
 | 
				
			|||||||
      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y
 | 
					      CONFIG_BT_BLE_50_FEATURES_SUPPORTED: y
 | 
				
			||||||
      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y
 | 
					      CONFIG_BT_BLE_42_FEATURES_SUPPORTED: y
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
output:
 | 
					 | 
				
			||||||
  - platform: gpio
 | 
					 | 
				
			||||||
    id: "relay_output"
 | 
					 | 
				
			||||||
    pin: GPIO26
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
status_led:
 | 
					 | 
				
			||||||
  pin:
 | 
					 | 
				
			||||||
    number: GPIO0
 | 
					 | 
				
			||||||
    inverted: true
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Enable logging
 | 
					# Enable logging
 | 
				
			||||||
logger:
 | 
					logger:
 | 
				
			||||||
  level: VERBOSE
 | 
					#  level: VERY_VERBOSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Enable Home Assistant API
 | 
					# Enable Home Assistant API
 | 
				
			||||||
api:
 | 
					api:
 | 
				
			||||||
  encryption:
 | 
					  encryption:
 | 
				
			||||||
    key: !secret api_encryption_key
 | 
					    key: "gZVa2Smtq23LxQudEPzXAmnHu4CkjuOkhZQTwgbJXl4="
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ota:
 | 
					ota:
 | 
				
			||||||
  password: !secret ota_password
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
wifi:
 | 
					wifi:
 | 
				
			||||||
  ssid: !secret wifi_ssid
 | 
					  ssid: !secret wifi_ssid
 | 
				
			||||||
@@ -93,18 +73,7 @@ wifi:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  # Enable fallback hotspot (captive portal) in case wifi connection fails
 | 
					  # Enable fallback hotspot (captive portal) in case wifi connection fails
 | 
				
			||||||
  ap:
 | 
					  ap:
 | 
				
			||||||
    password: !secret wifi_password
 | 
					    ssid: "Esphome-Web-Dc7854"
 | 
				
			||||||
 | 
					    password: "zBeoh1DTmc9m"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
switch:
 | 
					captive_portal:
 | 
				
			||||||
  - platform: output
 | 
					 | 
				
			||||||
    id: shelly_relay
 | 
					 | 
				
			||||||
    name: "${entity_id}"
 | 
					 | 
				
			||||||
    output: "relay_output"
 | 
					 | 
				
			||||||
    # after reboot, keep the relay off. this prevents light turning on after a power outage
 | 
					 | 
				
			||||||
    restore_mode: ALWAYS_OFF
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# From https://community.home-assistant.io/t/shelly-plus-1-esphome-bletracker/363549/38
 | 
					 | 
				
			||||||
# Setup a button in home assistant to reboot the shelly into safe mode
 | 
					 | 
				
			||||||
button:
 | 
					 | 
				
			||||||
  - platform: safe_mode
 | 
					 | 
				
			||||||
    name: "${entity_id}_safe_mode_restart"
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user