I’m getting lost somewhere. I have a script that looks like the below. Notice the nested repeats, the inner one is attempting to loop through the lights (this works) but through the individual characters in the “pattern” string. This latter does not.
When looking at the trace, the first iteration of the inner repeat gives color==“R”, but the second thru eighth give a null string. They do not give an error.
Am I wrong that the
color: '{{ pattern[(repeat.index - 1):1] }} '
should pick up 1 character in increasing positions? It does not seem to work. (My intent eventually is, when done, to shift that string by 1 character before the outer loop runs, but just trying to get iteration #1 working now). I’ve tried with and without the parentheses inside the brackets.
Also, for the definition of color remove the :1 so that you are producing an index instead of an invalid index sequence. Only the first is working because [0:1] is the only valid index sequence that is produced.
Jinja for’s loop.index also starts at 1… but don’t worry, you can use loop.index0 if you prefer… and it has both options in reverse too. I’m just waiting for an LLM-produced template monstrosity that has all 4 in the same template.
First, this seems to work, but could you help me understand why?
isn’t “pattern” a string?
Isn’t a substring shown as X[start:length]?
Is it acting as an array?
This is going to confuse me as I was hoping to do a rotate on that string by something like setting pattern = pattern[1:7] + pattern[0:1] (please ignore my made up syntax, I haven’t gotten that far, but conceptually).
Also apologies – it’s late on the east coast, will research more carefully tomorrow, but any insights into substring vs array notation much appreciated.
I think the thing that I spend the most time for with HA is finding definitive answers for scripting tools. I was excited to find an actual “script syntax” document, but it is about 40% of the syntax, doesn’t include this for example. And there seem to many flavors of templating, with differing features and syntax for different places (and even worse if you add in node-red and esphome).
So I appreciate the quick and correct answers. Thank you.
Jinja2 and python are open-source projects thoroughly documented by their respective maintainers.
It’s impractical to duplicate their documentation within Home Assistant’s documentation; only the most relevant functions are included and the balance is up to the user to learn from the source (Jinja2/python) documentation.
FWIW, I learned about string slicing by stumbling across it in examples posted on the forum and then found a good tutorial (see link posted above) that explained its operation in greater detail.
That helps. There’s no mention of that on the script syntax, but if I search for templating I do find that mentioned, which gives me a place to start reading. What’s a bit less obvious is the shared variable context – I guess a variable in script becomes a variable in the template language (but not the reverse)?
Could I beg one more pointer though. I thought I had this all set to go but I appear to have a scoping problem.
I quickly found out inside a loop I could not redefine a variable from outside the loop, same with an if statement (and that’s documented well). I want to have the outer loop as it runs continually shift the variable ‘pattern’ which is used in the inner loop.
I first tried just defining it in terms of itself (i.e. below savepat replaced with pattern) then tried saving it in case the variable definition is wiped out before being redefined, neither one works.
Is there a scope change per loop?
Is there some simple way to do the shift as below each time the loop iterates?
I guess I could do some math and mod the repeat.index to produce a shifted value based on total iterations, but now I’m stubborn and want to understand how I could do this?
In your mind, keep “script variable” (what you define in the script’s variables option) separate from “Jinja2 variable” (what you define within a Jinja2 template).
Scoping rules apply to both script and Jinja2 variables.
A Jinja variable’s scope is exclusively within the template where it’s defined.
A script variable’s scope depends where it’s defined within the script.
If you define it at the “top level”, it can be referenced anywhere else in the script.
If you define it elsewhere like within a repeat or choose or if-then or anywhere that’s not “top level”, it’s scope is limited to that “block”.
As for your repeat within a repeat, as you have discovered, you cannot redefine a script variable’s value that way (i.e. the inner loop cannot change the value of a variable defined in the outer loop).
The example you posted doesn’t call any actions so it doesn’t benefit from employing repeat. It can be redesigned to be a Jinja2 template. A Jinja2 variable’s value can be redefined by an inner loop provided you define the variable using namespace.
Here’s a contrived example:
{% set ns = namespace(x = 0, y = 0) %}
{% for i in range(0,5) %}
{% set ns.x = ns.x + i %}
{% for j in range(0,5) %}
{% set ns.y = ns.y + j %}
{% endfor %}
{% endfor %}
{{ ns.x }}
{{ ns.y }}
Thank you. I didn’t include the whole script, it does call actions in the inner loop, the other loop just runs forever (with a delay as the last step).
So while I can refer to a variable defined in an outer loop within the inner loop, I cannot change its value in the inner loop.
And I think you are saying that for a script (not template) variable i cannot change its value inside the same loop. And I can’t use a template variable (which I can change) because the template gets broken when I do service calls.
I guess I’m back to doing math based on repeat.index and forming a new value each time. I can do that.
In case it helps anyone else (or if anyone wants to suggest corrections), I think this script works (there are some fairly obvious dependencies but they are not relevant to the looping and pattern shift):
landscape_christmas:
alias: Landscape Christmas
sequence:
choose:
conditions:
- condition: state
entity_id: input_boolean.landscape_christmas_on
state: "off"
sequence:
# Turn off so we get a clean start
- service: script.landscape_off_internal
# Turn on indicator we are on/running (this is the only one that will be running)
- service: input_boolean.turn_on
entity_id:
- input_boolean.landscape_christmas_on
# Any number of lights can be added here, but then fill in the pattern also with that many
- variables:
floods:
- light.flood1
- light.flood2
- light.flood3
- light.flood4
- light.flood5
- light.flood6
- light.flood7
- light.flood8
orig_pattern: "RRRRGGGG"
numlights: '{{ floods | count }}'
- repeat:
sequence:
- variables:
pattern: >- # this will calculate the rotated location based on repeat.index
{% set modpat = ( (repeat.index - 1) % numlights) %}
{% set pat = orig_pattern[modpat:numlights] + orig_pattern[0:modpat] %}
{{ pat }}
- repeat:
count: '{{ numlights }}'
sequence:
- variables:
flood: '{{ floods[(repeat.index - 1)] }}'
color: '{{ pattern[(repeat.index - 1):(repeat.index)] }} ' # pull from same spot, but the string rotates each outer loop
# Red lights
- if:
- condition: template
value_template: "{{ color == 'R' }} "
then:
- sequence:
- service: light.turn_on
target:
entity_id:
- "{{ flood }}"
data:
effect: none
brightness: 180
color_name: Red
transition: 3
else:
- sequence:
- service: light.turn_on
target:
entity_id:
- "{{ flood }}"
data:
effect: none
brightness: 255
color_name: Green
transition: 3
- delay:
seconds: 6
until:
- condition: state
entity_id: input_boolean.landscape_christmas_on
state: "off"
default:
- service: script.landscape_off