Pool control with a picture entities card and aqualinkd

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.

image

more screenshots

image
image
image
image
image
image
image


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

image
image
image

There are 3 elements to freeze protection:

  1. a switch to enable/disable
  2. a sensor indicating if it’s active
  3. 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

image
image
image
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

image
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

image
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

image
image
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

image
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

image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image

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

image
image
image
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

image
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 :slight_smile:

6 Likes

A quick note on the Special Overlays section. I noticed this week that they aren’t appearing when they should. I tracked it down to an odd behavior of opacity in the filter and state_filter settings. I suspect it started happening when I updated to core 0.117.x, but I haven’t downgraded my version to confirm. The original behavior was that filter: opacity(0) would keep it hidden until specific state_filter values would make it visible with opacity(1).

I poked at the opacity values to see what was going on and determined that the lower of the 2 values is what is being used. For example, if I set filter: opacity(0.5) and state_filter for the active value to opacity(0.3), the 0.3 is used. If I reverse them, 0.3 is still used.

It’s a weird bug, and I plan to report it later today.

If you are having this problem, using a transparent image as the base image works, and you can remove all the opacity settings.

  - type: image
    entity: sensor.aqualink_run_mode
    image: /local/pictures/transparent.png
    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
    style:
      border-radius: 50%
      bottom: 120px
      left: 10px
      transform: none
      width: 30%

I recently refactored my entire pool UI using the custom button card for most of the elements (still on a picture-elements card).

image

The biggest difference is the way I’m showing the temperatures. I created a compact climate button so that they are more informational about the status of my HVAC systems and the pool/spa temperatures & heaters. I also show a spinning fan icon while actively heating or cooling. I did a separate post on that here.

If you’re interested in yaml for the other changes in this picture let me know.

Good day Keith,

You have helped me with a few things on your button-card post that have gotten me this far. I was there trying to get this post working. I’m getting some things worked out but could use a possible pointer or two if I may. This is what mine looks like:

I am currently trying to get the run modes worked out. In the aqualinkd.yaml file, there is this config:

        aqualink_run_mode:
          value_template: >-
            {%- if states('sensor.aqualink_mode') == '0' %}
              AUTO
            {% elif states('sensor.aqualink_mode') == '1' %}
              SERVICE
            {% elif states('sensor.aqualink_mode') == '2' %}
              TIMEOUT
            {%- endif %}
          icon_template: >-
            {%- if states('sensor.aqualink_mode') == '0' %}
              mdi:robot
            {% elif states('sensor.aqualink_mode') == '1' %}
              mdi:cog
            {% elif states('sensor.aqualink_mode') == '2' %}
              mdi:clock
            {%- endif %}
          friendly_name: 'Aqualink Run Mode'
          - type: 'custom:paper-buttons-row'
            buttons:
              - entity: sensor.aqualink_run_mode
                state_styles:
                  cleaner-delay:
                    text:
                      color: yellow
                  cleaning:
                    text:
                      color: lightgreen
...
                state_text:
                  cleaner-delay: Cleaner Delay
                  cleaning: Cleaning The Pool
...
                style:
                  icon:
                    background-color: 'rgba(0,0,0,0.4)'
                    border-radius: 30%
                    color: var(--paper-item-icon-active-color)
                    padding: 2px

Now, I might be way off base but I thought the styles need to match the value of the sensor. In the first file, the value is set as AUTO, SERVICE, or TIMEOUT but the config is looking for cleaner-delay, cleaning, etc… Or am I just completely wrong here?

Also, if you have any insight why the temperatures are in Celsius instead of Fahrenheit which they are in the Aqualink app, that would be great! :slight_smile:

Thanks so much for all the work you are sharing. It is greatly appreciated by those of us just starting out.

@portigui, you are correct about the styles needing to match the values. I looked at my config and I must have examples here from different points in time when things got renamed in between. The aqualink_run_mode sensor you show is called aqualink_service_mode in my current config, and the aqualink_run_mode sensor is below. That should make more sense. There may still be some differences, but at least it’s the right sensor :slight_smile:

aqualink_run_mode sensor:
#all the pool modes rolled into one sensor so I can switch the picture elements background and mode indicator accordingly
aqualink_run_mode:
  value_template: >-
    {%- if is_state('sensor.aqualink_mode', '1') -%}
      service
    {%- elif is_state('sensor.aqualink_mode', '2') -%}
      timeout
    {%- elif is_state('binary_sensor.filter_pump_delay', 'on') -%}
      filter-delay
    {%- elif is_state('binary_sensor.cleaner_delay', 'on') -%}
      cleaner-delay
    {%- elif is_state('binary_sensor.freeze_protecting', 'on') -%}
      freeze
    {%- elif is_state('switch.sprinkler_zone_12', 'on') -%}
      filling
    {%- elif is_state('switch.spa_mode', 'on') and is_state('binary_sensor.spa_heating', 'on') -%}
      spa-heating
    {%- elif is_state('switch.spa_mode', 'on') and is_state('switch.spa_heater', 'on') -%}
      spa-heated
    {%- elif is_state('switch.spa_mode', 'on') and is_state('switch.spa_blower', 'on') -%}
      spa-blowing
    {%- elif is_state('switch.spa_mode', 'on') -%}
      spa
    {%- elif is_state('switch.filter_pump', 'on') and is_state('binary_sensor.pool_heating', 'on') -%}
      pool-heating
    {%- elif is_state('switch.filter_pump', 'on') and is_state('switch.pool_heater', 'on') -%}
      pool-heated
    {%- elif is_state('switch.filter_pump', 'on') and is_state('switch.cleaner', 'on') -%}
      cleaning
    {%- elif is_state('switch.filter_pump', 'on') -%}
      pool
    {%- elif is_state('switch.spa_light', 'on') or is_state('switch.pool_light', 'on') -%}
      lights
    {%- else -%}
      off
    {%- endif -%}
  icon_template: >-
    {%- if is_state('sensor.aqualink_mode', '1') -%}
      mdi:cog
    {%- elif is_state('sensor.aqualink_mode', '2') -%}
      mdi:clock
    {%- elif is_state('binary_sensor.filter_pump_delay', 'on') -%}
      mdi:timer-sand
    {%- elif is_state('binary_sensor.cleaner_delay', 'on') -%}
      mdi:timer-sand
    {%- elif is_state('binary_sensor.freeze_protecting', 'on') -%}
      mdi:snowflake
    {%- elif is_state('switch.sprinkler_zone_12', 'on') -%}
      mdi:water-pump
    {%- elif is_state('switch.spa_mode', 'on') and is_state('binary_sensor.spa_heating', 'on') -%}
      mdi:fire
    {%- elif is_state('switch.spa_mode', 'on') and is_state('switch.spa_heater', 'on') -%}
      mdi:hot-tub
    {%- elif is_state('switch.spa_mode', 'on') and is_state('switch.spa_blower', 'on') -%}
      mdi:hot-tub
    {%- elif is_state('switch.spa_mode', 'on') -%}
      mdi:hot-tub
    {%- elif is_state('switch.filter_pump', 'on') and is_state('binary_sensor.pool_heating', 'on') -%}
      mdi:fire
    {%- elif is_state('switch.filter_pump', 'on') and is_state('switch.pool_heater', 'on') -%}
      mdi:pool
    {%- elif is_state('switch.filter_pump', 'on') and is_state('switch.cleaner', 'on') -%}
      mdi:broom
    {%- elif is_state('switch.filter_pump', 'on') -%}
      mdi:pool
    {%- elif is_state('switch.spa_light', 'on') or is_state('switch.pool_light', 'on') -%}
      mdi:lightbulb
    {%- else -%}
      mdi:power-sleep
    {%- endif -%}

I’m not sure about the C vs. F for the temps. My system is using F consistently. I’ve heard that some Aqualink systems report C on the MQTT. You might need to modify the MQTT sensors for the temps to convert from C to F.

@ktownsend-personal Any chance you could include complete config for this card? I’ld love to use yours as a starting point to iterate from… Its quite awesome !

2 Likes

@johntdyer, thanks for the praise :slight_smile:

I don’t have the original config anymore, but I can give you the current config I’m using. Be aware…this is a LOT of yaml.

image

I have used the custom button card extensively in this design. I recommend getting familiar with that if you aren’t already. Especially the templates feature.

Note that I’ve switched to manually managing my dashboards as yaml files so that I can re-use common elements more easily with !include directives. If you aren’t familiar with the manual way, you can start here with the basics. I loosely followed the excellent example of organizing all the files that @frenck has available on github here.

If you want to do this with a UI configured dashboard, you should be able to get the same result if you merge pool.yaml and pool/*.yaml files into a single block of yaml, and merge the template files into the right section of the raw dashboard.

The pool.yaml file is the starting point, which your dashboard would !include. See the end for an example dashboard. This file uess !include_dir_list to merge in all the individual element files.

config/dashboards/common/cards/pool.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: !include_dir_list ./pool

These files are used by pool.yaml:

config/dashboards/common/cards/pool/aqualink-message.yaml
type: 'custom:button-card'
entity: sensor.aqualink_message
show_icon: false
show_state: true
show_name: false
styles:
  card:
    - display: '[[[ return entity.state.length > 0 ? "block" : "none" ]]]'
    - background-color: 'rgba(0,0,0,0.6)'
    - padding: 0.1em 0.3em
    - z-index: 1
    - width: 380px
  state:
    - font-size: 1.2em
    - white-space: normal
style:
  left: 50%
  text-align: center
  top: 58%
  z-index: 1```
config/dashboards/common/cards/pool/aqualink-mode.yaml
type: 'custom:button-card'
template: container
entity: sensor.aqualink_run_mode
show_name: false
show_icon: false
style:
  border-radius: 0 15px 0 0
  bottom: 60px
  left: 0
  transform: none
  z-index: 1
styles:
  card:
    - width: auto
    - box-shadow: none
    - background-color: transparent
  custom_fields:
    buttons:
      - background-color: transparent
custom_fields:
  buttons:
    card:
      type: horizontal-stack
      cards:
        - type: 'custom:button-card'
          template: wide
          color_type: icon
          entity: sensor.aqualink_run_mode
          name: |
            [[[
              switch(entity.state){
                case "cleaner-delay": return "Cleaner Delay";
                case "cleaning": return "Cleaning The Pool";
                case "filling": return "Adding Water";
                case "filter-delay": return "Filter Delay";
                case "freeze": return "Freeze Protecting";
                case "lights": return "Lights Are On";
                case "off": return "System Off";
                case "pool": return "Pool Is Running";
                case "pool-heated": return "Pool Is Heated";
                case "pool-heating": return "Pool Is Heating Up";
                case "service": return "Service Mode";
                case "spa": return "Spa Is Running";
                case "spa-blowing": return "Spa Is Extra Bubbly";
                case "spa-heated": return "Spa Is Heated";
                case "spa-heating": return "Spa Is Heating Up";
                case "timeout": return "Temporary Service Mode";
                default: return entity.state;
              }
            ]]]
          styles:
            card:
              - background-color: transparent
              - padding: 0 5px 0 0
              - box-shadow: none
              - width: auto
            icon:
              - background-color: 'rgba(0,0,0,0.0)'
              - border-radius: 30%
              - opacity: 1
              - padding: 2px 0
              - width: 30px
              - color: |
                  [[[
                    switch(entity.state){
                      case "pool-heated":
                      case "pool-heating":
                      case "spa-heated":
                      case "spa-heating": return "crimson";
                      default: return "var(--paper-item-icon-color)";
                    }
                  ]]]
            name:
              - white-space: nowrap
              - font-size: 1.27em
              - font-weight: bold
              - color: |
                  [[[
                    switch(entity.state){
                      case "cleaner-delay":
                      case "filter-delay": return "yellow";
                      case "cleaning":
                      case "pool": return "lightgreen";
                      case "filling": return "lightblue";
                      case "freeze": return "cyan";
                      case "lights": return "white";
                      case "pool-heated":
                      case "spa-blowing":
                      case "spa-heated": return "orange";
                      case "pool-heating":
                      case "spa-heating": return "orangered";
                      case "service": 
                      case "timeout": return "red";
                      case "spa": return "aquamarine";
                      default: return "white";
                    }
                  ]]]
        - type: 'custom:button-card'
          template: toggle
          entity: binary_sensor.aqualink_battery
          icon: 'mdi:battery-alert-variant'
          name: Replace
          styles:
            card:
              - width: auto
            icon:
              - color: yellow
              - width: 30px
            name:
              - color: yellow
              - font-size: 1.2em
              - font-weight: bold
          state:
            - value: 'off'
              styles:
                card:
                  - display: none
config/dashboards/common/cards/pool/blinds.yaml
entity: cover.pool_view_blinds
type: 'custom:button-card'
template: presets
name: Blinds
show_name: false
color_type: icon
icon: 'mdi:blinds'
variables:
  entity: cover.pool_view_blinds
  option_aspect: 1.2/1
  option_template: blind-option
  option_button_on_color: lightgreen
  option_text_on_color: black
  option_button_off_color: 'rgba(0,0,0,0.5)'
  option1_name: ANG
  option1_set_value: 85
  option1_range_start: 61
  option1_range_stop: 98
  option2_name: OPN
  option2_set_value: 50
  option2_range_start: 2
  option2_range_stop: 60
  option3_name: CLN
  option3_set_value: 0
  option3_range_start: 0
  option3_range_stop: 1
  option4_name: CLS
  option4_set_value: 100
  option4_range_start: 99
  option4_range_stop: 100
  option4_highlight: false
styles:
  card:
    - width: 180px
    - background-color: transparent
    - box-shadow: none
  icon:
    - color: var(--paper-item-icon-color)
    - opacity: 1
style:
  border-radius: 10px 0 0 0
  right: 0
  bottom: 62px
  transform: none
  z-index: 1
config/dashboards/common/cards/pool/buttons.yaml
type: 'custom:button-card'
style:
  background-color: 'rgba(0,0,0,0.4)'
  -webkit-backdrop-filter: blur(4px)
  backdrop-filter: blur(4px)
  bottom: 0
  left: 0
  right: 0
  padding-top: 35px
  transform: none
styles:
  card:
    - background-color: transparent
    - padding: 3px
    - box-shadow: none
  grid:
    - grid-template-areas: '"card1 card2 card3 card4" "card5 card6 card7 card8"'
    - grid-template-columns: auto auto auto auto
    - grid-template-rows: 30px 30px
    - justify-content: space-evenly
  custom_fields:
    card1:
      - max-width: unset !important
custom_fields:
  card1:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.sprinkler_zone_12
      icon: 'mdi:water-pump'
      name: |-
        [[[ 
          var e = states["switch.sprinkler_zone_12"];
          if (e.state == "off") return "Fill 1\"";
          var sec = parseInt(e.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)}`;
        ]]]
      styles:
        card:
          - padding: 0 10px
        icon:
          - margin-right: 5px
      tap_action:
        action: |
          [[[
            var e = states["switch.sprinkler_zone_12"];
            return e.state == "off" ? 'call-service' : 'toggle'
          ]]]
        service: rainmachine.start_zone
        service_data:
          entity_id: switch.sprinkler_zone_12
          zone_id: 13
          zone_run_time: 7200
      style:
        background-color: 'rgba(0,0,0,0.4)'
        border-radius: 10px 0 0 0
        bottom: 66px
        color: white
        font-size: 1.2em
        padding: 2px 5px 0
        right: 0
        transform: none
  card2:
    card:
      type: 'custom:button-card'
      template: poolmode
      name: Pool
      entity: sensor.aqualink_pump_mode
      icon: 'mdi:pool'
      variables:
        mode: pool
        on_state: pool
  card3:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.cleaner
  card4:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.pool_light
      name: Pool Light
  card6:
    card:
      type: 'custom:button-card'
      template: poolmode
      name: Spa
      entity: sensor.aqualink_pump_mode
      icon: 'mdi:hot-tub'
      variables:
        mode: spa
        on_state: spa
  card7:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.spa_blower
      name: Bubbles
      state:
        - id: on_state
          spin: true
  card8:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.spa_light
      name: Spa Light
config/dashboards/common/cards/pool/climate-downstairs.yaml
type: 'custom:button-card'
template: bigtemp
entity: climate.downstairs
variables:
  device_name: HVAC
style:
  transform: none
  top: 85px
  right: 5px
config/dashboards/common/cards/pool/climate-garage.yaml
type: custom:button-card
template: bigtemp-sensor
entity: sensor.garage_motion_temperature
name: Garage
style:
  transform: none
  top: 85px
  left: 130px
config/dashboards/common/cards/pool/climate-office.yaml
type: custom:button-card
template: bigtemp-sensor
entity: sensor.office_motion_temperature
name: Office
style:
  transform: none
  top: 85px
  left: 5px
  z-index: 10
state:
  - index: hot-cutoff
    value: 120
config/dashboards/common/cards/pool/climate-outside.yaml
type: 'custom:button-card'
template: bigtemp
entity: climate.freeze_protect
name: Outside
variables:
  device_name: Run at
  alt_humidity_entity: weather.home
  alt_humidity_attr: humidity
  color_setpoint_cool: 'rgba(255,255,0,0.6)'
  icon_setpoint_cool: 'mdi:shield-alert'
  text_setpoint_cool: green
  icon_mode_disabled: true
style:
  transform: none
  top: 5px
  left: 5px
config/dashboards/common/cards/pool/climate-pool.yaml
type: 'custom:button-card'
template: bigtemp
entity: climate.pool_heater
name: Pool
variables:
  device_name: Heater
  hide: >-
    [[[ return states["switch.filter_pump"].state == "off" ||
    states["switch.spa_mode"].state == "on" ]]]
style:
  transform: none
  top: 5px
  left: 130px
config/dashboards/common/cards/pool/climate-spa.yaml
type: 'custom:button-card'
template: bigtemp
entity: climate.spa_heater
name: Spa
variables:
  device_name: Heater
  hide: >-
    [[[ return states["switch.filter_pump"].state == "off" ||
    states["switch.spa_mode"].state == "off" ]]]
style:
  transform: none
  top: 5px
  left: 130px
config/dashboards/common/cards/pool/climate-upstairs.yaml
type: 'custom:button-card'
template: bigtemp
entity: climate.upstairs
variables:
  device_name: HVAC
style:
  transform: none
  top: 5px
  right: 5px
config/dashboards/common/cards/pool/mode-overlay.yaml
type: image
entity: sensor.aqualink_run_mode
image: /local/pictures/transparent.png
# filter: opacity(0)
# image: /local/pictures/pool.jpg
# state_filter:
#   cleaner-delay: opacity(1)
#   cleaning: opacity(1)
#   filling: opacity(1)
#   filter-delay: 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
style:
  border-radius: 50%
  bottom: 120px
  left: 10px
  transform: none
  width: 30%

See next post for the rest of the info…there is a limit of 32,000 characters per post apparently.

1 Like

Continuing from prior post…

The following custom-button-card templates are referenced by the above files. These are !include into the dashboard file in the property button-card-templates. See example dashboard file after these templates.

config/dashboards/common/button-card-templates/container.yaml
color_type: label-card
color: transparent
styles:
  card:
    - padding: 0
  name:
    - border-radius: 0.4em 0.4em 0 0
    - padding: 0.1em
    - width: 100%
    - font-weight: bold
  grid:
    - grid-template-areas: '"i" "n" "buttons"'
    - grid-template-columns: 1fr
    - grid-template-rows: 1fr min-content min-content
  custom_fields:
    buttons:
      - background-color: 'rgba(0,0,0,0.3)'
      - margin: 0
      - padding: 0.3em
config/dashboards/common/button-card-templates/wide.yaml
template: standard
styles:
  grid:
    - display: contents
  img_cell:
    - display: contents
  icon:
    - margin: 0
    - margin-right: 0.1em
    - width: 40px
    - position: relative
  name:
    - font-size: 0.8em
config/dashboards/common/button-card-templates/toggle.yaml
template: wide
color_type: icon
variables:
  on_state: 'on'
  button_color_on: lightgreen
  on_text_color: black
state:
  - value: '[[[ return variables.on_state ]]]'
    id: on_state
    styles:
      card:
        - background-color: '[[[ return variables.button_color_on ]]]'
      name:
        - color: '[[[ return variables.on_text_color ]]]'
      icon:
        - color: var(--paper-item-icon-active-color)
styles:
  card:
    - background-color: 'rgba(0,0,0,0.2)'
    - border-radius: 10px
    - box-shadow: none
    - padding: 0 5px 0 0
    - width: 100%
    - min-width: 100px
  name:
    - font-size: 14px
    - white-space: nowrap
  state:
    - margin-left: 2px
    - font-size: 14px
    - white-space: nowrap
  icon:
    - width: 24px
    - opacity: 1
    - padding: 2px 0
config/dashboards/common/button-card-templates/standard.yaml
color_type: card
size: 80%
hold_action:
  action: more-info
styles:
  card:
    - padding: 0.2em
    - '--mdc-ripple-color': yellow
    - '--mdc-ripple-press-opacity': 0.5
  icon:
    - opacity: 0.5
  name:
    - font-size: 0.65em
    - white-space: normal
  state:
    - font-size: 0.65em
    - white-space: normal
  label:
    - font-size: 0.4em
    - white-space: normal
config/dashboards/common/button-card-templates/presets.yaml
template: standard
color_type: icon
tap_action:
  action: none
styles:
  card:
    - background-color: 'rgba(0,0,0,0.3)'
  icon:
    - color: white
  grid:
    - grid-template-areas: '"i opt1 opt2 opt3 opt4" "n opt1 opt2 opt3 opt4"'
    - grid-template-columns: 1fr 1fr 1fr 1fr 1fr
  custom_fields:
    opt1:
      - margin: 0.1em
      - overflow: visible
    opt2:
      - margin: 0.1em
      - overflow: visible
    opt3:
      - margin: 0.1em
      - overflow: visible
    opt4:
      - margin: 0.1em
      - overflow: visible
variables:
  option_aspect: 1/1
  option_template: dimmer-option
  option_button_on_color: var(--paper-item-icon-active-color)
  option_button_on2_color: var(--paper-card-background-color)
  option_button_off_color: var(--paper-card-background-color)
  option_text_on_color: white
  option_text_on2_color: white
  option_text_off_color: white
  option1_name: Low
  option1_set_value: 51
  option1_range_start: 1
  option1_range_stop: 77
  option1_highlight: true
  option2_name: Med
  option2_set_value: 102
  option2_range_start: 78
  option2_range_stop: 170
  option2_highlight: true
  option3_name: High
  option3_set_value: 255
  option3_range_start: 171
  option3_range_stop: 255
  option3_highlight: true
  option4_name: 'Off'
  option4_set_value: 0
  option4_range_start: 0
  option4_range_stop: 0
  option4_highlight: true
custom_fields:
  opt1:
    card:
      type: 'custom:button-card'
      entity: '[[[ return variables.entity ]]]'
      template: '[[[ return variables.option_template ]]]'
      name: '[[[ return variables.option1_name ]]]'
      aspect_ratio: '[[[ return variables.option_aspect ]]]'
      variables:
        set_value: '[[[ return variables.option1_set_value ]]]'
        range_start: '[[[ return variables.option1_range_start ]]]'
        range_stop: '[[[ return variables.option1_range_stop ]]]'
        highlight: '[[[ return variables.option1_highlight ]]]'
        option_button_on_color: '[[[ return variables.option_button_on_color ]]]'
        option_button_on2_color: '[[[ return variables.option_button_on2_color ]]]'
        option_button_off_color: '[[[ return variables.option_button_off_color ]]]'
        option_text_on_color: '[[[ return variables.option_text_on_color ]]]'
        option_text_on2_color: '[[[ return variables.option_text_on2_color ]]]'
        option_text_off_color: '[[[ return variables.option_text_off_color ]]]'
  opt2:
    card:
      type: 'custom:button-card'
      entity: '[[[ return variables.entity ]]]'
      template: '[[[ return variables.option_template ]]]'
      name: '[[[ return variables.option2_name ]]]'
      aspect_ratio: '[[[ return variables.option_aspect ]]]'
      variables:
        set_value: '[[[ return variables.option2_set_value ]]]'
        range_start: '[[[ return variables.option2_range_start ]]]'
        range_stop: '[[[ return variables.option2_range_stop ]]]'
        highlight: '[[[ return variables.option2_highlight ]]]'
        option_button_on_color: '[[[ return variables.option_button_on_color ]]]'
        option_button_on2_color: '[[[ return variables.option_button_on2_color ]]]'
        option_button_off_color: '[[[ return variables.option_button_off_color ]]]'
        option_text_on_color: '[[[ return variables.option_text_on_color ]]]'
        option_text_on2_color: '[[[ return variables.option_text_on2_color ]]]'
        option_text_off_color: '[[[ return variables.option_text_off_color ]]]'
  opt3:
    card:
      type: 'custom:button-card'
      entity: '[[[ return variables.entity ]]]'
      template: '[[[ return variables.option_template ]]]'
      name: '[[[ return variables.option3_name ]]]'
      aspect_ratio: '[[[ return variables.option_aspect ]]]'
      variables:
        set_value: '[[[ return variables.option3_set_value ]]]'
        range_start: '[[[ return variables.option3_range_start ]]]'
        range_stop: '[[[ return variables.option3_range_stop ]]]'
        highlight: '[[[ return variables.option3_highlight ]]]'
        option_button_on_color: '[[[ return variables.option_button_on_color ]]]'
        option_button_on2_color: '[[[ return variables.option_button_on2_color ]]]'
        option_button_off_color: '[[[ return variables.option_button_off_color ]]]'
        option_text_on_color: '[[[ return variables.option_text_on_color ]]]'
        option_text_on2_color: '[[[ return variables.option_text_on2_color ]]]'
        option_text_off_color: '[[[ return variables.option_text_off_color ]]]'
  opt4:
    card:
      type: 'custom:button-card'
      entity: '[[[ return variables.entity ]]]'
      template: '[[[ return variables.option_template ]]]'
      name: '[[[ return variables.option4_name ]]]'
      aspect_ratio: '[[[ return variables.option_aspect ]]]'
      variables:
        set_value: '[[[ return variables.option4_set_value ]]]'
        range_start: '[[[ return variables.option4_range_start ]]]'
        range_stop: '[[[ return variables.option4_range_stop ]]]'
        highlight: '[[[ return variables.option4_highlight ]]]'
        option_button_on_color: '[[[ return variables.option_button_on_color ]]]'
        option_button_on2_color: '[[[ return variables.option_button_on2_color ]]]'
        option_button_off_color: '[[[ return variables.option_button_off_color ]]]'
        option_text_on_color: '[[[ return variables.option_text_on_color ]]]'
        option_text_on2_color: '[[[ return variables.option_text_on2_color ]]]'
        option_text_off_color: '[[[ return variables.option_text_off_color ]]]'
config/dashboards/common/button-card-templates/blind-option.yaml
template: option-button
tap_action:
  service: cover.set_cover_position
  service_data:
    position: '[[[ return variables.set_value ]]]'
variables:
  match_type: range
  match_attribute: current_position
config/dashboards/common/button-card-templates/dimmer-option.yaml
template: option-button
tap_action:
  service: light.turn_on
  service_data:
    brightness: '[[[ return variables.set_value ]]]'
variables:
  match_type: range
  match_attribute: brightness
config/dashboards/common/button-card-templates/option-button.yaml
template: standard
show_icon: true
aspect_ratio: 1/1
styles:
  grid:
    - display: contents
  img_cell:
    - display: contents
  card:
    - border-radius: 5px
  icon:
    - color: white
    - position: absolute
    - opacity: 0.2
state:
  - id: state_off
    operator: default
    styles:
      img_cell:
        - display: none
      card:
        - background-color: '[[[ return variables.option_button_off_color ]]]'
        - color: '[[[ return variables.option_text_off_color ]]]'
  - id: state_on
    operator: template
    value: |
      [[[
        switch (variables.match_type) {
          case "range":
            return (entity.attributes[variables.match_attribute]||0) >= variables.range_start
                    && (entity.attributes[variables.match_attribute]||0) <= variables.range_stop;
          case "match":
            return entity.attributes.speed == variables.set_value; 
        }
      ]]]
    styles:
      card:
        - background-color: |
            [[[ 
              return variables.highlight  
                ? variables.option_button_on_color 
                : variables.option_button_on2_color
            ]]]
        - color: |
            [[[ 
              return variables.highlight
                ? variables.option_text_on_color 
                : variables.option_text_on2_color
            ]]]
tap_action:
  action: call-service
  service_data:
    entity_id: entity
config/dashboards/common/button-card-templates/poolmode.yaml
template: toggle
variables:
  mode: null
styles:
  icon:
    - padding-left: 3px
    - padding-right: 3px
state:
  - value: delay
    icon: 'mdi:timer-sand'
    styles:
      card:
        - background-color: yellow
      name:
        - color: black
      icon:
        - color: black
tap_action:
  action: call-service
  service: python_script.set_pool_mode
  service_data:
    mode: '[[[ return variables.mode ]]]'
config/dashboards/common/button-card-templates/bigtemp.yaml
variables:
  hide: false
  color: 'rgba(0,0,0,0.3)'
  backdrop_filter: blur(4px)
  device_name: Set at
  alt_humidity_entity: null
  alt_humidity_attr: null
  color_setpoint_off: 'rgba(0,0,0,0.4)'
  color_setpoint_idle: 'rgba(144,238,144,0.6)'
  color_setpoint_cool: 'rgba(0,0,255,0.6)'
  color_setpoint_heat: 'rgba(255,0,0,0.6)'
  color_setpoint_wait: 'rgba(255,255,0,0.6)'
  icon_setpoint_off: 'mdi:thermometer-off'
  icon_setpoint_idle: null
  icon_setpoint_cool: 'mdi:snowflake'
  icon_setpoint_heat: 'mdi:fire'
  icon_setpoint_wait: 'mdi:timer-sand'
  text_setpoint_off: white
  text_setpoint_idle: black
  text_setpoint_cool: white
  text_setpoint_heat: white
  text_setpoint_wait: black
  icon_mode_disabled: false
show_icon: true
icon: 'mdi:fan'
show_state: true
state_display: |-
  [[[ 
    return Math.trunc(entity.attributes.current_temperature || entity.state).toString()+"°" 
  ]]]
custom_fields:
  device: |
    [[[
      //if(entity.attributes.temperature || entity.attributes.target_temp_low || entity.attributes.target_temp_high)
        return `<div style="line-height:1">${variables.device_name}</div>`;
    ]]]
  humidity:
    card:
      type: 'custom:button-card'
      show_icon: true
      icon: 'mdi:water-percent'
      show_name: false
      show_state: true
      state_display: |
        [[[
          if (entity.attributes.current_humidity) 
            return entity.attributes.current_humidity;
          var alt = variables.alt_humidity_entity && states[variables.alt_humidity_entity];
          var attr = alt && alt.attributes[variables.alt_humidity_attr];
          return attr;
        ]]]
      styles:
        grid:
          - grid-template-areas: '"s i"'
        img_cell:
          - display: contents
        state:
          - font-size: 0.8em
          - align-self: flex-end
        card:
          - padding: 0
          - padding-left: 2px
          - background-color: 'rgba(0,0,0,0.6)'
        icon:
          - width: 18px
          - height: 15px
          - margin-top: '-4px'
          - margin-left: '-4px'
          - margin-right: '-3px'
  mode:
    card:
      type: 'custom:button-card'
      show_icon: true
      show_name: false
      show_state: false
      icon: |
        [[[
          if(variables.icon_setpoint_idle || variables.icon_mode_disabled) return '';
          switch(entity.attributes.hvac_action) {
            case "cool":
            case "cooling":
            case "heat":
            case "heating":
              return '';
            default:
              if (entity.attributes.hvac_mode == 3)
                return '';
              switch(entity.state){
                case "heat":
                  return variables.icon_setpoint_heat;
                case "cool":
                  return variables.icon_setpoint_cool;
              }
          }
        ]]]
      styles:
        card:
          - background-color: transparent
        icon:
          - width: 18px
          - color: white
  setpoint:
    card:
      type: 'custom:button-card'
      show_icon: true
      show_name: true
      show_state: true
      state_display: |
        [[[ 
          return entity.attributes.temperature 
            ? entity.attributes.temperature + "°" 
            : entity.attributes.target_temp_low
              ? entity.attributes.target_temp_low + "°" 
              : ''
        ]]]
      name: |
        [[[ 
          return entity.attributes.target_temp_high
            ? entity.attributes.target_temp_high + "°" 
            : ''
        ]]]
      icon: |
        [[[
          switch(entity.attributes.hvac_action) {
            case "cool":
            case "cooling":
              return variables.icon_setpoint_cool;
            case "heat":
            case "heating":
              return variables.icon_setpoint_heat;
            default:
              if (entity.attributes.hvac_mode == 3)
                return variables.icon_setpoint_wait;
              return entity.state != "off"
                ? variables.icon_setpoint_idle
                : entity.attributes.temperature
                  ? ''
                  : variables.icon_setpoint_off 
          }
        ]]]
      styles:
        icon:
          - width: 18px
          - color: |
              [[[ 
                switch(entity.attributes.hvac_action) {
                  case "cool":
                  case "cooling":
                    return variables.text_setpoint_cool;
                  case "heat":
                  case "heating":
                    return variables.text_setpoint_heat;
                  default:
                    if (entity.attributes.hvac_mode == 3)
                      return variables.text_setpoint_wait;
                    return entity.state == "off"
                      ? variables.text_setpoint_off
                      : variables.text_setpoint_idle 
                }
              ]]]
        state:
          - font-size: 0.8em
          - color: |
              [[[ 
                switch(entity.attributes.hvac_action) {
                  case "cool":
                  case "cooling":
                    return variables.text_setpoint_cool;
                  case "heat":
                  case "heating":
                    return variables.text_setpoint_heat;
                  default:
                    if (entity.attributes.hvac_mode == 3)
                      return variables.text_setpoint_wait;
                    return entity.state == "off"
                      ? variables.text_setpoint_off
                      : variables.text_setpoint_idle 
                }
              ]]]
        name:
          - font-size: 0.8em
          - color: |
              [[[ 
                switch(entity.attributes.hvac_action) {
                  case "cool":
                  case "cooling":
                    return variables.text_setpoint_cool;
                  case "heat":
                  case "heating":
                    return variables.text_setpoint_heat;
                  default:
                    if (entity.attributes.hvac_mode == 3)
                      return variables.text_setpoint_wait;
                    return entity.state == "off"
                      ? variables.text_setpoint_off
                      : variables.text_setpoint_idle 
                }
              ]]]
        card:
          - padding: 0 2px
          - background-color: |
              [[[ 
                switch(entity.attributes.hvac_action) {
                  case "cool":
                  case "cooling":
                    return variables.color_setpoint_cool;
                  case "heat":
                  case "heating":
                    return variables.color_setpoint_heat;
                  default:
                    if (entity.attributes.hvac_mode == 3)
                      return variables.color_setpoint_wait;
                    return entity.state == "off"
                      ? variables.color_setpoint_off
                      : variables.color_setpoint_idle 
                }
              ]]]
state:
  - id: value_any
    operator: '!='
    value: all
    spin: true
styles:
  card:
    - background-color: '[[[ return variables.color || "transparent" ]]]'
    - -webkit-backdrop-filter: '[[[ return variables.backdrop_filter || "blur(4px)" ]]]'
    - backdrop-filter: '[[[ return variables.backdrop_filter || "blur(4px)" ]]]'
    - overflow: visible
    - box-shadow: none
    - padding: 2px 0 5px 2px
    - display: '[[[ return variables.hide ? "none" : "flex" ]]]'
    - width: fit-content
    - margin-right: 15px
  grid:
    - display: contents
  img_cell:
    - display: contents
  icon:
    - width: 70%
    - position: absolute
    - left: 3px
    - color: silver
    - display: >-
        [[[ return entity.attributes.fan_state &&
        entity.attributes.fan_state == 1 ? "block" : "none" ]]]
  state:
    - color: var(--primary-text-color)
    - font-size: 3.5em
    - text-shadow: 0 0 2px black
    - overflow: visible
    - z-index: 1
    - margin-top: '-10px'
  name:
    - color: var(--primary-text-color)
    - text-shadow: 0 0 2px black
    - overflow: visible
    - font-size: 0.75em
    - font-weight: 600
    - position: absolute
    - transform: rotate(90deg)
    - transform-origin: right bottom
    - bottom: 0
    - right: 0
    - z-index: 10
  custom_fields:
    device:
      - text-shadow: 0 0 0.2em black
      - overflow: visible
      - font-size: 0.6em
      - position: absolute
      - bottom: 1px
      - right: 28px
      - z-index: 1
    mode:
      - position: absolute
      - bottom: 18px
      - right: 2px
    setpoint:
      - position: absolute
      - bottom: 0
      - right: 0
      - z-index: 1
    humidity:
      - z-index: 2
      - position: absolute
      - bottom: 0
      - left: 0
      - display: |
          [[[ 
            return entity.attributes.current_humidity == undefined 
                    && !variables.alt_humidity_entity
              ? "none" 
              : "flex" 
          ]]]
config/dashboards/common/button-card-templates/bigtemp-sensor.yaml
type: custom:button-card
show_state: true
show_icon: false
show_name: true
show_label: true
state_display: '[[[ return parseInt(entity.state) + "°" ]]]'
label: '[[[ return "." + entity.state.split(".")[1] ]]]'
extra_styles: |
  [[[ return `
    @keyframes pulse {
      5% {
        background-color: rgba(240,52,52, 0.9);
      }
    }
  `]]]
state:
  - index: cold-cutoff
    value: 32
    operator: '<='
    styles:
      card:
        - animation: pulse ease-in-out 1s infinite
  - index: hot-cutoff
    value: 100
    operator: '>='
    styles:
      card:
        - animation: pulse ease-in-out 1s infinite
styles:
  grid:
    - display: contents
  img_cell:
    - display: contents
  card:
    - background-color: rgba(0, 64, 255, 0.3)
    - -webkit-backdrop-filter: blur(4px)
    - backdrop-filter: blur(4px)
    - display: flex
    - padding: 2px 0 5px 2px
    - width: fit-content
    - overflow: visible
  state:
    - color: var(--primary-text-color)
    - font-size: 3.5em
    - line-height: 0.75em
    - text-shadow: 0 0 2px black
    - overflow: visible
    - z-index: 1
  name:
    - color: var(--primary-text-color)
    - text-shadow: 0 0 2px black
    - overflow: visible
    - font-size: 0.75em
    - font-weight: 600
    - position: absolute
    - transform: rotate(90deg)
    - transform-origin: right bottom
    - bottom: 0
    - right: 0
    - z-index: 10
  label:
    - color: var(--primary-text-color)
    - position: absolute
    - bottom: 0
    - left: 2.4em
    - font-size: 1.6em
    - font-weight: 600
    - text-shadow: 0 0 2px black
    - overflow: visible

Example dashboard file. Note where it uses !include for the pool.yaml file.

config/dashboards/dash-lounge/dash-lounge.yaml
title: Lounge
button_card_templates: !include_dir_named ../common/button-card-templates
views:
  - title: Lounge
    path: lounge
    panel: true
    cards:
      - type: horizontal-stack
        cards:
          - type: vertical-stack
            cards:
              - !include ../cards/entry-and-formal.yaml
              - !include ../cards/eating-patio-lounge.yaml
              - !include ../cards/garage-upstairs.yaml
              - !include ../cards/motivation-with-date.yaml
          - type: vertical-stack
            cards:
              - !include ../cards/perimeter.yaml
              - !include ../cards/pool.yaml    

Example main dashboard yaml:

config/dashboards/dashboards.yaml
dash-lounge:
  mode: yaml
  title: Lounge
  show_in_sidebar: false
  filename: dashboards/dash-lounge/dash-lounge.yaml
dash-master-suite:
  mode: yaml
  title: Master Suite
  show_in_sidebar: false
  filename: dashboards/dash-master-suite/dash-master-suite.yaml

The above main dashboard file is referenced in your configuration.yaml file:

config/configuration.yaml
# Define mixed mode Lovelace dashboards
lovelace:
  mode: storage
  dashboards: !include dashboards/dashboards.yaml
1 Like

Hey Thanks for this info - I decided to get back into HA after being gone for about 2 years, and I feel like I’ve forgotten everything.

I was running MQTT to my aqualinkD in habitat, with moderate success, but your configuration is much better. I did have a question. Were you able to set up the pool or spa lights to a light show or specific color from a single button? I figured I would asked before I start ripping my theoretical hair out (cuz I’m bald :slight_smile: ).

@AwShifts,

You’re welcome. I spent a lot of hours on the pool integration and I like to share the benefits of the results.

I just have simple white incandescent lights on my pool, so I never tinkered with colors or light shows.

Good choice of hairstyle :slight_smile:

Is there a repository to download all these files? I might have missed it. Please let me know.

@heffneil, no repository but all the code is in several collapsed yaml sections in the main post. If you missed them, just click each one to see the yaml.

Hi Keith i really love your pool dashboard design! I’ve been trying to get it to work for days in my home assistant environment and i cant seem to get to work. I’m trying to set this up using the using the UI configured dashboards. i see that you mentioned all that needs to be done is to merge the pool.yamal and pool/*,yaml files into a single block and also add the templates in the correct section of the raw dashboard and of course, replace all of the entities with my entities. no matter what i try and i cant figure it out.

i think the biggest issue i have is with indentation when i combined all of the YAML files together or the order of which im pasting each yaml file. Are you able to send me the combined yaml code that i can paste into directly into home assistant as well as the combined templates with the correct indentation ?

Any assistance you can provide is extremely apricated!

Thanks
Mike

@miannelli516, here is an example of merging pool.yaml with two of the child yaml files.

The crucial point is that each of the child yaml files is that it is indented under elements and the contents of each file are prefixed with - on the first line because they are an array of cards to show on the picture elements card.

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:
  - type: 'custom:button-card'
    entity: sensor.aqualink_message
    show_icon: false
    show_state: true
    show_name: false
    styles:
      card:
        - display: '[[[ return entity.state.length > 0 ? "block" : "none" ]]]'
        - background-color: 'rgba(0,0,0,0.6)'
        - padding: 0.1em 0.3em
        - z-index: 1
        - width: 380px
      state:
        - font-size: 1.2em
        - white-space: normal
    style:
      left: 50%
      text-align: center
      top: 58%
      z-index: 1
  - type: 'custom:button-card'
    template: bigtemp
    entity: climate.downstairs
    variables:
      device_name: HVAC
    style:
      transform: none
      top: 85px
      right: 5px

Thanks so much Keith you made my day! ive been having fun with this all day long. Thanks to your reply and helpful information i was able to get almost everything working! one question i have is, how can i make the icons/buttons at the bottom bigger (the ones for the pumps, cleaner & lights. i would like them to be a little bigger? below is a screenshot of how everything is looking so far. i also pasted my raw code as well.

Thanks again!

title: 
views:
  - theme: Backend-selected
    title: test
    path: test
    visible:
      - user: 6db56098e8664f91abe6ddfeab4e74af
      - user: ca12ecc937f646d3b2472c64387a4127
      - user: 74a88362b6e14703a2618de589981e2c
    type: panel
    badges: []
    cards:
      - type: picture-elements
        entity: sensor.pool_current_status
        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
        aspect_ratio: 1
        elements:
          - type: custom:button-card
            template: bigtemp
            entity: climate.pentair_0e_79_2b_pool_heat
            variables:
              device_name: ''
            style:
              transform: none
              top: 85px
              right: 5px
          - type: custom:button-card
            entity: sensor.pool_current_water_tempature
            attribute: current_temperature
            name: Outside
            show_state: true
            show_icon: false
            state_display: '[[[ return Math.trunc(entity.state).toString()+"°" ]]]'
            style:
              transform: none
              top: 160px
              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.pentair_0e_79_2b_cleaner
                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
          - type: custom:button-card
            entity: sensor.pentair_0e_79_2b_air_temperature
            name: Outside
            show_state: true
            show_icon: false
            state_display: '[[[ return Math.trunc(entity.state).toString()+"°" ]]]'
            style:
              transform: none
              top: 245px
              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
          - 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: switch.pentair_0e_79_2b_pool
                  icon: mdi:engine
                  name: Pool Pump
                  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: toggle
                    service_data:
                      mode: pool
                - entity: light.pentair_0e_79_2b_pool_light
                  name: Pool Light
                - entity: number.pentair_0e_79_2b_scg_level_1
                  name: Chlorine  Generator
                - entity: ''
                  name: ''
              - - entity: switch.pentair_0e_79_2b_cleaner
                  icon: mdi:vacuum
                  name: Cleaner Pump
                  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: climate.pentair_0e_79_2b_pool_heat
                  name: Pool Heater
                  icon: mdi:fire
                - entity: ''
                  name: ''
                - entity: ''
                  name: ''
            style:
              background-color: rgba(0,0,0,0.4)
              bottom: 0
              left: 0
              padding: 3px
              right: 0
              transform: none
            type: custom:paper-buttons-row
          - type: custom:button-card
            template: container
            entity: sensor.pool_current_status
            show_name: false
            show_icon: false
            style:
              border-radius: 0 15px 0 0
              bottom: 60px
              left: 0
              transform: none
              z-index: 1
            styles:
              card:
                - width: auto
                - box-shadow: none
                - background-color: transparent
              custom_fields:
                buttons:
                  - background-color: transparent
            custom_fields:
              buttons:
                card:
                  type: horizontal-stack
                  cards:
                    - type: custom:button-card
                      template: wide
                      color_type: icon
                      entity: sensor.pool_current_status
                      name: |
                        [[[
                          switch(entity.state){
                            case "cleaner-delay": return "Cleaner Delay";
                            case "cleaning": return "Cleaning The Pool";
                            case "filling": return "Adding Water";
                            case "filter-delay": return "Filter Delay";
                            case "freeze": return "Freeze Protecting";
                            case "lights": return "Lights Are On";
                            case "off": return "System Off";
                            case "pool": return "Pool Is Running";
                            case "pool-heated": return "Pool Is Heated";
                            case "pool-heating": return "Pool Is Heating Up";
                            case "service": return "Service Mode";
                            case "spa": return "Spa Is Running";
                            case "spa-blowing": return "Spa Is Extra Bubbly";
                            case "spa-heated": return "Spa Is Heated";
                            case "spa-heating": return "Spa Is Heating Up";
                            case "timeout": return "Temporary Service Mode";
                            default: return entity.state;
                          }
                        ]]]
                      styles:
                        card:
                          - background-color: transparent
                          - padding: 0 5px 0 0
                          - box-shadow: none
                          - width: auto
                        icon:
                          - background-color: rgba(0,0,0,0.0)
                          - border-radius: 30%
                          - opacity: 1
                          - padding: 2px 0
                          - width: 30px
                          - color: |
                              [[[
                                switch(entity.state){
                                  case "pool-heated":
                                  case "pool-heating":
                                  case "spa-heated":
                                  case "spa-heating": return "crimson";
                                  default: return "var(--paper-item-icon-color)";
                                }
                              ]]]
                        name:
                          - white-space: nowrap
                          - font-size: 1.27em
                          - font-weight: bold
                          - color: |
                              [[[
                                switch(entity.state){
                                  case "cleaner-delay":
                                  case "filter-delay": return "yellow";
                                  case "cleaning":
                                  case "pool": return "lightgreen";
                                  case "filling": return "lightblue";
                                  case "freeze": return "cyan";
                                  case "lights": return "white";
                                  case "pool-heated":
                                  case "spa-blowing":
                                  case "spa-heated": return "orange";
                                  case "pool-heating":
                                  case "spa-heating": return "orangered";
                                  case "service": 
                                  case "timeout": return "red";
                                  case "spa": return "aquamarine";
                                  default: return "white";
                                }
                              ]]]
                    - type: custom:button-card
                      template: toggle
                      entity: zone.home
                      icon: mdi:battery-alert-variant
                      name: Replace
                      styles:
                        card:
                          - width: auto
                        icon:
                          - color: yellow
                          - width: 30px
                        name:
                          - color: yellow
                          - font-size: 1.2em
                          - font-weight: bold
                      state:
                        - value: 'off'
                          styles:
                            card:
                              - display: none
button_card_templates:
  bigtemp:
    variables:
      hide: false
      color: rgba(0,0,0,0.3)
      backdrop_filter: blur(4px)
      device_name: Set at
      alt_humidity_entity: null
      alt_humidity_attr: null
      color_setpoint_off: rgba(0,0,0,0.4)
      color_setpoint_idle: rgba(144,238,144,0.6)
      color_setpoint_cool: rgba(0,0,255,0.6)
      color_setpoint_heat: rgba(255,0,0,0.6)
      color_setpoint_wait: rgba(255,255,0,0.6)
      icon_setpoint_off: mdi:thermometer-off
      icon_setpoint_idle: null
      icon_setpoint_cool: mdi:snowflake
      icon_setpoint_heat: mdi:fire
      icon_setpoint_wait: mdi:timer-sand
      text_setpoint_off: white
      text_setpoint_idle: black
      text_setpoint_cool: white
      text_setpoint_heat: white
      text_setpoint_wait: black
      icon_mode_disabled: false
    show_icon: true
    icon: mdi:fan
    show_state: true
    state_display: |-
      [[[ 
        return Math.trunc(entity.attributes.current_temperature || entity.state).toString()+"°" 
      ]]]
    custom_fields:
      device: |
        [[[
          //if(entity.attributes.temperature || entity.attributes.target_temp_low || entity.attributes.target_temp_high)
            return `<div style="line-height:1">${variables.device_name}</div>`;
        ]]]
      humidity:
        card:
          type: custom:button-card
          show_icon: true
          icon: mdi:water-percent
          show_name: false
          show_state: true
          state_display: |
            [[[
              if (entity.attributes.current_humidity) 
                return entity.attributes.current_humidity;
              var alt = variables.alt_humidity_entity && states[variables.alt_humidity_entity];
              var attr = alt && alt.attributes[variables.alt_humidity_attr];
              return attr;
            ]]]
          styles:
            grid:
              - grid-template-areas: '"s i"'
            img_cell:
              - display: contents
            state:
              - font-size: 0.8em
              - align-self: flex-end
            card:
              - padding: 0
              - padding-left: 2px
              - background-color: rgba(0,0,0,0.6)
            icon:
              - width: 18px
              - height: 15px
              - margin-top: '-4px'
              - margin-left: '-4px'
              - margin-right: '-3px'
      mode:
        card:
          type: custom:button-card
          show_icon: true
          show_name: false
          show_state: false
          icon: |
            [[[
              if(variables.icon_setpoint_idle || variables.icon_mode_disabled) return '';
              switch(entity.attributes.hvac_action) {
                case "cool":
                case "cooling":
                case "heat":
                case "heating":
                  return '';
                default:
                  if (entity.attributes.hvac_mode == 3)
                    return '';
                  switch(entity.state){
                    case "heat":
                      return variables.icon_setpoint_heat;
                    case "cool":
                      return variables.icon_setpoint_cool;
                  }
              }
            ]]]
          styles:
            card:
              - background-color: transparent
            icon:
              - width: 18px
              - color: white
      setpoint:
        card:
          type: custom:button-card
          show_icon: true
          show_name: true
          show_state: true
          state_display: |
            [[[ 
              return entity.attributes.temperature 
                ? entity.attributes.temperature + "°" 
                : entity.attributes.target_temp_low
                  ? entity.attributes.target_temp_low + "°" 
                  : ''
            ]]]
          name: |
            [[[ 
              return entity.attributes.target_temp_high
                ? entity.attributes.target_temp_high + "°" 
                : ''
            ]]]
          icon: |
            [[[
              switch(entity.attributes.hvac_action) {
                case "cool":
                case "cooling":
                  return variables.icon_setpoint_cool;
                case "heat":
                case "heating":
                  return variables.icon_setpoint_heat;
                default:
                  if (entity.attributes.hvac_mode == 3)
                    return variables.icon_setpoint_wait;
                  return entity.state != "off"
                    ? variables.icon_setpoint_idle
                    : entity.attributes.temperature
                      ? ''
                      : variables.icon_setpoint_off 
              }
            ]]]
          styles:
            icon:
              - width: 18px
              - color: |
                  [[[ 
                    switch(entity.attributes.hvac_action) {
                      case "cool":
                      case "cooling":
                        return variables.text_setpoint_cool;
                      case "heat":
                      case "heating":
                        return variables.text_setpoint_heat;
                      default:
                        if (entity.attributes.hvac_mode == 3)
                          return variables.text_setpoint_wait;
                        return entity.state == "off"
                          ? variables.text_setpoint_off
                          : variables.text_setpoint_idle 
                    }
                  ]]]
            state:
              - font-size: 0.8em
              - color: |
                  [[[ 
                    switch(entity.attributes.hvac_action) {
                      case "cool":
                      case "cooling":
                        return variables.text_setpoint_cool;
                      case "heat":
                      case "heating":
                        return variables.text_setpoint_heat;
                      default:
                        if (entity.attributes.hvac_mode == 3)
                          return variables.text_setpoint_wait;
                        return entity.state == "off"
                          ? variables.text_setpoint_off
                          : variables.text_setpoint_idle 
                    }
                  ]]]
            name:
              - font-size: 0.8em
              - color: |
                  [[[ 
                    switch(entity.attributes.hvac_action) {
                      case "cool":
                      case "cooling":
                        return variables.text_setpoint_cool;
                      case "heat":
                      case "heating":
                        return variables.text_setpoint_heat;
                      default:
                        if (entity.attributes.hvac_mode == 3)
                          return variables.text_setpoint_wait;
                        return entity.state == "off"
                          ? variables.text_setpoint_off
                          : variables.text_setpoint_idle 
                    }
                  ]]]
            card:
              - padding: 0 2px
              - background-color: |
                  [[[ 
                    switch(entity.attributes.hvac_action) {
                      case "cool":
                      case "cooling":
                        return variables.color_setpoint_cool;
                      case "heat":
                      case "heating":
                        return variables.color_setpoint_heat;
                      default:
                        if (entity.attributes.hvac_mode == 3)
                          return variables.color_setpoint_wait;
                        return entity.state == "off"
                          ? variables.color_setpoint_off
                          : variables.color_setpoint_idle 
                    }
                  ]]]
    state:
      - id: value_any
        operator: '!='
        value: all
        spin: true
    styles:
      card:
        - background-color: '[[[ return variables.color || "transparent" ]]]'
        - '-webkit-backdrop-filter': '[[[ return variables.backdrop_filter || "blur(4px)" ]]]'
        - backdrop-filter: '[[[ return variables.backdrop_filter || "blur(4px)" ]]]'
        - overflow: visible
        - box-shadow: none
        - padding: 2px 0 5px 2px
        - display: '[[[ return variables.hide ? "none" : "flex" ]]]'
        - width: fit-content
        - margin-right: 15px
      grid:
        - display: contents
      img_cell:
        - display: contents
      icon:
        - width: 70%
        - position: absolute
        - left: 3px
        - color: silver
        - display: >-
            [[[ return entity.attributes.fan_state &&
            entity.attributes.fan_state == 1 ? "block" : "none" ]]]
      state:
        - color: var(--primary-text-color)
        - font-size: 3.5em
        - text-shadow: 0 0 2px black
        - overflow: visible
        - z-index: 1
        - margin-top: '-10px'
      name:
        - color: var(--primary-text-color)
        - text-shadow: 0 0 2px black
        - overflow: visible
        - font-size: 0.75em
        - font-weight: 600
        - position: absolute
        - transform: rotate(90deg)
        - transform-origin: right bottom
        - bottom: 0
        - right: 0
        - z-index: 10
      custom_fields:
        device:
          - text-shadow: 0 0 0.2em black
          - overflow: visible
          - font-size: 0.6em
          - position: absolute
          - bottom: 1px
          - right: 28px
          - z-index: 1
        mode:
          - position: absolute
          - bottom: 18px
          - right: 2px
        setpoint:
          - position: absolute
          - bottom: 0
          - right: 0
          - z-index: 1
        humidity:
          - z-index: 2
          - position: absolute
          - bottom: 0
          - left: 0
          - display: |
              [[[ 
                return entity.attributes.current_humidity == undefined 
                        && !variables.alt_humidity_entity
                  ? "none" 
                  : "flex" 
              ]]]
  container:
    color_type: label-card
    color: transparent
    styles:
      card:
        - padding: 0
      name:
        - border-radius: 0.4em 0.4em 0 0
        - padding: 0.1em
        - width: 100%
        - font-weight: bold
      grid:
        - grid-template-areas: '"i" "n" "buttons"'
        - grid-template-columns: 1fr
        - grid-template-rows: 1fr min-content min-content
      custom_fields:
        buttons:
          - background-color: rgba(0,0,0,0.3)
          - margin: 0
          - padding: 0.3em
  wide:
    template: standard
    styles:
      grid:
        - display: contents
      img_cell:
        - display: contents
      icon:
        - margin: 0
        - margin-right: 0.1em
        - width: 40px
        - position: relative
      name:
        - font-size: 0.8em
  toggle:
    template: wide
    color_type: icon
    variables:
      on_state: 'on'
      button_color_on: lightgreen
      on_text_color: black
    state:
      - value: '[[[ return variables.on_state ]]]'
        id: on_state
        styles:
          card:
            - background-color: '[[[ return variables.button_color_on ]]]'
          name:
            - color: '[[[ return variables.on_text_color ]]]'
          icon:
            - color: var(--paper-item-icon-active-color)
    styles:
      card:
        - background-color: rgba(0,0,0,0.2)
        - border-radius: 10px
        - box-shadow: none
        - padding: 0 5px 0 0
        - width: 100%
        - height: 200%
        - min-width: 100px
      name:
        - font-size: 14px
        - white-space: nowrap
      state:
        - margin-left: 2px
        - font-size: 14px
        - white-space: nowrap
      icon:
        - width: 24px
        - opacity: 1
        - padding: 2px 0
  standard:
    color_type: card
    size: 80%
    hold_action:
      action: more-info
    styles:
      card:
        - padding: 0.2em
        - '--mdc-ripple-color': yellow
        - '--mdc-ripple-press-opacity': 0.5
      icon:
        - opacity: 0.5
      name:
        - font-size: 0.65em
        - white-space: normal
      state:
        - font-size: 0.65em
        - white-space: normal
      label:
        - font-size: 0.4em
        - white-space: normal
  poolmode:
    template: toggle
    variables:
      mode: null
    styles:
      icon:
        - padding-left: 3px
        - padding-right: 3px
    state:
      - value: delay
        icon: mdi:timer-sand
        styles:
          card:
            - background-color: yellow
          name:
            - color: black
          icon:
            - color: black
    tap_action:
      action: call-service
      service: python_script.set_pool_mode
      service_data:
        mode: '[[[ return variables.mode ]]]'

Might try setting a height or bigger font size in base_config:style:button:

There should be good info at the github repository for the paper-buttons-row card. I don’t have the link handy, but I’m sure an internet search will find it. It’s been a long time since I set that up, so it’s long gone from my memory.

This is absolutely amazing. I have it working with the the aqualink integration here Jandy iAqualink - Home Assistant . I had to change some sensors and made a couple custom sensors to make deciphering pool mode and UV index easier. I am having one issues that I can’t get fixed since I went to separate yaml files. My buttons are stacked and wont space evenly. I did not change anything in the poolmode, standard, toggle, and wide template that buttons.yaml uses. I did change sensor and switch info to match mine but that is it. Any ideas what could be causing this? Any help is greatly appreciated.

@salphonso, that’s definitely unusual for the buttons to stack. A while back, sometime after creating this thread I refactored the buttons with a different technique. Since I’m not using the paper buttons row card anymore, maybe there is something specific about that card that isn’t working. My revised yaml for the buttons is below in case you might find it useful. It makes use of nesting multiple button cards inside another button card.

revised buttons.yaml
type: 'custom:button-card'
style:
  background-color: 'rgba(0,0,0,0.4)'
  -webkit-backdrop-filter: blur(4px)
  backdrop-filter: blur(4px)
  bottom: 0
  left: 0
  right: 0
  padding-top: 35px
  transform: none
styles:
  card:
    - background-color: transparent
    - padding: 3px
    - box-shadow: none
  grid:
    - grid-template-areas: '"card1 card2 card3 card4" "card5 card6 card7 card8"'
    - grid-template-columns: auto auto auto auto
    - grid-template-rows: 30px 30px
    - justify-content: space-evenly
  custom_fields:
    card1:
      - max-width: unset !important
custom_fields:
  card1:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.geronimo_zone_12
      icon: 'mdi:water-pump'
      name: |-
        [[[ 
          var e = states["switch.geronimo_zone_12"];
          if (e.state == "off") return "Fill 1\"";
          //var sec = parseInt(e.attributes.time_remaining); //this attribute was removed in v2022.5.0 so now we have to manually calculate from new zone completion time entity
          setTimeout(()=>this.update(), 5000); //force recalculation every 5 seconds because the entity state isn't changing anymore
          var completionUTC = Date.parse(states["sensor.geronimo_zone_12_run_completion_time"].state);
          var ticksRemaining = completionUTC - new Date();
          if (ticksRemaining < 0) return "starting"; //handle old stale completion time until entity gets updated value
          var sec = ticksRemaining / 1000;
          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)}`;
        ]]]
      styles:
        card:
          - padding: 0 10px
        icon:
          - margin-right: 5px
      tap_action:
        action: |
          [[[
            var e = states["switch.geronimo_zone_12"];
            return e.state == "off" ? 'call-service' : 'toggle'
          ]]]
        service: rainmachine.start_zone
        service_data:
          entity_id: switch.geronimo_zone_12
          zone_run_time: 7200
      style:
        background-color: 'rgba(0,0,0,0.4)'
        border-radius: 10px 0 0 0
        bottom: 66px
        color: white
        font-size: 1.2em
        padding: 2px 5px 0
        right: 0
        transform: none
  card2:
    card:
      type: 'custom:button-card'
      template: poolmode
      name: Pool
      entity: sensor.aqualink_pump_mode
      icon: 'mdi:pool'
      variables:
        mode: pool
        on_state: pool
  card3:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.cleaner
  card4:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.pool_light
      name: Pool Light
  card6:
    card:
      type: 'custom:button-card'
      template: poolmode
      name: Spa
      entity: sensor.aqualink_pump_mode
      icon: 'mdi:hot-tub'
      variables:
        mode: spa
        on_state: spa
  card7:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.spa_blower
      name: Bubbles
      state:
        - id: on_state
          spin: true
  card8:
    card:
      type: 'custom:button-card'
      template: toggle
      entity: switch.spa_light
      name: Spa Light

It sounds like you are doing yaml dashboards and !include mapping to organize them…which is exactly what I’m doing now too. The buttons.yaml file above is in a folder named pool, along with files for each of the other overlay items, and the whole directory is mapped to my picture-elements card’s elements property.

pool.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: !include_dir_list ./pool
1 Like

Thanks for the quick reply. You are correct, I am doing the yaml with mapping. Comparing the files after a quick break i figured out what the problem was. I did not notice it before but card5 for you is a in the grid but not actually a card in existence, making it sort of a placeholder/blank card. I just omitted a card from my grid and had 7 instead of 8 which made it act as it did. Once I added an 8th placeholder card it spaces them out evenly.

  grid:
    - grid-template-areas: '"card1 card2 card3 card4" "card5 card6 card7 card8"'
    - grid-template-columns: auto auto auto auto
    - grid-template-rows: 30px 30px
    - justify-content: space-evenly

Thanks again for sharing this. It has also changed the way I am doing all my dashboards now.

1 Like

How does sensor.aqualink_pump_mode button in buttons.yaml work exactly ? I assume that is calling python_script.set_pool_mode, can you share that perhaps?