Step-by-step presence detection with bluetooth tag (e.g., GTags) and ESPresense on ESP32

Since this MQTT, subscribing and parsing topic, while it refers to ESPresense (, GitHub: ESPresense · GitHub), does not give enough detail for beginners to get this up and running easily and the ESPresense website focuses on HomeAssistant config, I’ll try to leave a few notes here that may help other beginners to get up and running if the aim is to get presence detection with bluetooth tags (like the GTags, other bluetooth devices, including mobiles, for which ESPresense has some fingerprinting features apparently, to address random MAC changes, should work similarly).

NOTE: I’m an OpenHab/MQTT newbie, so there may be way more elegant ways to do this (implementing the below requires quite a bit of clicking around). Any advice most welcome!

  • Step 1: get ESP32 or similar with ESPresense firmware up and running by following the instructions given here: Install – ESPresense – ESP32 based indoor positioning system
  • Step 2: install Mosquitto MQTT broker on OpenHab server (Raspi in my case), by selecting Optional Components/Mosquitto in openhabian-config; set adequate password and make note of this; install MQTT binding and JSONPATH transformation add on in OpenHab unless already present
  • Step 3: Set your ESPresense ESP32 to target your mosquitto by setting IP to OpenHab server IP, user ID to openhabian and password to the Mosquitto password you set via the ESPresense web UI
  • Step 4: check MQTT message stream on your Moquitto by running mosquitto_sub -v -u openhabian -P YOURMOSQUITTOPASSWORD -h localhost -p 1883 -t '#' on your OpenHab server; you should see a stream of espresence/# related MQTT messages on your console
  • Step 5: create MQTT broker thing in OpenHab
  • Step 6: Use autodetection for HomeAssistant things (should show up in your thing inbox) to get a first representation of ESPresense ESP32, which gives status, firmware, etc.
  • Step 7: create one generic MQTT thing using your MQTT broker configured in step 5 per ESP32 ESPresense device; create equipment from this thing in the appropriate room in your semantic model; you can group the other stuff there in the following steps
  • Step 7a: create status channel on this thing as ON/OFF switch type channel, with MQTT State topic “espresense/rooms/[ESP 32 ID]/status”; create status item from this channel; this seems to respond more fluidly to status changes of the ESP32 than the autodiscovered status from Step 6
  • Step 8: create one string typed MQTT “JSON complete” channel per ESP32 per bluetooth device you would like to detect, with state topic “espresense/devices/[bluetooth tag MAC]/[ESP32 ID]” as per the MQTT stream from step 4 (you can to identify your bluetooth tag MACs from the MQTT message stream by moving them around a little and looking at distance changes or whatever)
  • Step 9: from the “JSON complete” channel context, by using “create points”, create one number item per data item per tag per ESP32 that you want to use in OpenHab (I use distance, rssi, speed for each GTag on each ESP32)
  • Step 10: configure the “JSONPATH” profile for each item/channel link, inserting JSONPATH expression as appropriate, i.e., “$.distance”, “$.rssi”, “$.speed”, without quotes, of course).
  • Step 11: add metadata to item, i.e. for units (state description “%.1f m” to add meters unit to distance, “%.1f dB” for RSSI, etc.) and Expiration Timer to make sure you notice when the tag is gone (I currently use 10 s expiration).
  • Step 12: make some rules to fuse you various ESP32 distance values etc. into something you can build further logic on.

Optional/additional GTag detection using Raspi builtin Bluetooth: In addition to using the ESP32s for detecting the GTags, one can use the OpenHab Bluetooth binding and the BlueZ stack on the Raspi (if that’s what your OpenHab is running on) to cover the vicinity of the Raspi using the Raspi builtin Bluetooth hardware. After successful installation/configuration of the Bluetooth binding, just make sure to add the autodiscovered things of type “beacon”, not “generic” (sometimes takes a few tries), which then give you an RSSI channel, and add an expiration timer to the RSSI items build from the “beacon” things. Works like a charm for GTags, no need for non-standard bluetooth binding in the current version of OpenHab to get this to work, from my experience. Useful in our case so we don’t need an additional ESP32 in the basement.


That’s great! Thanks!
Do you have any experience on how this compares to room-asssistant?

The whole room presence detection is still on my to-do list.

Sorry, I have no first hands experience with Home Assistant (if that’s what you’re referring to). Decided to go with OpenHab since it’s supposedly more robust for production use. I also like the tech stack better.

Talking about room-asssistant which I saw the ESP32 version is based on.
I run a room-assistant instance for testing since a few months and it seems that the overall functionality is similar.

Ah, I see. No hands on experience there, either, sry.

I have built something similar to room assistant that uses the OH model. It’s done in jython

I moved this to the tutorials and solutions section where it can more easily be found.


I’m also starting to use espresense but I think I found an easier way :slight_smile:

I added a channel to my broker to get all MQTT updates in the devices topic for a special device:

“14_Pro_Max” is my in the espresense configured and monitored iPhone. With the “#” I receive alle keys and values for handling later in a rule.

After that I created a dummy String item for visualising the room name.

I created following rule to check for the room name and set it the previous created dummy String item:

var klausLastRoom = ""
var klausLastDistance = 10.0

rule "espresense_room_klaus"
        Channel "mqtt:broker:Matt_broker:c_14" triggered
        val json = receivedEvent.split('#').get(1)
        val distance =  transform("JSONPATH", "$.distance", json)
        val distanceDouble =  Double.parseDouble(distance)
        val room = receivedEvent.split('#').get(0).split('/').get(3)
        //checking if the room is the same current
        if(klausLastRoom != room){
        	logInfo("espresense_log", "Checking new room: " + room)
        	//checking if new room is closer than current one
        	if(distanceDouble < klausLastDistance){
        		klausLastRoom = room
        		klausLastDistance = distanceDouble
        		// translating the technical espresense names to human readable ones (hard coded)
        		if(klausLastRoom == "esp_og_eltern"){
        		}else if(klausLastRoom == "esp_eg_living_room"){
        		}else if(klausLastRoom == "esp_ug_office"){
        		}else if(klausLastRoom == "esp_ug_cellar"){
        			iphone14Room.sendCommand("Unbekannter Raum")
        		logInfo("espresense_log", "New room for device detected: " + room)
        		logInfo("espresense_log", "Room ist too far away: " + room + ". Current distance is " + klausLastDistance + " vs. " +  distanceDouble)
        // update distance for current room	
       	 	logInfo("espresense_log", "Current room info for: " + room)
       	 	klausLastDistance = distanceDouble
       	 	logInfo("espresense_log", "Updated distance for current room: " + klausLastDistance)

For other devices a new channel for the broker, a new dummy String item and a new rule is needed (easy copy paste modify)

Hope it helps and saves you some time :slight_smile:

1 Like

Hi @apfelklaus,
How do you add the channel to the broker? GUI or files?
I can’t find how to do it with the GUI. I added this in .things file:

Thing mqtt:topic:ESPresenseIPhone "ESPresense iPhone" (mqtt:broker:mymqtt) {
    Type string : ESPresenseIPhoneAll "ESPresense iPhone all" [ stateTopic="espresense/devices/irk:eecsadfds24asdfds8b385b4955b4f2859beed/#" ]

But in the GUI I get a “mqtt:topic” thing instead of a “mqtt:broker” thing as you get. So, the rule doesn’t work because the channel is never triggered.
I can create an item with my channel and change the rule to “received update”, but then I loose the part of the MQTT topic where the room is.

Thank you.

Navigate to Settings → Things → Your MQTT Broker Thing. Click the “Channels” tab. Click “Add Channel”.

Note that the only type of channel available is a “Publish Trigger”. These channels cannot be linked to an Item. They are used to directly trigger a Rule. The event that gets set as an implicit variable in the rule will have the format <topic>#<message> given @apfelklaus’s posted configuration.

That’s just the ID of the Channel. They have their broker Thing named differently from you. But the fact it’s different is irrelevant.

You have to use the ID of your Channel to trigger the rule.

That’s because you’ve created a state trigger, not an event trigger. A state trigger is linked to an Item. An event trigger is used to directly trigger a rule. And an event trigger is the only way to get the MQTT topic into the rule.

Thank you, @rlkoshak
I’ve figured it out. I couldn’t find it because my MQTT broker was defined in a .things file, so the “Add Channel” option isn’t there.
I found an example of how to define it in a file here: Channels in MQTT Broker not loading after openHAB restart
So now I have this, working as expected:

Bridge mqtt:broker:espresense "ESPresense MQTT broker" [ host="", secure=false]
        Type publishTrigger : ESPresenseIPhoneAll "ESPresense iPhone all" [ stateTopic="espresense/devices/irk:eecsadfds24asdfds8b385b4955b4f2859beed/#", separator="#" ]

I took a quick look at the docs and it isn’t there.

I know. I had an item named ESPresense_iPhone_all created with the GUI from the MQTT channel above, and I had changed the rule to:

rule "ESPresense"
    Item ESPresense_iPhone_all received update
    logInfo("espresense_log", "ESPresense: "+ESPresense_iPhone_all.state)


I just implemented it for my apple watch.

Assuming you have the MQTT broker installed/configured the ESPresense and enrolled your device (see BTW, You only need to enroll on one ESPresense device. They automatically replicate the information on your MQTT system.

Example message on “espresense/devices/mywatch/livingroom”

  "mac": "xxxxxxxxxxx",
  "id": "mywatch",
  "name": "MyWatch",
  "disc": "xxxxxxxxxx",
  "idType": 250,
  "rssi@1m": -65,
  "rssi": -93,
  "raw": 6.31,
  "distance": 1.89,
  "int": 1508

My things file:

Thing mqtt:topic:espresense:mywatch "my watch" (mqtt:broker:mqttBroker) {
        Type number : livingroom    [stateTopic="espresense/devices/mywatch/livingroom", transformationPattern="JSONPATH:$.distance"]
        Type number : myoffice      [stateTopic="espresense/devices/mywatch/myoffice", transformationPattern="JSONPATH:$.distance"]
        Type number : familyroom    [stateTopic="espresense/devices/mywatch/familyroom", transformationPattern="JSONPATH:$.distance"]
        Type number : kitchen       [stateTopic="espresense/devices/mywatch/kitchen", transformationPattern="JSONPATH:$.distance"]

Items File:

Group    MyWatch               "my watch" <contact> (Devices)  ["Sensor"]
Number   MyWatch_livingroom    "my watch livingroom [%.1f]"    (MyWatch) [ "Measurement", "Level" ]  {channel="mqtt:topic:espresense:mywatch:livingroom"}
Number   MyWatch_myoffice      "my watch myoffice [%.1f]"     (MyWatch) [ "Measurement", "Level" ]  {channel="mqtt:topic:espresense:mywatch:myoffice"}
Number   MyWatch_familyroom    "my watch familyroom [%.1f]"    (MyWatch) [ "Measurement", "Level" ]  {channel="mqtt:topic:espresense:mywatch:familyroom"}
Number   MyWatch_kitchen       "my watch kitchen [%.1f]"  (MyWatch) [ "Measurement", "Level" ]  {channel="mqtt:topic:espresense:mywatch:kitchen"}
DateTime MyWatch_lastseen      "my watch last [%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS]"   (MyWatch)
String   MyWatch_location      "my watch location [%s]"  (MyWatch)


rule "React on MyWatch location change/update"
    Item MyWatch_myoffice changed or
    Item MyWatch_kitchen changed or
    Item MyWatch_familyroom changed or
    Item MyWatch_livingroom changed
    var myoffice = (MyWatch_myoffice.state as DecimalType).floatValue()
    var kitchen = (MyWatch_kitchen.state as DecimalType).floatValue()
    var familyroom = (MyWatch_familyroom.state as DecimalType).floatValue()
    var livingroom = (MyWatch_livingroom.state as DecimalType).floatValue()


    if (myoffice < kitchen && myoffice < familyroom && myoffice < livingroom) {

    } else if (kitchen < myoffice && kitchen < familyroom && kitchen < livingroom) {
    } else if (familyroom < myoffice && familyroom < kitchen && familyroom < livingroom) {
    } else if (livingroom < myoffice && livingroom < familyroom && livingroom < kitchen) {


I could probably simplify this with a group for the set or rooms. Probably a future enhancement.

MQTT Explorer is a very handy tool for these types of implementations.

Just a heads up, for some reason when I click on your link I get redirected to a spam/scam site. This link works for me though. Weirdly yours and mine both seem to be spelled the same but even if I copy and paste your link into my address bar I get redirected but not mine.

Edit: Tried a whole different machine with the same results so there’s definitely something funky with that link

there’s definitely something funky with that link

The difference is the c at the end of vs the s at the end of :slight_smile:

1 Like

Geez that’s what I get for looking at things before coffee lol. Good catch.

1 Like