A List generated by a template - is it possible? HELP!

Tags: #<Tag:0x00007f3269e3cb80>

Hi all,
I’m running of of ideas how to achieve my goal :worried:, so asking out loud - PLEASE HELP ME!!!
I want to have a script for my Xiaomi Vacuum zoned cleanup. A zone is basically an array / a list of 4 integer values/coordinates defining a rectangular area.
The idea is that the zones to be cleaned up (the number of zones) should be dependent on input_boolean values. I defined a couple of input_boolean in my config and now depending on which one is ‘on’ I want to add a list of 4 ints to a service data payload e.g. [1,2,3,4]. So if input_boolean is ‘on’ then a zone is added as a list of 4 ints.
At the end the service has a parameter “zone” that needs a list of lists e.g. [[1,2,3,4]] or [[1,2,3,4], [5,6,7,8]]. Number of lists within the main list should depend on input_boolean.

Simplifying things (my real config is more complex and contains more zones):

  • input_boolean.living_room corresponds to example coordinates [25500, 20000, 28000, 25500]
  • input_boolean.kitchen corresponds to example coordinates [20000, 20000, 24000, 25500]

Script syntax:

vacuum_selected:
  alias: Vacuum selected zones
  sequence:
  - data_template:
      entity_id: vacuum.xiaomi
      zone:
      - >-
{% if is_state('input_boolean.living_room', 'on') -%}
{{[25500|int, 20000|int, 28000|int, 25500|int]}},
        {%- endif %}
{% if is_state('input_boolean.kitchen', 'on') -%}
{{[20000|int, 20000|int, 24000|int, 25500|int]}}
        {%- endif %}
      repeats: 1
    service: vacuum.xiaomi_clean_zone

This doesn’t work and gives the following result in logs:

File “/usr/local/lib/python3.7/site-packages/voluptuous/schema_builder.py”, line 427, in validate_mapping
raise er.MultipleInvalid(errors)
voluptuous.error.MultipleInvalid: None @ data[‘zone’][0]

I decided to try the ‘native’ list [] characters as using YAML syntax (the “-” character) for each potential list entry would result in empty list elements. So I think I can’t use the following syntax:

vacuum_selected:
  alias: Vacuum selected zones
  sequence:
  - data_template:
      entity_id: vacuum.xiaomi
      zone:
      - >-
{% if is_state('input_boolean.living_room', 'on') -%}
{{25500|int, 20000|int, 28000|int, 25500|int}},
        {%- endif %}
        >-
{% if is_state('input_boolean.kitchen', 'on') -%}
{{[20000|int, 20000|int, 24000|int, 25500|int]}}
        {%- endif %}
      repeats: 1
    service: vacuum.xiaomi_clean_zone

Assuming that one of the input_boolean is ‘off’ the second syntax would rather result in an empty list element.

I’ve also tried the list jinjia filter:

vacuum_selected:
  alias: Vacuum selected zones
  sequence:
  - data_template:
      entity_id: vacuum.xiaomi
      zone:
      - >-
{% if is_state('input_boolean.living_room', 'on') -%}
{{(25500|int, 20000|int, 28000|int, 25500|int)|list}},
        {%- endif %}
{% if is_state('input_boolean.kitchen', 'on') -%}
{{[20000|int, 20000|int, 24000|int, 25500|int]}}
        {%- endif %}
      repeats: 1
    service: vacuum.xiaomi_clean_zone

but no luck - the logs show the same output.

In all cases I’ve tried - the template editor shows the correct output after the jinjia template is rendered. So it doesn’t help much basically :frowning:
I’ve got no clue why this doesn’t map to a list and why is the service complaining. BTW - HA does not deliver enough debugging information in the logs. I’d love to see variable values like the service payload defined in the ‘data_template’ section. This would greatly help getting cases like this resolved.

Hopefully there are some jinjia-ninjas out there in the community to help me get this resolved. Thanks!

Debugging Jinja is about the most annoying thing there is. So I’ve not read through your code enough to figure out what’s wrong. I only want to make sure you’re aware that the Xiaomi Vacuum can’t take more than 5 sets of coordinates on the xiaomi_clean_zone command. So, if your end result cares for more than 5 defined zones and they are all turned on at once, you’ll have issues.

Additionally, for this kind of work, where the Jinja is just annoying, I use a python_script instead.

My current script does not even work with one :frowning: I will have 4 or 5 maximum in one call so I’m ok with the limit.
Sometimes I think that having jinjia as the way to have scripts more flexible and parametrized is a lot more tricky and difficult than programming a code. Also because jinjia is so not-popular. And when you combine this with yaml - it’s a disaster. Shame HA does not support LUA scripts.

How could I do this with a python script? You mean ython script called as a HA service? If so - how can I pass input_boolean values to my python script? With a template? :face_with_head_bandage:

Here are some things you can do in a python_script:

watts = hass.states.get('switch.coffee_maker').attributes.get('current_power_w',0.0)

hass.states.set('sensor.something_i_made_up','brewing')

        data = {
            "entity_id": 'light.hallway',
            "rgb_color": [ 255, 255, 0],
            "brightness": 128
        }
        hass.services.call('light','turn_on',data)

You can get the values directly, as I’ve shown above. If you want to pass them to the python script, you can do so and they are available in a “dict” called “data”.

your syntax looks really screwed up. try this:

zone: >
  {% if is_state('input_boolean.living_room', 'on') -%}
    {{25500|int, 20000|int, 28000|int, 25500|int}}
  {% elif is_state('input_boolean.living_room', 'on') -%}
    {{25500|int, 20000|int, 28000|int, 25500|int}}
  {%- endif %}

not really understanding why you have the same data twice in the if-elif statements tho.

and i don’t use the vacuum so i don’t know what the end result of the format for zone needs to be but at least the syntax of the template is better. i think…

As I wrote above - the expected input is a list of lists e.g. [[1,2,3,4]] or [[1,2,3,4], [5,6,7,8]]. So my script at the end should generate a list of lists. If I switch on 4 input_booleans the script should end up with 4 lists inside a list. If I switch 2 - it should produce 2 lists within a list. Zero - a list with no elements etc.

Regarding the if-elif - my mistake. I made up these configs as the real ones I have in my scripts are a lot more complicated as they use offset calculation so for clarity purposes I simplified them.

Any other replies are welcome, however I think I will go with the idea proposed by @swiftlyfalling (thanks!) - a custom python script. It should be a lot simpler and the Python code will be definitely cleaner than these if-elif and {{}} crazy jinjia constructs.
I mentioned LUA - now I think that if I can produce a custom Python script - I dontt need LUA anymore. Python itself gives enough possibilities for scripting.

Try this:

vacuum_selected:
  alias: Vacuum selected zones
  sequence:
  - data_template:
      entity_id: vacuum.xiaomi
      zone: >-
        {% set do_living = is_state('input_boolean.living_room', 'on') %}
        {% set do_kitchen = is_state('input_boolean.kitchen', 'on') %}
        {% set living = [25500, 20000, 28000, 25500] if do_living else '' %}
        {% set kitchen = [20000, 20000, 24000, 25500] if do_kitchen else '' %}
        {% set x = ',' if do_living and do_kitchen else '' %}
        [{{living}}{{x}}{{kitchen}}]
      repeats: 1
    service: vacuum.xiaomi_clean_zone

Paste the template into the Template Editor to confirm it produces the results you want. I tested a slightly modified version on my system and the resulting ‘list of lists’ had the correct appearance.

For example, if both input_booleans are on, the template produces:

[[25500, 20000, 28000, 25500],[20000, 20000, 24000, 25500]]

If the only the second input_boolean is on, then we get:

[[20000, 20000, 24000, 25500]]

EDIT
I would appreciate it if you could let me know if this solution works for you. I’m curious to learn if the result produced by the template is understood to be a list (of lists) or becomes a string when assigned to zone.

The problem you’re running into is you’re trying to use a template to generate a string that needs to be interpreted as YAML and be parsed as a list of lists, but by the time the template is rendered, YAML parsing has already been done. So what you’re ending up with is a string that looks like a YAML list, but it’s just a string.

I agree with @swiftlyfalling, you should do this with a python_script.

Thank you very much guys for all your ideas, explanations and support.
@pnbruckner - thank you for explaining how things work in HA. This will help me in the future. It’s always better to know the rules as it allows to eliminate false ideas and come up with a correct approach.

@123 thanks for your idea - I really appreciate. I will give it a try from curiosity as soon as I sit again in front of my HA’s config, but having in mind what @pnbruckner wrote above - it’s unlikely it will work. Probably it will only become a string which won’t be ‘understood’ as a zone or ‘list of lists’. But your idea looks good from the syntax point of view (set) and I will use this kind of syntax definitely in the future. I see you spotted the problem with the comma, which you solved with jinjia syntax

Yeserday I did my own python scrit and can confirm it works really well. It seems that more complicated cases (e.g. data that should be something else than just a string or number) are not possible to be expressed with YAML+jinjia and then a python script will do just fine.

Hopefully this post will help somebody else in the future in a similar situation.

The difference is that your template attempted to produce this:

zone:
  - [25500, 20000, 28000, 25500],[20000, 20000, 24000, 25500]

Your second version attempted to produce this:

zone:
  - [25500, 20000, 28000, 25500]
  - [20000, 20000, 24000, 25500]

My template attempts to produce this:

zone: [[25500, 20000, 28000, 25500],[20000, 20000, 24000, 25500]]

The overriding question is “Can a template’s output ever be interpreted as a list and not a string?” That’s why I’m hoping you can test it and report the results.

Just tested your config. Unfortunately it is as I expected after @pnbruckner’s input on how template rendering and YAML config work together.
In the template editor your script renders correctly, however the service call fails with an error. This time however it is a different error:

voluptuous.error.MultipleInvalid: expected list for dictionary value @ data[‘zone’]

So at the end of the day - no chance to get a real list of lists with jinjia. A python script stays as the only solution.

However I wonder what would happen if there was a way to apply the ‘list’ jinjia filer?

While Jinja is rendering a template, it can have lists, dictionaries, etc. In fact you can do a lot of Python “inside” a template, too. But at the end of the day (when the result is returned), everything gets turned into one big string.

@pnbruckner

So if we were to define zone without using a template we would do this:

zone: [[25500, 20000, 28000, 25500],[20000, 20000, 24000, 25500]]

However, because Jinja2 always returns a string, the template effectively produces this:

zone: "[[25500, 20000, 28000, 25500],[20000, 20000, 24000, 25500]]"

Is that correct?


BTW, while attempting to understand how the Xiaomi Vacuum component reads the zone option, I found that the code responsible for this operation appears to be duplicated. Not sure why this code block appears on line 71 and again on line 80:

SERVICE_SCHEMA_CLEAN_ZONE = VACUUM_SERVICE_SCHEMA.extend({
    vol.Required(ATTR_ZONE_ARRAY):
        vol.All(list, [vol.ExactSequence(
            [vol.Coerce(int), vol.Coerce(int),
             vol.Coerce(int), vol.Coerce(int)])]),
    vol.Required(ATTR_ZONE_REPEATER):
        vol.All(vol.Coerce(int), vol.Clamp(min=1, max=3)),
})

That is my understanding and what I have observed.

I noticed that, too. Just a typo I guess that someone should eventually fix. Since it defines the variable twice, but with the same value, it isn’t hurting anything. Just unnecessary.

BTW, you can use multiple templates in a data value, and still get a list. E.g., something like this:

xyz: [["{{ 1 }}", "{{ 2 }}"],["{{ 3 }}", "{{ 4 }}"]]

This would result in:

[['1', '2'], ['3', '4']]

Which the schema would then turn into:

[[1, 2], [3, 4]]

Obviously my example templates are trivial just to show the point. The problems however are twofold. First, since each number needs its own template, it would quickly become unwieldy, typically repeating the same expressions multiple times. But more importantly, relative to this topic, you couldn’t conditionally include or not include the internal lists, because they’d get turned into strings (because the conditional processing is inside a template.)

And as long as we’re on the topic, it would be possible to enhance the schema to also accept a single string in the form we’ve been talking about:

'[ [ number, number ], [ number, number] ]'

And have it extract the list of lists of numbers. Then you could template to your heart’s content! :slight_smile:

Now who would like to sign up to do that? :wink:

The thought crossed my mind while I was looking at the component’s code. I see things like coerce(int) and wonder how many other ways can it coerce something? However, I know very little about the capabilities of the voluptuous module (other than that’s a helluva name for it).

1 Like

It would probably be a custom validator, something along the lines of:

But more complicated.

1 Like

:slight_smile:

I followed your explanation and initially thought that’s what I tried to achieve with this:

[{{living}}{{x}}{{kitchen}}]

However, there must be an important difference because my approach was reported to have failed.

But what was {{living}} supposed to output? It couldn’t be a list like [1, 2], because it would really be a string '[1, 2]'. And was {{x}} supposed to be a comma separating the two lists? It couldn’t because it wouldn’t be ,, it would be ','.

The only way (that I know of) to get anything but a string from a single template is to have a schema that processes/converts that string into something else. Which, BTW, happens after YAML processing, and after template processing.

1 Like