Closed ncrmnt closed 10 months ago
Going to use this card to keep notes since I've been to asked to add templating in 5-10 requests across two projects and it's a lot more complicated than I initially thought.
button-card
uses Thomas Loven's lovelace-card-tools with a lot of additional logic. card-tools
hasn't been updated since 2020 and doesn't work well with TypeScript, so I gave up on trying to implement it.
card-mod
has a newer implementation of template processing but it's wrapped up in other card-mod
logic and also confusing (I am no where near the front end dev that Thomas Loven is, I'm a backend engineer by trade) so I don't think I should go that route either.
I'm going to work on a "reinvent the wheel" approach where instead of having Home Assistant process the template as is I'll reimplement a limited set of Home Assistant jinja2 templating, as defined on the Home Assistant templating page.
Edit 1: So far I have the basic functions working (states, is_state, state_attr, is_state_attr, has_value) but if else statements will be harder. And this is all being processed by JS in the frontend rather than Python in the backend so some of the surrounding syntax is different which makes things harder for end users.
Edit 2: Actually if else statements work with my code as is if they're JS syntax if else statements or ternary operators. Happy little accident.
Implementing nunjucks was a huge success! I've ripped out the DIY template evaluating system and the string interpolation system (it's gonna be a big breaking change come v3.0.0). The latter can be completely replaced by nunjucks, which is functionally 99% like jinja2.
I've got what I think is a pretty functional HA/jinja2-like template system in the dev builds. Here is an example config:
features:
- type: custom:service-call
entries:
- service: light.toggle
icon_color: red
flex_basis: 200%
icon: >
{% if is_state("light.chandelier", "on") %} mdi:ceiling-light {% else
%} mdi:ceiling-light-outline {% endif %}
color: |
{% if is_state("light.chandelier", "on") %}
rgb({{ state_attr("light.chandelier", "rgb_color") }})
{% endif %}
label: >-
{{ (100*state_attr("light.chandelier", "brightness")/255) | round or
undefined}}
unit_of_measurement: '%'
- service: light.toggle
icon: mdi:lightbulb
icon_color: orange
label: Bulb 1
target:
entity_id: light.chandelier_bulb_1
- service: light.toggle
icon: mdi:lightbulb
icon_color: yellow
label: Bulb 2
target:
entity_id: light.chandelier_bulb_2
- service: light.toggle
icon: mdi:lightbulb
icon_color: green
label: Bulb 3
target:
entity_id: light.chandelier_bulb_3
- service: light.toggle
icon: mdi:lightbulb
icon_color: blue
label: Bulb 4
target:
entity_id: light.chandelier_bulb_4
- service: light.toggle
icon: mdi:lightbulb
icon_color: purple
label: Bulb 5
target:
entity_id: light.chandelier_bulb_5
- type: custom:service-call
entries:
- type: selector
entity_id: light.chandelier
value_attribute: rgb_color
background_color: rgb(VALUE)
invert_label: true
options:
- service: light.turn_on
option: 255,0,0
color: red
label: Red
label_color: red
icon: mdi:alpha-r
data:
color_name: red
- service: light.turn_on
option: 0,128,0
color: green
label: Green
label_color: green
icon: mdi:alpha-g
data:
color_name: green
- service: light.turn_on
option: 0,0,255
color: blue
label: Blue
label_color: blue
icon: mdi:alpha-b
data:
color_name: blue
- service: light.turn_on
option: 255,166,86
color: white
label: White
label_color: white
icon: mdi:alpha-w
flex_basis: 300%
invert_icon: true
data:
color_temp: 500
- type: custom:service-call
entries:
- type: slider
color: rgb({{ state_attr("light.chandelier", "rgb_color") or (0, 0, 0) }})
label: >-
{{ (100*state_attr("light.chandelier", "brightness")/255) | round or
undefined}}
unit_of_measurement: '%'
value_attribute: brightness
icon: mdi:brightness-4
service: light.turn_on
flex_basis: 200%
data:
brightness_pct: VALUE
- type: slider
thumb: line
background_color: linear-gradient(-90deg, rgb(255, 167, 87), rgb(255, 255, 251))
background_opacity: 1
value_attribute: color_temp
service: light.turn_on
label: '{{ state_attr("light.chandelier", "color_temp") }}'
unit_of_measurement: ' Mireds'
label_color: var(--disabled-color)
icon: mdi:thermometer
range:
- 153
- 371
step: 1
data:
color_temp: VALUE
type: tile
entity: light.chandelier
Implementing all of the custom HA template functions is going to be a pain or not possible, especially since a lot of them likely wouldn't be used for a card like this. I think I'm going to opt to not implement them but was able to expose the js eval
function to nunjucks.
While nunjucks warns against allowing for user defined templates, the whole purpose of this is to allow for user defined templates in the HA frontend. So similarly I'm going to use the same logic to allow end users access to the eval
function to run whatever JS they want in templates. Generally both user defined nunjucks templates and the JS eval
function are extremely bad practice but since Home Assistant servers should be private and not publicly exposed it's up to end users to not abuse or allow for this to be abused on their Home Assistant servers.
Note to self - put warnings about user defined templates and eval
function in README.
Edit: eval
is probably too dangerous to expose to end users, so I'm removing it.
Released version 3.0.0, which adds templating thanks to Nunjucks.
Thnks a lot! It's a very useful feature.
It'd be nice to set some properties like icon, color, etc depends on a target object state.
For example: I have a tile card for my bedside lights. It has some additional features (buttons) for setting light brightness or color. And one button to swich the light alarm function:
It works but I can't determine the current state of this switch. Plugins like button-card and some other allows to set templates for properties. And my config with the same ability would looks like:
Thank you for this awesome project!