Templates in shell commands

Hi folks,

I’m having a hard time with templates and I hope you can point me in the right direction.

What I want to achieve: On my router, I have a quarantined zone, which basically forbids all traffic unless I allow it. If a device drops off that zone, my router (and consequently HA) will not realize this for some reason. However, I noticed, that I can force my router to update by pinging the device. So… I want to periodically ping all devices on my network.

What I tried: I have updated the luci device scanner to include the ip address of my devices. That works already, so each home device has an attribute called ip. (If the PR hasn’t reached your installation, you should be able to also test with the nmap scanner. It provides the IP already since quite a while.)

Next, I tested a template in the frontend:

    {% for entity_id in states.group.all_devices.attributes.entity_id %}
    {%- if states(entity_id) == 'home' and state_attr(entity_id, 'ip') != 'None' -%}
    ping -c 1 {{state_attr(entity_id, 'ip')}} ;
    {% endif %}
    {%- endfor %}

It works as expected and lists all online IPs in a giant list of ping statements.

Finally, I just copied this template to a shell command:

shell_command:
  ping: >
    {% for entity_id in states.group.all_devices.attributes.entity_id %}
    {% if states(entity_id) == 'home' and state_attr(entity_id, 'ip') != 'None' %}
    ping -c 1 {{state_attr(entity_id, 'ip')}} ;
    {% endif %}
    {% endfor %}

Strangely, when calling this shell_command, I get the following error in the logs and nothing is pinged:

Oct 29 23:42:09 ak-test hass[18454]: 2018-10-29 23:42:09 ERROR (MainThread) [homeassistant.components.shell_command] Error rendering command template: TemplateSyntaxError: Encountered unknown tag 'endfor'.
Oct 29 23:42:09 ak-test hass[18454]: Traceback (most recent call last):
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/helpers/template.py", line 104, in ensure_valid
Oct 29 23:42:09 ak-test hass[18454]:     self._compiled_code = ENV.compile(self.template)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/environment.py", line 591, in compile
Oct 29 23:42:09 ak-test hass[18454]:     self.handle_exception(exc_info, source_hint=source_hint)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/environment.py", line 780, in handle_exception
Oct 29 23:42:09 ak-test hass[18454]:     reraise(exc_type, exc_value, tb)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/_compat.py", line 37, in reraise
Oct 29 23:42:09 ak-test hass[18454]:     raise value.with_traceback(tb)
Oct 29 23:42:09 ak-test hass[18454]:   File "<unknown>", line 1, in template
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/environment.py", line 497, in _parse
Oct 29 23:42:09 ak-test hass[18454]:     return Parser(self, source, name, encode_filename(filename)).parse()
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 901, in parse
Oct 29 23:42:09 ak-test hass[18454]:     result = nodes.Template(self.subparse(), lineno=1)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 883, in subparse
Oct 29 23:42:09 ak-test hass[18454]:     rv = self.parse_statement()
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 144, in parse_statement
Oct 29 23:42:09 ak-test hass[18454]:     self.fail_unknown_tag(token.value, token.lineno)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 97, in fail_unknown_tag
Oct 29 23:42:09 ak-test hass[18454]:     return self._fail_ut_eof(name, self._end_token_stack, lineno)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 90, in _fail_ut_eof
Oct 29 23:42:09 ak-test hass[18454]:     self.fail(' '.join(message), lineno)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/jinja2/parser.py", line 59, in fail
Oct 29 23:42:09 ak-test hass[18454]:     raise exc(msg, lineno, self.name, self.filename)
Oct 29 23:42:09 ak-test hass[18454]: jinja2.exceptions.TemplateSyntaxError: Encountered unknown tag 'endfor'.
Oct 29 23:42:09 ak-test hass[18454]: During handling of the above exception, another exception occurred:
Oct 29 23:42:09 ak-test hass[18454]: Traceback (most recent call last):
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/components/shell_command.py", line 54, in async_service_handler
Oct 29 23:42:09 ak-test hass[18454]:     rendered_args = args_compiled.async_render(service.data)
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/helpers/template.py", line 126, in async_render
Oct 29 23:42:09 ak-test hass[18454]:     self._ensure_compiled()
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/helpers/template.py", line 173, in _ensure_compiled
Oct 29 23:42:09 ak-test hass[18454]:     self.ensure_valid()
Oct 29 23:42:09 ak-test hass[18454]:   File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/helpers/template.py", line 106, in ensure_valid
Oct 29 23:42:09 ak-test hass[18454]:     raise TemplateError(err)
Oct 29 23:42:09 ak-test hass[18454]: homeassistant.exceptions.TemplateError: TemplateSyntaxError: Encountered unknown tag 'endfor'.

I am out of my depths here… Does anyone have an idea, what I am missing?!

Thanks a lot!
Andreas

Unfortunately you can’t use a template like that with the shell_command. First, the template can only be the part after the first space character. And second, I think it can only be the arguments for a single command. So I think what you’ll have to do is to create a shell script and use the template to pass a list of IP addresses to it, which the shell script would then use to ping each of the addresses in turn.

Thanks for the hint! Fortunately there is the fping tool out there, which saved me from writing a new shell script. So, now this works:

 fping: 'fping {{ states.device_tracker | join | regex_replace( find="<.*?ip\=([0-9.]+).*?>", replace="\\1 ") | regex_replace( find="<.*?\>", replace="") }}'

It looks much messier, but it works…

How about:

fping: "fping {{ states.device_tracker|selectattr('attributes.ip')|map(attribute='attributes.ip')|join(' ') }} "

This template selects device_tracker’s that have an ip attribute, and then generates a list of the ip attributes, each separated by a space.

Hmm… sometimes, things can be so easy… I was trying this once:

states.device_tracker|map(attribute='ip')

Obviously, that failed…

Thanks again! I like the lean configuration this gives now!

Yep, I got tripped up by that at first, too. To Jinja an attribute is a field of a state object. But, of course, to HA an attribute is a field of a state object’s attributes field. Too many overloaded terms in HA. :frowning:

Just as a note to the afterworld. I have been running this solution for some time. Successfully so. However, I was unhappy with two things:

  1. The command would always fail and this would be reported in the logs. Obviously, there is always one IP or the other which cannot be reached. I wanted these reports to stop.
  2. I have quite some configuration to set up the automation and the command. I wanted that simplified.

So, I just found out that the command-line-sensor allows to chain multiple shell commands. Also, it is called repeatedly. So, the only setup I am using now is the following:

sensor:
  - platform: command_line
    name: Last fping keepalive
    command:  "fping -q {{ states.device_tracker|selectattr('attributes.ip')|map(attribute='attributes.ip')|join(' ') }}; date +%H:%M"
    scan_interval: 300

Seems to work like a charm…