I’ve been using the excellent aqualinkd project to bridge my Jandy Aqualink pool control system to Home Assistant via MQTT and I would like to share the dashboard UI that I created for controlling everything about the pool. There is A LOT happening in this UI, so I will break this down into a few sections.
This post will focus on the Lovelace UI. If you are interested in my custom entity configs for aqualinkd, the author of aqualinkd has kindly let me provide my entity configs in his repository as examples. You can find them here.
Note: some of the elements are made from the button-card, and I have some related templates defined that are explained here.
Here is an example of what is displayed most of the time while the pump is running and nothing else is going on. There are several hidden items that appear under certain conditions. I will explain each one separately in the following sections.
more screenshots
The Picture Elements Card
This is the main card for the whole thing. All of the other examples are within this card. The main config for this card is all about showing the appropriate background image based on the state of the pool devices. I have a template sensor I defined that gives me a discrete state for each combination of things that I want to show. I’m using state_filter for the off condition to make the background black & white to make it obvious the system is off.
yaml
type: picture-elements
entity: sensor.aqualink_run_mode
image: /local/pictures/pool.jpg
state_filter:
'off': blur(2px) grayscale(100%)
state_image:
service: /local/pictures/pool-equipment.jpg
spa: /local/pictures/spa.jpg
spa-blowing: /local/pictures/spa.jpg
spa-heated: /local/pictures/spa.jpg
spa-heating: /local/pictures/spa.jpg
timeout: /local/pictures/pool-equipment.jpg
elements:
#...elements from other sections go here...
Freeze Protection
There are 3 elements to freeze protection:
- a switch to enable/disable
- a sensor indicating if it’s active
- a climate entity for the setpoint
There are 4 elements to this status display, 3 of them conditional. I’m currently using element types that are native to the picture elements card, but now that I have discovered the button-card I think I can dramatically simplify this when I have a chance.
yaml
- entity: switch.freeze_protection
prefix: 'freeze protect '
style:
background-color: 'rgba(0,0,0,0.4)'
color: white
height: 34px
left: 0
top: '-3px'
padding-left: 30px
transform: none
width: 100%
tap_action:
action: toggle
type: state-label
- type: conditional
conditions:
- entity: binary_sensor.freeze_protecting
state: 'off'
elements:
- entity: switch.freeze_protection
icon: 'mdi:snowflake'
style:
color: white
left: 0
top: '-5px'
transform: none
z-index: 1
tap_action:
action: toggle
type: state-icon
- type: conditional
conditions:
- entity: switch.freeze_protection
state: 'on'
elements:
- attribute: temperature
entity: climate.freeze_protect
prefix: '@ '
style:
color: white
left: 150px
top: '-3px'
transform: none
suffix: °
type: state-label
- type: conditional
conditions:
- entity: binary_sensor.freeze_protecting
state: 'on'
elements:
- icon: 'mdi:shield-alert'
style:
color: yellow
left: 8px
top: 3px
transform: none
type: icon
Pool Heater
This is very similar to the freeze protection, but with some extra conditions that the pool pump must be on and the system must be in pool mode. The spa heater displays in the same place when the system is in spa mode, so I have included the yaml for both.
yaml
- conditions:
- entity: switch.filter_pump
state: 'on'
elements:
- conditions:
- entity: switch.spa_mode
state: 'off'
elements:
- conditions:
- entity: binary_sensor.pool_heating
state: 'off'
elements:
- entity: switch.pool_heater
icon: 'mdi:fire'
style:
color: white
right: 150px
top: 2px
transform: none
tap_action:
action: toggle
confirmation:
text: Are you sure? Heating the pool is expensive!
type: icon
type: conditional
- conditions:
- entity: binary_sensor.pool_heating
state: 'on'
elements:
- entity: switch.pool_heater
icon: 'mdi:fire'
style:
color: red
right: 150px
top: 2px
transform: none
tap_action:
action: toggle
type: icon
type: conditional
- entity: switch.pool_heater
prefix: 'pool heater '
style:
color: white
right: 42px
top: '-5px'
transform: none
tap_action:
action: toggle
confirmation:
text: Are you sure? Heating the pool is expensive!
type: state-label
- attribute: temperature
entity: climate.pool_heater
prefix: '~ '
style:
color: white
right: 0
top: '-5px'
transform: none
suffix: °
type: state-label
type: conditional
- conditions:
- entity: switch.spa_mode
state: 'on'
elements:
- conditions:
- entity: binary_sensor.spa_heating
state: 'off'
elements:
- entity: switch.spa_heater
icon: 'mdi:fire'
style:
color: white
right: 150px
top: 3px
transform: none
tap_action:
action: toggle
type: icon
type: conditional
- conditions:
- entity: binary_sensor.spa_heating
state: 'on'
elements:
- entity: switch.spa_heater
icon: 'mdi:fire'
style:
color: red
right: 150px
top: 3px
transform: none
tap_action:
action: toggle
type: icon
type: conditional
- entity: switch.spa_heater
prefix: 'spa heater '
style:
color: white
right: 43px
top: '-3px'
transform: none
tap_action:
action: toggle
type: state-label
- attribute: temperature
entity: climate.spa_heater
prefix: '~ '
style:
color: white
right: 0
top: '-3px'
transform: none
suffix: °
type: state-label
type: conditional
type: conditional
Large Temperature Displays
These are just basic displays of the outside temperature, and if running, the pool or spa temperature. I used some of the button-card’s powerful styling and layout features to make it use most of the circle area.
yaml
- type: 'custom:button-card'
entity: sensor.air_temp
name: Outside
show_state: true
show_icon: false
state_display: '[[[ return Math.trunc(entity.state).toString()+"°" ]]]'
style:
transform: none
top: 35px
right: 5px
z-index: 1
styles:
grid:
- grid-template-areas: '"s"'
- grid-template-columns: 1fr
- grid-template-rows: 1fr
card:
- border-radius: 50%
- padding: 0.1em 0.75em
- background-color: 'rgba(0,0,0,0.4)'
state:
- font-size: 3.5em
name:
- font-size: 0.75em
- position: absolute
- transform: 'translate(-50%,0)'
- bottom: 0
- left: 50%
- opacity: 0.5
- conditions:
- entity: switch.filter_pump
state: 'on'
elements:
- conditions:
- entity: switch.spa_mode
state: 'off'
elements:
- type: 'custom:button-card'
entity: sensor.pool_temp
name: Pool
show_state: true
show_icon: false
size: 2em
state_display: '[[[ return Math.trunc(entity.state).toString()+"°" ]]]'
style:
transform: none
top: 115px
right: 5px
z-index: 1
styles:
grid:
- grid-template-areas: '"s"'
- grid-template-columns: 1fr
- grid-template-rows: 1fr
card:
- border-radius: 50%
- padding: 0.1em 0.75em
- background-color: 'rgba(0,0,0,0.4)'
state:
- font-size: 3.5em
name:
- font-size: 0.75em
- position: absolute
- transform: 'translate(-50%,0)'
- bottom: 0
- left: 50%
- opacity: 0.5
type: conditional
- conditions:
- entity: switch.spa_mode
state: 'on'
elements:
- type: 'custom:button-card'
entity: sensor.spa_temp
name: Spa
show_state: true
show_icon: false
size: 2em
state_display: '[[[ return Math.trunc(entity.state).toString()+"°" ]]]'
style:
transform: none
top: 115px
right: 5px
z-index: 1
styles:
grid:
- grid-template-areas: '"s"'
- grid-template-columns: 1fr
- grid-template-rows: 1fr
card:
- border-radius: 50%
- padding: 0.1em 0.75em
- background-color: 'rgba(0,0,0,0.4)'
state:
- font-size: 3.5em
name:
- font-size: 0.75em
- position: absolute
- transform: 'translate(-50%,0)'
- bottom: 0
- left: 50%
- opacity: 0.5
type: conditional
type: conditional
Main Pool Controls
For this section I used the paper-buttons-row element. I found this to be very flexible for setting up a grid of buttons and it has a nice clean look to it.
yaml
- base_config:
state_styles:
'on':
button:
background-color: lightgreen
icon:
color: var(--paper-item-icon-active-color)
text:
color: black
style:
button:
border-radius: 10px
justify-content: center
margin: 1px
padding: 0 5px 0 5px
white-space: nowrap
width: 100%
buttons:
- - entity: sensor.aqualink_pump_mode
icon: 'mdi:pool'
name: Pool
state_icons:
delay: 'mdi:timer-sand'
state_styles:
delay:
button:
background-color: yellow
icon:
color: black
text:
color: black
pool:
button:
background-color: lightgreen
icon:
color: var(--paper-item-icon-active-color)
text:
color: black
tap_action:
action: call-service
service: python_script.set_pool_mode
service_data:
mode: pool
- entity: switch.pool_heater
name: Pool Heat
- entity: switch.cleaner
- entity: switch.pool_light
name: Pool Light
- - entity: sensor.aqualink_pump_mode
icon: 'mdi:hot-tub'
name: Spa
state_icons:
delay: 'mdi:timer-sand'
state_styles:
delay:
button:
background-color: yellow
icon:
color: black
text:
color: black
spa:
button:
background-color: lightgreen
icon:
color: var(--paper-item-icon-active-color)
text:
color: black
tap_action:
action: call-service
service: python_script.set_pool_mode
service_data:
mode: spa
- entity: switch.spa_heater
name: Spa Heat
- entity: switch.spa_blower
name: Bubbles
- entity: switch.spa_light
name: Spa Light
style:
background-color: 'rgba(0,0,0,0.4)'
bottom: 0
left: 0
padding: 3px
right: 0
transform: none
type: 'custom:paper-buttons-row'
Pool Filler
This is a convenient button to turn on one of my sprinkler zones that is set up to fill the pool. Someday I’ll have water level sensors to automate the filling, but for now I am using this button. I timed how long it takes to add 1 inch of water to the pool and use that to set the duration on the sprinkler zone. My RainMachine integration gives me the time remaining, which I display on the button and it will automatically turn off when the duration expires. I’m using a button-card for this because it can handle a conditional templated status display.
yaml
- type: 'custom:button-card'
entity: switch.sprinkler_zone_12
name: |-
[[[
if (entity.state == "off") return "Fill 1\"";
var sec = parseInt(entity.attributes.time_remaining);
const h = Math.floor(sec / 3600);
const m = Math.floor((sec % 3600) / 60);
const leftPad = (num) => (num < 10 ? `0${num}` : num);
return `${leftPad(h)}:${leftPad(m)}`;
]]]
icon: 'mdi:water-pump'
layout: icon_name
tap_action:
action: |
[[[
return entity.state == "off" ? 'call-service' : 'toggle'
]]]
service: rainmachine.start_zone
service_data:
zone_id: 13
zone_run_time: 7200
state:
- value: 'on'
styles:
card:
- background-color: lightgreen
- border-radius: 8px
name:
- color: black
- font-weight: bold
styles:
card:
- background-color: transparent
- box-shadow: unset
- margin: 5px
- padding: 1px 3px
- width: 5em
name:
- font: unset
- font-size: 0.9em
icon:
- width: 1.5em
style:
background-color: 'rgba(0,0,0,0.4)'
border-radius: 15px 0 0 0
bottom: 66px
color: white
font-size: 1.2em
padding-right: 5px
right: 0
transform: none
Pool Status Display
This is the main situational status display. This is driven by a template sensor entity where I have composited the important device states of the system. There is also a secondary element to show if the battery needs to be replaced in the Jandy Aqualink panel.
more status images
yaml
- buttons:
- entity: sensor.aqualink_run_mode
state_styles:
cleaner-delay:
text:
color: yellow
cleaning:
text:
color: lightgreen
filling:
text:
color: lightblue
filter-delay:
text:
color: yellow
freeze:
text:
color: cyan
lights:
text:
color: white
'off':
icon:
color: inherit
pool:
text:
color: lightgreen
pool-heated:
icon:
color: crimson
text:
color: orange
pool-heating:
icon:
color: crimson
text:
color: orangered
service:
text:
color: red
spa:
text:
color: aquamarine
spa-blowing:
text:
color: orange
spa-heated:
icon:
color: crimson
text:
color: orange
spa-heating:
icon:
color: crimson
text:
color: orangered
timeout:
text:
color: red
state_text:
cleaner-delay: Cleaner Delay
cleaning: Cleaning The Pool
filling: Adding Water
filter-delay: Filter Delay
freeze: Freeze Protect Active
lights: Lights Are On
'off': System Off
pool: Pool Is Running
pool-heated: Pool Is Heated
pool-heating: Pool Is Heating Up
service: Service Mode
spa: Spa Is Running
spa-blowing: Spa Is Extra Bubbly
spa-heated: Spa Is Heated
spa-heating: Spa Is Heating Up
timeout: Temporary Service Mode
style:
icon:
background-color: 'rgba(0,0,0,0.4)'
border-radius: 30%
color: var(--paper-item-icon-active-color)
padding: 2px
- entity: binary_sensor.aqualink_battery
state_icons:
'on': 'mdi:battery-alert-variant'
state_text:
'off':
'on': Replace
style:
icon:
color: yellow
text:
color: yellow
style:
'--mdc-icon-size': 30px
background-color: 'rgba(0,0,0,0.4)'
border-radius: 0 15px 0 0
bottom: 66px
font-size: 1.5em
font-weight: bold
left: 0
transform: none
type: 'custom:paper-buttons-row'
Special Overlays
For fun I added some circular image overlays for certain conditions.
yaml
- entity: sensor.aqualink_run_mode
filter: opacity(0)
image: /local/pictures/pool.jpg
state_filter:
cleaner-delay: opacity(1)
cleaning: opacity(1)
filling: opacity(1)
filter-delay: opacity(1)
lights: opacity(1)
state_image:
cleaner-delay: /local/pictures/pool-cleaner.jpg
cleaning: /local/pictures/pool-cleaner.jpg
filling: /local/pictures/pool-filling.jpg
filter-delay: /local/pictures/pool-valve.jpg
lights: /local/pictures/pool-light.jpg
style:
border-radius: 50%
bottom: 120px
left: 10px
transform: none
width: 30%
type: image
Temporary System Messages
The Jandy Aqualink occasionally has special informational messages to display. I didn’t bother getting a picture of it, but I have them conditionally displayed right in the center of the UI with a simple markdown card.
yaml
- conditions:
- entity: binary_sensor.aqualink_has_message
state: 'on'
elements:
- card_type: markdown
content: |
{{ states.sensor.aqualink_message.state }}
style:
background-color: 'rgba(0,0,0,0.6)'
border-radius: 10px
font-size: 1.5em
left: 50%
text-align: center
top: 50%
z-index: 1
type: 'custom:hui-element'
type: conditional
Pool View Blinds
All of my windows on the house with a view of the pool have automated iBlinds, so why not have a nice control for them right on the pool UI?
The control I am using for the blinds is described on my other post here, so I’m not going to cover that in this post.
Summary
There is a lot of config here. Feel free to ask questions. Hopefully a few people find some inspiration from it