This template works in Developer Tools, but not in a script. Why?

I have the following template in a script. When I test the template in Developer Tools, it works like I expect. But when I call it, I get an error. Is there a way to accomplish what I’m trying to do here?

The script:

service_template: |
  {% if is_state('device_tracker.frank_iphone', 'work') %}
     notify.email_frank
  {% else %}
     notify.mobile_app_frank_iphone
  {% endif %}
data: |
  {% if title is defined %}
    title: "{{ title }}"
  {% endif %}
  {% if message is defined %}
    message: "{{ message }}"
  {% endif %}
  {% if image is defined or url is defined %}
    data:
      {% if image is defined %}
        image: "{{ image }}"
      {% endif %}
      {% if url is defined %}
        url: "{{ url }}"
      {% endif %}
  {% endif %}

Example of calling it:

service: script.notify_frank_with_data
data:
  title: foo
  message: bar

The error when calling it:

2022-05-07 07:14:19 ERROR (MainThread) [homeassistant.components.script.notify_frank_with_data] notify_frank_with_data: Error executing script. Error for call_service at pos 1: Error rendering data template: Result is not a Dictionary
2022-05-07 07:14:19 ERROR (MainThread) [homeassistant.helpers.script.websocket_api_script] websocket_api script: Error executing script. Error for call_service at pos 1: Error rendering data template: Result is not a Dictionary
2022-05-07 07:14:19 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection] [139955239246912] Error handling message: Error rendering data template: Result is not a Dictionary (unknown_error)

I get the same error. Should I file a bug report for this?

service: script.turn_on
data:
  variables:
    title: "foo"
    message: "bar"
target:
  entity_id: script.notify_frank_with_data
2022-05-07 07:41:09 ERROR (MainThread) [homeassistant.components.script.notify_frank_with_data] notify_frank_with_data: Error executing script. Error for call_service at pos 1: Error rendering data template: Result is not a Dictionary
2022-05-07 07:41:09 ERROR (MainThread) [homeassistant] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/script/__init__.py", line 428, in _async_run
    return await self.script.async_run(script_vars, context)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 1513, in async_run
    await asyncio.shield(run.async_run())
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 405, in async_run
    await self._async_step(log_exceptions=False)
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 449, in _async_step
    self._handle_exception(
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 472, in _handle_exception
    raise exception
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 447, in _async_step
    await getattr(self, handler)()
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 653, in _async_call_service_step
    params = service.async_prepare_call_from_config(
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 245, in async_prepare_call_from_config
    raise HomeAssistantError(
homeassistant.exceptions.HomeAssistantError: Error rendering data template: Result is not a Dictionary

You can’t template yaml fields. You have to treat it like a dictionary and output the entire object for the data field for this to work.

If you don’t understand what I said, you should use choose instead of a template.

This is a working version

data: |
  {% set ns = namespace(items=[]) %}
  {% if title is defined %}
    {% set ns.items = ns.items + [('title', title)] %}
  {% endif %}
  {% if message is defined %}
    {% set ns.items = ns.items + [('message', message)] %}
  {% endif %}
  {% if image is defined or url is defined %}
      {% set ns2 = namespace(items=[]) %}
      {% if image is defined %}
        {% set ns2.items = ns2.items + [('image', image)] %}
      {% endif %}
      {% if url is defined %}
        {% set ns2.items = ns2.items + [('url', url)] %}
      {% endif %}
      {% if ns.items %}
        {% set ns.items = ns.items + [('data', dict.from_keys(ns2.items))] %}
      {% endif %}
  {% endif %}
  {{ dict.from_keys(ns.items) }}

EDIT: But I don’t suggest you use it unless you understand it.

Perfect, thank you! This does make sense.

Thank you!
Your code enabled me to configure the following service call and pass it a brightness: <value> parameter if another entity’s attribute exists or pass {} if it does not.

service: light.turn_on
target:
  entity_id: light.bathroom_light
data: |
  {% set ns = namespace(items=[]) -%}
  {% if state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct') -%}
    {% set ns.items = ns.items + [('brightness', state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct')|int(0))] -%}
  {% endif -%}
  {{ dict.from_keys(ns.items) }}
enabled: true

For your application, it’s also possible to use a more traditional technique like this (executes the service call only if the condition evaluates to true).

  - variables:
      b: "{{ state_attr('switch.adaptive_lighting_bathroom_light', 'brightness_pct') }}"
  - if: '{{ b is not none }}'
    then:
      - service: light.turn_on
        target:
           entity_id: light.bathroom_light
        data:
          brightness: '{{ b }}'

Me use more traditional techniques??? Ha, LOL!
I was previously using if/then/else but figured there had to be a way to make a service call pass brightness: if it could or nothing if it could not. In the end, I decided to simply call light.turn_on with no parameters and let adaptive lighting if enabled set the brightness. I may have to adjust that if and when adaptive lighting is off or AWOL if the brightness ends up not being what I want without it.
I’ve actually come up with three template versions that seem to work:

{% set ns = namespace(items=[]) -%}
  {% if state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct') -%}
    {% set ns.items = ns.items + [('brightness', state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct')|int(0))] -%}
  {% endif -%}
  {{ dict.from_keys(ns.items) }}
{% set b = dict(brightness=state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct')|int(0)) -%}
  {% if b -%}
    {{ b }}
  {%- else -%}
    {{ dict() }}
  {%- endif %}

And finally, a one-liner…

{{ iif(state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct'), dict(brightness = state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct')|int(0)),dict()) }}

I don’t have that sensor in my system and I tested all three examples. Ideally, they should have produced the same result (empty dictionary) but the second one doesn’t. It reports {brightness: 0} because although state_attr reports none, the final int(0) defaults to 0.

Moving the int(0) out of the set and into the if fixes it (just like it’s done in your third example).

{% set b = state_attr('switch.adaptive_lighting_bathroom_light','brightness_pct') %}
{{ iif(b, dict(brightness = b|int(0)), dict()) }}
1 Like

@123 brightness_pct is not a valid attribute. brightness is, brightness_pct for service calls is derived from brightness

@petro would this also be an alternative approach, or are there certain reasons why yours would be preferred?

data: >
  {% set base = dict(title=title) if title is defined else dict() %}
  {% set base = dict(base, **dict(message=message) if message is defined else base) %}
  {% if image is defined or url is defined %}
    {% set data = dict(image=image if image is defined else dict()) %}
    {% set data = dict(data, **dict(url=url) if url is defined else data) %}
    {% set base = dict(base, **dict(data=data)) %}
  {% endif %}
  {{ base }}

that’s a good alternate approach. If your keys don’t have spaces or specials characters, that route is great.

I typically use the dict() route when I don’t want to type quotes all over the place. I use it for my MQTT discovery info because they are always slugified keys and the syntax is less verbose than using key value pairs.

There are drawbacks to both methods in regards to merging information. If you go the key value pair route, you can have duplicate keys. If you go the dict route, you may run into issues with duplicate keys. Also, the KVP route allows you to filter out keys you don’t want at the end instead of as you go.

It’s just a bit different. They each have their benefits.

1 Like

Take it up with dbrunt. I just used what’s in his template. Apparently it’s an attribute of a switch entity he has.

Yeah, my bad. I read brighntess_pct and assumed a light entity :slight_smile:

It’s an an attribute of the Adaptive Lighting switch entitiies. I’m referencing that value and passing it to the light service call as brightness: ##.

image


image