Index

A Tutorial Introduction to the CBI Scheduling Language

The CBI scheduling language is an asynchronous language, meaning that by default it doesn't wait for one scheduling command to complete before starting the next one. This is possible because the commands are actually executed in parallel by separate slave processes in the real time control system. This means that multiple commands execute in parallel, and that, if say a track command is issued for a source that is below the horizon, or if a phase shifter motor fails to move, the schedule doesn't block waiting for the source to rise, or for the phase shifter motor to move. If the same command is sent twice in succession, but with different arguments, the first command is aborted immediately and the new one starts. For example the following commands result in the telescope tracking Saturn, without spending any time on 3c273, because the second track command aborts the first.
  track 3c273
  track Saturn
This 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 > 10m
This 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 >= 15m

Continuing 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 >= 5m
As 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 >= 5m
Having 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, 15m
This 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.sch
OK 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.

Catching exceptional conditions

While it is possible to use the until command to detect problems, such as something taking too long, or a source setting, this can get messy if there are many conditions to detect and many places to detect them. For example, the following real-life example looks really messy.
 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.

Sending and receiving signals

Interactive control of a running schedule from the cbiviewer command line can be accomplished by sending the schedule signals. This is done using the 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)