}

Contents

Introduction

STUI scripts are a way to automate tasks you perform frequently.

Scripts are written in Python. If you are not familiar with python, I strongly recommend the python tutorial at Python's home. A knowledge of python's basics will make script writing much easier.

Once you have the basics of python, I recommend looking through the Scripting Tutorial. It offers many simple example scripts. Other scripts may be found in <tui_root>/STUI/Scripts.

Script Contents

A STUI script must be a text file whose name ends in ".py". The file must include either a ScriptClass class or a run function. I strongly suggest you use a ScriptClass for all but the very simplest of scripts.

ScriptClass

A class with the following special methods:

  • run(self, sr) (required): executed every time the script is run. "sr" is a ScriptRunner object which contains useful methods such as waitCmd.
  • __init__(self, sr): (optional): executed once, when the script is loaded. It is useful for initialization, creating GUI widgets, etc. It may NOT contain a "yield", and thus may not wait for anything.
  • end(self, sr): (optional): executed whenever the script (run(sr)) ends for any reason, whether it succeeded, failed or was cancelled. It may NOT contain a "yield", and thus may not wait for anything.

In addition, ScriptClass may contain any other methods and any variables you like. For examples, see the Scripting Tutorial.

Run Function

For very simple scripts, you may prefer to just define a run(sr) function. Your script may also contain init(sr) and/or end(sr) functions, and all work the same as the ScriptClass methods discussed above. However, if your script needs init and/or end, I strongly recommend writing it as a ScriptClass, so data can more easily be passed around.

Reporting Errors

To report an error, raise sr.ScriptError. For example:

def run(sr):
            ...
            if expTime <= 0:
                raise sr.ScriptError("Specify exposure time")
        

This will print the specified message to the status bar and halt the script. Other exceptions do this, as well, but also print a traceback to the error log. Thus other exceptions are ideal for internal bug checks, but sr.ScriptError is best for reporting "normal" errors.

ScriptRunner Reference

The run function (as well as init and end, if supplied) is passed a ScriptRunner object. This script runner provides support for your script, including:

  • getKeyVar(): return the current value of a keyword variable.
  • showMsg(): display a message on the status bar.
  • startCmd(): method
  • ScriptError: raise this exception to report an error to the status bar and halt the script. All other exceptions will do the same, but will also print a traceback to the error log (which is handy for debugging but annoying for normal use).
  • globals: a container for data you wish to pass between init, run and end functions.

The following methods wait, so a yield is required:

  • waitCmd(): start a command and wait for it to finish.
  • waitCmdVars(): wait for one or more command variables (returned by startCmd) to finish.
  • waitKeyVar(): get data from a keyword variable.
  • waitMS(): wait for a period of time specified in milliseconds.
  • waitThread(): run a function as a background thread and wait for completion.

getKeyVar(keyVar, ind, defVal)

Return the current value of a keyword variable.

Note: if you want to be sure the keyword data was in response to a particular command that you sent, then use the keyVars argument of startCmd or waitCmd, instead.

Inputs:

  • keyVar: the keyword variable of interest
  • ind: the index of the desired value. If None then the whole list of values is returned.
  • defVal: the default value to return if the keyVar's data is invalid (this can happen if it was not supplied by the hub since the last time you connected). If defVar is omitted then the script fails if the keyVar's data is invalid.

A keyword variable contains data read from an instrument, the telescope or other "actor". Although you can create your own keyword variables, you rarely have to do so. Every actor has an associated model in STUI containing most or all of the useful keyword variables for that device.

Every keyword variable contains a list of 0 or more values. Often you only want one particular value. Thus the "ind" argument.

See also waitKeyVar, which can wait for a value.

showMsg(msg, severity=RO.Constants.sevNormal)

Display a message on the status bar.

Inputs:

  • msg: string to display, without a final "\n"
  • severity: one of RO.Constants.sevNormal (default), sevWarning or sevError

Note that the status bar also shows the execution status of the script; for instance it says "Done" when the script finishes successfully. If you want more permanent output, consider adding a text or label widget and writing your message to that.

startCmd(
            actor = "",
            cmdStr = "",
            timeLim = 0,
            callFunc = None,
            callCodes = opscore.actor.DoneCodes,
            timeLimKeyword = None,
            abortCmdStr = None,
            keyVars = None,
            checkFail = True,
        )
        

Start a command using the same arguments as waitCmd.

Returns a command variable that you can wait for using waitCmdVars.

waitCmd(
            actor="",
            cmdStr = "",
            timeLim = 0,
            callFunc=None,
            callCodes = opscore.actor.DoneCodes,
            timeLimKeyword = None,
            abortCmdStr = None,
            keyVars = None,
            checkFail = True,
        )
        

Start a command and wait for it to finish. A yield is required. If you want to read any data that must only be in response to this command and no other then be sure to use the keyVars argument.

Inputs:

  • actor: the name of the device which issued the keyword
  • cmdStr: the command; no terminating \n wanted
  • timeLim: maximum time before command expires, in sec; 0 for no limit (which is normally what you want).
  • callFunc: rarely needed; see opscore.actor.CmdVar for details
  • callCodes: rarely needed; see opscore.actor.CmdVar for details
  • timeLimKeyword: rarely needed; see opscore.actor.CmdVar for details
  • abortCmdStr: a command string that will abort the command. This string is sent to the actor if the command is aborted, e.g. if the script is cancelled while the command is executing.
  • keyVars: a sequence of 0 or more keyword variables to monitor. Any data for those variables that arrives in response to this command is saved and can be retrieved using cmdVar.getKeyVarData or cmdVar.getLastKeyVarData, where cmdVar is returned in sr.value.
  • checkFail: check for command failure? If True (the default) command failure will halt your script.

See also startCmd and waitCmdVars.

waitCmdVars(cmdVars)

Wait for one or more command variables (returned by startCmd) to finish. A yield is required.

Inputs:

  • cmdVars: one or more commands (opscore.actor.CmdVar)

Returns successfully once all commands succeed, but fails as soon as any command fails.

See also startCmd and waitCmd.

waitKeyVar(keyVar, ind=0, defVal=Exception, waitNext=False)

Get data from a keyword variable. A yield is required.

Inputs:

  • keyVar keyword variable
  • ind which value is wanted? (None for all values)
  • defVal value to return if value cannot be determined (if omitted, the script halts)
  • waitNext if True, ignores the current value and waits for the next transition.

The data is returned in sr.value.

If the value is currently unknown or if waitNext is true, wait for the variable to be updated.

See also getKeyVar (which does not wait).

waitMS(msec)

Wait for a period of time specified in milliseconds. A yield is required.

Inputs:

  • msec number of milliseconds to pause

waitThread(func, *args, **kargs)

Run a function as a background thread and wait for completion. A yield is required.

Inputs:

  • func: the function to run as a background thread
  • any additional arguments are passed to func

Any value returned by func is put into sr.value.

Warning: func must NOT interact with Tkinter widgets or variables (not even reading them) because Tkinter is not thread-safe. (The only thing I'm sure a background thread can safely do with Tkinter is generate an event, a technique that is used to detect end of thread).

For more information, see opscore.actor.ScriptRunner.

Yield When You Wait

Whenever your script calls an sr.wait... function (i.e. wants to wait for anything), it must use yield, as in:

	yield sr.wait...(...)
        

This is a painful, but it could be much worse. Most languages would force you to break code into many small functions, each of which is registered as a separate callback function. That sort of programming is fine for GUIs (and is used extensively in STUI), but it is not a nice way to write a script.

If you forget the "yield", your script will plow ahead instead of waiting, which is a recipe for trouble. However, STUI will catch this problem the next time you use an sr.wait...() function, at which point it will kill your script, print a message to the status bar and print details to the error log.

Calling Sub-Tasks

Repetitive tasks can be separated out into sub-tasks. If you find your script running the same bit of code more than once, you may want to move that bit into a separate function.

To execute a sub-task that contains yield you must use yield. To remind yourself, I suggest using a name that starts with "wait". For example:

        class ScriptClass(object):
            def run(self, sr):
                #...
                yield self.waitDumbTask(sr, 3, 1000)
                #...
            
            def waitDumbTask(self, sr, nreps, dtime):
                for n in range(nreps):
                    sr.showMsg("Wait %d" % n)
                    yield sr.waitMS(dtime)
        

Only call a function with yield if the function contains yield. Note that __init__ and end cannot contain yield (cannot wait), so they cannot call functions that contain yield.

Where To Put Scripts

To make a script show up in the Scripts menu, put it into the Scripts directory (or a subdirectory) in the shared or local version of STUIAdditions. If the directory STUIAdditions or STUIAdditions/Scripts doesn't exist, create it. Some details:

  • Any subdirectory hierarchy in a Scripts folder will be reflected in the submenu hierarchy in the Scripts menu.
  • The Scripts menu is rebuilt whenever it is selected, so changes show up automatically.
  • Please be careful when naming your scripts. Built-in scripts take precedence over user scripts, and user scripts take precedence over shared scripts. There is no warning if one script shadows another.

Debugging

Debug Mode

The script runner includes a basic debug mode which is enabled by putting sr.debug = True in your script. In debug mode, getKeyVar, startCmd and all the wait... commands print a diagnostic message to the error log when they run. Also:

  • Commands are not sent to the hub and are not waited for! The script simply assumes that they executed correctly and continues.
  • Keyword variables are not waited for. If they have a valid current value, it is returned, else the default value (if any) is returned, else None is returned.

Reloading a Script

To reload a script (i.e. after modifying it), select Reload from the contextual pop-up menu for any of the control buttons (Start, Pause or Cancel).

Warnings:

  • Using Reload is the only way to reload a script; simply closing the script and re-opening it from the Script menu will not work.
  • If your script uses widgets, you are likely to lots of error messages when running a reloaded script. This is because the old widgets have died, but code is still trying to talk to them. I hope to fix this someday. Meanwhile, once you get your script running to your satisfaction, I suggest you quit STUI and restart it to get rid of the error messages.

Diagnostic Output

print statements will print to to the error log. sr.showMsg is an alternative if standard output is not convenient for your platform.

More Information

APO Documentation contains pointers to manuals for the various instruments, the hub and the TCC.

The manual STUI Programming contains much information about the internals of STUI. Some of it is made simpler by using the scripting interface, but much of it is relevant -- especially if you want your script to use widgets (i.e. for user input or output).