Templating custom filters

Hi,
is there any way to add custom filters in the Jinja templating language used in ha?

Thank you

2 Likes

Nope, you’d have to overwrite the helpers/template.py file. What are you trying to do? Chances are, there’s already a filter or a better way to do what you want, unless it’s extremely specific.

1 Like

Hi petro,
thank you for your answer.

I’d like to have a custom version of “relative_time” filter,
with output in Italian and with always hours and minutes stated.
for example: my_timestamp | my_custom_relative_time would output "3 hours and 15 minutes" (in Italian).

Is there any smarter way to obtain this result?
Since I would use this in several templates, I do not want to calculate this message in each template.

Thank you

HI petro,
do you have any hint for me?

Thank you.
Matteo

Hello,
I’m also looking for a way to implement custom filters for a specific purpose. To calculate the absolute humidity from temperature and humidity sensors I need to use the math.exp function.
I found a HACS plugin where someone added their own filters, maybe this is something to build on.

I recently made some updates to this HACS integration (originally built by zvldz):
Custom Filters for Jinja (Home Assistant)

The most recent updates fix all of the issues I was experiencing (e.g. it was not loading consistently on restart). It works flawlessly now (for me, at least).

You will have to write any additional custom filters in Python, but it comes with a handful of useful filters by default. Below is a list of the filters that come built-in with the integration, including documentation (description, usage, etc). Enjoy!

Custom Filters

replace_all                 - Replace all provided values with replacement value(s) [e.g. `replace_all("A B C D", ["B", "D"], "?")` => "`A ? C ?`"]
is_defined                  - Check if a variable is defined by it's string name [e.g. `is_defined('varname')`]
get_type                    - Return the object type (as a string) [e.g. `get_type(['A','B'])` => "`list`"]
is_type                     - Check if a value is of given type [e.g. `is_type('str', 'float')` => `true`]
inflate                     - Compress with zlib
deflate                     - Decompress with zlib
decode_base64_and_inflate   - Decode base64 content and decompress it
deflate_and_base64_encode   - Compress content and base64 encode it
decode_valetudo_map         - Decode Valetudo (https://github.com/Hypfer/Valetudo) map
urldecode                   - Decode URL characters (replace %xx escapes by their single-character equivalent)
strtolist                   - Turn a string into a list [e.g. `strtolist("['A','B','C']") | length` => `3`]
listify                     - Convert a string or non-list/dict into a list/dict (like JSON string)
get_index                   - Return the numeric index of a list or dict item [e.g. `get_index(['A','B','C'], 'A')` => `0`]
grab                        - Get a list/dict item by key, with optional fallback [e.g. `grab(['A','B','C'], 4, 'Z')` => "`Z`"]
reach                       - Get a dict item by full path of key(s), with optional fallback [e.g. `reach({"a": {"b": "c"}}, "a.b")` => "`c`"]
ternary                     - Returns one value if true, another if false, and third if null [e.g. `ternary(("a" == "b"), "Y", "N", "U")` => "`N`"]
shuffle                     - Randomize an existing list, giving a different order every invocation [e.g. `shuffle([1,2,3])` => `[2,1,3]`]
to_ascii_json               - Convert string to ASCII JSON
2 Likes

Thanks for this @blizzrdof77. I’ll install it ASAP

1 Like

This is working well for me, thanks. I was able to add my own filter as well.

1 Like

That’s great to hear! Thanks for reporting back on this. Feel free to share any custom filters (or contribute on GitHub) if you think they’d be useful to others.

This is an obscure and narrow use case.
Thanks again.

@blizzrdof77 maybe you can help with this. I installed the add-on and added a filter. I can successfully use the filter in the template playground in the Developer tools as shown


I’ve also written a script to use the same filter but when I execute it I get an error that flt_2_ieee is not defined. Here’s the script:

modbus_brite_22_set_temp:
  alias: Modbus Brite_22 set temp
  sequence:
  - service: modbus.write_register
    data:
      value: >
        {{flt_2_ieee(temp)}}
      hub: modbus_hub
      unit: 2
      address: 00

Can you think of reason it would work in one and not the other?

Hmm… I’d have to see your code. Have you tried using any of the other filters within the same context?

1 Like

Why are you using this to create a function but not using it as a filter? If you’re going to use it as a function only, there’s no reason to use this custom library. Just make a macro.

1 Like

From my testing, template macros cannot import. I need import struct for this filter.

… why. There are HA built-in template functions/filters that encode/decode information. You shouldn’t need struct at all

1 Like

Here the code (don’t laugh too hard - I’m new to python. Snippets around each mention of my filter. From __init__.py

import struct

from random import Random, SystemRandom, shuffle

from homeassistant.helpers import template

_LOGGER = logging.getLogger(__name__)

_TemplateEnvironment = template.TemplateEnvironment

## Greg Martin ieee conversion
def flt_2_ieee(f):
    """converts float to ieee754 returned in two part hex"""
    binary_string = ''.join(f'{b:08b}' for b in struct.pack('>f', f))
    ieeehex =hex(int(binary_string, 2))
    ieeelt = ieeehex[2:6]
    ieeert = ieeehex[6:10]
    retval = "[0x" + ieeelt + ",0x" + ieeert + "]"
    return retval
...
def init(*args):
    """Initialize filters"""
    env = _TemplateEnvironment(*args)
    env.filters["flt_2_ieee"] = flt_2_ieee
    env.filters["replace_all"] = replace_all
...
    env.filters["shuffle"] = shuffle
    env.filters["to_ascii_json"] = to_ascii_json
    env.globals["flt_2_ieee"] = flt_2_ieee
    env.globals["replace_all"] = replace_all
...
   template.TemplateEnvironment = init
   template._NO_HASS_ENV.filters["flt_2_ieee"] = flt_2_ieee
   template._NO_HASS_ENV.filters["replace_all"] = replace_all
...
   template._NO_HASS_ENV.filters["to_ascii_json"] = to_ascii_json
   template._NO_HASS_ENV.globals["flt_2_ieee"] = flt_2_ieee
   template._NO_HASS_ENV.globals["replace_all"] = replace_all
...
async def async_setup(hass, hass_config):
    tpl = template.Template("", template._NO_HASS_ENV.hass)
    tpl._env.globals = flt_2_ieee
    tpl._env.globals = replace_all

I’m no python coder, but I couldn’t find a way to use pack. Code posted. Happy to solve this with a macro. This code has to convert a float into a string of the two hex values for a ieee 754 representation of the float. The brackets are for passing the two valuse to a service call using a list

so 54.1 would return [0x4258,0x6666]

Here’s a curious thing (to me). If I call the filter as a function (as @petro pointed out), it works in the template playground. If I call is as a filter it fails with no filter named flt_2_ieee.

And I get the same behavior with get_type from the custom filter package. Looking at the docs, all the examples use the filters as functions, not pipeline filters.

yeah, that’s all built in…

{% set value = 54.1 %}
{% set a = '0x%X' % value | pack('>f') | unpack('>H') %}
{% set b = '0x%X' % value | pack('>f') | unpack('>H', offset=2) %}
[{{ a }}, {{ b }}]

In fact, most of the filters in that lib aren’t necessary and it can all be done in jinja

EDIT: if you want it as a proper macro

{%- macro flt_2_ieee(f) %}
{%- set a = f | pack('>f') | unpack('>H') %}
{%- set b = f | pack('>f') | unpack('>H', offset=2) %}
{{- "[ 0x%X, 0x%X ]" % (a, b) }}
{%- endmacro %}

Just keep in mind that the template editor will turn it into a list of int’s when you try to view the result.

Covered here in the documentation:

1 Like

Wow - great work! Thanks so much.
The ints work as well. I’ll give this a shot.