Reusing data in scripts and automations

Hi, I am trying to figure out how to define a structure in one place, and then use it in multiple places.

For example, I have a working script that I use to assign z-wave door codes using the last 6 digits of a person’s phone number.
It just iterates through a data structure and calls another script to do the actual work.
(At the time I wrote it, the “name” was just a comment for me)

alias: Set all door codes
sequence:
  - repeat:
      for_each: 
        - phone: 123 123 1234
          name: Foo
        - phone: 234 234 2345
          name: Bar
        - phone: 345 345 3456
          name: Baz
          garage_side: false
      sequence:
        - service: script.set_slot_for_all_door_locks
          data:
            slot: "{{ repeat.index }}"
            code: "{{ (repeat.item.phone | replace(' ', ''))[-6:]}}"
            doors: "{{ repeat.item }}"
mode: single
icon: mdi:chevron-triple-right

Now, I am working on making an automation which will send me a notification when someone uses a door code to unlock a door.

I am able to catch the event to trigger the automation, and identify the slot number that unlocked the door, (e.g. slot 2 would correspond to Bar). But I would like the notification to contain the name of the person, not just the slot number. So I will need the data from the script in some form in the automation as well. I would prefer not to have two copies of the same data.

My first try was to use secrets.yaml with something like:

door_codes:
  - phone: 123 123 1234
    name: Foo
  - phone: 234 234 2345
    name: Bar
  - phone: 345 345 3456
    name: Baz
    garage_side: false

And then in the script, use:

      for_each: !secret door_codes

But it seems that secrets.yaml can only hold strings? I couldn’t make it work. The script didn’t like the !, iirc.

Next try was to use custom_templates with a doorcodes.jinja containing:

{%- macro door_codes() -%}
[{"phone":"123 123 1234","name":"Foo"}, {"phone":"234 234 2345","name":"Bar"}, {"phone":"345 345 345","name":"Baz","garage_side":false}]
{%- endmacro -%}

I can get the content of the macro to show up in the Developer Tools Template thingy using

{% from 'doorcodes.jinja' import door_codes -%} {{ door_codes() }}

So the template itself is being loaded and interpreted correctly.

And then in the script, I use this:

  for_each: {% from 'doorcodes.jinja' import door_codes -%} {{ door_codes() }}

But it just gives me this error:

Message malformed: template value is None for dictionary value @ data['sequence'][0]['repeat']['for_each']

Is there a better way to go about this? Someplace I can store some structured data like this and then access it in scripts, automations, etc?

Use to_json to format your macro output so that the string it returns can be converted back to a list:

{%- macro door_codes() -%}
{{ [{"phone":"123 123 1234","name":"Foo"}, {"phone":"234 234 2345","name":"Bar"}, {"phone":"345 345 345","name":"Baz","garage_side":false}] | to_json }}
{%- endmacro -%}

Then use from_json in the repeat so you get an actual list, not a list-shaped string:

for_each: "{% from 'doorcodes.jinja' import door_codes -%} {{ door_codes()|from_json }}"

I tested the following method of YAML substitution and can confirm it works.

Create a new file (in the same directory as your configuration.yaml file) named door_codes.yaml containing the following:

- phone: 123 123 1234
  name: Foo
- phone: 234 234 2345
  name: Bar
- phone: 345 345 3456
  name: Baz
  garage_side: false

Modify your existing script to reference the file with an !include statement like this:

alias: Set all door codes
sequence:
  - repeat:
      for_each: !include door_codes.yaml
      sequence:

The example assumes your scripts are in the same directory as your configuration.yaml file. If your scripts are in a sub-directory, then the inclusion statement should be changed to this:

for_each: !include ../door_codes.yaml

Thanks! This worked perfectly. It even let me reformat the data a bit so it wasn’t one long line. So bonus!

I also tried the !include approach, but was unable to make that work.

Then you did something wrong because, as mentioned, it was tested and confirmed to work.

Advantages over the macro suggestion:

  • Allows you to compose a list/dictionary in YAML which is simpler than in JSON.
  • Referencing it in the script is more concise than referencing a macro.
  • It’s a straightforward YAML substitution performed when the script is loaded by the YAML processor. It doesn’t require the extra JSON processing performed by the Jinja2 interpreter.

@123 @Didgeridrew @rossc719 sorry for the tag, but I want all you to see

With static objects, you don’t need to use a macro. You can use set and import the set variable.

inside doorcodes.jinja

{% set door_codes = [{"phone":"123 123 1234","name":"Foo"}, {"phone":"234 234 2345","name":"Bar"}, {"phone":"345 345 345","name":"Baz","garage_side":false}] %}

Then in your macro, just use it. No to_json from_json crap

{% from 'doorcodes.jinja' import door_codes -%}
{{ door_codes[0] }}

TLDR: You can import variables or macros.

EDIT:
Also, if you name your variable _<whatever> it’s not importable, same goes for macros.

3 Likes

Ah, even better, thanks! This works very well.

I appreciate that the !include version works for you, but when I try it, it simply gives me the same error.
My script is just in the normal scripts.yaml since I created it with the UI, so I shouldn’t even need the ../ to find the file to include. I don’t know what might be wrong, but I just can’t seem to make that approach work.

I like your suggestion, effectively defining a “global constant” without the need for JSON processing, but I still think a simple YAML inclusion is ideal. Everything about it is more concise, from defining the data to referencing it in the script.


It’s interesting that a macro is limited to returning a string but a variable can return any type.

Yes I agree. The only place I use the jinja alternative is for alternatives to !secret for template sensors. Otherwise I exclusively set up all variables in the automation or script.