esrf

Beamline Instrument Software Support
SPEC Macro documentation: [ Macro Index | BCU Home ]

#%TITLE% OSCILLATION.MAC
#%NAME%  
#  Oscillation macros for X-ray diffractaion data collection
#
#%CATEGORY% Scans
#
#%DESCRIPTION%
# Use for standard single crystal X-ray diffraction data collection.
# The sample is exposed to the X-ray beam rotating with constant speed on a
# goniometer spindle while the diffraction pattern is recorded by an area
# detector (X-ray image intensifier/CCD). 
# Because of the short exposure times at a synchtrotron (down to 0.1 s) the
# timing of X-ray shutter and rotation has to be done with care.
#%BR%
# The stepper motor pulses for the spindle rotation are monitored by counting
# module (VME Counter-Timer 6). This card generates a gate pulse opening and
# closing an electromagnetical fast X-ray shutter at the begin and end of the
# oscillation range. Acceleration and deceleration ramp are taken into account
# as well. 
#
#%BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% 
#%BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% %BR% 
# On ID30 the VCT6 card has to be connected the following way:
#%BR% %BR%
# From the Mtheta motor the "%B%TOPMOT%B%" signal MUST be connected to both 
# %B%STRT 2! (not 1)%B% and equally %B%IN 2! (not 1)%B%.
#%BR% %BR%
# This is in the little rectangle on the front of the Vct6:
#%BR% %BR%
#%PRE%
#          ----------------
#          | O ......... O |<-
#          | 1 . STRT .. 2 |   |
#          | O ......... O |   |--- connect both to TOPMOT mth
#          | 1 . STOP .. 2 |   |
#          | O ......... O |   |
#          | 1 GATE-IN . 2 |   |
#          | O ......... O |<- 
#          | 1 . IN .... 2 |
#          | O ......... O | Sorry all the dots are just to
#          | 1 GATE-OUT. 2 | make the picture look correct.
#          ----------------
#%PRE%
# You must also connect the fast shutter cable to the Vct6 plug 1 in the lower
# rectangle at the bottom of the card.
#%PRE%
#          ----------------
#          | ............. |
#          | ...  O. 1 ..  |< ---- fast shutter
#          | ............. |
#          | ...  O. 2 ..  |
#          | ............. |
#          | ...  O. 3 ..  |
#          | ............. |
#          | ...  O. 4 ..  |
#          | ............. |
#          ----------------
#%PRE%
# Dark current substraction is done online automatically. Every time you
# change the exposure time a new dark current image will be read out to be
# substracted from the image data.
#%END%

#%SETUP%
#
#
# Please make sure the input 1 of the VCT6 card are configured for TTL level
# (low = 0V, high = +5V).
#%BR%
# Open the file ~dserver/dbase/res/ID30/id301Vct6_1.res und check whether
# the following lines are present:
#%PRE%
# 
# ID30/vct301_1/1/GateLevel:       TTL
# id30/vct301_1/1/InputLevel:      TTL
# 
#%PRE%
# Restart the "Vct6" device server:
#%PRE%
# 
# opid30@megabar> telnet id301
# User name?: root rt
# id301[1]: procs ! grep Vct
# 85   0   0.0     128   92.00k   0 s        0.21 127:00 Vct6 <>>>nil
# id301[2]: os9kill -2 85
# id301[3]: ID301Vct6_0.startup
# 
#%PRE%
# I hope a future release of the VCT6 device server will provide a device server
# call for the input level so this operation can be included in the SPEC 
# macro.
#
#%LOG%
#$Revision: 1.14 $
#$Log: oscillation.mac,v $
#Revision 1.14  2012/06/05 09:33:31  witsch
#function max() was called within oscillation, but had been commented out.
#
#Using max2, as suggested from spec_utils.mac
#
#Revision 1.13  2011/09/16 07:54:39  guilloud
#* load spec_utils.mac (for max() function)
#* removed max() function
#
#Revision 1.12  2008/08/12 15:24:49  rey
#documentation changes
#
#Revision 1.10  2005/09/21 12:51:41  pepellin
#Use clscreen()
#
#Revision 1.9  2005/04/07 13:35:11  claustre
#create a oscillation_restore macro to well manage vct6 and vpap default
#configuration
#
#Revision 1.8  2004/08/27 07:17:43  claustre
#Restore use of safety-shutter options.
#Check now if the calculated speed is not smaller than the base-rate.
#Add some sleep() when polling motor position during oscillation.
#
#Revision 1.6  2004/03/03 08:32:48  claustre
#create oscillation_user_* macros to allow beamline add-on implementation.
#
#Revision 1.5  2004/02/18 16:24:50  claustre
# Hoops, a bug introduced with the last version, take_still_image macro.
#
#Revision 1.4  2004/02/18 15:42:50  claustre
#special premove and postmove cdef'ed macros for ID09 usage, fixe pb
#with prepare_mar() to initialize spec internal data about file pars.
#
#Revision 1.3  2003/01/23 16:06:10  claustre
#This is the real oscillation macros used on beamline.
#
#%END%


# Modifications history:
# 16/04/2003 LC fix a bug in oscillation_cleanup() which restored a wrong acceleration
#               change cleanup rdef with cdef() on cleanup_once.
#               create oscillation_premove and oscillation_postmove for cdef() usage on ID09
# 20/07/2001 HW image_par now sends a big string with lots of different
#								information, the range is now 
# 04/07/2001 HW add handling for the Bruker detector. It can't be used like a
#               any other ccd, because it doesn't allow external trigger.
# 13/02/2001 MP+JK remove the CntInit when getting out of main menu (don't ask
#               why, please!!)
# 30/01/2001 HW change the MAR IP handling to match the new device server which
#               simulates a CCD type device. (Edit token : #HW30101)
# 18/06/2000 HW take out all reference to diodes. 
#               Reset the vct6 for it to work as time base after the
#               oscillation macro
# 16/06/2000 HW cable the Vct6 so that channel 2 is the gate channel, setup of
#               fourc
# 12/06/2000 HW put all access to old fast shutter to comment.
#               Access to new fast shutter should be handled by macros in the
#               new-fast-shutter.mac file.
# 28/04/99 MP correct if() bug after SPEC update
# 28/02/99 LF number of digits in "spindle at" message increased from 2 to 3
# 25/02/99 MP add fast shutter opening time as a parameter to oscillsetup
# 24/02/99 LF real_time added to OSCILL and beam intensity output per second
# 23/02/99 MP add value check on speed entering (enter_speed_parameter,...)
# 23/02/99 MP replace adc with VCT6 channel for beam monitoring
# 23/02/99 MP suppress putting in/out the diode at each exposure 
# 24/09/98 MP add wait_ms_shutter() needed if open_ms_shutter called with arg
# 24/09/98 MP add f_open_ms_shutter() (allow calls from other macros)
# 01/09/98 MP in fsds_osc_readout, suppress fastscan controler reset
# 01/09/98 MP in take_oscillation_image, force tcp protocol 
# 01/09/98 MP add oscillation_prepare_diode, _read_diode, _log_diode
# 01/09/98 MP Err: oscillation_cleanup, add tests on esrf_io()
# 01/09/98 MP Err: oscillation_cleanup, suppress access to trig ch if not CCD
# 07/07/98 MP Err: in take_oscillation_image, gate number not directly from
#             OSC_SUP["gateg"] (if several VCT6 cards, channel number wrong)
# 27/06/98 MK in fsds_osc_readout, changed time-delay afer fastscan-reset
#             from 2 to 4 
# 26/06/98 MP in oscillation_log, add current diode intensity information
# 25/06/98 MP in oscillation_data_collection, change the end condition of for()
# 11/06/98 MP Err: in take_oscillation_image, gate number from OSC_SUP["gateg"]
# 11/06/98 MP Err: in oscillation_log, test on file_info() return value wrong
# 11/06/98 MP Add some printf("\n") and info for clearer display while scaning
# 10/06/98 MP Add "FASTSCAN" as OSCILL["detector"] possible value
# 10/06/98 MP Err: in restore_array, SPEC subsitution wrong, quote missing
# 10/06/98 MP Err: in restore_array, test on file_info() return value wrong
# 10/06/98 MP Err: in take_oscillation_image, add "local exposure"
# 10/06/98 MP Err: in oscillation_initialize "exposure_time" naming
# 10/06/98 MP Add max() function.



# for max() (at least)
need spec_utils



#%UU% %MDESC% (or "collect") a menu driven routine to take complete data sets
 
def oscillation 'oscillation_data_collection_menu'

#%UU% %MDESC% (same as "oscillation")

def collect 'oscillation_data_collection_menu'

#%UU% [gonio] [detector] [shutter] [vct6] [gate] [trigger] [timer] [ccd]
#%MDESC% Global setup. Define all beeamline dependent parameters. Put this line
# in your beamline's setup file.

def oscillsetup '
{
 global OSC_SUP

 if ($#>=10) {
  OSC_SUP["gonio"]= "$1"
  OSC_SUP["detec"]= "$2"
  OSC_SUP["shutt"]= "$3"
  OSC_SUP["vct6"] = "$4"
  OSC_SUP["gateg"]= $5
  OSC_SUP["trigg"]= $6
  OSC_SUP["watch"]= $7
  OSC_SUP["ccd_u"]= $8
#  OSC_SUP["b_mon_mne"]= "$9"
  OSC_SUP["b_mon_mne"]= $9
  OSC_SUP["fs_open"]= $10
 } else {
  OSC_SUP["gonio"]= getval ("oscillation motor mnemonic",OSC_SUP ["gonio"])
  OSC_SUP["detec"]= getval ("detector pos. motor mnemonic",OSC_SUP ["detec"])
  OSC_SUP["shutt"]= getval ("safety shutter device name",OSC_SUP ["shutt"])
  OSC_SUP["vct6"] = getval ("Vct6 device name",OSC_SUP ["vct6"])
  OSC_SUP["gateg"]= getval ("Vct6 shutter gate channel",OSC_SUP ["gateg"])
  OSC_SUP["trigg"]= getval ("Vct6 camera gate channel",OSC_SUP ["trigg"])
  OSC_SUP["watch"]= getval ("Vct6 timer channel",OSC_SUP ["watch"])
  OSC_SUP["ccd_u"]= getval ("CCD Unit configured",OSC_SUP ["ccd_u"])
  OSC_SUP["b_mon_mne"] = getval ("Vct6 beam monitor counter", \
	OSC_SUP ["b_mon_mne"])
  OSC_SUP["fs_open"]= getval ("Fast Shutter opening time (sec)", \
	OSC_SUP ["fs_open"])

  if(OSC_SUP["fs_open"] < 0) OSC_SUP["fs_open"]=0;
  if(OSC_SUP["fs_open"] > 0.01) OSC_SUP["fs_open"]=0.01;
 }

 OSC_SUP["gonio_n"]= motor_num(OSC_SUP["gonio"])
 OSC_SUP["detec_n"]= motor_num(OSC_SUP["detec"])
 OSC_SUP["gate"]= OSC_SUP ["vct6"] "/" OSC_SUP ["gateg"]
 OSC_SUP["trig"]= OSC_SUP ["vct6"] "/" OSC_SUP ["trigg"]
 OSC_SUP["watc"]= OSC_SUP ["vct6"] "/" OSC_SUP ["watch"]
}'

#%IU%
#%MDESC% setup all needed parameters to acquire a full data set, such
# as angular step, rotation range, exposure time and filenames
 
def oscillation_data_collection_menu '
{ 
 oscillation_initialize

 local letter ; letter = ""

 while (letter != "\n")
 {
  # modify oscillations and exposure time because of hardware constraints
  check_oscillation_parameters # values taken from "OSCILL" array
  
  # [md] = mode double-bright, [me] = mode attributes end  
  # [us] = underline start, [ue] = underline end

  clscreen() # clear screen
  tty_move (1,1,"\[md]\[us]OSCILLATION DATA COLLECTION\[me]\[ue]")
  if (OSC_SUP["mar_use"]) tty_move (1,30,"\[md](MAR IP)\[me]")
  if (OSC_SUP["bruker_use"]) tty_move (1,30,"\[md](Bruker)\[me]") #HW 04072001

  tty_move (1,3, "\[us]\[md]F\[me]\[ue]rom:"); 
  tty_move (20,3,OSC_DATA["start"]); tty_move (27,3,"deg")
  tty_move (1,4, "\[us]\[md]T\[me]\[ue]o:");   tty_move (20,4,OSC_DATA["end"]); tty_move(27,4,"deg")
  tty_move (40,3,"\[us]\[md]A\[me]\[ue]ngular Step:")
  tty_move (60,3,OSC_DATA ["step"]); tty_move(66,3,"deg") 
  tty_move (40,4,"\[us]\[md]O\[me]\[ue]scillation Range:")
  tty_move (60,4,OSCILL["range"]); tty_move(66,4,"deg")
  tty_move (40,6,sprintf("(%-2d half-oscillations)",OSCILL["number"]))
 
  tty_move (1,6, "\[us]\[md]E\[me]\[ue]xposure Time:")
  tty_move (20,6,sprintf("%.2f",OSCILL["exposure_time"])); tty_move(27,6,"s")
  
  tty_move (1,8, "\[us]\[md]B\[me]\[ue]asename:"); 
  tty_move (20,8,OSC_DATA["basename"] ? OSC_DATA["basename"] : "-")
  tty_move (40,8,"\[us]\[md]L\[me]\[ue]og File:"); 
  tty_move (60,8,OSC_DATA["log_file"] ? OSC_DATA["log_file"] : "-")
  tty_move (1,9, "\[us]\[md]I\[me]\[ue]mage Number:")
  tty_move (20,9,sprintf("%03d",OSC_DATA["image_number"]))
  
  tty_move (40,9,"S\[us]\[md]u\[me]\[ue]ffix:")
  tty_move (60,9,OSC_DATA["suffix"] ? OSC_DATA["suffix"] : "-")

  tty_move (1,11,"\[us]\[md]P\[me]\[ue]ath:"); tty_move (20,11,tail(OSC_DATA["dir"],58))
  if (file_info(OSC_DATA["dir"],"isdir") != 1) printf (" ?")
  tty_move (1,12,"\[us]\[md]N\[me]\[ue]ote:"); 
  tty_move (20,12,OSC_DATA["description"] ? OSC_DATA["description"] : "-")

  #HW070301begin and change HW04072001
  if (OSCILL["detector"] == "MAR IP" || OSCILL["detector"] == "BRUKER") {
    tty_move ( 1,14, "X-ray beam \[us]\[md]w\[me]\[ue]avelength in A: "); 
    tty_move (31,14, OSCILL["wavelength"]); 
    tty_move (40,14, "Detector-to-sample distance in \[us]\[md]m\[me]\[ue]m: "); 
    tty_move (75,14, OSCILL["d_s_dist"]); 
  }
  else tty_move (1,14, note = "(" data_set("info") ")" )
  #HW070301end
  #HW070301 if (OSCILL["detector"]!="FASTSCAN")

  tty_move (1,16,sprintf ("(current orientation is %g deg)",A[OSC_SUP["gonio_n"]]))
  tty_move (40,16,"(\[us]r\[ue]eset to 0)")

  tty_move (1,19,"\[mr] \[md]\[us]S\[ue]\[me]\[mr]INGLE IMAGE \[me]") # [mr] = mode reverse
  tty_move (20,19,"\[mr] \[md]\[us]C\[ue]\[me]\[mr]OLLECT \[me]")    
  tty_move (40,19,"\[md]\[us]H\[ue]\[me]ardware setup... ")     
  if ((OSCILL["detector"] != "") && OSCILL["detector"] != "none")
    tty_move (60,19,"\[md]\[us]D\[ue]\[me]etector... ")     
  
  tty_move (1,22,"--> Type underligned letter (lower case, [Return] to quit) ");
  
  # wait until user types hits a key
  letter = input(-1); while (asc(letter) == 0) letter = input(-1) ; input(1)
  
  tty_move (1,22,"--> \[ce]");
  
  if (letter == "f") 
    OSC_DATA["start"] = getval("First oscillation starts at",OSC_DATA["start"])
  if (letter == "t")
    OSC_DATA["end"] = getval("Last oscillation starts at",OSC_DATA["end"])
  if (letter == "a") OSC_DATA["step"] = \
    getval("Advance spindle between exposures by",OSC_DATA["step"])
  if (letter == "o") OSCILL["range"] = \
    OSC_DATA["step"] = getval("Rotate during exposure by",OSCILL["range"])
#  if (letter == "e") OSCILL["exposure_time"] = \
#    getval("Exposure time [s]",OSCILL["exposure_time"])+0
  if (letter == "e" || letter == "o") enter_speed_parameter;
  if (letter == "b") { tty_move (20,8); edit OSC_DATA["basename"] 19 }
  if (letter == "l") { tty_move (60,8); edit OSC_DATA["log_file"] 19 }
  if (letter == "i") OSC_DATA["image_number"] = \
    int (getval ("Number appended to image name",OSC_DATA["image_number"]))
  if (letter == "u") OSC_DATA["suffix"] = \
    getval("Filename ext. (- for none)",OSC_DATA["suffix"])
  if (OSC_DATA["suffix"] == "-") OSC_DATA["suffix"] = "" 
  
  if (letter == "p")
  {
    tty_move (20,11); edit OSC_DATA["dir"] 59
#    if (file_info(directory,"isdir") != 1) unix (command="mkdir -p "directory)
  }
  if (letter == "n") { tty_move (20,12); edit OSC_DATA["description"] 59 }
  if (letter == "c") oscillation_data_collection
  if (letter == "s") single_oscillation_image
  if (letter == "h") oscillation_setup
  if (letter == "d") oscillation_detector_setup
  if (letter == "r") { chg_offset (OSC_SUP["gonio_n"],0); get_angles }
  if (letter == "w") OSCILL["wavelength"] = \
    getval("X-ray beam wavelength in A:", OSCILL["wavelength"])
  if (letter == "m") OSCILL["d_s_dist"] = \
    getval("Detector-to-sample distance in mm:", OSCILL["d_s_dist"])
 }
 save_oscillation_parameters
 # Configure the VCT6 channel 2 back to normal. No gate use!
#MP
# esrf_io(OSC_SUP["gate"],"DevCntClear")
 local vct6_init
 vct6_init[0] = 0 # Mode = master
 vct6_init[1] = 0 # Gate number - not used for master
 vct6_init[2] = 0 # Clock - 0 = internal, 1 = external
 vct6_init[3] = 0 # Clock Frequency - 100kHz if internal, not used if external
 vct6_init[4] = 0 # Free run mode - no
 vct6_init[5] = 0 # Start counting signal - internal
 vct6_init[6] = 0 # Stop counting - internal
 vct6_init[7] = 0 # Divider mode - not used (only for free-run)
 vct6_init[8] = 0 # Autoclear of counting value before each count (slave only) 
 vct6_init[9] = 0 # Hold counting value if external start - unused (slave only)
 vct6_init[10] = 0 # Precount clock source - internal
#MP
# esrf_io(OSC_SUP["gate"],"DevCntInit",vct6_init)
}'

# %IU% %MDESC% setup menu which contains hardware related parameters which
# you normally do not change during a data collation.

def oscillation_setup '
{ 
 oscillation_initialize
 
 local letter; letter = ""
 
 while (letter != "\n")
 {
  local text
  
  clscreen() # clear screen
  tty_move (1,1,"\[md]\[us]OSCILLATION HARDWARE SETUP\[me]\[ue]")

  tty_move (1,3,"\[us]\[md]D\[me]\[ue]etector:") 
  tty_move (20,3,text="<"OSCILL["detector"]">") 
  if (OSCILL["detector"] != "none")
    tty_move (40,3,"\[us]\[md]S\[me]\[ue]etup...")
  
  tty_move (1,5,"Auto \[us]\[md]o\[me]\[ue]pen safety shutter:")
    tty_move (40,5,OSC_SUP["open shutter"] ? "<yes>" : "<no>")
  tty_move (1,6,"Auto \[us]\[md]c\[me]\[ue]lose safety shutter:"); 
    tty_move (40,6,OSC_SUP["close shutter"] ? "<yes>" : "<no>")

  tty_move (1,22,"--> Type underligned letter (lower case, [Return] to quit) ");
  
  # wait until user types hits a key
  letter = input(-1) ; while (asc(letter) == 0) letter = input(-1) ; input(1)
  
  tty_move (1,22,"--> \[ce]"); # clear to the end of line
  
  if (letter == "d") 
    if (OSCILL["detector"]=="XRII/CCD") OSCILL["detector"]="MAR IP"
    else if (OSCILL["detector"]=="MAR IP") OSCILL["detector"]="BRUKER" #HW04072001
    else if (OSCILL["detector"]=="BRUKER") OSCILL["detector"]="FASTSCAN" #HW04072001
    else if (OSCILL["detector"]=="FASTSCAN") OSCILL["detector"]="none"
    else OSCILL["detector"]="XRII/CCD"
  if (letter == "s") oscillation_detector_setup
  if (letter == "o") OSC_SUP["open shutter"] = ! OSC_SUP["open shutter"] 
  if (letter == "c") OSC_SUP["close shutter"] = ! OSC_SUP["close shutter"] 
 } 
}'

#%IU% (arg[,param])
#%MDESC% print inforation about the number of images in the data set
# warn if files may be overwritten
# %DL% 
# %DT% arg = "filename", param = image_number
# %DD% Return full absolute path name for image data
# %DT% arg = "info"
# %DD% Return informational message string about number of images beeing collected 
#      and file risking to be overwritten.
# %DT% arg = "overwrite?"
# %DD% prompt the user to comfirm overwriting of files (one for all files in a 
#      data set). Returns 1 or 0.
# %XDL%

def data_set (arg,param) '{
  local images overwriting warning text
    
  if (arg == "filename")
  {
    local nnn filename
    image_number = param
    if (OSC_DATA["basename"] == "") return ""
    nnn = sprintf("%03d",image_number)
    filename = OSC_DATA["dir"]"/"OSC_DATA["basename"]"_"nnn OSC_DATA["suffix"]
    return filename
  }
  if (arg == "info")
  {
    data_set_check
    if (overwriting) warning = ", overwriting "overwriting" file/s"
    if (overwriting >= 1000) warning = ", overwriting more than 1000 file/s"
    text="Will take "images" image/s" warning
    return text
  }
  if (arg == "overwrite?")
  {
    data_set_check 
    if (overwriting)
      return yesno (warning = "OK to overwrite "overwriting" file/s",1)
    else return 1
  }
}'

#%MDESC% used by the "data_set()" macro

def data_set_check '
{
  local start step end image_number orientation filename

  start = OSC_DATA ["start"]; step = OSC_DATA ["step"]; end = OSC_DATA ["end"]
  image_number = OSC_DATA["image_number"]

  if (step != 0)
  {
    for (orientation=start; (orientation-end)/step < 0; orientation += step)
    {
      images++
      filename = data_file(image_number)
      if ((filename != "") && file_info (filename,"-e") == 0) overwriting++
      image_number++
      if (images > 1000) break
    }
  }
  if (step == 0)
  {
    images = 1
    filename = data_file(image_number)
    if ((filename != "") && file_info (filename,"-e") == 0) overwriting=1
  }
}'

#%IU% (image_number) %MDESC% Return full absolute path name for image

def data_file (image_number) '{
  local nnn filename
  if (OSC_DATA["basename"] == "") return ""
  nnn = sprintf("%03d",image_number)
  filename = OSC_DATA["dir"]"/"OSC_DATA["basename"]"_"nnn OSC_DATA["suffix"]
  return filename
}'

#%IU%
#%MDESC% Set the user configurable parameters to some resoable defaults
# when the "oscillation" macro is invoked the first time. 
# The parameters wont be reinitialized in subsequent calls Since they
# are stored on global variables, which keep their values from one SPEC
# session to the next.
# Only if the parameters seem to be cleared, because of a SPEC restart with
# the -f (from fresh) option or a crash, they will be reloaded from the ASCII
# file, where the "save_oscillation_parameters" macro saves them.

def oscillation_initialize '
{
  global OSCILL[] OSC_DATA[]
  # if global vars are cleared, try to reload values from ASCII files 
  restore_array OSCILL
  restore_array OSC_DATA
  
  # set some resonable default values
  if (! ("exposure_time" in OSCILL)) OSCILL["exposure_time"] = 20
  if (! ("range" in OSCILL)) OSCILL["range"] = 5
  if (! ("number" in OSCILL)) OSCILL["number"] = 1
  if (! ("detector" in OSCILL)) OSCILL["detector"] = "XRII/CCD"

  if (! ("start" in OSC_DATA)) OSC_DATA["start"] = "0"
  if (! ("end" in OSC_DATA)) OSC_DATA["end"] = "180"
  if (! ("step" in OSC_DATA)) OSC_DATA["step"] = "5"
  if (! ("dir" in OSC_DATA)) OSC_DATA["dir"] = CWD
}'

#%IU%
#%MDESC% Save all user configurable parameters into an ASCII file so they
# are not not lost when SPEC is restarted with the -f (from fresh) option, 
# clearing all global variables. The parameters are hold in 2 global arrays,
# OSCILL and OSC_DATA.

def save_oscillation_parameters '
{
  save_array OSCILL 
  save_array OSC_DATA
}'

#%IU%
#%MDESC% Acquire a complete data set. parameters such as angular step, rotation
# range, exposure time and filenames are passed in the global array
# "OSCILL"
 
def oscillation_data_collection '
{
  if (data_set("overwrite?"))
  {
    global cancelled ; cancelled = 0
    local start step end orientation
  
    start = OSC_DATA["start"]; end = OSC_DATA["end"]; step = OSC_DATA["step"]
  
    oscillation_user_precollect
 
    if (step != 0)
      for (orientation = start; (orientation-end)/step < 0; orientation += step)
      {
        rotate_spindle_to orientation
        take_oscillation_image
        if (cancelled) break
      }
    if (step == 0) 
    { 
      orientation = OSC_DATA["start"]
      rotate_spindle_to orientation
      take_oscillation_image
    }
  
    oscillation_user_postcollect   
  }
}'



#%IU% %MDESC% useful for sample screening and optimizing the exposure time

def single_oscillation_image '
{
  local orientation; orientation = A[OSC_SUP["gonio_n"]]
  local start; start = OSC_DATA["start"]
  cancelled = 0

  oscillation_user_presingle

  take_oscillation_image

  oscillation_user_postsingle
  
  }'
#%IU% %MDESC% chain your code to this macro to prepare  the image taken
cdef ("oscillation_user_preimage")
#%IU% %MDESC%  chain your code to this macro to finish the image taken
cdef ("oscillation_user_postimage")
#%IU% %MDESC% chain your code to this macro to prepare  the collection
cdef ("oscillation_user_presingle")
#%IU% %MDESC%  chain your code to this macro to finish the collection
cdef ("oscillation_user_postsingle")
#%IU% %MDESC% chain your code to this macro to prepare  the collection
cdef ("oscillation_user_precollect")
#%IU% %MDESC%  chain your code to this macro to finish the collection
cdef ("oscillation_user_postcollect")
#%IU% %MDESC%  chain your code to this macro to restore in case of Ctrl-C
cdef ("oscillation_user_cleanup")

## Here example of cdef() used on ID09 (new-fast-shutter.mac file)
#cdef("oscillation_user_preimage","close_fast_shutter 36000\n","_id9_oscill")
#cdef("oscillation_user_postimage","release_fast_shutter\n","_id9_oscill")
#cdef("oscillation_user_presingle","rotate_spindle_to OSC_DATA[\"start\"];disk down\n","_id9_oscill")
#cdef("oscillation_user_postsingle","disk up;rotate_spindle_to 0\n","_id9_oscill")
#cdef("oscillation_user_precollect","disk down\n","_id9_oscill")
#cdef("oscillation_user_postcollect","disk up;rotate_spindle_to 0\n","_id9_oscill")
#cdef("oscillation_user_cleanup","disk up;release_fast_shutter\n","_id9_oscill")

#%IU% <new_position>
#%MDESC% manages absolute motions of the goniometer axis. 
def rotate_spindle_to '{
  local spindle ; spindle = OSC_SUP["gonio_n"]
  waitmove ; get_angles # needed - other motors could be out of sync
  A[spindle]=$1
  move_em
  while (chk_move)
  {
    get_angles # update motor position array A[]
    notice (sprintf("Rotating spindle... %7.2f deg (ESC to cancel)",A[spindle]))
    ESC = "\33"; if (input(-1) == ESC) {stop(); break}
  }
  sleep(.05)
  get_angles # update motor position array A[]
  notice (sprintf ("spindle at %7.3f deg",A[spindle]))
  printf("\n")
  input(1) # back to normal input mode
}'

#%IU% <amplitude>
#%MDESC% manages relative motions of the goniometer axis. 

def rotate_spindle_by '{
   waitmove; get_angles 
   A[OSC_SUP["gonio_n"]]+=$1
   move_em
   move_poll
   get_angles # update motor position array A[]
   notice (sprintf ("spindle at %7.3f deg",A[OSC_SUP["gonio_n"]]))
   printf("\n")
}'

#%IU% filename angle 
#%MDESC% Append data collection information to the log file as defined by
# OSC_DATA ["log_file"].

def oscillation_log (filename,orientation) '{
  if (OSC_DATA ["log_file"] != "")
  {
    local real_time

    log_file = OSC_DATA ["log_file"]
    
    
    file_exists = file_info (log_file,"-e") == 0 ? 0 : 1 
    
    if (! file_exists)
    {
      # Start with a short descriptions of the column format as comment line
      columns = "date     time     filename          orient. osc.  dist.[mm]"
      if (OSCILL["detector"]=="FASTSCAN")
       columns = "date|time|filename|start_angle|osc_range|expo_time|intensity_average";

      fprintf (log_file,"#%s\n",columns)
      for (i in LOGGED) LOGGED[i]=0 # nothing is logged yet
    }
   
    # Add description, exposure time and info about dark current subtraction
    # as comment lines (not always - only if changed)
    global LOGGED
    if (OSC_DATA["description"] != LOGGED["description"])
      fprintf (log_file,"# %s\n",OSC_DATA["description"])
    LOGGED["description"] = OSC_DATA["description"]
    if (OSCILL["exposure_time"] != LOGGED["exposure_time"])
      fprintf (log_file,"# Calculated exposure time %g s\n",OSCILL["exposure_time"])
    LOGGED["exposure_time"] = OSCILL["exposure_time"]


    real_time = esrf_io (OSC_SUP["watc"],"DevCntRead") / 100000
    OSCILL["real_time"] = real_time

    # image information needed for the data processing
    split (date(),word); month=word[1]; day=word[2]; dtime=word[3]; year=word[4]
    fprintf (log_file,"%02d-%s-%02d %s ",day,month,year%100,dtime)
    fprintf (log_file,"%-25s %4g %5g ",filename,orientation,OSCILL["range"]) 
    if (OSCILL["detector"]!="FASTSCAN")
    {
     # distance
     if (OSC_SUP["detec_n"]!=-1) fprintf (log_file,"%g",A[OSC_SUP["detec_n"]])
    }
    fprintf (log_file,"%7.3f ",real_time)


#	comment out following line: HW , 19.6.2000
#    oscillation_log_diode(log_file)

    fprintf (log_file,"\n")
    close (log_file)
  }
}'

#%IU%
#%MDESC% Display progres info at bottom of screen 
# This macro allows subroutine to display messages keeping a clear screen layout

def data_collection_info '
  tty_move(1,20); tty_cntl("ce") # clear to the end of line
  print '

#%IU% open_safety_shutter
#%MDESC% replaces "shopen" macro, which causes problems because it messes up the sceen
# by unnessary messages and does not wait until the shutter is actually open.  

def open_safety_shutter '
{
 if ((OSC_SUP["shutt"] != "") && OSC_SUP["open shutter"]) 
 {
  ESRF_ERR = -1 # suppress error message in case "esrf_io" signals a failure
  esrf_io (OSC_SUP["shutt"],"DevOpen")
  if (ESRF_ERR > 0) # use error code to detect failure instead 
  {
    info "Safety shutter won\'t open. Did you interlock the experiments hutch?"
  } else {
    # This shutter is slow, wait until it has has finished opening (~1.5 s)
    state = esrf_io (OSC_SUP["shutt"],"DevState")
    while (state != 4) # 1 = Fault, 2 = Moving, 3 == Closed; 4 == Open
    { 
      notice ("Opening safety shutter... (ESC to cancel)")
      if (input(-1) == "\33") { cancelled=1; break }
      state = esrf_io (OSC_SUP["shutt"],"DevState")
    }
    if (cancelled) close_safety_shutter
  }
 }
}'

def close_safety_shutter '
{
 if ((OSC_SUP["shutt"] != "")  && OSC_SUP["close shutter"])
 {
  notice ("Closing safety shutter..."); 
  esrf_io (OSC_SUP["shutt"],"DevClose")
  # Wait until the shutter has finished closing
  state = esrf_io (OSC_SUP["shutt"],"DevState")
  # 1 = Fault, 2 = Moving, 3 == Closed; 4 == Open
  while (state == 2) state = esrf_io (OSC_SUP["shutt"],"DevState")
 }
}'

#%IU% [range [exposure-time [no-of-oscillations] ] ]
#%MDESC% Takes a single oscillation image oscillating by <range> degrees 
# in positive direction from the current position. <exposure-time> is the 
# total accumulated exposure time the X-ray shutter is openend. The actual
# time used may be longer. 

def take_oscillation_image '
{
 local exposure
 local i
 
 global ID30_BMON_AVG

 if ($# >= 1) OSCILL["range"] = $1 # oscillation in deg range starting from current position
 if ($# >= 2) OSCILL["exposure_time"] = $2 # accumulated exposure time over all oscillations
 if ($# >= 3) OSCILL["number"] = $3 # number of half-oscillations
  
 if (OSCILL["range"] == 0) take_still_image # needs different synchronization
 if (OSCILL["range"] != 0)
 {
  check_oscillation_parameters # adjust the above 3 values to hardware limits

  global cancelled ; cancelled = 0
 
  range = OSCILL["range"]
  exposure_time = OSCILL["exposure_time"]
  nr_oscillations = OSCILL["number"]

  # Establish a recovery procedure when canceled by Control-C
  OSCILL ["starting_position"] = A[OSC_SUP["gonio_n"]]
  OSCILL ["normal_base_rate"] = motor_par (OSC_SUP["gonio_n"],"base_rate")
  OSCILL ["normal_steady_state_rate"] = motor_par (OSC_SUP["gonio_n"],"velocity")
  OSCILL ["normal_acceleration_time"] = motor_par (OSC_SUP["gonio_n"],"acceleration")/1000
  cdef("cleanup_once", "oscillation_cleanup();","_oscillation_")

  # Calculate the number of motor steps for one half-oscillation
  steps_per_degree = motor_par (OSC_SUP["gonio_n"],"step_size")
  steps = round (fabs (range) * steps_per_degree)
  info "steps per oscillation = "steps

  # Calculate the steady-state speed needed of the rotation motor [steps/s]
  speed = steps*nr_oscillations/exposure_time
  # ***************************** bug to be fixed here
  if (speed > 6000) speed = 6000;
  info "required speed = "speed" steps/s"

  # In case the steady state stepping speed would by slower than the preconfi-
  # gured motor base rate, this parameter has to be changed, too 

  base_rate = motor_par (OSC_SUP["gonio_n"],"base_rate")
  info "normal base rate = "base_rate" steps/s"
  
  if (base_rate >= speed)
  {
    # The base rate may be rounded up or down by the CY550 controller on the
    # ESRF VPAP card. That would no problem I the value would be rounded down.
    # But in half of the cases it is rounded up, and then the motor will allways
    # run at the base rate and never reach the steady state rate. 
  
    # A safe way to program the base rate would be to read it back from the
    # hardware and if it is higher than the base rate try a lower value.
    # Unfortunately SPEC might crash in the "setpars" routine if both values
    # happened to be equal or are rounded to an equal value (OK for the hardware).
    # Maybe there is division 0/0 when recalulating the ramp-up time?  

    # This is the beginning of a list of possible base values taken form CY550
    # handbook so I can be sure the there will be no rounding in hardware
    values = "15 100 200 300 400 500 640 750 843 926 1002 1072 1138 1199"
    split (values,base_rates)
    for (i in base_rates)
      if (base_rates[i] <= speed) { base_rate = base_rates[i]; break }
  }

  # SPEC tries to keep constant the ramp-up time so it modifies the acceleration
  # every time the base or steady state rate is changed. I will restore
  # the normal preconfigured acceleration
  
  normal_speed = motor_par (OSC_SUP["gonio_n"],"velocity")
  normal_base_rate = motor_par (OSC_SUP["gonio_n"],"base_rate")
  normal_acceleration_time = motor_par (OSC_SUP["gonio_n"],"acceleration") / 1000

  if (normal_acceleration_time > 0)
    acceleration = (normal_speed - normal_base_rate) / normal_acceleration_time
  else acceleration = 0
  if (acceleration > 0) acceleration_time = (speed - base_rate) / acceleration
  else acceleration_time = 0
  
  info "normal acceleration time = "normal_acceleration_time" s"
  info "acceleration = "acceleration" steps/s/s"
  
  # set the new parameters in hardware
  info "setting steady state rate to "speed" steps/s..."
  motor_par (OSC_SUP["gonio_n"],"velocity",round(speed)) # integer argument
  info "setting base rate to "base_rate" steps/s..."
  motor_par (OSC_SUP["gonio_n"],"base_rate",base_rate)
  info "setting acceleration time to "acceleration_time*1000" ms..."
  motor_par (OSC_SUP["gonio_n"],"acceleration",acceleration_time*1000)

  # check the values actually set in hardware
  info "checking programmed stepper motor parameters..."
  # force SPEC to update the changes now rather than at the next move command
  # and read the parameters back
  motor_par (OSC_SUP["gonio_n"],"setpars") # this command also reads back the rounded values
  base_rate = motor_par (OSC_SUP["gonio_n"],"base_rate")
  speed = motor_par (OSC_SUP["gonio_n"],"velocity")
  acceleration_time = motor_par (OSC_SUP["gonio_n"],"acceleration") / 1000
  info "base rate is now "base_rate" steps/s"
  info "steady state is now "speed" steps/s"
  info "acceleration time is now "acceleration_time*1000" ms"

  # Calculate the number of steps needed for the acceleration ramp
   
  if (acceleration_time > 0 && speed > base_rate)
  {
    acceleration = (speed - base_rate) / acceleration_time
    info "acceleration = "acceleration" steps/s/s"
    acceleration_time = (speed - base_rate) / acceleration
    acceleration_steps = (base_rate+speed)/2 * acceleration_time
    acceleration_steps = int (acceleration_steps + 1)
  }
  else
  { 
    acceleration_steps = 1
    # need at least on step to start the counter which won"t be counted
    acceleration_time = 0
  }

  # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  acceleration_steps *= 2
  # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  info "acceleration time = "acceleration_time" s"
  info "acceleration steps = "acceleration_steps
  
  # Need the acceleration range in degrees for the "mv" command
  acceleration_range = acceleration_steps / steps_per_degree
  if (range<0) acceleration_range = -acceleration_range
  # both rotations must have the same direction
  info "acceleration range = "acceleration_range" deg"
  
  # estimate the exposure time needed for the CCD detector
  detector_exposure_time = exposure_time + 2*acceleration_time*nr_oscillations
  buffer_time = 0.7 + (nr_oscillations-1)*0.6; # 0.5 and 0.4 are the real values
  detector_exposure_time += buffer_time;
  info "estimated detector exposure time = "detector_exposure_time" s"
  OSCILL["detector_exposure_time"] = detector_exposure_time

  # Configure the VCT6 channel 2 as gate gererator opening and closing
  # the X-ray shutter by counting stepper motor pulses
  esrf_io (OSC_SUP["gate"],"tcp")
  esrf_io (OSC_SUP["gate"],"DevCntClear") # must come before "DevCntInit"
  vct6_init[0] = 0 # Master
  vct6_init[1] = 0 # Gate number - not used
  vct6_init[2] = 1 # Clock source - external
  vct6_init[3] = 0 # Clock Frequency - not used
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 1 # Start counting signal - external
  vct6_init[6] = 0 # Stop counting - internal 
  vct6_init[7] = 0 # Divider mode - not used
  vct6_init[8] = 0 # Autoclear of counting value - disabled 
  vct6_init[9] = 0 # Hold counting value if external start - disabled
  vct6_init[10] = 1 # Precount clock source - external
  esrf_io (OSC_SUP["gate"],"DevCntInit",vct6_init)

  # Use the number of steps the motor needs to come up to speed as a pretrigger
  # count on "Start-In 1" before setting the "Gate-Out 1" to positive and
  # starting to count the motor steps on "In 1"
  info "VCT6 precount edge transactions = "acceleration_steps-1
  esrf_io (OSC_SUP["gate"],"DevCntEdgeSet",acceleration_steps-1)
  # set the length of the "Gate-Out 1" pulse in number motor pulses
  # coming in at "In 1"
  info "VCT6 preset value = "steps" "sprintf("(%.15f)",steps)
  esrf_io (OSC_SUP["gate"],"DevCntPresetValue",steps)
 
  # get the physical channel number from the logical one (they are different
  # if the device server handles more than one VCT6 card and if we are not
  # working on the first one)
  local gate_info[]
  esrf_io (OSC_SUP["gate"],"DevCntState",gate_info)

  # Configure the VCT6 channel 3 as a watchdog for the X-ray shutter.
  # (Channel 2 is reserved for the extral synchronizaton of the CCD camera)
  # It will be counted up by a 100kHz internal clock as long there is a positive
  # TTL signal on 1 set by VCT6 counter 1 
  esrf_io (OSC_SUP["watc"],"tcp")
  esrf_io (OSC_SUP["watc"],"DevCntClear")
  vct6_init[0] = 1 # Slave
  vct6_init[1] = gate_info[7] # Gate number to watch (if slave)
  vct6_init[2] = 0 # Clock - internal
  vct6_init[3] = 0 # Internal clock frequency - 100 kHz
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 0 # Start counting signal - internal 
  vct6_init[6] = 0 # Stop counting - internal
  vct6_init[7] = 0 # Divider mode (when free run) - disabled
  vct6_init[8] = 1 # Autoclear of counting value before each count - enabled
  vct6_init[9] = 0 # Hold counting value if external start - disabled
  vct6_init[10] = 0 # Precount clock source - internal
  esrf_io (OSC_SUP["watc"],"DevCntInit",vct6_init)
  esrf_io (OSC_SUP["watc"],"DevCntStart")

  ## has to be chained to oscillation_user_preimage
  ## see the ID09/ID30 cdef() examples
  ## close fast shutter ( remember it is inactive open)
  #close_fast_shutter 36000
  ## ce
  #shopen  

  oscillation_user_preimage
  
  open_safety_shutter
      
  # remember the current spindle orientation
  get_angles ; orientation =  A[OSC_SUP["gonio_n"]] 
  
  rotate_spindle_by -acceleration_range

  oscillation_prepare_detector
  
#	comment out: hw, 19.6.2000
#  oscillation_prepare_diode

  for (oscillation_nr = 1; oscillation_nr <= nr_oscillations; oscillation_nr++)
  {
    # Arm the VCT6 for external start - does not actually start counting
    esrf_io (OSC_SUP["gate"],"DevCntStart")

    if (oscillation_nr % 2 == 1) direction = 1; else direction = -1
    rotation_angle = direction*(acceleration_range+range+acceleration_range)
    spindle = OSC_SUP["gonio_n"]
    get_angles # update motor position array A[]
    A[spindle] += rotation_angle

    info "starting rotation of "rotation_angle" deg..."
    move_em # Start the rotation, don"t wait to complete

    # The VCT6 will open the X-ray shutter after the first
    # acceleration range and close again before the second acceleration range
    
    while (chk_move) # during exposure display progress
    {
      # Get X-ray shutter opening time as recorded by a 100 kHz clock by the 
      # for debugging VCT6 channel 3 watching the "Gate-Out 1"
      counts = esrf_io (OSC_SUP["gate"],"DevCntRead")
      opening_time = esrf_io (OSC_SUP["watc"],"DevCntRead") / 100000
      get_angles # update A[] motor position array
      rotation = sprintf ("spindle at %6.2f deg, ",A[OSC_SUP["gonio_n"]])
      exposure = sprintf ("exposure %6.3f s ",opening_time)
      notice (text=rotation exposure "(ESC to cancel)")
      sleep(0.05)
      ESC = "\33"; if (input(-1) == ESC) {cancelled=1; stop(); break}
      
    }
    # Force the X-ray shutter closed 
    # (in case of missing counts the VCT6 gate out 1 would be still at high)
    if (esrf_io (OSC_SUP["gate"],"DevState")!=2)
      esrf_io (OSC_SUP["gate"],"DevCntStop")
    
# comment out: HW, 19.6.2000
#    oscillation_read_diode

    opening_time = esrf_io (OSC_SUP["watc"],"DevCntRead") / 100000
    get_angles # update A[] motor position array
    rotation = sprintf ("spindle at %7.3f deg, ",A[OSC_SUP["gonio_n"]])
    exposure = sprintf ("exposure %6.3f s for %7.3f deg, ",opening_time,range)
    notice (text=rotation exposure)
    printf("\n")

    # for debugging check motor counts recorded by VCT6/1 and opening time by VCT6/3
    counts = esrf_io (OSC_SUP["gate"],"DevCntRead")
    info "counted "counts" of "steps" motor steps"
      
    if (cancelled) break
  }
	oscillation_nr--

  #HW070301begin and changed HW040701
  if (OSCILL["detector"] == "MAR IP" || OSCILL["detector"] == "BRUKER") {
    MM_mar_imagepar(CCD_U, orientation, orientation + OSC_DATA["step"], \
      OSCILL["exposure_time"], range, oscillation_nr, OSCILL["d_s_dist"] / 10, OSCILL["wavelength"])
  }
  #HW070301end
   
  ## has to be chained to oscillation_user_postimage
  ## see the ID09/ID30 cdef() examples
  ## reopen fast shutter
  #release_fast_shutter
  
  close_safety_shutter
  
  oscillation_user_postimage 
  
  oscillation_readout_detector

  # Restore the parameters in the stepper motor controller as they where before
  oscillation_restore()
  
  if (! cancelled)
  {
    filename = data_set ("filename",OSC_DATA["image_number"])
    if (filename != "") save_oscillation_image (filename);
    oscillation_log (filename,orientation)
    if (filename != "") OSC_DATA["image_number"]++
  } 
 }
}' 

#%IU% %MDESC% Used instead of "take_oscillation_image" when the oscillation
# range is set to 0. If the spindle axis is not rotated the synchronziation of
# the X-ray shutter must be done in a different way.

def take_still_image '
{
  open_safety_shutter # if not already open

  # In case of emergency stop by Control-C close the X-ray shutter
  cdef("cleanup_once","esrf_io (OSC_SUP[\"gate\"],\"DevCntStop\");", "_oscillation_")

  oscillation_prepare_detector

  # configure the VCT6 
  esrf_io(OSC_SUP["gate"],"DevCntClear")
  local vct6_init
  vct6_init[0] = 0 # Mode = master
  vct6_init[1] = 0 # Gate number - not used for master
  vct6_init[2] = 0 # Clock - 0 = internal, 1 = external
  vct6_init[3] = 0 # Clock Frequency - 100kHz if internal, not used if external
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 0 # Start counting signal - internal
  vct6_init[6] = 0 # Stop counting - internal
  vct6_init[7] = 0 # Divider mode - not used (only for free-run)
  vct6_init[8] = 0 # Autoclear of counting value before each count (slave only) 
  vct6_init[9] = 0 # Hold counting value if external start - unused (slave only)
  vct6_init[10] = 0 # Precount clock source - internal
  esrf_io(OSC_SUP["gate"],"DevCntInit",vct6_init)
  esrf_io(OSC_SUP["gate"],"DevCntPresetValue",OSCILL["exposure_time"]*100000) 
  esrf_io(OSC_SUP["gate"],"DevCntStart") 

  # wait for the exposure to finish - show progres info
  local busy opening_time
  busy = esrf_io (OSC_SUP["gate"],"DevCntStatus") & 0x800
  while (busy && ! cancelled)
  {
    opening_time = esrf_io (OSC_SUP["gate"],"DevCntRead") / 100000
    notice (sprintf ("Exposure %4.1f s (ESC to cancel)",opening_time))
    # Give the user a chance to cancel by hitting [Escape]
    ESC = "\33" ; if (input(-1) == ESC) { cancelled=1; break }
    busy = esrf_io (OSC_SUP["gate"],"DevCntStatus") & 0x800
    sleep(.05)
  }
  if (cancelled) oscillation_restore()
   
  opening_time = esrf_io (OSC_SUP["gate"],"DevCntRead") / 100000
  info "X-ray shutter opening time was "opening_time" s"
  
  oscillation_readout_detector
  
  oscillation_restore()
  
  if (! cancelled)
  {
    local filename
    filename = data_set ("filename",OSC_DATA["image_number"]++)
    if (filename != "") save_oscillation_image (filename)
    oscillation_log (filename,orientation)
  }   
}'

#%IU%
#%MDESC% There are hardware constraints which limit the allowed combinations
# of oscillation range, exposure time and number of oscillations. This routine
# will try to find the closest possible set of parameters to a given set
# first modifying the number of oscillations if the rotation speed is too low or 
# too high, then exposure time, but leaving constant the rotation angle.
# The minimum stepping speed of the gonio-rotation is 15 steps/s ,as for all
# stepper motors controlled by an ESRF VPAP card. In principle there is no minimum
# rate for a stepping motor, but unfortunatly the value of 15 Hz is hard coded
# in the CY550 microcontroller the VPAP is based on. The maximum stpping is
# taken as the one preconfigured by SPEC at startup. You may increase it
# using "config" if you feel confident about this.
# The parameters oscillation range, exposure time and number of oscillations
# are passed and returned in a global array called "OSCILL".

def check_oscillation_parameters '
{
 if (OSCILL["range"] && OSCILL["number"])
 {
  local range exposure_time actual_exposure_time speed actual_speed nr_oscillations
  
  if( OSCILL["exposure_time"]+0 <= 0) OSCILL["exposure_time"] = 0.01
  range = OSCILL["range"]
  exposure_time = OSCILL["exposure_time"]
  nr_oscillations = 1 # Reset the number of oscillations to 1.

  # save the normal motor parameters to be restored at the end of this macro
  normal_base_rate = motor_par (OSC_SUP["gonio_n"],"base_rate")
  normal_speed = motor_par (OSC_SUP["gonio_n"],"velocity")
  normal_acceleration_time = motor_par (OSC_SUP["gonio_n"],"acceleration")/1000

  # Calculate the number of motor steps for one half-oscillation
  steps_per_degree = motor_par (OSC_SUP["gonio_n"],"step_size")
  info "motor needs "steps_per_degree" steps/deg"
  steps = int (fabs (range) * steps_per_degree)
  info "steps per oscillation = "steps

  # Strange feature of the VCT6: if external start is used the count number
  # must be not be even, otherwise a miscounting in the range -1 to -127 occurres
  if (steps % 2 == 0) steps++
  info "steps per oscillation set to "steps" (VCT6 odd feature)"
  range = (range > 0 ? 1 : -1) * steps / steps_per_degree
  info "oscillation range set to "range" deg"

  # Calculate the stady-state speed needed of the rotation motor [steps/s]
  speed = steps*nr_oscillations/exposure_time
  info "required speed = "speed" steps/s"

  # maybe the number of oscillations has to be reduced because the motor can-
  # not run fast enough, I presume the maximum speed is the one set by "config"
  if (speed > normal_speed) {
    speed = normal_speed
    info "speed set to "speed" steps/s (must not exceed "normal_speed" steps/s)"
  }
  
  if (speed <= normal_base_rate) {
    speed = normal_base_rate
    info "speed set to "speed" steps/s (must not be lower than "normal_base_rate" steps/s)"  
  }
   
  # stepping rate might be quantized in hardware, try out and read back

  # compare with what is feasible due to hardware constraints
  motor_par (OSC_SUP["gonio_n"],"velocity",speed)
  motor_par (OSC_SUP["gonio_n"],"base_rate",speed)
  motor_par (OSC_SUP["gonio_n"],"get_pars")
  
  actual_speed = motor_par (OSC_SUP["gonio_n"],"velocity")
  info "actual speed is "actual_speed" steps/s (hardware constraints)"

  actual_exposure_time = nr_oscillations * steps / actual_speed
  info "exposure time set to "actual_exposure_time" s (number of oscillations changed)"

  #if the actual exposure time defined by hardware is differing by more
  #than 5% of what was asked by the user, ramp up the number of oscillation
  #until this condition is matched.

  while ( fabs(exposure_time-actual_exposure_time) > max2(0.05,0.05*exposure_time)) {
   speed = steps*nr_oscillations/exposure_time
   
   if (speed <= normal_base_rate) {
    speed = normal_base_rate
    info "speed set to "speed" steps/s (must not be lower than "normal_base_rate" steps/s)"  
   }

   motor_par (OSC_SUP["gonio_n"],"velocity",speed)
   motor_par (OSC_SUP["gonio_n"],"base_rate",speed)
   motor_par (OSC_SUP["gonio_n"],"get_pars")
  
   actual_speed = motor_par (OSC_SUP["gonio_n"],"velocity")
   info "actual speed is "actual_speed" steps/s (hardware constraints)"

   actual_exposure_time = nr_oscillations * steps / actual_speed
   info "exposure time set to "actual_exposure_time" s (number of oscillations changed)"

   nr_oscillations++
  }

  # the chosen exposure time is the actual_exposure_time last chosen by
  # hardware
  exposure_time = actual_exposure_time

  # Fill the global variables  
  OSCILL["range"] = range
  OSCILL["exposure_time"] = exposure_time
  OSCILL["number"] = nr_oscillations
  
  # Restore the parameters in the stepper motor controller as they where before
  motor_par (OSC_SUP["gonio_n"],"velocity",normal_speed)
  motor_par (OSC_SUP["gonio_n"],"base_rate",normal_base_rate)
  motor_par (OSC_SUP["gonio_n"],"acceleration",normal_acceleration_time*1000)
 }
 angular_step = OSC_DATA["step"] ; start=OSC_DATA["start"] ; end=OSC_DATA["end"]
 steps_per_degree = motor_par (OSC_SUP["gonio_n"],"step_size")
 steps = int (fabs(angular_step) * steps_per_degree)
 info "angular step "steps" motor steps"
 if (steps == 0) steps = 1
 info "angular step "steps" motor steps (must no be zero)"
 angular_step =  steps / steps_per_degree * (angular_step > 0 ? 1 : -1)
 info "angular step set to "angular_step" deg (motor step size)"
 if (sign(angular_step) != sign(end-start)) angular_step *= -1
 info "angular step set to "angular_step" deg (must lead from start to end)"
 OSC_DATA["step"] = angular_step 
}'  

#%IU%
#%MDESC% This Macro is executed when an oscillation image is cancelled 
# by Control-C. It closes the ms shutter, sets the original values for the 
# speed of the rotation motor gonio and rotates it back to the starting 
# position. 
def oscillation_cleanup() '{  
 
  close_safety_shutter

  oscillation_user_cleanup
  
  oscillation_restore()
}'

#%IU%
#%MDESC% restore default settings of the VCT6 and of the spindle motor
def oscillation_restore () '{
  # Close the X-ray shutter controlled by the VCT6 gate 1 output
  info "closing shutter..."
  ESRF_ERR=-1
  if (esrf_io (OSC_SUP["gate"],"DevState")!=2)
    esrf_io (OSC_SUP["gate"],"DevCntStop")
  if(ESRF_ERR) {
	printf("Error accessing %s\n",OSC_SUP["gate"])

  esrf_io(OSC_SUP["gate"],"DevCntClear")
  }
  # re-configure the VCT6 like a counter/timer [0,2,0,1,0,0,0]
  local vct6_init 
  vct6_init[0] = 0 # Mode = master
  vct6_init[1] = 2 # Gate number - not used for master
  vct6_init[2] = 0 # Clock - 0 = internal, 1 = external
  vct6_init[3] = 1 # Clock Frequency - 100kHz if internal, not used if external
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 0 # Start counting signal - internal
  vct6_init[6] = 0 # Stop counting - internal
  vct6_init[7] = 0 # Divider mode - not used (only for free-run)
  vct6_init[8] = 0 # Autoclear of counting value before each count (slave only) 
  vct6_init[9] = 0 # Hold counting value if external start - unused (slave only)
  vct6_init[10] = 0 # Precount clock source - internal
  ESRF_ERR=-1
  esrf_io(OSC_SUP["gate"],"DevCntInit",vct6_init)
  if (ESRF_ERR) {
    	printf("Error accessing %s\n",OSC_SUP["gate"])
  }
  
  # End the CCD exposure by resetting the gate output 2 to 0
  # of the VCT6 to 0
  if (OSCILL["detector"]=="XRII/CCD") {
  	ESRF_ERR=-1
        if (esrf_io (OSC_SUP["trig"],"DevState")!=2)
 	  esrf_io(OSC_SUP["trig"],"DevCntStop")
  	if(ESRF_ERR) {
		printf("Error accessing %s\n",OSC_SUP["trig"])
  	}
  }

  # Restore the normal parameters in the stepper motor controller
  # i.e. base rate = 500 steps/s and steady state rate = 1000 steps/s for gonio
  info "resetting motor parameters..."
  motor_par (OSC_SUP["gonio_n"],"velocity",OSCILL ["normal_steady_state_rate"])
  motor_par (OSC_SUP["gonio_n"],"base_rate",OSCILL ["normal_base_rate"])
  motor_par (OSC_SUP["gonio_n"],"acceleration",OSCILL["normal_acceleration_time"]*1000)
}'

# Detector independent macros

# %MDESC% Detector independent setup menu. You can add your detector's
# "setup" macro here.

def oscillation_detector_setup '
{
  if (OSCILL["detector"] == "XRII/CCD") { xrii_setup }
  if (OSCILL["detector"] == "MAR IP")   { oscillation_mar_setup() }
  if (OSCILL["detector"] == "BRUKER")   { oscillation_bruker_setup() } #HW040701
  if (OSCILL["detector"] == "FASTSCAN") { fsds_osc_setup() }
}'

# %MDESC% Detector independent macro called once before the exposure starts
# You can add your detector's "prepare" macro here. You should use a function
# macro so in case it included macros from other file which are not normally
# loaded

def oscillation_prepare_detector '
{
  if (OSCILL["detector"] == "XRII/CCD") { prepare_ccd() } 
  if (OSCILL["detector"] == "MAR IP")   { prepare_mar() } # added HW 29/1/2001
  if (OSCILL["detector"] == "BRUKER")   { prepare_bruker() } #HW040701
  if (OSCILL["detector"] == "FASTSCAN") { fsds_osc_prepare() }
}'

# %MDESC% Detector independent macro called once after the end of the exposure
# You can add your detector's "readout" macro here.

def oscillation_readout_detector '
{
  if (OSCILL["detector"] == "XRII/CCD") { readout_ccd() } 
  if (OSCILL["detector"] == "MAR IP")   { readout_mar() } 
  if (OSCILL["detector"] == "BRUKER")   { readout_bruker() } #HW040701
  if (OSCILL["detector"] == "FASTSCAN") { fsds_osc_readout()}
}'

# %MDESC% Detector independent macro called once after the end of the exposure
# You can add your detector's "save_image()" macro here.

def save_oscillation_image (filename)' {
  if (OSCILL["detector"] == "XRII/CCD") { save_ccd_image (filename) }
  if (OSCILL["detector"] == "FASTSCAN") { fsds_osc_save(filename)}
}'

# CCD detector related macros

#%IU% (parameter,value)
#%MDESC% General setup for CCD detector (Princeton or FRELON)

def ccd (parameter,value) '{
  if (whatis("CCD",1) != "A global array.") restore_array CCD

  local prompt choices choice

  if (parameter == "dark current") return CCD ["dark_current"]
  else if (parameter == "dark current?")
  {
    choices = "\[us]\[md]s\[me]\[ue]ubtract, save to \[us]\[md]f\[me]\[ue]ile, do \[us]\[md]n\[me]\[ue]othing?"
    choice = key (prompt="Dark current: "choices" ")
    if (choice == "s") CCD ["dark_current"] = "subtract"
    if (choice == "f") CCD ["dark_current"] = "save to file"
    if (choice == "n") CCD ["dark_current"] = "do nothing"
  }
  else if (parameter == "dark current file") return CCD ["dark_current_file"]
  else if (parameter == "dark current file?") CCD ["dark_current_file"] = \
    getval ("Save dark image to",CCD ["dark_current_file"])
  else if (parameter == "exposure time") return CCD ["exposure_time"]
  else if (parameter == "exposure time?")
    CCD ["exposure_time"] = getval ("Exposure time",CCD ["exposure_time"])
  else if (parameter == "?")
    print "dark current, dark current file, exposure time"
  else
  {
    print "\""parameter"\" is not a parameter for ccd() (\"?\" for a list)"
    exit
  }
  
  if (index (parameter,"?") > 1) save_array CCD
}'

#%MDESC% Configure the detector and start the exposure. Works for both FRELON
# and Princeton CCD.

def prepare_ccd() '{
  # Preprogramming an exposure time of 0 put both Princeton and Frelon CCD
  # into external trigger mode and disables the continuos cleans of the 
  # Princeton CCD 
  image_par (0,"preset",0)

  # make sure that the memory buffer to hold the image is allocated
  ccd_createarray 
 
  # configure the VCT6 channel 2 to trigger the CCD camera (used by Frelon only)
  vct6_init[0] = 0 # Mode = master
  vct6_init[1] = 0 # Gate number - not used for master
  vct6_init[2] = 0 # Clock - 0 = internal, 1 = external
  vct6_init[3] = 0 # Clock Frequency - 100kHz if internal, not used if external
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 0 # Start counting signal - internal
  vct6_init[6] = 0 # Stop counting - internal
  vct6_init[7] = 0 # Divider mode - not used (only for free-run)
  vct6_init[8] = 0 # Autoclear of counting value before each count (slave only) 
  vct6_init[9] = 0 # Hold counting value if external start - unused (slave only)
  vct6_init[10] = 0 # Precount clock source - internal
  esrf_io(OSC_SUP["trig"],"DevCntInit",vct6_init)
  esrf_io(OSC_SUP["trig"],"DevCntPresetValue",1e9) # 10000 s

  ccdstart ; # start Princeton CCD - Frelon needs this as well
  esrf_io(OSC_SUP["trig"],"DevCntStart") # start Frelon CCD 
}'

#%MDESC% Stops the exposure and waits for the data transfer to the workstation's
# memory to finish. The image content will be in the global array image_data.
# The image is not save as a file. You have to call "save_ccd_image" to do this.
# Works for both FRELON and Princeton CCD.

def readout_ccd() '{
  global CCD, cancelled
  # End the CCD exposure and start the readout by resetting the gate output 2
  # of the VCT6 to 0
  if (esrf_io (OSC_SUP["trig"],"DevState")!=2)
    esrf_io(OSC_SUP["trig"],"DevCntStop")
  ccd_exposure_time = esrf_io (OSC_SUP["trig"],"DevCntRead") / 100000
  info "CCD exposure time was "ccd_exposure_time" s"
  CCD["exposure time"] = ccd_exposure_time

  # Wait for the chip readout and transfer to the computer to finish
  start=time()
  while (wait (0x24) && ! cancelled)
  { 
    # Give the user a chance to cancel by hitting any key
    notice (sprintf ("Reading out CCD... %3.1f s (ESC to cancel)",time()-start))
    ESC = "\33"; if (input(-1) == ESC) cancelled=1
  }
  if (! cancelled) 
  {
    info "Copying image to buffer..."
    ccdread
    info "image done"
    camera_online # Bring up image display window
  }
}'

def save_ccd_image (filename) '{
  correct_image
  save_image (filename)
}'

#HW040701 added bruker functions 
# Bruker CCD detector related code

#%MDESC% Setup menu
def oscillation_bruker_setup() '{
}'

#%MDESC% Configure the Bruker CCD detector and start the exposure.
def prepare_bruker()  '{
  #add the safe way of putting the filename into the server.
	local exp_t dummy
  _ccd_remote_file_setup CCD_U OSC_DATA["dir"] OSC_DATA["basename"] \
    dummy OSC_DATA["image_number"]
  #start exposure for exposure time plus how much 50ms ?
	exp_t = sprintf("%f", OSCILL["exposure_time"] + 0.05)
	ccdstart exp_t
}'

#%MDESC% starts the acquisition in the Bruker detector PC. 
def readout_bruker() '{
 	waitacq2
  ccdsave
}'

# MAR Image plate scanner related code
#HW300101, added a number of menu points
#%MDESC% Setup menu

def oscillation_mar_setup() '{
 restore_array MAR
 local letter i; letter = ""
 if (MAR["glimpse"] == "") MAR["glimpse"] = "no"
 if (MAR["factor"]  == "") MAR["factor"]  = "10"
 #HW070301begin
 if (MAR["ccd_u"] == "") {
   for (i = 0; i < NO_CCDS; i++) {
     if (index(sprintf("%s", image_par(i, "device_id")), "mar345") ) {
       MAR["ccd_u"] = i; break;
     }
   }
 }
 #HW070301end
 
 while (letter != "\n")
 {
  local text
  
  clscreen() # clear screen
  tty_move ( 1, 1, "\[md]\[us]MAR IMAGE PLATE SCANNER SETUP\[me]\[ue]")
 
  tty_move ( 1, 6, "\[us]\[md]d\[me]\[ue]isplay a low resolution glimpse (thumbmail):")
  tty_move (60, 6, MAR["glimpse"]?"YES":"NO")

  tty_move ( 1, 7, "The glimpse should be smaller by a \[us]\[md]f\[me]\[ue]actor of :")
  tty_move (60, 7, MAR["factor"])

  tty_move ( 1, 9, "Set the detector f\[us]\[md]o\[me]\[ue]rmat :")
  
  tty_move (1, 22,"--> Type underligned letter (lower case, [Return] to quit) ");
  
  # wait until user types hits a key
  letter = input(-1) ; while (asc(letter) == 0) letter = input(-1) ; input(1)
  
  tty_move (1,22,"--> \[ce]"); # clear to the end of line
  
  if (letter == "p") { tty_move (20,4); ccd_remote_file_setup}
  if (letter == "d") { tty_move (60,6); MAR["glimpse"] = yesno("", MAR["glimpse"]) }
  if (letter == "f") { tty_move (60,7); edit MAR["factor"] 59 }
  if (letter == "o") { 
    __mode = getval("Mode [1800,1200,2400,1600,3000,2000,3450,2300]",\
    image_par(MAR["ccd_u"],"mode"))  
    image_par(MAR["ccd_u"],"mode",__mode)
 	}
 }
 image_par(MAR["ccd_u"], "view_factor", MAR["factor"])#HW300101 set the above display factor
 #HW300101 need to redo the array and stuff
 ccd_createarray
 if (MAR["glimpse"]) {
   #HW300101 start onze with appr. arguments
   camera_online MAR["ccd_u"]
 } else { # or take onze away.
  local pid file guicmd arrayname
  file = sprintf("/tmp/disgui_%s_%s.pid", USER, SPEC)
  hwcommand = sprintf("kill `cat %s >/dev/null` 2>&1", file)
  unix(sprintf("kill `cat %s` >/dev/null 2>&1", file), output)
  unix(sprintf("/bin/rm -f %s",file))

 }
 save_array MAR 
}'

#%MDESC% Configure the MAR image plate scanner and start the exposure.
def prepare_mar()  '{
  #HW070301begin: add the safe way of putting the filename into the server.
  local temp1 temp2
  
  #LC170403begin: ask spec to load the file parms from the server.
  #that avoids error message about wrong file parameters !!
  image_par(MAR["ccd_u"],"file_dir")
  #LC170403end
  
  temp1 = sprintf("%s/", OSC_DATA["dir"]); temp2=""
  _ccd_remote_file_setup MAR["ccd_u"] temp1 OSC_DATA["basename"] \
    temp2 OSC_DATA["image_number"]
  #HW070301end
  # set overwrite mode of file
  image_par(MAR["ccd_u"],"overwrite",1)
  ccd_createarray
}'

#%MDESC% Scans and erase the MAR IP scanner
def readout_mar() '{
  # the newer implementation of the MAR IP device server make an automatic 
  # copy of the data as soon as it is taken.
  # show the glimpse if demanded in setup
	ccdstart
 	waitacq2
  if ( MAR["glimpse"] ) ccdread
}'

# FASTSCAN Image plate scanner related code

#%MDESC% Setup menu
def fsds_osc_setup() '{
  info sprintf("FASTSCAN: doing setup\n")
}'

#%MDESC% 
def fsds_osc_prepare() '{
  info sprintf("FASTSCAN: preparing reading\n")
}'

#%MDESC%
def fsds_osc_readout() '{
  local counts

  # make sure diode 2 is out
  diode 2 off
 
  # move diode 0 to expose the steel
#  diode 0 off

  # move out the beam stop
#  umvr bsz -4

  # locate the direct beam on the image plate without burning it!
#  counts = OSC_SUP["fs_open"] # here are 100000 to much ! #* 100000
#  counts = OSCILL["exposure_time"]
#  f_open_ms_shutter(counts)

  # move in the beam stop
#  umvr bsz 4

  # move diode 0 back in
#  diode 0 on

  # read the image plate
  printf("\tFASTSCAN: Reading Image Plate with Fastscan on the PC\n")
  if(fsds_go()) {
   printf("\n\nReading aborted!! No data saved!\n\n")
   exit
  }


}'

#%MDESC%
def fsds_osc_save(filename) '{
  local dirs
  local ndirs
  local i
  local pc_filename
    
  ndirs = split(filename,dirs,"/")
  pc_filename = dirs[0]
  for (i=1;i<ndirs;i++)
   pc_filename = sprintf("%s\\\\%s",pc_filename,dirs[i]) 

  printf("\tFASTSCAN: saving in file: %s ",pc_filename)
  if(fsds_save(pc_filename,0)) {
   printf("\n\nSaving aborted!! No data saved!\n\n")
   exit
  }
  printf("done\n")
}'




#%UU% [on|off]
#%MDESC% use this macro to obtain more detailed information what is happening
# in case off problems. Informational messages will be written line by line
# rather than on the status line on the botton of the screen erasing each other.

def oscillation_debug '
{
  if ("$1" == "on" || $# == 0)
  {
    # for diagnotic purposes display all messages line by line  
    global overwrite
    rdef info "overwrite=0; print"
  }
  if ("$1" == "off")
  {
    # display all messages on a single line at the bottom of the screen
    # so they do not mess up the layout
    rdef info "dont_print="
  }
}'

##%UU% [seconds]
##%MDESC% Utility to open the millisecond X-ray shutter manually for alig-
# nment (without current closed).
# Useful when the shutter is controlled by VCT6 as for the oscillation data 
# collection. 
# If you give an argument the shutter will close automatically after the
# given number of seconds. Otherwise it will reain open unlimited.
# (But note that the maximum programmable time of the VCT6 is about 12 h.)
##%PRE%
# 
# VCT6 1 (mezzanine card, TTL) --> to shutter
# 
##%PRE%

#def open_ms_shutter '
#{
#  local counts
#  # if argument program opening for this time else use maximum value
#  if ($# > 0) counts = $1*100000 ; else counts = pow(2,32)-1
#
#  f_open_ms_shutter(counts)
#}'

##%IU% 
##
#def f_open_ms_shutter(counts) '{
#  # configure the VCT6 
#  local vct6_init

#  vct6_init[0] = 0 # Mode = master
#  vct6_init[1] = 0 # Gate number - not used for master
#  vct6_init[2] = 0 # Clock - 0 = internal, 1 = external
#  vct6_init[3] = 0 # Clock Frequency - 100kHz if internal, not used if external
#  vct6_init[4] = 0 # Free run mode - no
#  vct6_init[5] = 0 # Start counting signal - internal
#  vct6_init[6] = 0 # Stop counting - internal
#  vct6_init[7] = 0 # Divider mode - not used (only for free-run)
#  vct6_init[8] = 0 # Autoclear of counting value before each count (slave only) 
#  vct6_init[9] = 0 # Hold counting value if external start - unused (slave only)
#  vct6_init[10] = 0 # Precount clock source - internal
#  esrf_io(OSC_SUP["gate"],"DevCntInit",vct6_init)
#  esrf_io(OSC_SUP["gate"],"DevCntPresetValue",counts) 
#  esrf_io(OSC_SUP["gate"],"DevCntStart") 
#}'

##%UU% %MDESC% Utility to close the shutter again after "open_ms_shutter". 

#def close_ms_shutter '
#{
#  esrf_io(OSC_SUP["gate"],"DevCntStop")
#}'

##%UU% %MDESC% Utility to wait for the shutter closing
## after "open_ms_shutter opening_time". 

#def wait_ms_shutter '{
#  while(esrf_io(OSC_SUP["gate"],"DevCntStatus") == 2048) sleep(0.1);
#}'

# The following are general utility macros

#%IU%
#%MDESC% round up to the next larger or equal integer, as opposed to int()
# which rounds down

def ceil(x) '{if (int(x) == x) return x; else return int(x+1) }'

#%IU%
#%MDESC% round up to the next integer, as opposed to int()
# which rounds down

def round(x) '{if (x>=0) return int(x+0.5); else return int(x-0.5) }'

#%IU% <message> ...
#%MDESC% "info" used to display informational messages which can be
# helpful for debugging. The reason not simply to use "print" is to the
# collect macro can redirect or supress these messages by redefining "info" 

def info 'dont_print='

# %IU% (message)
# %MDESC% utility for display informational messages.

def notice (message) '{
  tty_cntl ("ce") # clear to end of line
  tty_move (1000,1000,message) # print message starting from current position 
  tty_move (-1000-length(message),1000) # move cursor back to starting position 
}'

#%IU% ([prompt])  
#%MDESC% Utility for keybard input in single character no-echo mode.
# The prompt string is optional.

def key (prompt) '{
  if (! (whatis("prompt") & 0x08000000)) print_underlined_highlighted (prompt)
  local letter
  letter = input(-1); while (asc(letter) == 0) letter = input(-1) ; input(1)
  return letter
}'

#%IU% <variable> [<max length>]

def edit '{$1 = editline($1,$2)}'

#%IU% (template-text[,max_length])
#%MDESC% Prompts the user for test input, providing a template, like SPEC's
# built-in getval(), but you do not have to retype it but can modify it
# using the cursor keys. 
# Note: The cursor keys work only on VT100 terminal emulators, "xterm" and
# "dtterm" are compatible with VT100, however "hpterm" is not. 

def editline (template,max) '{
  if (max == 0) max = 80
  
  global linebuffer, maxlength, start, cursorpos, currentpos
  
  linebuffer = template ; maxlength = max
  cursorpos = length (template) # place the cursor at end of line
  # text might be longer than avaiable space 
  start = 0; currentpos = 0

  update_line

  local c ; c = ""
  
  while (c != "\n")
  {
    # wait for charater to be typed, used char-by-char input mode (-1)
    c = input (-1); while (asc(c) == 0) c = input (-1) 

    if (asc(c) == 27) escape_sequence
    if (asc(c) == 8) delete_char
    if (asc(c) == 127) delete_char
    if (asc(c) >= 32 && asc(c) < 127) insert_char(c)

    update_line
  }
  input (1) # back to line-by-line input mode (1)
  
  return linebuffer
}'

#%MDESC% used by "editline"

def update_line '
{
  scroll # make sure that cursor is within bounds
  # relative cursor positioning is done by adding an offset of 1000 or -1000
  tty_move(-1000-currentpos,1000)
  local text; text = substr(linebuffer,start+1,maxlength)
  while (length(text) < maxlength) text = text" " # pad with blanks
  printf ("%s",text)
  tty_move(-1000-length(text),1000)
  tty_move(1000+cursorpos-start,1000)
  currentpos = cursorpos-start
}'

#%MDESC% used by "editline". Scroll text so cursor is within bounds

def scroll '
{
  if (cursorpos >= start+maxlength) start = cursorpos-maxlength+1;
  if (cursorpos < start) start = cursorpos;
}'

#%MDESC% used by "editline". Decode VT100 escape sequences generated by the 
# cursor keys

def escape_sequence '
{
  local c
  # wait for end of escape-seqence (always with upper-case letter)
  while (asc(c) < asc("A") || asc(c) > asc("Z")) c = input(-1)
  if (c == "C") cursor_right
  if (c == "D") cursor_left
  # ignore all other escape-codes
}'

#%MDESC% used by "editline"

def cursor_left 'if (cursorpos > 0) cursorpos--'

#%MDESC% used by "editline"

def cursor_right 'if (cursorpos < length (linebuffer)) cursorpos++'

#%MDESC% used by "editline"

def insert_char(c) '{
  part1 = substr (linebuffer,1,cursorpos)
  part2 = substr (linebuffer,cursorpos+1)
  linebuffer = part1 c part2 # concatenate strings
  cursorpos++
}'

#%MDESC% used by "editline"

def delete_char '{
  if (cursorpos > 0)
  {
    part1 = substr (linebuffer,1,cursorpos-1)
    part2 = substr (linebuffer,cursorpos+1)
    linebuffer = part1 part2 # concatenate strings
    cursorpos--
  }
}'

#%IU% <array>
#%MDESC% Useful for save global variables across a "spec -f".
# Writes the contents of all members of an array to an ASCII file,
# so they can be reloaded later by the "restore_array" macro. The file is in
# the directory ~specadm/local/userconf and has the same name as the array
# (no extension).

def save_array '
{
  local dir file command
  
  dir = BLISSADM"/local/userconf/"SPEC
  if (file_info (dir,"isdir") != 1) unix (command="mkdir -p "dir)
  
  file = dir"/$1"  
  unix (command = "rm -f "file)

  for (i in $1) fprintf (file,"%s=%s\n",i,$1[i])
  close (file)
}'

#%IU% <array>
#%MDESC% Loads back saved values from an ASCII file from a previous "save_array"
# call.

def restore_array '
{
  local file line words tag value n
  
  file = BLISSADM"/local/userconf/"SPEC"/$1"
  
  if (file_info(file,"-e") == 1)
  {
    global $1
    getline (file,"open")
  
    while ((line = getline(file)) != -1)
    {
      n = split (line,words,"="); tag = words[0]; value = words [1]
      if (n < 1) continue
      split (value,words,"\n"); value = words [0] # get rid of trailing new-line
      if (!(tag in $1)) $1[tag] = value
    }
    getline (file,"close")
  }
}'

#%IU% (x)
#%MDESC% returns 1 if x>0 -1 if x<0 0 if x=0

def sign (x) '{
  if (x>0) return 1
  if (x<0) return -1
  if (x==0) return 0
}'

#%IU% (string,n)
#%MDESC% returns the last n characters of a string. If the sting length
# is less the n then the full string is returned 

def tail (s,n) '{ return substr(s,length(s)-n+1) }'



# #%IU% (n1,n2)
# #%MDESC% returns the greater value of the two input arguments.
# 
# def max(n1,n2) '{ return n1>n2?n1:n2 }'
#
# please use spec_utils.mac version.




#
# Beam intensity measurement macros
#

#%IU%
# %MDESC% Beam intensity measurement macro, called once before the exposure (ie
# before the set of half-oscillations).
def oscillation_prepare_diode '{
  local i
  local vct6_init
  local gate_info[]

  

  ID30_BMON_AVG = 0

  # Look for the VCT6 channel used for beam monitor counter
#  for(i=0; (i<COUNTERS) && (cnt_mne(i)!=OSC_SUP["b_mon_mne"]); i++);
#  if(i == COUNTERS)
#  {
#   printf("\nError: VCT6 beam monitor counter (%s) not found\n", \
#	OSC_SUP["b_mon_mne"])
#   exit
#  }

#  OSC_SUP["b_mon"] = OSC_SUP ["vct6"] "/" counter_par(i,"channel")
  OSC_SUP["b_mon"] = OSC_SUP ["vct6"] "/" OSC_SUP["b_mon_mne"]
  info sprintf("VCT6 beam monitor counter (%s) using: %s", \
	OSC_SUP["b_mon_mne"], OSC_SUP["b_mon"])

  # get the physical channel number from the logical one (they are different
  # if the device server handle more than one VCT6 card and if we are not
  # working on the first one)
  esrf_io (OSC_SUP["gate"],"DevCntState",gate_info)

  # Configure a VCT6 channel to count during the fast shutter opening time
  esrf_io (OSC_SUP["b_mon"],"tcp")
  esrf_io (OSC_SUP["b_mon"],"DevCntClear")
  vct6_init[0] = 1 # Slave
  vct6_init[1] = gate_info[7] # Gate number to watch (if slave)
  vct6_init[2] = 1 # Clock - External
  vct6_init[3] = 0 # Internal clock frequency - 100 kHz
  vct6_init[4] = 0 # Free run mode - no
  vct6_init[5] = 0 # Start counting signal - internal 
  vct6_init[6] = 0 # Stop counting - internal
  vct6_init[7] = 0 # Divider mode (when free run) - disabled
  vct6_init[8] = 1 # Autoclear of counting value before each count - enabled
  vct6_init[9] = 0 # Hold counting value if external start - disabled
  vct6_init[10] = 0 # Precount clock source - internal
  esrf_io (OSC_SUP["b_mon"],"DevCntInit",vct6_init)
  esrf_io (OSC_SUP["b_mon"],"DevCntStart")

  info "VCT6 beam monitor counter started"
}'

#%IU%
# %MDESC% Beam intensity measurement macro, called during the exposure after
# each  half-oscillation.
def oscillation_read_diode '{
  local b_mon_val

  info "measuring beamcurrent"
  b_mon_val = esrf_io (OSC_SUP["b_mon"],"DevCntRead")
  info sprintf("Beam monitor intensity: %8d ", b_mon_val)

  ID30_BMON_AVG += b_mon_val
}'

#%IU%
# %MDESC% Beam intensity measurement macro, called during the exposure after
# each  half-oscillation.
def oscillation_log_diode(d_log_file) '{
  local b_mon_val
 
  b_mon_val = ID30_BMON_AVG/OSCILL["number"]/OSCILL["exposure_time"]
  fprintf (d_log_file,"%8d ", b_mon_val)
  info sprintf("Beam monitor intensity per second : %8d ", b_mon_val)
}'




#%IU%
# %MDESC% Enter the speed parameter with some value check.
#
def enter_speed_parameter '{
  local max_speed
  local new_speed
  local expo_time

  max_speed = motor_par (OSC_SUP["gonio_n"],"velocity")
  
  expo_time = getval("Exposure time [s]",OSCILL["exposure_time"])+0
  new_speed = calculate_speed(expo_time) 

  while(new_speed > max_speed)
  {
   printf("Warning: too short exposure time !!\n")
   expo_time = getval("Exposure time [s]",OSCILL["exposure_time"])+0
   new_speed = calculate_speed(expo_time) 
  }

  OSCILL["exposure_time"] = expo_time
}'

#%IU%
# %MDESC% Calculate the motor speed according to the range parameter
# and the motor parameters.
#
def calculate_speed(exposure_time) '{
  local range
  local speed
  local nr_oscillations
  local steps
  local steps_per_degree


  if(exposure_time <= 0) exposure_time = 0.01;
  range = OSCILL["range"]
  nr_oscillations = 1 # Reset the number of oscillations to 1.

  # Calculate the number of motor steps for one half-oscillation
  steps_per_degree = motor_par (OSC_SUP["gonio_n"],"step_size")
  info "motor needs "steps_per_degree" steps/deg"
  steps = int (fabs (range) * steps_per_degree)
  info "steps per oscillation = "steps

  # Strange feature of the VCT6: if external start is used the count number
  # must be not be even, otherwise a miscounting in the range -1 to -127 occurres
  if (steps % 2 == 0) steps++
  info "steps per oscillation set to "steps" (VCT6 odd feature)"
  range = (range > 0 ? 1 : -1) * steps / steps_per_degree
  info "oscillation range set to "range" deg"

  # Calculate the stady-state speed needed of the rotation motor [steps/s]
  speed = steps*nr_oscillations/exposure_time
  info "required speed = "speed" steps/s"

  return speed
}'

#%UU%
#%MDESC%Set image parameters, detector distance and wavelength
def MM_mar_imagepar(__ccd_u, start_angle, stop_angle, exp_time, range, n_phi_osc, d_s_dist, \
    wavelength) '{
	local _i 

	image_par(__ccd_u, "hw_par", \
	  sprintf("opt_phi_start=%f opt_phi_stop=%f opt_expo_time=%f osc_range=%f opt_phi_osci=%f opt_distance=%f opt_wavelength=%f", \
		start_angle, stop_angle, exp_time, range, n_phi_osc, d_s_dist, wavelength))
}'


#%MACROS%
#%IMACROS%
#%AUTHOR% by Friedrich Schotte, Manu Perez, Holger Witsch with bits from L.
#Claustre and D. Fernandez last changes March 2001