#%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 # writting c-code, you could also use the concept of pseudo motors. # Be warned that the full implementation can be done more robust # directly inside SPEC. #%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% # waitcheckcount waitcheckmove waitmove waitcount waitmove w # config user_waitall user_waitcount user_ctwaitmove # global USER_MOTORSRUN USER_COUNTERSRUN #%DT% IN COUNT.MAC %DD% # uct #%DT% IN MOTOR.MAC %DD% # uwm _update1 _update2 _update4 _update5hkl _update4hkl _update2hkl # We do not use: PCAII.MAC, QELS.MAC #%DT% IN SCANS1.MAC %DD% # _upd_move _upd_count # For the moment I define this stuff in move_poll (not very reliable) #%DT% IN MOTOR.MAC %DD% # umv umvr _ord_move move_poll user_finished #%DT% Some more changes to have the hooks for the pseudo stuff %DD% # move_em user_premoveall user_postmoveall count_em #%XDL% #%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]) } if (PRINTER != "") { fprintf(PRINTER, "\n%s", macname) for(i = 0; i < N; i += 2) fprintf(PRINTER, " %s %g", aux[i], aux[i+1]) fprintf(PRINTER, "\n") } 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 ", 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 }' 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% # < config> #%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 will be called before and the # 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 will have to fill the A[] # array with the actual position of the motor. # The will be called if the user hits ^c or some # error condition occures (A limit is hit for example). The # macro will be called after the user called config. # The and 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% #%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% ... #%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 #%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) # is called before starting the timer and # 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 or macro will start your timer. # In 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 macro will be called if the user hits ^c or some # error condition occures (A limit is hit for example). The # 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 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% #%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