Lovelace: Button card

I have found a ‘bug’.
I think…

It seems to me that entity.state is not available if the button is included in a pop-up.

It might of course be that it is actually a ‘problem’ with browser_mod but I have no way of knowing.

Also, you might decide that is too obscure to want to look into but here it is in case you want to look at it.

This is the error when used in a pop-up:

button-card.js:1693 Uncaught ButtonCardJSTemplateError: TypeError: Cannot read property 'state' of undefined in 'var on_icon = 'mdi:checkbox-marked';
  var off_icon = 'mdi:checkbox-blank-outline';
  
  if (entit...'
    at HTMLElement.eval (eval at _evalTemplate (http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1691:14), <anonymous>:7:14)
    at HTMLElement._evalTemplate (http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1691:106)
    at HTMLElement._getTemplateOrValue (http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1700:97)
    at http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1958:62
    at Array.forEach (<anonymous>)
    at n (http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1957:40)
    at http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1958:40
    at Array.forEach (<anonymous>)
    at n (http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1957:40)
    at http://192.168.1.25:8123/hacsfiles/button-card/button-card.js:1958:40

which is caused by this segment:

custom_fields:
  check_box: >
    [[[
      var on_icon = 'mdi:checkbox-marked';
      var off_icon = 'mdi:checkbox-blank-outline';
      
      if (entity.state == 'on')
          return '<ha-icon icon="' + on_icon + '"></ha-icon>';
      else
          return '<ha-icon icon="' + off_icon + '"></ha-icon>';
    ]]] 

and here is the full config for the button which works perfectly outside a pop-up:

image

type: custom:button-card
color_type: icon
entity: {{ entity }}
show_state: false
show_name: true
name: {{ name }}
show_icon: true
size: 50%
tap_action:
  action: toggle
styles:
  grid:
    - grid-template-areas: '"i n check_box"'
    - grid-template-columns: 20% auto 15%
    - grid-template-rows: 1fr
  img_cell:
    - padding: 0em
  card:
    - font-family: "[[[ return states['input_text.ui_font_family'].state ]]]"
    - padding: 0em
    - height: 2.5em
    - font-size: 14px
    - border-radius: 5px
  name:
    - justify-self: start
  custom_fields:
    check_box:
      - padding: 0.4em
custom_fields:
  check_box: >
    [[[
      var on_icon = 'mdi:checkbox-marked';
      var off_icon = 'mdi:checkbox-blank-outline';
      
      if (entity.state == 'on')
          return '<ha-icon icon="' + on_icon + '"></ha-icon>';
      else
          return '<ha-icon icon="' + off_icon + '"></ha-icon>';
    ]]] 
state:
  - value: 'on'
    name: {{ on_name }}
    styles:
      card:
        - background-color: var(--primary-background-color)
        - border: 1px solid var(--accent-color)
  - value: 'off'
    name: {{ off_name }}
    styles:
      card:
        - color: var(--secondary-text-color)
      icon:
        - color: var(--secondary-text-color)
1 Like

This might be related to https://github.com/custom-cards/button-card/issues/337
I will take some time over the weekend to deal with it… But I don’t manage to reproduce it for now.

1 Like

Can you share please The entire config of this?

I’m struggling with some custom colouring of a button card.

I have a button to control a garage door (cover). I also have an automation that starts a timer when it’s opened. I will get a notification on the companion app when this timer expires (i.e. I regard the door as left open for too long, for safety reasons).

On my button, I want to show an icon to indicate whether the door is open or closed. This works fine when using the cover as the entity. The button has a name displayed at the top, then the icon and underneath it the timer which should either display idle or the remaining time.

I discovered on this thread that for the countdown to work, the button’s entity must be the timer and not the cover. This is fine, as I can still implement the different icons and all that (I think). The config below will show a timer/stopwatch icon – I’m not worried about that for now.

I set the button’s entity to the timer and defined a style for the state with colours for the conditions, but it doesn’t change the red countdown time back to blue when the state changes to idle and I can’t see why.

The label in the config below is just a bit of debugging and also because I tried to do this with the label before I learned that the label won’t update.

Does anybody have an idea why it’s not working as I expect?

            cards:
              - type: custom:button-card
                name: Rouvé
                entity: timer.lhs_garage_door_timer #cover.lhs_garage_door
                label: >
                  [[[
                    return states['timer.lhs_garage_door_timer'].state;
                  ]]]
                show_name: true
                show_state: true
                show_label: true
                color_type: icon
                color: var(--paper-item-icon-color)
                styles:
                  name:
                    - color: var(--paper-item-icon-color)
                  state:
                    - operator: "=="
                      value: "idle"
                      color: var(--paper-item-icon-color);
                    - operator: "!="
                      value: "idle"
                      color: "red"
                  label:
                    - color: var(--paper-item-icon-color)
                  card:
                    - font-size: 12px
                  grid:
                    - grid-template-areas: '"n" "i" "s" "l"'
                    - grid-template-rows: min-content 1fr min-content
                    - grid-template-columns: 1fr

Screenshot 2020-06-05 at 18.42.58 Screenshot 2020-06-05 at 18.43.07

The state part of your config should be a main config entry, not under styles.

state under styles is to apply CSS to the state field.
state as a main config entry is to apply several settings based on different state of the entity declared in the button.

Thank you.

When I move that section, it colours the icon red but not the state (meaning the timer countdown/idle below the icon), and the icon’s colour doesn’t change (revert) when the state is idle. I must still be doing something wrong but I see the docs say that the colour change will only apply to the icon. How would one apply dynamic colours to the timer state text?

            cards:
              - type: custom:button-card
                name: Rouvé
                entity: timer.lhs_garage_door_timer #cover.lhs_garage_door
                label: >
                  [[[
                    return states['timer.lhs_garage_door_timer'].state;
                  ]]]
                show_name: true
                show_state: true
                show_label: true
                # color_type: icon
                # color: var(--paper-item-icon-color)
                state:
                  - operator: "=="
                    value: "idle"
                    color: var(--paper-item-icon-color);
                  - operator: "!="
                    value: "idle"
                    color: "red"                

I noticed something else. When I define a minimal config for this button, I get an error in the log. I’m not sure whether this is related to my issue. I’m performing a full upgrade at the moment.

            cards:
              - type: custom:button-card
                entity: timer.lhs_garage_door_timer #cover.lhs_garage_door
2020-06-06 02:41:48 ERROR (MainThread) [frontend.js.latest.202004071] http://.../local/button-card.js:1436:27 Uncaught TypeError: Cannot read property 'states' of undefined

UPDATE:
I’ve upgraded to HA 0.110.5 (latest) and I’m using Button Card 3.3.5 (also latest) and I still see the error. Then I saw this was already reported above and logged as issue #337.

SOLUTION:
I’m quite happy with this. The power was in using an embedded custom button card that represents the timer. I first did it with the main card tied to the timer and then using a lot of JavaScript to get it to show state according to the cover’s status but then one needs to do more work for the actions to be tied to the cover. So, I reversed all the logic, which I think makes more sense and reads easier (but just mentioning it anyway to confirm how powerful this custom button is).

              - type: custom:button-card
                name: Rouvé
                entity: cover.lhs_garage_door
                show_name: true
                color_type: icon
                color: var(--paper-item-icon-color)
                custom_fields:
                  t:
                    card:
                      type: custom:button-card
                      name: Timer
                      entity: timer.lhs_garage_door_timer
                      show_name: false
                      show_icon: false
                      show_state: true
                      styles:
                        card:
                          - font-size: 12px
                        state:
                          - color: >
                              [[[
                                if (states['timer.lhs_garage_door_timer'].state == 'idle')
                                  return "var(--paper-item-icon-color)";
                                else
                                  return "red";
                              ]]]
                styles:
                  name:
                    - color: var(--paper-item-icon-color)
                  card:
                    - font-size: 12px
                  grid:
                    - grid-template-areas: '"n" "i" "t"'
                    - grid-template-rows: min-content 1fr min-content
                    - grid-template-columns: 1fr
                tap_action:
                  action: toggle
                  haptic: light
                hold_action:
                  action: more-info
                  haptic: selection

Screenshot 2020-06-06 at 17.04.26 Screenshot 2020-06-06 at 17.04.37

2 Likes

I just installed button-card using HACS in my ‘regular’ install of HA on RPi running HASSOS. Super excited about adding it to my UI! On the HACS screen, when I click on the Plugin, I can see the instructions, including the TOC. The links in the TOC don’t seem to be working. I can see the anchor reference in the URL when clicked, but (at least in Firefox on Windows), it doesn’t take me to the anchor. Also, I would have thought that this web page would be in /config/www, but it’s not there, so I assume it is in a container somewhere, relatively inaccessable. Am I missing something?

Also, is the ui-lovelace-card.yaml still a thing? I thought that went away.

You can still use yaml file for Lovelace - it’s still a supported option.

The icon doesn’t get resized any longer. “size:” is ignored, and different sizes of the button don’t change anything. The icon is always displayed very small.

I had this issue last night upgrading to 0.110 and the resource for this custom button. After clearing the cache of my browser and resetting my iOS companion app’s frontend cache all custom buttons and icons were sized correctly.

Many thanks! Clearing the cache really did the job. I should have thought of that.

1 Like

Thanks David. Do you see the same behavior with the instructions as I reported? It’s not a problem the links don’t work, it just makes me think something isn’t configured correctly.

The links never work in HACS, you have to go to the repository directly.

1 Like

Thanks! Cheers.

Oh, and THANKS for all your hard work on this and making it possible!

1 Like

If you click on the plugin all it does is display the actual code for the card. As romrider said you need to go to the repo.

I don’t know what’s wrong. But the icon does not enlarge.

icon2

- type: picture-elements
  image: /local/images/transparent_1to1.png
  style: |
    ha-card { 
      border-radius: 15px;
    }
  elements:
    - type: custom:mini-graph-card
      style:
        top: 40%
        left: 50%
        width: 100%
        height: 80%
        translate: translate(-50%, -50%)
        '--paper-card-background-color': 'rgba(0, 0, 0, 0.0)'
        '--ha-card-background': "rgba(0, 0, 0, 0.0)"
        '--ha-card-box-shadow': 'none'
        z-index: 3
        pointer-events: none
      entities:
        - sensor.energia_mensal_alta
      group: true
      points_per_hour: 1
      hour24: true
      line_color: '#4ba6ed'
      line_width: 10
      hours_to_show: 24
      update_interval: 600
      show:
        name: false
        icon: false
        state: false
        points: false
        legend: false
        average: false
        extrema: false
        labels: false
        fill: false
        labels_secondary: false
        name_adaptive_color: false
        icon_adaptive_color: false

    - type: custom:button-card
      style:
        top: 50%
        left: 50%
        width: 100%
        translate: translate(-50%, -50%)
      aspect_ratio: 1/1
      entity: sensor.energia_mensal_alta
      name: teste
      icon: 'mdi:flash'
      color: var(--paper-item-icon-color)
      show_name: true
      show_label: true
      show_icon: true
      show_last_changed: true
      size: 70%
      tap_action:
        action: more-info
        haptic: light
      styles:
        icon:
        - opacity: 0.3
        - width: 100%
        img_cell:
        - top: 0%
        - left: 30%
        - position: absolute
        - z-index: 2
        grid:
        - grid-template-areas: '"info info" "n n" "l l"'
        - grid-template-columns: 40% 1fr
        - grid-template-rows: 1fr min-content min-content
        - position: relative
        card:
        - padding: 10px
        - z-index: 1
        name:
        - justify-self: start
        - align-self: end
        - font-weight: bold
        - font-family: Helvetica 
        - font-size: 12px
        - text-align: start
        - background-image: linear-gradient(to right, white 0%, white 80%, rgba(0,0,0,0))
        - -webkit-background-clip: text
        - -webkit-text-fill-color: transparent
        - position: relative
        - display: inline-block
        - width: 100%
        - align-content: start
        - text-align: start
        - text-overflow: unset
        - z-index: 5
        label:
        - justify-self: start
        - align-self: end
        - font-weight: bold
        - font-family: Helvetica 
        - font-size: 12px
        - text-align: start
        - background-image: linear-gradient(to right, var(--paper-item-icon-color) 0%, var(--paper-item-icon-color) 80%, rgba(0,0,0,0))
        - -webkit-background-clip: text
        - -webkit-text-fill-color: transparent
        - position: relative
        - display: inline-block
        - width: 100%
        - align-content: start
        - text-align: start
        - text-overflow: unset
        - z-index: 5
        custom_fields:
          info:
          - align-self: start
          - width: 40%
          - z-index: 5
      custom_fields:
        info: >
          [[[

            var entity_id = (entity === undefined) ? 'Invalid Entity' : entity.entity_id;
            var statestr = (entity === undefined || entity.state === undefined) ? null : entity.state;
            var units = (statestr && entity.attributes.unit_of_measurement) ? entity.attributes.unit_of_measurement : null;
            var date = (statestr && entity.attributes.device_class === 'timestamp') ? new Date(statestr) : null;
            var value;

            if (statestr && date === null) {
              if (statestr.split('.').length - 1 <= 1){
                var test = parseFloat(parseFloat(statestr).toFixed(2));
                value = (isNaN(test)) ? null : test;

                // test if units are in the state because some sensors are stupid.  Looking
                //   at you synology.

                const expr = /[^-.0-9]+/;
                var has_units = expr.test(statestr.trim());

                // console.log(`${entity_id}: "${statestr}" ${matches}`);
                if (value && has_units)
                  units = statestr.replace(/[.0-9]+/, ''); 
              }
            }

            // console.log(`${entity_id}: ${statestr}, ${units}, ${date}, ${value}`);

            const length = 50;
            const width = 3;
            var radius = length / 2;

            if (date){
              let now = new Date();
              var tdelta = Math.floor((now - date)/1000);

              // console.log(`${entity_id}: ${tdelta}`);

              function plural(descriptor, divisor){
                var ret = Math.floor(tdelta/divisor);
                return (ret == 1) ? [ret, descriptor] : [ret, `${descriptor}s`];
              }

              var values;
              if (tdelta < 60)
                values = plural('second', 1);
              else if (tdelta < 60 * 60)
                values = plural('minute', 60);
              else if (tdelta < 60 * 60 * 24)
                values = plural('hour', 60 * 60);
              else if (tdelta < 7 * 60 * 60 * 24)
                values = plural('day', 60 * 60 * 24);
              else
                values = plural('week', 7 * 60 * 60 * 24);

              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="#4ba6ed" />
                  <text x="50%" y="43%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${values[0]}<tspan x="50%" dy="1.2em" font-size="10" font-weight="normal" >${values[1]}</tspan>
                  </text>
                </svg>
                `;
            }

            else if (value && units && units === '%') {
              radius = (length - 3) / 2;
              const circumference = radius * 2 * Math.PI;
              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="none" stroke="var(--paper-item-icon-color)" opacity="0.5" stroke-width="${width}" />
                  <circle style="
                      transform: rotate(-90deg);
                      transform-origin: 50% 50%;
                      stroke-dasharray: ${circumference};
                      stroke-dashoffset: ${circumference - value / 100 * circumference};
                    "
                    id="c_brightness" cx="25" cy="25" r="${radius}" stroke="var(--paper-item-icon-active-color)" stroke-width="${width}" fill="none" stroke-linecap="round" />
                  <text x="50%" y="54%" fill="var(--primary-text-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >%</tspan>
                  </text>
                </svg>
                `;
              }
            else if (value && units && units.includes('°')) {
              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="#4ba6ed" />
                  <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >${units}</tspan>
                  </text>
                </svg>
                `;
            }

            else if (value && units && units.length > 1) {
              var y = (String(value).length > units.length) ? "50%" : "43%";
              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="#4ba6ed" />
                  <text x="50%" y="${y}" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan x="50%" dy="1.2em" font-size="10" font-weight="normal" >${units}</tspan>
                  </text>
                </svg>
                `;
              }

            else if (value) {
              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="#4ba6ed" />
                  <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${value}<tspan font-size="10" font-weight="normal" >${units}</tspan>
                  </text>
                </svg>
                `;
            }
            return `
                <svg viewBox="0 0 50 50">
                  <circle cx="25" cy="25" r="${radius}" fill="#4ba6ed" />
                  <text x="50%" y="54%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${statestr}
                  </text>
                </svg>
                `; 
          ]]]

What I changed was only the variables such as icon, entity and color.

It worked.
Install “lovelace-button-card” again via the HACS component and updated a variable “–mdc-icon-size”…

Thank you for sharing. :grinning:

:tada::tada: Version 3.3.6 :tada::tada:

Bugfixes

  • Fix some more issues with HA >= 0.110 (Fix #337)
  • Fix a long running issue: embedded cards would refresh completely on every update (Fix #334)
1 Like

Hi, I need a little help.

I have gate, it takes 20s to open/close. I would like to indicate this process on the frontend.
So when I press the button the icon should blink for 20 sec then change to steady on or off.
Is it possible?

Thank you

I need this too. :slight_smile: Have a button for my gate. When I press the button a switch entity toggle 1 sec impulse to one relay and the gate open or close. I have one door sensor which is show that the gate state is open or closed. Is it possible merge this two entity and this button do the opening or closing and show the gate state with color (green-open, red-closed)?

you can merge them into one button:

entity: binary_sensor.test_motion4_6
show_icon: true
show_state: true
state:
  - color: 'rgb(237, 202, 68)'
    icon: 'mdi:door-open'
    size: 50px
    value: 'on'
  - color: 'rgb(70, 116, 156)'
    icon: 'mdi:door-closed'
    size: 50px
    value: 'off'
styles:
  card:
    - height: 100px
tap_action:
  action: call-service
  service: switch.toggle
  service_data:
    entity_id: switch.kiskapu
type: 'custom:button-card'