[SOLVED] Executing a python script from a rule - not working

I’m trying to put together a setup to control a wifi enabled thermostat and I’m using the base code from this post with a few minor modifications.

Part of it is a python script that is executed from a rule. I have all the scripts saved in /etc/openhab2/scripts, all the scripts are executable, I gave the openhab user ownership of the scripts, and I even went so far as to give open 777 permissions on the files. The openhab user is also part of the sudo group. I think, at the very least, the permission side of things is taken care of.

There are 2 main scripts; ‘therm.py’ which performs the actual communications with the server and ‘tstat’ which calls on therm.py to pull information from the server and update the OH items. I know the script works as for as communicating with the server and making the appropriate changes because I can manually run the scripts from a terminal and everything works. It will change the temperature set point, switch between cool and heat, cancel holds, etc. The script will also return a result to let me know that it was successful or if it failed. However, when I attempt to execute the python script via a Rule, the logs show me that it executed the script but it does not show any return information and nothing changes on the server. To make things more confusing, I can call on the ‘tstat’ script from a rule to pull the information from the server and it runs correctly, returns the values, and updates the items without a problem. It is only when I call the python script directly by itself that it doesn’t work.

Any insight into what I might be missing? Would it be easier just to create a bash script to call the python script like the ‘tstat’ script?

Items

Number ACMode "A/C System Mode [MAP(acstatus.map)]"
Number ACInsideTemp "Inside Temp [%.0f °F]" <temperature> (persist)
Number ACCoolSetPoint "Cool Set Point [%.0f °F]" (persist)
Number ACHeatSetPoint "Heat Set Point [%.0f °F]" (persist)
Number ACSetPoint "Set Temp [%.0f °F]" (persist)
Number ACHoldTime "Hold Until [MAP(holdtime.map):%s]"
Number ACCoolHoldStatus "[MAP(achold.map):%s]"
Number ACHeatHoldStatus "[MAP(achold.map):%s]"
Switch ACCancelHold "Cancel Hold"

Rules

// Send Setpoint
rule "Send SetPoint to MyTotalConnectComfort Portal"
when
    Item ACSetPoint received command
then
        if ( ACMode.state==3) {
                executeCommandLine("python /etc/openhab2/scripts/therm.py -c " + receivedCommand)
        }
else
        executeCommandLine("sudo /etc/openhab2/scripts/therm.py -h " + receivedCommand)
end

//Update thermostat after update is sent
rule "Sync after update"
when
        Item ACSetPoint received command
then
        Thread::sleep(10000)
        executeCommandLine("/etc/openhab2/scripts/tstat")
end

tstat File

#!/bin/bash
#
# Script by Jamie R. Cid
# jay@cidcomm.com
#
#Get data from MyTotalControl and send to OH. This grabs one copy of the data and stores it instead of doing it for every request below. That's how you get blocked ;)
echo Getting Data from MyTotalControl Website
/etc/openhab2/scripts/therm.py -s > /var/log/honeywell.log
TEMPDATA="/var/log/honeywell.log"
#
#
echo Checking AC Mode
MODESTATUS=$( grep "System Mode" $TEMPDATA | sed -n -e 's/^.*System Mode: //p')
curl --header "Content-Type: text/plain" -X POST  "http://192.168.0.8:8080/rest/items/ACMode"  -d "$MODESTATUS"
echo $MODESTATUS
#
echo Checking Indoor Temp
TEMPSTATUS=$( grep "Indoor Temperature" $TEMPDATA | sed -n -e 's/^.*Indoor Temperature: //p')
curl --header "Content-Type: text/plain" -X POST  "http://192.168.0.8:8080/rest/items/ACInsideTemp"  -d "$TEMPSTATUS"
echo $TEMPSTATUS
#
echo Checking Cool Setpoint
COOLSTATUS=$( grep "Cool Setpoint" $TEMPDATA | sed -n -e 's/^.*Cool Setpoint: //p')
curl --header "Content-Type: text/plain" -X POST "http://192.168.0.8:8080/rest/items/ACCoolSetPoint" -d "$COOLSTATUS"
echo $COOLSTATUS
#
echo Checking Heat Setpoint
HEATSTATUS=$( grep "Heat Setpoint" $TEMPDATA | sed -n -e 's/^.*Heat Setpoint: //p')
curl --header "Content-Type: text/plain" -X POST "http://192.168.0.8:8080/rest/items/ACHeatSetPoint" -d "$HEATSTATUS"
echo $HEATSTATUS
#
echo Checking Cool Hold Status
COOLHOLDSTATUS=$( grep "Status Cool" $TEMPDATA | sed -n -e 's/^.*Status Cool: //p')
curl --header "Content-Type: text/plain" -X POST "http://192.168.0.8:8080/rest/items/ACCoolHoldStatus" -d "$COOLHOLDSTATUS"
echo $COOLHOLDSTATUS
#
echo Checking Heat Hold Status
HEATHOLDSTATUS=$( grep "Status Heat" $TEMPDATA | sed -n -e 's/^.*Status Heat: //p')
curl --header "Content-Type: text/plain" -X POST "http://192.168.0.8:8080/rest/items/ACHeatHoldStatus" -d "$HEATHOLDSTATUS"
echo $HEATHOLDSTATUS
#
echo Checking Hold Time
HEATHOLDSTATUS=$( grep "Hold Until" $TEMPDATA | sed -n -e 's/^.*Hold Until : //p')
curl --header "Content-Type: text/plain" -X POST "http://192.168.0.8:8080/rest/items/ACHoldTime" -d "$HEATHOLDSTATUS"
echo $HEATHOLDSTATUS

#
echo Syncing Temperature Setpoint
MODECHECK=$( grep "System Mode" $TEMPDATA | sed -n -e 's/^.*System Mode: //p')
echo $MODECHECK
if [ "$MODECHECK" = "3" ];
then
        curl --header "Content-Type: text/plain" -X PUT "http://192.168.0.8:8080/rest/items/ACSetPoint/state" -d "$COOLSTATUS"
else
        curl --header "Content-Type: text/plain" -X PUT "http://192.168.0.8:8080/rest/items/ACSetPoint/state" -d "$HEATSTATUS"
fi

#
#
exit 0

therm.py

#!/usr/bin/python

# By Brad Goodman
# http://www.bradgoodman.com/
# brad@bradgoodman.com

####################### Fill in settings below #######################

USERNAME="my user name"
PASSWORD="my pass"
DEVICE_ID=my device ID

############################ End settings ############################

import urllib2
import urllib
import json
import datetime
import re
import time
import math
import base64
import time
import httplib
import sys
import getopt
import os
import stat
import subprocess
import string
    
AUTH="https://mytotalconnectcomfort.com/portal"

cookiere=re.compile('\s*([^=]+)\s*=\s*([^;]*)\s*')

def client_cookies(cookiestr,container):
  if not container: container={}
  toks=re.split(';|,',cookiestr)
  for t in toks:
    k=None
    v=None
    m=cookiere.search(t)
    if m:
      k=m.group(1)
      v=m.group(2)
      if (k in ['path','Path','HttpOnly']):
        k=None
        v=None
    if k: 
      #print k,v
      container[k]=v
  return container

def export_cookiejar(jar):
  s=""
  for x in jar:
    s+='%s=%s;' % (x,jar[x])
  return s



def get_login(action, value=None):
    
    cookiejar=None
    #print
    #print
    #print "Run at ",datetime.datetime.now()
    headers={"Content-Type":"application/x-www-form-urlencoded",
            "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Encoding":"sdch",
            "Host":"mytotalconnectcomfort.com",
            "DNT":"1",
            "Origin":"https://mytotalconnectcomfort.com/portal",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    conn.request("GET", "/portal/",None,headers)
    r0 = conn.getresponse()
    #print r0.status, r0.reason
    
    for x in r0.getheaders():
      (n,v) = x
      #print "R0 HEADER",n,v
      if (n.lower() == "set-cookie"): 
        cookiejar=client_cookies(v,cookiejar)
    #cookiejar = r0.getheader("Set-Cookie")
    location = r0.getheader("Location")

    retries=5
    params=urllib.urlencode({"timeOffset":"240",
        "UserName":USERNAME,
        "Password":PASSWORD,
        "RememberMe":"false"})
    #print params
    newcookie=export_cookiejar(cookiejar)
    #print "Cookiejar now",newcookie
    headers={"Content-Type":"application/x-www-form-urlencoded",
            "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Encoding":"sdch",
            "Host":"mytotalconnectcomfort.com",
            "DNT":"1",
            "Origin":"https://mytotalconnectcomfort.com/portal/",
            "Cookie":newcookie,
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    conn.request("POST", "/portal/",params,headers)
    r1 = conn.getresponse()
    #print r1.status, r1.reason
    
    for x in r1.getheaders():
      (n,v) = x
      #print "GOT2 HEADER",n,v
      if (n.lower() == "set-cookie"): 
        cookiejar=client_cookies(v,cookiejar)
    cookie=export_cookiejar(cookiejar)
    #print "Cookiejar now",cookie
    location = r1.getheader("Location")

    if ((location == None) or (r1.status != 302)):
        #raise BaseException("Login fail" )
        print("ErrorNever got redirect on initial login  status={0} {1}".format(r1.status,r1.reason))
        return


   # Skip second query - just go directly to our device_id, rather than letting it
    # redirect us to it. 

    code=str(DEVICE_ID)

    t = datetime.datetime.now()
    utc_seconds = (time.mktime(t.timetuple()))
    utc_seconds = int(utc_seconds*1000)
    #print "Code ",code

    location="/portal/Device/CheckDataSession/"+code+"?_="+str(utc_seconds)
    #print "THIRD"
    headers={
            "Accept":"*/*",
            "DNT":"1",
            #"Accept-Encoding":"gzip,deflate,sdch",
            "Accept-Encoding":"plain",
            "Cache-Control":"max-age=0",
            "Accept-Language":"en-US,en,q=0.8",
            "Connection":"keep-alive",
            "Host":"mytotalconnectcomfort.com",
            "Referer":"https://mytotalconnectcomfort.com/portal/",
            "X-Requested-With":"XMLHttpRequest",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
            "Cookie":cookie
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    conn.set_debuglevel(999);
    #print "LOCATION R3 is",location
    conn.request("GET", location,None,headers)
    r3 = conn.getresponse()
    if (r3.status != 200):
      print("Error Didn't get 200 status on R3 status={0} {1}".format(r3.status,r3.reason))
      return


    # Print thermostat information returned

    if (action == "status"):
    
        print r3.status, r3.reason
        rawdata=r3.read()
        j = json.loads(rawdata)
        #print "R3 Dump"
        #print json.dumps(j,indent=2)
        #print json.dumps(j,sort_keys=True,indent=4, separators=(',', ': '))
        #print "Success:",j['success']
        #print "Live",j['deviceLive']
	print "System Mode:",j['latestData']['uiData']["SystemSwitchPosition"]
        print "Indoor Temperature:",j['latestData']['uiData']["DispTemperature"]
        print "Indoor Humidity:",j['latestData']['uiData']["IndoorHumidity"]
        print "Cool Setpoint:",j['latestData']['uiData']["CoolSetpoint"]
        print "Heat Setpoint:",j['latestData']['uiData']["HeatSetpoint"]
        print "Hold Until :",j['latestData']['uiData']["TemporaryHoldUntilTime"]
        print "Status Cool:",j['latestData']['uiData']["StatusCool"]
        print "Status Heat:",j['latestData']['uiData']["StatusHeat"]
        print "Status Fan:",j['latestData']['fanData']["fanMode"]
        return

    headers={
            "Accept":'application/json; q=0.01',
            "DNT":"1",
            "Accept-Encoding":"gzip,deflate,sdch",
            'Content-Type':'application/json; charset=UTF-8',
            "Cache-Control":"max-age=0",
            "Accept-Language":"en-US,en,q=0.8",
            "Connection":"keep-alive",
            "Host":"mytotalconnectcomfort.com",
            "Referer":"https://mytotalconnectcomfort.com/portal/",
            "X-Requested-With":"XMLHttpRequest",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
            'Referer':"/TotalConnectComfort/Device/CheckDataSession/"+code,
            "Cookie":cookie
        }


    # Data structure with data we will send back

    payload = {
        "CoolNextPeriod": None,
        "CoolSetpoint": None,
        "DeviceID": DEVICE_ID,
        "FanMode": None,
        "HeatNextPeriod": None,
        "HeatSetpoint": None,
        "StatusCool": 0,
        "StatusHeat": 0,
        "SystemSwitch": None
    }


    # Calculate the hold time for cooling/heating

#    t = datetime.datetime.now();

#    stop_time = ((t.hour+hold_time)%24) * 60 + t.minute
#    stop_time = stop_time/15


    # Modify payload based on user input

    if (action == "cool"):
      payload["CoolSetpoint"] = value
      payload["StatusCool"] = 1
      payload["StatusHeat"] = 1
      payload["CoolNextPeriod"] = 99
    
    if (action == "heat"):
      payload["HeatSetpoint"] = value
      payload["StatusCool"] = 1
      payload["StatusHeat"] = 1
      payload["HeatNextPeriod"] = 'null'

    if (action == "cancel"):
      payload["StatusCool"] = 0
      payload["StatusHeat"] = 0

    if (action == "fan"):
      payload["FanMode"] = value


    # Prep and send payload

    location="/portal/Device/SubmitControlScreenChanges"

    rawj=json.dumps(payload)
        
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com");
    conn.set_debuglevel(999);
    #print "R4 will send"
    #print rawj
    conn.request("POST", location,rawj,headers)
    r4 = conn.getresponse()
    if (r4.status != 200): 
      print("Error Didn't get 200 status on R4 status={0} {1}".format(r4.status,r4.reason))
      return
    else:
        print "Success in configuring thermostat!"
    #  print "R4 got 200"


def printUsage():
    print
    print "Cooling: -c temperature -t hold_time"
    print "Heating: -h temperature -t hold_time"
    print "Status: -s"
    print "Cancel: -x"
    print "Fan: -f [0=auto|1=on]"
    print
    print "Example: Set temperature to cool to 80f for 1 hour: \n\t therm.py -c 80 -t 1"
    print
    print "If no -t hold_time is provided, it will default to one hour from command time."
    print
    
    
def main():

    if sys.argv[1] == "-s":
        get_login("status")
        sys.exit()

    if sys.argv[1] == "-x":
        get_login("cancel")
        sys.exit()        
        
    if (len(sys.argv) < 3) or (sys.argv[1] == "-help"):
        printUsage()
        sys.exit()
        
    if sys.argv[1] == "-c":
        get_login("cool", sys.argv[2])
        sys.exit()

    if sys.argv[1] == "-h":
        get_login("heat", sys.argv[2])
        sys.exit()

    if sys.argv[1] == "-f":
        get_login("fan", sys.argv[2])
        sys.exit()        
        
if __name__ == "__main__":
    

############################ End settings ############################

 import urllib2
 import urllib
 import json
 import datetime
 import re
 import time
 import math
 import base64
 import time
 import httplib
 import sys
 import getopt
 import os
 import stat
 import subprocess
 import string
    
AUTH="https://mytotalconnectcomfort.com/portal"

cookiere=re.compile('\s*([^=]+)\s*=\s*([^;]*)\s*')

def client_cookies(cookiestr,container):
  if not container: container={}
  toks=re.split(';|,',cookiestr)
  for t in toks:
    k=None
    v=None
    m=cookiere.search(t)
    if m:
      k=m.group(1)
      v=m.group(2)
      if (k in ['path','Path','HttpOnly']):
        k=None
        v=None
    if k: 
      #print k,v
      container[k]=v
  return container

def export_cookiejar(jar):
  s=""
  for x in jar:
    s+='%s=%s;' % (x,jar[x])
  return s



def get_login(action, value=None, hold_time=0):
    
    cookiejar=None
    #print
    #print
    #print "Run at ",datetime.datetime.now()
    headers={"Content-Type":"application/x-www-form-urlencoded",
            "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Encoding":"sdch",
            "Host":"mytotalconnectcomfort.com",
            "DNT":"1",
            "Origin":"https://mytotalconnectcomfort.com/portal",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    conn.request("GET", "/portal/",None,headers)
    r0 = conn.getresponse()
    #print r0.status, r0.reason
    
    for x in r0.getheaders():
      (n,v) = x
      #print "R0 HEADER",n,v
      if (n.lower() == "set-cookie"): 
        cookiejar=client_cookies(v,cookiejar)
    #cookiejar = r0.getheader("Set-Cookie")
    location = r0.getheader("Location")

    retries=5
    params=urllib.urlencode({"timeOffset":"240",
        "UserName":USERNAME,
        "Password":PASSWORD,
        "RememberMe":"false"})
    #print params
    newcookie=export_cookiejar(cookiejar)
    #print "Cookiejar now",newcookie
    headers={"Content-Type":"application/x-www-form-urlencoded",
            "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
            "Accept-Encoding":"sdch",
            "Host":"mytotalconnectcomfort.com",
            "DNT":"1",
            "Origin":"https://mytotalconnectcomfort.com/portal/",
            "Cookie":newcookie,
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36"
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    conn.request("POST", "/portal/",params,headers)
    r1 = conn.getresponse()
    #print r1.status, r1.reason
    
    for x in r1.getheaders():
      (n,v) = x
      #print "GOT2 HEADER",n,v
      if (n.lower() == "set-cookie"): 
        cookiejar=client_cookies(v,cookiejar)
    cookie=export_cookiejar(cookiejar)
    #print "Cookiejar now",cookie
    location = r1.getheader("Location")

    if ((location == None) or (r1.status != 302)):
        #raise BaseException("Login fail" )
        print("ErrorNever got redirect on initial login  status={0} {1}".format(r1.status,r1.reason))
        return


   # Skip second query - just go directly to our device_id, rather than letting it
    # redirect us to it. 

    code=str(DEVICE_ID)

    t = datetime.datetime.now()
    utc_seconds = (time.mktime(t.timetuple()))
    utc_seconds = int(utc_seconds*1000)
    #print "Code ",code

    location="/portal/Device/CheckDataSession/"+code+"?_="+str(utc_seconds)
    #print "THIRD"
    headers={
            "Accept":"*/*",
            "DNT":"1",
            #"Accept-Encoding":"gzip,deflate,sdch",
            "Accept-Encoding":"plain",
            "Cache-Control":"max-age=0",
            "Accept-Language":"en-US,en,q=0.8",
            "Connection":"keep-alive",
            "Host":"mytotalconnectcomfort.com",
            "Referer":"https://mytotalconnectcomfort.com/portal/",
            "X-Requested-With":"XMLHttpRequest",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
            "Cookie":cookie
        }
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com")
    #conn.set_debuglevel(999);
    #print "LOCATION R3 is",location
    conn.request("GET", location,None,headers)
    r3 = conn.getresponse()
    if (r3.status != 200):
      print("Error Didn't get 200 status on R3 status={0} {1}".format(r3.status,r3.reason))
      return


    # Print thermostat information returned

    if (action == "status"):
    
        print r3.status, r3.reason
        rawdata=r3.read()
        j = json.loads(rawdata)
        #print "R3 Dump"
        #print json.dumps(j,indent=2)
        #print json.dumps(j,sort_keys=True,indent=4, separators=(',', ': '))
        #print "Success:",j['success']
        #print "Live",j['deviceLive']
	print "System Mode:",j['latestData']['uiData']["SystemSwitchPosition"]
        print "Indoor Temperature:",j['latestData']['uiData']["DispTemperature"]
        print "Indoor Humidity:",j['latestData']['uiData']["IndoorHumidity"]
        print "Cool Setpoint:",j['latestData']['uiData']["CoolSetpoint"]
        print "Heat Setpoint:",j['latestData']['uiData']["HeatSetpoint"]
        print "Hold Until :",j['latestData']['uiData']["TemporaryHoldUntilTime"]
        print "Status Cool:",j['latestData']['uiData']["StatusCool"]
        print "Status Heat:",j['latestData']['uiData']["StatusHeat"]
        print "Status Fan:",j['latestData']['fanData']["fanMode"]
        return
    
    headers={
            "Accept":'application/json; q=0.01',
            "DNT":"1",
            "Accept-Encoding":"gzip,deflate,sdch",
            'Content-Type':'application/json; charset=UTF-8',
            "Cache-Control":"max-age=0",
            "Accept-Language":"en-US,en,q=0.8",
            "Connection":"keep-alive",
            "Host":"mytotalconnectcomfort.com",
            "Referer":"https://mytotalconnectcomfort.com/portal/",
            "X-Requested-With":"XMLHttpRequest",
            "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36",
            'Referer':"/TotalConnectComfort/Device/CheckDataSession/"+code,
            "Cookie":cookie
        }


    # Data structure with data we will send back

    payload = {
        "CoolNextPeriod": None,
        "CoolSetpoint": None,
        "DeviceID": DEVICE_ID,
        "FanMode": None,
        "HeatNextPeriod": None,
        "HeatSetpoint": None,
        "StatusCool": 0,
        "StatusHeat": 0,
        "SystemSwitch": None
    }


    # Calculate the hold time for cooling/heating

    #t = datetime.datetime.now();

    #stop_time = ((t.hour+hold_time)%24) * 60 + t.minute
    #stop_time = stop_time/15


    # Modify payload based on user input

    if (action == "cool"):
      payload["CoolSetpoint"] = value
      payload["StatusCool"] = 1
      payload["StatusHeat"] = 1
      payload["CoolNextPeriod"] = 'null'
    
    if (action == "heat"):
      payload["HeatSetpoint"] = value
      payload["StatusCool"] = 1
      payload["StatusHeat"] = 1
      payload["HeatNextPeriod"] = 'null'

    if (action == "cancel"):
      payload["StatusCool"] = 0
      payload["StatusHeat"] = 0

    if (action == "fan"):
      payload["FanMode"] = value


    # Prep and send payload

    location="/portal/Device/SubmitControlScreenChanges"

    rawj=json.dumps(payload)
        
    conn = httplib.HTTPSConnection("mytotalconnectcomfort.com");
    #conn.set_debuglevel(999);
    #print "R4 will send"
    #print rawj
    conn.request("POST", location,rawj,headers)
    r4 = conn.getresponse()
    if (r4.status != 200): 
      print("Error Didn't get 200 status on R4 status={0} {1}".format(r4.status,r4.reason))
      return
    else:
        print "Success in configuring thermostat!"
    #  print "R4 got 200"


def printUsage():
    print
    print "Cooling: -c temperature -t hold_time"
    print "Heating: -h temperature -t hold_time"
    print "Status: -s"
    print "Cancel: -x"
    print "Fan: -f [0=auto|1=on]"
    print
    print "Example: Set temperature to cool to 80f for 1 hour: \n\t therm.py -c 80 -t 1"
    print
    print "If no -t hold_time is provided, it will default to one hour from command time."
    print
    
    
def main():

    if sys.argv[1] == "-s":
        get_login("status")
        sys.exit()

    if sys.argv[1] == "-x":
        get_login("cancel")
        sys.exit()        
        
    if (len(sys.argv) < 3) or (sys.argv[1] == "-help"):
        printUsage()
        sys.exit()
        
    if sys.argv[1] == "-c":
        get_login("cool", sys.argv[2])
        sys.exit()

    if sys.argv[1] == "-h":
        get_login("heat", sys.argv[2])
        sys.exit()

    if sys.argv[1] == "-f":
        get_login("fan", sys.argv[2])
        sys.exit()        
        
if __name__ == "__main__":
    main()


  • Platform information:
    • Hardware: Intel Atom x64 1.46GHz, 2G RAM, 64Gb SSD
    • OS: Ubuntu 18.04
    • Java Runtime Environment: OpenJDK Runtime Environment (Zulu 8.36.0.1-CA-linux64) (build 1.8.0_202-b05)
    • openHAB version: 2.4 release build

That folder is intended for Rules DSL scripts that can be called from Rules, not general scripts. In the past there have been problems with putting general purpose scripts into that folder because OH will go through all of it’s files and “fix” the permissions which includes removing the execute permission on all the files in that folder, since none of those files are expected to be executable.

I don’t think this is still a problem, but you should double check the permissions are correct.

As for your calls to executeCommandLine, you are using the version of executeCommandLine that does not wait for the script to complete before returning. As a result you never get any of the results it prints out, including error messages.

Change the call to

val result = executeCommandLine(<cmd>, 5000)

This will wait up to 5 seconds for the cmd to complete and put the results of the call into result. You can then log result and perhaps see the error message that is being printed out.

I think I have it working now and it ended up being several things.

1 - I moved all my scripts into my home folder and re-applied the permissions
2 - I updated the rules to include the timeout and write the results to the log as suggested
3 - With all the permissions I checked, I never ended up allowing the openhab user to use sudo without a password.

If I had to guess, the sudo nopassword option for the openhab user is what ultimately did the trick but I’m all for best practices.

Thanks, Rich!