esrf

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

"""
#%TITLE% PSEUDO.MAC 
#%NAME%
#  Utility macros to define pseudomotors
#%DESCRIPTION%
# Pseudo motors are a commonly used feature to implement motors in spec,
# which do not have actual hardware connected. An example could be
# the gap and offset motors for slits.
# In some cases, where you would like to use real hardware without 
# writing c-code, you could also use the concept of pseudo motors.
# With the feature of %B%Macro\0Hardware%B% (type help mac_hdw at the spec
# prompt), there is no need to create new pseudo motors/counters. Please use
# the macro hardware feature for new implementations.
#%EXAMPLE%
#%DL%
#%DT% pseudodef piezo1 none piezo_move piezo_getangles none none none none 3 0
#%DD%     Define a pseudo motor piezo1. Each time the user tries to move the
#     piezo piezo_move is called. If the user wants to know the position
#     of the piezo piezo_getangles is called. No action is taken on set
#     ^c. The user parameters were 3 0 and could represent the channel
#     on the corresponding DAC card.
# %DT% cpseudodef cnttim1 none cnt_move piezo_getangles none none none none 3 0
# %DD%
#     Define a pseudo motor piezo1. Each time the user tries to move the
#     piezo piezo_move is called. If the user wants to know the position
#     of the piezo piezo_getangles is called. No action is taken on set
#     ^c. The user parameters were 3 0 and could represent the channel
#     on the corresponding DAC card.
#%XDL%
#
#%INTERNALS%
# As updated moves and counting used the c-function wait directly, these 
# waits had to be replaced by the new waitcheckcount ,... macros.
# These changes have been made:
#%DL%  
#%DT% IN UTIL.MAC %DD%
#     config user_waitall user_waitmove user_waitcount 
#%XDL%
#%ATTENTION%
# This version of pseudo.mac is to be used with SPEC version 6.00.07 and later!
# Many ESRF specific macros like mv, ct have been eliminated by improvements
# made to the standard macros in standard.mac. 
# The effort to keep all ESRF functionality was quite big, but we need to be
# vigilant to eliminate errors. All old code has been kept in this file as
# comments, which should allow tracking errors.
#%END%
"""

# Changes to standard SPEC macros 
# -------------------------------
#
global USER_WAITINGCOUNT USER_WAITINGMOVE
def chk_count '(USER_WAITINGCOUNT=(wait(0x22)||user_counters_run()))'
def chk_move '(USER_WAITINGMOVE=(wait(0x21)||user_motors_run()))'
def user_counters_run() '{user_countersrun; return(0)}'
def user_motors_run() '{user_motorsrun; return(0)}'


def chk_beam 'if (chk_beamf()) {break} '
def chk_beamf() '{
  _chk_beamc
  return 1
}'
def _chk_beamc ''

# ESRF uses traditionally its own names for the user_ functions with these
# cdef both should work
cdef ("user_precount", "user_prepcount; ", "pseudo")
cdef ("user_getangles", "user_getpangles; ", "pseudo")
cdef ("user_premove", "user_checkall; ", "pseudo")
cdef ("user_postmove", "user_moveall; ", "pseudo")
cdef ("user_cleanup", "user_cleanup2; ", "pseudo")
cdef ("cleanup1", "user_cleanup; ", "pseudo") #This definition could be erased !
def user_setpos '{local motor2set position2set; 
motor2set = $1 ; position2set = $2 ; user_set }'


# user_wait is defined to poll pseudo motors and pseudo counters
def user_waitall 'user_waitcount; user_waitmove; user_wait2'

global MOTORSPOLLTIME
MOTORSPOLLTIME=.05

def user_waitmove '
    while (chk_move) {
      user_pollmove
      sleep (MOTORSPOLLTIME)
    }
'
global COUNTERSPOLLTIME
COUNTERSPOLLTIME=.01

def user_waitcount '
  while (chk_count) {
    user_pollcounts
    sleep (COUNTERSPOLLTIME)
  }
'

# In normal move commands, wait until motor stops and call user macro 
def move_poll '
  waitmove
  user_finished
  '

cdef("user_finished", "user_finished1; ", "pseudo")

# -------------------- Move multiple motors with std move commands -----------

## the position can an arbitrary expression only for the first motor.
#def mv   'if ($# == 2){_mv  $*; move_poll}      else _mmv(0x00, "$*")'
#def umv  'if ($# == 2){_mv  $*; _update("$1") } else _mmv(0x02, "$*")'
#def mvr  'if ($# == 2){_mvr $*; move_poll }     else _mmv(0x01, "$*")'
#def umvr 'if ($# == 2){_mvr $*; _update("$1") } else _mmv(0x03, "$*") '
#def mvd  'if ($# == 2){_mvd $*; move_poll }     else _mmv(0x04, "$*")'
#def umvd 'if ($# == 2){_mvd $*; _update("$1")}  else _mmv(0x06, "$*")'
#
#def _mmv(mode, args) '{
#   local i N aux mot macname posl
#   N = split(args, aux)
#   if (mode & 0x01) {
#      macname = "mvr"; posl="relative-"
#   } else if (mode & 0x04) {
#      macname = "mvd"; posl="dial-"
#   } else {
#      macname = "mv"; posl=""
#   }
#   macname = sprintf("%s%s", (mode & 0x02)?"u":"", macname)
#   if (!N || N%2){
#      printf("Usage:  %s motor %sposition [[motor %sposition] ...]", macname, posl, posl)
#      exit
#   }
#
#   for(i = 0; i < N; i += 2){
#      mot[i] = motor_num(aux[i])
#      if (mot[i] < 0){
#         print "Invalid motor mnemonic: ", aux[i]
#         exit
#      }
#      if (aux[i+1]+0 != aux[i+1]){
#         print "Invalid motor position: ", aux[i+1]
#         exit
#      }
#   }
#   waitmove; get_angles
#   mlist = ""
#   for(i = 0; i < N; i += 2) {
#      if (mode & 0x01)
#         A[mot[i]] += aux[i+1]
#      else if (mode & 0x04)
#         A[mot[i]] = user(mot[i], aux[i+1])
#      else
#         A[mot[i]] = aux[i+1]
#      mlist = sprintf("%s %s", mlist, aux[i])
#   }
#   move_em
#   if (mode & 0x02){
#      _update(mlist)
#   } else {
#      move_poll
#   }
#}'
#
#def _ord_move 'move_em; move_poll; get_angles; calcHKL'
#def _upd_move '{
#   local mlist
#
#   move_em
#   mlist = ""
#   if (_stype&1) {
#      for (i=0;i<_nm;i++)
#         mlist = sprintf("%s %s", mlist, motor_name(_m[i]))
#      _update(mlist, 9, -1)
#   } else if (_stype&2) {
#      _update("H K L", 10, -1)
#   }
#}'
#

# Redefine update
#   a.) have maximum number of columns on the screen (even on rescale)
#   b.) guess a nice output format
#   c.) have a common update function to poll user defined counters/motors


#def _upd_count '
#   if ($1) for (;;) {
#      local clist
#
#      printf("\r%3d %s     <counting ...>", NPTS,VPRNT)
#      count_em $1
#      for (i = 0, clist = ""; i < COUNTERS; i++) {
#         if (cnt_name(i) != "unused")
#            clist = sprintf("%s %s",clist,cnt_mne(i))
#      }
#      _update(clist, 0, 1)
#      chk_beam
#   }
#'

#def _update1 '_update("$*", 10)'
#def _update2 '_update("$*", 10)'
#def _update3 '_update("$*", 10)'
#def _update4 '_update("$*", 10)'
#def _update2hkl '_update("$* H K L", 9)'
#def _update3hkl '_update("$* H K L", 9)'
#def _update4hkl '_update("$* H K L", 9)'
#def _update5hkl '_update("$* H K L", 9)'
#def _update6hkl '_update("$* H K L", 9)'

#global NOENTERKEY

#def _update(mlist, width, scanning) '{
#   local devarr devname iscounter
#   local i j n update cols rows oldrows
#   local tnext polltime labeldone
#   local plines fmtth absc
#
#   n = split(mlist, devarr)
#   for (i = 0; i < n; i++) {
#      iscounter[i] = ((c = cnt_num(devarr[i])) >= 0)
#      if (iscounter[i]) {
#         devarr[i] = c
#         devname[i] = cnt_name(devarr[i])
#         USER_WAITINGCOUNT = 1
#      } else {
#         c = motor_num(devarr[i])
#         devname[i] = (c >= 0) ? motor_name(c) : devarr[i]
#         devarr[i] = c
#         USER_WAITINGMOVE = 1
#      }
#   }
#
#   if (width <=0) width = 12
#   fmtth = exp10(width-2) 
#
#   while(USER_WAITINGMOVE || USER_WAITINGCOUNT) {
#      update = (time() > tnext)
#
#      polltime = 0
#      if (USER_WAITINGMOVE) {
#         if (!chk_move) {
#            user_finished
#            update = 1
#         }
#         if (update) {
#            get_angles
#            calcHKL
#         } else {
#            user_pollmove
#            if (!polltime || MOTORSPOLLTIME < polltime)
#               polltime = MOTORSPOLLTIME
#         }
#      }
#      if (USER_WAITINGCOUNT) {
#         if (!chk_count) update = 1
#         if (update) {
#            get_counts
#         } else {
#            user_pollcounts
#            if (!polltime || COUNTERSPOLLTIME < polltime)
#               polltime = COUNTERSPOLLTIME
#         }
#      }
#      if (update) {
#         tty_cntl("resized?")
#         cols = int((COLS-1)/width)
#         rows = int((n-1)/cols) + 1
#         if (scanning == -1)
#            printf("\r%3d ", NPTS)
#         else if (plines > 0) {
#            tty_move(1000, 1000+plines)
#            if (rows < oldrows) {
#               printf("\r")
#               tty_cntl("cd")
#            }
#            plines = 0
#         }
#         oldrows = rows
#         plines = 0
#         for (i = 0; i < n; i++) {
#            if (!labeldone && !(i % cols) && scanning != -1) {
#               if (scanning || i != 0) {
#                  if (i != 0) tty_cntl("ce")
#                  printf("\n")
#                  plines++;
#               }
#               tty_cntl("ce")
#               printf("\n")
#               for (j = 0; j < cols && i+j < n; j++) {
#                 printf("%*s", width, devname[i+j])
#               }
#               tty_cntl("ce")
#               printf("\n")
## JK CHANGE : Global variable NOENTERKEY set to 0 to be able to press 
##   Enter to scroll up in updated moves.
#	       if (!scanning && !NOENTERKEY)
#                 labeldone = 1
#               else
#                 plines += 2
#            }
#            if (devarr[i] >= 0)
#               c = iscounter[i]?S[devarr[i]]:A[devarr[i]]
#            else if (devname[i] == "H")
#               c = H
#            else if (devname[i] == "K")
#               c = K
#            else if (devname[i] == "L")
#               c = L
#            else
#               c = 0
#
#            if (tty_cntl("resized?")) break;
#            if ((absc = fabs(c)) < 1 && absc >= 0.001)
#               printf("% *.*f", width, width-3, c)
#            else
#               printf("% *.*G", width, width-2-4*(absc<0.001 || absc>fmtth), c)
#         }
#         tty_cntl("ce")
#         printf("\r");
#         tnext = time() + UPDATE
#      } else 
#         sleep(polltime)
#   }
#   if (!scanning)
#      print
#   else if (plines > 0) {
#      tty_move(1000, 1000+plines-1)
#      tty_cntl("cd")
#      tty_move(1000, 1001)
#   }
#}'

# Redefine count macros: 
#     have user_handlecounts to save MCA spectra
#     _cols = 0 to now that we are in ct

#def ct '{
#   _cols = 0
#   waitmove
#   count_em $*
#   rdef cleanup \'
#        undef cleanup
#        onp; show_cnts; offp
#        user_handlecounts
#   \'
#   waitcount
#   undef cleanup
#   onp; show_cnts; offp
#   user_handlecounts
#}'
cdef("user_ct", "user_handlecounts;", "user_handlecounts")

#def uct '{
#   local clist
#
#   _cols = 0
#   waitmove
#   count_em $*
#   for (i = 0, _c2 = ""; i < COUNTERS; i++) {
#      if (cnt_name(i) != "unused")
#         _c2 = sprintf("%s %s",_c2,cnt_mne(i))
#   }
#   rdef cleanup \'
#         undef cleanup
#         _update(_c2)
#         user_handlecounts
#   \'
#   _update(_c2)
#   undef cleanup
#   user_handlecounts
#}'

#----------------------------END CHANGES ------------------------------- 

#%UU%  <motormne> <checkmacro> <movemacro> <getanglemacro> 
# <cleanupmacro> < config> <setdial> <finished> <userdata1> <userdata2>
#%MDESC%
#        
#        This macro defines a new pseudo-motor. The motor has to be configured
#        and the controller set to NONE. Whenever a move_all is called the
#        userdefined macro <checkmacro> will be called before and the 
#	<movemacro> afterwards. 
#        These macros will get three parameters. The first parameter will
#        be the motor mnemonic, the second the string userdata1 and the third 
#	the string userdata2.
#        The macro is responsible to move the motor to the position
#        given in the global variable A[motornumber]. The userdata can be used
#        to give some information to the macro (for example the device server
#        name).
# 	In the same way the <getanglemacro> will have to fill the A[]
#	array with the actual position of the motor.
#	The <cleanup macro> will be called if the user hits ^c or some
#	error condition occures (A limit is hit for example). The <config>
#	macro will be called after the user called config. 
#	The <setdial> and <finished> macros are not yet fully implemented
#	and will server future additions.
#	If you do not want to wait in your macros until your move has 
#	finished (and then allow updated move commands), you can
#	hook code in user_motorsrun that executes `return(1)' in case your
#	move is not yet finished. While waiting your getangles macro will
#	be called.
def pseudodef '
{
#defines pseudomotor
#pseudodef motorname check_all move_all getangles cleanup user1 user2
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
__p1="$1"; __p2="$2"; __p3="$3"; __p4="$4"; __p5="$5"; __p6="$6";
__p7="$7"; __p8="$8"; __p9="$9"; __p10="$10"; __p11="$11"
__p0=$1
_pseudodef
}
'

#%UU%  <var-name>
#%MDESC%

#        same as pseudodef , the input parameter is just a variable name
def pseudosdef '
{
#defines pseudomotor
#pseudosdef stringvariable
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
{
local xx i
split ($1,xx)
__p1=xx[0]; __p2=xx[1]; __p3=xx[2]; __p4=xx[3]; __p5=xx[4]; __p6=xx[5];
__p7=xx[6]; __p8=xx[7]; __p9=xx[8]; __p10=xx[9]; __p11=xx[10]
_pseudodef
}
}
'

def _pseudodef '
{
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
local flag 
flag = 0
if (substr(__p1,1,1)=="_") {
  flag = 0 #If a motor name starts with underscore then this is a hook to
  }
else {
  flag = 1
  }

pseudo_hook "user_checkall" __p2 __p1 flag __p9 __p10
pseudo_hook "user_moveall" __p3 __p1 flag __p9 __p10
pseudo_hook "user_getpangles" __p4 __p1 flag __p9 __p10
pseudo_hook "user_cleanup1" __p5 __p1 flag __p9 __p10
pseudo_hook "user_config" __p6 __p1 flag __p9 __p10
pseudo_hook "user_setdial" __p7 __p1 flag __p9 __p10
pseudo_hook "user_finished1" __p8 __p1 flag __p9 __p10

}
'

def pseudo_hook '
{
local rstring
if (($2 != "") && ($2 != "none") && ($2 != "NONE")) {
  rstring = sprintf ("%s %s %s %s; ",$2,$3,$5,$6)
  cdef("$1",rstring,$3,$4)
  }
}
'

#%UU%  [motormne] : A pseudomotor can be deleted with this macro
#%MDESC%

def pseudodel 'cdef("","","$1","delete")'
def pseudosdel 'cdef("","",$1,"delete")'
def pseudoshow 'cdef("","","","?")'

def pseudohack '
pseudomotformula
'

#%UU% <formula> <mot-to-calc> <depend-mot1> <depend-mot2> ...
#%MDESC%
# This macro can be used to define simple pseudo motors very easily.
# A pseudomotor like tlaue := piezo/38 could be implemented
# by %BR% pseudomotformula "A[piezo]/38" laue piezo %BR%
# and pseudomotformula "A[tlaue]*38" piezo laue
def pseudomotformula '
global PSEUDO_OLDPOS
rdef pseudomform_$2 \' { local _p iu
  _p[0]="$3";_p[1]="$4";_p[2]="$5";_p[3]="$6";_p[4]="$7";_p[5]="$8";_p[6]="$9"
  for (iu=0;iu<($#-2);iu++) if (PSEUDO_OLDPOS[motor_num(_p[iu])] != A[motor_num(_p[iu])]) break
  if (iu < ($#-2)) A[$2] = $1
} \'
rdef pseudomsave \'for (iu=0;iu<MOTORS;iu++) {PSEUDO_OLDPOS[iu]=A[iu]};\'
cdef("user_checkall","pseudomform_$2 ;","$2",0x21)
cdef("user_getpangles","pseudomsave ;" ,"$2",0x21)
'

#  %DL%
# %XDL%

#%UU%  <counter_mne> <precount> <postcount> <getcounts> <cleanup> <config> <userdata1> <userdata2>
#%MDESC%
#     This macro defines a new pseudo-counter. The counter has to be 
#     configured and the controller set to NONE. Whenever a count_em
#     macro is used (all the time spec wants to start a counter/timer)
#     <precount> is called before starting the timer and <postcount>
#     after.
#     These macros will get three parameters. The first parameter will
#     be the counter number, the second the string userdata1 and the third 
#     the string userdata2.
#     Normally the <precount> or <postcount> macro will start your timer.
#     In <getcounts> you have to fill the S[] array with the counts read
#     from the counter/timer. S[sec := 0] should be the time in seconds.
#     The userdata can be used to give some information to the 
#     macro (for example the device server name).
#     The <cleanup> macro will be called if the user hits ^c or some
#     error condition occures (A limit is hit for example). The <config>
#     macro will be called after the user called config. 
#     If you do not want to wait in your macros until counting has 
#     finished (and in this allow updated count commands), you can
#     hook code in user_countersrun that executes `return(1)' in case your
#     count is not yet finished. While waiting your <pollcounts> macro will
#     be called.
def cpseudodef '
{
#defines pseudocounter
#cpseudodef countername precount postcount getcounts cleanup config user1 user2
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
__p1="$1"; __p2="$2"; __p3="$3"; __p4="$4"; __p5="$5"; __p6="$6"; __p7="$7"
__p8="$8"; __p9="$9"; __p10="$10" ; __p11="$11";  __p0=$1
_cpseudodef
}
'

#%UU%  <var-name>
#%MDESC%
#        same as cpseudodef , the input parameter is just a variable name
def cpseudosdef '
{
#defines pseudocounter
#cpseudosdef stringvariable
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
{
local xx i
split ($1,xx)
__p1=xx[0]; __p2=xx[1]; __p3=xx[2]; __p4=xx[3]; __p5=xx[4]; __p6=xx[5];
__p7=xx[6]; __p8=xx[7]; __p9=xx[8]; __p10=xx[9]; __p11=xx[10]
for (i=0,__p0=0;i<COUNTERS;i++) { 
  if (cnt_mne(i) == __p1) {
    __p0=i
    }
  }
}
_cpseudodef
}
'
 
def _cpseudodef '
{
global __p0 __p1 __p2 __p3 __p4 __p5 __p6 __p7 __p8 __p9 __p10 __p11
local flag 
flag = 0
if (substr(__p1,1,1)=="_") {
  flag = 0 #If a counter name starts with underscore then def in any case
  }
else {
  flag = 2
  }

pseudo_hook "user_prepcount" __p2 __p1 flag __p7 __p8
pseudo_hook "user_postcount" __p3 __p1 flag __p7 __p8
pseudo_hook "user_getcounts" __p4 __p1 flag __p7 __p8 
pseudo_hook "user_cleanup2" __p5 __p1 flag __p7 __p8
pseudo_hook "user_config" __p6 __p1 flag __p7 __p8
}
'

#%UU%  [countermne] 
#%MDESC%
# A pseudomotor/counter can be deleted with this macro
def cpseudodel 'pseudodel $* ; '
def cpseudosdel 'pseudosdel $* ; '
def cpseudoshow 'pseudoshow $* ; '

def pseudo_define_empty_stubs '
if (!(whatis("user_wait2")&2)) rdef user_wait2 ""
if (!(whatis("user_cleanup2")&2)) rdef user_cleanup2 ""
if (!(whatis("user_finished1")&2)) rdef user_finished1 ""
if (!(whatis("user_set")&2)) rdef user_set ""
if (!(whatis("user_checkall")&2)) rdef user_checkall ""
if (!(whatis("user_moveall")&2)) rdef user_moveall ""
if (!(whatis("user_getpangles")&2)) rdef user_getpangles ""
if (!(whatis("user_setdial")&2)) rdef user_setdial ""
if (!(whatis("user_finished")&2)) rdef user_finished ""
if (!(whatis("user_prepcount")&2)) rdef user_prepcount ""
if (!(whatis("user_pollcounts")&2)) rdef user_pollcounts ""
if (!(whatis("user_pollmove")&2)) rdef user_pollmove ""
if (!(whatis("user_config") & 2)) rdef user_config ""
if (!(whatis("user_countersrun") & 2)) rdef user_countersrun ""
if (!(whatis("user_motorsrun") & 2)) rdef user_motorsrun ""
if (!(whatis("user_handlecounts") & 2)) rdef user_handlecounts ""
'

pseudo_define_empty_stubs

#%MACROS%
#%IMACROS%
#%ATTENTION%
#%UL%
# %LI% Hitting ^c (so ^c twice) while the cleanup macro is running will undefine
#  the cleanup macro (This will not erase your macro, it will just not be
#  executed). Next time a motor is moved it is redefined again. 
# %LI% If you want SPEC to check for software limits, all the motor
#  positions must be calculated before the move. (If you calculate the
#  gap only in getangles, SPEC will not check for limits on the gap)
# %XUL%
#%DEPENDENCIES% 
#  The file pseudo.mac has to be loaded.		! This is done in startup
#%AUTHOR%
# JK 3.94
# taken out all fprintf(PRINTER , Holger, 9.12.2004
#%TOC%