Shell_command templating variables error with a bash script

I’m a little puzzled why the following doesn’t work - does anyone have any suggestions please?

I have a shell_command that I’d like to pass variables such as outdoor temperature to a bash script. e.g.

shell_commands.yaml
papirusupdate: /home/homeassistant/papirus.sh {{ states.sensor.netatmo_outdoor1_temperature.state }} {{ states.sensor.lights_on.state }} {{ states.sensor.doors_open.state }}

This generates an error each time it runs:

2018-01-17 15:50:00 INFO (MainThread) [homeassistant.helpers.script] Script Update Papirus: Running script
2018-01-17 15:50:00 INFO (MainThread) [homeassistant.helpers.script] Script Update Papirus: Executing step call service
2018-01-17 15:50:00 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/core.py", line 1031, in _event_to_service_call
    yield from service_handler.func(service_call)
  File "/srv/homeassistant/lib/python3.5/site-packages/homeassistant/components/shell_command.py", line 86, in async_service_handler
    process = yield from create_process
  File "/usr/lib/python3.5/asyncio/subprocess.py", line 212, in create_subprocess_exec
    stderr=stderr, **kwds)
  File "/usr/lib/python3.5/asyncio/base_events.py", line 1189, in subprocess_exec
    bufsize, **kwargs)
  File "/usr/lib/python3.5/asyncio/unix_events.py", line 191, in _make_subprocess_transport
    **kwargs)
  File "/usr/lib/python3.5/asyncio/base_subprocess.py", line 39, in __init__
    stderr=stderr, bufsize=bufsize, **kwargs)
  File "/usr/lib/python3.5/asyncio/unix_events.py", line 695, in _start
    universal_newlines=False, bufsize=bufsize, **kwargs)
  File "/usr/lib/python3.5/subprocess.py", line 676, in __init__
    restore_signals, start_new_session)
  File "/usr/lib/python3.5/subprocess.py", line 1282, in _execute_child
    raise child_exception_type(errno_num, err_msg)
OSError: [Errno 8] Exec format error

If I remove the template and just leave manual numbers in there it works:

papirusupdate: /home/homeassistant/papirus.sh 1 2 3

I don’t think it’s an issue with templating and shell_commands because I also have the following which uses the same templates and works:

pushover_glance: 'curl --data "token=tokenremoved&title={{ states.sensor.pushover_title_glance.state }}&text=OutD:{{ states.sensor.netatmo_outdoor1_temperature.state }} Humi:{{ states.sensor.netatmo_outdoor1_humidity.state }}&subtext=Lights:{{ states.sensor.lights_on.state }} Door/Win:{{ states.sensor.doors_open.state }}" https://api.pushover.net/1/glances.json'

Is there a known issue with calling bash scripts and templates?

I’m guessing the answer to those states isn’t a value the script will accept, maybe an interger / float thing. Perhaps try ‘printf -v’ with them?

I’m curious if you’re using the Papirus e-ink screen? I’ve one and use it to display Hass data, and the temperature from the papirus itself which I also send back to Hass.

The way I arrange it, I don’t have to call a script to send data to the Papirus.

The Papirus takes the data from Hass via the api, and displays it.

Here’s the code, maybe it can be an alternative method. It works very well, though it’s not going to win beauty contests.

#!/usr/bin/python3
from requests import get
from papirus import PapirusText, PapirusTextPos, Papirus, LM75B
from datetime import datetime, time
from PIL import ImageFont
from subprocess import PIPE, Popen


FONT_FILE = '/usr/share/fonts/truetype/freefont/FreeSerif.ttf'


def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])

def get_temp():
    sensor = LM75B()
    temp_c = '{c:.2f}'.format(c=sensor.getTempCFloat())
    temp_c = float(temp_c)
    cpu_temp_c = get_cpu_temperature()
    temp_c_cal = temp_c - ((cpu_temp_c-temp_c)/1.99)
    temp_c_cal = round(temp_c_cal, 1)
    return (str(temp_c_cal))


def in_between(now, start, end):
    if start <= end:
        return start <= now < end
    else: # over midnight e.g., 23:30-04:15
        return start <= now or now < end


text = PapirusTextPos(False ,0)

## Time of Readings
response = get('http://192.168.18.13:8123/api/states/sensor.kells')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    time_readings = "??:??"
else:
    time_readings = str(response.json()['state'])

## Papirus Thermometer Reading
papi_temp = get_temp()

## Actual Outside Temperature
response = get('http://192.168.18.13:8123/api/states/sensor.dark_sky_temperature')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    outside_temp = "??"
else:
    outside_temp = str(float(response.json()['state']))

## Apparent Temperature - 'Feels like ...'
response = get('http://192.168.18.13:8123/api/states/sensor.dark_sky_apparent_temperature')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    feels_like = "??"
else:
    feels_like = str(float(response.json()['state']))


## Nearest Squall
response = get('http://192.168.18.13:8123/api/states/sensor.dark_sky_nearest_storm_distance')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    squall_dist = "?? km"
else:
    squall_dist = str(response.json()['state'])


## Cloud Cover
response = get('http://192.168.18.13:8123/api/states/sensor.dark_sky_cloud_coverage')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    cloud_cover = "??%"
else:
    cloud_cover = str(response.json()['state'])


## Rain Chance %
response = get('http://192.168.18.13:8123/api/states/sensor.dark_sky_precip_probability')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    rain_chance = "??%"
else:
    rain_chance = str(response.json()['state'])

## Enviro Temp
response = get('http://192.168.18.13:8123/api/states/sensor.envirophat_temp')
if str(response.raise_for_status) == "<bound method Response.raise_for_status of <Response [404]>>":
    pi_temp = "??"
else:
    pi_temp = str(response.json()['state'])


line_one = time_readings + ". " + "Outside it's " + outside_temp + " " + u"\u00B0c."
line_two = "It feels like " + feels_like + " " + u"\u00B0c."
line_three = "Here it's " + papi_temp + " " + u"\u00B0c."
line_four = "Sunroom temp is " + pi_temp + " " + u"\u00B0c."
line_five = "Cloud cover is " + cloud_cover + "%."
line_six = "Rain Chance is " + rain_chance + "%."
line_seven = "Nearest squall is " + squall_dist + " km."


text.AddText(line_one,0,0)
text.AddText(line_two,0,25)
text.AddText(line_three,0,50)
text.AddText(line_four,0,75)
text.AddText(line_five,0,100)
text.AddText(line_six,0,125)
text.AddText(line_seven,0,150)

if in_between(datetime.now().time(), time(8), time(23)):
    text.WriteAll()

Fantastic, I will most definitely have a look at the code you posted to see how I can use it for my needs. I don’t really know Python but I can usually stumble along, especially if I can reference working examples like you have above. Thank you!

I thought you were onto something with the script not accepting the variables, but a quick test shows something like the following would work. However it doesn’t even attempt to run the script from what I can tell (I can manually run it with no parameters), Hass just fails the moment I add a template variable.

papirusupdate: /home/homeassistant/papirus.sh 1.1 2.2 3.3

My shell script just calls papirus-write via ssh:

NOW="`date +"%Y/%m/%d %H:%M:%S"`"
temp=$1
lights=$2
doors=$3
ssh pi3pap << E1
papirus-write $'Home Assistant Status\n\nOutdoor Temp: $temp\nLights On: $lights\nDoors Open: $doors\n\nLast update: $NOW'
E1

I would still be interested in finding a solution because I’m thinking of using this method with other scripts, but for Papirus I will certainly use the above script for now.

I should add that the script is executable and owned by homeassistant.

@Bit-River I’ve been having a more detailed look at your code and I do like how straight-forward it is to understand. If you don’t mind I’d like to ask a couple of clarification questions please.

What type of sensor do you use to report back on time? What does sensor.kells look like in Hass?

How does the in_between / time function work, what do the values mean?

in_between(datetime.now().time(), time(8), time(23)):

Thanks!

Ah so if it’s not that, then it might be quotation marks / single quotes that are needed.

Perhaps this version of the line.

papirusupdate: "/home/homeassistant/papirus.sh {{ states.sensor.netatmo_outdoor1_temperature.state }} {{ states.sensor.lights_on.state }} {{ states.sensor.doors_open.state }}"

If that doesn’t work, try underscores between the template values instead of spaces. After that, I’m afraid I’m out of ideas.

Also here’s the rest api I use on the Papirus, so it can send its temperature back to Hass:

from flask import Flask, jsonify, make_response
from subprocess import PIPE, Popen
from papirus import LM75B

app = Flask(__name__)

def get_cpu_temperature():
    process = Popen(['vcgencmd', 'measure_temp'], stdout=PIPE)
    output, _error = process.communicate()
    return float(output[output.index('=') + 1:output.rindex("'")])


@app.route('/pi/api/v1.0/temp', methods=['GET'])
def get_temp():
        sensor = LM75B()
        temp_c = '{c:.2f}'.format(c=sensor.getTempCFloat())
        temp_c = float(temp_c)
        cpu_temp_c = get_cpu_temperature()
        temp_c_cal = temp_c - ((cpu_temp_c-temp_c)/1.99)
        temp_c_cal = round(temp_c_cal, 1)
        return (str(temp_c_cal))

@app.errorhandler(404)
def not_found(error):
    return make_response(jsonify({'error': 'Not found'}), 404)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=False)

And a sensors.yaml entry to go with it:

  - platform: rest
    resource: http://[Your Papirus IP here]:5000/pi/api/v1.0/temp
    name: papirus_temp
    unit_of_measurement: "°C"

  - platform: statistics
    entity_id: sensor.papirus_temp
    name: stats_papirus_temp
    sampling_size: 20

Ah, sensor.kells is the time sensor. It will be “sensor.[what you called your home in HA]” that you set in your sensors.yaml - looks like this:

  - platform: worldclock
    time_zone: America/Seattle
    name: [whatever you named it]

The line:

if in_between(datetime.now().time(), time(8), time(23)):
    text.WriteAll()

means that the display will be updated from 8am to 11pm - outside of those times the display won’t be written to. Saves a bit of wear & tear on the e-ink when no one will be looking at it anyway.

Change to whatever values suit, or just comment out the if statement and leave the text.WriteAll() part to run the display all the time.

Superb, thank you!

Also thanks for the quote suggestions. I thought a single quote might have worked as that’s what my pushover_glance shell_command uses, but unfortunately not. I also tried a single template with no luck either. Ah well, thanks for the suggestions, I’ll just focus on the Papirus code for now.

Enjoy!

I wouldn’t be without mine, perfect for outdoors too.