Since_last_boot gone, now how to?

Yeah that’s one thing I’m more conscious of as I have so many, however I have an i5 nuc with 32gb ram so I think it’ll be right.

It almost certainly is, hence I only ‘use’ it :wink:

And yes…

I think I prefer this too.

Except that the longer the uptime the more wildly ‘inaccurate’ it becomes.
‘1 month, 29 days’ will show as ‘1 month’
or worse still (but probably unlikely :slight_smile: )
‘1 year 11 months and 29 days’ will show as ‘1 year’

But it does give room for the ‘since’ date to display fully

image

Yep those are drawbacks too. However I don’t believe it does months because months don’t easily fit into calculations.

And it’s hardly information that is critical to anything, for me at least.
Thanks for the upgrade/improvement/alternative (again).

And I love your buttons…

I’ll have a configuration soon for the buttons. Still playing around with everything at the moment. My github currently has them if you can follow lovelace_gen. The 2 current versions /lovelace/templates/on_off_button.yaml and /lovelace/templates/sensor_button.yaml. Both have had updates that I haven’t pushed yet.

Ha! I’ve just been going back through the button-card post to find if you ever posted it. I remember seeing a picture of them and thinking that I might like to (ahem) borrow that.

I’m looking forward to seeing it…

I’ll check out the github. Yes I use a LOT of lovelace_gen

image

The on_off_button is pretty much the same, just removed some redundant style configs.

The sensor_button is 100% different and has a ton of ‘bug’ fixes.

2 Likes

Looks nice and clean and far easier to manage then my Frankenstein’s monster

1 Like

I couldn’t use your sensor in the end as it didn’t template correctly on my setup (all custom cards)

Ah, yes, if you use custom cards, the card needs to use device_classes. If you use javascript cards, I can post the JS that I use

        var date = (statestr && entity.attributes.device_class === 'timestamp') ? new Date(statestr) : null;
        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 `${values[0]} ${values[1]} ago`;
        }
        return '';

The benefit here is that you offload the processing power to the unit loading the JS intead of home assistant.

2 Likes

Would you have any idea of the load this takes?
Using my share of larger backend templates myself as you might know… this is an example of a template which is evaluated continuously while the JS version only weighs in on view.

Still, wondering about the actual benefit in figures.

Don’t know the actual numbers, but it could be noticeable on a old raspi. Newer ones might not have the issue.

I’m loving these buttons. Can’t wait for the final versions!

image image

I couldn’t get the value of the timer in the circle but swapping it with the last-changed seems to work nicely.

Actually I could get the timer countdown in the circle but I couldn’t anchor the middle of the text to the middle of the circle. (The actual time isn’t exposed to the javascript as far as I can tell).

Of course go ahead and prove me wrong if you want :slight_smile:

Sorry… we’re a bit off topic here.

(By the way, is that a typo in the middle button? ‘Office Fixure’)

that’s a typo :man_facepalming:

Here’s the current version of the sensor, give it a whirl. See if everything is centered.

# lovelace_gen

{% set color = color|default('var(--paper-item-icon-active-color)') %}
{% set ratio = ratio|default('1/1') %}
{% set imgratio = 'transparent_' ~ 'to'.join(ratio.split('/')) ~ '.png' %}

type: picture-elements
image: /local/images/{{ imgratio }}
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:
    - {{ entity }}
  group: true
  points_per_hour: 1
  hour24: true
  line_color: {{ color }}
  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: {{ ratio }}
  entity: {{ entity }}
  icon: {{ icon }}
  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="{{ color }}" />
              <text x="50%" y="46%" 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="{{ color }}" />
              <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) {
          return `
            <svg viewBox="0 0 50 50">
              <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
              <text x="50%" y="50%" 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="{{ color }}" />
              <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="{{ color }}" />
              <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>
            `; 
      ]]]
      

EDIT: It will probably require modifications for xx:xx timestamps. Let me know if it’s needed. Shouldn’t be too hard to change.

EDIT 2: Also want to point out that this rendition has a performance optimize that the one in my current repo does not have.

Only had a quick look but I still get the same problem with not actually getting the time remaining. Can the javascript see this or can it only see ‘active’?

image

Also some sizing issues but I suspect I just haven’t looked properlay at what is going on in this button.
And there is a lot going on!!!

You need to download the square transparent image for sensor_button called transparent_1_to_1.png.

What’s the sensor and it’s attributes?

It’s not, it is a timer.

Ah, so that must be under a different domain? Does it countdown Real time?

It does if you just display it as an entity in an entity card and it does using your button so long as the state is used outside javascript:

I’ve just been playing araound and I adapted teh on-off button. The entity.state in here only returns active or idle

      else if (entity.state === 'on') {
        const radius = (length - width) / 2;
        return `
          <svg viewBox="0 0 50 50">
            <circle cx="25" cy="25" r="${radius}" fill="{{ color }}" />
            <text x="50%" y="51%" fill="var(--paper-card-background-color)" font-weight="bold" font-size="14" text-anchor="middle" alignment-baseline="middle">${capitalizeFirstLetter(entity.state)}
            </text>
          </svg>
          `;
      }

But outside I can get the actual counting down time remaining.

amongst other things I changed…

- grid-template-areas: '"info" "n" "s" "l"'

and added a label style like the state
both with state defined styles based on ‘active’ or ‘idle’ as appropriate

- display: none

As I said, just playing at the moment

I hope that makes sense. In a hurry right now and gotta go…