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
- Mushroom Cards (
custom:mushroom-template-card
) - auto-entities (
custom:auto-entities
) - Lovelace HTML Jinja2 Template card (
custom:html-template-card
)
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> {{ 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 %}
—
{%- 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
.