track 3c273 track SaturnThis probably wasn't what the user intended, and clearly there are many instances like this where one wants to wait for one or more commands to complete before continuing. This is where the
until
command comes into play. This command
prevents subsequent commands in the schedule from being executed until
a specified set of conditions becomes true. For example, to ask the
control system to track a source, and wait up to 10 minutes for it to
acquire it, one could type:
track 3c273 until $acquired(source) | $elapsed > 10mThis example uses two functions $acquired(source), and elapsed(), the first of which returns true once the source is acquired by the telescope, and the second of which returns the amount of time that the until statement has been waiting. Note that to invoke a function, it is preceded by a $ character. The same is true of evaluating variables. This allows the compiler to figure out when you are typing in a variable or function name, as opposed to a literal value, like the name of a source. Also note the use of the boolean OR operator, denoted by |, and the greater-than operator, denoted by >. Thus what the above until command says is "wait until either the source has been acquired, or 10 minutes have passed.
The following is another example, showing a scheduling script which
initiates two time-consuming operations, then uses the
until
command to wait for both of them to complete.
# Tell the control system to start iteratively adjusting the # attenuators to achieve an output power level close to 26.4. power_level 26.4 # Tell the control system to start slewing to 3c273. track 3c273 # Wait for the target power level and source to be acquired. until $acquired(power) & $acquired(source) # Stay on source for another 15 minutes. until $elapsed >= 15mContinuing the above example, imagine that the real-time control system is unable to achieve the requested power level, or that the source couldn't be acquired. In such cases the
until $acquired(power) & $acquired(source)statement might wait indefinitely. To remedy this, the following example shows a modified version of the statement which times out after 5 minutes have elapsed.
until $acquired(power) & $acquired(source) | $elapsed >= 5mAs mentioned above, the | symbol stands for boolean OR, whereas & denotes boolean AND, so the above statement reads, "wait until the desired power level has been reached, and the source has been acquired, or the elapsed time exceeds 5 minutes." Boolean expressions are evaluated left to right, so each & and | operator operates on the result of the expression to its left and the single boolean value to its right. This associativity rule can be overriden with the use of parentheses. Using parentheses, the above example is equivalent to writing:
until ($acquired(power) & $acquired(source)) | $elapsed >= 5mHaving added a timeout to the statement, one might also want to detect that something went wrong before continuing the script. In the following addition to the script, the script is aborted if the source wasn't acquired, but it is allowed to continue if the requested power level wasn't reached.
if(!$acquired(source)) { print "Source not acquired - aborting." exit } else if(!$acquired(power)) { print "At least one of the attenuators failed to acquire - continuing." } else { print "Now tracking 3c273." }Notice how statements within the if command are grouped between {} braces. Also notice the use of the ! operator to negate the value returned by the
$acquired()
function. This is the boolean NOT
operator. With this in mind the first if
clause reads as,
"if not acquired source, then...". Admittedly this doesn't
read very well in English, but it is understandable.
Having got to this point, you may be thinking that you don't want to have to type so much, every time that you observe a new source. This is where user-defined commands come to the rescue. The following example shows how the above procedure could be encapsulated in a user-defined command.
command observe(Source src, Power pwr, Interval timeout, Interval dt) { # Tell the control system to start iteratively adjusting the # attenuators to achieve the power level contained in the # pwd variable. power_level $pwr # Tell the control system to start slewing to the source named in # the src variable. track $src # Wait for the target power level and source to be acquired, but # don't wait longer than the time contained in the timeout # variable. until ($acquired(power) & $acquired(source)) | $elapsed >= $timeout # Report our status. if(!$acquired(source)) { print "Failed to acquire ", $src, " - aborting." exit } if(!$acquired(power)) { print "At least one of the attenuators failed to acquire - continuing." } print "Now tracking ", $src, "." # Stay on source for the time contained in the dt variable, # or the source sets. until $elapsed >= $dt | $signaled(source_set) }The first line gives the command a name and then lists the types and names of each of its arguments. The arguments are thereafter accessible to the statements within the command as local variables. As previously mentioned, the value of a variable is accessed by prefixing the variable name with a $ character.
Having defined this command one can then use it to sequentially observe a couple of different sources by typing:
observe 3c273, 26.4, 5m, 15m observe 3c286, 25, 5m, 15mThis is much more convenient, but there are a few ways to improve the situation. First, you probably don't want to have to manually define the same command in every script that you write. Instead, you can collect commonly used command definitions in separate files and then use the import command to read them into subsequent scripts. For example, if I were to place the above definition of the observe command into a file called
~mcs/scripts/observing.sch
, then I could include it in
subsequent scripts by typing:
import ~mcs/scripts/observing.schOK so far, but what if one has a lot of sources to observe? It would be a bit irritating to have to type in the observe command for each source, particularly if you wanted to add your own commands before and after the canned observe command. A better alternative is to form a list, and use the foreach command to apply the observe command to each member of the list. But a list of what? The observe command takes more than one argument, so we need to form a list in which each member of the list is a group of values. This is where user-defined group data-types become useful. The following command defines a new datatype called
Observation
, which contains a list of the arguments
needed by the observe command.
group Observation { Source src, # The source to be observed. Power pwr, # The output power level to achieve. Interval timeout, # The acquisition timeout. Interval dt # The desired integration time. }Having done this, we could create and initialize a variable that contains the above parameters by typing:
Observation obs = {3c273, 26.4, 5m, 15m}This sets the
src
member of the variable to 3c273, the
pwr
member of the variable to 26.4, the
timeout
member to 5m and the dt
member to
15m. To access the src
member of the obs
variable, you would type $obs.src
. Thus we could print
out the contents of this variable by typing:
print $obs.src, " ", $obs.pwr, " ", $obs.timeout, " ", $obs.dt
To form a list of observation variables, and then assign the list to a
variable called ol
one could then type:
listof Observation ol = { {3c273, 26.4, 5m, 15m}, {3c286, 25, 5m, 10m}, {arp220, 30, 10m, 4m} }Having formed such a source list, one could then run the observe command like:
foreach(Observation obs) $ol { print "Moving to source: ", $obs.src observe $obs.src, $obs.pwr, $obs.timeout, $obs.dt print "Finished observing source: ", $obs.src }To make this even more convenient, one could rewrite the observe command to take a list of observations as its sole argument, and then include the foreach loop inside the command. If the definition of an observation datatype and the observe command were then placed in a file called
~mcs/scripts/better.sch
, then subsequent
observing scripts could be reduced to:
import ~mcs/scripts/better.sch listof Observation ol = { {3c273, 26.4, 5m, 15m}, {3c286, 25, 5m, 10m}, {arp220, 30, 10m, 4m} } observe $olIn fact we could simplify this even further by writing the list in place, rather than first assigning it to a variable. This would look like:
import ~mcs/scripts/better.sch observe { {3c273, 26.4, 5m, 15m}, {3c286, 25, 5m, 10m}, {arp220, 30, 10m, 4m} }This brings us to the question of how the compiler figures out when one statement ends and the next begins. To add some fuel, take a look at the following example:
print "This is actually", " one print command", " that stretches across a few lines."Unlike most languages, the CBI scheduling language has neither line termination characters nor continuation characters. Instead the compiler figures out whether a line is continued from context. In the above example, each line ended with a comma. On seeing the comma, the compiler expects another argument. So when it can't find one on the current line, it looks on the next line. Similarly, in the following example, the compiler sees the | operator at the end of the first line and assumes that its second operand must lie on the following line.
until $elapsed >= 20m | $elevation(current) < 40.0 | $after(23:34, LST)Unclosed parentheses have the same effect, so the compiler accepts the following statement without complaint.
until ( $elapsed >= 20m | $signaled(quit) )The only down side of this is that an accidentally unclosed parenthesis could result in a puzzling error message from the compiler!
Also note that in the above examples, although $elapsed
is a function, the parentheses
that normally follow such a function call, have been omitted, because
this function doesn't expect any arguments. This is legal for all
functions that don't expect arguments.
command calibrate(Time end_time) { track Jupiter until $elapsed > 10m | $after($end_time, lst) | $signaled(source_set) if($after($end_time, lst) | $signaled(source_set)) { return } fire_noise_cal 2 track Saturn until $elapsed > 10m | $after($end_time, lst) | $signaled(source_set) if($after($end_time, lst) | $signaled(source_set)) { return } fire_noise_cal 2 }Not only is it messy, but imagine what happens if "fire_noise_cal" is user-defined command that happens to use an until statement itself. The fact is that it doesn't know anything about the
end_time
argument of the
calibrate()
command, so it could end up waiting past the
programmed end time. This wouldn't be too bad if you could modify the
fire_noise_cal
command to take the end time as additional
argument, but if instead it imported from a separate file, which was
used by many other scheduling scripts, this wouldn't be feasible.
This is where the catch
command is
useful. Using the catch command, the above mess can be rewritten as:
catch $after($end_time, lst) | $signaled(source_set) { track Jupiter until $elapsed > 10m fire_noise_cal 2 track Saturn until $elapsed > 10m fire_noise_cal 2 } { noise_cal off sky_offset x=0, y=0 return }The catch command takes a boolean expression which it reevaluates repeatedly while executing a specified block of commands. If the expression becomes true, the block of commands is aborted. Effectively the specified condition is invisibly added to all of the until statements that execute within the block of commands, including ones buried inside any user commands that get called from the enclosed block of commands. In detail, the expression is evaluated before the first statement of the catch statement, while any until commands are executing, and whenever a signal is received. In the above example an optional second block of commands is provided. This block is executed when and if the expression becomes true, and allows you to respond to the problem. In this case, it has been used to make sure that the system is left in a known state, with the knowledge that the aborted fire_noise_cal command might have left the noise cal diode switched on and/or might have left a tracking offset instated. If this is ommitted, execution resumes with the first statement that follows the catch statement.
signal
command, and the
schedule can test if a signal has been sent by using the signaled()
function. Apart from predefined
hardware generated signals, such as the source_set signal, all
recognized signal names are normally defined in the cbicontrol
initialization script, which currently defines two signals,
"quit" and "done". The former is normally used to
signal that a script should start a new task, such as moving to the
next star in a pointing schedule, and the second for telling a
schedule to abort what it is doing. The quit signal is actually rather
redundant nowadays, given that the abort_schedule command does this already. A
schedule that wanted to wait for a done
signal, might
include a statement like the following:
until $signaled(done)Alternatively, a schedule that wanted to abort a block of commands on receiving a done signal could use a catch statement like:
catch $signaled(done) { ...commands... }