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
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 )
‘1 year 11 months and 29 days’ will show as ‘1 year’
But it does give room for the ‘since’ date to display fully
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
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.
Looks nice and clean and far easier to manage then my Frankenstein’s monster
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.
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!
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
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
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’?
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…