Very cool. thanks guys. I’ll look into both. I’m trying to keep the time spent in a zone inside the circle. But have the circle show the percentage. I should be able to figure it out now. Thanks
Hmmm. I’m almost there. But for some reason, the units (timed presence) inside the circle won’t show.
Dashboard:
cards:
- type: custom:button-card
entity: sensor.paul_combined
name: Paul
triggers_update:
- sensor.paul_last_changed
tap_action: !include popup/home_paul.yaml
template:
- person
variables:
battery_level: sensor.pauls_iphone_12_battery_level
battery_charge: sensor.pauls_iphone_battery_status
person.yaml:
person:
template:
- base
- circle
state_display: >
[[[
if (entity) {
const stateperson = entity.state === 'home' ? variables.translate_home : variables.state === 'not_home' ? variables.translate_not_home : variables.state;
return `<div style="white-space: nowrap; overflow: hidden; text-overflow: clip; width: 100%;" scrollamount="3">${stateperson}</div>`
}
return variables.translate_unknown;
]]]
triggers_update: sensor.time
tap_action:
action: none
variables:
circle_input: >
[[[
if (entity && entity.last_changed) {
const lastChanged = new Date(entity.last_changed);
const now = new Date();
const diff = Math.floor((now - lastChanged) / 1000); // difference in seconds
if (diff < 60) return `${diff}s`;
if (diff < 3600) return `${Math.floor(diff / 60)}m`;
if (diff < 86400) return `${Math.floor(diff / 3600)}h`;
return `${Math.floor(diff / 86400)}d`;
}
return '';
]]]
battery_level: ' '
battery_charge: ' '
styles:
custom_fields:
icon:
- clip-path: circle()
- width: 82%
- pointer-events: none
- display: grid
steps:
- position: absolute
- right: 5%
- bottom: 5%
- font-size: 20px
- font-weight: 500
- color: '#4b5254'
charge_icon:
- position: absolute
- right: 20%
- left: 58%
- top: 70%
custom_fields:
icon: >
[[[
return entity && entity.attributes.entity_picture
? `<img src="${entity.attributes.entity_picture}" width="100%">`
: null;
]]]
steps: >
[[[
return entity && entity.attributes.steps
? `${entity.attributes.steps}`
: '';
]]]
circle: >
[[[
let inputs = states[variables.battery_level].state,
radius = 22.1,
circumference = radius * 2 * Math.PI;
var color = "rgba(48, 128, 181, 0.8)";
if (inputs <= 10) {
color = "#FDD60F";
} else if (inputs <= 25) {
color = "rgba(48, 128, 181, 0.8)";
}
else {
color = "#27C950";
}
return `
<svg viewBox="0 0 50 50">
<style>
circle {
transform: rotate(-90deg);
transform-origin: 50% 50%;
stroke-dasharray: ${circumference};
stroke-dashoffset: ${circumference - inputs / 100 * circumference};
}
</style>
<circle cx="25" cy="25" r="${radius}" stroke="${color}" stroke-width="2" fill="none" stroke-linecap="round"/>
</svg>
`;
]]]
charge_icon: >
[[[
const state = states[variables.battery_charge].state;
const lightbulbIcon = `<ha-icon icon="mdi:flash" style="width: 20px; height: 20px; color: red;"></ha-icon>`;
const blinkingAnimation = '@keyframes blink { 50% { opacity: 0; } }';
if (state === 'Charging') {
return `
${lightbulbIcon}
<style>
${blinkingAnimation}
[lightbulb] {
animation: blink 1s infinite;
}
</style>
`;
} else {
return " ";
}
]]]
My circle.yaml file is the same as the original with this code still in there:
/* * * * * * * * * * * * * * * * * *
* *
* PERSON *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'person') {
// Time calculation logic
let time = c => {
let s = (c / 1e3),
m = (c / 6e4),
h = (c / 36e5),
d = (c / 864e5);
return s < 60
? parseInt(s) + 's'
: m < 60 ? parseInt(m) + 'm'
: h < 24 ? parseInt(h) + 'h'
: parseInt(d) + 'd';
};
let input = states[variables.retain] === undefined || states[variables.retain].state === 'unavailable'
? time(Date.now() - Date.parse(entity.last_changed))
: time(Date.now() - Date.parse(states[variables.retain].state)),
unit = ' ';
return circle(state, input, unit);
}
I do realize that I took out this line:
<text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${input}<tspan font-size="10">${unit}</tspan></text>
But it was because I didn’t want the battery level percentage inside the circle.
I still want the time spent in said zone.
I can’t figure out what’s wrong.
EDIT: Or is it that I have to now define the circle_input within the person.yaml?
Now I’m not sure what you need at all. Do you want to have time in the circle, but a stroke for battery level?
You don’t use the ‘person’ domain for the main entity, so the input for the circle is using battery state. Add another variable like ‘person_retain’ with sensor ‘sensor.paul_last_changed’, further in template add variable for display the text in circle…
updated template with added retain variable and battery charging state icon
battery_circle:
template:
- base
- circle
variables:
battery: ' '
circle_unit: ' '
person_retain: ' '
battery_status: ' '
state_display: >
[[[
if (entity) {
return variables.state === 'home'
? variables.translate_home
: variables.state === 'not_home'
? variables.translate_not_home
: variables.state;
}
return variables.translate_unknown;
]]]
triggers_update: sensor.time
custom_fields:
icon: >
[[[
return entity && variables.entity_picture
? `<img src="${variables.entity_picture}" width="100%">`
: null;
]]]
circle: >
[[[
let input = states[variables.battery].state,
radius = 20.5,
circumference = radius * 2 * Math.PI;
let unit = variables.circle_unit;
let time = c => {
let s = (c/1e3),
m = (c/6e4),
h = (c/36e5),
d = (c/864e5);
return s < 60
? parseInt(s) + 's'
: m < 60 ? parseInt(m) + 'm'
: h < 24 ? parseInt(h) + 'h'
: parseInt(d) + 'd';
};
let person_retain = states[variables.person_retain] !== undefined || states[variables.person_retain].state !== 'unavailable'
? time(Date.now() - Date.parse(states[variables.person_retain].state))
: time(Date.now() - Date.parse(entity.last_changed));
var color = "rgba(48, 128, 181, 0.8)";
if (input <= 20) {
color = "#FDD60F";
} else if (input <= 40) {
color = "rgba(48, 128, 181, 0.8)";
}
else {
color = "#27C950";
}
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;
}
.flash-icon {
font-size: 14px;
display: ${states[variables.battery_status].state === 'charging' ? 'block' : 'none'};
animation: blink 1s linear infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
</style>
<circle cx="25" cy="25" r="${radius}" stroke="${color}" stroke-width="3" fill="none" stroke-linecap="round"/>
<text x="50%" y="15%" class="flash-icon" text-anchor="middle" alignment-baseline="middle">⚡</text>
<text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${person_retain}<tspan font-size="10">${unit}</tspan></text>
</svg>
`;
]]]
styles:
custom_fields:
icon:
- clip-path: circle()
- width: 82%
- pointer-events: none
- display: grid
- filter: >
[[[
return variables.state === 'not_home'
? `grayscale(1)`
: null;
]]]
button
card:
type: custom:button-card
entity: person.viet_ngoc
name: Viet Ngoc
template:
- battery_circle
variables:
battery: sensor.viet_ngoc_battery_level
person_retain: sensor.viet_ngoc_last_changed
battery_status: sensor.roidmi_v60_029d_charging_state
@chezpaul2
Or a stroke as a border for the icon…
Yes, that was it. Thanks a ton.
I added a charging icon as a flash above the circle, it only shows when the state is charging
Yes I just saw that. I’ll use it too.
Thanks a ton
It looks like my person_retain is “undefined”.
It used to work fine.
person_retain: sensor.paul_last_changed
That’s created by the mqtt integration right?
I’m guessing that’s why you can’t see it in the dev>states page.
Can you share your updated config?
Sure. Thanks
person.yaml:
person:
template:
- base
- circle
state_display: >
[[[
if (entity) {
return variables.state === 'home'
? variables.translate_home
: variables.state === 'not_home'
? variables.translate_not_home
: variables.state;
}
return variables.translate_unknown;
]]]
triggers_update: sensor.time
tap_action:
action: none
variables:
battery_level: ' '
battery_status: ' '
person_retain: ' '
circle_unit: ' '
styles:
custom_fields:
icon:
- clip-path: circle()
- width: 82%
- pointer-events: none
- display: grid
steps:
- position: absolute
- right: 2%
- bottom: 8%
- font-size: 20px
- font-weight: 500
- color: '#4b5254'
custom_fields:
icon: >
[[[
return entity && entity.attributes.entity_picture
? `<img src="${entity.attributes.entity_picture}" width="100%">`
: null;
]]]
steps: >
[[[
return entity && entity.attributes.steps
? `${entity.attributes.steps}`
: '';
]]]
circle: >
[[[
let input = states[variables.battery_level].state,
radius = 22.1,
circumference = radius * 2 * Math.PI;
let unit = variables.circle_unit;
let time = c => {
let s = (c/1e3),
m = (c/6e4),
h = (c/36e5),
d = (c/864e5);
return s < 60
? parseInt(s) + 's'
: m < 60 ? parseInt(m) + 'm'
: h < 24 ? parseInt(h) + 'h'
: parseInt(d) + 'd';
};
let person_retain = states[variables.person_retain] !== undefined || states[variables.person_retain].state !== 'unavailable'
? time(Date.now() - Date.parse(states[variables.person_retain].state))
: time(Date.now() - Date.parse(entity.last_changed));
var color = "rgba(48, 128, 181, 0.8)";
if (input <= 10) {
color = "#FDD60F";
} else if (input <= 20) {
color = "rgba(48, 128, 181, 0.8)";
}
else {
color = "#27C950";
}
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;
}
.flash-icon {
font-size: 14px;
display: ${states[variables.battery_status].state === 'charging' ? 'block' : 'none'};
animation: blink 1s linear infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}
</style>
<circle cx="25" cy="25" r="${radius}" stroke="${color}" stroke-width="2" fill="none" stroke-linecap="round"/>
<text x="50%" y="15%" class="flash-icon" text-anchor="middle" alignment-baseline="middle">⚡</text>
<text x="50%" y="54%" fill="#8d8e90" font-size="14" text-anchor="middle" alignment-baseline="middle" dominant-baseline="middle">${person_retain}<tspan font-size="10">${unit}</tspan></text>
</svg>
`;
]]]
card:
cards:
- type: custom:button-card
entity: sensor.paul_combined
name: Paul
triggers_update:
- sensor.paul_last_changed
tap_action: !include popup/home_paul.yaml
template:
- person
variables:
battery_level: sensor.pauls_iphone_12_battery_level
battery_status: sensor.pauls_iphone_battery_status
person_retain: sensor.paul_last_changed
Hmmm. Strange…
This is where it’s created right?
#Person Persistance
- platform: mqtt
name: paul_last_changed
state_topic: homeassistant/persistence/paul
value_template: >
{{ value | replace(' ', 'T') }}
I would look at how you have created the sensor, yes it is a mqqt sensor, including automation to update the sensor…
alias: person_home_change
description: ""
trigger:
- platform: state
entity_id:
- person.viet_ngoc
from:
- home
- not_home
to:
- home
- not_home
action:
- data:
topic: |
homeassistant/persistence/{{ trigger.to_state.name | lower }}
payload: |
{{ now() }}
retain: true
action: mqtt.publish
mode: parallel
Yeah, I have all those defined in my HA. I mean last_changed has always been working for years. It’s always been there. It’s just that now, trying to use the variable: person_retain that doesn’t seem to work.
And for some reason, sensor.paul_last_changed has never existed in my dev>states page.
I now remember already trying to find it and not being able to but because the person button always showed the time accordingly to what was really happening, I never gave it a second thought. (I thought it was an mqtt thing to not show the sensor.)
Try to recreate the sensor in a new format, without platforms… but directly for mqtt, like this…
and after that either restart HA or in dev tools trigger mqtt to reload config.
mqtt:
sensor:
- name: 'paul_last_changed'
.
.
For debug, you can manually change the state for person. This will trigger the update mqtt sensor…
Seems to be working…
But still no last_changed
I just noted that my *** PERSON *** section of circle is different than yours.
This is mine:
/* * * * * * * * * * * * * * * * * *
* *
* PERSON *
* *
* * * * * * * * * * * * * * * * * */
else if (domain === 'person') {
let time = c => {
let s = (c/1e3),
m = (c/6e4),
h = (c/36e5),
d = (c/864e5);
return s < 60
? parseInt(s) + 's'
: m < 60 ? parseInt(m) + 'm'
: h < 24 ? parseInt(h) + 'h'
: parseInt(d) + 'd';
};
let input = states[variables.retain] === undefined || states[variables.retain].state === 'unavailable'
? time(Date.now() - Date.parse(entity.last_changed))
: time(Date.now() - Date.parse(states[variables.retain].state)),
unit = ' ';
return circle(state, input, unit);
}
My template does not use the script from the circle base template. The problem is that your sensor is unavailable. You can create a test template sensor and use it in a variable for person retain. And remove the triggers_update in you config.
triggers_update:
- sensor.paul_last_changed
test sensor template… don’t forget to reload yaml via dev tools after.
template:
# SENSORS
- sensor:
- unique_id: person_last_changed
name: 'Test person last changed'
state: >-
{{ states.person.viet_ngoc.last_changed | replace(" ", "T") }}
you get this sensor with the available state…
Finally figure out what it was. My mqtt.yaml file was not being called up! Pfff!
Fixed it. Now the buttons work, of course.
Thank you so much for taking the time to help me out.
I encourage anyone to go and use John’s cards that he has created for HA.
The Vehicle one is AMAZING!
The Lunar phase one is also really cool.
Come to think of it, John, you need to make a really nice person card.
Anyway, you went way beyond the call. thanks again.
@chezpaul2 I’m working on a custom card for HA that lets you browse and manage your movie library, working with Kodi integration. You can search for movies in your local collection or pull up results from TMDB. For movie detail, it has its own popup with trailer, description etc…
You can control your media player directly from the card, sending movies to any device in real time using websocket. It all happens smoothly without any delays. Right now, it’s just for my personal use, so it is not published anywhere yet.
You can see how it looks here
I also use Music Assistant, though I don’t have much experience with how addons work in Home Assistant. However, I’ve created my own card similar to this one, but for my music library. It currently works with YouTube Music and Spotify, offering similar features to Music Assistant, including full media player control.
Wow @VietNgoc. That looks real, real nice. Everything you do looks so professional.
I use your car card everyday, 4 to 5 times a day. Love it. Ho by the way, the range progress bar doesn’t seem to show at all. Whatever I put in there, nothing shows on the card. Not sure why.
But everything else works like a charm.
The movie library thing you working on looks amazing. I would love to use it. I can beta test if you want. haha. But I use plex as my movie server and I have mostly French movies. Haha…
I’ve been trying since the beginning of using HA to do a radio card for my wife so she can listen to French radio while at home on any speaker but it’s harder to do than I thought. Well I mean to do well, with a nice UI. Music assistant looks promising.
I do like what you have on your Dashboard on the movie section. the automatic swiping of movies with their rating etc. I tried to copy it from your GitHub but I felled miserably. I also think you’ve changed your HA yaml file but not the screenshots.
But I thought it was for all about to be released movies and not the ones in your Kodi. I thought that would be cool to see what’s coming out.
I did use kodi a while back for a long time but find plex to be a little nicer on the eye and less work to keep it up and running.And for some reason, I don’t like the no mouse attitude of Kodi.
I wouldn’t mind tryin your music assistant card though.