Create a sensor mapping from another integer sensor to text

Hello.

I have a sensor, sensor.warnings, that broadcasts an integer represented as a bit vector, where each bit indicates the presence of a specific warning. I want to create a sensor called sensor.warnings_text that contains a semi-colon joined list of corresponding text warnings.

For example, if sensor.warnings = 5, and bits 0 and 2 map to text warnings ‘A’ and ‘C’ respectively, I need the sensor sensor.warnings_text to have the value A;C.

The implementation in Python is straightforward:

def warnings_text(warnings):
    WARNINGS = [
        '0',
        'B',
        'C',
        # ...
    ]

    return ';'.join(
        text
        for bit, text in enumerate(WARNINGS)
        if warnings & (1 << bit)
    )

However, I need to implement this in Home Assistant.

I considered using a template sensor, but I am unable to replicate the functionality of bitwise shifting in Jinja2 templating. Home Assistant templating does not seem to expose bitwise shifting operations.

Perharps, there a way to create a template sensor that invokes a Python function?

Are there any alternative methods to achieve this? Thank you.

Hello!

I think Jinja doesn’t have bitwise operators natively.

But you don’t need bitwise operators. It requires perhaps some more verbiage, but you can use the modulo operator (%). For example, to tell if the 4th least significant bit is 1 in the variable my_variable:

(my_variable % 2*2*2) | bool

If it’s set, that template will evaluate true. Otherwise, it will evaluate false.

EDIT: Original version of the post said “3rd least significant.”

1 Like

Great idea, thank you.

However, I still can’t figure out how to implement the whole task using Jinja2.
Here is what I came up with:

{% set WARNINGS = [(0, 'A'), (1, 'B'), (2, 'C'), (6, 'D')] %}
{% set warning_bitmask = states('sensor.warnings') | int %}
{% set active_warnings = ['A'] %}

{% for bit, text in WARNINGS %}
  {% set active = warning_bitmask | bitwise_and(2 ** bit) | bool %}
  {% if active %}
    {% set _ = active_warnings.append(text) %}
  {% endif %}
{% endfor %}

{{ active_warnings | join(';') }}

But this does not work because of “access to attribute ‘append’ of ‘list’ is unsafe” error.

Any there any other safe ways to append to a list?

I assume you know what you’re doing with using 6 as the modulus.

That aside, you’re hitting an entirely different issue, which is basic Jinja semantics. You can’t call append on something the way you might in Python.

Can you explain at a high level–explain the problem to me from beginning to end like i’m an idiot–what you’re trying to do?

Also… I can’t tell you one way or the other whether _ is a valid variable name in Jinja, but even if it is, I am doubting that setting that variable to something does what you need.

Edited: Jinja is not PERL

At a high-level, I want to create a text sensor (sensor.warnings_text) that would list (separated by a semicolon) all the warnings, indicated by another integer sensor (sensor.warnings).

Presence of individual bits signify presence of corresponding warnings.

For example,
Bit 0 - “Low voltage”
Bit 1 - “Overload”
Bit 2 - “Fan blocked”
etc

When the sensor.warnings broadcasts a value 3, which has enabled bits 0 and 1, I want the sensor.warnings_text to be updated to Low voltage;Overload.

That is a high level task.

The python code I provided in the first post does exactly what I need.

Maybe there is a way to add a custom home assistant component that will expose a python function as a Jinja2 filter, globally? I looked at the source code of core/homeasisstant/helpers/template.py, and it seems like this feature is not implemented.

Python is not used natively in Home Assistant. Can you explain in more detail what you’re doing? Are you using an integration to have Python scripts do things to Home Assistant?

I don’t use python in home assistant at all. The code I provided is solely for the purposes of demonstration of the algorithm.

But if there was a way to call a python function from a template, I would do that.

There isn’t.

So WTF are you trying to accomplish, if I may ask, with this:

  {% if active %}
    {% set _ = active_warnings.append(text) %}
  {% endif %}

If a warning is active (by checking the presence of the corresponding bit), add a warning text from variable text to the list of all active warnings active_warnings.

set _ = is just used to execute the code after =. At least, that’s what gpt proposed.

No. GPT is massively wrong. Try again by reading the documentation and using your actual brain. And don’t post again in this thread; post when you have an actual problem that your actual brain encountered.

Don’t ever come here for problems that stem from a Chat GPT “solution”. You waste real peoples’ time.

Jinja scoping means active_warnings is not available - you need to use a namespace. The code below works, noting that I’ve hardcoded the bitmask to 5 for testing.

{% set WARNINGS = [(0, 'A'), (1, 'B'), (2, 'C'), (6, 'D')] %}
{% set warning_bitmask = 5 %}
{% set n = namespace(active_warnings=[]) %}

{% for bit, text in WARNINGS %}
  {% set active = warning_bitmask | bitwise_and(2 ** bit) | bool %}
  {% if active %}
    {% set n.active_warnings = n.active_warnings + [text] %}
  {% endif %}
{% endfor %}

{{ n.active_warnings | join(';') }}
1 Like

He is relying on ChatGPT. You wasted your time with this response.

Thank you very much. That is exactly what I needed!