A different take on designing a Lovelace UI

For Anyone wanting it I created an Octoprint 3D Printer card to use with Matthias’s UI style:

image

button_card_templates.yaml code:

printer:
  template:
    - base
  aspect_ratio: 1/1
  show_state: true
  show_icon: false
  show_name: true
  show_current_temperature: true
  show_control: true
  state:
    - operator: template
      value: sensor.octoprint_print_state
  custom_fields:
    circle: >
      [[[
        if (Math.round(states['sensor.octoprint_percentage_complete'].state) > 0 && states['sensor.octoprint_percentage_complete'].state < 100) {
          const input = states['sensor.octoprint_percentage_complete'].state
          const radius = 20.5;
          const circumference = radius * 2 * Math.PI;
          return `
            <svg viewBox="0 0 50 50">
              <style>
                circle {
                  transform: rotate(-90deg);
                  transform-origin: 50% 50%;
                  stroke-dasharray: ${circumference};
                  stroke-dashoffset: ${circumference - input / 100 * circumference};
                }
                tspan {
                  font-size: 10px;
                }
              </style>
              <circle cx="25" cy="25" r="${radius}" stroke="#b2b2b2" stroke-width="1.5" fill="none" />
              <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${input}<tspan font-size="10">%</tspan></text>
            </svg>
          `;
        }
      ]]]

Modify the line below in the “base” template to include “Printing” as an “on” state:

Note: This is what controls when a card’s background turns white for future reference…

base:
  template:
    - settings
  variables:
    state_on: >
      [[[ return ['on', 'home', 'cool', 'fan_only', 'playing','Printing'].indexOf(entity === undefined || entity.state) !== -1; ]]]
    state: >
     . 
     .
     .
#Rest of base template

Icon Code (to be added to the bottom of button_card_templates.yaml):

icon_3dprinter:
  styles:
    custom_fields:
      icon:
        - width: 77%
        - margin-left: 1%
        - margin-top: 5%
  custom_fields:
    icon: >
      <svg viewBox="0 0 90 73">
        <<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g><rect fill="#9da0a2" x="0" y="63" width="90" height="10" rx="3"></rect><rect fill="#9da0a2" transform="translate(10.000000, 33.000000) rotate(90.000000) translate(-10.000000, -33.000000) " x="-23" y="30" width="66" height="6" rx="3"></rect><rect fill="#9da0a2" transform="translate(78.000000, 34.000000) rotate(90.000000) translate(-78.000000, -34.000000) " x="44" y="31" width="68" height="6" rx="3"></rect><path d="M7,3 C7,1.34314575 8.34757814,6.41357826e-15 9.99253964,6.2593707e-15 L78.0074604,-1.16714867e-16 C79.6601944,-2.71651068e-16 81,1.34651712 81,3 L81,3 C81,4.65685425 79.6524219,6 78.0074604,6 L9.99253964,6 C8.33980564,6 7,4.65348288 7,3 L7,3 Z" fill="#9da0a2"></path><rect fill="#9da0a2" x="16" y="56" width="56" height="3"></rect><rect fill="#9da0a2" transform="translate(44.000000, 26.500000) scale(1, -1) translate(-44.000000, -26.500000) " x="12" y="25" width="64" height="3"></rect><rect fill="#9da0a2" transform="translate(44.000000, 19.500000) scale(1, -1) translate(-44.000000, -19.500000) " x="12" y="18" width="64" height="3"></rect><rect fill="#9da0a2" x="25" y="23" width="15" height="10"></rect><rect fill="#9da0a2" x="31" y="31" width="3" height="4"></rect></g></g>>
      </svg>

lovelace.yaml code: add this to where you want the card to live in your dashboard

          - type: custom:button-card
            entity: sensor.octoprint_print_state
            tap_action: !include popup/Printer.yaml
            name: 3D Printer
            template:
              - base
              - printer
              - icon_3dprinter

and popup/Printer.yaml code: (this requires the threedy card from HACS)

action: fire-dom-event
browser_mod:
  command: popup
  title: Printer
  style:
    .: |
      :host .content {
        width: calc(385px + 510px);
        max-width: 90vw;
      }
      threedy-card div div div:nth-child(2) {
        height: auto !important;
        opacity: 1 !important;
            transform: none !important;
      }
    layout-card$grid-layout:
      $: |
        hui-vertical-stack-card {
          animation: border 1s forwards;
        }
        @keyframes border {
          0%, 100% {
              border-right: 1.5px solid rgba(0, 0, 0, 0.2);
          }
        }
        /* phone */
        @media screen and (max-width: 800px) {
          hui-vertical-stack-card {
              border-bottom: 1.5px solid rgba(0, 0, 0, 0.2);
              padding-right: 0;
              animation: none;
          }
        }
      $hui-vertical-stack-card:
        $: |
          hui-horizontal-stack-card {
            padding: 0em 2em 2.3em 2em;
          }
        $hui-entities-card$: |
          .card-content {
            padding: var(--card-content-padding);
          }
        $hui-horizontal-stack-card$: |
          #root {
            justify-content: space-evenly;
          }
  card:
    type: custom:threedy-card
    base_entity: sensor.octoprint
    monitored:
      - Status
      - ETA
      - Elapsed
      - Hotend
      - Bed
      - Remaining
    printer_type: I3
    name: Prusa Mk2.5S Bear
    theme: Default
    temperature_unit: C

NOTE: using this card requires you to add the following template sensors:

template:
  - sensor:
      - unique_id: OctoPrint Time Elapsed
        name: "OctoPrint Time Elapsed"
        unit_of_measurement: "s"
        availability: >
          {{ not is_state('sensor.octoprint_start_time', 'unavailable') }}
        state: >
          {% set start = as_timestamp(states('sensor.octoprint_start_time')) %}
          {% if is_number(start) %}
            {{ (as_timestamp(now()) - start) | int }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          start_time: "states('sensor.octoprint_start_time')"

      - unique_id: OctoPrint Time Remaining
        name: "OctoPrint Time Remaining"
        unit_of_measurement: "s"
        availability: >
          {{ not is_state('sensor.octoprint_estimated_finish_time', 'unavailable') }}
        state: >
          {% set finish = as_timestamp(states('sensor.octoprint_estimated_finish_time')) %}
          {% if is_number(finish) %}
            {{ (finish - as_timestamp(now())) | int }}
          {% else %}
            unknown
          {% endif %}
        attributes:
          start_time: "states('sensor.octoprint_estimated_finish_time')"

      - unique_id: OctoPrint Percentage complete
        name: "OctoPrint Percentage Complete"
        unit_of_measurement: "%"
        state: >
          {{ states['sensor.octoprint_job_percentage'].state | round(0)}}

      - unique_id: OctoPrint Print State
        name: "OctoPrint Print State"
        state: >
          {% if is_state('sensor.octoprint_current_state', 'Operational') %}
            idle
          {% elif is_state('sensor.octoprint_current_state', 'Printing from SD') %}
            Printing
          {% elif is_state('sensor.octoprint_current_state', 'Starting print from SD') %}
            Printing
          {% else %}
            Down
          {% endif %}

Threedy Card can be found here although be warned it seems to be a dead project:
https://github.com/dangreco/threedy

BE WARNED: The pop-up card does not currently scale to mobile resolutions as the code above stands… I’m not an expert by any means but I THINK it’s because the threedy card does not support it but if someone wants to swing by the thread and see if I messed up the scaling somewhere along the way let me know…

4 Likes

you mean behind the name?

title: Wohnzimmer ↔

easy copy it here and paste it in your ui-lovelace.yaml behind the name

2 Likes

anyone else having issue with sidebar not showing after update to Home Assistant 2022.7.0 my sensor.template_sidebar doesn’t exsist anymore the state list

1 Like

Mine is working fine. Did you check the update blog to see what breaking changes they did? They did some changes to Google Calendars so maybe that might be affecting you.

Indeed! I have it too.

Thought it was the sonoff update at first but maybe its the version update… ill rollback the backup from yesterday.

Hello. How do I identify if it is the battery percentage or the lock, what I want to see in the upper right space.

Because the circle is configured with a width of 45 and so the padlock looks good but the percentage does not. Since the two share the same configuration. Do I have a way to put a condition to know who I am setting that width for? Thank you

Sin título

lock:
  tap_action:
    loader: |
      [[[
        let elt = this.shadowRoot;
        if (variables.state_on && variables.lock === 'locked') {
          elt.getElementById('lock').classList.add('locked');
          window.setTimeout(() => {
            elt.getElementById('lock').classList.remove('locked');
          }, 1100);
        } else {
          let loader = (id, style, timeout) => {
              elt.getElementById(id) && (elt.getElementById(id).style.display = style,
                window.setTimeout(() => {
                  elt.getElementById('loader').style.display = 'none'
                }, 20000))
          };
          loader('circle', 'none', 'initial'),
          loader('loader', 'initial', 'none');
        }
      ]]]
  custom_fields:
    circle: >
      [[[
        if(entity) {
          let state = states[entity.entity_id + '_estado_de_sesion'].state.toLowerCase();
          if (state === 'locked') {
            return `
              <svg viewBox="0 0 50 50">
                <style>
                  @keyframes locked {
                    from,
                    to {
                      transform: translateX(0);
                    }
                    10%,
                    30%,
                    50%,
                    70%,
                    90% {
                      transform: translateX(-8%);
                    }
                    20%,
                    40%,
                    60%,
                    80% {
                      transform: translateX(8%);
                    }
                  }
                  .locked {
                    animation: locked 1.1s;
                  }
                </style>
                <path id="lock" class="${state}" d="M8.2 22.6h2.4v-7.2C10.6 7.5 17.1 1 25 1s14.4 6.4 14.4 14.4v7.2h2.4V49H8.2m26.4-26.4v-7.2c0-5.3-4.3-9.6-9.6-9.6s-9.6 4.3-9.6 9.6v7.2"/>
              </svg>
            `;
          } else {
            let bateria = "";        
            const stroke_color = '#b2b2b2';
            const fill_color = 'none';
            if(entity.state == 'online') {
              if(entity.entity_id == 'sensor.pc_florencia') 
                bateria = states['sensor.pc_florencia_bateria_charge_remaining_percentage'].state + "%";
              if(entity.entity_id == 'sensor.pc_milena') 
                return;
              let stroke_color = '#b2b2b2';
              let fill_color = 'rgba(255,255,255,0.04)';
              return `
                <svg viewBox="0 0 50 50">
                  <circle cx="29" cy="21" r="20.5" stroke="${stroke_color}" stroke-width="1.5" fill="${fill_color}" />
                  <text x="60%" y="45%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${bateria}</text>
                </svg>
              `;
            }
          }
        }
      ]]]
  styles:
    custom_fields:
      circle:
        - width: 45%
        - fill: var(--state-icon-color)
        - margin: 1% 2% 0 0
        - justify-self: end
        - opacity: 1
        - display: grid

Hey Lars,

I’ve restored a backup from yesterday. This backup is on the latest version 2022.7.0 and sidebar works.

I’m now going to update the Sonoff LAN intergration from HACS as that was something I updated too as the intergration broke yesterday due to an issue with an ID hidden in the code.

… Maybe that breaks it… I’ll report back in a min :slight_smile:

i see your door icon, is it animated?
or is this the default “icon_closet” icon ?

Yep its the closet icon indeed.

Welp, restored backup and updated every addon again, sidebar still works so I have no clue what broke it in the first place then lol …

@larsvb88 The update broke more than the sidebar. Due to the fact it’s running on a new BT engine and Python 3.0 more addons break with this update unfortunatly. I think rolling back to 6.7 is the best (for me) at this point.

1 Like

That’s sad, did look forced breaking changes but couldn’t find any particular that would affect the sidebar template. Hmm also roll back to a previous backup for now

Mine broke too. But checking the logs showed it was because somehow the sidebar entity was duplicated and it wouldn’t allow two entities with the same name/ID.
So for now I’ve renamed it in template to sidebar2 and referenced that in the UI yaml and everything is running smoothly on the new HA core.

Someone explain to me how it works and what the card lock icon Gives?

@Mattias_Persson

hey
perhaps you can help me, i use this code, but now the tilt animation is gone and the biggest problem my graph is not rounded at the bottom and the graph gradient is not high enough, i tried it now for hours, but i had no luck.

can you please help?

shit

- type: custom:button-card
            template:
              - temperature
              - icon_climate
            entity: sensor.multisensor_ankleidezimmer
            name: Temp
            tap_action: 
              action: none
            hold_action: 
              action: none
            custom_fields:
              graph:
                card:
                  entities:
                    - entity: sensor.multisensor_ankleidezimmer
                      color_thresholds:
                        - value: 0
                          color: "#276696"
                        - value: 69
                          color: "#228C22"
                        - value: 75
                          color: "#d35400"
                        - value: 76
                          color: "#c0392b"
                   

and the code from the button-card-templates

temperature:
    template:
      - base
    show_name: true # Hides Card Name
    show_state: true # Hides Card state
    state_display: >
      [[[ return '&nbsp;'; ]]]
    custom_fields:
      circle: >
        [[[ {
        const temperature = Math.round(entity.state);
        return `<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="20.5" stroke="#313638" stroke-width="1.5" fill="#FFFFFF08" style="
        transform: rotate(-90deg); transform-origin: 50% 50%;" />
        <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle" dominant-baseline="middle">${temperature}°C</text></svg>`; } ]]]
      graph:
        card:
          type: "custom:mini-graph-card"
          height: 140
          hours_to_show: 24
          points_per_hour: 1
          line_width: 8
          font_size: 75
          decimals: 0
          animate: true
          show:
            name: false
            icon: false
            state: false
            legend: false
            labels: false
            labels_secondary: false
            points: false
          color_thresholds:
            - value: 0
              color: "#276696"
            - value: 69
              color: "#228C22"
            - value: 75
              color: "#d35400"
            - value: 76
              color: "#c0392b"
              
    styles:
      custom_fields:
        graph: [bottom: 8%, left: 2%, width: 123%, position: absolute, margin: 0% 0% -13% -14%]
        icon:
          - width: 67%
          - fill: "#9da0a2"
        circle:
          - display: initial
          - width: 90%
          - margin: -6% -5% 0 0
          - justify-self: end
          - opacity: 1

could you explain what you exactly renamed?

I’m assuming you used my code? A different take on designing a Lovelace UI - #3181 by mobiustorr

I had changed how my code is laid out so maybe you can try the latest version of what I did.
ui-lovelace.yaml:

                - type: custom:button-card
                  entity: sensor.my_ecobee_temperature
                  name: Temp
                  tap_action: !include popup/livingroom_temperature.yaml
                  hold_action: 
                    action: none
                  custom_fields:
                    graph:
                      card:
                        entities:
                          - entity: sensor.my_ecobee_temperature
                          - entity: sensor.nightstate
                            color: gray
                            y_axis: secondary
                            show_line: false
                            show_points: false
                            show_legend: false
                            show_labels: false
                  template:
                    - temperature
                    - icon_temp

button_card_templates.yaml:

  #################################################
  #                                               #
  #                  TEMPERATURE                  #
  #                                               #
  #################################################

  temperature:
    template:
      - base
    show_name: true # Hides Card Name
    show_state: true # Hides Card state
    state_display: >
      [[[ return '&nbsp;'; ]]]
    custom_fields:
      circle: >
        [[[ {
        const temperature = Math.round(entity.state);
        return `<svg viewBox="0 0 50 50"><circle cx="25" cy="25" r="20.5" stroke="#313638" stroke-width="1.5" fill="#FFFFFF08" style="
        transform: rotate(-90deg); transform-origin: 50% 50%;" />
        <text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle" dominant-baseline="middle">${temperature}°F</text></svg>`; } ]]]
      graph:
        card:
          type: "custom:mini-graph-card"
          height: 140
          hours_to_show: 24
          points_per_hour: 1
          line_width: 8
          font_size: 75
          decimals: 0
          show:
            name: false
            icon: false
            state: false
            legend: false
            labels: false
            labels_secondary: false
            points: false
          color_thresholds:
            - value: 65
              color: "#276696"
            - value: 75
              color: "#228C22"
            - value: 76
              color: "#d35400"
            - value: 78
              color: "#c0392b"
              
    styles:
          custom_fields:
            graph: [bottom: 0%, left: 0%, width: 128%, position: absolute, margin: 0% 0% -13% -14%]
            icon:
              - width: 67%
              - fill: "#9da0a2"
            circle:
              - display: initial
              - width: 90%
              - margin: -6% -5% 0 0
              - justify-self: end
              - opacity: 1

As for fixing the graph not rounding at the bottom of the card, verify you have the “overflow” setting in button_card_templates.yaml. It is near the bottom of the code below.

  #################################################
  #                                               #
  #                     BASE                      #
  #                                               #
  #################################################

  base:
    template:
      - settings
      - tilt
      - extra_styles
    variables:
      state_on: >
        [[[ return ['on', 'home', 'cool', 'fan_only', 'playing', 'unlocked'].indexOf(entity === undefined || entity.state) !== -1; ]]]
      state: >
        [[[ return entity === undefined || entity.state; ]]]
      entity_id: >
        [[[ return entity === undefined || entity.entity_id; ]]]
      media_on: >
        [[[ return entity === undefined || ['playing', 'paused'].indexOf(entity.state) !== -1; ]]]
      media_off: >
        [[[ return entity === undefined || ['off', 'idle', 'standby', 'unknown', 'unavailable'].indexOf(entity.state) !== -1; ]]]
      entity_picture: >
        [[[ return entity === undefined || entity.attributes.entity_picture; ]]]
      timeout: >
        [[[ return entity === undefined || Date.now() - Date.parse(entity.last_changed); ]]]
      tilt_options: >
        [[[
          let options = {
            max: 5,
            scale: 1.06,
            glare: true,
            'max-glare': 0.15,
            perspective: 800,
            speed: 800,
            parallax: '25px'
          }
          if (this._config.template.includes('conditional_media')) {
            options.scale = options.scale % parseInt(options.scale) / 2 + parseInt(options.scale);
            options.perspective = options.perspective * 2;
            return options;
          }
          return options;
        ]]]
    aspect_ratio: 1/1
    show_state: true
    show_icon: false
    state_display: >
      [[[ if (variables.state === true) return variables.translate_unknown; ]]]
    tap_action:
      ui_sound_tablet: |
        [[[
          let screensaver = states[variables.entity_tablet] === undefined || 
            states[variables.entity_tablet].state;

          if (variables.state === 'off' && screensaver === 'off') {
            hass.callService('media_player', 'play_media', {
              entity_id: variables.entity_browser_mod,
              media_content_id: '/local/sound/on.m4a',
              media_content_type: 'music'
            });
          }
          if (variables.state_on && screensaver === 'off') {
            hass.callService('media_player', 'play_media', {
              entity_id: variables.entity_browser_mod,
              media_content_id: '/local/sound/off.m4a',
              media_content_type: 'music'
            });
          }
        ]]]
      action: toggle
      haptic: medium
    hold_action:
      haptic: success
    styles:
      grid:
        - grid-template-areas: |
            "icon  circle"
            "n     n"
            "s     s"
        - grid-template-columns: repeat(2, 1fr)
        - grid-template-rows: auto repeat(2, min-content)
        - gap: 1.3%
        - align-items: start
      name:
        - justify-self: start
        - line-height: 121%
      state:
        - justify-self: start
        - line-height: 115%
      card:
        - border-radius: var(--custom-button-card-border-radius)
        - -webkit-tap-highlight-color: rgba(0,0,0,0)
        - transition: none
        - padding: 11.5% 10.5% 10.5% 11.5%
        - overflow: hidden
        - --mdc-ripple-color: >
2 Likes

yeah correct, you are complete right,

- overflow: hidden

did the trick, I would never have found that, thank you very much for the help

can you perhaps share your icon ?

icon_temp

  icon_temp:
    styles:
      custom_fields:
        icon:
          - margin-top: 2%
    custom_fields:
      icon: >
        <svg viewBox="10 5 50 50">
          <style>@keyframes animate{0%{transform: scale(0.85);}20%{transform: scale(1.1);}40%{transform: scale(0.95);}60%{transform: scale(1.03);}80%{transform: scale(0.97);}100%{transform: scale(1);}}.animate{animation: animate 0.8s; transform-origin: center;}</style>
          <path fill="#9da0a2" d="M41.74 10.852v2h-7.75v-2zm-3.25 4.36h-4.5v2h4.5zm-4.5 6.36h7.75v-2h-7.75zm4.5 2.36h-4.5v2h4.5zm-4.5 6.36h7.75v-2h-7.75zM35.2 41.685A10.14 10.14 0 0 1 25.074 51.81a10.14 10.14 0 0 1-10.125-10.125c0-3.618 1.9-6.906 5-8.725V10.006c0-2.826 2.3-5.125 5.125-5.125s5.125 2.3 5.125 5.125V32.96c3.1 1.817 5 5.106 5 8.725zm-2 0c0-3.07-1.706-5.845-4.453-7.24l-.547-.278v-24.16a3.13 3.13 0 0 0-3.125-3.125 3.13 3.13 0 0 0-3.125 3.125v24.16l-.547.278a8.09 8.09 0 0 0-4.453 7.24c0 4.48 3.645 8.125 8.125 8.125s8.125-3.645 8.125-8.125zm-1.666 0a6.47 6.47 0 0 1-6.459 6.458 6.47 6.47 0 0 1-6.458-6.458 6.46 6.46 0 0 1 4.796-6.233l.37-.1v-22.23h2.583v22.23l.37.1a6.46 6.46 0 0 1 4.796 6.233zm-6.14-4.305c-.154-.684-.842-1.134-1.543-.974a5.31 5.31 0 0 0-4.158 5.207 1.29 1.29 0 0 0 2.58 0c0-1.277.902-2.41 2.147-2.7.692-.16 1.13-.85.974-1.543z"/>
        </svg>
3 Likes

thank you , but in your code the 3d function is not avaiable?

your code

notilt

mattias code

tilt

1 Like