Variables in automation?

I want to display 3 columns of data on display (eink via OpenEPaperLink integration). Data is time/route/direction of buses from 3 stops close to my place (data comes from GTFS2 integration).

Ugly way creates variable three times (dropped all not needed fields and sensor names):

actions:
  - action: open_epaper_link.drawcustom
    metadata: {}
    data:
      payload:
        - type: multiline
          value: >-
            {% set stop1 = 
            state_attr('sensor.stop1',
            'next_departures_lines') %}

            {% set stop2 = 
            state_attr('sensor.stop2',
            'next_departures_lines') %}

            {% set stop3 = 
            state_attr('sensor.stop3',
            'next_departures_lines') %}


            {% for dep in (stop1 + stop2 + stop3)|sort(attribute="departure")
            -%}

            {{ "%s" % (dep.departure) }}|

            {% endfor -%}
          x: 0
        - type: multiline
          value: >-
            {% set stop1 = 
            state_attr('sensor.stop1',
            'next_departures_lines') %}

            {% set stop2 = 
            state_attr('sensor.stop2',
            'next_departures_lines') %}

            {% set stop3 = 
            state_attr('sensor.stop3',
            'next_departures_lines') %}


            {% for dep in (stop1 + stop2 + stop3)|sort(attribute="departure")
            -%}

            {{ "%02s" % ( dep.route) }}|

            {% endfor -%}
          x: 100
        - type: multiline
          value: >-
            {% set stop1 = 
            state_attr('sensor.stop1',
            'next_departures_lines') %}

            {% set stop2 = 
            state_attr('sensor.stop2',
            'next_departures_lines') %}

            {% set stop3 = 
            state_attr('sensor.stop3',
            'next_departures_lines') %}


            {% for dep in (stop1 + stop2 + stop3)|sort(attribute="departure")
            -%}

            {{ "%s" % (dep.headsign) }}|

            {% endfor -%}
          x: 160

So I decided to try to create a list of departures once:

actions:
  - action: open_epaper_link.drawcustom
    metadata: {}
    data:
      payload:
        - type: multiline
          value: |-
            {% for dep in odjazdy -%}
            {{ dep }}|
            {% endfor -%}
          x: 0
        - type: multiline
          value: |-
            {% for dep in odjazdy %}
            {{ "%02s" % ( dep.route) }}|
            {% endfor -%}
          x: 100
        - type: multiline
          value: |-
            {% for dep in odjazdy %}
            {{ dep.headsign }}|
            {% endfor -%}
          x: 160
variables:
  odjazdy: |-
    {% set stop1 = 
      state_attr('sensor.stop1',
      'next_departures_lines') %}
    {% set stop2 = 
      state_attr('sensor.stop2',
      'next_departures_lines') %}
    {% set stop3 = 
      state_attr('sensor.stop3',
      'next_departures_lines') %}
    {{ (stop1 + stop2 + stop3)|sort(attribute="departure") }}

“actions” part is now shorter, easier to read. “odjazdy” variable contains a list of departures:

[{'departure': '09:12', 'departure_datetime': datetime.datetime(2025, 2, 20, 8, 12, tzinfo=datetime.timezone.utc), 'departure_realtime': '-', 'departure_realtime_datetime': '-', 'delay_realtime_derived': '-', 'delay_realtime': '-', 'date': '2025-02-20', 'stop_name': 'Stop 12', 'route': '4', 'route_long': 'Here - There', 'headsign': 'There', 'trip_id': '436291_POW', 'direction_id': 0, 'icon': 'mdi:bus'},
 {'departure': '09:12', 'departure_datetime': datetime.datetime(2025, 2, 20, 8, 12, tzinfo=datetime.timezone.utc), 'departure_realtime': '-', 'departure_realtime_datetime': '-', 'delay_realtime_derived': '-', 'delay_realtime': '-', 'date': '2025-02-20', 'stop_name': 'Stop 12', 'route': 'B', 'route_long': 'Somewhere - Otherwhere', 'headsign': 'Otherwhere', 'trip_id': '434102_POWS', 'direction_id': 0, 'icon': 'mdi:bus'},
 {'departure': '09:15', 'departure_datetime': datetime.datetime(2025, 2, 20, 8, 15, tzinfo=datetime.timezone.utc), 'departure_realtime': '-', 'departure_realtime_datetime': '-', 'delay_realtime_derived': '-', 'delay_realtime': '-', 'date': '2025-02-20', 'stop_name': 'Stop 13', 'route': '3', 'route_long': 'There - Nowhere', 'headsign': 'Nowhere', 'trip_id': '375423_POW', 'direction_id': 1, 'icon': 'mdi:bus'}]

But in “actions” block it is treated as a string:

          [|
          {|
          '|
          d|
          e|
          p|
          a|
          r|
          t|
          u|
          r|
          e|
          '|
          :|

What is wrong? Where the mistake was made?

1 Like

While Jinja variables can contain things like datetime objects, YAML variables can only contain basic data types null, boolean, number, string, list and dict.

Jinja templates can only ever output strings, which are then reinterpreted by the YAML parser. The YAML parser does not understand the stringification of the datetime object, thus it falls back on interpreting everything as one single string.

If you want to get around this, you must manually convert your datetime objects to a string or number (timestamp) before returning it as a value to be stuffed into a YAML variable.

1 Like

In other words: stay with ugly version.

Or make nice version even uglier than ugly one as it would need to loop over result to drop datetime field from each entry. removing is unsafe…

I realize this is not the same as yours. But here is how I display the electricity prices on a display.
Maybe it can help you with how to loop stuff with.

target:
  entity_id:
    - open_epaper_link.0000021e68ba3b1b
data:
  background: white
  rotate: 270
  payload: >
    {% set ns = namespace( text_list = [], line =0) %} 
    {% set nordpool = "sensor.nordpool_kwh_se4_sek_3_10_025" %} 
    {% set cheapest = 5 %} 
    {% set expensivest = 5 %} 
    {% set decimal = "," %} {## decimal separator in currency ##} 
    {% set currency = "kr" %} {## include space if needed/wanted ##} 
    {% set position = "trailing" %} {## trailing or leading ##} 
    {% set decimal_places = 2 %}

    {% for day in ["today", "tomorrow"] %}
      {% for s in state_attr(nordpool,'raw_' ~ day)-%}
        {% if s.end > now() -%}
          {%- set ns.text_list = ns.text_list +  

           [{ "type": "rectangle",  
             "x_start": 1,
             "x_end": 127,
             "y_start": 1 + (25*ns.line), 
             "y_end": 25 + (25*ns.line),
             "width": 4 if s.value in (state_attr(nordpool, day) | sort(reverse = false))[0:cheapest] else 2,
             "fill": "white",
             "outline": "red" if s.value in (state_attr(nordpool, day) | sort(reverse = false))[0:cheapest] else "black"}] +

          [{ "type": "text",  
             "value": ("0" ~ s.start.hour)[-2:3] ~ ":00", 
             "font": "ppb.ttf",
             "x": 5,
             "y":  7 + (25*ns.line), 
             "size": 16,
             "color": "red" if s.value in (state_attr(nordpool, day) | sort(reverse = true))[0:expensivest] else "black"} ] +
             
          [{ "type": "text",  
             "value": (currency if position == "leading") ~ (((s.value | round(decimal_places) ~ "00000")[0:decimal_places+(s.value|string).split(".")[0]|length +1]) |string).replace(".", decimal) ~ (currency if position == "trailing"),
             "font": "ppb.ttf",
             "x": 58,
             "y":  7 + (25*ns.line), 
             "size": 16,
             "color": "red" if s.value in (state_attr(nordpool, day) | sort(reverse = true))[0:expensivest] else "black"} ] -%}
        
          {% set ns.line = ns.line + 1 %} 
        {% endif %}
      {%- endfor -%}
    {%- endfor -%}


    {{ ns.text_list }}
  enabled: true
action: open_epaper_link.drawcustom

so it generates all that I need based on conditions

1 Like

{%- set hrw = namespace(odjazdy = []) -%}

{% set stop1 = 
	state_attr('sensor.85012_local_stop_zone_osiedle_sloneczne_petla',
			'next_departures_lines') %}
{% set stop2 = 
	state_attr('sensor.85053_local_stop_zone_osiedle_sloneczne_petla',
			'next_departures_lines') %}
{% set stop3 = 
	state_attr('sensor.85054_local_stop_zone_osiedle_sloneczne_petla',
			'next_departures_lines') %}

{% for dep in (stop1 + stop2 + stop3)|sort(attribute="departure") %}

	{% set hrw.odjazdy = hrw.odjazdy + [{
			'departure':dep.departure, 
				'route': dep.route,
				'headsign': dep.headsign
		}] %}

{% endfor %}

{{ hrw.odjazdy }}

Works in devtools. Now have to put it into automation.

Thanks @Hellis81

You can always make the code a lot more compact, but whether you consider it less ugly (or less/more readable) is always open for debate. For example your very long variable and loop declaration could be compacted to this:

{% for dep in ['85012', '85053', '85054']
| map('regex_replace', '(.+)',
'sensor.\\1_local_stop_zone_osiedle_sloneczne_petla')
| map('state_attr', 'next_departures_lines')
| sort(attribute='departure') %}
1 Like