My ESPresense setup and Lovelace dashboard

I thought I would share the Lovelace cards I’ve been working on for my ESPresense setup.

There’s an HTML table at the bottom, with stats for all of my base stations, and links to the web UI.

HACs Requirements

ESPresense Requirements

You will need ESPresense running on ESP32s, sending data to the MQTT add-on for Home Assistant.

You will also need to manually create some sensors for each person in your home, as well as some input_booleans to track the occupancy of rooms (input_boolean.<room>_occupied). I do this instead of using the “count” feature from ESPresense, because I have some custom logic that also looks at motion sensors. (Don’t set a room to unoccupied until all motion sensors are cleared.)

sensor.espresense_person_<person>

I have set up sensor.espresense_person_* sensors for my wife and I. These just get updated with our most recently changed device. E.g. If I leave my phone in the bedroom and walk around the house while wearing my watch, then sensor.espresense_person_nathan just gets updated based on my watch.

input_boolean.<room>_occupied

Here’s the YAML files I use to automate these “occupied” input booleans: espresense.yaml · GitHub

(I have some custom scripts for my HA configuration that compile Jinja templates into YAML.)

Lovelace YAML

I use “View Type” => “Panel (1 card)”, so I can have the long horizontal HTML table at the bottom.

Also note: I’m configuring the icon on each input_boolean that I set up as a Helper, and I’ve set up some Jinja templates that fetch the icon for each room. So I only need to set the icon in one place, and it’s easy to add new rooms.

type: vertical-stack
cards:
  - type: horizontal-stack
    cards:
      - type: vertical-stack
        cards:
          - type: custom:auto-entities
            card:
              type: grid
              columns: 1
              square: false
            card_param: cards
            sort:
              method: entity_id
            filter:
              template: >
                [{%- for s in states.sensor 
                  if s.entity_id.startswith("sensor.espresense_person_") %}
                {%- set input_boolean = states.input_boolean[s.state ~
                '_occupied'] %}

                {%- set icon = input_boolean.attributes.icon if input_boolean
                else "mdi:radar" %}

                {{
                  {
                    "type": 'custom:mushroom-template-card', 
                    "entity": s.entity_id,
                    "icon": icon,
                    "icon_color": "grey" if s.state in ["not_home", "unknown"] else "blue",
                    "primary": (s.name | replace('ESPresense Person ', '')),
                    "secondary": (s.state | replace('_', ' ') | title 
                      | replace("Nathans ", "Nathan's ")
                      | replace("Mashas ", "Masha's ")) ~ 
                      ("" if s.state in ["not_home", "unknown"] else " (" ~ s.attributes.distance ~ " m)")
                  }
                }},

                {%- endfor %}]
          - type: custom:auto-entities
            card:
              type: grid
              columns: 1
              square: false
            card_param: cards
            sort:
              method: entity_id
            filter:
              template: >
                [{%- for s in states.sensor 
                  if s.entity_id.startswith("sensor.espresense_device_") %}
                {%- set input_boolean = states.input_boolean[s.state ~
                '_occupied'] %}

                {%- set icon = input_boolean.attributes.icon if input_boolean
                else "mdi:radar" %}

                {{
                  {
                    "type": 'custom:mushroom-template-card', 
                    "entity": s.entity_id,
                    "icon": icon,
                    "icon_color": "grey" if s.state == "not_home" else "blue",
                    "primary": (s.name | replace('ESPresense Device ', '')),
                    "secondary": (s.state | replace('_', ' ') | title 
                      | replace("Nathans ", "Nathan's ")
                      | replace("Mashas ", "Masha's ")) ~ 
                      ("" if s.state == "not_home" else " (" ~ s.attributes.distance ~ " m)")

                  }
                }},

                {%- endfor %}]
      - type: custom:auto-entities
        card:
          type: grid
          columns: 1
          square: false
        card_param: cards
        sort:
          method: entity_id
        filter:
          template: |
            {% set comma = joiner(",") %}
            [{%- for s in states.input_boolean 
              if s.entity_id.endswith("_occupied") %}
             {%- set room = s.entity_id.replace('input_boolean.', '').replace('_occupied', '') %}
             {%- set input_boolean = states.input_boolean[room.replace('nathan_s', 'nathans') ~ '_occupied'] %}
             {%- set icon = input_boolean.attributes.icon if input_boolean else "mdi:radar" %}
             {%- set count = states('sensor.espresense_' ~ room ~ '_count') | int(0) %}
            {{ comma() }} {
                "type": 'custom:mushroom-template-card', 
                "tap_action": "more-info",
                "entity": "{{ s.entity_id }}",
                "icon": "{{ icon }}",
                {#
                {% if count > 0 %}
                "badge_icon": "mdi:numeric-{{ '9+' if count > 9 else count }}",
                "badge_color": "#171",
                {% endif %} #}
                "icon_color": "{{ "blue" if s.state == "on" else "grey" }}",
                "primary": "{{ (s.name | replace(' Occupied', '')) }}",
                "secondary": "{{ "Occupied (" ~ count ~ ")" if s.state == "on" else ("Unoccupied" if count == 0 else "Unoccupied (" ~ count ~ ")") }}"
              }
            {%- endfor %}]
  - type: custom:html-template-card
    title: ESPresense Base Stations
    ignore_line_breaks: true
    content: |
      <style>
        table, th, td {
          border: 1px solid #aaa;
          border-collapse: collapse;
        }
        th, td { padding: 6px 12px;
      </style>
      <table>
        <tr>
          <th>Room</th>
          <th>IP</th>
          <th>Connected</th>
          <th>Uptime</th>
          <th>Count (<code>exp:20</code>)</th>
          <th>Links</th>
        </tr>
        {% for entity in states.binary_sensor if
            entity.entity_id.startswith('binary_sensor.espresense') %}
          {% set room = entity.entity_id.replace("binary_sensor.espresense_", "") %}
          {% set icon = states.input_boolean[room.replace('nathan_s', 'nathans') ~ '_occupied'].attributes.icon %}
          <tr>
            <td><ha-icon icon="{{ icon }}"></ha-icon>&nbsp;&nbsp;{{ entity.name | replace('ESPresense ', '') | replace('_', ' ') | title }}</td>
            <td><a href="{{ entity.attributes.ip }}"><code>{{ entity.attributes.ip }}</code></a></td>
            <td>{{ '<span style="color: #8f8">yes</span>' if entity.state == 'on' else '<span style="color: #f55">no</span>' }}</td>
            <td>
              {% if entity.state == 'on' %}
              {% set time = entity.attributes.uptime %}
              {% set minutes = ((time % 3600) / 60) | int %}
              {% set hours = ((time % 86400) / 3600) | int %}
              {% set days = (time / 86400) | int %}
              {%- if time < 60 -%}
                Less than a minute
              {%- else -%}
                {%- if days > 0 -%}
                  {{ days }}d
                {%- endif -%}
                {%- if hours > 0 -%}
                  {%- if days > 0 -%}
                    {{ ' ' }}
                  {%- endif -%}
                  {{ hours }}h
                {%- endif -%}
                {%- if minutes > 0 -%}
                  {%- if days > 0 or hours > 0 -%}
                    {{ ' ' }}
                  {%- endif -%}
                  {{ minutes }}m
                {%- endif -%}
              {%- endif -%}
              {%- else %}
              &mdash;
              {%- endif -%}
            </td>
            <td>{{states.sensor['espresense_' ~ room ~ '_count'].state }}</td>
            <td>
              <a target="_blank" href="http://{{ entity.attributes.ip }}">
                Settings
              </a> | 
            
              <a target="_blank" href="http://{{ entity.attributes.ip }}/ui">
                Enroll
              </a> |
            
              <a target="_blank" href="http://{{ entity.attributes.ip }}/ui/#/fingerprints">
                Fingerprints
              </a>
            </td>
          </li>
        {%endfor%}
      </ul>

P.S. Is it possible to create a new tag? I wanted to add one for espresense.

8 Likes

How are you creating these sensors? What kind of sensors are they?

1 Like

I created these sensors in Node-RED:

These are entity nodes with a type of “Sensor”, and device class set to “presence”.

The device sensors are configured using the mqtt_room platform:

  sensor:
    - platform: mqtt_room
      name: "ESPresense Device Masha's iPhone"
      device_id: mashas_iphone
      state_topic: "espresense/devices/mashas_iphone"
      timeout: 10
      away_timeout: 180
    - platform: mqtt_room
      name: "ESPresense Device Nathan's iPhone"
      device_id: nathans_iphone
      state_topic: "espresense/devices/nathans_iphone"
      timeout: 10
      away_timeout: 180
    - platform: mqtt_room
      name: "ESPresense Device Nathan's Apple Watch"
      device_id: !secret espresense_device_id_nathans_apple_watch
      state_topic: !secret espresense_state_topic_nathans_apple_watch
      timeout: 10
      away_timeout: 180

See: Home Assistant | ESPresense

1 Like

That helped, that was my first time using node red to create a sensor.

I’m curious about the 5 Second Timer in your node red flow. I found node-red-contrib-delay-topic-message node-red-contrib-delay-topic-message (node) - Node-RED but I don’t think it’s the same one you’re using.

I love what you did with this, and I wanted to borrow some of your hard work from the bottom section (ESPresence Base Stations) of your card. But, I am trying to figure this out, not sure what it means:

I use “View Type” => “Panel (1 card)”, so I can have the long horizontal HTML table at the bottom.

I can not get the bottom section to span the the entire width of the table. Any help with this would be greatly appreciated, thanks.

This is what I did.

2 Likes

I’m using stoptimer nodes for the 5 second timer. (It just helps to debounce quick changes when walking past rooms)

Oh Yeah that is the Ticket, Thanks for showing me that : )

1 Like

It would be great if the esppresence could update the “Person” which currently can only be done via a device_tracker entity…

Can you please help me which entities I should create and how to create them.

Thanks
Rien

First, I love what you have done. I am having some trouble just with the “Person” sensors. Can you go into more detail on how you created the sensors in Node-RED. I can’t seem to match what you have solely based on your screenshots. For example, the Home Assistant Config (optional) I do not have that at all, nor do I have the Type section under Server. I must be doing something wrong I just cannot see it. Thanks for sharing what you have done, I can’t wait to have my version of it up and running.

1 Like

Sure, these are under the “home assistant entities” category in Node-RED. You can set up entities in here and then send them payloads to update the state.

Also the Lovelace config I posted is for configuring the UI for your dashboard, and it doesn’t go in the Home Assitant config. You can create a new view in Lovelace (under “Overview” in the menu), and then edit the page as YAML

1 Like

I’ve just set up esp pressence, followed the ios setup step by step. I’ve gotten my apple watch to be tracked. I found the irk for the iphone following the guide, but when I go into mqtt explorer, it only seems to find the irk for the watch, and never discovers my iphone?

Did you have any problems tracking your iphones?

This is awesome, got most if it implemented and learner a lot like the NodeRED companion tie in to HA through HAC’s. I think I got it all except for the distances for the devices.

Excellent work Nathan!

I want to implement the same as well, trying to understand how to work with the espresense.yaml file you linked, a few questions:

  1. Is the .yaml or .jinja file included by the configuration? You mention you use something to generate the yaml from the source jinja file? Can you provide more information about that?
  2. Is there an “espresense” addon? The first directive on the yaml file espresence: confuses me as to its purpose, can you explain what it does and how it works?

If you have your HA configuration on a repo, I’d appreciate a link.

Cheers and thank you for the amazing work!

If anyone else out there is having issues getting the Base Station table to show at the bottom like I was.

I finally figured out it was because the HTML card was looking for icons based on templets OP has set up that I do not.

Taking out the following line fixed this for me since I do not really care about the icons.

{% set icon = states.input_boolean[room.replace('nathan_s', 'nathans') ~ '_occupied'].attributes.icon %}

Hope this helps someone in the future.

Thanks for the work OP!

I would love to have this too, but also bumped creating the sensores in NodeRed, I never used it before. The device sensores was already created before, but the person sensors and all the connections is above my knowledge. If any Good Samaritan want to make a good noob tutorial for guys like me you’re welcome.
Thanks.

1 Like

can yoy give us the code for importing in node-red?
thanks!!