esrf

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

#%TITLE% BCDU8.MAC 
#
#%NAME%
# Macros to control the 8-channel Bunch Clock Delay Unit (BCDU8).
#
#%DESCRIPTION%
#
# This macroset implements macromotors to drive the variuous
# programmable delays in a BCDU8 module. It also allows to open a
# dedicated window to dialogate interactively with the module, check
# and/or set the configuration and give the possibility of executing
# directly any BCDU8 command.
# The macro %B%bcdu8%B% opens the interactive window. The macros
# %B%bcdu_rflock%B% and %B%bcdu8_sync%B% perform initialisation and
# synchronisation actions. The configuration of macro motors is explained
# below . 
#
#%DL%
#%DT%Configuring macro motors %DD%
# %DL%
#  %DT% 1)
#       You must have an entry in "MOTORS" table for each BCDU8 module
#       %DL%
#         %DT% -
#         The "DEVICE" field must be set to string "bcdu8"
#         %DT% -
#         The "ADDR" must be set to the module hostname.
#         The communication port can optionaly be specified also 
#         (ex: "bcdu8id261")
#         %DT% -
#         The "NUM" should be set to 8
#         %DT% -
#         The "TYPE" field must be set to "Macro Motors"
#       %XDL%
#  %DT% 2) 
#       For each delay value to control you must define a motor with:
#       %DL%
#         %DT% -
#         The "Controller" field set to "MAC_MOT"
#         %DT% -
#         The "Unit" field, numbered from 0,
#         set to MOTORS entry
#         %DT% -
#         The "Module" field, specify the delay value to control:
#         %DL%
#             %DT% - 1: 
#             coarse delay, unit is RF clock cycles
#             %DT% - 2: 
#             fine   delay, unit is RF clock cycles
#             %DT% - 3: 
#             global delay, the channel field is not used
#         %XDL% 
#         %DT% -
#         The "Chan" field, numbered from 1 to 8, 
#         must be set to the particular BCDU8 channel
#         %DT% -
#         The "Backlash" value set to 0
#       %XDL%
#  %DT% 3)
#       Extra "Custom Parameters":
#       %DL%
#         %DT% "user_unit"%DD%
#         Specifies the time unit used by the macro motor,
#         possible values are "ps" "ns" "us" "ms" "s" "#".
#         If the parameter is not set, then the unit is RF clock cycles.
#       %XDL%
#
#  %DT% 4)
#       Extra motor_par() parameters: 
#       %DL%
#         %DT% motor_par(motor,"ramp_dd",delay)%DD%
#         %DT% motor_par(motor,"ramp_dt",time)%DD%
# Instead of changing the delay values instantaneously, macromotors may vary 
# the channel delays progressively by approximating the variation by a 
# stepping ramp. 
# The amplitud of each step is given by ramp_dd, while the minimum duration 
# of each step is ramp_dt in seconds.
# In order to activate this special ramping mode, both parameters must 
# be specified.
#       %XDL%
#
#
#%XDL%
#%END%
#
#
#%DEPENDENCIES%
#  These macros make use of the following macro sets:
#  %UL%
#  %LI%deepdevice.mac
#  %XUL%
#%END%
#


#
# --------------------------- global definitions ---------------------------
#

def dprint    'print  "BCDU8: "'
def dprinterr 'dprint "ERROR: "'
def dprintdbg 'if(BCDU8DEBUG) dprint'

constant BCDU8_COARSE 1
constant BCDU8_FINE   2
constant BCDU8_GLOBAL 3
constant BCDU8_UNITS  " ps ns us ms s # "
#
#
#
need deepdevice

#
# ----------------------- MACRO MOTOR implementation -----------------------
#

#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec after reading the config file
#
def bcdu8_config(num,type,p1,p2,p3) '{
 local ans
 local dev
 local cmd
 local typ
 local cha
 local uni


 #
 # p1==controller index p2==number of motors supported
 #
 if(type=="ctrl") {
   if(whatis("bcdu8_ADDR") & 0x08000000) {
     dprinterr "Missing \"ADDR\" field, must be set to hostname"
     return ".error." 
   }

   # try to connect to the device and check its type
   if(!deepdev_add(bcdu8_ADDR,bcdu8_ADDR,3,"BCDU8")) {
     dprinterr "unusable controller \""bcdu8_ADDR"\""
     return ".error." 
   }

   dprintdbg "using controller: \""bcdu8_ADDR"\""
 }




 #
 # p1==unit p2==module p3==channel
 #
 if(type=="mot") {
   dprintdbg "configuring motor: \""motor_mne(num)"\""

   # check parameter type to control
   if(p2 == BCDU8_COARSE) {
     dprintdbg "    type   : coarse delay"
   }
   else if(p2 == BCDU8_FINE) {
     dprintdbg "    type   : fine delay"
   }
   else if(p2 == BCDU8_GLOBAL) {
     dprintdbg "    type   : global delay"
   }
   else {
     dprinterr "unsupported parameter type, hint: set \"module\" field"
     return ".error."
   }

   # check channel to use
   if((p2 != BCDU8_GLOBAL) && ((p3 < 1) || (p3 > 8))) {
     dprinterr "invalid channel, hint: to be set from 1 to 8"
     return ".error."
   }

   # check fine delay channels
   if((p2 == BCDU8_FINE) && (p3 != 1) && (p3 != 2)) {
     dprinterr "invalid fine delay channel, hint: to be set to 1 or 2"
     return ".error."
   }
   dprintdbg "    channel: O"p3


   # check that the selected channel is configured
   dev = bcdu8_ADDR
   typ = p2
   cha = p3
   if(typ != BCDU8_GLOBAL) {
     cmd = sprintf("?CHAN O%d",cha)
     ans = deepdev_comm(dev,cmd)
     if(ans == DEEPDEV_ERRANSW) {
      dprinterr "unable to get channel config"
      return ".error."
     }
     dprintdbg "    config : "ans
     if(index(ans,"OFF")) {
      dprinterr "channel \"O"cha"\" not configured"
      return ".error."
     }
   }


   # at this point we have a valid motor configuration
   motor_par(num,"delay_mod",typ,"add")
   motor_par(num,"channel",  cha,"add")
   motor_par(num,"ramp_dd",    0,"add")
   motor_par(num,"ramp_dt",    0,"add")

   # check if unit will be other than RF clock cycles
   if(uni = motor_par(num,"user_unit")) {
     if(!index(BCDU8_UNITS, sprintf(" %s ",uni))) {
      dprinterr "invalid unit, hint: edit \"user_unit\" parameter in config"
      
      # do not forget to remove bad parameter
      motor_par(num,"user_unit",0)
      return ".error."
     }
     dprintdbg "    unit   : "uni
   }


   # check if requested delay is within limits
   bcdu8_update_limits(num)

 }

}'


#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec on motor_par()
#
def bcdu8_par(num,key,todo,p1) '{

 # minimum auto documentation
 if (key == "?") {
   if (todo == "get") {
     return "ramp_dd, ramp_dt, user_unit"
   }
 }

 if (key == "ramp_dd") {
   if (todo == "set") {
     # NOTE MP 2011/01/01: once implemented in the BCDU module
     # add here code to set the ramp inside the module
     dprintdbg "ramping delta changed: "p1
   }
 }

 if (key == "ramp_dt") {
   if (todo == "set") {
     dprintdbg "ramping dtime changed: "p1
   }
 }

}'


#%IU%(motor)
#%MDESC%
# MACRO MOTOR: 
# Update the motor software limits if needed
# 
def bcdu8_update_limits(num) '{
 local ans
 local dev
 local cmd
 local typ
 local cha
 local uni
 local lim_low
 local lim_high

 dev = motor_par(num,"address")
 cha = motor_par(num,"channel")
 typ = motor_par(num,"delay_mod")
 uni = motor_par(num,"user_unit") ; if(!uni) { uni="" }

 # NOTE MP 2011/02/28: only the fine delay limits are dynamic but
 # no optimization for the moment
 if(typ == BCDU8_COARSE) {
   cmd = sprintf("?DELAYLM O%d", cha)
 } else if(typ == BCDU8_FINE) {
   cmd = sprintf("?FDELAYLM O%d",cha)
 } else if(typ == BCDU8_GLOBAL) {
   cmd = sprintf("?GFDELAYLM")
 }


 # specify the unit wanted for the answer
 cmd = sprintf("%s %s", cmd, uni)

 ans = deepdev_comm_ack(dev,cmd)
 if(ans == DEEPDEV_ERRANSW) {
  dprinterr "unable to get limits"
  return ".error."
 }

 if(sscanf(ans,"%g %g",lim_low, lim_high) != 2) {
  dprinterr "wrong limits format"
  return ".error."
 }
 dprintdbg "lim_high: "lim_high
 dprintdbg "lim_low : "lim_low 

 if((get_lim(num,1)  != lim_high) || (get_lim(num,-1) != lim_low)) {
   dprintdbg "limits  : changed" 
   set_lim(num, lim_low, lim_high)
 }
}'


#%IU%
#%MDESC%
# MACRO MOTOR: 
# Called by spec on motor operation.
# 
def bcdu8_cmd(num,key,p1,p2) '{
 local devcmd
 local chacmd
 local ans
 local dev
 local cmd
 local typ
 local cha
 local uni


 #
 # Handle actions at controller level
 #
 if(num == "..") { 
  return
 }



 #
 # Handle actions at motor level
 #
 dev = motor_par(num,"address")
 cha = motor_par(num,"channel")
 typ = motor_par(num,"delay_mod")
 uni = motor_par(num,"user_unit") ; if(!uni) { uni="" }

 # prepare channel string
 chacmd = (typ == BCDU8_GLOBAL)?"":sprintf("O%d",cha)

 #
 # return the current motor position in user unit
 #
 if (key == "position") {

   if(typ == BCDU8_GLOBAL) {
    devcmd = "?GFDELAY"
   } else {
    devcmd = "?DELAY"
   }

   # specify the unit wanted for the answer
   cmd = sprintf("%s %s %s", devcmd, chacmd, uni)

   ans = deepdev_comm(dev,cmd)
   if(ans == DEEPDEV_ERRANSW) {
    dprinterr "unable to get delay value"
    return ".error."
   }

   # normal end
   dprintdbg "delay   : "ans
   return ans
 }


 #
 # start a motion (p1==abs pos, p2==rel pos, with pos in mm or deg)
 #
 if (key == "start_one") {
   local i
   local p0
   local ps
   local pp



   # check if requested delay is within limits
   bcdu8_update_limits(num)


   # request delay modification
   if(typ == BCDU8_COARSE) {
     devcmd = "DELAY"
   } else if(typ == BCDU8_FINE) {
     devcmd = "FDELAY"
   } else if(typ == BCDU8_GLOBAL) {
     devcmd = "GFDELAY"
   }


   # check if ramping well configured
   ramp_dt = fabs(motor_par(num,"ramp_dt"))
   ramp_dd = fabs(motor_par(num,"ramp_dd"))
   if(ramp_dt || ramp_dd) {
     if(!ramp_dt && ramp_dd) {
       dprinterr "missing motor_par("motor_mne(num)",\"ramp_dt\",value)"
       return ".error."
     }
     if(!ramp_dd && ramp_dt) {
       dprinterr "missing motor_par("motor_mne(num)",\"ramp_dd\",value)"
       return ".error."
     }
  

     # check if ramping is needed
     if(fabs(p2) > ramp_dd) {
       p0 = p1 - p2
       ps = p2/fabs(p2)
       dprintdbg "ramping"
       for(i=0;i<int(fabs(p2)/ramp_dd);i++) {
         pp  = p0 + i*ramp_dd*ps
         dprintdbg "move to : "pp

         # special case of "fine delay steps" unit
         if(uni == "#") {
           cmd = sprintf("%s %s %s%d", devcmd, chacmd, uni, int(pp))
         } else {
           cmd = sprintf("%s %s %f%s", devcmd, chacmd, pp, uni)
         }
         ans = deepdev_comm_ack(dev,cmd)
         if(ans == DEEPDEV_ERRANSW) {
           dprinterr "unable to change delay"
           return ".error."
         }
         sleep(ramp_dt)
       }
     }
   }

   # trigger change
   dprintdbg "move to : "p1

   # special case of "fine delay steps" unit
   if(uni == "#") {
     cmd = sprintf("%s %s %s%d", devcmd, chacmd, uni, int(p1))
   } else {
     cmd = sprintf("%s %s %f%s", devcmd, chacmd, p1, uni)
   }

   ans = deepdev_comm_ack(dev,cmd)
   if(ans == DEEPDEV_ERRANSW) {
     dprinterr "unable to change delay"
     return ".error."
   }


   # For coarse motion update position+limits for each fine 
   # motor with the same channel (cf avoid PR+NQ in config)
   # TODO MP: do the motor list scan only once
   if(typ == BCDU8_COARSE) {
     local i
     local our_device_id our_address
     local our_channel
     local pos
    
     our_device_id = motor_par(num, "device_id")
     our_address   = motor_par(num, "address")
     our_channel   = motor_par(num, "channel")
     for(i=0;i<MOTORS;i++) {
        if(i == num) { continue}
        if(motor_par(i,"device_id") != our_device_id) { continue }
        if(motor_par(i,"address")   != our_address)   { continue }
        if(motor_par(i,"channel")   != our_channel)   { continue }
        if(motor_par(i,"module")    != BCDU8_FINE)    { continue }

        # Update also the position silently
        # NOTE MP: the read_motors() will update position for all motors,
        # no other way, except the NQ flag in config file
        read_motors(0x06, i)

        dprintdbg "fine motor to update: "motor_mne(i)
        bcdu8_update_limits(i)
     }
   }

   # normal end of start_one
   return
 }

}'





#
# ---------------------------- user macros -----------------------------
#

#%UU% [hostname]
#%MDESC%
# Launch an interactive command line interface in an external xterm window.
# This allows for instance to check the configuration of the module with the
# ?CONFIG query.
#
def bcdu8 '{
  local ndev
  local dev
  local cmd
  

  dev = $#?"$1":""
 
  if(!dev && (ndev=list_n(DEEP_CONF))>=1) {
    if(ndev>1) {
      dprint "oops, not implemented yet, hint: give hostname"
    }
    i = 1
    name = DEEP_CONF[i]
    dev  = DEEP_CONF[name]["comdev"]
  }

  cmd  = sprintf("deep %s", dev)
  eval(cmd)
}'





#%UU% [hostname]
#%MDESC%
# Initialize RF clock locking. To be used if the BCDU8 RFMODE is set
# to USER and the RF clock lock is lost.
#
def bcdu8_rflock '{
  local dev
  local ans

  dev = deepdev_setdefault("BCDU8","$*")
  if (dev) {
    ans = deepdev_comm_ack(dev,"RFLOCK")
    if(ans == DEEPDEV_ERRANSW) {
      dprinterr "initializing RF clock"
    }
  }
}'

#%UU% [hostname]
#%MDESC%
# Try to synchronize the BCDU8 channels with external signal.
# The module waits for an external pulse at the 'SYNC IN' input.
# Automatically gives up after a while.
#
def bcdu8_sync '{
  local dev
  local ans
  local cmd
  local tbeg

  dev = deepdev_setdefault("BCDU8","$*")
  if (dev) {
    ans  = deepdev_comm_ack(dev,"SYNC")
    if(ans == DEEPDEV_ERRANSW) {
      dprinterr "synchronizing with external signal"
    } else {
      cmd = sprintf("print \"aborted !!\" ; \
            deepdev_comm(\"%s\",\"SYNC CLEAR\")",dev) 
      cdef("cleanup_once",cmd)

      print "waiting for external signal synchronization..."
      tbeg = time()
      while(deepdev_comm_ack(dev,"?SYNC") == "WAIT") {
        if((time()-tbeg) > 2) {
          print "timeout !!"
          eval(cmd)
          break
        }
        sleep(0.1)
      }
    }
  }

}'


#%UU%
#%MDESC%
# Switches debug mode 
#
def bcdu8debug '{
   DEBUG ^= 0x80000000
   dprint "debug mode is now:", BCDU8DEBUG? "On":"Off"
}'
def BCDU8DEBUG '(DEBUG & 0x80000000)'


#%MACROS%
#%IMACROS%
#%AUTHOR% PF+MP BLISS (Original 2011/Feb).
#%BR%$Revision: 1.1 $ / $Date: 2012/03/26 12:15:08 $
#%TOC%