A different take on designing a Lovelace UI

I suspect what is happening is that media_position is being updated more frequently than what media_position_updated_at is reporting.

You could test this a couple ways. First, refresh that view, and see if media_position is updated and media_position_updated_at stays the same while a song is playing.

Second, wait until the progress bar is near 100%, but the song is only halfway through, and then pause it. Does the progress bar change to the proper position?

Edit: Curiosity got the better of me and I signed up for a trial spotify account. The issue here is exactly what I had guessed. media_position is updated every 30 seconds while media_position_updated_at is not updated. From what I understand this is a bug in the spotify integration. I opened a github issue for it, but in the meantime, the best we can do is use the paused progress bar calculation media_position / media_duration * 100 for spotify. However, that means the progress bar is only updated every 30 seconds, which kind of sucks, but I guess it’s better than being wrong.

I only tested briefly, but this should work for progress_bar.yaml

progress_bar:
  styles:
    custom_fields:
      progress:
        - background-color: var(--progress-bar-background-color)
        - position: absolute
        - left: 0%
        - top: 73.4%
        - height: var(--progress-bar-height)
        - width: 100%
      bar:
        - background-color: var(--progress-bar-color)
        - position: absolute
        - left: 0%
        - top: 73.4%
        - height: var(--progress-bar-height)
        - z-index: 1
        - transition: 1s ease-out
  custom_fields:
    bar: >
      [[[
        if (entity.attributes.media_position !== undefined) {
        setTimeout(() => {
          let elt = this.shadowRoot,
              card = elt.getElementById('card'),
              container = elt.getElementById('container'),
              bar = elt.getElementById('bar');
        
          if (elt && card && container && bar) {
              card.insertBefore(bar, container);
                function update() {
                  let mediaPositionUpdatedAt = entity.attributes.media_position_updated_at,
                      mediaPosition = entity.attributes.media_position,
                      mediaDuration = entity.attributes.media_duration,
                      mediaContentID = entity.attributes.media_content_id;

                  let percentage = entity.state === 'paused' || (typeof mediaContentID === 'string' && mediaContentID.includes('spotify'))
                    ? (mediaPosition / mediaDuration * 100)
                    : entity.state === 'playing'
                      ? (((Date.now() / 1000) - (new Date(mediaPositionUpdatedAt).getTime() / 1000) + mediaPosition) / mediaDuration * 100)
                      : 0;

                  bar.style.width = percentage.toFixed(1) + '%';
                  requestAnimationFrame(update);
                }
                requestAnimationFrame(update);
          }
        }, 0);
        return ' ';}
      ]]]
    progress: >
      [[[
        if (entity.attributes.media_position !== undefined) {
        setTimeout(() => {
            let elt = this.shadowRoot,
                card = elt.getElementById('card'),
                container = elt.getElementById('container'),
                progress = elt.getElementById('progress');

            if (elt && card && container && progress) {
                card.insertBefore(progress, container);
            }
          }, 0);
        return ' ';}
      ]]]

I’m sorry but I was just doing the test, unfortunately I was away from home for work. thank you very much for the momentary resolution, I will adopt it immediately but I hope they resolve. Thanks again

Hi,
Just got back after a couple of months off, But i now see a border around all my buttons?
Any ideas on how to remove?


image

Read the OP.

Hi everyone,
Can you please help me?
I have an LG washing machine that reports the time remaining in the wash cycle as h:m (1:35 for example).
Does anyone have any advice on how to make this time appear in the circle (to the right of the icon) in the same format as for person how long they are at home/not_home?
Like, for example, 1h 35m until the end of the cycle, so that it shows like 2h. And under 1h until the end of the cycle (like 55m) it was displayed as 55m etc
Thank you so much

I actually just did this with my LG washer / dryer, and my cat caused me to spill coffee all over my bedding this morning so I can show you what it looks like in action. I set up a custom template so the state displays run cycle if the unit is on, and if it’s off, the state displays how long it’s been since it was last on. The circle itself runs off a percentage value remain_time / initial_time * 100 while the text inside the circle displays total minutes remaining. I use card_mod to hide the circle if it’s off.

edit: simplify template

2023-0-31_12-48-54

packages/laundry.yaml

template:
  - sensor:
    - unique_id: washer
      name: >
        {{ states.sensor.washer.attributes.friendly_name }}
      state: >
        {%- if states.sensor.washer.state == "off" -%}
          {% set time_diff = as_timestamp(now()) - as_timestamp(states.sensor.washer.last_changed) %}
          {% set time_diff_minutes = time_diff/60 %}
          {% set time_diff_hours = time_diff_minutes/60 %}
          {% set time_diff_days = time_diff_hours/24 %}
          {% if time_diff_minutes <= 59 %}
            {{ time_diff_minutes | round(0) }} min{% if time_diff_minutes > 1 %}s{% endif %} ago
          {% elif time_diff_minutes > 59 and time_diff_minutes < 1440 %}
            {{ time_diff_hours | round(0) }} hour{% if time_diff_hours > 1 %}s{% endif %} ago
          {% else %}
            {{ time_diff_days | round(0) }} day{% if time_diff_days > 1 %}s{% endif %} ago
          {% endif %}
        {%- elif states.sensor.washer.state == "on" -%}
         {{ states.sensor.washer.attributes.run_state }}
        {%- endif -%}
      attributes:
        remain_time: >
          {% set h, m, s = states.sensor.washer.attributes.remain_time.split(":") %}
          {{ ((h | int * 60) + (m | int) + (s | int) / 60) | round() }}
        initial_time: >
          {% set h, m, s = states.sensor.washer.attributes.initial_time.split(":") %}
          {{ ((h | int * 60) + (m | int) + (s | int) / 60) | round() }}
  - sensor:
    - unique_id: dryer
      name: >
        {{ states.sensor.dryer.attributes.friendly_name }}
      state: >
        {% if states.sensor.dryer.state == "off" %}
          {% set time_diff = as_timestamp(now()) - as_timestamp(states.sensor.dryer.last_changed) %}
          {% set time_diff_minutes = time_diff/60 %}
          {% set time_diff_hours = time_diff_minutes/60 %}
          {% set time_diff_days = time_diff_hours/24 %}
          {% if time_diff_minutes <= 59 %}
            {{ time_diff_minutes | round(0) }} min{% if time_diff_minutes > 1 %}s{% endif %} ago
          {% elif time_diff_minutes > 59 and time_diff_minutes < 1440 %}
            {{ time_diff_hours | round(0) }} hour{% if time_diff_hours > 1 %}s{% endif %} ago
          {% else %}
            {{ time_diff_days | round(0) }} day{% if time_diff_days > 1 %}s{% endif %} ago
          {% endif %}
        {% elif states.sensor.dryer.state == "on" %}
         {{ states.sensor.dryer.attributes.run_state }}
        {% endif %}
      attributes:
        remain_time: >
          {% set h, m, s = states.sensor.dryer.attributes.remain_time.split(":") %}
          {{ ((h | int * 60) + (m | int) + (s | int) / 60) | round() }}
        initial_time: >
          {% set h, m, s = states.sensor.dryer.attributes.initial_time.split(":") %}
          {{ ((h | int * 60) + (m | int) + (s | int) / 60) | round() }}

my button cards are set up like this

- type: custom:button-card
  entity: sensor.template_washer
  tap_action:
    !include popup/laundry.yaml
  template:
    - base
    - icon_washer
    - circle
  variables:
    state_on: >
      [[[ return ['Detecting', 'Washing', 'Rinsing', 'Spinning'].indexOf(!entity || entity.state) !== -1; ]]]
    circle_input: >
      [[[
        if (entity) {
          let initial_time = entity.attributes.initial_time,
              remain_time = entity.attributes.remain_time,
              percent = (remain_time / initial_time) * 100,
              result = Math.round(percent);
          return ['Detecting', 'Washing', 'Rinsing', 'Spinning'].includes(entity.state) ? result : 'NA';
        }
      ]]]
    circle_input_unit: 'm'
    circle_text: >
      [[[
      if (entity) {
          let remain_time = entity.attributes.remain_time;
          return ['Detecting', 'Washing', 'Rinsing', 'Spinning'].includes(entity.state) ? remain_time : 'NA';
        }
      ]]]
  card_mod:
    style: |
      #circle {
        {%- if states.sensor.template_washer.attributes.remain_time == 0 -%}
          display:none !important;
        {% endif %}
      }
- type: custom:button-card
  entity: sensor.template_dryer
  tap_action:
    !include popup/laundry.yaml
  template:
    - base
    - icon_dryer
    - circle
  variables:
    state_on: >
      [[[ return ['Detecting', 'Drying', 'Cooling'].indexOf(!entity || entity.state) !== -1; ]]]
    circle_input: >
      [[[
        if (entity) {
          let initial_time = entity.attributes.initial_time,
              remain_time = entity.attributes.remain_time,
              percent = (remain_time / initial_time) * 100,
              result = Math.round(percent);
          return ['Detecting', 'Drying', 'Cooling'].includes(entity.state) ? result : 'NA';
        }
      ]]]
    circle_input_unit: 'm'
    circle_text: >
      [[[
      if (entity) {
          let remain_time = entity.attributes.remain_time;
          return ['Detecting', 'Drying', 'Cooling'].includes(entity.state) ? remain_time : 'NA';
        }
      ]]]
  card_mod:
    style: |
      #circle {
        {%- if states.sensor.template_dryer.attributes.remain_time == 0 -%}
          display:none !important;
        {% endif %}
      }

and you have to make a couple edits to button_card_templates/circle.yaml so the text can be a different value than circle_input.
Change line 68 from

<text id="circle_value" x="50%" y="52%">${input}${tspan}${unit}</tspan></text>

to

<text id="circle_value" x="50%" y="52%">${domain === 'sensor' ? ctext : input}${tspan}${unit}</tspan></text>

and add ctext = variables.circle_text || ' ', between line 31 and 32.

That will affect all sensors with the circle template, but there aren’t any default sensor cards that use the circle template.

and lastly, I made a couple of animated icons based on the mdi icons. I used the fan animation as inspiration.

add to icons.yaml

icon_washer:
  styles:
    custom_fields:
      icon:
        - width: 78%
        - margin-left: -6%
  custom_fields:
    icon: >
      [[[
        let center = `
          <path d="M31.8,23.1c3.8,3.8,3.8,9.8,0,13.6c-3.8,3.8-9.8,3.8-13.6,0L31.8,23.1"/>
          <path fill="none" d="M25,15.4c-8,0-14.4,6.4-14.4,14.4S17,44.3,25,44.3s14.4-6.4,14.4-14.4S33,15.4,25,15.4z"/>
        `,
        base = `
          <path fill="#9da0a2" d="M10.6,1.1h28.7c2.6,0,4.8,2.1,4.8,4.8v38.4c0,2.6-2.1,4.8-4.8,4.8H10.6c-2.6,0-4.8-2.1-4.8-4.8V5.8C5.8,3.2,8,1.1,10.6,1.1 M13.1,5.8c-1.4,0-2.4,1.1-2.4,2.4c0,1.4,1.1,2.4,2.4,2.4s2.4-1.1,2.4-2.4S14.3,5.8,13.1,5.8 M20.2,5.8c-1.4,0-2.4,1.1-2.4,2.4 c0,1.4,1.1,2.4,2.4,2.4c1.4,0,2.4-1.1,2.4-2.4S21.5,5.8,20.2,5.8 M25,15.4c-8,0-14.4,6.4-14.4,14.4S17,44.3,25,44.3 s14.4-6.4,14.4-14.4S33,15.4,25,15.4z"/>
        `,
        style = `
          <svg viewBox="0 0 50 50">
            <style>
              @keyframes rotate {
                0% {
                  visibility: visible;
                  transform: rotate(0deg) translateZ(0);
                }
                100% {
                  transform: rotate(720deg) translateZ(0);
                }
              }
              .start {
                animation: rotate 2.8s ease-in;
                transform-origin: center 60%;
                fill: #5daeea;
                visibility: hidden;
                will-change: transform;
              }
              .on {
                animation: rotate 1.8s linear infinite;
                transform-origin: center 60%;
                fill: #5daeea;
                animation-delay: 2.8s;
                visibility: hidden;
                will-change: transform;
              }
              .end {
                animation: rotate 2.8s;
                transform-origin: center 60%;
                fill: #9ca2a5;
                animation-timing-function: cubic-bezier(0.61, 1, 0.88, 1);
                will-change: transform;
              }
              .start_timeout {
                animation: rotate 1.8s linear infinite;
                transform-origin: center 60%;
                fill: #5daeea;
                visibility: hidden;
                will-change: transform;
              }
              .end_timeout {
                fill: #9ca2a5;
              }
            </style>
        `;
        if (variables.state_on) {
          return `${style}${base}<g class="start">${center}</g><g class="on">${center}</g></svg>`;
        }
        if (!variables.state_on) {
          return `${style}${base}<g class="end">${center}</g></svg>`;
        }
        if (variables.state_on && variables.timeout > 2000) {
          return `${style}${base}<g class="start_timeout">${center}</g></svg>`;
        } else {
          return `${style}${base}<g class="end_timeout">${center}</g></svg>`;
        }
      ]]]

icon_dryer:
  styles:
    custom_fields:
      icon:
        - width: 78%
        - margin-left: -6%
  custom_fields:
    icon: >
      [[[
        let center = `
          <path d="M15.7,21.5h4.6c-0.6,3.3,0,5.2,1.4,6.7c2.6,2.5,3.8,5.8,3.1,10.1h-4.6c0.6-3.3,0-5.2-1.4-6.7 C16.2,28.9,15.1,25.7,15.7,21.5"/>
        	<path d="M25.3,21.5h4.6c-0.6,3.3,0,5.2,1.4,6.7c2.6,2.5,3.8,5.8,3.1,10.1h-4.6c0.6-3.3,0-5.2-1.4-6.7 C25.8,28.9,24.6,25.7,25.3,21.5z"/>
        	<path fill="none" d="M25,15.4c-8,0-14.4,6.4-14.4,14.4S17,44.3,25,44.3s14.4-6.4,14.4-14.4S33,15.4,25,15.4"/>
        `,
        base = `
          <path fill="#9da0a2" d="M10.6,1.1h28.7c2.6,0,4.8,2.1,4.8,4.8v38.4c0,2.6-2.1,4.8-4.8,4.8H10.6c-2.6,0-4.8-2.1-4.8-4.8V5.8C5.8,3.2,8,1.1,10.6,1.1 M13.1,5.8c-1.4,0-2.4,1.1-2.4,2.4c0,1.4,1.1,2.4,2.4,2.4s2.4-1.1,2.4-2.4S14.3,5.8,13.1,5.8 M20.2,5.8c-1.4,0-2.4,1.1-2.4,2.4 c0,1.4,1.1,2.4,2.4,2.4c1.4,0,2.4-1.1,2.4-2.4S21.5,5.8,20.2,5.8 M25,15.4c-8,0-14.4,6.4-14.4,14.4S17,44.3,25,44.3 s14.4-6.4,14.4-14.4S33,15.4,25,15.4"/>
        `,
        style = `
          <svg viewBox="0 0 50 50">
            <style>
              @keyframes rotate {
                0% {
                  visibility: visible;
                  transform: rotate(0deg) translateZ(0);
                }
                100% {
                  transform: rotate(720deg) translateZ(0);
                }
              }
              .start {
                animation: rotate 2.8s ease-in;
                transform-origin: center 60%;
                fill: #5daeea;
                visibility: hidden;
                will-change: transform;
              }
              .on {
                animation: rotate 1.8s linear infinite;
                transform-origin: center 60%;
                fill: #5daeea;
                animation-delay: 2.8s;
                visibility: hidden;
                will-change: transform;
              }
              .end {
                animation: rotate 2.8s;
                transform-origin: center 60%;
                fill: #9ca2a5;
                animation-timing-function: cubic-bezier(0.61, 1, 0.88, 1);
                will-change: transform;
              }
              .start_timeout {
                animation: rotate 1.8s linear infinite;
                transform-origin: center 60%;
                fill: #5daeea;
                visibility: hidden;
                will-change: transform;
              }
              .end_timeout {
                fill: #9ca2a5;
              }
            </style>
        `;
        if (variables.state_on) {
          return `${style}${base}<g class="start">${center}</g><g class="on">${center}</g></svg>`;
        }
        if (!variables.state_on) {
          return `${style}${base}<g class="end">${center}</g></svg>`;
        }
        if (variables.state_on && variables.timeout > 2000) {
          return `${style}${base}<g class="start_timeout">${center}</g></svg>`;
        } else {
          return `${style}${base}<g class="end_timeout">${center}</g></svg>`;
        }
      ]]]

I know this is a bit different than what you had asked for, but I’m sure you can find some code in here to help you accomplish what you’re after.

25 Likes

Thanks again for this! was just trying to figure out how to achive the same with my sauna to let the circle show the progress of heating till set temperature.

Got it figured out now based on your code!

2 Likes

Great thanks a lot!! Would anyone be able to help adjust the letter m and h to be in the same plane as the number of hours? They saw a smaller difference in height

That is set with the tspan element, specifically dy="-.4" is what is raising the text above the baseline. If you want to change it for everything, you can adjust the tspan variable in circle.yaml and set dy="0" (or a larger positive value to shift it further down).

Keep in mind the font size is different for unit vs text (10.5px vs 14px) as well. You can adjust that in the styles in circle.yaml. If the font size is the same for unit & text, then dy should be 0 to get the same baseline. If you want to keep the smaller size, you may have to set dy to a positive value to shift it further down.

If you don’t want to change it for everything, you could define an alternate tspan variable and add a check for 'sensor' similar to how I replaced the input with ctext in the #circle_value text element.

I’m trying to accomplish a backup for plex last added but I’m unsure how to proceed. Hopefully, someone has some pointers :no_mouth:. Simplified whenever the Main server with plex is offline I get no image and I want to bypass that somehow and ideally make it a bit obvious that the server is down.

Before: image

After: image

or image

Current issue:
Whenever my server is offline the [sensor.plex_recently_added] returns “cannot be reached” and as such, the media card displays an empty space. (Conditional media is for Last Added although its unreachable)

What I’m trying to achieve:
1.Create a new condition where instead of the [sensor.plex_recently_added] it takes the output image (which is still present in the /www/upcoming-media-card-images/plex/Plex_Recently_Added/ NAME.jpg)
2. Display that image instead whenever the server holding plex is unreachable due to Shutdown/Sleep
3. Display a text/image or something which should point me toward the fact that the server is “off”

Or altogether don’t rely on the sensor.plex_recently_added but create “something” that only points towards the image and thus, is always available. And only use the recently added to deal with updating the image.

I do know that the sensor carries the title and other information; however I do not care if the title was missing as long as I see an image :sweat_smile:

Thanks for all your good Job !
I verry like all what you do !
Can you please share your !include popup/laundry.yaml
Thanks

Sure, you have to make some extra sensors if you want the graph. Edit you don’t need the extra sensors, you can use apex charts with attributes.

this is my popup/laundry.yaml

action: fire-dom-event
haptic: medium
browser_mod:
  service: browser_mod.popup
  data:
    title: Laundry
    card_mod:
      style:
        #popup header
        .: |
          div.content {
            margin-top: -24px !important;
          }
    content:
      type: custom:tabbed-card
      styles:
        --mdc-theme-primary: var(--primary-text-color)
        --mdc-tab-text-label-color-default: rgba(var(--rgb-primary-text-color), 0.8)
        --mdc-typography-button-font-size: 14px
      tabs:
        - card:
            type: vertical-stack
            cards:
              - type: entities
                card_mod:
                  class: content
                  style: |
                    #states {
                      padding-top: 1.2em;
                      padding-bottom: 0em;
                    }
                entities:
                  - type: custom:mushroom-entity-card
                    entity: sensor.washer
                    name: Washer
                    icon: mdi:washing-machine
                    primary_info: name
                    secondary_info: state
                    card_mod:
                      style: |
                        mushroom-card {
                          margin: -5px -13px 0 -4px;
                        }
                  - type: attribute
                    entity: sensor.washer
                    attribute: run_state
                    icon: mdi:washing-machine
                    name: Run State
                  - type: attribute
                    entity: sensor.washer
                    attribute: current_course
                    icon: mdi:format-list-bulleted-square
                    name: Current Course
                  - type: attribute
                    entity: sensor.washer
                    attribute: spin_speed
                    icon: mdi:rotate-right
                    name: Spin Speed
                  - type: attribute
                    entity: sensor.washer
                    attribute: water_temp
                    icon: mdi:thermometer
                    name: Water Temp
                  - type: attribute
                    entity: sensor.washer
                    attribute: remain_time
                    icon: mdi:clock
                    name: Time Remaining
              - type: custom:apexcharts-card
                layout: minimal
                locale: en
                graph_span: 8h
                show:
                  loading: false
                apex_config:
                  plotOptions:
                    area:
                      fillTo: end
                  grid:
                    padding:
                      top: -15
                  fill:
                    type: gradient
                    gradient:
                      type: vertical
                      opacityFrom: 0.8
                      opacityTo: 0
                      stops:
                        - 0
                        - 99
                        - 100
                  stroke:
                    width: 4
                  tooltip:
                    style:
                      fontSize: 14px
                    x:
                      format: dddd HH:mm
                  chart:
                    height: 100px
                    offsetY: -20px
                  xaxis:
                    crosshairs:
                      show: false
                series:
                  - entity: sensor.washer
                    attribute: remain_time
                    name: Time Remaining
                    color: '#385581'
                    type: area
                    fill_raw: last
                    group_by:
                      func: raw
                      duration: 1h
          attributes:
            label: Washer
        - card:
            type: vertical-stack
            cards:
              - type: entities
                card_mod:
                  class: content
                  style: |
                    #states {
                      padding-top: 1.2em;
                      padding-bottom: 0em;
                    }
                entities:
                  - type: custom:mushroom-entity-card
                    entity: sensor.dryer
                    name: Dryer
                    icon: mdi:tumble-dryer
                    primary_info: name
                    secondary_info: state
                    card_mod:
                      style: |
                        mushroom-card {
                          margin: -5px -13px 0 -4px;
                        }
                  - type: attribute
                    entity: sensor.dryer
                    attribute: run_state
                    icon: mdi:tumble-dryer
                    name: Run State
                  - type: attribute
                    entity: sensor.dryer
                    attribute: current_course
                    icon: mdi:format-list-bulleted-square
                    name: Current Course
                  - type: attribute
                    entity: sensor.dryer
                    attribute: dry_level
                    icon: mdi:water-off
                    name: Dry Level
                  - type: attribute
                    entity: sensor.dryer
                    attribute: temp_control
                    icon: mdi:thermometer
                    name: Temp Control
                  - type: attribute
                    entity: sensor.dryer
                    attribute: remain_time
                    icon: mdi:clock
                    name: Time Remaining

              - type: custom:apexcharts-card
                layout: minimal
                locale: en
                graph_span: 8h
                show:
                  loading: false
                apex_config:
                  plotOptions:
                    area:
                      fillTo: end
                  grid:
                    padding:
                      top: -15
                  fill:
                    type: gradient
                    gradient:
                      type: vertical
                      opacityFrom: 0.8
                      opacityTo: 0
                      stops:
                        - 0
                        - 99
                        - 100
                  stroke:
                    width: 4
                  tooltip:
                    style:
                      fontSize: 14px
                    x:
                      format: dddd HH:mm
                  chart:
                    height: 100px
                    offsetY: -20px
                  xaxis:
                    crosshairs:
                      show: false
                series:
                  - entity: sensor.dryer
                    attribute: remain_time
                    name: Time Remaining
                    color: '#385581'
                    type: area
                    fill_raw: last
                    group_by:
                      func: raw
                      duration: 1h
          attributes:
            label: Dryer
6 Likes

I was trying the above weather widget. How i can add the left margin? seems to be offWeather_Widget

Figured out - margin-left: 10px

1 Like

When I look in my logfile I get "*

[homeassistant.helpers.template] Template variable warning: ‘None’ has no attribute ‘state’ when rendering ' {% set lights = [

what does that mean ?

Probably you have at least one wrong entity in the sidebar.yaml . Did you replace Mattias’es ones with yours?

      active: >
          <b>
          {% set lights = [
            states.light.vardagsrum_tv,
           ...........
            states.switch.deltaco_sh_p01_socket
          ] %}

image
looking great on desktop.

Bildschirmfoto 2023-02-03 um 15.03.36
Any ideas, on how to add a line break, or marquee on mobile?

what do you mean by “wrong” ? a typo ?

Well if I understand it correctly for HA - “None” is a state which means undefined. Thus you might have a sensor, but it has not been initialized to a value yet, or there is a misspelling and thus that sensor doesn’t actually exist and therefore it is “None”.

You could check this in various ways, either by checking them through Developer Tools>States for each sensor/entity which you added in your template; Or copying your template in Developer Tools>Templates and then testing that one to check which is the one which returns “None”

So if the template was like this

{% set lights = [
            states.light.vardagsrum_tv
             ] %}
.......

It would return the None template issue because the light doesn’t actually exist as seen below.

This has been bothering me, too.
Your question was just the push I needed to try and come up with a temporary solution - and I think I got something working.
I’d much prefer if the root cause got fixed, but let’s make this a learning experience.
It’s not the most elegant, but it does the job – hopefully.

Preview

Plex Recently Added Backup

image

Requirements

Helper (Helper entity, to store the info)

Example: input_text.backup_plex_recently_added

I created one via the GUI:
Navigate using command: Press the letter C → Type: Helpers → Press enter

image

Or, browse here: Settings → Devices & Services → Helpers → Create Helper → Text

image

OBS: Maximum length should be 255

Template sensor (To make it easier with JavaScript later)

This is unnecessary, but it makes sense to me that we have a sensor with attributes.
I tried creating an attribute with a dict similar to the ‘Recently Added’ sensor, but jinja and the formatting got weird on me, so I just did this for now:

This sensor is grabbing the state of the input_text helper, and cleans it up.

Like this

Template select

Updated the select template to handle the new sensor.

Automation (To take backup of 1 item in the ‘Recently Added’ sensor)

Not tested properly yet…

Triggers

platform: state
entity_id:
  - sensor.recently_added_mix
to: Online

Conditions

condition: template
value_template: >-
  {{ trigger.to_state.state not in [trigger.from_state.state, 'cannot be
  reached', 'unavailable', 'undefined','unknown','none','null'] }}

Actions

service: input_text.set_value
data:
  value: >-
    {% if not states('sensor.recently_added_mix') in
    ['unavailable','undefined','unknown','none','null','0'] %}
      {% set state = namespace(return='') %}
      {% set data = state_attr('sensor.recently_added_mix','data') %}
      {%- for value in data %}
        {%- if not loop.first and value is defined and state.return == '' %}
          {%- if not value.number is defined %}
            {% set state.return = 
              "aired:" + value.aired + "|" +
              "title:" + value.title  + "|" +
              "fanart:" + value.fanart + "|" +
              "poster:" + value.poster
            %}
          {%- else %}
            {% set state.return = 
              "aired:" + value.aired + "|" +
              "title:" + value.title  + "|" +
              "number:" + value.number  + "|" +
              "fanart:" + value.fanart + "|" +
              "poster:" + value.poster
            %}
          {%- endif %}
        {%- endif %}
      {%- endfor %}
      {{ state.return }}
    {% endif %}
target:
  entity_id: input_text.backup_plex_recently_added

lovelace.yaml – swipe-card update

button_card_templates.yaml – conditional_media update

(state_display and background-image)

Edit: Added a change in the text color.
This is optional, of course, but I wanted another indicator that something was wrong.

conditional_media:

        - color: >
            [[[
              if (entity.state === "Active") {
                return entity === undefined
                    ? '#97989c'
                    : 'rgba(239, 239, 239, 0.5)';
              }
              else {
                return entity === undefined
                    ? '#97989c'
                    : '#efefef';
              }
            ]]]

PS: I haven’t tested it fully yet, so try at your own risk :wink:

6 Likes