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 $ol
In 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...
}
Martin Shepherd (9-Dec-1999)