From 85650a6204fe36b7351888100cac3fef9ba49d58 Mon Sep 17 00:00:00 2001 From: Florian Date: Mon, 20 Mar 2023 07:31:00 -0500 Subject: [PATCH] [BT] Device tracker presence detection (#1548) Add device tracker detection and automatic creation following HA discovery convention So as to trigger the away state, add an offline status to the identified BLE trackers --- ...MQTTGateway-BLE-tracker-Home-Assistant.png | Bin 0 -> 19072 bytes docs/use/ble.md | 47 ++++++------- main/ZgatewayBT.ino | 66 +++++++++++++++++- main/ZmqttDiscovery.ino | 20 +++++- main/config_BT.h | 5 ++ 5 files changed, 107 insertions(+), 31 deletions(-) create mode 100644 docs/img/OpenMQTTGateway-BLE-tracker-Home-Assistant.png diff --git a/docs/img/OpenMQTTGateway-BLE-tracker-Home-Assistant.png b/docs/img/OpenMQTTGateway-BLE-tracker-Home-Assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..b97125be16af614ab7768e3a46d0e5a0c67a6ef9 GIT binary patch literal 19072 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 MboFyt=akR{0Kej<{r~^~ literal 0 HcmV?d00001 diff --git a/docs/use/ble.md b/docs/use/ble.md index a363dd1..b68372f 100644 --- a/docs/use/ble.md +++ b/docs/use/ble.md @@ -29,6 +29,26 @@ Once the data has been transmitted to the MQTT broker, it can be easily integrat Examples of compatible sensors among [our list](https://decoder.theengs.io/devices/devices_by_brand.html: Mi Flora, Mi jia, LYWDS02, LYWSD03MMC, ClearGrass, Mi scale, iBBQ, TPMS +## Receiving signals from BLE tracker devices for Presence detection +By default the gateway will detect the BLE trackers from Tile, NUT, TAGIT, ITAG, MiBand, Amazfit and RuuviTag and create automaticaly a device tracker entity following the Home Assistant discovery convention (if the auto discovery is activated). +The entity can be attached to a person to leverage presence detection. The `away` or `not home` state is triggered if the BLE tracker is not detected during the timer defined by `presenceawaytimer`. + +![](../img/OpenMQTTGateway-BLE-tracker-Home-Assistant.png) + +If you have multiple gateways, your BLE trackers may not be detected temporary by one gateway but still by the others. In this case you will see the tracker appears offline briefly and online again once it is detected by the others gateways. + +By default `presenceawaytimer` is set to 120s, you can change it with the following command (ms) + +`mosquitto_pub -t home/OpenMQTTGateway/commands/MQTTtoBT/config -m '{"presenceawaytimer":66000}'` + +Generally BLE devices will not broadcast if they are paired so you may need to ensure your beacons is unpaired/disconnected before it will be seen by the gateway. + +Consider the distance estimation as a beta feature. + +Note that you can find apps to simulate beacons and do some tests like [Beacon simulator](https://play.google.com/store/apps/details?id=net.alea.beaconsimulator) + +iOS version >=10 devices advertise without an extra app MAC address, nevertheless this address [changes randomly](https://github.com/1technophile/OpenMQTTGateway/issues/71) and cannot be used for presence detection. You must install an app to advertise a fixed MAC address. + ## Setting a white or black list A black list is a list of MAC addresses that will never be published by OMG to set black list @@ -158,33 +178,6 @@ If you want to change this characteristic: With Home Assistant, this command is directly available through MQTT auto discovery as a switch into the HASS OpenMQTTGateway device entities list. ::: -## Receiving signals from BLE beacon devices for Presence detection - -Subscribe to all the messages with mosquitto or open your MQTT client software: - -` sudo mosquitto_sub -t +/# -v` - -_NOTE: HM-10 or HM-11 module needed if you are not using ESP32; configure in `User_config.h`_ - -The BT gateway module for OpenMQTTGateway enables the detection of BLE beacons and their signal strength. Generally BLE devices will not broadcast if they are paired so you may need to ensure your beacons is unpaired before it will be seen by the gateway. - -If beacons are detected the gateway will periodically publish messages to MQTT (beacons must not be paired, see above): - -``` -home/OpenMQTTGateway/BTtoMQTT/45E174126E00 {"id":"45:e1:74:12:6e:00","rssi":-89,"distance":21.51847,"servicedata":"fe0000000000000000000000000000000000000000"} -``` -``` -home/OpenMQTTGateway/BTtoMQTT/C7FaaD132C00 {"id":"c7:fa:ad:13:2c:00","rssi":-68,"distance":2.799256,"servicedata":"drfgdrgdsrgesrdgdrgdregesrgtrhtyhtfyhdtyhh"} -``` - -The subtopic after `home/BTtoMQTT/` is the MAC address of the Bluetooth low energy beacon. The rssi value is the [RSSI signal level](https://www.metageek.com/training/resources/understanding-rssi.html) from which you may deduce the relative distance to the device. -Consider the distance as a beta feature as currently we are not retrieving the emitting power of the beacon to make it more accurate. - -Note that you can find apps to simulate beacons and do some tests like [Beacon simulator](https://play.google.com/store/apps/details?id=net.alea.beaconsimulator) - -iOS version >=10 devices advertise without an extra app MAC address, nevertheless this address [changes randomly](https://github.com/1technophile/OpenMQTTGateway/issues/71) and cannot be used for presence detection. You must install an app to advertise a fixed MAC address. - - ## Setting if the gateway publish into Home Assistant Home presence topic If you want to publish to Home Assistant presence topic, you can activate this function by the HASS interface (this command is auto discovered), [here is a yaml example](../integrate/home_assistant.md#mqtt-room-presence). diff --git a/main/ZgatewayBT.ino b/main/ZgatewayBT.ino index f0b9ff5..6db69e7 100644 --- a/main/ZgatewayBT.ino +++ b/main/ZgatewayBT.ino @@ -98,6 +98,7 @@ void BTConfig_init() { BTConfig.pubAdvData = pubBLEAdvData; BTConfig.pubBeaconUuidForTopic = useBeaconUuidForTopic; BTConfig.ignoreWBlist = false; + BTConfig.presenceAwayTimer = PresenceAwayTimer; } unsigned long timeBetweenConnect = 0; @@ -124,6 +125,7 @@ void stateBTMeasures(bool start) { jo["pubadvdata"] = BTConfig.pubAdvData; jo["pubBeaconUuidForTopic"] = BTConfig.pubBeaconUuidForTopic; jo["ignoreWBlist"] = BTConfig.ignoreWBlist; + jo["presenceawaytimer"] = BTConfig.presenceAwayTimer; jo["btqblck"] = btQueueBlocked; jo["btqsum"] = btQueueLengthSum; jo["btqsnd"] = btQueueLengthCount; @@ -143,6 +145,16 @@ void BTConfig_fromJson(JsonObject& BTdata, bool startup = false) { Config_update(BTdata, "bleconnect", BTConfig.bleConnect); // Identify AdaptiveScan deactivation to pass to continuous mode or activation to come back to default settings if (startup == false) { + if (BTdata.containsKey("hasspresence") && BTdata["hasspresence"] == false && BTConfig.presenceEnable == true) { + BTdata["adaptivescan"] = true; +# ifdef ZmqttDiscovery + // Remove discovered entities + eraseTopic("number", (char*)getUniqueId("presenceawaytimer", "").c_str()); +# endif + } else if (BTdata.containsKey("hasspresence") && BTdata["hasspresence"] == true && BTConfig.presenceEnable == false) { + BTdata["adaptivescan"] = false; + } + if (BTdata.containsKey("adaptivescan") && BTdata["adaptivescan"] == false && BTConfig.adaptiveScan == true) { BTdata["interval"] = MinTimeBtwScan; BTdata["intervalacts"] = MinTimeBtwScan; @@ -159,9 +171,12 @@ void BTConfig_fromJson(JsonObject& BTdata, bool startup = false) { } } Config_update(BTdata, "adaptivescan", BTConfig.adaptiveScan); + // Home Assistant presence message + Config_update(BTdata, "hasspresence", BTConfig.presenceEnable); # ifdef ZmqttDiscovery // Create discovery entities btScanParametersDiscovery(); + btPresenceParametersDiscovery(); # endif // Time before before active scan // Scan interval set @@ -177,12 +192,12 @@ void BTConfig_fromJson(JsonObject& BTdata, bool startup = false) { Config_update(BTdata, "onlysensors", BTConfig.pubOnlySensors); // publish devices which randomly change their MAC addresses Config_update(BTdata, "randommacs", BTConfig.pubRandomMACs); - // Home Assistant presence message - Config_update(BTdata, "hasspresence", BTConfig.presenceEnable); // Home Assistant presence message topic Config_update(BTdata, "presenceTopic", BTConfig.presenceTopic); // Home Assistant presence message use iBeacon UUID Config_update(BTdata, "presenceUseBeaconUuid", BTConfig.presenceUseBeaconUuid); + // Timer to trigger a device state as offline if not seen + Config_update(BTdata, "presenceawaytimer", BTConfig.presenceAwayTimer); // MinRSSI set Config_update(BTdata, "minrssi", BTConfig.minRssi); // Send undecoded device data @@ -230,6 +245,7 @@ void BTConfig_fromJson(JsonObject& BTdata, bool startup = false) { jo["pubadvdata"] = BTConfig.pubAdvData; jo["pubBeaconUuidForTopic"] = BTConfig.pubBeaconUuidForTopic; jo["ignoreWBlist"] = BTConfig.ignoreWBlist; + jo["presenceawaytimer"] = BTConfig.presenceAwayTimer; // Save config into NVS (non-volatile storage) String conf = ""; serializeJson(jsonBuffer, conf); @@ -408,11 +424,12 @@ void createOrUpdateDevice(const char* mac, uint8_t flags, int model, int mac_typ device->connect = flags & device_flags_connect; device->macType = mac_type; device->sensorModel_id = model; + device->lastUpdate = millis(); devices.push_back(device); newDevices++; } else { Log.trace(F("update %s" CR), mac); - + device->lastUpdate = millis(); device->macType = mac_type; if (flags & device_flags_isDisc) { @@ -439,6 +456,30 @@ void createOrUpdateDevice(const char* mac, uint8_t flags, int model, int mac_typ xSemaphoreGive(semaphoreCreateOrUpdateDevice); } +void updateDevicesStatus() { + for (vector::iterator it = devices.begin(); it != devices.end(); ++it) { + BLEdevice* p = *it; + unsigned long now = millis(); + if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::NUT || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::MIBAND || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::TAGIT || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::TILE || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::ITAG || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV1 || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV2) { // We apply the offline status only for tracking device, can be extended further to all the devices + if ((p->lastUpdate != 0) && (p->lastUpdate < (now - BTConfig.presenceAwayTimer) && (now > BTConfig.presenceAwayTimer)) && + (BTConfig.ignoreWBlist || ((!oneWhite || isWhite(p)) && !isBlack(p)))) { // Only if WBlist is disabled OR ((no white MAC OR this MAC is white) AND not a black listed MAC)) { + JsonObject BLEdata = getBTJsonObject(); + BLEdata["id"] = p->macAdr; + BLEdata["state"] = "offline"; + pubBT(BLEdata); + // We set the lastUpdate to 0 to avoid replublishing the offline state + p->lastUpdate = 0; + } + } + } +} + void dumpDevices() { for (vector::iterator it = devices.begin(); it != devices.end(); ++it) { BLEdevice* p = *it; @@ -449,6 +490,7 @@ void dumpDevices() { Log.trace(F("isBlkL %d" CR), p->isBlkL); Log.trace(F("connect %d" CR), p->connect); Log.trace(F("sensorModel_id %d" CR), p->sensorModel_id); + Log.trace(F("LastUpdate %u" CR), p->lastUpdate); } } @@ -646,6 +688,8 @@ void procBLETask(void* pvParameters) { } else { Log.trace(F("Filtered MAC device" CR)); } + if (BTConfig.presenceEnable) + updateDevicesStatus(); } delete (advertisedDevice); } @@ -869,6 +913,7 @@ void setupBT() { Log.notice(F("Active BLE scan interval: %d" CR), BTConfig.intervalActiveScan); Log.notice(F("minrssi: %d" CR), -abs(BTConfig.minRssi)); Log.notice(F("Low Power Mode: %d" CR), lowpowermode); + Log.notice(F("Presence Away Timer: %d" CR), BTConfig.presenceAwayTimer); atomic_init(&forceBTScan, 0); // in theory, we don't need this atomic_init(&jsonBTBufferQueueNext, 0); // in theory, we don't need this @@ -983,6 +1028,21 @@ void launchBTDiscovery(bool overrideDiscovery) { model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false, stateClassMeasurement); } + if (p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::NUT || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::MIBAND || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::TAGIT || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::TILE || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::ITAG || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV1 || + p->sensorModel_id == TheengsDecoder::BLE_ID_NUM::RUUVITAG_RAWV2) { + createDiscovery("device_tracker", + discovery_topic.c_str(), entity_name.c_str(), unique_id.c_str(), + will_Topic, "occupancy", "{% if value_json.rssi -%}home{%- else -%}not_home{%- endif %}", + "", "", "", + 0, "", "", false, "", + model.c_str(), brand.c_str(), model_id.c_str(), macWOdots.c_str(), false, + stateClassNone); + } } } } else { diff --git a/main/ZmqttDiscovery.ino b/main/ZmqttDiscovery.ino index ac5ae7b..6e4196f 100644 --- a/main/ZmqttDiscovery.ino +++ b/main/ZmqttDiscovery.ino @@ -295,6 +295,8 @@ void createDiscovery(const char* sensor_type, } if (payload_off[0]) sensor["pl_off"] = payload_off; //payload_off + if (strcmp(sensor_type, "device_tracker") == 0) + sensor["source_type "] = "bluetooth_le"; // payload_install for update if (off_delay != 0) sensor["off_delay"] = off_delay; //off_delay if (payload_available[0]) @@ -386,8 +388,22 @@ void eraseTopic(const char* sensor_type, const char* unique_id) { } # ifdef ZgatewayBT +void btPresenceParametersDiscovery() { + if (BTConfig.presenceEnable) { + createDiscovery("number", //set Type + subjectBTtoMQTT, "BT: Presence detection timer", (char*)getUniqueId("presenceawaytimer", "").c_str(), //set state_topic,name,uniqueId + will_Topic, "", "{{ value_json.presenceawaytimer/60000 }}", //set availability_topic,device_class,value_template, + "{\"presenceawaytimer\":{{value*60000}},\"save\":true}", "", "min", //set,payload_on,payload_off,unit_of_meas, + 0, //set off_delay + Gateway_AnnouncementMsg, will_Message, true, subjectMQTTtoBTset, //set,payload_available,payload_not available ,is a gateway entity, command topic + "", "", "", "", false, // device name, device manufacturer, device model, device ID, retain, + stateClassNone //State Class + ); + } +} + void btScanParametersDiscovery() { - if (BTConfig.adaptiveScan == false) { + if (!BTConfig.adaptiveScan) { createDiscovery("number", //set Type subjectBTtoMQTT, "BT: Interval between scans", (char*)getUniqueId("interval", "").c_str(), //set state_topic,name,uniqueId will_Topic, "", "{{ value_json.interval/1000 }}", //set availability_topic,device_class,value_template, @@ -1105,6 +1121,8 @@ void pubMqttDiscovery() { btScanParametersDiscovery(); + btPresenceParametersDiscovery(); + createDiscovery("switch", //set Type subjectBTtoMQTT, "BT: Publish HASS presence", (char*)getUniqueId("hasspresence", "").c_str(), //set state_topic,name,uniqueId will_Topic, "", "{{ value_json.hasspresence }}", //set availability_topic,device_class,value_template, diff --git a/main/config_BT.h b/main/config_BT.h index 88ff0fb..ad442f6 100644 --- a/main/config_BT.h +++ b/main/config_BT.h @@ -89,6 +89,9 @@ extern int btQueueLengthCount; #ifndef TimeBtwConnect # define TimeBtwConnect 3600000 //define default time between BLE connection attempt (not used for immediate actions); in milliseconds #endif +#ifndef PresenceAwayTimer +# define PresenceAwayTimer 120000 //define the time between Offline Status update for the sensors +#endif #ifndef BLEScanDuplicateCacheSize # define BLEScanDuplicateCacheSize 200 @@ -160,6 +163,7 @@ struct BTConfig_s { bool pubAdvData; // Publish advertisement data bool pubBeaconUuidForTopic; // Use iBeacon UUID as topic, instead of sender (random) MAC address bool ignoreWBlist; // Disable Whitelist & Blacklist + unsigned long presenceAwayTimer; //Timer that trigger a tracker state as offline if not seen }; // Global struct to store live BT configuration data @@ -209,6 +213,7 @@ struct BLEdevice { bool isBlkL; bool connect; int sensorModel_id; + unsigned long lastUpdate; }; class BLEconectable {