Script Service Template Throws error

Hi all

I seem to be unable to use AND in service templating in scripts.

livingareaautolight:
  alias: Auto Light - Living Area Manage
  sequence:
   - data: {}
    entity_id: light.backdoor
    service_template: >
        {% if states('sensor.diningroom_brightness') | float > 10 and states('sensor.diningroom_brightness') | float < 12 %} 
          light.turn_off
        {% endif %}

generates the following Error:

Logger: homeassistant.components.script
Source: helpers/service.py:123
Integration: Script (documentation, issues)
First occurred: 1:40:52 PM (1 occurrences)
Last logged: 1:40:52 PM

Auto Light - Living Area Manage: Error executing script. Unexpected error for call_service at pos 2: Template rendered invalid service:
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 115, in async_prepare_call_from_config
    domain_service = cv.service(domain_service)
  File "/usr/src/homeassistant/homeassistant/helpers/config_validation.py", line 411, in service
    raise vol.Invalid(f"Service {value} does not match format <domain>.<name>")
voluptuous.error.Invalid: Service  does not match format <domain>.<name>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 153, in _async_step
    self, f"_async_{cv.determine_script_action(self._action)}_step"
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 623, in _async_call_service_step
    *self._prep_call_service_step(), blocking=True, context=self._context
  File "/usr/src/homeassistant/homeassistant/helpers/script.py", line 252, in _prep_call_service_step
    return async_prepare_call_from_config(self._hass, self._action, self._variables)
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 123, in async_prepare_call_from_config
    ) from ex
homeassistant.exceptions.HomeAssistantError: Template rendered invalid service: 

But the following:

livingareaautolight:
  alias: Auto Light - Living Area Manage
  sequence:
   - data: {}
    entity_id: light.backdoor
    service_template: >
        {% if states('sensor.diningroom_brightness') | float > 10 %} light.turn_off
        {% endif %}

executes without error.
Any Advice would be appreciated.
Thanks

You donā€™t have an else, so whenever the template resolves to false you will get an error as you are providing a null value for the service.

(Iā€™m presuming the indentation problems are paste errors rather than a further issue)

Your template wants the brightness to be between 10 and 12 in order to turn on the light. What if the brightness is NOT in that range? Your template will produce nothing which is an invalid service call.

Create a condition prior to the service call (Template Condition). Then the service call doesnā€™t need a template because it will be executed only if the condition was satisfied.


EDIT

Hereā€™s an example of what I suggest you do:

livingareaautolight:
  alias: Auto Light - Living Area Manage
  sequence:
    - condition: template
      value_template: "{{ 10 < states('sensor.diningroom_brightness') | float < 12 }}"
    - service: light.turn_off
      entity_id: light.backdoor
  • If the brightness is within the desired range, the condition is satisfied and execution proceeds to the next step and the backdoor light is turned on.
  • If the brightness is not within the desired range, execution stops.

This technique avoids the entire question of ā€œWhat should I put in the ā€˜elseā€™ when I donā€™t need/want an ā€˜elseā€™?ā€


EDIT

Correction. Replaced turn_on with turn_off

Thanks mf_social

yes the indentation is copy/paste only.

Is there any reason that the 2nd code snippet would then NOT error out? As it also doesnt have an else part to it.

Your code is wrong. An ā€œifā€ always needs an ā€œelseā€ just because it sometimes does not error out does not mean you are okay. Itā€™s simply bad programming practice.
AND following the script/automation erroring out, it crashes, and any subsequent actions following it in the script/automation will not be performed.

You can omit as many elifā€™s as you like but always include an else.
If you donā€™t need an else just use a condition to block further processing

Presumably the majority of the time the template resolves to true and the few times it doesnā€™t it is erroring and you just havenā€™t spotted it :man_shrugging:

Thanks all

I will make use of Conditions in this case instead.

Just to note. The HA docs make reference to using an IF statement without ELSE in its template docs

I disagree that an if always needs an else from a programming point of view (and Iā€™ve been doing this since the 1980s in a variety of languages); and there are examples in the template documentation to back me up on that for Jinja templates in general; although I agree that the template in thicase must provide a valid output for service_template.

I wonder if the problem is one of priority that can be solved with brackets? Going overkill to start with, try this:

{% if ((states('sensor.diningroom_brightness') | float) > 10) and ((states('sensor.diningroom_brightness') | float) < 12) %}

thanks Troon

I have given this a try. same error occurs.

I have also attempted assigning variables and using those instead. Same issue, with or without brackets around the conditions

   service_template: >
        {% set brightness = states('sensor.diningroom_brightness') | float %}
        {% if brightness > 10 and brightness < 12 %} light.turn_off
        {% endif %}

and

   service_template: >
        {% set brightness = states('sensor.diningroom_brightness') | float %}
        {% if (brightness > 10) and (brightness < 12) %} light.turn_off
        {% endif %}

service_template must be supplied with a service call. So if the template fails to provide one (i.e. returns an empty string), Home Assistant will throw this error:

Invalid: Service does not match format <domain>.<name>

The way the template was constructed, if the brightness fails to be within the desired range, it produces nothing and thatā€™s not a valid service call. Therefore the usual recourse is add an ā€˜elseā€™ to supply something. However, scripting allows you to use conditions which can be used in lieu of if-else statements (see example above).

That example generates a long string of text. The ā€˜if without an elseā€™ inside it simply decides whether or not to include something in the middle of the sentence (ā€œit is warmā€ if it is warm, nothing if it is not). The result of the template in its entirety still generates the required data (location of Paulus, times the sensors were last activated etc).

In the case of a service template you MUST always resolve to an actual service, otherwise you will get an error because you told the system to run a command but didnā€™t actually give it a command to run.

Thanks Taras

This logic makes sense.

It is then an implementation of if else within Home Assistant rather than a programming rule of thumb as Troon pointed out.

From your link :

{% if states('sensor.temperature') | float > 20 %}
  It is warm!
{% endif %}

This is the only one I can find, I agree itā€™s misleading AND wrong
If used in a sensor the the sensor says ??? ā€œIt is warm!ā€ or ā€¦
This is a crap sensor.
If itā€™s used in an automation/script then what happens when itā€™s not(if()) ? again crap - the document should be corrected.
Just seen your new templates. They still employ the same error
How about you try what Teras, Marc and I suggested : -

   service_template: >
        {% set brightness = states('sensor.diningroom_brightness') | float %}
        {% if (brightness > 10) and (brightness < 12) %}
          light.turn_off
        {% else %}
          light.turn_on
        {% endif %}

@Troon ā€¦ I was going to jump on that but Taras beat me to it (out Ninjaā€™d (AGAIN !))

Edit: corrected the on/off (but thereā€™s no reason not to have off and off (just donā€™t see the point))

If you follow the ā€œrule of thumbā€ (if thatā€™s what you want to call it) you will never fail. if you donā€™t ā€¦ ???

And the case here is that we are writting code for HA running through a Jinja wrapped version of python (and the sandbox only supports a subset of ALL python commands too). We have to bear that in mind as this is not assembler. fortran, cobol, visual basic etc. etc. etc.

Thanks Mutt

I do not want this script to turn the light on, only switch it off at a certain brightness level.

The problem with a condition in a script, is that it is applied to the script as a whole. This script turns a number of lights off on this brightness setting. Adding a condition to check if a single light is on, it would circumvent switching any other lights off if the condition for one light isnt met.

No.

Templates support ā€˜if-elseā€™ statements and there are situations where they are useful. However, thereā€™s a distinction between the purpose of an ā€˜if-elseā€™ in a template and using a condition like I did in the example above.

  • The condition is being used to control the scriptā€™s execution flow (go, no-go). If the condition is satisfied, execution proceeds to the next step. If the condition is not satisfied, execution stops.
  • An if-else within a template determines the templateā€™s output. The service_template option expects to receive the name of a service call and your templateā€™s job is to provide it with one. Perhaps an ā€˜if-elseā€™ could be used to return light.turn_on or light.turn_off. However, in your template, it really only needs to return one service, light.turn_on, and thereā€™s no other service to call. In other words, you were trying to use the template to control the scriptā€™s execution flow. That job is best handled by a condition.

So you have to call a non acting service if you still wish to proceed (by the way the error will block it regardless)

Correct.

In your example, thatā€™s fine however, as you stated, itā€™s problematic if there are other steps that the script must execute.

In that tricky situation, you would need to use the workaround suggested by Mutt. Your original template would use an else that calls a ā€˜do-nothingā€™ service (because the template is still obliged to provide a service call). Itā€™s not pretty but it gets the job done.

There is no specific ā€˜do-nothingā€™ or ā€˜dummyā€™ service but this one serves as a fairly safe substitute in most situations: homeassistant.update_entity

Example:

   service_template: >
     {% if 10 < states('sensor.diningroom_brightness') | float < 12 }}
       light.turn_off
     {% else %}
       homeassistant.update_entity
     {% endif %}
3 Likes

Thanks!

Did not know about the ā€˜do-nothingā€™ service workaround. Thats very helpful

This wonā€™t work here (please educate me :smiley: ) as he wants to call light.turn off as the other service see other posts below
presumably with the entity id just before or after depending on your inclinations or if the UI genertaes it.
BUT

   service_template: >
        {% set brightness = states('sensor.diningroom_brightness') | float %}
        {% if (brightness > 10) and (brightness < 12) %}
          script.turn_off_the_dang_light
        {% else %}
          script.zero_delay_script
        {% endif %}