Shell_command running into timeout

Hey @Burningstone ,
thanks for the quick reply.

Hmm, I have to gray out a lot :slight_smile:, but I will take a look into.

Like I said, the script is running until it is terminated by the shell_command as I can see in the script log.

Attention, this is no master piece of python programming, but it worked perfectly for my needs.
I also left out some parts, that are not necessary for debugging.

#!/usr/bin/python3

## Imports
import os, sys, time
import json, requests

## wakeonlan
import struct, socket

## PID handling
import psutil

## Logging konfigurieren
import logging

logger = **defined**

headers = {
    "Authorization": "Bearer xxx",
    "content-type": "application/json",
}

## Funktionen definieren
# pingcheck
def check_ping(host):
    ping = os.system("ping -c 1 " + host + " > /dev/null")
    return(ping)

# Funktionen fuer die unterschiedlichen Geraete
def get(endpoint, entity):
    url = "http://localhost:8123/api/" + endpoint + "/" + entity
    response = requests.get(url, headers=headers, timeout=5)
    obj = json.loads(response.text)
    try:
        return(str(obj['state']))
    except KeyError:
        return("Da stimmt etwas nicht. Existiert die entity?")

def post(endpoint, service, entity_type, entity):
    url = "http://localhost:8123/api/" + endpoint + "/" + service + "/" + entity_type
    data = '{"entity_id": "' + entity + '"}'
    requests.post(url, headers=headers, data=data, timeout=5)

def getPowerStatus():
    url = 'http://ip-of-receiver:10000/sony/system'
    data = '{"method":"getPowerStatus","id":55,"params":[],"version":"1.1"}'
    try:
        response = requests.post(url, headers=headers, data=data, timeout=5)
        obj = json.loads(response.text)
        status = obj['result'][0]['status']
    except KeyError:
        status = 'false'
        logger.error("KeyError in getPowerStatus")
    except OSError:
        status = 'false'
        logger.error("OSError in getPowerStatus")
    return status

def setPowerStatus(status):
    url = 'http://ip-of-receiver:10000/sony/system'
    data = '{"method":"setPowerStatus","id":55,"params":[{"status":"' + status + '"}],"version":"1.1"}'
    try:
        requests.post(url, headers=headers, data=data, timeout=5)
    except KeyError:
        status = 'false'
        logger.error("KeyError in setPowerStatus")
    except OSError:
        status = 'false'
        logger.error("OSError in setPowerStatus")

def setPlayContent(command):
    try:
        url = 'http://ip-of-receiver:10000/sony/avContent'
        data = '{"method":"setPlayContent","id":47,"params":[{"uri":"' + command + '"}],"version":"1.2"}'
        requests.post(url, headers=headers, data=data, timeout=5)
    except Error as e:
        logger.error(e)

def setVolume(value):
    url = 'http://ip-of-receiver:10000/sony/audio'
    data = '{"method":"setAudioVolume","id":98,"params":[{"volume":"' + value + '","output":""}],"version":"1.1"}'
    requests.post(url, headers=headers, data=data, timeout=5)

# radio
def radio():
    logger.debug("Anfang def radio")

    radioready = False
    standbycounter = 0

    # check Sony TV status
    if get('states', 'media_player.sony_bravia_tv') == 'on':
        logger.debug("def radio - media_player für TV ausgeschaltet")
        post('services', 'media_player', 'turn_off', 'media_player.sony_bravia_tv')
        time.sleep(1)

    # check Steckdosenstatus vom Sony Receiver
    if get('states', 'switch.wohnzimmer_onkyo') == 'off':
        post('services', 'switch', 'turn_on', 'switch.wohnzimmer_onkyo')
        logger.debug("def radio - hass Schalter für Receiver ein geschaltet")
        time.sleep(1)

    ## wenn der Strom an ist, prüfe auf IP
    while check_ping('ip-of-receiver') > 0:
        logger.debug("def radio - in der ping Schleife, warten bis IP erreichbar")
        time.sleep(2)

    if check_ping(ip-of-receiver') == 0:
        logger.debug("def radio - checkping positiv")
    else:
        logger.debug("def radio - checkping negativ")

    logger.debug("def radio - vor der radioready Schleife")
    while radioready == False:
        if getPowerStatus() == 'standby' and standbycounter <= 10:
            standbycounter = standbycounter + 1
            time.sleep(2)
        elif getPowerStatus() == 'standby' and standbycounter >= 10:
            setPowerStatus('active')
            radioready = True
        elif getPowerStatus() == 'active':
            radioready = True

    # wechsle  zu Radio
    if radioready == True:
        setPlayContent('radio:fm')
        time.sleep(1)
        setVolume('13')
    else:
        logger.debug("def radio - irgendetwas ist am Ende schief gelaufen")

    # check Steckdosenstatus vom Sony TV
    if get('states', 'switch.wohnzimmer_tv') == 'on':
        post('services', 'switch', 'turn_off', 'switch.wohnzimmer_tv')
        logger.debug("def radio - hass Schalter für TV aus geschaltet")
        time.sleep(2)

    logger.debug("Ende def radio")

# prüfe ob das Skript bereits läuft, anhand der PID
## PID Variablen
pidfile = "/tmp/wzmultimedia.pid"
if os.path.isfile(pidfile):
    logger.warn("%s existiert bereits..." % pidfile)
    file = open(pidfile, 'r')
    pid = file.read()
    file.close()
    if psutil.pid_exists(pid):
        logger.warn("ein Prozess mit der pid: %d läuft ebenfalls, breche ab.")
        sys.exit()
    else:
        print("Ein Prozess mit der pid %d existiert nicht, lösche %s.")
        os.remove(pidfile)

pid = str(os.getpid())
open(pidfile, 'w').write(pid)

## Start der Ausführung
try:
    if len(sys.argv) <= 1:
        logger.error("Da wurde kein Parameter übergeben.")
        sys.exit()
    elif sys.argv[1] == 'radio':
        radio()
    elif sys.argv[1] == 'tv':
        tv()
    elif sys.argv[1] == 'aus':
        aus()
    else:
        logger.error("Irgendwie konnte kein Parameter zugeordnet werden.")
        logger.error(sys.argv)
except Exception as e:
    logger.error(e)
    os.unlink(pidfile)
    logger.info("## Skript ist mit Exception ausgestiegen.")
finally:
    os.unlink(pidfile)
    logger.info("## Skript ist zuende ##")

I didn’t read the whole script, but why are you doing all this from a python script and not with a HA script? Seems overly complicated what you do.

I already used this, before I got HA.
And there are some things I have to workaround when using ha_script, like the sony api requests. Sony HA integrations are not that straight forward, when disconnecting multimedia devices from power :-).

Aaaand, the script is ready and working. The only thing that blocks it using with a shell_command is this 60s timeout. I think after 90s or so, the script is also done, but shell_command terminates it to early.

Several versions ago the “feature” was added to stop running after 1 minute. It didn’t previously have a limit. This arbitrary feature never made sense to me, and it aborts my snapshot file sync script mid file copy if multiple snapshots are pending sync. I’ve been quite mad about it but hadn’t spoken up about it yet. A timeout duration override would be an acceptable compromise, but I don’t think that was implemented.

1 Like

I just opened a feature request :slight_smile:

We ll see if it helps.

1 Like

I don’t think they plan to change anything about it

see

my discussion

Hey @blavak68,

thanks for the link. I replied that post with my feature request :slight_smile:

Home Assistant is not created for managing long-running external processes.

I absolutely aggre, but I think we dont want to run 30 minute scripts.
Like for my case, I have to “power on” and ping a device until it is available. Depending on the device this could last more than 60 seconds.
I just thought about another option for “shell_command”, to tell it not to wait for result, but start the external task and exit the home-assistant internal process, without touching the external process.

Just think about extending the feature request :-).

Currently the only way to reach my goal is, to split up my external script into parts and run this snippets through home-assistant internal script engine :crazy_face:

Hi all
I found one solution to our problem
when use addon “SSH & Web Terminal”
we can our “long” shell command call

Executing commands in this add-on using a Home Assistant service call

This add-on uses the hassio.addon_stdin service to expose a shell interface to Home Assistant. This allows you to execute commands and scripts within the SSH & Web Terminal add-on, straight from Home Assistant.

This is particularly helpful when you want to execute custom scripts or commands from automations.

Example automation running my_command :

automation:
  - alias: "Example my script"
    trigger:
      platform: state
      entity_id: binary_sensor.motion_sensor
      to: "ON"
    action:
      service: hassio.addon_stdin
      data:
        addon: a0d7b954_ssh
        input: "/config/scripts/my_command"
1 Like

Hello @blavak68

nice solution but i have no “scripts” folder under config…dont understand what to do, can you help please?

Another workaround is to run the script in the background. AFAIK this doesn’t actually run the script in background (since there is no bg/fg), but it does cause the script to ignore SIGINT and keep chugging.

shell_command:
   long_script: /script_that_runs_more_than_60_seconds.sh &

Hass will still report timeout, but the script will execute until it’s done. Not perfect, but enough for my use case.

1 Like

shameless plug :

I had the same needs to run long running commands from Home Assistant.
This runs a command as a daemon (no timeout) and reports the status back on an entity’s attributes

If you are wanting to run a command in the background while avoiding the shell command timeout, one may want to try:
your_command > /dev/null 2>&1 &

When I run a python program xxxx.py in the background, I use the following which launches the python program in the background and quickly returns control back to HA without incurring the timeout log error.

/usr/bin/env nohup python3 /config/shell_commands/xxxx.py > /dev/null 2>&1 &
3 Likes

That works like a charm without running into timeouts anymore, thanks a bunch mate!

Before I try that to cure my problem of a shell script timing out and crashing the automation midstream, can you tell me what that does. I don’t know any Unix.

Given that a shell is being used to run a shell command/script, the “&” at the end tells the shell that it is run the command/script in a separate (and newly created) process which frees up the shell to do other things. The “> /dev/null 2>&1” means that if the command/script tries to send output to the display or if errors are to printed out, they are instead blocked and not printed out.

Thanks @wmaker

I’ve tested it where

command=touch /tmp/timeout && sleep 70 && touch /tmp/timeout2

but I still see an error raised

Timed out running command: `/usr/bin/env nohup ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' [email protected] '{{ command }}' > /dev/null 2>&1 &`, after: 60s

12:00:07 AM – (ERROR) Shell Command

The command itself runs to completion with the 70 second wait
In hindsight I think I need to make the nested command run in the background too ? <— Nope wasn’t that - I still see an error but the command completes

better to modify the Shell command pyton script in /usr/local/lib/python3.xx/dist-packages/homeassistant/components/shell_command/init.py

change timeout from 60 to 120 or 240 depending how long you want to keep it live.

Will this patch survive an upgrade ?

nop! you have to modify same file after upgrade as home assistant repolace all components prior to upgrade