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)
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
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.
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.
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 %}
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()) }}
@123brightness_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.