A Template for a General Scan Module

In the following you find a template for a general scan module. It has a mandatory data structure %GS::h and some mandatory functions.

Let's start with the hash %GS::h, a string-indexed array. All data that control a scan are stored here. This data structure is displayed, if the user selects Module-Info.

The key a1 has the value MOT1 in this example. It is used by the functions get_position() and set_position(). It is a possible value of the $axis. a1 can be assigned to a motor name, a virtual motor name, dummy1 or energy. The same applies to a2 and a3. But they don't have to be supplied for 1D scans. For 2D scans we need also a2 and for 3D scan a3 has to be defined.

The key description is also mandatory.

The other keys of the hash are arbitrary. The have no meaning for Online. Putting them into this hash has the advantage that they can be displayed by selecting the menu option Info.

This is the list of the functions:

That's all. The general scan widget creates a framework for executing repeated measurements. It defines a minimal interface. Basically the name, the limits and some action buttons. All the rest is left to the user. Anything can be done in the before, during and after macros.

#!/usr/bin/perl -w
use strict; 
package GS; 
#
# An example for a General Scan Module
#
# The following symbols are defined by Online, they 
# may be used in this module
#
#      $Spectra::SYM{ scan_name} 
#      $Spectra::SYM{ np_total} 
#      $Spectra::SYM{ sample_time}    may change from point to point
#      $Spectra::SYM{ scan_offset_c1} 
#      $Spectra::SYM{ scan_offset_c...} 
#      $Spectra::SYM{ start}          start, stop and delta are unknown, 
#      $Spectra::SYM{ stop}           if the positions are specified as
#      $Spectra::SYM{ delta}          commands.
#
use vars qw( %h); 
%h = ( a1 => "MOT1", 
       a2 => "MOT2", 
       a3 => "MOT3",
       counters => [ qw( c3 c4 c5 c8)],
       timer    => "t1", 
       samples => { s1 => 12, s2 => 13}, 
       description => "General Scan Demo Module", 
     );
1;
#
# set/get position
#
sub set_position
{
    my ( $axis, $new_pos) = @_; 
    my $status = 0;
    
    if( ref( $new_pos) eq "CODE")
    {
	$status = &$new_pos; 
    }
    else 
    {
	$status = Spectra::move( $h{ $axis} => $new_pos); 
    }
 finish:
    return $status; 
}
sub get_position
{
    my ( $axis) = @_; # 'a1', 'a2' or 'a3'

    return Spectra::gmup( $h{ $axis}); 
}
#
# this function is called once when the scan starts
#
sub exec_before
{
    my $status = 0; 

    Spectra::delete();
    Spectra::cls(); 

    my $scan_name = $Spectra::SYM{ scan_name}; 
    my $np_total = $Spectra::SYM{ np_total}; 
    #
    # the preparation: copy the  offsets to %h, 
    # thereby checking whether they exist
    #
    $h{ sample_time} = $Spectra::SYM{ sample_time}; 
    
    foreach my $c ( @{ $h{ counters}})
    {
	$h{ "offset_${c}"} = $Spectra::SYM{ "scan_offset_$c"}; 
	if( !defined $h{ "offset_${c}"})
	{
	    Spectra::error( "before: failed to find scan_offset_${c}"); 
	    goto finish;
	}
    }
    $h{ start_time} = Spectra::date_and_time(); 
    $h{ start_position} = GS::get_position( "a1"); 
    $h{ np_total} = $np_total; 

    TEXT->create( name => 0, 
		  string => "T1 Scan ($scan_name) started at $h{ start_time}", 
		  y => 1.12, 
                  x => 1.0, 
                  v_aling => 'top', 
                  h_align => 'right'); 

    TEXT->create( name => 0, 
		  string => $h{ a2} . " at " . Spectra::gmup( $h{a2}) , 
		  y => 1.05, 
                  x => 1.0, 
                  v_aling => 'top', 
                  h_align => 'right'); 
    #
    # it is important to create a SCAN named scan_name because
    # it helps Online to keeps track of the scan numbers
    #
    $h{ scan_name} = SCAN->create( name => $scan_name,
				   np => $np_total); 
    #
    # allocate space for the counter readings 
    #
    foreach my $c ( @{ $h{ counters}})
    {
	$h{ $c} = SCAN->create( name => $scan_name . "_" . $c, 
				title => $c,
				colour => 'blue', 
				np => $np_total); 
    }
    #
    # allocate space for the sample time
    #
    $h{ st} = SCAN->
	create( name => $scan_name . "_sample_time", 
		title => "Sample Time",
		colour => 'blue', 
		np => $np_total); 
    
    $h{ scan_name}->deactivate(); 
    Spectra::display( vp => 1);
    $status = 1;
 finish:
    return $status; 
}
#
# this function is called for each stop
#
sub exec_during
{
    my ( $index) = @_; 
    my $status = 0; 

repeat:
    #
    # get the current position ...
    #
    my $pos = GS::get_position( "a1");
    #
    # ... and store it
    #
    $h{ scan_name}->{ x}[ $index] = $pos; 
    $h{ sample_time} = $Spectra::SYM{ sample_time}; 
    if( $h{ sample_time} <= 0.)
    {
	Spectra::error( "exec-during: sample time: $h{ sample_time}");
	goto finish;
    }	    

    Spectra::reset_all_counters(); 

    if( $Util::db_h{ flag_automatic_beamshutter})
    {
	my $t = $Util::db_h{ timer_scan}; 
	$Util::db_h{ timer_scan} = $h{ timer}; 
	$status = Util::was_injection(); 
	$Util::db_h{ timer_scan} = $t; 
	if( !$status) 
	{
	    goto finish;
	}
    }
    else
    {
	if( !Spectra::start_and_wait_for_timer( $h{ timer}, 
						$h{ sample_time}))
	{
	    goto finish; 
	}
    }
    #
    # loop over the counters
    #
    foreach my $c ( @{ $h{ counters}})
    {
	$h{ $c}->{ x}[ $index] = $pos;
	$h{ $c}->{ y}[ $index] = Spectra::rc( $c)/$h{ sample_time} -
	    $h{ "offset_$c"}; 
    }
    #
    # store the sample time
    #
    $h{ st}->{x}[ $index] = $pos;
    $h{ st}->{y}[ $index] = $h{ sample_time}; 

    Spectra::autoscale(); 
    Spectra::display(); 
    $status = 1;
    #
    # the after file needs to know the final scan position
    #
    $h{ stop_position} = GS::get_position( "a1"); 
  finish:
    return $status;
}
#
# this function is called once after the scan
#
sub exec_after
{
    my $status; 
    my $scan_name = $h{ scan_name}->get( "name"); 
    #
    # prepare the command lines
    #
    $h{ scan_name}->set( com_1 => $h{ a1} . "-Scan started at " . 
			 $h{ start_time} . ", ended " . Spectra::time()); 
    $h{ scan_name}->set( com_2 => "Name: $scan_name from " . 
			 $h{ start_position} . " to " . $h{ stop_position} . 
                         ", sampling " . $h{ sample_time} . "s"); 
    $h{ scan_name}->set( com_3 => $h{ a2} . " at " . 
                         GS::get_position( "a2") . ", " . $h{ a3} .
                         " at " . GS::get_position( "a3")); 
    $h{ scan_name}->set( com_4 => 
                         "Counter reading are offset corrected, the offsets are");
    my $line = ""; 
    foreach my $c ( @{ $h{ counters}})
    {
	$line .= "$c " . $Spectra::SYM{ "scan_offset_$c"} . " "; 
    }
    $h{ scan_name}->set( com_5 => $line); 
						       
    $status = Spectra::gra_command( "write/fio/scan/motors $Spectra::SYM{ scan_name}"); 

#
# create log file
#
open( FH, ">${scan_name}.log"); 
print FH " Scan name ${scan_name}\n";
print FH " User $ENV{ USER} \n"; 
print FH " Start $Spectra::SYM{ start}\n";
print FH " Step  $Spectra::SYM{ delta} \n";
print FH " Stop  $Spectra::SYM{ stop} \n";
print FH " Sample time  $Spectra::SYM{ sample_time} \n";
print FH " Started " . $Util::res_h{ start_time} . "\n";
print FH " Ended " . Spectra::date_and_time() . "\n";
print FH " Offset C3 " . $Spectra::SYM{ scan_offset_c3} . "\n";
print FH " Offset C4 " . $Spectra::SYM{ scan_offset_c4} . "\n";
print FH " Offset C5 " . $Spectra::SYM{ scan_offset_c5} . "\n";
print FH " Offset C8 " . $Spectra::SYM{ scan_offset_c8} . "\n";
if( defined( $Scan::menu_h{ w_comment}))
{
    print FH " Comment " . $Scan::menu_h{ w_comment}->get() . "\n"; 
}
close( FH); 
return $status;
}