A different take on designing a Lovelace UI

Hi
I have an error in logs : TemplateError('TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'') while processing template 'Template("{{ this.attributes.values() | sum }}")' for attribute '_attr_native_value' in entity 'sensor.template_updates'
This error come from version_updates.yaml, line 51 {{ this.attributes.values() | sum }}
How to fix it ? Did I miss something in my fles ?

Thanks

Hello all,
I have a question about the persons and the last_change sensor.
I would have the quite gladly exchanged against my proximity sensor, so that there displays the current distance instead of the last change.
However, dipping the sensor through the proximity has not helped :confused:

Update: Figured it out. You have to modify the fire-dom-event action in each of your popup yaml files as described here.

Just updated to browser mod 2 today and I noticed I can’t open any popups. I haven’t changed anything in the ui-lovelace.yaml file, but here’s an example of one of my buttons with a tap action to open a popup:

cards:
  - type: button
    icon: mdi:security
    tap_action:
      !include popup/sidebar_security.yaml
    hold_action:
      action: none

Searched through this thread to see if anyone else has already had this issue but couldn’t find anything. Any pointers?

sidebar_security.yaml is a custom popup, browser mod 2 is completely rewritten FAQ

Are all popups broken or just popups that you have written?

Have you migrated your custom popups to the new browser mod 2 syntax? like so

Have you completed the full browser mod install? Quickstart

This doenst work for me.

Have to change config to browser_mod w

action: fire-dom-event
browser_mod:
  service: browser_mod.popup
  data:
    title: Cameras
    style:
      .: |
        :host .main-title {
          pointer-events: none;
        }
      $: |
        .mdc-dialog__surface {
          background: transparent !important;
          border-style: none !important;
          border: 0px !important;
          box-shadow: none;
        }

    card:
      type: vertical-stack
      cards:
        - type: custom:layout-card
          layout_type: grid
          layout:
            grid-gap: 0.4vw
            #grid-template-columns: 860px 860px
            grid-template-rows: 540px 540px
            grid-template-areas: |
              "cam1 cam2"
              "cam3 cam4"

            mediaquery:
              #hide_header: false
              #phone
              "(max-width: 800px)":
                grid-gap: 1.5vw
                grid-template-columns: auto
                grid-template-rows: auto auto auto auto
                grid-template-areas: |
                  "cam1"
                  "cam2"
                  "cam3"
                  "cam4"

              #tablet
              "(max-width: 2000px)":
                grid-gap: 0.5vw
                grid-template-rows: 23vw 23vw
                grid-template-areas: |
                  "cam1 cam2"
                  "cam3 cam4"

          cards:
            - type: picture-entity
              entity: camera.garagemfrente
              aspect_ratio: 50%
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam1

            - type: picture-entity
              entity: camera.varandafrente
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam2

            - type: picture-entity
              entity: camera.varandafundo
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam3

            - type: picture-entity
              entity: camera.terraco
              show_info: false
              show_state: false
              show_name: false
              camera_view: auto
              view_layout:
                grid-area: cam4

WIP kitchen timer card

this weekend I worked on a kitchen timer card for my kitchen dashboard, this is still a work in progress. I wanted to put it up to see if anyone had any thoughts

there are 3 sections,
the 1st is the time input that I got from this post (thanks @Mattias_Persson and @ParalaX)
this is where how you can enter a time for the timer to run in hours and minutes, the last enter time is saved, and tapping the button will start the timer

the 2ed sections, is the countdown, if a timer is running this is displayed that shows how long the timer has remaining, and can be tapped to stop a running timer, a lot of work went into drawing the outline around the circle as the timer ticks down the circle outline will fill in.


The last section is just 4 buttons that can be used to start common timers with one click, this section can be shown by swiping

setup

you will need an input date time and a timer Entity, along with the following script that is used to start the timer with the input from the card

script

kitchen_timer:
  alias: calculates the time given for the timer and then starts the kitchen timer,
  fields:
    time_input:
      description: Entity id of the date time input
      example: input_datetime.kitchen_timer
    timer:
      description: Entity id of the timer to set
      example: timer.kitchen_timer
  sequence:
  - service: timer.start
    data:
      duration:  '{{ states(time_input) }}'
    target:
      entity_id: '{{ timer }}'
  mode: single

code

use

      #################################################
      #                                               #
      #                     Timer                     #
      #                                               #
      #################################################

      - type: grid
        title: Timer ↔
        view_layout:
          grid-area: timer
        columns: 1
        cards: !include decks/kitchen_timer_deck.yaml

kitchen_timer_deck.yaml

  #################################################
  #                                               #
  #             kitchen timer deck                #
  #                                               #
  #################################################
- type: custom:swipe-card
  start_card: 1
  parameters:
    roundLengths: true
    effect: coverflow
    speed: 650
    spaceBetween: 20
    threshold: 7
    coverflowEffect:
      rotate: 80
      depth: 300
  cards:
# page 1
    - type: horizontal-stack
      cards:
      # show input if timer is idle, else show countdown
        - type: conditional
          conditions:
            - entity: timer.kitchen_timer
              state: idle
          card:
            #################################################
            #                                               #
            #                     Input                     #
            #                                               #
            #################################################
            type: custom:button-card
            name: >
              [[[ return ' '; ]]]
            state_display: Kitchen Timer
            template:
              - base
            custom_fields:
              time:
                card:
                  type: custom:time-picker-card
                  entity: input_datetime.kitchen_timer
                  hide:
                    name: true
                  card_mod:
                    style:
                      .: |
                        .time-picker-content {
                          justify-content: space-evenly !important;
                          padding-right: 1%;
                        }
                        .time-separator {
                          color: rgba(255, 255, 255, 0.3);
                        }
                        .time-picker-row {
                          display: block !important;
                          padding: 0 !important;
                          overflow: hidden !important;
                        }
                        .time-input {
                          border-radius: 10px;
                        }
                        :host {
                          --ha-card-border-width: 0px;
                          --time-picker-elements-background-color: rgba(0, 0, 0, 0.15);
                          --time-picker-icon-color: rgba(255, 255, 255, 0.4);
                          --time-picker-text-color: rgba(255, 255, 255, 0.6);
                          --time-picker-control-padding: 6px;
                        }
                      time-unit:
                        $: |
                          .time-input {
                            border-radius: 0.4vw;
                          }
                          .time-unit {
                            padding: 0 !important;
                          }
            styles:
              custom_fields:
                time:
                  - position: absolute
                  - width: 100%
                  - height: 80%
                  - clip-path: inset(0 round var(--custom-button-card-border-radius))
                  - left: 0
                  - top: 15%
            tap_action:
              action: call-service
              service: script.kitchen_timer
              service_data:
                time_input: input_datetime.kitchen_timer
                timer: timer.kitchen_timer
              
        - type: conditional
          conditions:
            - entity: timer.kitchen_timer
              state_not: idle
          card:
            #################################################
            #                                               #
            #                   Countdown                   #
            #                                               #
            #################################################
            type: custom:button-card
            entity: timer.kitchen_timer
            template:
              - base
            tap_action:
              action: call-service
              service: timer.cancel
              service_data:
                entity_id: timer.kitchen_timer
            custom_fields:
              countdown: >
                [[[
                  setTimeout(() => {

                    let elt = this.shadowRoot,
                    circle_stroke = elt.getElementById('circle_stroke'),
                    r = 22.1,
                    c = r * 2 * Math.PI,
                    now = new Date().getTime(),
                    endDate = new Date(entity.attributes.finishes_at),
                    remaining = entity.attributes.remaining.split(':'),
                    duration = entity.attributes.duration.split(':'),
                    startDate = new Date(endDate.getTime() - (remaining[0]*3600+remaining[1]*60)*1000),
                    percent = ((now - startDate) / (endDate - startDate)) * 100;
                    circle_stroke.style.strokeDashoffset = c - percent / 100  * c;
                    circle_stroke.style.strokeWidth = 'var(--c-stroke-width-dragging)';
                  }, 0);

                  let r = 22.1,
                  c = r * 2 * Math.PI,
                  state = true,
                  input = variables.circle_input || ' ';

                  return `
                  <svg viewBox="0 0 50 50">
                    <style>
                      circle {
                        transform: rotate(-90deg);
                        transform-origin: 50% 50%;
                        stroke-dasharray: ${c};
                        stroke-dashoffset: ${typeof input === 'number' && c - input / 100 * c};
                        stroke-width: var(--c-stroke-width);
                        
                      }
                      #circle_stroke{
                        stroke: ${'var(--c-stroke-color-on)' };
                        fill: ${'var(--c-fill-color-on)'};
                      }
                      #circle_bg {
                        stroke: ${'var(--c-stroke-color-off)'};
                        fill: ${'var(--c-fill-color-off)'};

                      }
                      text {
                        font-size: var(--c-font-size);
                        font-weight: var(--c-font-weight);
                        letter-spacing: var(--c-letter-spacing);
                        fill: var(--c-font-color);
                      }
                      tspan {
                        font-size: var(--c-unit-font-size);
                      }
                      #circle_value, tspan {
                        text-anchor: middle;
                        dominant-baseline: central;
                      }
                    </style>
                    <circle id="circle_stroke" cx="25" cy="25" r="${r}"/>
                    <circle id="circle_bg" cx="25" cy="25" r="${r}"/>
                  </svg>       `;           
                ]]]
            styles:
              state:
                - position: relative
                - line-height: 0px
                - overflow: visible
                - top: -2.5em
                - margin: auto
                - display: initial
                - --button-card-font-size: 4vw
              card:
                - --c-stroke-color-on: '#b0b0b0'
                - --c-stroke-color-off: none
                - --c-fill-color-on: none
                - --c-fill-color-off: rgba(255,255,255,0.04)
                - --c-stroke-width: 2.3
                - --c-stroke-width-dragging: 1
                - --c-font-color: '#97989c'
                - --c-font-size: 14px
                - --c-unit-font-size: 10.5px
                - --c-font-weight: 700
                - --c-letter-spacing: -0.02rem
              custom_fields:
                countdown:
                  - position: absolute
                  - width: 100%
                  - height: 100%
                  - left: 0
                  - top: -8%
                  - display: initial
                  - opacity: 1
                  - justify-self: end
# page 2
    - type: grid
      columns: 2
      cards:
        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_plex
          template: 
            - base
          name: 5 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-two-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:05:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_youtube
          template: 
            - base
          name: 25 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-five-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:25:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_netflix
          template: 
            - base
          name: 45 min
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-eight-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "00:45:00"
              entity_id: timer.kitchen_timer

        - type: custom:button-card
          entity: binary_sensor.template_living_room_tv_source_funimation
          template: 
            - base
          name: 1 hour
          show_state: false
          custom_fields:
            icon: <ha-icon icon="mdi:clock-time-twelve-outline"></ha-icon>
          tap_action:
            action: call-service
            service: timer.start
            service_data:
              duration: "01:00:00"
              entity_id: timer.kitchen_timer
14 Likes

hi,

i have the same Problem.

Can you post a screenshot of your home assistance folder with all sub folders expanded?

@Laffer has made his config available, there is no “problem” you are just using outdated code.

2 Likes

So I ended up rebuilding the card and managed to fix the glitchy scrolls by eliminating the horizontal stack card, but still couldn’t avoid a normally behaving vertical scroll appearing (despite the popup having enough space that it shouldn’t vertically scroll)

So I tried adding the scene chips to the top of the card instead, which eliminated all scroll entirely!
I also did a quick check that a horizontal stack behaves normally when it’s the first card of the bunch, and it does.

My code is missing some of the card mod styling yours had, but seems to display correctly on all my devices:

Code
light:
  template:
    - base
    - circle
    - loader
  double_tap_action:
    action: fire-dom-event
    browser_mod:
      service: browser_mod.popup
      data:
        title: >
          [[[
            return !entity || entity.attributes.friendly_name;
          ]]]
        content:
          type: vertical-stack
          cards:
            - type: custom:mushroom-chips-card
              card_mod:
                style: |
                  ha-card {
                    --chip-background: rgba(var(--rgb-primary-text-color), 0.05);
                  }
              alignment: center
              chips: >
                [[[
                  if (entity) {
                    let id = Boolean(entity.attributes.entity_id)
                          ? [entity.entity_id].concat(entity.attributes.entity_id)
                          : [entity.entity_id];
                    let foundSenes = Object.keys(states).filter(s => s.indexOf("scene.") > -1).map(s=>states[s]).filter(s=>s.attributes.entity_id.some(e=>id.includes(e))),
                    scenes = [];
                    for (let i = 0; i < foundSenes.length; i++) {
                      scenes.push({"type": "entity",
                        "entity": foundSenes[i].entity_id,
                        "content_info": "",
                        "tap_action": {
                          "action": "call-service",
                          "service": "scene.turn_on",
                          "target": {
                            "entity_id": foundSenes[i].entity_id
                          },
                          "data": {
                            "transition": "1"
                          }
                        }
                      });
                    }
                    return variables.show_scenes? scenes: [];
                  }
                ]]]
            - type: entities
              card_mod:
                style: |
                  #states {
                    margin-top: -1em;
                  }
              entities: >
                [[[
                  if (entity) {
                      let lights = [],
                          id = Boolean(entity.attributes.entity_id)
                              ? [entity.entity_id].concat(entity.attributes.entity_id)
                              : [entity.entity_id];
                      for (let i = 0; i < id.length; i++) {
                          lights.push({
                              "type": "custom:mushroom-light-card",
                              "entity": id[i],
                              "fill_container": false,
                              "primary_info": "name",
                              "secondary_info": "state",
                              "icon_type": "icon",
                              "show_brightness_control": true,
                              "use_light_color": true,
                              "show_color_temp_control": true,
                              "show_color_control": true,
                              "collapsible_controls": false
                          });
                      }

                      return lights;
                  }
                ]]]
  variables:
    circle_input: >
      [[[
        if (entity) {
            // if light group get brightness from child to remove bounce
            let child = entity.attributes.entity_id,
                brightness = child && states[child[0]].attributes.brightness
                    ? Math.round(states[child[0]].attributes.brightness / 2.54)
                    : Math.round(entity.attributes.brightness / 2.54);
            return brightness === 0 && entity.state !== 'off'
                ? 1
                : brightness
        }
      ]]]
    circle_input_unit: "%"
    show_scenes: true

I bought styled chips back, and dropped the name display. I also eliminated some dead space below the scene card that annoyed me

Part of me still wants to use the manual listing version you first posted tho. I really like that can order my scenes with it, and I also have a couple of scenes that control lights indirectly (I have switches for a circadian temp mode and one for extracted spotify album colours).

Perhaps it’d be wiser to figure out a similar template to add switches alongside the scenes, but I do want to avoid a second row of chips, which may further complicate things (with my lack of coding ability).

As another solution, I’m also curious about pulling scenes based on area in common. Not sure if this is possible, as templating for area seems a little different from just checking attributes.

Would there be a chance that you’d know a resource or two for me to better understand how to code the dynamics parts that you’ve made?

Totally understand if knowing such a specific resource is a bit too specific of a request.

Thanks again for the base template!

evening, that’s looking nice, lots to go over apologies if I don’t cover everything.

I also moved the chips to the top of the card as I felt it was more useful.

yes most of it was copy and pasted for other sections of my dashboard so I would not be surprised if most of what I had was not needed.

nice

its looking a lot better than my attempt, im better at the make it work not so good at the make it look nice.

it would be possible to order the scenes alphabetically, that could be friendly_name or entity_id. think we could even order by last used or last added date.

you could do that but it might be a little over kill and if it is only a 1 off then just create a custom popup for that light and dont worry about the dynamics parts

I dont think it will be possible to pull out scenes based on area, we are not working with the Jinja2 templating engine, but we are accessing the JavaScript object directly. so we are limited by the data that is on the JavaScript object

I have 10+ years of coding with javascript, a diploma in programming and a bachelors in computer science, and I work as a software developer where i work with javascript for 40 - 50 hours a week.
even with all that I still had lots of google tabs and referenced @Mattias_Persson’s code.

as a start you could look at JavaScript Tutorial for some basic javascript, but programming is all about foundations, a loop is a loop no mater the language or syntax.

Thanks for the detailed reply, I know it’s shooting a little off topic at this point

You’re probably right that I should save time and just make a couple of custom pop-ups instead of getting carried away.

we are accessing the JavaScript object directly. so we are limited by the data that is on the JavaScript object

ah makes sense, I had a feeling it was something like this

I have 10+ years of coding with javascript, a diploma in programming and a bachelors in computer science, and I work as a software developer where i work with javascript for 40 - 50 hours a week.
even with all that I still had lots of google tabs and referenced @Mattias_Persson’s code.

This makes me feel much better about not being able to figure some of it out myself lol

programming is all about foundations, a loop is a loop no mater the language or syntax

A good reminder that I really should start to learn some programming to complement the endless poking around with code I find, cheers.

Please use Google translate to english before posting.

I had 2 directories of home assistant, I was looking in the wrong directory . stupid me. Thanks for your time anyway

Can you post your energy card code? :slight_smile:

Of course.
Tell me if you have some trouble:

          - type: custom:button-card
            custom_fields:
              graph:
                card:
                  type: 'custom:mini-graph-card'
                  name: Casa
                  entities:
                    - sensor.shellyem_5e28b2_channel_1_power
                  line_color: "#ff9800"
                  line_width: 4
                  font_size: 75
                  upper_bound: ~100
                  lower_bound: ~0
                  show:
                    graph: bar
                    name: false
                    icon: false
                    state: true
                    legend: false
                    labels: false
                    hours_to_show: 168
                    aggregate_func: max
                    group_by: date
            styles:
              name: [top: 57%, left: 0%, width: 100%, position: absolute]
              custom_fields:
                graph: [bottom: 0%, left: 0%, width: 100%, position: absolute]
                icon:
                  - width: 67%
                  - fill: "#9da0a2"
            template:
              - base_1
              - icon_energy_2
            card_mod:
              style: |
                :host{
                  --accent-color: #039be5;
                  --ha-card-border-width: 0px;
                }

          - type: custom:button-card
            custom_fields:
              graph:
                card:
                  type: 'custom:mini-graph-card'
                  name: Forno
                  entities:
                    - sensor.shellyem_5e28b2_channel_2_power
                  line_color: "#ff9800"
                  line_width: 4
                  font_size: 75
                  upper_bound: ~100
                  lower_bound: ~0
                  show:
                    graph: bar
                    name: false
                    icon: false
                    state: true
                    legend: false
                    labels: false
            styles:
              name: [top: 57%, left: 0%, width: 100%, position: absolute]
              custom_fields:
                graph: [bottom: 0%, left: 0%, width: 100%, position: absolute]
                icon:
                  - width: 67%
                  - fill: "#9da0a2"
            template:
              - base_1
              - icon_forno_3
            card_mod:
              style: |
                :host{
                  --accent-color: #039be5;
                  --ha-card-border-width: 0px;
                }

I want to show my garbage collection remaining days on the sidebar. I wrote this code, it works fine. Except it’s not in order. How can I make them sorted by date?

garbage: |
  {% set garbage_types = ['Biotonne', 'Gelber Sack', 'Papiertonne', 'Restmüll'] %}

  {% for garbage_type in garbage_types -%}
	{% if as_timestamp(state_attr('sensor.garbage_collection', garbage_type)) < as_timestamp(now()) + 604800 -%}
	  {% if garbage_type == 'Biotonne' -%}
		Bio: {{ states('sensor.biotonne_garbage_collection') }}<br>
	  {% elif garbage_type == 'Gelber Sack' -%}
		Gelber: {{ states('sensor.gelber_sack_garbage_collection') }}<br>
	  {% elif garbage_type == 'Papiertonne' -%}
		Papier: {{ states('sensor.papiertonne_garbage_collection') }}<br>
	  {% elif garbage_type == 'Restmüll' -%}
		{{ garbage_type }}: {{ states('sensor.restmull_garbage_collection') }}<br>
	  {% endif %}
	{% endif -%}
  {% endfor -%}

image

FYI: sensors are string, they are created like this.

- platform: waste_collection_schedule
  name: biotonne_garbage_collection
  details_format: appointment_types
  value_template: '{% if value.daysTo == 0 %}Today{% elif value.daysTo == 1 %}Tomorrow{% else %}in {{value.daysTo}} days{% endif %}'
  types:
    - Biotonne
{% set garbage_types = [
  {'name':'a','temp':10,'message':'in 10 days'},
  {'name':'b','temp':4,'message':'in 4 days'},
  {'name':'c','temp':1,'message':'today'},
  {'name':'d','temp':11,'message':'in 11 days'}
] | sort(attribute='temp') %}


{% for garbage_type in garbage_types -%}
  {{garbage_type.name}} : {{garbage_type.message}}
{% endfor -%}

you will need a list of objects that have both the date value and the message
then you just sort that list on the oject that has the date value, loop over the list and print it out

edit:

this might work but im not 100%

{% set garbage_types = [
{'name':'Biotonne','date':state_attr('sensor.garbage_collection', 'Biotonne'),'message':states('sensor.biotonne_garbage_collection')},
{'name':'Gelber Sack','date':state_attr('sensor.garbage_collection', 'Gelber Sack'),'message':states('sensor.gelber_sack_garbage_collection')},
{'name':'Papiertonne','date':state_attr('sensor.garbage_collection', 'Papiertonne'),'message':states('sensor.papiertonne_garbage_collection')},
{'name':'Restmüll','date':state_attr('sensor.garbage_collection', 'Restmüll'),'message':states('sensor.restmull_garbage_collection')},
] | sort(attribute='temp') %}


{% for garbage_type in garbage_types -%}
  {% if as_timestamp(state_attr('sensor.garbage_collection', garbage_type.name)) < as_timestamp(now()) + 604800 -%}
      {{garbage_type.name}} : {{garbage_type.message}}
  {% endif -%}
{% endfor -%}
1 Like

I have been working on my version of this for a bit now. I am wanting to have a popup with a switch group but can’t figure it out. I copied how you did light groups but nothing shows where I put the group. So I just want one square that says Christmas Lights that when double tapped opens a popup with 5 switches. Any guidance would be awesome.

This might help.

this is what it looks like:

- type: custom:button-card
  name: Monitors
  entity: group.office_monitors
  template:
    - switch
    - icon_imac
group:
  office_monitors:
    name: Office Monitors
    icon: mdi:monitor
    entities:
      - switch.port_2
      - switch.port_3
      - switch.port_4

switch:
  template:
    - base
    - loader
  double_tap_action:
    action: fire-dom-event
    browser_mod:
      service: browser_mod.popup
      data:
        title: >
          [[[
            return !entity || entity.attributes.friendly_name;
          ]]]
        content:
          type: entities
          card_mod:
            style: |
              #states {
                padding-top: 0.5em;
              }
          entities: >
            [[[
              if (entity) {
                  let switches = [],
                      id = Boolean(entity.attributes.entity_id)
                          ? [entity.entity_id].concat(entity.attributes.entity_id)
                          : [entity.entity_id];

                  for (let i = 0; i < id.length; i++) {
                      switches.push({
                          "type": "custom:mushroom-entity-card",
                          "entity": id[i],
                          "fill_container": false,
                          "primary_info": "name",
                          "secondary_info": "last-changed",
                          "icon_type": "icon",
                          "icon_color": "light-green",
                          "tap_action": { action: 'toggle' }
                      });
                  }
                  return switches;
              }
            ]]]